[osgearth] 04/15: New upstream version 2.9~rc1+dfsg

Bas Couwenberg sebastic at debian.org
Sun Jan 7 14:40:58 UTC 2018


This is an automated email from the git hooks/post-receive script.

sebastic pushed a commit to branch experimental
in repository osgearth.

commit 9901b6e2eaa8fe48b3550d6637a5739ceff53dd3
Author: Bas Couwenberg <sebastic at xs4all.nl>
Date:   Sun Jan 7 10:13:47 2018 +0100

    New upstream version 2.9~rc1+dfsg
---
 .travis.yml                                        |    12 +-
 CMakeLists.txt                                     |   276 +-
 CMakeModules/FindExpat.cmake                       |    52 -
 CMakeModules/FindGDAL.cmake                        |    18 +-
 CMakeModules/FindGEOS.cmake                        |    15 +-
 CMakeModules/FindGLCORE.cmake                      |    36 +
 CMakeModules/FindJavaScriptCore.cmake              |    46 -
 CMakeModules/FindLevelDB.cmake                     |     5 +
 CMakeModules/FindLibNoise.cmake                    |    64 -
 CMakeModules/FindOSG.cmake                         |    14 +-
 CMakeModules/FindOpenGLES.cmake                    |    12 +
 CMakeModules/FindRocksDB.cmake                     |     2 +
 CMakeModules/FindV8.cmake                          |   245 -
 CMakeModules/ModuleInstall.cmake                   |    12 +-
 CMakeModules/OsgEarthMacroUtils.cmake              |   140 +-
 data/resources/textures_us/catalog.xml             |     4 +
 docs/source/about.rst                              |    41 +-
 docs/source/data.rst                               |    81 +-
 docs/source/developer/shader_composition.rst       |     4 +-
 docs/source/faq.rst                                |     8 +-
 docs/source/index.rst                              |     1 +
 docs/source/references/drivers/tile/tms.rst        |     6 +-
 docs/source/references/earthfile.rst               |    18 +-
 docs/source/references/envvars.rst                 |     2 +-
 docs/source/startup.rst                            |     2 +-
 docs/source/support.rst                            |    24 +
 docs/source/user/features.rst                      |     6 +-
 src/CMakeLists.txt                                 |    22 +-
 src/applications/CMakeLists.txt                    |    38 +-
 src/applications/osgearth_3pv/osgearth_3pv.cpp     |     4 +-
 .../osgearth_annotation/osgearth_annotation.cpp    |    37 +-
 src/applications/osgearth_atlas/osgearth_atlas.cpp |     6 +-
 .../osgearth_boundarygen/BoundaryUtil.cpp          |     2 +-
 .../osgearth_boundarygen/boundarygen.cpp           |     8 +-
 src/applications/osgearth_city/osgearth_city.cpp   |    94 +-
 src/applications/osgearth_clamp/osgearth_clamp.cpp |   178 -
 .../osgearth_clipplane/osgearth_clipplane.cpp      |     2 +-
 .../osgearth_colorfilter/osgearth_colorfilter.cpp  |    11 +-
 .../osgearth_computerangecallback.cpp              |     1 +
 .../osgearth_controls/osgearth_controls.cpp        |     2 +-
 src/applications/osgearth_conv/osgearth_conv.cpp   |   200 +-
 .../osgearth_createtile/osgearth_createtile.cpp    |     8 +-
 .../osgearth_datetime/osgearth_datetime.cpp        |     4 +-
 .../osgearth_deformation/osgearth_deformation.cpp  |    34 +-
 .../osgearth_elevation/osgearth_elevation.cpp      |    85 +-
 .../osgearth_ephemeris/osgearth_ephemeris.cpp      |     7 +-
 .../osgearth_featureeditor.cpp                     |   286 -
 .../osgearth_featurefilter.cpp                     |     3 +-
 .../osgearth_featureinfo/osgearth_featureinfo.cpp  |     6 +-
 .../osgearth_featuremanip.cpp                      |   168 -
 .../osgearth_featurequery.cpp                      |    21 +-
 .../osgearth_features/osgearth_features.cpp        |    63 +-
 src/applications/osgearth_fog/osgearth_fog.cpp     |   100 -
 .../osgearth_graticule/osgearth_graticule.cpp      |   109 +-
 .../osgearth_horizon/osgearth_horizon.cpp          |     5 +-
 .../{osgearth_fog => osgearth_htm}/CMakeLists.txt  |     4 +-
 .../osgearth_htm.cpp}                              |    95 +-
 .../osgearth_imageoverlay.cpp                      |    23 +-
 .../CMakeLists.txt                                 |     5 +-
 .../osgearth_infinitescroll.cpp                    |   207 +
 .../CMakeLists.txt                                 |     4 +-
 .../osgearth_lights/osgearth_lights.cpp            |   244 +
 src/applications/osgearth_los/osgearth_los.cpp     |    63 +-
 src/applications/osgearth_manip/osgearth_manip.cpp |    53 +-
 src/applications/osgearth_map/osgearth_map.cpp     |    43 +-
 .../osgearth_minimap/osgearth_minimap.cpp          |     6 +-
 src/applications/osgearth_mrt/osgearth_mrt.cpp     |    51 +-
 .../CMakeLists.txt                                 |     5 +-
 .../osgearth_noisegen.cpp}                         |    60 +-
 .../osgearth_occlusionculling.cpp                  |     4 +-
 .../osgearth_package/osgearth_package.cpp          |    42 +-
 .../osgearth_package_qt/CMakeLists.txt             |     8 +-
 .../osgearth_package_qt/ExportDialog.cpp           |    45 +-
 .../osgearth_package_qt/PackageQtMainWindow        |    14 +-
 .../osgearth_package_qt/TMSExporter.cpp            |    25 +-
 .../osgearth_pagingtest/osgearth_pagingtest.cpp    |     6 +-
 src/applications/osgearth_pick/osgearth_pick.cpp   |   228 +-
 src/applications/osgearth_qt_simple/CMakeLists.txt |     8 +-
 .../osgearth_qt_windows/CMakeLists.txt             |     8 +-
 .../osgearth_qt_windows/osgearth_qt_windows.cpp    |     7 +-
 src/applications/osgearth_seed/osgearth_seed.cpp   |    63 +-
 .../osgearth_sequencecontrol.cpp                   |     3 +-
 .../osgearth_shadercomp/osgearth_shadercomp.cpp    |    29 +-
 .../osgearth_shadergen/osgearth_shadergen.cpp      |     6 +-
 .../osgearth_sharedlayer/osgearth_sharedlayer.cpp  |    10 +-
 .../osgearth_silverlining.cpp                      |     1 +
 src/applications/osgearth_splat/osgearth_splat.cpp |   255 +-
 .../osgearth_terrainprofile.cpp                    |     9 +-
 src/applications/osgearth_tfs/osgearth_tfs.cpp     |     2 +-
 .../osgearth_tilesource/osgearth_tilesource.cpp    |     3 +-
 src/applications/osgearth_toc/osgearth_toc.cpp     |   407 +-
 .../osgearth_tracks/osgearth_tracks.cpp            |     4 +-
 .../osgearth_transform/osgearth_transform.cpp      |    46 +-
 .../osgearth_triton/osgearth_triton.cpp            |   168 +-
 .../CMakeLists.txt                                 |     4 +-
 .../osgearth_video.cpp}                            |    34 +-
 .../osgearth_viewer/osgearth_viewer.cpp            |    15 +-
 .../{osgEarthViewerIOS => }/AppDelegate.h          |     0
 .../AppDelegate.m => AppDelegate.mm}               |     0
 src/applications/osgearth_viewerIOS/CMakeLists.txt |   143 +
 .../EarthMultiTouchManipulator.cpp                 |     0
 .../EarthMultiTouchManipulator.h                   |     0
 .../StartViewerController.h                        |     0
 ...ViewerController.m => StartViewerController.mm} |    33 +-
 .../StartViewerController.xib                      |     0
 .../{osgEarthViewerIOS => }/ViewController.h       |     2 +
 .../ViewController.m => ViewController.mm}         |   103 +-
 .../osgearth_viewerIOS/ViewController.xib          |    39 +
 .../{osgEarthViewerIOS/main.m => main.mm}          |     0
 .../osgEarthViewerIOS-Info.plist                   |     0
 .../osgEarthViewerIOS.xcodeproj/project.pbxproj    |  1066 --
 .../project.xcworkspace/contents.xcworkspacedata   |     7 -
 .../ShaderGen/GLES2ShaderGenVisitor.cpp            |   581 -
 .../ShaderGen/GLES2ShaderGenVisitor.h              |    84 -
 .../osgEarthViewerIOS/ShaderGen/ShaderGenScene.h   |   227 -
 .../osgEarthViewerIOS/en.lproj/InfoPlist.strings   |     2 -
 .../en.lproj/ViewController_iPad.xib               |   125 -
 .../en.lproj/ViewController_iPhone.xib             |   124 -
 .../osgEarthViewerIOS/osgEarthViewerIOS-Prefix.pch |    14 -
 .../osgEarthViewerIOS/osgPlugins.h                 |    84 -
 .../osgEarthViewerIOS/osgearth_viewerIOS.cpp       |    66 -
 src/applications/osgearth_viewerIOS/osgPlugins.h   |   206 +
 src/applications/osgearth_wfs/osgearth_wfs.cpp     |    66 +-
 src/osgEarth/AlphaEffect                           |    73 -
 src/osgEarth/AlphaEffect.cpp                       |   151 -
 src/osgEarth/AlphaEffect.frag.glsl                 |    12 -
 src/osgEarth/AutoScale.cpp                         |   138 -
 src/osgEarth/Bounds.cpp                            |     2 +-
 src/osgEarth/CMakeLists.txt                        |    74 +-
 src/osgEarth/Cache                                 |    22 +-
 src/osgEarth/Cache.cpp                             |     6 +-
 src/osgEarth/CacheBin.cpp                          |    19 +-
 src/osgEarth/CacheEstimator.cpp                    |     3 -
 src/osgEarth/CachePolicy                           |    10 +-
 src/osgEarth/CachePolicy.cpp                       |    25 -
 src/osgEarth/CacheSeed                             |     9 +-
 src/osgEarth/CacheSeed.cpp                         |    46 +-
 src/osgEarth/Capabilities                          |    14 +-
 src/osgEarth/Capabilities.cpp                      |    41 +-
 src/osgEarth/ClampableNode                         |    41 +-
 src/osgEarth/ClampableNode.cpp                     |   138 +-
 src/osgEarth/Clamping                              |    73 +-
 src/osgEarth/Clamping.cpp                          |   148 +-
 src/osgEarth/ClampingTechnique                     |    10 +-
 src/osgEarth/ClampingTechnique.cpp                 |   159 +-
 src/osgEarth/ColorFilter                           |     8 +
 src/osgEarth/CompositeTileSource.cpp               |    86 +-
 src/osgEarth/Config                                |    62 +-
 src/osgEarth/Config.cpp                            |    30 +-
 src/osgEarth/Containers                            |    99 +-
 src/osgEarth/Cube                                  |     9 +-
 src/osgEarth/Cube.cpp                              |    53 +-
 src/osgEarth/CullingUtils                          |    44 +-
 src/osgEarth/CullingUtils.cpp                      |    82 +-
 src/osgEarth/DPLineSegmentIntersector              |     8 +-
 src/osgEarth/DPLineSegmentIntersector.cpp          |    14 +-
 src/osgEarth/DateTime                              |     2 +-
 src/osgEarth/DateTimeRange                         |    13 +
 src/osgEarth/DepthOffset                           |    31 +-
 src/osgEarth/DepthOffset.cpp                       |    30 +-
 src/osgEarth/DepthOffset.vert.glsl                 |    15 +-
 src/osgEarth/DrapeableNode                         |    19 +-
 src/osgEarth/DrapeableNode.cpp                     |    42 +-
 src/osgEarth/Draping.frag.glsl                     |    13 +-
 src/osgEarth/Draping.vert.glsl                     |     2 +-
 src/osgEarth/DrapingCullSet                        |    22 +-
 src/osgEarth/DrapingCullSet.cpp                    |    25 +-
 src/osgEarth/DrapingTechnique                      |     9 +-
 src/osgEarth/DrapingTechnique.cpp                  |    95 +-
 src/osgEarth/DrawInstanced                         |     4 +-
 src/osgEarth/DrawInstanced.cpp                     |    41 +-
 src/osgEarth/ElevationLOD                          |     3 +
 src/osgEarth/ElevationLayer                        |   118 +-
 src/osgEarth/ElevationLayer.cpp                    |   595 +-
 src/osgEarth/ElevationPool                         |   266 +
 src/osgEarth/ElevationPool.cpp                     |   513 +
 src/osgEarth/ElevationQuery                        |   140 +-
 src/osgEarth/ElevationQuery.cpp                    |   420 +-
 src/osgEarth/Endian                                |   178 +
 src/osgEarth/Export                                |     3 +
 src/osgEarth/Extension                             |    55 +-
 src/osgEarth/FadeEffect                            |     2 +-
 src/osgEarth/FadeEffect.cpp                        |     6 +-
 src/osgEarth/FileUtils.cpp                         |   113 +-
 src/osgEarth/GLSLChunker.cpp                       |     2 +-
 src/osgEarth/GPUClamping.frag.glsl                 |     3 +-
 src/osgEarth/GPUClamping.vert.glsl                 |   120 +-
 src/osgEarth/GPUClamping.vert.lib.glsl             |     2 +-
 src/osgEarth/GeoCommon                             |     6 +-
 src/osgEarth/GeoData                               |   143 +-
 src/osgEarth/GeoData.cpp                           |  1124 +-
 src/osgEarth/GeoTransform                          |    18 +-
 src/osgEarth/GeoTransform.cpp                      |   143 +-
 src/osgEarth/GeometryClamper                       |     7 +-
 src/osgEarth/GeometryClamper.cpp                   |   180 +-
 src/osgEarth/HTTPClient                            |     7 +-
 src/osgEarth/HTTPClient.cpp                        |  3362 +++---
 src/osgEarth/HeightFieldUtils                      |   102 +-
 src/osgEarth/HeightFieldUtils.cpp                  |   221 +-
 src/osgEarth/Horizon.cpp                           |     9 +-
 src/osgEarth/IOTypes                               |     8 +
 src/osgEarth/ImageLayer                            |   151 +-
 src/osgEarth/ImageLayer.cpp                        |   360 +-
 src/osgEarth/ImageMosaic.cpp                       |     3 +-
 src/osgEarth/ImageToHeightFieldConverter           |     2 +
 src/osgEarth/ImageToHeightFieldConverter.cpp       |    27 +
 src/osgEarth/ImageUtils                            |    22 +
 src/osgEarth/ImageUtils.cpp                        |   372 +-
 src/osgEarth/Instancing.vert.glsl                  |     2 +
 src/osgEarth/IntersectionPicker                    |     5 +
 src/osgEarth/LandCover                             |   250 +
 src/osgEarth/LandCover.cpp                         |   315 +
 src/osgEarth/LandCoverLayer                        |   108 +
 src/osgEarth/LandCoverLayer.cpp                    |   635 ++
 src/osgEarth/Layer                                 |   280 +-
 src/osgEarth/Layer.cpp                             |   307 +-
 src/osgEarth/LayerListener                         |   146 +
 src/osgEarth/Lighting                              |   137 +
 src/osgEarth/Lighting.cpp                          |   264 +
 src/osgEarth/LineFunctor                           |    12 -
 src/osgEarth/Locators.cpp                          |     4 +-
 src/osgEarth/Map                                   |   269 +-
 src/osgEarth/Map.cpp                               |  1052 +-
 src/osgEarth/MapCallback                           |    44 +-
 src/osgEarth/MapCallback.cpp                       |   109 +-
 src/osgEarth/MapFrame                              |    87 +-
 src/osgEarth/MapFrame.cpp                          |   264 +-
 src/osgEarth/MapInfo                               |    23 +-
 src/osgEarth/MapInfo.cpp                           |    27 +-
 src/osgEarth/MapModelChange                        |    20 +-
 src/osgEarth/MapNode                               |    89 +-
 src/osgEarth/MapNode.cpp                           |   540 +-
 src/osgEarth/MapNodeOptions                        |     7 +
 src/osgEarth/MapNodeOptions.cpp                    |     2 +-
 src/osgEarth/MapOptions                            |    17 +-
 src/osgEarth/MapOptions.cpp                        |    22 +-
 src/osgEarth/MaskLayer                             |    85 +-
 src/osgEarth/MaskLayer.cpp                         |   200 +-
 src/osgEarth/MaskNode                              |     1 +
 src/osgEarth/MaskSource                            |     3 +
 src/osgEarth/MaskSource.cpp                        |     7 +-
 src/osgEarth/MemCache                              |     3 -
 src/osgEarth/MemCache.cpp                          |     8 +-
 src/osgEarth/Mercator                              |    46 -
 src/osgEarth/Mercator.cpp                          |   149 -
 src/osgEarth/MetaTile                              |    77 +
 src/osgEarth/MetaTile.cpp                          |   122 +
 src/osgEarth/Metrics                               |   269 +
 src/osgEarth/Metrics.cpp                           |   496 +
 src/osgEarth/ModelLayer                            |   170 +-
 src/osgEarth/ModelLayer.cpp                        |   292 +-
 src/osgEarth/ModelSource                           |    41 +-
 src/osgEarth/ModelSource.cpp                       |   103 +-
 src/osgEarth/NodeUtils                             |    31 +-
 src/osgEarth/Notify                                |     2 +
 src/osgEarth/ObjectIndex                           |     7 +-
 src/osgEarth/ObjectIndex.cpp                       |     2 +
 src/osgEarth/OverlayDecorator                      |    14 +-
 src/osgEarth/OverlayDecorator.cpp                  |   138 +-
 src/osgEarth/OverlayNode                           |    94 -
 src/osgEarth/OverlayNode.cpp                       |   398 -
 src/osgEarth/PagedNode                             |    95 +
 src/osgEarth/PagedNode.cpp                         |   165 +
 src/osgEarth/PatchLayer                            |   121 +
 .../{TilePatchCallback.cpp => PatchLayer.cpp}      |    26 +-
 src/osgEarth/PhongLighting.frag.glsl               |   145 +
 src/osgEarth/PhongLighting.vert.glsl               |    20 +
 src/osgEarth/PhongLightingEffect                   |     5 +-
 src/osgEarth/PhongLightingEffect.cpp               |   160 +-
 src/osgEarth/PluginLoader                          |    83 +
 src/osgEarth/Profile                               |     5 +-
 src/osgEarth/Profile.cpp                           |    42 +-
 src/osgEarth/Profiler                              |     6 +
 src/osgEarth/Profiler.cpp                          |     7 +
 src/osgEarth/QuadTree                              |   199 -
 src/osgEarth/QuadTree.cpp                          |   825 --
 src/osgEarth/Registry                              |    65 +-
 src/osgEarth/Registry.cpp                          |   106 +-
 src/osgEarth/ResourceReleaser.cpp                  |     8 +-
 src/osgEarth/SceneGraphCallback                    |   115 +
 src/osgEarth/SceneGraphCallback.cpp                |   149 +
 src/osgEarth/ScreenSpaceLayout                     |    16 +-
 src/osgEarth/ScreenSpaceLayout.cpp                 |   150 +-
 src/osgEarth/ShaderFactory                         |    11 +-
 src/osgEarth/ShaderFactory.cpp                     |    86 +-
 src/osgEarth/ShaderGenerator                       |    10 +-
 src/osgEarth/ShaderGenerator.cpp                   |   258 +-
 src/osgEarth/ShaderLoader                          |     4 +
 src/osgEarth/ShaderLoader.cpp                      |   294 +-
 src/osgEarth/ShaderUtils                           |    76 -
 src/osgEarth/ShaderUtils.cpp                       |   370 +-
 src/osgEarth/Shaders                               |     2 +-
 src/osgEarth/Shaders.cpp.in                        |    13 +-
 src/osgEarth/Shadowing.cpp                         |     7 +-
 src/{osgEarthUtil => osgEarth}/SimplexNoise        |    22 +-
 src/{osgEarthUtil => osgEarth}/SimplexNoise.cpp    |    80 +-
 src/osgEarth/SpatialReference                      |    15 +-
 src/osgEarth/SpatialReference.cpp                  |   126 +-
 src/osgEarth/StateSetCache.cpp                     |     4 +-
 src/osgEarth/StateSetLOD                           |     5 +-
 src/osgEarth/StringUtils                           |     2 +-
 src/osgEarth/StringUtils.cpp                       |     6 +-
 src/osgEarth/Terrain                               |    72 +-
 src/osgEarth/Terrain.cpp                           |   143 +-
 src/osgEarth/TerrainEngineNode                     |    99 +-
 src/osgEarth/TerrainEngineNode.cpp                 |   224 +-
 src/osgEarth/TerrainEngineRequirements             |     2 +
 src/osgEarth/TerrainLayer                          |   305 +-
 src/osgEarth/TerrainLayer.cpp                      |   871 +-
 src/osgEarth/TerrainOptions                        |    22 +-
 src/osgEarth/TerrainOptions.cpp                    |    79 +-
 src/osgEarth/TerrainResources                      |   138 +
 src/osgEarth/TerrainResources.cpp                  |   262 +
 src/osgEarth/TerrainTileModel                      |    78 +-
 src/osgEarth/TerrainTileModel.cpp                  |    88 +-
 src/osgEarth/TerrainTileModelFactory               |    82 +-
 src/osgEarth/TerrainTileModelFactory.cpp           |   381 +-
 src/osgEarth/TerrainTileNode                       |    19 +-
 src/osgEarth/Tessellator.cpp                       |     4 +-
 src/osgEarth/TextureBufferSerializer.cpp           |    22 +
 src/osgEarth/TextureCompositor                     |    71 -
 src/osgEarth/TextureCompositor.cpp                 |    79 -
 src/osgEarth/ThreadingUtils                        |   333 +-
 src/osgEarth/ThreadingUtils.cpp                    |   170 +
 src/osgEarth/TileKeyDataStore                      |     1 +
 src/osgEarth/TilePatchCallback                     |    79 -
 src/osgEarth/TileRasterizer                        |   116 +
 src/osgEarth/TileRasterizer.cpp                    |   284 +
 src/osgEarth/TileSource                            |   156 +-
 src/osgEarth/TileSource.cpp                        |   395 +-
 src/osgEarth/TileVisitor.cpp                       |     8 +-
 src/osgEarth/TraversalData                         |     6 +-
 src/osgEarth/TraversalData.cpp                     |    48 +-
 src/osgEarth/URI                                   |    35 +
 src/osgEarth/URI.cpp                               |    32 +-
 src/osgEarth/Units                                 |    10 +-
 src/osgEarth/Units.cpp                             |     2 +-
 src/osgEarth/Utils                                 |    41 +-
 src/osgEarth/Utils.cpp                             |    53 +-
 src/osgEarth/Version                               |     4 +-
 src/osgEarth/VerticalDatum.cpp                     |     9 +-
 src/osgEarth/VideoLayer                            |    93 +
 src/osgEarth/VideoLayer.cpp                        |   155 +
 src/osgEarth/VirtualProgram                        |    22 +-
 src/osgEarth/VirtualProgram.cpp                    |   172 +-
 src/osgEarth/VisibleLayer                          |   108 +
 src/osgEarth/VisibleLayer.cpp                      |   177 +
 src/osgEarth/WrapperLayer                          |    74 +
 src/osgEarth/catch.hpp                             | 10663 +++++++++++++++++++
 src/osgEarth/tinyxml.cpp                           |     2 +
 src/osgEarth/tinyxml.h                             |   104 +-
 src/osgEarth/tinyxmlerror.cpp                      |     1 +
 src/osgEarth/tinyxmlparser.cpp                     |    12 +-
 src/osgEarthAnnotation/AnnotationEditing.cpp       |     2 +-
 src/osgEarthAnnotation/AnnotationExtension         |    61 -
 src/osgEarthAnnotation/AnnotationExtension.cpp     |    91 -
 src/osgEarthAnnotation/AnnotationLayer             |    98 +
 src/osgEarthAnnotation/AnnotationLayer.cpp         |    74 +
 src/osgEarthAnnotation/AnnotationNode              |     6 +
 src/osgEarthAnnotation/AnnotationNode.cpp          |    59 +-
 src/osgEarthAnnotation/AnnotationRegistry          |     5 +
 src/osgEarthAnnotation/AnnotationUtils.cpp         |    68 +-
 src/osgEarthAnnotation/BboxDrawable.cpp            |     2 -
 src/osgEarthAnnotation/CMakeLists.txt              |    13 +-
 src/osgEarthAnnotation/Common                      |     5 +-
 src/osgEarthAnnotation/Decoration                  |   120 -
 src/osgEarthAnnotation/Decoration.cpp              |   106 -
 src/osgEarthAnnotation/FeatureEditing.cpp          |     6 +-
 src/osgEarthAnnotation/FeatureNode                 |    55 +-
 src/osgEarthAnnotation/FeatureNode.cpp             |   126 +-
 src/osgEarthAnnotation/GeoPositionNode             |    12 +-
 src/osgEarthAnnotation/GeoPositionNode.cpp         |    41 +-
 .../GeoPositionNodeAutoScaler.cpp                  |    55 +-
 src/osgEarthAnnotation/HighlightDecoration         |    62 -
 src/osgEarthAnnotation/HighlightDecoration.cpp     |    95 -
 src/osgEarthAnnotation/ImageOverlay                |    16 +-
 src/osgEarthAnnotation/ImageOverlay.cpp            |   289 +-
 src/osgEarthAnnotation/LabelNode                   |     4 +
 src/osgEarthAnnotation/LabelNode.cpp               |    76 +-
 src/osgEarthAnnotation/LocalGeometryNode           |    17 +-
 src/osgEarthAnnotation/LocalGeometryNode.cpp       |   101 +-
 src/osgEarthAnnotation/ModelNode.cpp               |     7 +-
 src/osgEarthAnnotation/PlaceNode                   |     4 +
 src/osgEarthAnnotation/PlaceNode.cpp               |    60 +-
 src/osgEarthAnnotation/RectangleNode.cpp           |     4 +-
 src/osgEarthAnnotation/ScaleDecoration             |    54 -
 src/osgEarthAnnotation/TrackNode                   |     4 +
 src/osgEarthAnnotation/TrackNode.cpp               |    11 +-
 src/osgEarthDrivers/CMakeLists.txt                 |    63 +-
 src/osgEarthDrivers/agglite/AGGLiteOptions         |     4 +-
 .../agglite/AGGLiteRasterizerTileSource.cpp        |    29 +-
 src/osgEarthDrivers/arcgis/ArcGISOptions           |     8 +-
 .../arcgis_map_cache/CMakeLists.txt                |     3 -
 .../ReaderWriterArcGISMapCache.cpp                 |   147 -
 src/osgEarthDrivers/bing/BingOptions               |     6 +-
 src/osgEarthDrivers/bing/BingTileSource.cpp        |    10 +-
 .../bumpmap/BumpMap.frag.common.glsl               |     2 +-
 .../bumpmap/BumpMap.frag.progressive.glsl          |    14 +-
 .../bumpmap/BumpMap.frag.simple.glsl               |    14 +-
 src/osgEarthDrivers/bumpmap/BumpMap.vert.view.glsl |    10 +
 src/osgEarthDrivers/bumpmap/BumpMapOptions         |    12 +-
 .../bumpmap/BumpMapTerrainEffect.cpp               |     1 -
 .../cache_filesystem/FileSystemCache.cpp           |    36 +-
 src/osgEarthDrivers/cache_rocksdb/CMakeLists.txt   |     2 +
 src/osgEarthDrivers/colorramp/CMakeLists.txt       |     2 +-
 src/osgEarthDrivers/colorramp/ColorRampOptions     |     4 +-
 src/osgEarthDrivers/debug/DebugOptions             |     6 +-
 src/osgEarthDrivers/debug/DebugTileSource.cpp      |    18 +-
 src/osgEarthDrivers/detail/Detail.frag.glsl        |     4 +-
 src/osgEarthDrivers/detail/Detail.vert.view.glsl   |     3 +-
 src/osgEarthDrivers/detail/DetailOptions           |    10 +-
 src/osgEarthDrivers/detail/DetailTerrainEffect.cpp |     2 +-
 src/osgEarthDrivers/earth/EarthFileSerializer1.cpp |     7 +-
 src/osgEarthDrivers/earth/EarthFileSerializer2.cpp |   234 +-
 .../engine_byo/BYOTerrainEngineDriver.cpp          |    66 -
 .../engine_byo/BYOTerrainEngineNode                |    57 -
 .../engine_byo/BYOTerrainEngineNode.cpp            |    81 -
 .../engine_byo/BYOTerrainEngineOptions             |    91 -
 src/osgEarthDrivers/engine_byo/CMakeLists.txt      |    20 -
 src/osgEarthDrivers/engine_byo/Common              |    24 -
 src/osgEarthDrivers/engine_mp/HeightFieldCache.cpp |     2 +-
 .../engine_mp/MPEngine.NormalMap.frag.glsl         |     3 +-
 .../engine_mp/MPEngine.NormalMap.vert.glsl         |     1 +
 src/osgEarthDrivers/engine_mp/MPEngine.frag.glsl   |    19 +-
 src/osgEarthDrivers/engine_mp/MPGeometry           |     5 +
 src/osgEarthDrivers/engine_mp/MPGeometry.cpp       |   104 +-
 .../engine_mp/MPTerrainEngineDriver.cpp            |     8 +-
 src/osgEarthDrivers/engine_mp/MPTerrainEngineNode  |    15 +-
 .../engine_mp/MPTerrainEngineNode.cpp              |   232 +-
 .../engine_mp/MPTerrainEngineOptions               |    40 +-
 .../engine_mp/SingleKeyNodeFactory.cpp             |    64 +-
 src/osgEarthDrivers/engine_mp/TileModel.cpp        |     4 +-
 src/osgEarthDrivers/engine_mp/TileModelCompiler    |     6 +-
 .../engine_mp/TileModelCompiler.cpp                |    51 +-
 src/osgEarthDrivers/engine_mp/TileModelFactory.cpp |    41 +-
 src/osgEarthDrivers/engine_mp/TileNode             |     6 +-
 src/osgEarthDrivers/engine_mp/TileNode.cpp         |     8 +-
 src/osgEarthDrivers/engine_mp/TileNodeRegistry     |     4 +-
 src/osgEarthDrivers/engine_mp/TileNodeRegistry.cpp |    11 +-
 src/osgEarthDrivers/engine_mp/TilePagedLOD         |    17 +-
 src/osgEarthDrivers/engine_mp/TilePagedLOD.cpp     |    67 +-
 src/osgEarthDrivers/engine_rex/CMakeLists.txt      |    16 +-
 src/osgEarthDrivers/engine_rex/DrawState           |   152 +
 src/osgEarthDrivers/engine_rex/DrawState.cpp       |    79 +
 src/osgEarthDrivers/engine_rex/DrawTileCommand     |   114 +
 src/osgEarthDrivers/engine_rex/DrawTileCommand.cpp |   155 +
 src/osgEarthDrivers/engine_rex/EngineContext       |    47 +-
 src/osgEarthDrivers/engine_rex/EngineContext.cpp   |    83 +-
 src/osgEarthDrivers/engine_rex/GeometryPool        |   121 +-
 src/osgEarthDrivers/engine_rex/GeometryPool.cpp    |   549 +-
 src/osgEarthDrivers/engine_rex/LayerDrawable       |    87 +
 src/osgEarthDrivers/engine_rex/LayerDrawable.cpp   |    96 +
 src/osgEarthDrivers/engine_rex/LoadTileData        |    23 +-
 src/osgEarthDrivers/engine_rex/LoadTileData.cpp    |   275 +-
 src/osgEarthDrivers/engine_rex/Loader              |    33 +-
 src/osgEarthDrivers/engine_rex/Loader.cpp          |   153 +-
 src/osgEarthDrivers/engine_rex/MPTexture           |   106 -
 src/osgEarthDrivers/engine_rex/MPTexture.cpp       |   171 -
 src/osgEarthDrivers/engine_rex/MaskGenerator       |     5 +-
 src/osgEarthDrivers/engine_rex/MaskGenerator.cpp   |    40 +-
 src/osgEarthDrivers/engine_rex/RenderBindings      |    45 +-
 .../engine_rex/RexEngine.Morphing.vert.glsl        |    21 +-
 .../engine_rex/RexEngine.NormalMap.frag.glsl       |    24 +-
 .../engine_rex/RexEngine.NormalMap.vert.glsl       |    15 +-
 .../engine_rex/RexEngine.SDK.vert.glsl             |    26 +-
 .../engine_rex/RexEngine.elevation.glsl            |    33 +
 src/osgEarthDrivers/engine_rex/RexEngine.frag.glsl |    58 +-
 src/osgEarthDrivers/engine_rex/RexEngine.gs.glsl   |     1 +
 src/osgEarthDrivers/engine_rex/RexEngine.tcs.glsl  |     1 +
 src/osgEarthDrivers/engine_rex/RexEngine.vert.glsl |     3 +-
 .../engine_rex/RexEngine.vert.view.glsl            |    27 +-
 .../engine_rex/RexTerrainEngineNode                |    99 +-
 .../engine_rex/RexTerrainEngineNode.cpp            |  1095 +-
 .../engine_rex/RexTerrainEngineOptions             |    79 +-
 src/osgEarthDrivers/engine_rex/SelectionInfo       |    28 +-
 src/osgEarthDrivers/engine_rex/SelectionInfo.cpp   |    98 +-
 src/osgEarthDrivers/engine_rex/Shaders             |     1 +
 src/osgEarthDrivers/engine_rex/Shaders.cpp.in      |     3 +
 src/osgEarthDrivers/engine_rex/SurfaceNode         |    21 +-
 src/osgEarthDrivers/engine_rex/SurfaceNode.cpp     |    39 +-
 src/osgEarthDrivers/engine_rex/TerrainCuller       |    94 +
 src/osgEarthDrivers/engine_rex/TerrainCuller.cpp   |   274 +
 src/osgEarthDrivers/engine_rex/TerrainRenderData   |    75 +
 .../engine_rex/TerrainRenderData.cpp               |   131 +
 src/osgEarthDrivers/engine_rex/TileDrawable        |   143 +-
 src/osgEarthDrivers/engine_rex/TileDrawable.cpp    |   626 +-
 src/osgEarthDrivers/engine_rex/TileNode            |   100 +-
 src/osgEarthDrivers/engine_rex/TileNode.cpp        |   952 +-
 src/osgEarthDrivers/engine_rex/TileNodeRegistry    |    10 +-
 .../engine_rex/TileNodeRegistry.cpp                |    90 +-
 src/osgEarthDrivers/engine_rex/TileRenderModel     |   171 +
 src/osgEarthDrivers/engine_rex/Unloader            |    12 +-
 src/osgEarthDrivers/engine_rex/Unloader.cpp        |    12 +-
 src/osgEarthDrivers/fastdxt/CMakeLists.txt         |    11 +-
 .../fastdxt/FastDXTImageProcessor.cpp              |     8 +-
 src/osgEarthDrivers/fastdxt/util.cpp               |     4 +-
 src/osgEarthDrivers/fastdxt/util.h                 |     4 +-
 .../feature_elevation/FeatureElevationOptions      |     6 +-
 .../ReaderWriterFeatureElevation.cpp               |    29 +-
 .../feature_mapnikvectortiles/FeatureSourceMVT.cpp |     4 +-
 .../feature_mapnikvectortiles/MVTFeatureOptions    |     2 +-
 src/osgEarthDrivers/feature_ogr/FeatureCursorOGR   |    16 +-
 .../feature_ogr/FeatureCursorOGR.cpp               |    31 +-
 .../feature_ogr/FeatureSourceOGR.cpp               |     2 +-
 src/osgEarthDrivers/feature_ogr/OGRFeatureOptions  |    16 +-
 .../feature_raster/FeatureSourceRaster.cpp         |    14 +-
 .../feature_raster/RasterFeatureOptions            |     6 +-
 .../feature_tfs/FeatureSourceTFS.cpp               |    30 +-
 src/osgEarthDrivers/feature_tfs/TFSFeatureOptions  |    10 +-
 .../feature_wfs/FeatureSourceWFS.cpp               |    53 +-
 src/osgEarthDrivers/feature_wfs/WFSFeatureOptions  |    14 +-
 src/osgEarthDrivers/feature_xyz/CMakeLists.txt     |    18 +
 .../feature_xyz/FeatureSourceXYZ.cpp               |   416 +
 .../feature_xyz/XYZFeatureOptions}                 |    48 +-
 .../IntersectFeatureFilter.cpp                     |    12 +-
 .../featurefilter_join/JoinFeatureFilter.cpp       |     2 +
 src/osgEarthDrivers/gdal/GDALOptions               |    16 +-
 src/osgEarthDrivers/gdal/ReaderWriterGDAL.cpp      |   799 +-
 src/osgEarthDrivers/kml/KMLReader.cpp              |    19 +-
 src/osgEarthDrivers/kml/KML_Placemark.cpp          |    22 +-
 src/osgEarthDrivers/kml/KML_PolyStyle.cpp          |    49 +-
 src/osgEarthDrivers/kml/rapidxml_ext.hpp           |    13 +-
 .../label_annotation/AnnotationLabelSource.cpp     |     1 +
 .../mapinspector/MapInspectorExtension             |     9 +-
 .../mapinspector/MapInspectorExtension.cpp         |    21 +-
 src/osgEarthDrivers/mapinspector/MapInspectorUI    |     2 +
 .../mapinspector/MapInspectorUI.cpp                |    48 +-
 .../mask_feature/FeatureMaskOptions                |     2 +-
 .../mask_feature/FeatureMaskSource.cpp             |     3 +
 src/osgEarthDrivers/mbtiles/MBTilesOptions         |     9 +-
 src/osgEarthDrivers/mbtiles/MBTilesTileSource.cpp  |   102 +-
 .../model_feature_geom/FeatureGeomModelOptions     |    17 +-
 .../model_feature_geom/FeatureGeomModelSource.cpp  |     2 +-
 .../model_simple/SimpleModelOptions                |    14 +-
 .../model_simple/SimpleModelSource.cpp             |     2 +-
 src/osgEarthDrivers/noise/CMakeLists.txt           |    24 -
 src/osgEarthDrivers/noise/NoiseExtension           |    74 -
 src/osgEarthDrivers/noise/NoiseExtension.cpp       |    80 -
 src/osgEarthDrivers/noise/NoiseOptions             |    82 -
 src/osgEarthDrivers/noise/NoiseTerrainEffect       |    65 -
 src/osgEarthDrivers/noise/NoiseTerrainEffect.cpp   |   167 -
 .../ocean_simple/ElevationProxyImageLayer          |     2 +-
 .../ocean_simple/ElevationProxyImageLayer.cpp      |    36 +-
 .../ocean_simple/SimpleOcean.FS.glsl               |    67 +-
 .../ocean_simple/SimpleOcean.VS.glsl               |    46 +-
 .../ocean_simple/SimpleOceanNode.cpp               |    70 +-
 .../ocean_simple/SimpleOceanOptions                |    22 +-
 src/osgEarthDrivers/ocean_triton/TritonDriver.cpp  |     4 +-
 src/osgEarthDrivers/osg/OSGOptions                 |    15 +-
 src/osgEarthDrivers/osg/OSGTileSource.cpp          |    38 +-
 src/osgEarthDrivers/quadkey/CMakeLists.txt         |    15 -
 src/osgEarthDrivers/quadkey/QuadKeyOptions         |    82 -
 .../quadkey/ReaderWriterQuadKey.cpp                |   199 -
 src/osgEarthDrivers/refresh/CMakeLists.txt         |    13 -
 .../refresh/ReaderWriterRefresh.cpp                |   251 -
 src/osgEarthDrivers/refresh/RefreshOptions         |    85 -
 .../script_engine_duktape/DuktapeEngine.cpp        |     2 +-
 .../script_engine_javascriptcore/CMakeLists.txt    |    26 -
 .../script_engine_javascriptcore/JSWrappers        |    33 -
 .../script_engine_javascriptcore/JSWrappers.cpp    |    87 -
 .../JavaScriptCoreEngine                           |    59 -
 .../JavaScriptCoreEngine.cpp                       |   126 -
 .../JavaScriptCoreEngineFactory.cpp                |    50 -
 .../script_engine_v8/CMakeLists.txt                |    29 -
 src/osgEarthDrivers/script_engine_v8/JSWrappers    |   233 -
 .../script_engine_v8/JSWrappers.cpp                |  1294 ---
 .../script_engine_v8/JavascriptEngineV8            |    74 -
 .../script_engine_v8/JavascriptEngineV8.cpp        |   474 -
 .../script_engine_v8/JavascriptEngineV8Factory.cpp |    50 -
 src/osgEarthDrivers/script_engine_v8/V8Util        |    79 -
 src/osgEarthDrivers/sky_gl/CMakeLists.txt          |     1 -
 src/osgEarthDrivers/sky_gl/GLSkyExtension.cpp      |    41 +-
 src/osgEarthDrivers/sky_gl/GLSkyNode               |     3 +-
 src/osgEarthDrivers/sky_gl/GLSkyNode.cpp           |    38 +-
 src/osgEarthDrivers/sky_gl/GLSkyShaders            |   130 -
 .../sky_silverlining/SilverLiningDriver.cpp        |     1 +
 .../sky_simple/SimpleSky.Atmosphere.frag.glsl      |    17 +-
 .../sky_simple/SimpleSky.Atmosphere.vert.glsl      |    20 +-
 .../sky_simple/SimpleSky.Ground.ONeil.frag.glsl    |   184 +-
 .../sky_simple/SimpleSky.Ground.ONeil.vert.glsl    |    56 +-
 .../sky_simple/SimpleSky.Moon.frag.glsl            |     6 +-
 .../sky_simple/SimpleSky.Moon.vert.glsl            |     5 +-
 .../sky_simple/SimpleSky.Stars.GLES.frag.glsl      |    11 +-
 .../sky_simple/SimpleSky.Stars.GLES.vert.glsl      |     9 +-
 .../sky_simple/SimpleSky.Stars.frag.glsl           |     5 +-
 .../sky_simple/SimpleSky.Stars.vert.glsl           |    10 +-
 .../sky_simple/SimpleSky.Sun.frag.glsl             |    13 +-
 .../sky_simple/SimpleSky.Sun.vert.glsl             |     2 +-
 .../sky_simple/SimpleSkyExtension.cpp              |    10 +-
 src/osgEarthDrivers/sky_simple/SimpleSkyNode       |     3 +-
 src/osgEarthDrivers/sky_simple/SimpleSkyNode.cpp   |    70 +-
 src/osgEarthDrivers/sky_simple/SimpleSkyOptions    |    22 +-
 src/osgEarthDrivers/skyview/SkyViewOptions         |     2 +-
 src/osgEarthDrivers/splat_mask/CMakeLists.txt      |    14 -
 src/osgEarthDrivers/splat_mask/SplatMaskDriver.cpp |   205 -
 src/osgEarthDrivers/splat_mask/SplatMaskOptions    |    79 -
 .../template_matclass/CMakeLists.txt               |    16 -
 .../template_matclass/TemplateMatClassDriver.cpp   |   167 -
 .../template_matclass/TemplateMatClassOptions      |    83 -
 .../terrainshader/TerrainShaderExtension.cpp       |     8 +-
 src/osgEarthDrivers/tilecache/CMakeLists.txt       |    17 -
 .../tilecache/ReaderWriterTileCache.cpp            |   131 -
 src/osgEarthDrivers/tilecache/TileCacheOptions     |    79 -
 src/osgEarthDrivers/tileindex/TileIndexOptions     |     2 +-
 src/osgEarthDrivers/tileservice/CMakeLists.txt     |    15 -
 .../tileservice/ReaderWriterTileService.cpp        |   138 -
 src/osgEarthDrivers/tileservice/TileServiceOptions |    79 -
 src/osgEarthDrivers/tms/TMSOptions                 |     6 +-
 src/osgEarthDrivers/tms/TMSTileSource.cpp          |    30 +-
 src/osgEarthDrivers/vpb/VPBOptions                 |    18 +-
 src/osgEarthDrivers/wcs/WCS11Source.cpp            |     4 +-
 src/osgEarthDrivers/wcs/WCSOptions                 |    12 +-
 src/osgEarthDrivers/wms/ReaderWriterWMS.cpp        |    15 +-
 src/osgEarthDrivers/wms/WMSOptions                 |    29 +-
 src/osgEarthDrivers/xyz/ReaderWriterXYZ.cpp        |   216 +-
 src/osgEarthDrivers/xyz/XYZOptions                 |    14 +-
 src/osgEarthDrivers/yahoo/CMakeLists.txt           |     9 -
 src/osgEarthDrivers/yahoo/ReaderWriterYahoo.cpp    |   151 -
 src/osgEarthDrivers/yahoo/YahooOptions             |    69 -
 .../feature_mapnikvectortiles/CMakeLists.txt       |    29 -
 .../feature_mapnikvectortiles/FeatureSourceMVT.cpp |   446 -
 .../feature_mapnikvectortiles/vector_tile.proto    |    92 -
 src/osgEarthExtensions/CMakeLists.txt              |    29 -
 src/osgEarthFeatures/AltitudeFilter.cpp            |   126 +-
 src/osgEarthFeatures/BufferFilter.cpp              |     1 +
 src/osgEarthFeatures/BuildGeometryFilter           |    27 +-
 src/osgEarthFeatures/BuildGeometryFilter.cpp       |   564 +-
 src/osgEarthFeatures/BuildTextFilter               |     2 -
 src/osgEarthFeatures/BuildTextFilter.cpp           |     2 +-
 src/osgEarthFeatures/CMakeLists.txt                |    42 +-
 src/osgEarthFeatures/CentroidFilter.cpp            |     1 +
 src/osgEarthFeatures/ConvertTypeFilter.cpp         |     2 -
 src/osgEarthFeatures/ExtrudeGeometryFilter         |     9 +-
 src/osgEarthFeatures/ExtrudeGeometryFilter.cpp     |    13 +-
 src/osgEarthFeatures/Feature                       |    31 +-
 src/osgEarthFeatures/Feature.cpp                   |    94 +-
 src/osgEarthFeatures/FeatureCursor                 |    18 +-
 src/osgEarthFeatures/FeatureCursor.cpp             |    27 +-
 src/osgEarthFeatures/FeatureDisplayLayout          |     9 +-
 src/osgEarthFeatures/FeatureDisplayLayout.cpp      |     5 +-
 src/osgEarthFeatures/FeatureDrawSet                |    91 -
 src/osgEarthFeatures/FeatureDrawSet.cpp            |   257 -
 src/osgEarthFeatures/FeatureListSource             |     7 +-
 src/osgEarthFeatures/FeatureListSource.cpp         |     4 +-
 src/osgEarthFeatures/FeatureMaskLayer              |   124 +
 src/osgEarthFeatures/FeatureMaskLayer.cpp          |   209 +
 src/osgEarthFeatures/FeatureModelGraph             |    80 +-
 src/osgEarthFeatures/FeatureModelGraph.cpp         |   519 +-
 src/osgEarthFeatures/FeatureModelLayer             |   130 +
 src/osgEarthFeatures/FeatureModelLayer.cpp         |   255 +
 src/osgEarthFeatures/FeatureModelSource            |    72 +-
 src/osgEarthFeatures/FeatureModelSource.cpp        |   138 +-
 src/osgEarthFeatures/FeatureRasterizer.cpp         |    20 +-
 src/osgEarthFeatures/FeatureSource                 |    20 +-
 src/osgEarthFeatures/FeatureSource.cpp             |    39 +-
 src/osgEarthFeatures/FeatureSourceIndexNode.cpp    |     1 +
 src/osgEarthFeatures/FeatureSourceLayer            |    94 +
 src/osgEarthFeatures/FeatureSourceLayer.cpp        |    94 +
 src/osgEarthFeatures/FeatureTileSource             |     2 +-
 src/osgEarthFeatures/FeatureTileSource.cpp         |    12 +-
 src/osgEarthFeatures/Filter                        |    30 +-
 src/osgEarthFeatures/Filter.cpp                    |    11 +-
 src/osgEarthFeatures/FilterContext                 |    30 +-
 src/osgEarthFeatures/FilterContext.cpp             |    55 +-
 src/osgEarthFeatures/GPULines                      |    96 +
 src/osgEarthFeatures/GPULines.cpp                  |   206 +
 src/osgEarthFeatures/GPULinesScreenProj.glsl       |   181 +
 src/osgEarthFeatures/GeometryCompiler              |    28 +-
 src/osgEarthFeatures/GeometryCompiler.cpp          |    52 +-
 src/osgEarthFeatures/LabelSource                   |     3 +-
 src/osgEarthFeatures/LabelSource.cpp               |    11 +-
 src/osgEarthFeatures/MVT.cpp                       |   339 +-
 src/osgEarthFeatures/OgrUtils.cpp                  |    97 +-
 src/osgEarthFeatures/OptimizerHints                |    78 -
 src/osgEarthFeatures/OptimizerHints.cpp            |    59 -
 src/osgEarthFeatures/PolygonizeLines               |     9 +-
 src/osgEarthFeatures/PolygonizeLines.cpp           |    61 +-
 src/osgEarthFeatures/ResampleFilter                |     1 +
 src/osgEarthFeatures/ResampleFilter.cpp            |     2 +-
 src/osgEarthFeatures/ScaleFilter.cpp               |     1 +
 src/osgEarthFeatures/ScatterFilter.cpp             |     1 +
 src/osgEarthFeatures/ScriptEngine.cpp              |     7 +-
 src/osgEarthFeatures/ScriptFilter.cpp              |     1 +
 src/osgEarthFeatures/Session                       |   128 +-
 src/osgEarthFeatures/Session.cpp                   |    96 +-
 src/{osgEarth => osgEarthFeatures}/Shaders         |    21 +-
 src/osgEarthFeatures/Shaders.cpp.in                |    13 +
 src/osgEarthFeatures/SubstituteModelFilter         |     6 +
 src/osgEarthFeatures/SubstituteModelFilter.cpp     |    16 +-
 src/osgEarthFeatures/TessellateOperator            |     2 +
 src/osgEarthFeatures/TessellateOperator.cpp        |    28 +-
 src/osgEarthFeatures/TextSymbolizer.cpp            |    13 +-
 src/osgEarthFeatures/TransformFilter.cpp           |     2 +
 src/osgEarthFeatures/VirtualFeatureSource          |     8 +-
 src/osgEarthFeatures/VirtualFeatureSource.cpp      |    24 +-
 src/osgEarthProcedural/CMakeLists.txt              |    67 -
 src/osgEarthProcedural/Common                      |    32 -
 src/osgEarthProcedural/CoverageLegend              |    99 -
 src/osgEarthProcedural/CoverageLegend.cpp          |    77 -
 src/osgEarthProcedural/Export                      |    74 -
 src/osgEarthProcedural/Shaders                     |    31 -
 src/osgEarthProcedural/Shaders.cpp.in              |     9 -
 src/osgEarthProcedural/SimplexNoise                |   159 -
 src/osgEarthProcedural/SimplexNoise.cpp            |   556 -
 src/osgEarthProcedural/SplatCatalog                |   161 -
 src/osgEarthProcedural/SplatCatalog.cpp            |   343 -
 src/osgEarthQt/CMakeLists.txt                      |   340 +-
 src/osgEarthQt/DataManager                         |    14 +-
 src/osgEarthQt/DataManager.cpp                     |    42 +-
 src/osgEarthQt/GuiActions                          |     1 +
 src/osgEarthQt/LayerManagerWidget                  |     3 +
 src/osgEarthQt/LayerManagerWidget.cpp              |    30 +-
 src/osgEarthQt/MapCatalogWidget.cpp                |    15 +-
 .../SilverLiningContextNode.cpp                    |     1 +
 src/osgEarthSilverLining/SilverLiningNode          |     2 +-
 src/osgEarthSilverLining/SilverLiningNode.cpp      |    17 +-
 src/osgEarthSplat/CMakeLists.txt                   |    27 +-
 src/osgEarthSplat/Coverage                         |     4 +-
 src/osgEarthSplat/Coverage.cpp                     |     3 +-
 src/osgEarthSplat/Export                           |     2 +-
 src/osgEarthSplat/GroundCover                      |   260 +
 src/osgEarthSplat/GroundCover.FS.glsl              |    29 +
 .../{LandCover.GS.glsl => GroundCover.GS.glsl}     |   243 +-
 .../{LandCover.TCS.glsl => GroundCover.TCS.glsl}   |    24 +-
 .../{LandCover.TES.glsl => GroundCover.TES.glsl}   |     6 +-
 src/osgEarthSplat/GroundCover.cpp                  |   448 +
 src/osgEarthSplat/GroundCoverLayer                 |   163 +
 src/osgEarthSplat/GroundCoverLayer.cpp             |   414 +
 src/osgEarthSplat/LandCover                        |   445 -
 src/osgEarthSplat/LandCover.FS.glsl                |    49 -
 src/osgEarthSplat/LandCover.cpp                    |   438 -
 src/osgEarthSplat/LandCoverTerrainEffect           |    87 -
 src/osgEarthSplat/LandCoverTerrainEffect.cpp       |   238 -
 src/osgEarthSplat/LandCoverTilePatchCallback       |    58 -
 src/osgEarthSplat/LandCoverTilePatchCallback.cpp   |    89 -
 src/osgEarthSplat/LandUseTileSource                |     6 +-
 src/osgEarthSplat/LandUseTileSource.cpp            |     8 +-
 src/osgEarthSplat/NoiseTextureFactory.cpp          |     5 +-
 src/osgEarthSplat/RoadSurfaceLayer                 |   160 +
 src/osgEarthSplat/RoadSurfaceLayer.cpp             |   407 +
 src/osgEarthSplat/Splat.Noise.glsl                 |     1 +
 src/osgEarthSplat/Splat.frag.common.glsl           |    35 -
 src/osgEarthSplat/Splat.frag.glsl                  |   162 +-
 src/osgEarthSplat/Splat.util.glsl                  |     1 +
 src/osgEarthSplat/Splat.vert.model.glsl            |    14 +
 src/osgEarthSplat/Splat.vert.view.glsl             |     7 +-
 src/osgEarthSplat/SplatCatalog                     |     3 +-
 src/osgEarthSplat/SplatExtension                   |    12 +-
 src/osgEarthSplat/SplatExtension.cpp               |    70 +-
 src/osgEarthSplat/SplatLayer                       |   139 +
 src/osgEarthSplat/SplatLayer.cpp                   |   352 +
 src/osgEarthSplat/SplatOptions                     |     6 +-
 src/osgEarthSplat/SplatShaders                     |    12 +-
 src/osgEarthSplat/SplatShaders.cpp.in              |    21 +-
 src/osgEarthSplat/SplatTerrainEffect               |   111 -
 src/osgEarthSplat/SplatTerrainEffect.cpp           |   232 -
 src/osgEarthSplat/Surface                          |    13 +-
 src/osgEarthSplat/Surface.cpp                      |   113 +-
 src/osgEarthSplat/Zone                             |   106 +-
 src/osgEarthSplat/Zone.cpp                         |    94 +-
 src/osgEarthSymbology/GEOS                         |     6 +
 src/osgEarthSymbology/GEOS.cpp                     |    21 +-
 src/osgEarthSymbology/Geometry                     |    10 +
 src/osgEarthSymbology/Geometry.cpp                 |    48 +-
 src/osgEarthSymbology/InstanceResource.cpp         |     2 +-
 src/osgEarthSymbology/LineSymbol                   |    13 +-
 src/osgEarthSymbology/LineSymbol.cpp               |    24 +-
 src/osgEarthSymbology/MarkerSymbolizer.cpp         |     4 +-
 src/osgEarthSymbology/MeshConsolidator.cpp         |     5 +-
 src/osgEarthSymbology/MeshSubdivider.cpp           |    12 +-
 src/osgEarthSymbology/ModelResource                |     2 +-
 src/osgEarthSymbology/ModelResource.cpp            |    14 +-
 src/osgEarthSymbology/PolygonSymbol                |     7 +
 src/osgEarthSymbology/PolygonSymbol.cpp            |     9 +-
 src/osgEarthSymbology/Query                        |    10 +-
 src/osgEarthSymbology/Query.cpp                    |    18 +-
 src/osgEarthSymbology/RenderSymbol                 |    10 +
 src/osgEarthSymbology/RenderSymbol.cpp             |    36 +-
 src/osgEarthSymbology/Resource                     |     9 +
 src/osgEarthSymbology/ResourceCache                |     6 +
 src/osgEarthSymbology/ResourceCache.cpp            |    30 +
 src/osgEarthSymbology/ResourceLibrary              |     1 +
 src/osgEarthSymbology/ResourceLibrary.cpp          |    10 +-
 src/osgEarthSymbology/Skins.cpp                    |    48 +-
 src/osgEarthSymbology/Style                        |     4 +
 src/osgEarthSymbology/StyleSheet                   |     4 +
 src/osgEarthSymbology/StyleSheet.cpp               |    10 +-
 src/osgEarthSymbology/Symbol                       |    13 +-
 src/osgEarthSymbology/TextSymbol                   |     5 -
 src/osgEarthSymbology/TextSymbol.cpp               |    10 -
 src/osgEarthTriton/CMakeLists.txt                  |     2 +
 src/osgEarthTriton/TritonAPIWrapper                |     8 +-
 src/osgEarthTriton/TritonContext                   |    24 +-
 src/osgEarthTriton/TritonContext.cpp               |    46 +-
 src/osgEarthTriton/TritonDrawable                  |     8 +-
 src/osgEarthTriton/TritonDrawable.cpp              |   101 +-
 src/osgEarthTriton/{TritonOptions => TritonLayer}  |    83 +-
 src/osgEarthTriton/TritonLayer.cpp                 |    96 +
 src/osgEarthTriton/TritonNode                      |    26 +-
 src/osgEarthTriton/TritonNode.cpp                  |   125 +-
 src/osgEarthTriton/TritonOptions                   |    16 +-
 src/osgEarthUtil/AnnotationEvents.cpp              |     1 +
 src/osgEarthUtil/AtlasBuilder.cpp                  |    17 +-
 src/osgEarthUtil/AutoClipPlaneHandler.cpp          |     4 -
 src/osgEarthUtil/CMakeLists.txt                    |    57 +-
 src/osgEarthUtil/ContourMap                        |     9 +-
 src/osgEarthUtil/ContourMap.cpp                    |     7 +-
 src/osgEarthUtil/ContourMap.frag.glsl              |     6 +-
 src/osgEarthUtil/ContourMap.vert.glsl              |     6 +-
 src/osgEarthUtil/Controls                          |    10 +-
 src/osgEarthUtil/Controls.cpp                      |    81 +-
 src/osgEarthUtil/EarthManipulator                  |    30 +-
 src/osgEarthUtil/EarthManipulator.cpp              |    97 +-
 src/osgEarthUtil/ExampleResources.cpp              |    37 +-
 src/osgEarthUtil/FeatureQueryTool                  |    57 -
 src/osgEarthUtil/FeatureQueryTool.cpp              |    35 -
 src/osgEarthUtil/FlatteningLayer                   |   217 +
 src/osgEarthUtil/FlatteningLayer.cpp               |   985 ++
 src/osgEarthUtil/Fog.cpp                           |     2 +-
 src/osgEarthUtil/Fog.frag.glsl                     |     4 +-
 src/osgEarthUtil/Fog.vert.glsl                     |     4 +-
 src/osgEarthUtil/FractalElevationLayer             |   154 +
 src/osgEarthUtil/FractalElevationLayer.cpp         |   378 +
 src/osgEarthUtil/GARSGraticule                     |   115 +
 src/osgEarthUtil/GARSGraticule.cpp                 |   382 +
 src/osgEarthUtil/GeodeticGraticule                 |   256 +-
 src/osgEarthUtil/GeodeticGraticule.cpp             |   860 +-
 src/osgEarthUtil/Graticule.frag.glsl               |    27 +-
 src/osgEarthUtil/Graticule.vert.glsl               |     8 +-
 src/osgEarthUtil/GraticuleExtension                |    75 -
 src/osgEarthUtil/GraticuleExtension.cpp            |    83 -
 src/osgEarthUtil/GraticuleNode                     |   118 -
 src/osgEarthUtil/GraticuleNode.cpp                 |   425 -
 src/osgEarthUtil/GraticuleOptions                  |   114 -
 src/osgEarthUtil/GraticuleTerrainEffect            |    61 -
 src/osgEarthUtil/GraticuleTerrainEffect.cpp        |    87 -
 src/osgEarthUtil/HTM                               |    52 +-
 src/osgEarthUtil/HTM.cpp                           |   162 +-
 src/osgEarthUtil/LODBlending.cpp                   |    42 +-
 src/osgEarthUtil/LinearLineOfSight.cpp             |    18 +-
 src/osgEarthUtil/LogDepthBuffer.VertOnly.vert.glsl |     4 +-
 src/osgEarthUtil/LogDepthBuffer.frag.glsl          |    12 +-
 src/osgEarthUtil/LogDepthBuffer.vert.glsl          |     7 +-
 src/osgEarthUtil/LogarithmicDepthBuffer            |     3 +
 src/osgEarthUtil/LogarithmicDepthBuffer.cpp        |     8 +-
 src/osgEarthUtil/MGRSGraticule                     |   123 +-
 src/osgEarthUtil/MGRSGraticule.cpp                 |  1357 ++-
 src/osgEarthUtil/MouseCoordsTool.cpp               |    22 +-
 src/osgEarthUtil/MultiElevationLayer               |   100 +
 src/osgEarthUtil/MultiElevationLayer.cpp           |   179 +
 src/osgEarthUtil/NightColorFilter.cpp              |     7 +-
 src/osgEarthUtil/ObjectLocator                     |   204 -
 src/osgEarthUtil/ObjectLocator.cpp                 |   288 -
 src/osgEarthUtil/PolyhedralLineOfSight.cpp         |     6 +-
 src/osgEarthUtil/RTTPicker                         |    28 +-
 src/osgEarthUtil/RTTPicker.cpp                     |   215 +-
 src/osgEarthUtil/RadialLineOfSight.cpp             |    26 +-
 src/osgEarthUtil/Shaders                           |     5 +-
 src/osgEarthUtil/Shaders.cpp.in                    |     6 +
 src/osgEarthUtil/Shadowing.cpp                     |     3 +-
 src/osgEarthUtil/Shadowing.frag.glsl               |    24 +-
 src/osgEarthUtil/Shadowing.vert.glsl               |     1 +
 src/osgEarthUtil/SimpleOceanLayer                  |   146 +
 src/osgEarthUtil/SimpleOceanLayer.cpp              |   202 +
 src/osgEarthUtil/SimpleOceanLayer.frag.glsl        |    46 +
 src/osgEarthUtil/SimpleOceanLayer.vert.glsl        |    33 +
 src/osgEarthUtil/SimplePager                       |     4 +
 src/osgEarthUtil/SimplePager.cpp                   |    52 +-
 src/osgEarthUtil/Sky                               |    34 +-
 src/osgEarthUtil/Sky.cpp                           |    38 +-
 src/osgEarthUtil/SpatialData                       |     1 -
 src/osgEarthUtil/SpatialData.cpp                   |    51 +-
 src/osgEarthUtil/TFS                               |     2 +-
 src/osgEarthUtil/TFSPackager.cpp                   |    20 +-
 src/osgEarthUtil/TMS.cpp                           |    31 +-
 src/osgEarthUtil/TMSBackFiller.cpp                 |     5 +-
 src/osgEarthUtil/TMSPackager.cpp                   |   139 +-
 src/osgEarthUtil/TerrainProfile                    |     2 +-
 src/osgEarthUtil/TerrainProfile.cpp                |     2 +-
 src/osgEarthUtil/TileIndex.cpp                     |     9 +-
 src/osgEarthUtil/TileIndexBuilder.cpp              |     1 +
 src/osgEarthUtil/TopologyGraph                     |   147 +
 .../TopologyGraph.cpp}                             |   333 +-
 src/osgEarthUtil/UTMGraticule                      |   150 +-
 src/osgEarthUtil/UTMGraticule.cpp                  |   344 +-
 src/osgEarthUtil/UTMLabelingEngine                 |    79 +
 src/osgEarthUtil/UTMLabelingEngine.cpp             |   511 +
 src/osgEarthUtil/ViewFitter                        |    65 +
 src/osgEarthUtil/ViewFitter.cpp                    |   230 +
 src/osgEarthUtil/WMS.cpp                           |    16 +
 src/tests/CMakeLists.txt                           |    31 +
 src/tests/osgEarth_tests/CMakeLists.txt            |    17 +
 src/tests/osgEarth_tests/EndianTests.cpp           |    95 +
 src/tests/osgEarth_tests/FeatureTests.cpp          |    53 +
 src/tests/osgEarth_tests/GeoExtentTests.cpp        |   238 +
 src/tests/osgEarth_tests/ImageLayerTests.cpp       |    59 +
 src/tests/osgEarth_tests/SpatialReferenceTests.cpp |    92 +
 src/tests/osgEarth_tests/ThreadingTests.cpp        |   135 +
 .../AutoScale => tests/osgEarth_tests/main.cpp}    |    29 +-
 tests/aeqd.earth                                   |    22 +
 tests/annotation.earth                             |    12 +-
 tests/annotation_dateline.earth                    |    27 +
 tests/annotation_dateline_projected.earth          |    45 +
 tests/boston-gpu.earth                             |    28 +-
 tests/boston.earth                                 |    56 +-
 tests/boston_tfs.earth                             |    50 -
 tests/city_labels.xml                              |    25 +
 tests/clouds.earth                                 |    57 +-
 tests/datum_override.earth                         |     3 +-
 tests/{night.earth => day_night_mp.earth}          |     0
 tests/day_night_rex.earth                          |    61 +
 tests/detail_texture.earth                         |     7 +-
 tests/fade_elevation.earth                         |    59 -
 tests/feature_clip_plane.earth                     |    14 +-
 tests/feature_country_boundaries.earth             |    43 +-
 tests/feature_custom_filters.earth                 |    16 +-
 tests/feature_draped_lines.earth                   |    13 +-
 tests/feature_draped_polygons.earth                |    23 +-
 tests/feature_extrude.earth                        |    38 +-
 tests/feature_geom.earth                           |    28 +-
 tests/feature_gpx.earth                            |    11 +-
 tests/feature_inline_geometry.earth                |    18 +-
 tests/feature_labels.earth                         |    45 +-
 tests/feature_labels_script.earth                  |    26 +-
 tests/feature_levels_and_selectors.earth           |    10 +-
 tests/feature_model_scatter.earth                  |     4 +-
 tests/feature_models.earth                         |    11 +-
 tests/feature_occlusion_culling.earth              |    27 +-
 tests/feature_overlay.earth                        |    38 -
 tests/feature_population_cylinders.earth           |    60 +-
 tests/feature_raster.earth                         |    67 +-
 tests/feature_rasterize.earth                      |     4 -
 tests/feature_tfs.earth                            |    23 +-
 tests/feature_tfs_scripting.earth                  |    33 +-
 tests/feature_wfs.earth                            |    21 +-
 tests/fractal_elevation.earth                      |    99 +
 tests/gdal_multiple_files.earth                    |     4 +-
 tests/geomshader.earth                             |    20 +-
 tests/glsl.earth                                   |    30 +-
 tests/glsl_filter.earth                            |    16 -
 tests/graticule.earth                              |    36 -
 tests/graticules.earth                             |    79 +
 tests/intersect_filter.earth                       |     7 +-
 tests/land_cover_mixed.earth                       |    84 +
 tests/lod_blending.earth                           |    31 -
 tests/mapbox.earth                                 |   178 +
 tests/mask.earth                                   |     7 +-
 tests/mb_tiles.earth                               |    10 +-
 tests/min_max_range.earth                          |     4 +
 tests/multiple_heightfields.earth                  |     7 +-
 tests/nodata.earth                                 |     6 +-
 tests/noise.earth                                  |    77 -
 tests/normalmap.earth                              |    22 -
 tests/ocean.earth                                  |    31 +-
 tests/{ocean.earth => ocean_no_elevation.earth}    |    58 +-
 tests/openstreetmap_buildings.earth                |    50 +-
 tests/openstreetmap_flat.earth                     |     7 +-
 tests/openstreetmap_full.earth                     |    80 +-
 ...eathermap_clouds.earth => openweathermap.earth} |    16 +-
 tests/openweathermap_precipitation.earth           |    31 -
 tests/openweathermap_pressure.earth                |    31 -
 ...eadymap.earth => readymap-elevation-only.earth} |    10 +-
 tests/readymap-priority.earth                      |    35 -
 tests/readymap-rex.earth                           |     5 +-
 tests/readymap.earth                               |    10 +-
 ...adymap_pixel_size.earth => readymap_flat.earth} |     9 +-
 tests/roads-flattened.earth                        |   198 +
 tests/roads-test.earth                             |   107 +
 tests/roads.earth                                  |   109 +
 tests/scene_clamping.earth                         |   130 +
 tests/{gdal_tiff.earth => simple.earth}            |     7 +-
 tests/splat-blended-with-imagery.earth             |    47 +
 tests/{splat-edit.bat => splat-detail-tool.bat}    |     0
 tests/splat-gpunoise.bat                           |    31 -
 tests/splat-groundcover-tool.bat                   |    15 +
 tests/splat-ranges.earth                           |    55 -
 tests/splat-server.earth                           |   105 -
 tests/splat-with-mask-layer.earth                  |    92 +
 tests/splat-with-multiple-zones.earth              |   116 +
 tests/splat-with-rasterized-land-cover.earth       |    81 +
 tests/splat-with-vectors.earth                     |   109 -
 tests/splat.bat                                    |    14 -
 tests/splat.earth                                  |   132 +-
 tests/tess-coastlines.earth                        |   157 -
 tests/tess-masking.earth                           |   176 -
 tests/tess-terrain.earth                           |   146 -
 tests/tess_screen_space.earth                      |   120 -
 tests/test-morphing.earth                          |    50 -
 tests/triton.earth                                 |    31 +-
 tests/{readymap-osm.earth => utm.earth}            |    24 +-
 tests/viewpoints.xml                               |   131 +-
 tests/viewpoints_flat.xml                          |    74 +
 992 files changed, 56320 insertions(+), 38149 deletions(-)

diff --git a/.travis.yml b/.travis.yml
index afdce64..6999b8c 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -17,20 +17,20 @@ before_install:
 - sudo apt-get update -qq
 - sudo apt-get install -y cmake
 #- sudo apt-get install -y libopenscenegraph-dev
-- sudo apt-get install -y --force-yes openscenegraph=3.4.0
+- sudo apt-get install -y --force-yes openscenegraph=3.4.1
 - sudo apt-get install -y libgdal-dev
 - sudo apt-get install -y libgeos-dev
 - sudo apt-get install -y libsqlite3-dev
 - sudo apt-get install -y protobuf-compiler libprotobuf-dev
+- sudo apt-get install -y libpoco-dev
 # Debug print out osgversion
 - osgversion
 script:
-- if [ "${COVERITY_SCAN_BRANCH}" != 1 ]; then cmake -DCMAKE_BUILD_TYPE=Release . && make -j3 ; fi
+- if [ "${COVERITY_SCAN_BRANCH}" != 1 ]; then cmake -DCMAKE_BUILD_TYPE=Release . && make -j3 && sudo make install && sudo ldconfig; fi
+- if [ "${COVERITY_SCAN_BRANCH}" != 1 ]; then cd tests && osgEarth_tests ; fi
 notifications:
-  email:
-    recipients:
-    - jasonbeverage at pelicanmapping.com
-    - gwaldron at pelicanmapping.com
+  slack:
+    secure: E/9LpduB9vyavYKA4HgQxf48JB3cQmKOo0We829cox67aixQCKhYjHG0KmT6wnjVVKWRbHru0ho7Gi1NuVNdGXAOG7PMP7XFOJB7pBfczy7HmQhsAttX/dTltb0XzRFx9O5qk3q2Vnc7JXKO5HbFDWCYh0O3RFoph9XzTfAQBdBNsy/wyYD0fogYsWk43qQPI8lLukBfJ705sn2un+bkfgnuS9g/uuQmmqIhXwkAamAUaioryVHDaLz2e1bMKaq62Rl4yV+lbY1/hsg8Ob1o1jm5MnEjcHuVU1uugX34FZZFVWrF3OuEvfEmDv2tN2Q72Zz/xmJR37osZ8x55gRg5w+lal0VEo/U7leMABzu8/k4DXsPLGEY8ft3aalBbVw2qh/IMOc8Jyq4/wg6/2aet0mS9FAmWy59HnDyYTsZtUdiU4aRdVvQcgJs5JEXypMYlVCCv325ixaA6CxGplGAqST2XYu4U+QP [...]
 addons:
   coverity_scan:
     project:
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 0f0b284..a85db48 100755
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,4 +1,4 @@
-CMAKE_MINIMUM_REQUIRED(VERSION 2.6.4 FATAL_ERROR)
+CMAKE_MINIMUM_REQUIRED(VERSION 2.8.11 FATAL_ERROR)
 
 if(COMMAND cmake_policy)
     # Works around warnings libraries linked against that don't
@@ -9,18 +9,9 @@ if(COMMAND cmake_policy)
     # statements.
     cmake_policy(SET CMP0005 OLD)
     
-    
-    IF(COMMAND cmake_policy)
-            IF(${CMAKE_MAJOR_VERSION} GREATER 2)
-                # Qt5 qt5_use_modules usage was causing "Policy CMP0043 is not set: Ignore COMPILE_DEFINITIONS_<Config> properties." warnings
-                cmake_policy(SET CMP0043 NEW)
-            ENDIF()
-    ENDIF()
+    # Qt5 qt5_use_modules usage was causing "Policy CMP0043 is not set: Ignore COMPILE_DEFINITIONS_<Config> properties." warnings
+    cmake_policy(SET CMP0043 NEW)
 
-    # disable autolinking to qtmain as we have our own main() functions (new in Qt 5.1)
-    if(NOT "${CMAKE_VERSION}" VERSION_LESS 2.8.11)
-        cmake_policy(SET CMP0020 OLD)
-    endif(NOT "${CMAKE_VERSION}" VERSION_LESS 2.8.11)
 endif(COMMAND cmake_policy)
 
 #
@@ -32,7 +23,7 @@ SET_PROPERTY( GLOBAL PROPERTY PREDEFINED_TARGETS_FOLDER "CMake Targets" )
 PROJECT(OSGEARTH)
 
 SET(OSGEARTH_MAJOR_VERSION 2)
-SET(OSGEARTH_MINOR_VERSION 8)
+SET(OSGEARTH_MINOR_VERSION 9)
 SET(OSGEARTH_PATCH_VERSION 0)
 SET(OSGEARTH_SOVERSION     0)
 
@@ -70,33 +61,29 @@ IF (OSGEARTH_EMBED_GIT_SHA)
   get_git_head_revision(GIT_REFSPEC OSGEARTH_GIT_SHA1)
 ENDIF (OSGEARTH_EMBED_GIT_SHA)
 
-# IPHONE_PORT at tom
-# Trying to get CMake to generate an XCode IPhone project, current efforts are to get iphoneos sdk 3.1 working
-# Added option which needs manually setting to select the IPhone SDK for building. We can only have one of the below 
-# set to true. Should realy have an OSG_BUILD_PLATFORM variable that we set to our desired platform
-OPTION(OSG_BUILD_PLATFORM_IPHONE "Enable IPhoneSDK Device support" OFF)
-OPTION(OSG_BUILD_PLATFORM_IPHONE_SIMULATOR "Enable IPhoneSDK Simulator support" OFF)
-IF(OSG_BUILD_PLATFORM_IPHONE OR OSG_BUILD_PLATFORM_IPHONE_SIMULATOR)
-  
-  #you need to manually set the default sdk version here
-  SET (IPHONE_SDKVER "6.1")
-  #the below is taken from ogre, it states the gcc stuff needs to happen before PROJECT() is called. I've no clue if we even need it
-  # Force gcc <= 4.2 on iPhone
-  include(CMakeForceCompiler)
-  CMAKE_FORCE_C_COMPILER(llvm-gcc-4.2 GNU)
-  CMAKE_FORCE_CXX_COMPILER(llvm-gcc-4.2 GNU)
-  SET(GCC_THUMB_SUPPORT NO)
-
-        #set either the device sdk or the simulator sdk. Can't find away to separate these in the same project
-        IF(OSG_BUILD_PLATFORM_IPHONE)
-            SET (IPHONE_DEVROOT "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer")
-            SET (IPHONE_SDKROOT "${IPHONE_DEVROOT}/SDKs/iPhoneOS${IPHONE_SDKVER}.sdk")
-        ELSE()
-            SET (IPHONE_DEVROOT "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer")
-            SET (IPHONE_SDKROOT "${IPHONE_DEVROOT}/SDKs/iPhoneSimulator${IPHONE_SDKVER}.sdk")
-        ENDIF()
+#
+SET(OSGEARTH_USE_GLES FALSE)
+
+# check for iOS build
+OPTION(OSGEARTH_BUILD_PLATFORM_IPHONE "Enable IPhoneSDK Device support" OFF)
+OPTION(OSGEARTH_BUILD_PLATFORM_IPHONE_SIMULATOR "Enable IPhoneSDK Simulator support" OFF)
+
+IF(OSGEARTH_BUILD_PLATFORM_IPHONE OR OSGEARTH_BUILD_PLATFORM_IPHONE_SIMULATOR)
+    SET(OSGEARTH_USE_GLES TRUE)
+    #you need to manually set the default sdk version here
+    SET (IPHONE_SDKVER "10.2" CACHE STRING "iOS SDK-Version")
+    SET (IPHONE_VERSION_MIN "8.0" CACHE STRING "IOS minimum os version, use 7.0 or greater to get 64bit support")
+    SET (IPHONE_PLATFORMSROOT "/Applications/Xcode.app/Contents/Developer/Platforms")
+
+    IF(OSGEARTH_BUILD_PLATFORM_IPHONE)
+        SET(CMAKE_OSX_SYSROOT "${IPHONE_PLATFORMSROOT}/iPhoneOS.platform/Developer/SDKs/iPhoneOS${IPHONE_SDKVER}.sdk" CACHE STRING "System root for iOS" FORCE)
+        SET(CMAKE_OSX_ARCHITECTURES "armv7;armv7s;arm64" CACHE STRING "Build architectures for iOS" FORCE)
+    ELSE()
+        SET(CMAKE_OSX_SYSROOT "${IPHONE_PLATFORMSROOT}/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator${IPHONE_SDKVER}.sdk" CACHE STRING "System root for iOS" FORCE)
+        SET(CMAKE_OSX_ARCHITECTURES "i386" CACHE STRING "Build architectures for iOS Simulator" FORCE)
+    ENDIF()
 ENDIF ()
-# IPHONE_PORT at tom
+
 
 # check if STLport is required
 OPTION(USE_STLPORT "Set to ON to build OSGEARTH with stlport instead of the default STL library." OFF)
@@ -122,7 +109,11 @@ IF(CMAKE_SYSTEM MATCHES IRIX)
     SET(CMAKE_THREAD_LIBS_INIT "" CACHE INTERNAL "")
 ENDIF(CMAKE_SYSTEM MATCHES IRIX)
 
-FIND_PACKAGE(OpenGL)
+IF (OSGEARTH_USE_GLES)
+    FIND_PACKAGE(OpenGLES) 
+ELSE ()
+    FIND_PACKAGE(OpenGL)
+ENDIF (OSGEARTH_USE_GLES)
 
 FIND_PACKAGE(CURL)
 FIND_PACKAGE(GDAL)
@@ -137,37 +128,13 @@ FIND_PACKAGE(RocksDB)
 FIND_PACKAGE(SilverLining QUIET)
 FIND_PACKAGE(Triton QUIET)
 
-FIND_PACKAGE(Protobuf QUIET)
-
-# JavaScript Engines:
-SET(V8_DIR "" CACHE PATH "set to base V8 install path")
-FIND_PACKAGE(V8)
+FIND_PACKAGE(PROTOBUF QUIET)
 
 SET (WITH_EXTERNAL_DUKTAPE FALSE CACHE BOOL "Use bundled or system wide version of Duktape")
 IF (WITH_EXTERNAL_DUKTAPE)
     FIND_PACKAGE(Duktape)
 ENDIF (WITH_EXTERNAL_DUKTAPE)
 
-OPTION(USE_V8 "Use V8 instead of Duktape if V8 is found" OFF)
-
-FIND_PACKAGE(Qt5Core QUIET)
-FIND_PACKAGE(Qt5Widgets QUIET)
-FIND_PACKAGE(Qt5Gui QUIET)
-FIND_PACKAGE(Qt5OpenGL QUIET)
-IF ( Qt5Core_FOUND AND Qt5Widgets_FOUND AND Qt5Gui_FOUND AND Qt5OpenGL_FOUND )
-    SET(QT_INCLUDES ${Qt5Widgets_INCLUDE_DIRS} ${Qt5OpenGL_INCLUDE_DIRS})
-ELSE()
-    FIND_PACKAGE(Qt4)
-    IF (QT4_FOUND)
-        INCLUDE(${QT_USE_FILE})
-		SET(QT_INCLUDES ${QT_INCLUDES} ${QT_INCLUDE_DIR} ${QT_QTCORE_INCLUDE_DIR} ${QT_QTGUI_INCLUDE_DIR}${QT_QTOPENGL_INCLUDE_DIR} )
-        SET(QT_ALL_LIBRARIES ${QT_LIBRARIES} ${QT_QTCORE_LIBRARY} ${QT_QTWEBKIT_LIBRARY} ${QT_QTNETWORK_LIBRARY} ${QT_QTXML_LIBRARY} ${QT_QTOPENGL_LIBRARY})
-    ENDIF (QT4_FOUND)
-ENDIF ()
-
-OPTION(OSGEARTH_USE_QT "Enable to use Qt (build Qt-dependent libraries, plugins and examples)" OFF)
-OPTION(OSGEARTH_QT_BUILD_LEGACY_WIDGETS "Build the legacy Qt widgets" OFF)
-
 # option to install shaders:
 OPTION(OSGEARTH_INSTALL_SHADERS "Whether to deploy GLSL shaders when doing a Make INSTALL" OFF)
 
@@ -184,12 +151,57 @@ IF(UNIX)
     FIND_LIBRARY(MATH_LIBRARY m)
 ENDIF(UNIX)
 
+
+OPTION(OSGEARTH_QT_BUILD "Enable to use Qt (build Qt-dependent libraries, plugins and examples)" OFF)
+OPTION(OSGEARTH_QT_BUILD_LEGACY_WIDGETS "Build the legacy Qt widgets" OFF)
+set(OSGEARTH_QT_VERSION 5 CACHE STRING "Qt version to use")
+
+IF(OSGEARTH_QT_BUILD)
+  # To select a specific version of QT define DESIRED_QT_VERSION
+  # via cmake -DDESIRED_QT_VERSION=5
+  # QUIET option disables messages if the package cannot be found.
+  IF  (OSGEARTH_QT_VERSION)
+      IF  (OSGEARTH_QT_VERSION MATCHES "5")
+          FIND_PACKAGE(Qt5Widgets REQUIRED)
+      ELSEIF (OSGEARTH_QT_VERSION MATCHES "4")
+          FIND_PACKAGE(Qt4 REQUIRED)
+      ENDIF()
+  ELSE()
+      FIND_PACKAGE(Qt5Widgets QUIET)
+      IF ( NOT Qt5Widgets_FOUND )
+          FIND_PACKAGE(Qt4 REQUIRED)
+      ENDIF()
+  ENDIF()
+ENDIF(OSGEARTH_QT_BUILD)
+
+IF(Qt5Widgets_FOUND)
+  message(STATUS "Qt: Using version 5")
+  FIND_PACKAGE(Qt5Core QUIET)
+  FIND_PACKAGE(Qt5Gui QUIET)
+  FIND_PACKAGE(Qt5OpenGL QUIET)
+  FIND_PACKAGE(Qt5OpenGLExtensions QUIET)
+  IF ( Qt5Core_FOUND AND Qt5Widgets_FOUND AND Qt5Gui_FOUND AND Qt5OpenGL_FOUND AND Qt5OpenGLExtensions_FOUND )
+    SET(QT_INCLUDES ${Qt5Widgets_INCLUDE_DIRS} ${Qt5OpenGL_INCLUDE_DIRS} ${Qt5OpenGLExtensions_INCLUDE_DIRS})
+  ENDIF ()
+ELSEIF(QT4_FOUND)
+  message(STATUS "Qt: Using version 4")
+  INCLUDE(${QT_USE_FILE})
+  SET(QT_INCLUDES ${QT_INCLUDES} ${QT_INCLUDE_DIR} ${QT_QTCORE_INCLUDE_DIR} ${QT_QTGUI_INCLUDE_DIR}${QT_QTOPENGL_INCLUDE_DIR} )
+  SET(QT_ALL_LIBRARIES ${QT_LIBRARIES} ${QT_QTCORE_LIBRARY} ${QT_QTWEBKIT_LIBRARY} ${QT_QTNETWORK_LIBRARY} ${QT_QTXML_LIBRARY} ${QT_QTOPENGL_LIBRARY})
+ENDIF ()
+
+
 # Common global definitions
 #ADD_DEFINITIONS(-D)
 # Platform specific definitions
 
 
 IF(WIN32)
+  FIND_PACKAGE(GLCORE)
+  IF(GLCORE_FOUND)
+      INCLUDE_DIRECTORIES( ${GLCORE_INCLUDE_DIR} )
+  ENDIF()    
+
   IF(MSVC)
         # This option is to enable the /MP switch for Visual Studio 2005 and above compilers
         OPTION(WIN32_USE_MP "Set to ON to build osgEarth with the /MP option (Visual Studio 2005 and above)." OFF)
@@ -209,25 +221,6 @@ IF(WIN32)
     ENDIF(MSVC)
 ENDIF(WIN32)
 
-########################################################################################################
-##### these were settings located in SetupCommon.cmake used in Luigi builds.... find out what are useful
-########################################################################################################
-#luigi#SET(CMAKE_VERBOSE_MAKEFILE TRUE)
-#luigi#SET(CMAKE_SKIP_RPATH TRUE)
-#luigi#SET(CMAKE_SKIP_RULE_DEPENDENCY TRUE)
-#luigi#IF(UNIX)
-#luigi#    LIST_CONTAINS(contains "g++" ${CMAKE_CXX_COMPILER_LIST})
-#luigi#    IF (contains)
-#luigi#        MESSAGE(${MY_MESSAGE_DEFAULT} "${CMAKE_CURRENT_LIST_FILE}:${CMAKE_CURRENT_LIST_LINE} setting  CMAKE_CXX_COMPILER to g++")
-#luigi#        SET(CMAKE_CXX_COMPILER "g++")
-#luigi#        SET(CMAKE_CXX_COMPILER_LOADED 2)
-#luigi#        SET(CMAKE_CXX_COMPILER_WORKS 2)
-#luigi#    ENDIF (contains)
-#luigi#    SET(CMAKE_CXX_FLAGS_RELEASE "-O2")
-#luigi#    SET(CMAKE_CXX_FLAGS_DEBUG "-ggdb -gstabs")
-#luigi#ENDIF(UNIX)
-########################################################################################################
-
 # Common to all platforms:
 
 SET(CMAKE_DEBUG_POSTFIX  "d" CACHE STRING "add a postfix, usually d on windows")
@@ -235,6 +228,14 @@ SET(CMAKE_RELEASE_POSTFIX "" CACHE STRING "add a postfix, usually empty on windo
 SET(CMAKE_RELWITHDEBINFO_POSTFIX "rd" CACHE STRING "add a postfix, usually empty on windows")
 SET(CMAKE_MINSIZEREL_POSTFIX "s" CACHE STRING "add a postfix, usually empty on windows")
 
+INCLUDE(OsgEarthMacroUtils)
+
+DETECT_OSG_VERSION()
+
+IF (NOT OPENSCENEGRAPH_VERSION)
+	SET(OPENSCENEGRAPH_VERSION ${OPENSCENEGRAPH_MAJOR_VERSION}.${OPENSCENEGRAPH_MINOR_VERSION}.${OPENSCENEGRAPH_PATCH_VERSION})
+ENDIF(NOT OPENSCENEGRAPH_VERSION)
+
 FIND_PACKAGE(OSG)
 
 # Make the headers visible to everything
@@ -280,11 +281,6 @@ SET(LIBRARY_OUTPUT_PATH ${OUTPUT_LIBDIR})
 LINK_DIRECTORIES( ${LINK_DIRECTORIES} ${OUTPUT_LIBDIR} )
 
 
-#SET(INSTALL_BINDIR VIRTUALPLANETBUILDER/bin)
-#SET(INSTALL_INCDIR VIRTUALPLANETBUILDER/include)
-#SET(INSTALL_LIBDIR VIRTUALPLANETBUILDER/lib)
-#SET(INSTALL_DOCDIR VIRTUALPLANETBUILDER/doc)
-
 ################################################################################
 # User Options
 
@@ -330,86 +326,44 @@ ENDIF(INSTALL_TO_OSG_DIR)
 # osgEarth Examples
 OPTION(BUILD_OSGEARTH_EXAMPLES "Enable to build osgEarth Examples" ON)
 
-INCLUDE(OsgEarthMacroUtils)
-
-DETECT_OSG_VERSION()
-
-SET(OPENSCENEGRAPH_VERSION ${OPENSCENEGRAPH_MAJOR_VERSION}.${OPENSCENEGRAPH_MINOR_VERSION}.${OPENSCENEGRAPH_PATCH_VERSION})
-
 # OE Core
 ADD_SUBDIRECTORY(src)
 
-# VPB Applications
-# OPTION(BUILD_APPLICATIONS "Enable to build OSGEARTH Applications" ON)
-# IF   (BUILD_APPLICATIONS)
-    # ADD_SUBDIRECTORY(applications)
-# ENDIF(BUILD_APPLICATIONS)
-
-
 
 # Set defaults for Universal Binaries. We want 32-bit Intel/PPC on 10.4
 # and 32/64-bit Intel/PPC on >= 10.5. Anything <= 10.3 doesn't support.
 IF(APPLE)
 
-        #Here we check if the user specified IPhone SDK
-    IF(OSG_BUILD_PLATFORM_IPHONE OR OSG_BUILD_PLATFORM_IPHONE_SIMULATOR)
-
-        IF(OSG_BUILD_PLATFORM_IPHONE)
-            SET(CMAKE_OSX_ARCHITECTURES "armv7" CACHE STRING "Build architectures for iOS" FORCE)
-            SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -miphoneos-version-min=4.1 -mno-thumb -arch armv7 -pipe -no-cpp-precomp" CACHE STRING "Flags used by the compiler during all build types." FORCE)
-        ELSE()
-            #simulator uses i386 architectures
-            SET(CMAKE_OSX_ARCHITECTURES "i386" CACHE STRING "Build architectures for iOS Simulator" FORCE)
-            SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mno-thumb -arch i386 -pipe -no-cpp-precomp" CACHE STRING "Flags used by the compiler during all build types." FORCE)
-        ENDIF()
-
-        #here we set the specific iphone sdk version. We can only set either device or simulator sdk. So if you want both you currently have to have two seperate projects
-        SET(CMAKE_OSX_SYSROOT "${IPHONE_SDKROOT}" CACHE STRING "System root for iOS" FORCE)
-
-        #hack, force link to opengles
-        set(CMAKE_EXE_LINKER_FLAGS "-framework Foundation -framework OpenGLES")
-
-        #use the IPhone windowing system
-        SET(OSG_WINDOWING_SYSTEM "IOS" CACHE STRING "Forced IPhone windowing system on iOS"  FORCE)
-        SET(OSG_DEFAULT_IMAGE_PLUGIN_FOR_OSX "imageio" CACHE STRING "Forced imageio default image plugin for iOS" FORCE)
-
-        #I think this or similar will be required for IPhone apps
-        #OPTION(OSG_BUILD_APPLICATION_BUNDLES "Enable the building of applications and examples as OSX Bundles" ON)
-
-    ELSE()
-
-        # These are just defaults/recommendations, but how we want to build
-        # out of the box. But the user needs to be able to change these options.
-        # So we must only set the values the first time CMake is run, or we
-        # will overwrite any changes the user sets.
-        # FORCE is used because the options are not reflected in the UI otherwise.
-        # Seems like a good place to add version specific compiler flags too.
-        IF(NOT OSGEARTH_CONFIG_HAS_BEEN_RUN_BEFORE)
-            # This is really fragile, but CMake doesn't provide the OS system
-            # version information we need. (Darwin versions can be changed
-            # independently of OS X versions.)
-            # It does look like CMake handles the CMAKE_OSX_SYSROOT automatically.
-            IF(EXISTS /Developer/SDKs/10.5.sdk)
-                SET(CMAKE_OSX_ARCHITECTURES "ppc;i386;ppc64;x86_64" CACHE STRING "Build architectures for OSX" FORCE)
-                SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mmacosx-version-min=10.5 -ftree-vectorize -fvisibility-inlines-hidden" CACHE STRING "Flags used by the compiler during all build types." FORCE)
-            ELSE(EXISTS /Developer/SDKs/10.5.sdk)
-                IF(EXISTS /Developer/SDKs/MacOSX10.4u.sdk)
-                    SET(CMAKE_OSX_ARCHITECTURES "ppc;i386" CACHE STRING "Build architectures for OSX" FORCE)
-                    SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mmacosx-version-min=10.4 -ftree-vectorize -fvisibility-inlines-hidden" CACHE STRING "Flags used by the compiler during all build types." FORCE)
-                ELSE(EXISTS /Developer/SDKs/MacOSX10.4u.sdk)
-                    # No Universal Binary support
-                    # Should break down further to set the -mmacosx-version-min,
-                    # but the SDK detection is too unreliable here.
-                ENDIF(EXISTS /Developer/SDKs/MacOSX10.4u.sdk)
-            ENDIF(EXISTS /Developer/SDKs/10.5.sdk)
-        ENDIF(NOT OSGEARTH_CONFIG_HAS_BEEN_RUN_BEFORE)
-
-
-        OPTION(OSGEARTH_BUILD_APPLICATION_BUNDLES "Enable the building of applications and examples as OSX Bundles" OFF)
-        OPTION(OSGEARTH_BUILD_FRAMEWORKS "Compile frameworks instead of dylibs" OFF)
-        SET(OSGEARTH_BUILD_FRAMEWORKS_INSTALL_NAME_DIR "@executable_path/../Frameworks" CACHE STRING "Install name dir for compiled frameworks")
-
-    ENDIF()
+    # These are just defaults/recommendations, but how we want to build
+    # out of the box. But the user needs to be able to change these options.
+    # So we must only set the values the first time CMake is run, or we
+    # will overwrite any changes the user sets.
+    # FORCE is used because the options are not reflected in the UI otherwise.
+    # Seems like a good place to add version specific compiler flags too.
+    IF(NOT OSGEARTH_CONFIG_HAS_BEEN_RUN_BEFORE)
+        # This is really fragile, but CMake doesn't provide the OS system
+        # version information we need. (Darwin versions can be changed
+        # independently of OS X versions.)
+        # It does look like CMake handles the CMAKE_OSX_SYSROOT automatically.
+        IF(EXISTS /Developer/SDKs/10.5.sdk)
+            SET(CMAKE_OSX_ARCHITECTURES "ppc;i386;ppc64;x86_64" CACHE STRING "Build architectures for OSX" FORCE)
+            SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mmacosx-version-min=10.5 -ftree-vectorize -fvisibility-inlines-hidden" CACHE STRING "Flags used by the compiler during all build types." FORCE)
+        ELSE(EXISTS /Developer/SDKs/10.5.sdk)
+            IF(EXISTS /Developer/SDKs/MacOSX10.4u.sdk)
+                SET(CMAKE_OSX_ARCHITECTURES "ppc;i386" CACHE STRING "Build architectures for OSX" FORCE)
+                SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mmacosx-version-min=10.4 -ftree-vectorize -fvisibility-inlines-hidden" CACHE STRING "Flags used by the compiler during all build types." FORCE)
+            ELSE(EXISTS /Developer/SDKs/MacOSX10.4u.sdk)
+                # No Universal Binary support
+                # Should break down further to set the -mmacosx-version-min,
+                # but the SDK detection is too unreliable here.
+            ENDIF(EXISTS /Developer/SDKs/MacOSX10.4u.sdk)
+        ENDIF(EXISTS /Developer/SDKs/10.5.sdk)
+    ENDIF(NOT OSGEARTH_CONFIG_HAS_BEEN_RUN_BEFORE)
+
+
+    OPTION(OSGEARTH_BUILD_APPLICATION_BUNDLES "Enable the building of applications and examples as OSX Bundles" OFF)
+    OPTION(OSGEARTH_BUILD_FRAMEWORKS "Compile frameworks instead of dylibs" OFF)
+    SET(OSGEARTH_BUILD_FRAMEWORKS_INSTALL_NAME_DIR "@executable_path/../Frameworks" CACHE STRING "Install name dir for compiled frameworks")
 
 ENDIF(APPLE)
 
diff --git a/CMakeModules/FindExpat.cmake b/CMakeModules/FindExpat.cmake
deleted file mode 100644
index 4e3dcd7..0000000
--- a/CMakeModules/FindExpat.cmake
+++ /dev/null
@@ -1,52 +0,0 @@
-# Locate EXPAT
-# This module defines
-# EXPAT_LIBRARY
-# EXPAT_FOUND, if false, do not try to link to expat
-# EXPAT_INCLUDE_DIR, where to find the headers
-#
-# $EXPAT_DIR is an environment variable that would
-# correspond to the ./configure --prefix=$EXPAT_DIR
-#
-# Created by Robert Osfield, adapted by GW
-
-FIND_PATH(EXPAT_INCLUDE_DIR expat.h
-    ${EXPAT_DIR}/include
-    $ENV{EXPAT_DIR}/include
-	$ENV{EXPAT_DIR}/Source/lib #Windows Binary Installer
-    $ENV{EXPAT_DIR}
-    ~/Library/Frameworks
-    /Library/Frameworks
-    /usr/local/include
-    /usr/include
-    /sw/include # Fink
-    /opt/local/include # DarwinPorts
-    /opt/csw/include # Blastwave
-    /opt/include
-    /usr/freeware/include
-    /devel
-)
-
-FIND_LIBRARY(EXPAT_LIBRARY
-    NAMES expat expat_i expat-2.0.1 libexpat
-    PATHS
-    ${EXPAT_DIR}/lib
-    $ENV{EXPAT_DIR}/lib
-	$ENV{EXPAT_DIR}/bin #Windows Binary Installer
-    $ENV{EXPAT_DIR}
-    ~/Library/Frameworks
-    /Library/Frameworks
-    /usr/local/lib
-    /usr/lib
-    /sw/lib
-    /opt/local/lib
-    /opt/csw/lib
-    /opt/lib
-    /usr/freeware/lib64
-)
-
-SET(EXPAT_FOUND "NO")
-IF(EXPAT_LIBRARY AND EXPAT_INCLUDE_DIR)
-    SET(EXPAT_FOUND "YES")
-ENDIF(EXPAT_LIBRARY AND EXPAT_INCLUDE_DIR)
-
-
diff --git a/CMakeModules/FindGDAL.cmake b/CMakeModules/FindGDAL.cmake
index fdb9b25..f765c68 100644
--- a/CMakeModules/FindGDAL.cmake
+++ b/CMakeModules/FindGDAL.cmake
@@ -19,13 +19,17 @@
 # This makes the presumption that you are include gdal.h like
 # #include "gdal.h"
 
+SET(GDAL_DIR "" CACHE PATH "Root folder of GDAL dependency")
+
 FIND_PATH(GDAL_INCLUDE_DIR gdal.h
-  $ENV{GDAL_DIR}
-  NO_DEFAULT_PATH
-    PATH_SUFFIXES include
+    ${GDAL_DIR}
+    $ENV{GDAL_DIR}
+    NO_DEFAULT_PATH
+    PATH_SUFFIXES include include/gdal
 )
 
 FIND_PATH(GDAL_INCLUDE_DIR gdal.h
+   ${GDAL_DIR}
     PATHS ${CMAKE_PREFIX_PATH} # Unofficial: We are proposing this.
     NO_DEFAULT_PATH
     PATH_SUFFIXES include
@@ -33,6 +37,7 @@ FIND_PATH(GDAL_INCLUDE_DIR gdal.h
 
 FIND_PATH(GDAL_INCLUDE_DIR gdal.h
   PATHS
+  ${GDAL_DIR}/include
   ~/Library/Frameworks/gdal.framework/Headers
   /Library/Frameworks/gdal.framework/Headers
   /usr/local/include/gdal
@@ -59,7 +64,8 @@ FIND_PATH(GDAL_INCLUDE_DIR gdal.h
 FIND_LIBRARY(GDAL_LIBRARY 
   NAMES gdal gdal_i gdal1.8.0 gdal1.7.0 gdal1.6.0 gdal1.5.0 gdal1.4.0 gdal1.3.2 GDAL 
   PATHS
-  c:/Program Files/FWTools2.1.0/lib
+  c:/Program Files/FWTools2.1.0/lib 
+  ${GDAL_DIR}/lib
   $ENV{GDAL_DIR}
   NO_DEFAULT_PATH
   PATH_SUFFIXES lib64 lib
@@ -67,13 +73,15 @@ FIND_LIBRARY(GDAL_LIBRARY
 FIND_LIBRARY(GDAL_LIBRARY 
   NAMES gdal gdal_i gdal1.8.0 gdal1.7.0 gdal1.6.0 gdal1.5.0 gdal1.4.0 gdal1.3.2 GDAL 
   PATHS ${CMAKE_PREFIX_PATH} # Unofficial: We are proposing this.
-   c:/Program Files/FWTools2.1.0/lib
+    c:/Program Files/FWTools2.1.0/lib
+    ${GDAL_DIR}/lib
     NO_DEFAULT_PATH
     PATH_SUFFIXES lib64 lib
 )
 FIND_LIBRARY(GDAL_LIBRARY 
   NAMES gdal gdal_i gdal1.8.0 gdal1.7.0 gdal1.6.0 gdal1.5.0 gdal1.4.0 gdal1.3.2 GDAL 
   PATHS
+    ${GDAL_DIR}/lib
     ~/Library/Frameworks
     /Library/Frameworks
     /usr/local
diff --git a/CMakeModules/FindGEOS.cmake b/CMakeModules/FindGEOS.cmake
index 29054d1..444b005 100644
--- a/CMakeModules/FindGEOS.cmake
+++ b/CMakeModules/FindGEOS.cmake
@@ -5,7 +5,10 @@
 # GEOS_FOUND, if false, do not try to link to geos
 # GEOS_INCLUDE_DIR, where to find the headers
 
+SET(GEOS_DIR "" CACHE PATH "Root directory of GEOS distribution")
+
 FIND_PATH(GEOS_INCLUDE_DIR geos/geom/Geometry.h
+  ${GEOS_DIR}
   $ENV{GEOS_DIR}
   NO_DEFAULT_PATH
     PATH_SUFFIXES include
@@ -13,14 +16,15 @@ FIND_PATH(GEOS_INCLUDE_DIR geos/geom/Geometry.h
 
 FIND_PATH(GEOS_INCLUDE_DIR geos/geom/Geometry.h
   PATHS
-  ~/Library/Frameworks/geos/Headers
-  /Library/Frameworks/geos/Headers
+  ${GEOS_DIR}/include
   /usr/local/include/geos
   /usr/local/include/GEOS
   /usr/local/include
   /usr/include/geos
   /usr/include/GEOS
   /usr/include
+  ~/Library/Frameworks/geos/Headers
+  /Library/Frameworks/geos/Headers
   /sw/include/geos 
   /sw/include/GEOS 
   /sw/include # Fink
@@ -39,6 +43,7 @@ FIND_PATH(GEOS_INCLUDE_DIR geos/geom/Geometry.h
 FIND_LIBRARY(GEOS_LIBRARY
   NAMES geos
   PATHS
+    ${GEOS_DIR}/lib
     $ENV{GEOS_DIR}
     NO_DEFAULT_PATH
     PATH_SUFFIXES lib64 lib
@@ -59,17 +64,17 @@ FIND_LIBRARY(GEOS_LIBRARY
   PATH_SUFFIXES lib64 lib
 )
 
-
 FIND_LIBRARY(GEOS_LIBRARY_DEBUG
-  NAMES geod_d geos_i_d geosd
+  NAMES geos_d geos_i_d geosd
   PATHS
+    ${GEOS_DIR}/lib
     $ENV{GEOS_DIR}
     NO_DEFAULT_PATH
     PATH_SUFFIXES lib64 lib
 )
 
 FIND_LIBRARY(GEOS_LIBRARY_DEBUG
-  NAMES geod_d geos_i_d geosd
+  NAMES geos_d geos_i_d geosd
   PATHS
     ~/Library/Frameworks
     /Library/Frameworks
diff --git a/CMakeModules/FindGLCORE.cmake b/CMakeModules/FindGLCORE.cmake
new file mode 100644
index 0000000..18c0347
--- /dev/null
+++ b/CMakeModules/FindGLCORE.cmake
@@ -0,0 +1,36 @@
+# Finds the OpenGL Core Profile (cp) header file.
+# Looks for glcorearb.h
+# 
+# This script defines the following:
+#  GLCORE_FOUND // Set to TRUE if glcorearb.h is found
+#  GLCORE_INCLUDE_DIR // Parent directory of directory (gl, GL3, or OpenGL) containing the CP header.
+#  GLCORE_GLCOREARB_HEADER // advanced
+#
+# GLCORE_ROOT can be set as an environment variable or a CMake variable,
+# to the parent directory of the gl, GL3, or OpenGL directory containing the CP header.
+#
+
+
+FIND_PATH( GLCORE_GLCOREARB_HEADER
+    NAMES GL/glcorearb.h GL3/glcorearb.h OpenGL/glcorearb.h gl/glcorearb.h
+    HINTS ${GLCORE_ROOT}
+    PATHS ENV GLCORE_ROOT
+)
+
+set( GLCORE_INCLUDE_DIR )
+if( GLCORE_GLCOREARB_HEADER )
+    set( GLCORE_INCLUDE_DIR ${GLCORE_GLCOREARB_HEADER} )
+endif()
+
+
+# handle the QUIETLY and REQUIRED arguments and set
+# GLCORE_FOUND to TRUE as appropriate
+INCLUDE( FindPackageHandleStandardArgs )
+FIND_PACKAGE_HANDLE_STANDARD_ARGS( GLCORE
+    "Set GLCORE_ROOT as the parent of the directory containing the OpenGL core profile header."
+    GLCORE_INCLUDE_DIR )
+
+MARK_AS_ADVANCED(
+    GLCORE_INCLUDE_DIR
+    GLCORE_GLCOREARB_HEADER
+)
diff --git a/CMakeModules/FindJavaScriptCore.cmake b/CMakeModules/FindJavaScriptCore.cmake
deleted file mode 100644
index 3877cd5..0000000
--- a/CMakeModules/FindJavaScriptCore.cmake
+++ /dev/null
@@ -1,46 +0,0 @@
-# Locate JavaScriptCore
-# This module defines
-# JAVASCRIPTCORE_LIBRARY
-# JAVASCRIPTCORE_FOUND, if false, do not try to link to JavaScriptCore
-# JAVASCRIPTCORE_INCLUDE_DIR, where to find the headers
-
-FIND_PATH(JAVASCRIPTCORE_INCLUDE_DIR JavaScriptCore.h
-    ${JAVASCRIPTCORE_DIR}/include
-    $ENV{JAVASCRIPTCORE_DIR}/include
-    $ENV{JAVASCRIPTCORE_DIR}
-    ~/Library/Frameworks
-    /Library/Frameworks
-    /usr/local/include
-    /usr/include
-    /sw/include # Fink
-    /opt/local/include # DarwinPorts
-    /opt/csw/include # Blastwave
-    /opt/include
-    /usr/freeware/include
-    /devel
-)
-
-FIND_LIBRARY(JAVASCRIPTCORE_LIBRARY
-    NAMES libJavaScriptCore JavaScriptCore
-    PATHS
-    ${JAVASCRIPTCORE_DIR}
-    ${JAVASCRIPTCORE_DIR}/lib
-    $ENV{JAVASCRIPTCORE_DIR}
-    $ENV{JAVASCRIPTCORE_DIR}/lib
-    ~/Library/Frameworks
-    /Library/Frameworks
-    /usr/local/lib
-    /usr/lib
-    /sw/lib
-    /opt/local/lib
-    /opt/csw/lib
-    /opt/lib
-    /usr/freeware/lib64
-)
-
-SET(JAVASCRIPTCORE_FOUND "NO")
-IF(JAVASCRIPTCORE_LIBRARY AND JAVASCRIPTCORE_INCLUDE_DIR)
-    SET(JAVASCRIPTCORE_FOUND "YES")
-ENDIF(JAVASCRIPTCORE_LIBRARY AND JAVASCRIPTCORE_INCLUDE_DIR)
-
-
diff --git a/CMakeModules/FindLevelDB.cmake b/CMakeModules/FindLevelDB.cmake
index 15e3db2..60e7743 100644
--- a/CMakeModules/FindLevelDB.cmake
+++ b/CMakeModules/FindLevelDB.cmake
@@ -4,8 +4,11 @@
 # LEVELDB_FOUND, if false, do not try to link to libnoise
 # LEVELDB_INCLUDE_DIR, where to find the headers
 
+SET(LEVELDB_DIR "" CACHE PATH "Root folder of LevelDB distribution")
+
 FIND_PATH(LEVELDB_INCLUDE_DIR leveldb/db.h
   PATHS
+  ${LEVELDB_DIR}
   $ENV{LEVELDB_DIR}
   NO_DEFAULT_PATH
     PATH_SUFFIXES include
@@ -38,6 +41,7 @@ FIND_PATH(LEVELDB_INCLUDE_DIR leveldb/db.h
 FIND_LIBRARY(LEVELDB_LIBRARY
   NAMES libleveldb leveldb leveldb_static
   PATHS
+    ${LEVELDB_DIR}
     $ENV{LEVELDB_DIR}
     NO_DEFAULT_PATH
     PATH_SUFFIXES lib64 lib
@@ -60,6 +64,7 @@ FIND_LIBRARY(LEVELDB_LIBRARY
 FIND_LIBRARY(LEVELDB_LIBRARY_DEBUG
   NAMES libleveldbd leveldbd leveldb_staticd
   PATHS
+    ${LEVELDB_DIR}
     $ENV{LEVELDB_DIR}
     NO_DEFAULT_PATH
     PATH_SUFFIXES lib64 lib
diff --git a/CMakeModules/FindLibNoise.cmake b/CMakeModules/FindLibNoise.cmake
deleted file mode 100644
index edbbc63..0000000
--- a/CMakeModules/FindLibNoise.cmake
+++ /dev/null
@@ -1,64 +0,0 @@
-# Locate libnoise.
-# This module defines
-# LIBNOISE_LIBRARY
-# LIBNOISE_FOUND, if false, do not try to link to libnoise
-# LIBNOISE_INCLUDE_DIR, where to find the headers
-
-FIND_PATH(LIBNOISE_INCLUDE_DIR noise.h
-  $ENV{LIBNOISE_DIR}
-  NO_DEFAULT_PATH
-    PATH_SUFFIXES include
-)
-
-FIND_PATH(LIBNOISE_INCLUDE_DIR noise.h
-  PATHS
-  ~/Library/Frameworks/noise/Headers
-  /Library/Frameworks/noise/Headers
-  /usr/local/include/noise
-  /usr/local/include/noise
-  /usr/local/include
-  /usr/include/noise
-  /usr/include/noise
-  /usr/include
-  /sw/include/noise 
-  /sw/include/noise 
-  /sw/include # Fink
-  /opt/local/include/noise
-  /opt/local/include/noise
-  /opt/local/include # DarwinPorts
-  /opt/csw/include/noise
-  /opt/csw/include/noise
-  /opt/csw/include # Blastwave
-  /opt/include/noise
-  /opt/include/noise
-  /opt/include  
-)
-
-FIND_LIBRARY(LIBNOISE_LIBRARY
-  NAMES libnoise noise
-  PATHS
-    $ENV{LIBNOISE_DIR}
-    NO_DEFAULT_PATH
-    PATH_SUFFIXES lib64 lib
-)
-
-FIND_LIBRARY(LIBNOISE_LIBRARY
-  NAMES libnoise noise
-  PATHS
-    ~/Library/Frameworks
-    /Library/Frameworks
-    /usr/local
-    /usr
-    /sw
-    /opt/local
-    /opt/csw
-    /opt
-    /usr/freeware    
-  PATH_SUFFIXES lib64 lib
-)
-
-SET(LIBNOISE_FOUND "NO")
-IF(LIBNOISE_LIBRARY AND LIBNOISE_INCLUDE_DIR)
-  SET(LIBNOISE_FOUND "YES")
-ENDIF(LIBNOISE_LIBRARY AND LIBNOISE_INCLUDE_DIR)
-
diff --git a/CMakeModules/FindOSG.cmake b/CMakeModules/FindOSG.cmake
index 6b43069..d862452 100644
--- a/CMakeModules/FindOSG.cmake
+++ b/CMakeModules/FindOSG.cmake
@@ -119,8 +119,18 @@ FIND_OSG_LIBRARY( OSGMANIPULATOR_LIBRARY_DEBUG osgManipulatord )
 FIND_OSG_LIBRARY( OSGPARTICLE_LIBRARY osgParticle )
 FIND_OSG_LIBRARY( OSGPARTICLE_LIBRARY_DEBUG osgParticled )
 
-FIND_OSG_LIBRARY( OSGQT_LIBRARY osgQt )
-FIND_OSG_LIBRARY( OSGQT_LIBRARY_DEBUG osgQtd )
+IF(OPENSCENEGRAPH_VERSION VERSION_LESS "3.5.6")
+  FIND_OSG_LIBRARY( OSGQT_LIBRARY osgQt )
+  FIND_OSG_LIBRARY( OSGQT_LIBRARY_DEBUG osgQtd )
+ELSE(OPENSCENEGRAPH_VERSION VERSION_LESS "3.5.6")
+  IF(Qt5Widgets_FOUND)
+    FIND_OSG_LIBRARY( OSGQT_LIBRARY osgQt5 )
+    FIND_OSG_LIBRARY( OSGQT_LIBRARY_DEBUG osgQt5d )
+  ELSE(Qt5Widgets_FOUND)
+    FIND_OSG_LIBRARY( OSGQT_LIBRARY osgQt )
+    FIND_OSG_LIBRARY( OSGQT_LIBRARY_DEBUG osgQtd )
+  ENDIF(Qt5Widgets_FOUND)
+ENDIF(OPENSCENEGRAPH_VERSION VERSION_LESS "3.5.6")
 
 FIND_OSG_LIBRARY( OPENTHREADS_LIBRARY OpenThreads )
 FIND_OSG_LIBRARY( OPENTHREADS_LIBRARY_DEBUG OpenThreadsd )
diff --git a/CMakeModules/FindOpenGLES.cmake b/CMakeModules/FindOpenGLES.cmake
new file mode 100644
index 0000000..be24eb4
--- /dev/null
+++ b/CMakeModules/FindOpenGLES.cmake
@@ -0,0 +1,12 @@
+IF(APPLE)
+    FIND_LIBRARY(OPENGL_LIBRARY NAMES OpenGLES
+        PATHS ${CMAKE_OSX_SYSROOT}/System/Library
+        PATH_SUFFIXES Frameworks
+        NO_DEFAULT_PATH)
+MESSAGE("Found OpenGLES library ${OPENGL_LIBRARY}") 
+    SET(OPENGL_LIBRARIES ${OPENGL_LIBRARY})
+    SET(OPENGL_INCLUDE_DIR "${OPENGL_LIBRARIES}/Headers")
+    SET(OPENGL_FOUND TRUE)
+ELSE ()
+    MESSAGE("Could not find OpenGLES library") 
+ENDIF()
diff --git a/CMakeModules/FindRocksDB.cmake b/CMakeModules/FindRocksDB.cmake
index 21d16a7..17fb335 100644
--- a/CMakeModules/FindRocksDB.cmake
+++ b/CMakeModules/FindRocksDB.cmake
@@ -1,5 +1,7 @@
 # Locate RocksDB
 
+SET(ROCKSDB_DIR "" CACHE PATH "Root directory of RocksDB distribution")
+
 FIND_PATH(ROCKSDB_INCLUDE_DIR rocksdb/db.h
   PATHS
   ${ROCKSDB_DIR}
diff --git a/CMakeModules/FindV8.cmake b/CMakeModules/FindV8.cmake
deleted file mode 100644
index dfb2d86..0000000
--- a/CMakeModules/FindV8.cmake
+++ /dev/null
@@ -1,245 +0,0 @@
-# Locate V8
-# This module defines
-# V8_LIBRARY
-# V8_FOUND, if false, do not try to link to V8
-# V8_INCLUDE_DIR, where to find the headers
-
-FIND_PATH(V8_INCLUDE_DIR v8.h
-    ${V8_DIR}/include
-    $ENV{V8_DIR}/include
-    $ENV{V8_DIR}
-    ~/Library/Frameworks
-    /Library/Frameworks
-    /usr/local/include
-    /usr/include
-    /sw/include # Fink
-    /opt/local/include # DarwinPorts
-    /opt/csw/include # Blastwave
-    /opt/include
-    /usr/freeware/include
-    /devel
-)
-
-# On non-Unix platforms (Mac and Windows specifically based on the forum),
-# V8 builds separate shared (or at least linkable) libraries for v8_base and v8_snapshot
-IF(NOT UNIX)
-    FIND_LIBRARY(V8_BASE_LIBRARY
-        NAMES v8_base v8_base.ia32 v8_base.x64 libv8_base
-        PATHS
-        ${V8_DIR}
-        ${V8_DIR}/lib
-        ${V8_DIR}/build/Release/lib
-        $ENV{V8_DIR}
-        $ENV{V8_DIR}/lib
-        ~/Library/Frameworks
-        /Library/Frameworks
-        /usr/local/lib
-        /usr/lib
-        /sw/lib
-        /opt/local/lib
-        /opt/csw/lib
-        /opt/lib
-        /usr/freeware/lib64
-    )
-
-    FIND_LIBRARY(V8_BASE_LIBRARY_DEBUG
-        NAMES v8_base v8_base.ia32 v8_base.x64 libv8_base
-        PATHS
-        ${V8_DIR}
-        ${V8_DIR}/lib
-        ${V8_DIR}/build/Debug/lib
-        $ENV{V8_DIR}
-        $ENV{V8_DIR}/lib
-        ~/Library/Frameworks
-        /Library/Frameworks
-        /usr/local/lib
-        /usr/lib
-        /sw/lib
-        /opt/local/lib
-        /opt/csw/lib
-        /opt/lib
-        /usr/freeware/lib64
-    )
-
-    FIND_LIBRARY(V8_SNAPSHOT_LIBRARY
-        NAMES v8_snapshot libv8_snapshot
-        PATHS
-        ${V8_DIR}
-        ${V8_DIR}/lib
-        ${V8_DIR}/build/Release/lib
-        $ENV{V8_DIR}
-        $ENV{V8_DIR}/lib
-        ~/Library/Frameworks
-        /Library/Frameworks
-        /usr/local/lib
-        /usr/lib
-        /sw/lib
-        /opt/local/lib
-        /opt/csw/lib
-        /opt/lib
-        /usr/freeware/lib64
-    )
-
-    FIND_LIBRARY(V8_SNAPSHOT_LIBRARY_DEBUG
-        NAMES v8_snapshot libv8_snapshot
-        PATHS
-        ${V8_DIR}
-        ${V8_DIR}/lib
-        ${V8_DIR}/build/Debug/lib
-        $ENV{V8_DIR}
-        $ENV{V8_DIR}/lib
-        ~/Library/Frameworks
-        /Library/Frameworks
-        /usr/local/lib
-        /usr/lib
-        /sw/lib
-        /opt/local/lib
-        /opt/csw/lib
-        /opt/lib
-        /usr/freeware/lib64
-    )
-
-# On Linux, there is just a libv8.so shared library built.
-# (well, there are pseudo-static libraries libv8_base.a and libv8_snapshot.a
-# but they don't seem to link correctly)
-ELSE()
-    FIND_LIBRARY(V8_LIBRARY
-        NAMES v8
-        PATHS
-        ${V8_DIR}
-        ${V8_DIR}/lib
-        ${V8_DIR}/build/Release/lib
-        # Having both architectures listed is problematic if both have been
-        # built (which is the default)
-        ${V8_DIR}/out/ia32.release/lib.target/
-        ${V8_DIR}/out/x64.release/lib.target/
-        $ENV{V8_DIR}
-        $ENV{V8_DIR}/lib
-        ~/Library/Frameworks
-        /Library/Frameworks
-        /usr/local/lib
-        /usr/lib
-        /sw/lib
-        /opt/local/lib
-        /opt/csw/lib
-        /opt/lib
-        /usr/freeware/lib64
-    )
-
-    FIND_LIBRARY(V8_LIBRARY_DEBUG
-        NAMES v8
-        PATHS
-        ${V8_DIR}
-        ${V8_DIR}/lib
-        ${V8_DIR}/build/Debug/lib
-        ${V8_DIR}/out/ia32.debug/lib.target/
-        ${V8_DIR}/out/x64.debug/lib.target/
-        $ENV{V8_DIR}
-        $ENV{V8_DIR}/lib
-        ~/Library/Frameworks
-        /Library/Frameworks
-        /usr/local/lib
-        /usr/lib
-        /sw/lib
-        /opt/local/lib
-        /opt/csw/lib
-        /opt/lib
-        /usr/freeware/lib64
-    )
-ENDIF(NOT UNIX)
-
-# icuuc and icui18n build fine on all platforms
-FIND_LIBRARY(V8_ICUUC_LIBRARY
-    NAMES icuuc libicuuc
-    PATHS
-    ${V8_DIR}
-    ${V8_DIR}/lib
-    ${V8_DIR}/build/Release/lib
-    ${V8_DIR}/out/ia32.release/lib.target/
-    ${V8_DIR}/out/x64.release/lib.target/
-    $ENV{V8_DIR}
-    $ENV{V8_DIR}/lib
-    ~/Library/Frameworks
-    /Library/Frameworks
-    /usr/local/lib
-    /usr/lib
-    /sw/lib
-    /opt/local/lib
-    /opt/csw/lib
-    /opt/lib
-    /usr/freeware/lib64
-)
-
-FIND_LIBRARY(V8_ICUUC_LIBRARY_DEBUG
-    NAMES icuuc libicuuc
-    PATHS
-    ${V8_DIR}
-    ${V8_DIR}/lib
-    ${V8_DIR}/build/Debug/lib
-    ${V8_DIR}/out/ia32.debug/lib.target/
-    ${V8_DIR}/out/x64.debug/lib.target/
-    $ENV{V8_DIR}
-    $ENV{V8_DIR}/lib
-    ~/Library/Frameworks
-    /Library/Frameworks
-    /usr/local/lib
-    /usr/lib
-    /sw/lib
-    /opt/local/lib
-    /opt/csw/lib
-    /opt/lib
-    /usr/freeware/lib64
-)
-
-FIND_LIBRARY(V8_ICUI18N_LIBRARY
-    NAMES icui18n libicui18n
-    PATHS
-    ${V8_DIR}
-    ${V8_DIR}/lib
-    ${V8_DIR}/build/Release/lib
-    ${V8_DIR}/out/ia32.release/lib.target/
-    ${V8_DIR}/out/x64.release/lib.target/
-    $ENV{V8_DIR}
-    $ENV{V8_DIR}/lib
-    ~/Library/Frameworks
-    /Library/Frameworks
-    /usr/local/lib
-    /usr/lib
-    /sw/lib
-    /opt/local/lib
-    /opt/csw/lib
-    /opt/lib
-    /usr/freeware/lib64
-)
-
-FIND_LIBRARY(V8_ICUI18N_LIBRARY_DEBUG
-    NAMES icui18n libicui18n
-    PATHS
-    ${V8_DIR}
-    ${V8_DIR}/lib
-    ${V8_DIR}/build/Debug/lib
-    ${V8_DIR}/out/ia32.debug/lib.target/
-    ${V8_DIR}/out/x64.debug/lib.target/
-    $ENV{V8_DIR}
-    $ENV{V8_DIR}/lib
-    ~/Library/Frameworks
-    /Library/Frameworks
-    /usr/local/lib
-    /usr/lib
-    /sw/lib
-    /opt/local/lib
-    /opt/csw/lib
-    /opt/lib
-    /usr/freeware/lib64
-)
-
-SET(V8_FOUND "NO")
-IF(NOT UNIX)
-    IF(V8_BASE_LIBRARY AND V8_SNAPSHOT_LIBRARY AND V8_ICUUC_LIBRARY AND V8_ICUI18N_LIBRARY AND V8_INCLUDE_DIR)
-        SET(V8_FOUND "YES")
-    ENDIF(V8_BASE_LIBRARY AND V8_SNAPSHOT_LIBRARY AND V8_ICUUC_LIBRARY AND V8_ICUI18N_LIBRARY AND V8_INCLUDE_DIR)
-ELSEIF(V8_LIBRARY AND V8_ICUUC_LIBRARY AND V8_ICUI18N_LIBRARY AND V8_INCLUDE_DIR)
-    SET(V8_FOUND "YES")
-ENDIF(NOT UNIX)
-
-
diff --git a/CMakeModules/ModuleInstall.cmake b/CMakeModules/ModuleInstall.cmake
index 0c510b4..4c13636 100644
--- a/CMakeModules/ModuleInstall.cmake
+++ b/CMakeModules/ModuleInstall.cmake
@@ -2,7 +2,15 @@
 # ${LIB_NAME}
 # ${LIB_PUBLIC_HEADERS}
 
-SET(INSTALL_INCDIR include)
+# Optional Vars:
+# ${HEADER_INSTALL_DIR}
+
+IF(HEADER_INSTALL_DIR)
+    SET(INSTALL_INCDIR include/${HEADER_INSTALL_DIR})
+ELSE()
+    SET(INSTALL_INCDIR include/${LIB_NAME})
+ENDIF()
+
 SET(INSTALL_BINDIR bin)
 IF(WIN32)
     SET(INSTALL_LIBDIR bin)
@@ -45,7 +53,7 @@ endif(OSGEARTH_INSTALL_SHADERS)
 IF(NOT OSGEARTH_BUILD_FRAMEWORKS)
     INSTALL(
         FILES        ${LIB_PUBLIC_HEADERS}
-        DESTINATION ${INSTALL_INCDIR}/${LIB_NAME}
+        DESTINATION ${INSTALL_INCDIR}
     )
 ELSE()
     SET(CMAKE_BUILD_WITH_INSTALL_RPATH TRUE)
diff --git a/CMakeModules/OsgEarthMacroUtils.cmake b/CMakeModules/OsgEarthMacroUtils.cmake
index ccd5a69..e1bd5d4 100644
--- a/CMakeModules/OsgEarthMacroUtils.cmake
+++ b/CMakeModules/OsgEarthMacroUtils.cmake
@@ -3,62 +3,64 @@
 #######################################################################################################
 MACRO(DETECT_OSG_VERSION)
 
+    # Fall back to OSG_DIR if OSG_INCLUDE_DIR is not defined
+    if(OSG_DIR AND NOT OSG_INCLUDE_DIR AND EXISTS "${OSG_DIR}/include/osg/Version")
+        set(OSG_INCLUDE_DIR "${OSG_DIR}/include")
+    endif()
+
     OPTION(APPEND_OPENSCENEGRAPH_VERSION "Append the OSG version number to the osgPlugins directory" ON)
 	
-    # detect if osgversion can be found
-    FIND_PROGRAM(OSG_VERSION_EXE NAMES
-        osgversion
-        ${OSG_DIR}/bin/osgversion
-        ${OSG_DIR}/bin/osgversiond)
-        
-    IF(OSG_VERSION_EXE AND NOT OPENSCENEGRAPH_MAJOR_VERSION AND NOT OPENSCENEGRAPH_MINOR_VERSION AND NOT OPENSCENEGRAPH_PATCH_VERSION)
-        #MESSAGE("OSGVERSION IS AT ${OSG_VERSION_EXE}")
-        # get parameters out of the osgversion
-        EXECUTE_PROCESS(COMMAND ${OSG_VERSION_EXE} --major-number OUTPUT_VARIABLE OPENSCENEGRAPH_MAJOR_VERSION OUTPUT_STRIP_TRAILING_WHITESPACE)
-        EXECUTE_PROCESS(COMMAND ${OSG_VERSION_EXE} --minor-number OUTPUT_VARIABLE OPENSCENEGRAPH_MINOR_VERSION OUTPUT_STRIP_TRAILING_WHITESPACE)
-        EXECUTE_PROCESS(COMMAND ${OSG_VERSION_EXE} --patch-number OUTPUT_VARIABLE OPENSCENEGRAPH_PATCH_VERSION OUTPUT_STRIP_TRAILING_WHITESPACE)
-        EXECUTE_PROCESS(COMMAND ${OSG_VERSION_EXE} Matrix::value_type OUTPUT_VARIABLE OSG_USE_FLOAT_MATRIX OUTPUT_STRIP_TRAILING_WHITESPACE)
-        EXECUTE_PROCESS(COMMAND ${OSG_VERSION_EXE} Plane::value_type OUTPUT_VARIABLE OSG_USE_FLOAT_PLANE OUTPUT_STRIP_TRAILING_WHITESPACE)
-        EXECUTE_PROCESS(COMMAND ${OSG_VERSION_EXE} BoundingSphere::value_type OUTPUT_VARIABLE OSG_USE_FLOAT_BOUNDINGSPHERE OUTPUT_STRIP_TRAILING_WHITESPACE)
-        EXECUTE_PROCESS(COMMAND ${OSG_VERSION_EXE} BoundingBox::value_type OUTPUT_VARIABLE OSG_USE_FLOAT_BOUNDINGBOX OUTPUT_STRIP_TRAILING_WHITESPACE)
-
-        # setup version numbers if we have osgversion
-        SET(OPENSCENEGRAPH_MAJOR_VERSION "${OPENSCENEGRAPH_MAJOR_VERSION}" CACHE STRING "OpenSceneGraph major version number")
-        SET(OPENSCENEGRAPH_MINOR_VERSION "${OPENSCENEGRAPH_MINOR_VERSION}" CACHE STRING "OpenSceneGraph minor version number")
-        SET(OPENSCENEGRAPH_PATCH_VERSION "${OPENSCENEGRAPH_PATCH_VERSION}" CACHE STRING "OpenSceneGraph patch version number")
-        SET(OPENSCENEGRAPH_SOVERSION "${OPENSCENEGRAPH_SOVERSION}" CACHE STRING "OpenSceneGraph so version number")
-		
-        # just debug info
-        #MESSAGE(STATUS "Detected OpenSceneGraph v${OPENSCENEGRAPH_VERSION}.")
-
-        # setup float and double definitions
-        IF(OSG_USE_FLOAT_MATRIX MATCHES "float")
-            ADD_DEFINITIONS(-DOSG_USE_FLOAT_MATRIX)
-        ENDIF(OSG_USE_FLOAT_MATRIX MATCHES "float")
-        IF(OSG_USE_FLOAT_PLANE MATCHES "float")
-            ADD_DEFINITIONS(-DOSG_USE_FLOAT_PLANE)
-        ENDIF(OSG_USE_FLOAT_PLANE MATCHES "float")
-        IF(OSG_USE_FLOAT_BOUNDINGSPHERE MATCHES "double")
-            ADD_DEFINITIONS(-DOSG_USE_DOUBLE_BOUNDINGSPHERE)
-        ENDIF(OSG_USE_FLOAT_BOUNDINGSPHERE MATCHES "double")
-        IF(OSG_USE_FLOAT_BOUNDINGBOX MATCHES "double")
-            ADD_DEFINITIONS(-DOSG_USE_DOUBLE_BOUNDINGBOX)
-        ENDIF(OSG_USE_FLOAT_BOUNDINGBOX MATCHES "double")
-
-    ENDIF(OSG_VERSION_EXE AND NOT OPENSCENEGRAPH_MAJOR_VERSION AND NOT OPENSCENEGRAPH_MINOR_VERSION AND NOT OPENSCENEGRAPH_PATCH_VERSION)
-	
-    #Initialize the version numbers to being empty.  If they were set by osgversion, they will be left alone
-	SET(OPENSCENEGRAPH_MAJOR_VERSION "" CACHE STRING "OpenSceneGraph major version number")
-    SET(OPENSCENEGRAPH_MINOR_VERSION "" CACHE STRING "OpenSceneGraph minor version number")
-    SET(OPENSCENEGRAPH_PATCH_VERSION "" CACHE STRING "OpenSceneGraph patch version number")
-    SET(OPENSCENEGRAPH_SOVERSION "" CACHE STRING "OpenSceneGraph so version number")
-	
-    if (OPENSCENEGRAPH_MAJOR_VERSION AND NOT OPENSCENEGRAPH_MINOR_VERSION STREQUAL "" AND NOT OPENSCENEGRAPH_PATCH_VERSION STREQUAL "")
-	  SET(OPENSCENEGRAPH_VERSION ${OPENSCENEGRAPH_MAJOR_VERSION}.${OPENSCENEGRAPH_MINOR_VERSION}.${OPENSCENEGRAPH_PATCH_VERSION})
-	else (OPENSCENEGRAPH_MAJOR_VERSION AND NOT OPENSCENEGRAPH_MINOR_VERSION STREQUAL "" AND NOT OPENSCENEGRAPH_PATCH_VERSION STREQUAL "")
-	  #MESSAGE("osgversion was found at ${OSG_VERSION_EXE} but failed to run")
-	  SET(OPENSCENEGRAPH_VERSION)
-	endif (OPENSCENEGRAPH_MAJOR_VERSION AND NOT OPENSCENEGRAPH_MINOR_VERSION STREQUAL "" AND NOT OPENSCENEGRAPH_PATCH_VERSION STREQUAL "")
+    # Try to ascertain the version...
+    # (Taken from CMake's FindOpenSceneGraph.cmake)
+    if(OSG_INCLUDE_DIR)
+        if(OpenSceneGraph_DEBUG)
+            message(STATUS "[ FindOpenSceneGraph.cmake:${CMAKE_CURRENT_LIST_LINE} ] "
+                "Detected OSG_INCLUDE_DIR = ${OSG_INCLUDE_DIR}")
+        endif()
+
+        set(_osg_Version_file "${OSG_INCLUDE_DIR}/osg/Version")
+        if("${OSG_INCLUDE_DIR}" MATCHES "\\.framework$" AND NOT EXISTS "${_osg_Version_file}")
+            set(_osg_Version_file "${OSG_INCLUDE_DIR}/Headers/Version")
+        endif()
+
+        if(EXISTS "${_osg_Version_file}")
+          file(STRINGS "${_osg_Version_file}" _osg_Version_contents
+               REGEX "#define (OSG_VERSION_[A-Z]+|OPENSCENEGRAPH_[A-Z]+_VERSION)[ \t]+[0-9]+")
+        else()
+          set(_osg_Version_contents "unknown")
+        endif()
+
+        string(REGEX MATCH ".*#define OSG_VERSION_MAJOR[ \t]+[0-9]+.*"
+            _osg_old_defines "${_osg_Version_contents}")
+        string(REGEX MATCH ".*#define OPENSCENEGRAPH_MAJOR_VERSION[ \t]+[0-9]+.*"
+            _osg_new_defines "${_osg_Version_contents}")
+        if(_osg_old_defines)
+            string(REGEX REPLACE ".*#define OSG_VERSION_MAJOR[ \t]+([0-9]+).*"
+                "\\1" _osg_VERSION_MAJOR ${_osg_Version_contents})
+            string(REGEX REPLACE ".*#define OSG_VERSION_MINOR[ \t]+([0-9]+).*"
+                "\\1" _osg_VERSION_MINOR ${_osg_Version_contents})
+            string(REGEX REPLACE ".*#define OSG_VERSION_PATCH[ \t]+([0-9]+).*"
+                "\\1" _osg_VERSION_PATCH ${_osg_Version_contents})
+        elseif(_osg_new_defines)
+            string(REGEX REPLACE ".*#define OPENSCENEGRAPH_MAJOR_VERSION[ \t]+([0-9]+).*"
+                "\\1" _osg_VERSION_MAJOR ${_osg_Version_contents})
+            string(REGEX REPLACE ".*#define OPENSCENEGRAPH_MINOR_VERSION[ \t]+([0-9]+).*"
+                "\\1" _osg_VERSION_MINOR ${_osg_Version_contents})
+            string(REGEX REPLACE ".*#define OPENSCENEGRAPH_PATCH_VERSION[ \t]+([0-9]+).*"
+                "\\1" _osg_VERSION_PATCH ${_osg_Version_contents})
+        else()
+            message(WARNING "[ FindOpenSceneGraph.cmake:${CMAKE_CURRENT_LIST_LINE} ] "
+                "Failed to parse version number, please report this as a bug")
+        endif()
+        unset(_osg_Version_contents)
+
+        set(OPENSCENEGRAPH_VERSION "${_osg_VERSION_MAJOR}.${_osg_VERSION_MINOR}.${_osg_VERSION_PATCH}"
+                                    CACHE INTERNAL "The version of OSG which was detected")
+        if(OpenSceneGraph_DEBUG)
+            message(STATUS "[ FindOpenSceneGraph.cmake:${CMAKE_CURRENT_LIST_LINE} ] "
+                "Detected version ${OPENSCENEGRAPH_VERSION}")
+        endif()
+    endif()
 	
 	MARK_AS_ADVANCED(OPENSCENEGRAPH_VERSION)
 
@@ -88,38 +90,22 @@ ENDMACRO(DETECT_OSG_VERSION)
 #  the content of this library for linking when in debugging
 #######################################################################################################
 
-
 MACRO(LINK_WITH_VARIABLES TRGTNAME)
     FOREACH(varname ${ARGN})
         IF(${varname}_DEBUG)
-            IF(${varname})
+            IF(${varname}_RELEASE)
+                TARGET_LINK_LIBRARIES(${TRGTNAME} optimized "${${varname}_RELEASE}" debug "${${varname}_DEBUG}")
+            ELSE(${varname}_RELEASE)
                 TARGET_LINK_LIBRARIES(${TRGTNAME} optimized "${${varname}}" debug "${${varname}_DEBUG}")
-            ELSE(${varname})
-                TARGET_LINK_LIBRARIES(${TRGTNAME} debug "${${varname}_DEBUG}")
-            ENDIF(${varname})
+            ENDIF(${varname}_RELEASE)
         ELSE(${varname}_DEBUG)
-            TARGET_LINK_LIBRARIES(${TRGTNAME} "${${varname}}" )
+            TARGET_LINK_LIBRARIES(${TRGTNAME} ${${varname}} )
         ENDIF(${varname}_DEBUG)
     ENDFOREACH(varname)
 ENDMACRO(LINK_WITH_VARIABLES TRGTNAME)
 
 MACRO(LINK_INTERNAL TRGTNAME)
-    IF("${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}" GREATER 2.4)
-        TARGET_LINK_LIBRARIES(${TRGTNAME} ${ARGN})
-    ELSE("${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}" GREATER 2.4)
-        FOREACH(LINKLIB ${ARGN})
-            IF(MSVC AND OSG_MSVC_VERSIONED_DLL)
-                #when using versioned names, the .dll name differ from .lib name, there is a problem with that:
-                #CMake 2.4.7, at least seem to use PREFIX instead of IMPORT_PREFIX  for computing linkage info to use into projects,
-                # so we full path name to specify linkage, this prevent automatic inferencing of dependencies, so we add explicit depemdencies
-                #to library targets used
-                TARGET_LINK_LIBRARIES(${TRGTNAME} optimized "${OUTPUT_LIBDIR}/${LINKLIB}${CMAKE_RELEASE_POSTFIX}.lib" debug "${OUTPUT_LIBDIR}/${LINKLIB}${CMAKE_DEBUG_POSTFIX}.lib")
-                ADD_DEPENDENCIES(${TRGTNAME} ${LINKLIB})
-            ELSE(MSVC AND OSG_MSVC_VERSIONED_DLL)
-                TARGET_LINK_LIBRARIES(${TRGTNAME} optimized "${LINKLIB}${CMAKE_RELEASE_POSTFIX}" debug "${LINKLIB}${CMAKE_DEBUG_POSTFIX}")
-            ENDIF(MSVC AND OSG_MSVC_VERSIONED_DLL)
-        ENDFOREACH(LINKLIB)
-    ENDIF("${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}" GREATER 2.4)
+    TARGET_LINK_LIBRARIES(${TRGTNAME} ${ARGN})
 ENDMACRO(LINK_INTERNAL TRGTNAME)
 
 MACRO(LINK_EXTERNAL TRGTNAME)
@@ -456,7 +442,7 @@ MACRO(SETUP_EXAMPLE EXAMPLE_NAME)
 
         SETUP_EXE(${IS_COMMANDLINE_APP})
 
-    INSTALL(TARGETS ${TARGET_TARGETNAME} RUNTIME DESTINATION share/OpenSceneGraph/bin  )
+    INSTALL(TARGETS ${TARGET_TARGETNAME} RUNTIME DESTINATION share/OpenSceneGraph/bin BUNDLE DESTINATION share/OpenSceneGraph/bin  )
 
 ENDMACRO(SETUP_EXAMPLE)
 
diff --git a/data/resources/textures_us/catalog.xml b/data/resources/textures_us/catalog.xml
index 3ee7f8b..b6912f8 100644
--- a/data/resources/textures_us/catalog.xml
+++ b/data/resources/textures_us/catalog.xml
@@ -379,6 +379,7 @@
        <image_width>25</image_width>
        <image_height>25</image_height>
        <tiled>true</tiled>
+       <atlas>false</atlas>
    </skin>
 
    <skin name="roof_tiled2" tags="rooftop">
@@ -386,6 +387,7 @@
        <image_width>25</image_width>
        <image_height>25</image_height>
        <tiled>true</tiled>
+       <atlas>false</atlas>
    </skin>
 
    <skin name="roof_tiled3" tags="rooftop">
@@ -393,6 +395,7 @@
        <image_width>25</image_width>
        <image_height>25</image_height>
        <tiled>true</tiled>
+       <atlas>false</atlas>
    </skin>
    
 <!-- rooftops : non-tiled -->
@@ -432,6 +435,7 @@
        <image_width>50</image_width>
        <image_height>50</image_height>
        <tiled>true</tiled>
+       <atlas>false</atlas>
    </skin>   
 
 </resources>
diff --git a/docs/source/about.rst b/docs/source/about.rst
index efecfc9..0bba018 100644
--- a/docs/source/about.rst
+++ b/docs/source/about.rst
@@ -34,15 +34,16 @@ Since osgEarth_ is a free open source SDK, the source code is available to
 anyone and we welcome and encourage community participation when it comes
 to testing, adding features, and fixing bugs.
 
-**Support Forum**
+**Public Forum**
 
-    The best way to interact with the osgEarth team and the user community is
+    The first way to interact with the osgEarth team and the user community is
     through the `support forum`_. **Please read** and follow these guidelines for
-    using the forum:
+    using the forum. FOLLOWING THESE GUIDELINES will make it MUCH MORE LIKELY
+    that someone will respond and try to help:
 
     * Sign up for an account and use your real name. You can participate
-      anonymously, but using your real name helps build a stronger community
-      (and makes it more likely that we will get to your question sooner).
+      anonymously, but using your real name helps build a stronger community.
+      Sign your posts too!
       
     * Limit yourself to *one topic* per post. Asking multiple questions in one
       post makes it too hard to keep track of responses.
@@ -54,10 +55,17 @@ to testing, adding features, and fixing bugs.
       
     * Be patient!
 
+**Priority Support**
+
+    If you have several questions, or need more in-depth help involving code
+    review, design, etc., consider purchasing `Priority Support`_ directly
+    from Pelican Mapping (the maintainers of osgEarth). Priority Support
+    gives you tracked, timely, personal email-based assistance!
+
 **OSG Forum**
 
     Since osgEarth_ is built on top of OpenSceneGraph_, many questions we get
-    on the message boards are *really* OSG questions. We will still try our
+    on the message boards are really OSG questions. We will still try our
     best to help. But it's worth your while to join the `OSG Mailing List`_ or
     read the `OSG Forum`_ regularly as well.
     
@@ -109,16 +117,17 @@ Maintainers
 `Pelican Mapping`_ maintains osgEarth_.
 
 
-.. _osgEarth:        http://osgEarth.org
-.. _OpenSceneGraph:  http://openscenegraph.org
-.. _Pelican Mapping: http://pelicanmapping.com
-.. _LGPL:            http://www.gnu.org/copyleft/lesser.html
-.. _Glenn:           http://twitter.com/#!/glennwaldron
-.. _Jason:           http://twitter.com/#!/jasonbeverage
-.. _Jeff:            http://twitter.com/#!/_jeffsmith
-.. _Paul:            http://twitter.com/#!/p_levy
-.. _ at pelicanmapping: https://twitter.com/pelicanmapping
-.. _Google+ Page:    https://plus.google.com/b/104014917856468748129/104014917856468748129/posts
+.. _osgEarth:         http://osgEarth.org
+.. _OpenSceneGraph:   http://openscenegraph.org
+.. _Pelican Mapping:  http://pelicanmapping.com
+.. _Priority Support: http://web.pelicanmapping.com/priority-support
+.. _LGPL:             http://www.gnu.org/copyleft/lesser.html
+.. _Glenn:            http://twitter.com/#!/glennwaldron
+.. _Jason:            http://twitter.com/#!/jasonbeverage
+.. _Jeff:             http://twitter.com/#!/_jeffsmith
+.. _Paul:             http://twitter.com/#!/p_levy
+.. _ at pelicanmapping:  https://twitter.com/pelicanmapping
+.. _Google+ Page:     https://plus.google.com/b/104014917856468748129/104014917856468748129/posts
 
 .. _support forum:    http://forum.osgearth.osg
 .. _OSG Mailing List: http://lists.openscenegraph.org/listinfo.cgi/osg-users-openscenegraph.org
diff --git a/docs/source/data.rst b/docs/source/data.rst
index 2ebb23b..de13c46 100644
--- a/docs/source/data.rst
+++ b/docs/source/data.rst
@@ -13,13 +13,8 @@ Help us add useful sources of Free data to this list.
     * `USGS National Map`_ - Elevation, orthoimagery, hydrography, geographic names, boundaries,
       transportation, structures, and land cover products for the US.
     
-    * `NASA EOSDIS`_ - NASA's Global Imagery Browse Services (GIBS) replaces the agency's old
-      JPL OnEarth site for global imagery products like MODIS.
-       
     * `NASA BlueMarble`_ - NASA's whole-earth imagery (including topography and bathymetry maps)
     
-    * `NRL GIDB`_ - US Naval Research Lab's GIDB OpenGIS Web Services
-    
     * `Natural Earth`_ - Free vector and raster map data at various scales
     
     * `Virtual Terrain Project`_ - Various sources for whole-earth imagery
@@ -41,10 +36,10 @@ Help us add useful sources of Free data to this list.
 
     * `OpenStreetMap`_ - Worldwide, community-sources street and land use data (vectors and rasterized tiles)
     
-    * `DIVA-GIS`_ - Free low-resolution vector data for any country
-    
     * `Natural Earth`_ - Free vector and raster map data at various scales
     
+    * `DIVA-GIS`_ - Free low-resolution vector data for any country
+    
 
 .. _CGIAR:                      http://srtm.csi.cgiar.org/
 .. _CGIAR European mirror:      ftp://xftp.jrc.it/pub/srtmV4/
@@ -52,15 +47,13 @@ Help us add useful sources of Free data to this list.
 .. _GEBCO:                      http://www.gebco.net/
 .. _GLCF:                       http://glcf.umiacs.umd.edu/data/srtm/
 .. _OpenStreetMap:              http://openstreetmap.org
-.. _NASA EOSDIS:                http://earthdata.nasa.gov/about-eosdis/system-description/global-imagery-browse-services-gibs
 .. _NASA BlueMarble:            http://visibleearth.nasa.gov/view_cat.php?categoryID=1484
 .. _Natural Earth:              http://www.naturalearthdata.com/
-.. _NRL GIDB:                   http://columbo.nrlssc.navy.mil/ogcwms/servlet/WMSServlet
 .. _SRTM30+:                    ftp://topex.ucsd.edu/pub/srtm30_plus/
 .. _USGS National Map:          http://nationalmap.gov/viewer.html
 .. _Virtual Terrain Project:    http://vterrain.org/Imagery/WholeEarth/
 .. _Bing Maps:                  http://www.microsoft.com/maps/choose-your-bing-maps-API.aspx
-.. _ReadyMap.org:               http://readymap.org/index_orig.html
+.. _ReadyMap.org:               http://readymap.org
 
 ----
 
@@ -88,30 +81,64 @@ Tips for Preparing your own Data
     **Build internal tiles**
     
     Typically formats such as GeoTiff store their pixel data in scanlines.
-    This generally works well, but because of the tiled approach that osgEarth
-    uses to access the data, you may find that using a tiled dataset will be more
-    efficient as osgEarth doens't need to read nearly as much data from disk to
-    extract a tile.
+    However, using a tiled dataset will be more efficient for osgEarth because
+    of how it uses tiles internally.
     
     To create a tiled GeoTiff using gdal_translate, issue the following command::
     
-        gdal_translate -of GTiff -co "TILED=YES" myfile.tif myfile_tiled.tif
+        gdal_translate -of GTiff -co TILED=YES input.tif output.tif
+        
+    Take is a step further and use compression to save space. You can use internal
+    JPEG compression if your data contains no transparency::
+    
+        gdal_translate -of GTiff -co TILED=YES -co COMPRESS=JPG input.tif output.tif   
+    
 
     **Build overviews**
     
     Adding overviews (also called ''pyramids'' or ''rsets'') can sometimes increase
-    the performance of a datasource in osgEarth.  You can use the
-    `gdaladdo <http://gdal.org/gdaladdo.html>`_ utility to add overviews to a dataset.
+    the performance of a large data source in osgEarth.  You can use the
+    `gdaladdo <http://gdal.org/gdaladdo.html>`_ utility to add overviews to a dataset::
     
-    For example::
-
         gdaladdo -r average myimage.tif 2 4 8 16
 
+
+**Building tile sets with osgearth_conv**
+
+   Pre-tiling your imagery can speed up load time dramatically, especially over the network.   
+   In fact, if you want to serve your data over the network, this is the only way!
+
+   *osgearth_conv* is a low-level conversion tool that comes with osgEarth. One useful 
+   application of the tool is tile up a large GeoTIFF (or other input) in a tiled format.   
+   Note: this approach only works with drivers that support writing (MBTiles, TMS).
+
+   To make a portable MBTiles file::
+
+       osgearth_conv --in driver gdal --in url myLargeFile.tif
+                     --out driver mbtiles --out filename myData.mbtiles
+                     --out format jpg
+
+   If you want to serve tiles from a web server, use TMS::
+
+       osgearth_conv --in driver gdal --in url myLargeData.tif
+                     --out driver tms --out url myLargeData/tms.xml
+                     --out format jpg
+
+   That will yield a folder (called "myLargeData" in this case) that you can deploy on the web
+   behind any standard web server (e.g. Apache).
+   
+   **Tip:** If you are tiling elevation data, you will need to add the ``--elevation`` option.
+   
+   **Tip:** The ``jpg`` format does NOT support transparency. If your data was an alpha
+   channel, use ``png`` instead.
+   
+   Just type *osgearth_conv* for a full list of options. The ``--in`` and ``--out`` options
+   correspond directly to properties you would normally include in an Earth file.
+   
         
-**Building tile sets**
+**Building tile sets with the packager**
 
-    Another way to speed up imagery and elevation loading in osgEarth is to build **tile sets**.
-    In fact, if you want to serve your data over the network, this is the only way!
+    Another way to speed up imagery and elevation loading in osgEarth is to build tile sets.
     
     This process takes the source data and chops it up into a quad-tree hierarchy of discrete
     *tiles* that osgEarth can load very quickly. Normally, if you load a GeoTIFF (for example),
@@ -140,3 +167,13 @@ Tips for Preparing your own Data
         --keep-empties                      Writes fully transparent image tiles (normally discarded)
         --db-options                        An optional OSG options string
         --verbose                           Displays progress of the operation
+        
+**Spatial indexing for feature data**
+
+    Large vector feature datasets (e.g., shapefiles) will benefit greatly from a spatial index.
+    Using the *ogrinfo* tool (included with GDAL/OGR binary distributions) you can create a 
+    spatial index for your vector data like so::
+
+        ogrinfo -sql "CREATE SPATIAL INDEX ON myfile" myfile.shp
+
+    For shapefiles, this will generate a ".qix" file that contains the spatial index information.
diff --git a/docs/source/developer/shader_composition.rst b/docs/source/developer/shader_composition.rst
index 240ed30..9dcfbf6 100644
--- a/docs/source/developer/shader_composition.rst
+++ b/docs/source/developer/shader_composition.rst
@@ -119,14 +119,14 @@ into various locations in the shader pipeline.
 For example, let's use user functions to create a simple "haze" effect::
 
     // haze_vertex:
-    varying vec3 v_pos;
+    out vec3 v_pos;
     void setup_haze(inout vec4 vertexView)
     {
         v_pos = vertexView.xyz;
     }
     
     // haze_fragment:
-    varying vec3 v_pos;
+    in vec3 v_pos;
     void apply_haze(inout vec4 color)
     {
         float dist = clamp( length(v_pos)/10000000.0, 0, 0.75 );
diff --git a/docs/source/faq.rst b/docs/source/faq.rst
index 1d1d777..45d730c 100644
--- a/docs/source/faq.rst
+++ b/docs/source/faq.rst
@@ -143,7 +143,7 @@ What is the best practice for using GitHub?
 	
 	1. Create your own GitHub account and log in.
 	2. Clone the osgEarth repo.
-	3. Work from your clone. Sync it to the main repository peridocially to get the
+	3. Work from your clone. Sync it to the main repository periodically to get the
 	   latest changes.
 
 
@@ -176,7 +176,11 @@ Can I hire someone to help me with osgEarth?
     services. The easiest way to get in touch with us is through our web site
     `contact form`_.
     
-.. _contact form:   http://pelicanmapping.com/?page_id=2
+    Pelican also offers a `Priority Support`_ package that is a good fit for 
+    companies that prefer to do most of their development in-house.
+    
+.. _contact form:     http://pelicanmapping.com/?page_id=2
+.. _Priority Support: http://web.pelicanmapping.com/priority-support/
 
 
 ----
diff --git a/docs/source/index.rst b/docs/source/index.rst
index 8a50310..806bd14 100644
--- a/docs/source/index.rst
+++ b/docs/source/index.rst
@@ -26,6 +26,7 @@ Table of Contents
    references/index
    faq
    releasenotes
+   support
 
 
 .. _osgEarth: http://osgearth.org
diff --git a/docs/source/references/drivers/tile/tms.rst b/docs/source/references/drivers/tile/tms.rst
index 66eabb1..9d53e1f 100644
--- a/docs/source/references/drivers/tile/tms.rst
+++ b/docs/source/references/drivers/tile/tms.rst
@@ -11,9 +11,9 @@ Example usage::
     
 Properties:
 
-    :url:      Root URL (or pathname) of the TMS repository
-    :tmsType:  Set to ``google`` to invert the Y axis of the tile index
-    :format:   Override the format reported by the service (e.g., jpg, png)
+    :url:       Root URL (or pathname) of the TMS repository
+    :tms_type:  Set to ``google`` to invert the Y axis of the tile index
+    :format:    Override the format reported by the service (e.g., jpg, png)
 
 
 .. _Tile Map Service:  http://wiki.osgeo.org/wiki/Tile_Map_Service_Specification
diff --git a/docs/source/references/earthfile.rst b/docs/source/references/earthfile.rst
index 939b740..7333a01 100644
--- a/docs/source/references/earthfile.rst
+++ b/docs/source/references/earthfile.rst
@@ -16,7 +16,7 @@ The *map* is the top-level element in an earth file.
         <:ref:`elevation <ElevationLayer>`>
         <:ref:`model     <ModelLayer>`>
         <:ref:`mask      <MaskLayer>`>
-        
+        <:ref:`libraries <Libraries>`>
 
 +------------------------+--------------------------------------------------------------------+
 | Property               | Description                                                        |
@@ -520,3 +520,19 @@ color data in a layer before the osgEarth engine composites it into the terrain.
             
 You can chain multiple color filters together. Please refer to :doc:`/references/colorfilters` for
 details on color filters.
+
+.. _Libraries:
+
+Libraries
+~~~~~~~~~
+Preload any libraries.
+
+.. parsed-literal::
+
+    <libraries>a</libraries>
+
+Multiple library names could be listed by using ';' as separator. 
+    
+    <libraries>a;b;c;d;e</libraries>
+
+The libraries are searched in the osg library path and library name needs to follow the osg nodekit library name convention (postfixed with osg library version)
diff --git a/docs/source/references/envvars.rst b/docs/source/references/envvars.rst
index 8623fb5..80ae5ab 100644
--- a/docs/source/references/envvars.rst
+++ b/docs/source/references/envvars.rst
@@ -44,7 +44,7 @@ Networking:
     :OSGEARTH_HTTP_TIMEOUT:                Sets an HTTP timeout (seconds)
     :OSG_CURL_PROXY:                       Sets a proxy server for HTTP requests (string)
     :OSG_CURL_PROXYPORT:                   Sets a proxy port for HTTP proxy server (integer)
-    :OSGEARTH_PROXYAUTH:                   Sets proxy authentication information (username:password)
+    :OSGEARTH_CURL_PROXYAUTH:              Sets proxy authentication information (username:password)
     :OSGEARTH_SIMULATE_HTTP_RESPONSE_CODE: Simulates HTTP errors (for debugging; set to HTTP response code)
 
 Misc:
diff --git a/docs/source/startup.rst b/docs/source/startup.rst
index 0062926..d3ae246 100644
--- a/docs/source/startup.rst
+++ b/docs/source/startup.rst
@@ -95,7 +95,7 @@ Here are a few tips.
 .. _GDAL:           http://www.gdal.org/
 .. _GDAL binaries:  http://www.gisinternals.com/
 .. _FWTools:        http://fwtools.maptools.org/
-.. _AlphaPixel:     http://openscenegraph.alphapixel.com/osg/downloads/openscenegraph-third-party-library-downloads
+.. _AlphaPixel:     http://downloads.alphapixel.org/
 .. _Mike Weiblen:   http://mew.cx/osg/
 .. _the forum:      http://forum.osgearth.org
 .. _LevelDB:        https://github.com/pelicanmapping/leveldb
diff --git a/docs/source/support.rst b/docs/source/support.rst
new file mode 100644
index 0000000..b4270b8
--- /dev/null
+++ b/docs/source/support.rst
@@ -0,0 +1,24 @@
+osgEarth Priority Support
+=========================
+
+The osgEarth free open source SDK is a leading platform for mapping and visualization. But let’s be honest, there’s a lot of learning involved in crafting a geospatial-enabled application! Whether you are using osgEarth or other geospatial platforms, we’re here to help.
+
+`Priority Support`_ is the best way to get peace of mind as you develop your own geospatial applications. Here’s what you can expect:
+
+    * Private, e-mail based support tickets, tracked in our system
+    * Quick turnaround times
+    * Custom code examples
+    * Code analysis and recommendations
+    * Testing and evaluation to help you track down problems
+    * Bug fixes to our open source software
+    * Recommendations on best practices
+    * General advice on anything OSG or geospatial!
+
+Go to the `Priority Support`_ page on our web site for pricing and terms. 
+
+How can we help you?
+
+----
+*Copyright Pelican Mapping Inc.*
+
+.. _Priority Support: http://web.pelicanmapping.com/priority-support/
diff --git a/docs/source/user/features.rst b/docs/source/user/features.rst
index a26b8be..d619f94 100644
--- a/docs/source/user/features.rst
+++ b/docs/source/user/features.rst
@@ -31,7 +31,7 @@ and then use that image tile in a normal image layer.
 osgEarth has one rasterizing feature driver: the ``agglite`` driver. Here's an example
 that renders an ESRI Shapefile as a rasterized image layer::
 
-    <model name="my layer" driver="agglite">
+    <image name="my layer" driver="agglite">
         <features name="states" driver="ogr">
             <url>states.shp</url>
         </features>
@@ -43,7 +43,7 @@ that renders an ESRI Shapefile as a rasterized image layer::
                 }
             </style>
         </styles>
-    </model>
+    </image>
 
 Tessellation
 ~~~~~~~~~~~~
@@ -440,4 +440,4 @@ Layout Settings
                         Default = 1.0.
     :min_expiry_time:   Minimum time, in second, before a feature tile is eligible for pageout.
                         Set this to a negative number to disable expiration altogether (i.e., tiles
-                        will never page out).
\ No newline at end of file
+                        will never page out).
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index d89c344..7035c3f 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -7,7 +7,7 @@ FOREACH( lib
          osgEarthUtil )
 
     ADD_SUBDIRECTORY(${lib})
-    
+
     SET_PROPERTY(TARGET ${lib} PROPERTY FOLDER "Core")
 
 ENDFOREACH( lib )
@@ -18,29 +18,19 @@ FOREACH( lib
          osgEarthSplat
          osgEarthSilverLining
          osgEarthTriton )
-    add_subdirectory( ${lib} )    
+    add_subdirectory( ${lib} )
 ENDFOREACH( lib )
 
 ADD_SUBDIRECTORY( osgEarthDrivers )
 
-IF(NOT OSG_BUILD_PLATFORM_IPHONE AND NOT OSG_BUILD_PLATFORM_IPHONE_SIMULATOR AND NOT ANDROID)
+IF(NOT ANDROID)
     ADD_SUBDIRECTORY( applications )
 ENDIF()
 
-#IF (Qt5Widgets_FOUND OR QT4_FOUND AND NOT ANDROID AND OSGEARTH_USE_QT)
-#    ADD_SUBDIRECTORY(osgEarthQt)
-#    SET_PROPERTY(TARGET osgEarthQt PROPERTY FOLDER "Libs")
-#ENDIF()
-
-#IF (SILVERLINING_FOUND)
-#    ADD_SUBDIRECTORY(osgEarthSilverLining)
-    #SET_PROPERTY(TARGET osgEarthSilverLining PROPERTY FOLDER "Extensions")
-#ENDIF()
+IF(NOT OSGEARTH_BUILD_PLATFORM_IPHONE AND NOT OSGEARTH_BUILD_PLATFORM_IPHONE_SIMULATOR)
+    ADD_SUBDIRECTORY( tests )
+ENDIF()
 
-#IF (TRITON_FOUND)
-    #ADD_SUBDIRECTORY(osgEarthTriton)
-    #SET_PROPERTY(TARGET osgEarthTriton PROPERTY FOLDER "Extensions")
-#ENDIF()
 
 IF(MSVC80)
   OPTION(OSGEARTH_MSVC_GENERATE_PLUGINS_AND_WRAPPERS_MANIFESTS "Generate or not manifests files under VS8 for dynamically loaded dlls" ON)
diff --git a/src/applications/CMakeLists.txt b/src/applications/CMakeLists.txt
index 2b5d1e4..3b5b28f 100644
--- a/src/applications/CMakeLists.txt
+++ b/src/applications/CMakeLists.txt
@@ -28,12 +28,14 @@ SET(TARGET_DEFAULT_APPLICATION_FOLDER "Applications")
 
 SET(TARGET_DEFAULT_LABEL_PREFIX "Tool")
 SET(TARGET_DEFAULT_APPLICATION_FOLDER "Tools")
+
+IF(NOT OSGEARTH_BUILD_PLATFORM_IPHONE AND NOT OSGEARTH_BUILD_PLATFORM_IPHONE_SIMULATOR)
+
 ADD_SUBDIRECTORY(osgearth_viewer)
 ADD_SUBDIRECTORY(osgearth_seed)
 ADD_SUBDIRECTORY(osgearth_package)
 ADD_SUBDIRECTORY(osgearth_tfs)
 ADD_SUBDIRECTORY(osgearth_boundarygen)
-ADD_SUBDIRECTORY(osgearth_backfill)
 ADD_SUBDIRECTORY(osgearth_overlayviewer)
 ADD_SUBDIRECTORY(osgearth_version)
 ADD_SUBDIRECTORY(osgearth_tileindex)
@@ -41,17 +43,15 @@ ADD_SUBDIRECTORY(osgearth_atlas)
 ADD_SUBDIRECTORY(osgearth_conv)
 ADD_SUBDIRECTORY(osgearth_3pv)
 
-IF (Qt5Widgets_FOUND OR QT4_FOUND AND NOT ANDROID AND OSGEARTH_USE_QT AND OSGEARTH_QT_BUILD_LEGACY_WIDGETS)
+IF (Qt5Widgets_FOUND OR QT4_FOUND AND NOT ANDROID AND OSGEARTH_QT_BUILD AND OSGEARTH_QT_BUILD_LEGACY_WIDGETS)
     ADD_SUBDIRECTORY(osgearth_package_qt)
 ENDIF()
 
 IF(BUILD_OSGEARTH_EXAMPLES)
     SET(TARGET_DEFAULT_LABEL_PREFIX "Sample")
     SET(TARGET_DEFAULT_APPLICATION_FOLDER "Samples")
-    ADD_SUBDIRECTORY(osgearth_clamp)
     ADD_SUBDIRECTORY(osgearth_manip)
     ADD_SUBDIRECTORY(osgearth_toc)
-    ADD_SUBDIRECTORY(osgearth_createtile)
     ADD_SUBDIRECTORY(osgearth_elevation)
     ADD_SUBDIRECTORY(osgearth_features)
     ADD_SUBDIRECTORY(osgearth_featureinfo)
@@ -64,11 +64,6 @@ IF(BUILD_OSGEARTH_EXAMPLES)
     ADD_SUBDIRECTORY(osgearth_transform)
     ADD_SUBDIRECTORY(osgearth_horizon)
     ADD_SUBDIRECTORY(osgearth_http)
-
-    IF(NOT ${OPENSCENEGRAPH_VERSION} VERSION_LESS "2.9.6")
-        ADD_SUBDIRECTORY(osgearth_featureeditor)
-    ENDIF()
-
     ADD_SUBDIRECTORY(osgearth_measure)
     ADD_SUBDIRECTORY(osgearth_controls)
     ADD_SUBDIRECTORY(osgearth_shadercomp)
@@ -81,27 +76,26 @@ IF(BUILD_OSGEARTH_EXAMPLES)
     ADD_SUBDIRECTORY(osgearth_colorfilter)
     ADD_SUBDIRECTORY(osgearth_sequencecontrol)
     ADD_SUBDIRECTORY(osgearth_minimap)
-    ADD_SUBDIRECTORY(osgearth_sharedlayer)
     ADD_SUBDIRECTORY(osgearth_mrt)
-    ADD_SUBDIRECTORY(osgearth_fog)
     ADD_SUBDIRECTORY(osgearth_shadergen)
     ADD_SUBDIRECTORY(osgearth_clipplane)
     ADD_SUBDIRECTORY(osgearth_cache_test)
     ADD_SUBDIRECTORY(osgearth_pick)
     ADD_SUBDIRECTORY(osgearth_wfs)
     ADD_SUBDIRECTORY(osgearth_datetime)
-    ADD_SUBDIRECTORY(osgearth_pagingtest)
-    ADD_SUBDIRECTORY(osgearth_xfbtest)
     ADD_SUBDIRECTORY(osgearth_ephemeris)
     ADD_SUBDIRECTORY(osgearth_computerangecallback)
-    ADD_SUBDIRECTORY(osgearth_splat)
     ADD_SUBDIRECTORY(osgearth_skyview)
     ADD_SUBDIRECTORY(osgearth_server)
-    ADD_SUBDIRECTORY(osgearth_deformation)
     ADD_SUBDIRECTORY(osgearth_srstest)
+    ADD_SUBDIRECTORY(osgearth_lights)
+    ADD_SUBDIRECTORY(osgearth_noisegen)
+    ADD_SUBDIRECTORY(osgearth_infinitescroll)
+    ADD_SUBDIRECTORY(osgearth_video)
+    ADD_SUBDIRECTORY(osgearth_splat)
+    ADD_SUBDIRECTORY(osgearth_htm)
 
-
-    IF (Qt5Widgets_FOUND OR QT4_FOUND AND NOT ANDROID AND OSGEARTH_USE_QT)
+    IF (Qt5Widgets_FOUND OR QT4_FOUND AND NOT ANDROID AND OSGEARTH_QT_BUILD)
         ADD_SUBDIRECTORY(osgearth_qt_simple)
         ADD_SUBDIRECTORY(osgearth_qt_windows)
     ENDIF()
@@ -113,5 +107,15 @@ IF(BUILD_OSGEARTH_EXAMPLES)
     IF(TRITON_FOUND)
         ADD_SUBDIRECTORY(osgearth_triton)
     ENDIF(TRITON_FOUND)
+
 ENDIF(BUILD_OSGEARTH_EXAMPLES)
 
+ELSE()
+
+    IF(BUILD_OSGEARTH_EXAMPLES)
+        SET(TARGET_DEFAULT_LABEL_PREFIX "Sample")
+        SET(TARGET_DEFAULT_APPLICATION_FOLDER "Samples")
+        ADD_SUBDIRECTORY(osgearth_viewerIOS)
+    ENDIF()
+
+ENDIF()
\ No newline at end of file
diff --git a/src/applications/osgearth_3pv/osgearth_3pv.cpp b/src/applications/osgearth_3pv/osgearth_3pv.cpp
index 36ae763..d88db28 100644
--- a/src/applications/osgearth_3pv/osgearth_3pv.cpp
+++ b/src/applications/osgearth_3pv/osgearth_3pv.cpp
@@ -236,7 +236,7 @@ main( int argc, char** argv )
 
     MapNode* mapNode = MapNode::get(node.get());
 
-    osg::ref_ptr<osg::Image> icon = osgDB::readImageFile("../data/placemark32.png");
+    osg::ref_ptr<osg::Image> icon = osgDB::readRefImageFile("../data/placemark32.png");
     PlaceNode* place = new PlaceNode(mapNode, GeoPoint::INVALID, icon.get(), "");
     place->getOrCreateStateSet()->setRenderBinDetails(10, "DepthSortedBin");
     place->setDynamic(true);
@@ -249,7 +249,7 @@ main( int argc, char** argv )
 
     mapNode->addChild(new HorizonNode());
 
-    viewer.getView(1)->getCamera()->addCullCallback( new VisitorData::Install("osgEarth.Stealth") );
+    viewer.getView(1)->getCamera()->setCullCallback( new VisitorData::Install("osgEarth.Stealth") );
 
     while (!viewer.done())
     {
diff --git a/src/applications/osgearth_annotation/osgearth_annotation.cpp b/src/applications/osgearth_annotation/osgearth_annotation.cpp
index f5e468d..b009860 100644
--- a/src/applications/osgearth_annotation/osgearth_annotation.cpp
+++ b/src/applications/osgearth_annotation/osgearth_annotation.cpp
@@ -33,6 +33,7 @@
 #include <osgEarthAnnotation/LabelNode>
 #include <osgEarthAnnotation/LocalGeometryNode>
 #include <osgEarthAnnotation/FeatureNode>
+#include <osgEarthAnnotation/ModelNode>
 
 #include <osgEarthAnnotation/AnnotationEditing>
 #include <osgEarthAnnotation/ImageOverlayEditor>
@@ -82,14 +83,14 @@ main(int argc, char** argv)
 
     // Group to hold all our annotation elements.
     osg::Group* annoGroup = new osg::Group();
-    root->addChild( annoGroup );
+    MapNode::get(node)->addChild( annoGroup );
 
     // Make a group for labels
     osg::Group* labelGroup = new osg::Group();
     annoGroup->addChild( labelGroup );
 
     osg::Group* editGroup = new osg::Group();
-    root->addChild( editGroup );
+    MapNode::get(node)->addChild( editGroup );
 
     // Style our labels:
     Style labelStyle;
@@ -131,7 +132,16 @@ main(int argc, char** argv)
     //--------------------------------------------------------------------
 
     // a box that follows lines of latitude (rhumb line interpolation, the default)
+    // and flashes on and off using a cull callback.
     {
+        struct C : public osg::NodeCallback {
+            void operator()(osg::Node* n, osg::NodeVisitor* nv) {
+                static int i=0;
+                i++;
+                if (i % 100 < 50)
+                    traverse(n, nv);
+            }
+        };
         Geometry* geom = new Polygon();
         geom->push_back( osg::Vec3d(0,   40, 0) );
         geom->push_back( osg::Vec3d(-60, 40, 0) );
@@ -149,7 +159,9 @@ main(int argc, char** argv)
         geomStyle.getOrCreate<AltitudeSymbol>()->technique() = AltitudeSymbol::TECHNIQUE_GPU;
         
         FeatureNode* fnode = new FeatureNode(mapNode, feature, geomStyle);
-        
+
+        fnode->addCullCallback(new C());
+
         annoGroup->addChild( fnode );
 
         labelGroup->addChild( new LabelNode(mapNode, GeoPoint(geoSRS,-30, 50), "Rhumb line polygon", labelStyle) );
@@ -204,6 +216,7 @@ main(int argc, char** argv)
         pathStyle.getOrCreate<PointSymbol>()->fill()->color() = Color::Red;
         pathStyle.getOrCreate<AltitudeSymbol>()->clamping() = AltitudeSymbol::CLAMP_TO_TERRAIN;
         pathStyle.getOrCreate<AltitudeSymbol>()->technique() = AltitudeSymbol::TECHNIQUE_GPU;
+        pathStyle.getOrCreate<RenderSymbol>()->depthOffset()->enabled() = true;
 
         //OE_INFO << "Path extent = " << pathFeature->getExtent().toString() << std::endl;
 
@@ -335,10 +348,10 @@ main(int argc, char** argv)
     // an image overlay.
     {
         ImageOverlay* imageOverlay = 0L;
-        osg::Image* image = osgDB::readImageFile( "../data/USFLAG.TGA" );
-        if ( image )
+        osg::ref_ptr<osg::Image> image = osgDB::readRefImageFile( "../data/USFLAG.TGA" );
+        if (image.valid())
         {
-            imageOverlay = new ImageOverlay(mapNode, image);
+            imageOverlay = new ImageOverlay(mapNode, image.get());
             imageOverlay->setBounds( Bounds( -100.0, 35.0, -90.0, 40.0) );
             annoGroup->addChild( imageOverlay );
 
@@ -348,6 +361,18 @@ main(int argc, char** argv)
 
     //--------------------------------------------------------------------
 
+    // a model node with auto scaling.
+    {
+        Style style;
+        style.getOrCreate<ModelSymbol>()->autoScale() = true;
+        style.getOrCreate<ModelSymbol>()->url()->setLiteral("../data/red_flag.osg.50.scale");
+        ModelNode* modelNode = new ModelNode(mapNode, style); 
+        modelNode->setPosition(GeoPoint(geoSRS, -100, 52));
+        annoGroup->addChild(modelNode);
+    }
+
+    //--------------------------------------------------------------------
+
     // initialize the viewer:    
     viewer.setSceneData( root );    
     viewer.getCamera()->setSmallFeatureCullingPixelSize(-1.0f);
diff --git a/src/applications/osgearth_atlas/osgearth_atlas.cpp b/src/applications/osgearth_atlas/osgearth_atlas.cpp
index bad44c1..d7feb13 100644
--- a/src/applications/osgearth_atlas/osgearth_atlas.cpp
+++ b/src/applications/osgearth_atlas/osgearth_atlas.cpp
@@ -211,8 +211,8 @@ show(osg::ArgumentParser& arguments)
             osgDB::getFileExtension(atlasFile);
     }
 
-    osg::Image* image = osgDB::readImageFile(atlasFile);
-    if ( !image )
+    osg::ref_ptr<osg::Image> image = osgDB::readRefImageFile(atlasFile);
+    if (!image.valid())
         return usage("Failed to load atlas image");
 
     if ( layer > image->r()-1 )
@@ -220,7 +220,7 @@ show(osg::ArgumentParser& arguments)
 
     // geometry for the image layer:
     std::vector<osg::ref_ptr<osg::Image> > images;
-    osgEarth::ImageUtils::flattenImage(image, images);
+    osgEarth::ImageUtils::flattenImage(image.get(), images);
     osg::Geode* geode = osg::createGeodeForImage(images[layer].get());
 
     const osg::BoundingBox& bbox = osgEarth::Utils::getBoundingBox(geode->getDrawable(0));
diff --git a/src/applications/osgearth_boundarygen/BoundaryUtil.cpp b/src/applications/osgearth_boundarygen/BoundaryUtil.cpp
index 66fb5e7..5eea478 100644
--- a/src/applications/osgearth_boundarygen/BoundaryUtil.cpp
+++ b/src/applications/osgearth_boundarygen/BoundaryUtil.cpp
@@ -647,5 +647,5 @@ BoundaryUtil::simpleBoundaryTest(const osg::Vec3dArray& boundary)
   outterPoly->push_back(osg::Vec3d(boundsBounds.xMin() - 10.0, boundsBounds.yMax() + 10.0, boundsBounds.zMin()));
 
   osg::ref_ptr<osgEarth::Symbology::Geometry> outPoly;
-  return outterPoly->difference(boundsPoly, outPoly);
+  return outterPoly->difference(boundsPoly.get(), outPoly);
 }
diff --git a/src/applications/osgearth_boundarygen/boundarygen.cpp b/src/applications/osgearth_boundarygen/boundarygen.cpp
index d33f91c..f2c1269 100644
--- a/src/applications/osgearth_boundarygen/boundarygen.cpp
+++ b/src/applications/osgearth_boundarygen/boundarygen.cpp
@@ -22,6 +22,8 @@
 
 #include "BoundaryUtil"
 
+#include <osgEarth/FileUtils>
+
 #include <iostream>
 #include <iomanip>
 #include <fstream>
@@ -80,11 +82,11 @@ int main(int argc, char** argv)
     bool convexOnly = arguments.read("--convex-hull");
     bool view = arguments.read("--view");
 
-    osg::Node* modelNode = osgDB::readNodeFiles( arguments );
-    if (!modelNode)
+    osg::ref_ptr<osg::Node> modelNode = osgDB::readNodeFiles( arguments );
+    if (!modelNode.valid())
         return usage( argv, "Unable to load model." );
 
-    osg::ref_ptr<osg::Vec3dArray> hull = BoundaryUtil::getBoundary(modelNode, geocentric, convexOnly);
+    osg::ref_ptr<osg::Vec3dArray> hull = BoundaryUtil::getBoundary(modelNode.get(), geocentric, convexOnly);
 
     if ( !outFile.empty() )
     {
diff --git a/src/applications/osgearth_city/osgearth_city.cpp b/src/applications/osgearth_city/osgearth_city.cpp
index a6116da..f3f4e0a 100644
--- a/src/applications/osgearth_city/osgearth_city.cpp
+++ b/src/applications/osgearth_city/osgearth_city.cpp
@@ -25,16 +25,21 @@
 #include <osgViewer/Viewer>
 
 #include <osgEarth/MapNode>
+#include <osgEarth/ImageLayer>
+#include <osgEarth/ModelLayer>
 
 #include <osgEarthUtil/ExampleResources>
 #include <osgEarthUtil/EarthManipulator>
 #include <osgEarthUtil/AutoClipPlaneHandler>
 #include <osgEarthUtil/LogarithmicDepthBuffer>
 
+#include <osgEarthFeatures/FeatureModelLayer>
+
 #include <osgEarthDrivers/tms/TMSOptions>
 #include <osgEarthDrivers/xyz/XYZOptions>
 #include <osgEarthDrivers/feature_ogr/OGRFeatureOptions>
 #include <osgEarthDrivers/model_feature_geom/FeatureGeomModelOptions>
+#include <osgEarthDrivers/engine_rex/RexTerrainEngineOptions>
 
 using namespace osgEarth;
 using namespace osgEarth::Drivers;
@@ -43,7 +48,7 @@ using namespace osgEarth::Symbology;
 using namespace osgEarth::Util;
 
 #define IMAGERY_URL      "http://readymap.org/readymap/tiles/1.0.0/22/"
-#define ELEVATION_URL    "http://readymap.org/readymap/tiles/1.0.0/9/"
+#define ELEVATION_URL    "http://readymap.org/readymap/tiles/1.0.0/116/"
 #define BUILDINGS_URL    "../data/boston_buildings_utm19.shp"
 #define RESOURCE_LIB_URL "../data/resources/textures_us/catalog.xml"
 #define STREETS_URL      "../data/boston-scl-utm19n-meters.shp"
@@ -87,8 +92,13 @@ main(int argc, char** argv)
     osg::Group* root = new osg::Group();
     viewer.setSceneData( root );
 
+    // Pick a terrain engine expressly:
+    RexTerrainEngine::RexTerrainEngineOptions terrainOptions;
+    MapNodeOptions mapNodeOptions;
+    mapNodeOptions.setTerrainOptions(terrainOptions);
+
     // make the map scene graph:
-    MapNode* mapNode = new MapNode( map );
+    MapNode* mapNode = new MapNode(map, mapNodeOptions);
     root->addChild( mapNode );
 
     // zoom to a good startup position
@@ -110,7 +120,7 @@ void addImagery(Map* map)
     // add a TMS imagery layer:
     TMSOptions imagery;
     imagery.url() = IMAGERY_URL;
-    map->addImageLayer( new ImageLayer("ReadyMap imagery", imagery) );
+    map->addLayer( new ImageLayer("ReadyMap imagery", imagery) );
 }
 
 
@@ -119,17 +129,17 @@ void addElevation(Map* map)
     // add a TMS elevation layer:
     TMSOptions elevation;
     elevation.url() = ELEVATION_URL;
-    map->addElevationLayer( new ElevationLayer("ReadyMap elevation", elevation) );
+    map->addLayer( new ElevationLayer("ReadyMap elevation", elevation) );
 }
 
 
 void addBuildings(Map* map)
 {
     // create a feature source to load the building footprint shapefile.
-    OGRFeatureOptions feature_opt;
-    feature_opt.name() = "buildings";
-    feature_opt.url() = BUILDINGS_URL;
-    feature_opt.buildSpatialIndex() = true;
+    OGRFeatureOptions buildingData;
+    buildingData.name() = "buildings";
+    buildingData.url() = BUILDINGS_URL;
+    buildingData.buildSpatialIndex() = true;
     
     // a style for the building data:
     Style buildingStyle;
@@ -181,16 +191,16 @@ void addBuildings(Map* map)
     // the visibility range combine to determine the tile size, such that
     // tile radius = max range / tile size factor.
     FeatureDisplayLayout layout;
-    layout.tileSizeFactor() = 52.0;
+    layout.tileSize() = 500;
     layout.addLevel( FeatureLevel(0.0f, 20000.0f, "buildings") );
 
-    // create a model layer that will render the buildings according to our style sheet.
-    FeatureGeomModelOptions fgm_opt;
-    fgm_opt.featureOptions() = feature_opt;
-    fgm_opt.styles() = styleSheet;
-    fgm_opt.layout() = layout;
+    FeatureModelLayer* layer = new FeatureModelLayer();
+    layer->setName("Buildings");
+    layer->options().featureSource() = buildingData;
+    layer->options().styles() = styleSheet;
+    layer->options().layout() = layout;
 
-    map->addModelLayer( new ModelLayer( "buildings", fgm_opt ) );
+    map->addLayer(layer);
 }
 
 
@@ -233,27 +243,28 @@ void addStreets(Map* map)
     // Set up a paging layout. The tile size factor and the visibility range combine
     // to determine the tile size, such that tile radius = max range / tile size factor.
     FeatureDisplayLayout layout;
-    layout.tileSizeFactor() = 7.5f;
-    layout.maxRange()       = 5000.0f;
+    layout.tileSize() = 500;
+    layout.maxRange() = 5000.0f;
 
     // create a model layer that will render the buildings according to our style sheet.
-    FeatureGeomModelOptions fgm_opt;
-    fgm_opt.featureOptions() = feature_opt;
-    fgm_opt.layout() = layout;
-    fgm_opt.styles() = new StyleSheet();
-    fgm_opt.styles()->addStyle( style );
-
-    map->addModelLayer( new ModelLayer("streets", fgm_opt) );
+    FeatureModelLayerOptions streets;
+    streets.name() = "streets";
+    streets.featureSource() = feature_opt;
+    streets.layout() = layout;
+    streets.styles() = new StyleSheet();
+    streets.styles()->addStyle( style );
+
+    map->addLayer(new FeatureModelLayer(streets));
 }
 
 
 void addParks(Map* map)
 {
     // create a feature source to load the shapefile.
-    OGRFeatureOptions feature_opt;
-    feature_opt.name() = "parks";
-    feature_opt.url() = PARKS_URL;
-    feature_opt.buildSpatialIndex() = true;
+    OGRFeatureOptions parksData;
+    parksData.name() = "parks";
+    parksData.url() = PARKS_URL;
+    parksData.buildSpatialIndex() = true;
 
     // a style:
     Style style;
@@ -264,10 +275,11 @@ void addParks(Map* map)
     // data are polygons, the PLACEMENT_RANDOM directive below will scatter
     // points within the polygon boundary at the specified density.
     ModelSymbol* model = style.getOrCreate<ModelSymbol>();
-    model->url()->setLiteral(TREE_MODEL_URL);
+    //model->url()->setLiteral(TREE_MODEL_URL);
     model->scale()->setLiteral( 0.2 );
     model->placement() = model->PLACEMENT_RANDOM;
     model->density() = 3000.0f; // instances per sqkm
+    model->setModel(osgDB::readRefNodeFile(TREE_MODEL_URL).release());
     
     // Clamp to the terrain:
     AltitudeSymbol* alt = style.getOrCreate<AltitudeSymbol>();
@@ -277,21 +289,25 @@ void addParks(Map* map)
     // that's sufficiently transparent; this will prevent depth-sorting anomolies
     // common when rendering lots of semi-transparent objects.
     RenderSymbol* render = style.getOrCreate<RenderSymbol>();
+    render->transparent() = true;
     render->minAlpha() = 0.15f;
 
     // Set up a paging layout. The tile size factor and the visibility range combine
     // to determine the tile size, such that tile radius = max range / tile size factor.
     FeatureDisplayLayout layout;
-    layout.tileSizeFactor() = 3.0f;
-    layout.maxRange()       = 2000.0f;
+    layout.tileSize() = 650;
+    layout.maxRange() = 2000.0f;
 
     // create a model layer that will render the buildings according to our style sheet.
-    FeatureGeomModelOptions fgm_opt;
-    fgm_opt.featureOptions() = feature_opt;
-    fgm_opt.layout() = layout;
-    fgm_opt.styles() = new StyleSheet();
-    fgm_opt.styles()->addStyle( style );
-    fgm_opt.compilerOptions().instancing() = true;
-
-    map->addModelLayer( new ModelLayer("parks", fgm_opt) );
+    FeatureModelLayerOptions parks;
+    parks.name() = "parks";
+    parks.featureSource() = parksData;
+    parks.layout() = layout;
+    parks.styles() = new StyleSheet();
+    parks.styles()->addStyle( style );
+
+    parks.instancing() = true;
+    parks.clusterCulling() = false;
+
+    map->addLayer(new FeatureModelLayer(parks));
 }
diff --git a/src/applications/osgearth_clamp/osgearth_clamp.cpp b/src/applications/osgearth_clamp/osgearth_clamp.cpp
deleted file mode 100644
index d813544..0000000
--- a/src/applications/osgearth_clamp/osgearth_clamp.cpp
+++ /dev/null
@@ -1,178 +0,0 @@
-/* -*-c++-*- */
-/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2016 Pelican Mapping
-* http://osgearth.org
-*
-* osgEarth is free software; you can redistribute it and/or modify
-* it under the terms of the GNU Lesser General Public License as published by
-* the Free Software Foundation; either version 2 of the License, or
-* (at your option) any later version.
-*
-* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
-* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
-* IN THE SOFTWARE.
-*
-* You should have received a copy of the GNU Lesser General Public License
-* along with this program.  If not, see <http://www.gnu.org/licenses/>
-*/
-
-#include <osg/Notify>
-#include <osgGA/StateSetManipulator>
-#include <osgGA/GUIEventHandler>
-#include <osgViewer/Viewer>
-#include <osgViewer/ViewerEventHandlers>
-#include <osgEarth/MapNode>
-#include <osgEarth/Terrain>
-#include <osgEarth/XmlUtils>
-#include <osgEarth/Viewpoint>
-#include <osgEarthUtil/EarthManipulator>
-#include <osgEarthUtil/AutoClipPlaneHandler>
-#include <osgEarthUtil/ObjectLocator>
-
-using namespace osgEarth;
-using namespace osgEarth::Util;
-
-class ClampObjectLocatorCallback : public osgEarth::TerrainCallback
-{
-public:
-    ClampObjectLocatorCallback(ObjectLocatorNode* locator):
-      _locator(locator),
-      _maxLevel(-1),
-      _minLevel(0)
-    {
-    }
-
-    virtual void onTileAdded(const osgEarth::TileKey& tileKey, osg::Node* terrain, TerrainCallbackContext&)
-    {           
-        if ((int)tileKey.getLevelOfDetail() > _minLevel && _maxLevel < (int)tileKey.getLevelOfDetail())
-        {
-            osg::Vec3d position = _locator->getLocator()->getPosition();
-
-            if (tileKey.getExtent().contains(position.x(), position.y()))
-            {
-                //Compute our location in geocentric
-                const osg::EllipsoidModel* ellipsoid = tileKey.getProfile()->getSRS()->getEllipsoid();
-                double x, y, z;            
-                ellipsoid->convertLatLongHeightToXYZ(
-                    osg::DegreesToRadians(position.y()), osg::DegreesToRadians(position.x()), 0,
-                    x, y, z);
-                //Compute the up vector
-                osg::Vec3d up = ellipsoid->computeLocalUpVector(x, y, z );
-                up.normalize();
-                osg::Vec3d world(x, y, z);
-
-                double segOffset = 50000;
-
-                osg::Vec3d start = world + (up * segOffset);
-                osg::Vec3d end = world - (up * segOffset);
-
-                osgUtil::LineSegmentIntersector* i = new osgUtil::LineSegmentIntersector( start, end );
-
-                osgUtil::IntersectionVisitor iv;            
-                iv.setIntersector( i );
-                terrain->accept( iv );
-
-                osgUtil::LineSegmentIntersector::Intersections& results = i->getIntersections();
-                if ( !results.empty() )
-                {
-                    const osgUtil::LineSegmentIntersector::Intersection& result = *results.begin();
-                    osg::Vec3d hit = result.getWorldIntersectPoint();
-                    double lat, lon, height;
-                    ellipsoid->convertXYZToLatLongHeight(hit.x(), hit.y(), hit.z(), 
-                        lat, lon, height);                
-                    position.z() = height;
-                    //OE_NOTICE << "Got hit, setting new height to " << height << std::endl;
-                    _maxLevel = tileKey.getLevelOfDetail();
-                    _locator->getLocator()->setPosition( position );
-                }            
-            }            
-        }            
-
-    }
-
-    osg::ref_ptr< ObjectLocatorNode > _locator;
-    int _maxLevel;
-    int _minLevel;
-};
-
-
-
-int
-main(int argc, char** argv)
-{
-    osg::ArgumentParser arguments(&argc,argv);
-    osgViewer::Viewer viewer(arguments);
-
-    unsigned int numObjects = 5000;
-    while (arguments.read("--count", numObjects)) {}
-
-
-    // load the .earth file from the command line.
-    osg::Node* earthNode = osgDB::readNodeFiles( arguments );
-    if (!earthNode)
-    {
-        OE_NOTICE << "Unable to load earth model" << std::endl;
-        return 1;
-    }
-
-    osg::Group* root = new osg::Group();
-
-    osgEarth::MapNode * mapNode = osgEarth::MapNode::findMapNode( earthNode );
-    if (!mapNode)
-    {
-        OE_NOTICE << "Could not find MapNode " << std::endl;
-        return 1;
-    }
-
-    osgEarth::Util::EarthManipulator* manip = new EarthManipulator();
-    manip->getSettings()->setArcViewpointTransitions( true );
-    viewer.setCameraManipulator( manip );
-    
-    root->addChild( earthNode );    
-
-    //viewer.getCamera()->addCullCallback( new AutoClipPlaneCullCallback(mapNode->getMap()) );
-
-    
-    osg::Node* tree = osgDB::readNodeFile("../data/tree.osg");         
-    osg::MatrixTransform* mt = new osg::MatrixTransform();
-    mt->setMatrix(osg::Matrixd::scale(10,10,10));
-    mt->addChild( tree );
-    //Create bound around mt rainer
-    double centerLat =  46.840866;
-    double centerLon = -121.769846;
-    double height = 0.2;
-    double width = 0.2;
-    double minLat = centerLat - (height/2.0);
-    double minLon = centerLon - (width/2.0);
-
-    OE_NOTICE << "Placing " << numObjects << " trees" << std::endl;
-
-    for (unsigned int i = 0; i < numObjects; i++)
-    {
-        osgEarth::Util::ObjectLocatorNode* locator = new osgEarth::Util::ObjectLocatorNode( mapNode->getMap() );        
-        double lat = minLat + height * (rand() * 1.0)/(RAND_MAX-1);
-        double lon = minLon + width * (rand() * 1.0)/(RAND_MAX-1);        
-        //OE_NOTICE << "Placing tree at " << lat << ", " << lon << std::endl;
-        locator->getLocator()->setPosition(osg::Vec3d(lon,  lat, 0 ) );        
-        locator->addChild( mt );
-        root->addChild( locator );
-        mapNode->getTerrain()->addTerrainCallback( new ClampObjectLocatorCallback(locator) );        
-    }    
-    
-    manip->setHomeViewpoint(Viewpoint("Home", centerLon, centerLat, 0.0, 0.0, -90, 45000 ));
-
-    viewer.setSceneData( root );    
-
-    // add some stock OSG handlers:
-    viewer.addEventHandler(new osgViewer::StatsHandler());
-    viewer.addEventHandler(new osgViewer::WindowSizeHandler());
-    viewer.addEventHandler(new osgViewer::ThreadingHandler());
-    viewer.addEventHandler(new osgViewer::LODScaleHandler());
-    viewer.addEventHandler(new osgGA::StateSetManipulator(viewer.getCamera()->getOrCreateStateSet()));
-
-    return viewer.run();
-}
diff --git a/src/applications/osgearth_clipplane/osgearth_clipplane.cpp b/src/applications/osgearth_clipplane/osgearth_clipplane.cpp
index a12f981..e64a441 100644
--- a/src/applications/osgearth_clipplane/osgearth_clipplane.cpp
+++ b/src/applications/osgearth_clipplane/osgearth_clipplane.cpp
@@ -89,7 +89,7 @@ main(int argc, char** argv)
 
         // Install a ClipNode. The ClipNode establishes positional state so it
         // doesn't need to parent anything. In this case it needs to be at the
-        // top of the scene graph since out clip plane calculator assumes 
+        // top of the scene graph since our clip plane calculator assumes 
         // you're in world space.
         osg::ClipNode* clipNode = new osg::ClipNode();
         root->addChild( clipNode );
diff --git a/src/applications/osgearth_colorfilter/osgearth_colorfilter.cpp b/src/applications/osgearth_colorfilter/osgearth_colorfilter.cpp
index fe65f48..6f2c8b1 100644
--- a/src/applications/osgearth_colorfilter/osgearth_colorfilter.cpp
+++ b/src/applications/osgearth_colorfilter/osgearth_colorfilter.cpp
@@ -22,6 +22,7 @@
 
 #include <osgViewer/Viewer>
 #include <osgEarth/MapNode>
+#include <osgEarth/ImageLayer>
 #include <osgEarthUtil/ExampleResources>
 #include <osgEarthUtil/EarthManipulator>
 #include <osgEarthUtil/Controls>
@@ -700,16 +701,18 @@ main(int argc, char** argv)
     osgEarth::MapNode* mapNode = osgEarth::MapNode::findMapNode( node );
     if ( node )
     {   
-        if (mapNode->getMap()->getNumImageLayers() == 0)
+        ImageLayerVector imageLayers;
+        mapNode->getMap()->getLayers(imageLayers);
+
+        if (imageLayers.empty())
         {
             return usage("Please provide a map with at least one image layer.");
         }
 
         // attach color filter to each layer.
-        unsigned numLayers = mapNode->getMap()->getNumImageLayers();
-        for( unsigned i=0; i<numLayers; ++i )
+        for (unsigned i = 0; i<imageLayers.size(); ++i)
         {
-            ImageLayer* layer = mapNode->getMap()->getImageLayerAt( i );
+            ImageLayer* layer = imageLayers[i].get();
 
             if ( layer->getEnabled() && layer->getVisible() )
             {
diff --git a/src/applications/osgearth_computerangecallback/osgearth_computerangecallback.cpp b/src/applications/osgearth_computerangecallback/osgearth_computerangecallback.cpp
index be37189..0f5fd92 100644
--- a/src/applications/osgearth_computerangecallback/osgearth_computerangecallback.cpp
+++ b/src/applications/osgearth_computerangecallback/osgearth_computerangecallback.cpp
@@ -60,6 +60,7 @@ struct MyComputeRangeCallback : public osgEarth::ComputeRangeCallback
             double angularSize = osg::RadiansToDegrees( 2.0*atan(radius/distance) );
             double dpp = osg::maximum(fov, 1.0e-17) / viewPort->height();
             float pixelSize = angularSize / dpp;
+            //OE_NOTICE << "Returning " << pixelSize << std::endl;
             return pixelSize;
         }
 
diff --git a/src/applications/osgearth_controls/osgearth_controls.cpp b/src/applications/osgearth_controls/osgearth_controls.cpp
index 9220327..c1c580f 100644
--- a/src/applications/osgearth_controls/osgearth_controls.cpp
+++ b/src/applications/osgearth_controls/osgearth_controls.cpp
@@ -112,7 +112,7 @@ createControls( ControlCanvas* cs )
         center->setVertAlign( Control::ALIGN_CENTER );
 
         // Add an image:
-        osg::ref_ptr<osg::Image> image = osgDB::readImageFile("../data/osgearth.gif");
+        osg::ref_ptr<osg::Image> image = osgDB::readRefImageFile("../data/osgearth.gif");
         if ( image.valid() )
         {
             s_imageControl = new ImageControl( image.get() );
diff --git a/src/applications/osgearth_conv/osgearth_conv.cpp b/src/applications/osgearth_conv/osgearth_conv.cpp
index 052e3b3..9dd5205 100644
--- a/src/applications/osgearth_conv/osgearth_conv.cpp
+++ b/src/applications/osgearth_conv/osgearth_conv.cpp
@@ -26,9 +26,13 @@
 #include <osgEarth/TileSource>
 #include <osgEarth/TileHandler>
 #include <osgEarth/TileVisitor>
+#include <osgEarth/ImageLayer>
+#include <osgEarth/ElevationLayer>
 #include <osg/ArgumentParser>
 #include <osg/Timer>
 #include <iomanip>
+#include <algorithm>
+#include <iterator>
 
 using namespace osgEarth;
 
@@ -47,49 +51,11 @@ int usage(char** argv)
         << "\n    --osg-options [OSG options string]  : options to pass to OSG readers/writers"
         << "\n    --extents [minLat] [minLong] [maxLat] [maxLong] : Lat/Long extends to copy"
         << std::endl;
-        
+
     return 0;
 }
 
 
-// TileHandler that copies images from one tilesource to another.
-struct TileSourceToTileSource : public TileHandler
-{
-    TileSourceToTileSource(TileSource* source, TileSource* dest, bool heightFields)
-        : _source(source), _dest(dest), _heightFields(heightFields)
-    {
-        //nop
-    }
-
-    bool handleTile(const TileKey& key, const TileVisitor& tv)
-    {
-        bool ok = false;
-        if (_heightFields)
-        {
-            osg::ref_ptr<osg::HeightField> hf = _source->createHeightField(key);
-            if ( hf.valid() )
-                ok = _dest->storeHeightField(key, hf.get(), 0L);
-        }
-        else
-        {
-            osg::ref_ptr<osg::Image> image = _source->createImage(key);
-            if ( image.valid() )
-                ok = _dest->storeImage(key, image.get(), 0L);
-        }
-        return ok;
-    }
-    
-    bool hasData(const TileKey& key) const
-    {
-        return _source->hasData(key);
-    }
-
-    TileSource* _source;
-    TileSource* _dest;
-    bool        _heightFields;
-};
-
-
 // TileHandler that copies images from an ImageLayer to a TileSource.
 // This will automatically handle any mosaicing and reprojection that is
 // necessary to translate from one Profile/SRS to another.
@@ -106,14 +72,14 @@ struct ImageLayerToTileSource : public TileHandler
         bool ok = false;
         GeoImage image = _source->createImage(key);
         if (image.valid())
-            ok = _dest->storeImage(key, image.getImage(), 0L);        
+            ok = _dest->storeImage(key, image.getImage(), 0L);
 
         return ok;
     }
-    
+
     bool hasData(const TileKey& key) const
     {
-        return _source->getTileSource()->hasData(key);
+        return _source->mayHaveDataInExtent(key.getExtent());
     }
 
     osg::ref_ptr<ImageLayer> _source;
@@ -140,10 +106,10 @@ struct ElevationLayerToTileSource : public TileHandler
             ok = _dest->storeHeightField(key, hf.getHeightField(), 0L);
         return ok;
     }
-    
+
     bool hasData(const TileKey& key) const
     {
-        return _source->getTileSource()->hasData(key);
+        return _source->mayHaveDataInExtent(key.getExtent());
     }
 
     osg::ref_ptr<ElevationLayer> _source;
@@ -154,21 +120,42 @@ struct ElevationLayerToTileSource : public TileHandler
 // Custom progress reporter
 struct ProgressReporter : public osgEarth::ProgressCallback
 {
-    bool reportProgress(double             current, 
-                        double             total, 
+    ProgressReporter() : _first(true) { }
+
+    bool reportProgress(double             current,
+                        double             total,
                         unsigned           currentStage,
                         unsigned           totalStages,
                         const std::string& msg )
     {
         _mutex.lock();
 
-        float percentage = current/total*100.0f;
-        std::cout 
+        if (_first)
+        {
+            _first = false;
+            _start = osg::Timer::instance()->tick();
+        }
+        osg::Timer_t now = osg::Timer::instance()->tick();
+
+        
+
+        float percentage = current/total;
+
+        double timeSoFar = osg::Timer::instance()->delta_s(_start, now);
+        double projectedTotalTime = timeSoFar/percentage;
+        double timeToGo = projectedTotalTime - timeSoFar;
+        double minsToGo = timeToGo/60.0;
+        double secsToGo = fmod(timeToGo,60.0);
+        double minsTotal = projectedTotalTime/60.0;
+        double secsTotal = fmod(projectedTotalTime,60.0);
+
+        std::cout
             << std::fixed
-            << std::setprecision(1) << "\r" 
+            << std::setprecision(1) << "\r"
             << (int)current << "/" << (int)total
-            << " (" << percentage << "%)"
-            << "                        "
+            << " (" << (100.0f*percentage) << "%, " 
+            << (int)minsTotal << "m" << (int)secsTotal << "s projected, "
+            << (int)minsToGo << "m" << (int)secsToGo << "s remaining)        "
             << std::flush;
 
         if ( percentage >= 100.0f )
@@ -180,6 +167,8 @@ struct ProgressReporter : public osgEarth::ProgressCallback
     }
 
     Threading::Mutex _mutex;
+    bool _first;
+    osg::Timer_t _start;
 };
 
 
@@ -235,7 +224,7 @@ main(int argc, char** argv)
         inConf.set(key, value);
 
     osg::ref_ptr<osgDB::Options> dbo = new osgDB::Options();
-    
+
     // plugin options, if the user passed them in:
     std::string str;
     while(args.read("--osg-options", str) || args.read("-O", str))
@@ -254,7 +243,7 @@ main(int argc, char** argv)
     Status inputStatus = input->open( input->MODE_READ, dbo.get() );
     if ( inputStatus.isError() )
     {
-        OE_WARN << LC << "Error initializing input" << std::endl;
+        OE_WARN << LC << "Error initializing input: " << inputStatus.message() << std::endl;
         return -1;
     }
 
@@ -295,17 +284,29 @@ main(int argc, char** argv)
     osg::ref_ptr<TileSource> output = TileSourceFactory::create(outOptions);
     if ( !output.valid() )
     {
-        OE_WARN << LC << "Failed to open output" << std::endl;
+        OE_WARN << LC << "Failed to open output." << std::endl;
         return -1;
     }
 
+    // Copy over the data extents to the output datasource.
+    for (DataExtentList::const_iterator itr = input->getDataExtents().begin(); itr != input->getDataExtents().end(); ++itr)
+    {
+        // Convert the data extent to the profile that is actually used by the output tile source
+        DataExtent dataExtent = *itr;
+        GeoExtent ext = dataExtent.transform(outputProfile->getSRS());
+        unsigned int minLevel = 0;
+        unsigned int maxLevel = outputProfile->getEquivalentLOD( input->getProfile(), *dataExtent.maxLevel() );
+        DataExtent outputExtent = DataExtent(ext, minLevel, maxLevel);
+        output->getDataExtents().push_back( outputExtent );
+    }
+
     Status outputStatus = output->open(
         TileSource::MODE_WRITE | TileSource::MODE_CREATE,
         dbo.get() );
 
     if ( outputStatus.isError() )
     {
-        OE_WARN << LC << "Error initializing output" << std::endl;
+        OE_WARN << LC << "Error initializing output: " << outputStatus.message() << std::endl;
         return -1;
     }
 
@@ -333,50 +334,50 @@ main(int argc, char** argv)
         visitor = new TileVisitor();
     }
 
-    // If the profiles are identical, just use a tile copier.
-    if ( isSameProfile )
+    if (heightFields)
     {
-        OE_NOTICE << LC << "Profiles match - initiating simple tile copy" << std::endl;
-        visitor->setTileHandler( new TileSourceToTileSource(input.get(), output.get(), heightFields) );
+        ElevationLayer* layer = new ElevationLayer(ElevationLayerOptions(), input.get());
+        Status layerStatus = layer->open();
+        if (layerStatus.isError())
+        {
+            OE_WARN << "Failed to create input ElevationLayer " << layerStatus.message() << std::endl;
+            return -1;
+        }
+        if ( !layer->getProfile() || !layer->getProfile()->isOK() )
+        {
+            OE_WARN << LC << "Input profile is not valid" << std::endl;
+            return -1;
+        }
+        visitor->setTileHandler( new ElevationLayerToTileSource(layer, output.get()) );
     }
-    else
-    {
-        OE_NOTICE << LC << "Profiles differ - initiating tile transformation" << std::endl;
 
-        if (heightFields)
+    else // image layers
+    {
+        ImageLayer* layer = new ImageLayer(ImageLayerOptions(), input.get());
+        Status layerStatus = layer->open();
+        if (layerStatus.isError())
         {
-            ElevationLayer* layer = new ElevationLayer(ElevationLayerOptions(), input.get());
-            Status layerStatus = layer->open();
-            if (layerStatus.isError())
-            {
-                OE_WARN << "Failed to create input ElevationLayer " << layerStatus.message() << std::endl;
-                return -1;
-            }
-            if ( !layer->getProfile() || !layer->getProfile()->isOK() )
-            {
-                OE_WARN << LC << "Input profile is not valid" << std::endl;
-                return -1;
-            }
-            visitor->setTileHandler( new ElevationLayerToTileSource(layer, output.get()) );
+            OE_WARN << "Failed to create input ImageLayer " << layerStatus.message() << std::endl;
+            return -1;
         }
-        else
+        if ( !layer->getProfile() || !layer->getProfile()->isOK() )
         {
-            ImageLayer* layer = new ImageLayer(ImageLayerOptions(), input.get());
-            Status layerStatus = layer->open();
-            if (layerStatus.isError())
-            {
-                OE_WARN << "Failed to create input ImageLayer " << layerStatus.message() << std::endl;
-                return -1;
-            }
-            if ( !layer->getProfile() || !layer->getProfile()->isOK() )
-            {
-                OE_WARN << LC << "Input profile is not valid" << std::endl;
-                return -1;
-            }
-            visitor->setTileHandler( new ImageLayerToTileSource(layer, output.get()) );
+            OE_WARN << LC << "Input profile is not valid" << std::endl;
+            return -1;
         }
+        visitor->setTileHandler( new ImageLayerToTileSource(layer, output.get()) );
     }
-    
+
+    // set the manula extents, if specified:
+    bool userSetExtents = false;
+    double minlat, minlon, maxlat, maxlon;
+    while( args.read("--extents", minlat, minlon, maxlat, maxlon) )
+    {
+        GeoExtent extent(SpatialReference::get("wgs84"), minlon, minlat, maxlon, maxlat);
+        visitor->addExtent( extent );
+        userSetExtents = true;
+    }
+
     // Set the level limits:
     unsigned minLevel = ~0;
     bool minLevelSet = args.read("--min-level", minLevel);
@@ -395,9 +396,14 @@ main(int argc, char** argv)
                 maxLevel = i->maxLevel().value();
             if ( !minLevelSet && i->minLevel().isSet() && i->minLevel().value() < minLevel )
                 minLevel = i->minLevel().value();
+
+            if (userSetExtents == false)
+            {
+                visitor->addExtent(*i);
+            }
         }
     }
-       
+
     if ( minLevel < ~0 )
     {
         visitor->setMinLevel( minLevel );
@@ -410,14 +416,6 @@ main(int argc, char** argv)
         OE_NOTICE << LC << "Calculated max level = " << maxLevel << std::endl;
     }
 
-    // set the extents:
-    double minlat, minlon, maxlat, maxlon;
-    while( args.read("--extents", minlat, minlon, maxlat, maxlon) )
-    {
-        GeoExtent extent(SpatialReference::get("wgs84"), minlon, minlat, maxlon, maxlat);
-        visitor->addExtent( extent );
-    }
-
     // Ready!!!
     std::cout << "Working..." << std::endl;
 
@@ -430,7 +428,7 @@ main(int argc, char** argv)
     osg::Timer_t t1 = osg::Timer::instance()->tick();
 
     std::cout
-        << "Time = " 
+        << "Time = "
         << std::fixed
         << std::setprecision(1)
         << osg::Timer::instance()->delta_s(t0, t1)
diff --git a/src/applications/osgearth_createtile/osgearth_createtile.cpp b/src/applications/osgearth_createtile/osgearth_createtile.cpp
index 56806ba..6084c0c 100644
--- a/src/applications/osgearth_createtile/osgearth_createtile.cpp
+++ b/src/applications/osgearth_createtile/osgearth_createtile.cpp
@@ -33,13 +33,11 @@
 #include <osgUtil/LineSegmentIntersector>
 #include <osgEarth/MapNode>
 #include <osgEarth/TerrainEngineNode>
-#include <osgEarth/ElevationQuery>
 #include <osgEarth/StringUtils>
 #include <osgEarth/Terrain>
 #include <osgEarth/GeoTransform>
 #include <osgEarthUtil/EarthManipulator>
 #include <osgEarthUtil/Controls>
-#include <osgEarthUtil/LatLongFormatter>
 #include <osgEarthUtil/ExampleResources>
 #include <osg/TriangleFunctor>
 #include <osgDB/WriteFile>
@@ -58,7 +56,11 @@ struct CollectTriangles
     {
         verts = new osg::Vec3Array();
     }
+#if OSG_VERSION_LESS_THAN(3,5,6)
     inline void operator () (const osg::Vec3& v1,const osg::Vec3& v2,const osg::Vec3& v3, bool treatVertexDataAsTemporary)
+#else
+    inline void operator () (const osg::Vec3& v1,const osg::Vec3& v2,const osg::Vec3& v3)
+#endif
     {
         verts->push_back(v1);
         verts->push_back(v2);
@@ -211,7 +213,7 @@ struct CreateTileHandler : public osgGA::GUIEventHandler
 
                 // Clamp the marker to the intersection of the triangles created by osgEarth.  This should line up with the mesh that is actually rendered.
                 double z = 0.0;
-                s_mapNode->getTerrain()->getHeight( node, s_mapNode->getMapSRS(), mapPoint.x(), mapPoint.y(), &z);
+                s_mapNode->getTerrain()->getHeight( node.get(), s_mapNode->getMapSRS(), mapPoint.x(), mapPoint.y(), &z);
 
                 GeoTransform* xform = new GeoTransform();
                 xform->setPosition( osgEarth::GeoPoint(s_mapNode->getMapSRS(),mapPoint.x(),  mapPoint.y(), z, ALTMODE_ABSOLUTE) );
diff --git a/src/applications/osgearth_datetime/osgearth_datetime.cpp b/src/applications/osgearth_datetime/osgearth_datetime.cpp
index 2df042b..9bec3a7 100644
--- a/src/applications/osgearth_datetime/osgearth_datetime.cpp
+++ b/src/applications/osgearth_datetime/osgearth_datetime.cpp
@@ -166,7 +166,7 @@ main(int argc, char** argv)
     
     osg::ref_ptr<CullNodeByDateTimeRange> callback = new CullNodeByDateTimeRange;
     
-    AssignCullCallbakVisitor assignVisitor(callback);
+    AssignCullCallbakVisitor assignVisitor(callback.get());
     assignVisitor.setNodeMaskOverride(~0);
     node->accept(assignVisitor);
 
@@ -178,7 +178,7 @@ main(int argc, char** argv)
     slider->setBackColor(.6, 0, 0, 1);
     slider->setHeight(25);
     slider->setWidth(300);    
-    slider->addEventHandler(new TimeSliderHandler(callback, collectVisitor.range));
+    slider->addEventHandler(new TimeSliderHandler(callback.get(), collectVisitor.range));
     slider->setValue(0.0);
     cs->addControl(slider);
 
diff --git a/src/applications/osgearth_deformation/osgearth_deformation.cpp b/src/applications/osgearth_deformation/osgearth_deformation.cpp
index fe13231..42ad66f 100644
--- a/src/applications/osgearth_deformation/osgearth_deformation.cpp
+++ b/src/applications/osgearth_deformation/osgearth_deformation.cpp
@@ -339,12 +339,10 @@ struct DeformationHandler : public osgGA::GUIEventHandler
         _tool(TOOL_CIRCLE),
         _root(root),
         _offset(-100.0f),
-        _radius(100.0),
-        _query( s_mapNode->getMap() )
+        _radius(100.0)
     {
         _map = s_mapNode->getMap();
-        _query.setMaxTilesToCache(10);
-        _query.setFallBackOnNoData( false );
+        _envelope = _map->getElevationPool()->createEnvelope(_map->getSRS(), 12u);
     }
 
     void update( float x, float y, osgViewer::View* view )
@@ -364,16 +362,17 @@ struct DeformationHandler : public osgGA::GUIEventHandler
             mapPoint.z() = 0;
 
             // do an elevation query:
-            double query_resolution = 0; // max.
-            double out_hamsl        = 0.0;
-            double out_resolution   = 0.0;
-
-            bool ok = _query.getElevation( 
-                mapPoint,
-                out_hamsl,
-                query_resolution, 
-                &out_resolution );
-            mapPoint.z() = out_hamsl;
+            typedef std::pair<float, float> ElAndRes;
+            ElAndRes elAndRes = _envelope->getElevationAndResolution(mapPoint.x(), mapPoint.y());
+
+            float hamsl = elAndRes.first;
+            float res = elAndRes.second;
+
+            if (hamsl != NO_DATA_VALUE)
+            {
+                mapPoint.z() = hamsl;
+            }
+
             _mapPoint = mapPoint;
 
             
@@ -523,7 +522,7 @@ struct DeformationHandler : public osgGA::GUIEventHandler
                 else if (_tool == TOOL_BLAST)
                 {
                     // Apply a simple blast radius
-                    applyBlast(_mapPoint, _radius, -_radius, itr->first, itr->second);
+                    applyBlast(_mapPoint, _radius, -_radius, itr->first, itr->second.get());
                 }
                 s_deformations->addHeightField( itr->first, itr->second.get());
             }
@@ -543,7 +542,8 @@ struct DeformationHandler : public osgGA::GUIEventHandler
     double _radius;
     GeoPoint _mapPoint;
     osg::ref_ptr < FeatureNode > _featureNode;
-    ElevationQuery   _query;
+    //ElevationQuery   _query;
+    osg::ref_ptr<ElevationEnvelope> _envelope;
 };
 
 
@@ -577,7 +577,7 @@ int main(int argc, char** argv)
 
     ElevationLayer* layer = new ElevationLayer(elevationOpt, s_deformations);
     layer->open();
-    s_mapNode->getMap()->addElevationLayer(layer);
+    s_mapNode->getMap()->addLayer(layer);
 
     osg::Group* root = new osg::Group();
     viewer.setSceneData( root );
diff --git a/src/applications/osgearth_elevation/osgearth_elevation.cpp b/src/applications/osgearth_elevation/osgearth_elevation.cpp
index 883d0a9..c9ad19c 100644
--- a/src/applications/osgearth_elevation/osgearth_elevation.cpp
+++ b/src/applications/osgearth_elevation/osgearth_elevation.cpp
@@ -27,7 +27,6 @@
 #include <osgUtil/LineSegmentIntersector>
 #include <osgEarth/MapNode>
 #include <osgEarth/TerrainEngineNode>
-#include <osgEarth/ElevationQuery>
 #include <osgEarth/StringUtils>
 #include <osgEarth/Terrain>
 #include <osgEarth/VerticalDatum>
@@ -35,7 +34,7 @@
 #include <osgEarthUtil/Controls>
 #include <osgEarthUtil/LatLongFormatter>
 #include <osgEarthUtil/ExampleResources>
-#include <osgEarthAnnotation/PlaceNode>
+#include <osgEarthAnnotation/ModelNode>
 #include <iomanip>
 
 using namespace osgEarth;
@@ -51,7 +50,7 @@ static LabelControl*  s_haeLabel    = 0L;
 static LabelControl*  s_egm96Label  = 0L;
 static LabelControl*  s_mapLabel    = 0L;
 static LabelControl*  s_resLabel    = 0L;
-static PlaceNode*     s_marker      = 0L;
+static ModelNode*     s_marker      = 0L;
 
 
 // An event handler that will print out the elevation at the clicked point
@@ -59,13 +58,11 @@ struct QueryElevationHandler : public osgGA::GUIEventHandler
 {
     QueryElevationHandler()
         : _mouseDown( false ),
-          _terrain  ( s_mapNode->getTerrain() ),
-          _query    ( s_mapNode->getMap() )
+          _terrain  ( s_mapNode->getTerrain() )
     {
         _map = s_mapNode->getMap();
-        _query.setMaxTilesToCache(10);
-        _query.setFallBackOnNoData( false );
         _path.push_back( s_mapNode->getTerrainEngine() );
+        _envelope = _map->getElevationPool()->createEnvelope(_map->getSRS(), 20u);
     }
 
     void update( float x, float y, osgViewer::View* view )
@@ -84,20 +81,20 @@ struct QueryElevationHandler : public osgGA::GUIEventHandler
             mapPoint.fromWorld( _terrain->getSRS(), world );
 
             // do an elevation query:
-            double query_resolution = 0; // max.
-            double out_hamsl        = 0.0;
-            double out_resolution   = 0.0;
+            double query_resolution  = 0.0;  // max.
+            double actual_resolution = 0.0;
+            float elevation          = 0.0f;
 
-            bool ok = _query.getElevation( 
-                mapPoint,
-                out_hamsl,
-                query_resolution, 
-                &out_resolution );
+            std::pair<float, float> result = _envelope->getElevationAndResolution(
+                mapPoint.x(), mapPoint.y());
 
-            if ( ok )
+            elevation = result.first;
+            actual_resolution = result.second;
+
+            if ( elevation != NO_DATA_VALUE )
             {
                 // convert to geodetic to get the HAE:
-                mapPoint.z() = out_hamsl;
+                mapPoint.z() = elevation;
                 GeoPoint mapPointGeodetic( s_mapNode->getMapSRS()->getGeodeticSRS(), mapPoint );
 
                 static LatLongFormatter s_f;
@@ -108,9 +105,15 @@ struct QueryElevationHandler : public osgGA::GUIEventHandler
                     << ", " 
                     << s_f.format(mapPointGeodetic.x(), false) );
 
-                s_mslLabel->setText( Stringify() << out_hamsl );
-                s_haeLabel->setText( Stringify() << mapPointGeodetic.z() );
-                s_resLabel->setText( Stringify() << out_resolution );
+                if (s_mapNode->getMapSRS()->isGeographic())
+                {
+                    double metersPerDegree = s_mapNode->getMapSRS()->getEllipsoid()->getRadiusEquator() / 360.0;
+                    actual_resolution *= metersPerDegree * cos(osg::DegreesToRadians(mapPoint.y()));
+                }
+
+                s_mslLabel->setText( Stringify() << elevation << " m" );
+                s_haeLabel->setText( Stringify() << mapPointGeodetic.z() << " m" );
+                s_resLabel->setText( Stringify() << actual_resolution << " m" );
 
                 double egm96z = mapPoint.z();
 
@@ -121,18 +124,23 @@ struct QueryElevationHandler : public osgGA::GUIEventHandler
                     mapPointGeodetic.x(),
                     egm96z);
                 
-                s_egm96Label->setText(Stringify() << egm96z);
+                s_egm96Label->setText(Stringify() << egm96z << " m");
 
                 yes = true;
             }
 
-            // finally, get a normal ISECT HAE point.
+            // now get a normal ISECT HAE point.
             GeoPoint isectPoint;
             isectPoint.fromWorld( _terrain->getSRS()->getGeodeticSRS(), world );
-            s_mapLabel->setText( Stringify() << isectPoint.alt() );
+            s_mapLabel->setText( Stringify() << isectPoint.alt() << " m");
 
             // and move the marker.
             s_marker->setPosition(mapPoint);
+
+            // normal test.
+            osg::Quat q;
+            q.makeRotate(osg::Vec3(0,0,1), hits.begin()->getLocalIntersectNormal());
+            s_marker->setLocalRotation(q);
         }
 
         if (!yes)
@@ -161,8 +169,24 @@ struct QueryElevationHandler : public osgGA::GUIEventHandler
     const Map*       _map;
     const Terrain*   _terrain;
     bool             _mouseDown;
-    ElevationQuery   _query;
     osg::NodePath    _path;
+    osg::ref_ptr<ElevationEnvelope> _envelope;
+};
+
+
+struct ClickToRemoveElevation : public ControlEventHandler
+{
+    void onClick(Control*)
+    {
+        Map* map = s_mapNode->getMap();
+        ElevationLayerVector layers;
+        map->getLayers(layers);
+        map->beginUpdate();
+        for (ElevationLayerVector::iterator i = layers.begin(); i != layers.end(); ++i) {
+            map->removeLayer(i->get());
+        }
+        map->endUpdate();
+    }
 };
 
 
@@ -204,6 +228,7 @@ int main(int argc, char** argv)
     grid->setControl(0,r++,new LabelControl("Scene graph intersection:"));
     grid->setControl(0,r++,new LabelControl("EGM96 elevation:"));
     grid->setControl(0,r++,new LabelControl("Query resolution:"));
+    grid->setControl(0, r++, new ButtonControl("Click to remove all elevation data", new ClickToRemoveElevation()));
 
     r = 1;
     s_posLabel = grid->setControl(1,r++,new LabelControl(""));
@@ -214,11 +239,15 @@ int main(int argc, char** argv)
     s_egm96Label = grid->setControl(1,r++,new LabelControl(""));
     s_resLabel = grid->setControl(1,r++,new LabelControl(""));
 
-    s_marker = new PlaceNode();
-    s_marker->setMapNode( s_mapNode );
-    s_marker->setIconImage(osgDB::readImageFile("../data/placemark32.png"));
+    
+    Style markerStyle;
+    markerStyle.getOrCreate<ModelSymbol>()->url()->setLiteral("../data/axes.osgt.64.scale");
+    markerStyle.getOrCreate<ModelSymbol>()->autoScale() = true;
+    s_marker = new ModelNode(s_mapNode, markerStyle);
+    //s_marker->setMapNode( s_mapNode );
+    //s_marker->setIconImage(osgDB::readImageFile("../data/placemark32.png"));
     s_marker->setDynamic(true);
-    root->addChild( s_marker );
+    s_mapNode->addChild( s_marker );
 
     const SpatialReference* mapSRS = s_mapNode->getMapSRS();
     s_vdaLabel->setText( mapSRS->getVerticalDatum() ? 
diff --git a/src/applications/osgearth_ephemeris/osgearth_ephemeris.cpp b/src/applications/osgearth_ephemeris/osgearth_ephemeris.cpp
index 797a98c..c5d4dc4 100644
--- a/src/applications/osgearth_ephemeris/osgearth_ephemeris.cpp
+++ b/src/applications/osgearth_ephemeris/osgearth_ephemeris.cpp
@@ -22,6 +22,7 @@
 
 #include <osgViewer/Viewer>
 #include <osgEarth/Notify>
+#include <osgEarth/NodeUtils>
 #include <osgEarthUtil/EarthManipulator>
 #include <osgEarthUtil/ExampleResources>
 #include <osgEarthUtil/Ephemeris>
@@ -76,7 +77,7 @@ main(int argc, char** argv)
 
     viewer.getCamera()->setSmallFeatureCullingPixelSize(-1.0f);
 
-    osg::ref_ptr<osg::Image> mark = osgDB::readImageFile("../data/placemark32.png");
+    osg::ref_ptr<osg::Image> mark = osgDB::readRefImageFile("../data/placemark32.png");
     
     App app;
 
@@ -92,12 +93,12 @@ main(int argc, char** argv)
 
         app.sunPos = new PlaceNode(mapNode, GeoPoint(), mark.get(), "Sun");
         app.sunPos->setDynamic(true);
-        root->addChild( app.sunPos.get() );
+        mapNode->addChild( app.sunPos.get() );
 
         app.moonPos = new PlaceNode(mapNode, GeoPoint(), mark.get(), "Moon");
         app.moonPos->setDynamic(true);
 
-        root->addChild( app.moonPos.get() );        
+        mapNode->addChild( app.moonPos.get() );        
 
 
         app.sky = osgEarth::findTopMostNodeOfType<SkyNode>(node);        
diff --git a/src/applications/osgearth_featureeditor/osgearth_featureeditor.cpp b/src/applications/osgearth_featureeditor/osgearth_featureeditor.cpp
deleted file mode 100644
index 432e994..0000000
--- a/src/applications/osgearth_featureeditor/osgearth_featureeditor.cpp
+++ /dev/null
@@ -1,286 +0,0 @@
-/* -*-c++-*- */
-/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2016 Pelican Mapping
-* http://osgearth.org
-*
-* osgEarth is free software; you can redistribute it and/or modify
-* it under the terms of the GNU Lesser General Public License as published by
-* the Free Software Foundation; either version 2 of the License, or
-* (at your option) any later version.
-*
-* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
-* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
-* IN THE SOFTWARE.
-*
-* You should have received a copy of the GNU Lesser General Public License
-* along with this program.  If not, see <http://www.gnu.org/licenses/>
-*/
-
-#include <osg/Notify>
-#include <osgGA/StateSetManipulator>
-#include <osgViewer/Viewer>
-#include <osgViewer/ViewerEventHandlers>
-#include <osgGA/GUIEventHandler>
-#include <osgEarth/Map>
-#include <osgEarth/MapNode>
-#include <osgEarthUtil/EarthManipulator>
-#include <osgEarthUtil/AutoClipPlaneHandler>
-#include <osgEarth/Utils>
-#include <osgEarthFeatures/GeometryUtils>
-
-#include <osgEarthSymbology/Style>
-
-#include <osgEarthDrivers/gdal/GDALOptions>
-#include <osgEarthDrivers/feature_ogr/OGRFeatureOptions>
-#include <osgEarthDrivers/agglite/AGGLiteOptions>
-#include <osgEarthDrivers/model_feature_geom/FeatureGeomModelOptions>
-
-#include <osgEarthUtil/Controls>
-
-#include <osgEarthAnnotation/FeatureEditing>
-
-using namespace osgEarth;
-using namespace osgEarth::Features;
-using namespace osgEarth::Drivers;
-using namespace osgEarth::Symbology;
-using namespace osgEarth::Util;
-using namespace osgEarth::Util::Controls;
-using namespace osgEarth::Annotation;
-
-osg::Vec4
-randomColor()
-{
-    float r = (float)rand() / (float)RAND_MAX;
-    float g = (float)rand() / (float)RAND_MAX;
-    float b = (float)rand() / (float)RAND_MAX;
-    return osg::Vec4(r,g,b,1.0f);
-}
-
-
-static int s_fid = 0;
-
-static osg::ref_ptr< AddPointHandler > s_addPointHandler;
-static osg::ref_ptr< osg::Node > s_editor;
-static osg::ref_ptr< FeatureNode > s_featureNode;
-static osgViewer::Viewer* s_viewer;
-static osg::ref_ptr< osg::Group > s_root;
-static osg::ref_ptr< osg::Group > s_editorsRoot;
-static osg::ref_ptr< MapNode > s_mapNode;
-
-Grid* createToolBar()
-{    
-    Grid* toolbar = new Grid();
-    toolbar->setBackColor(0,0,0,0.5);
-    toolbar->setMargin( 10 );
-    toolbar->setPadding( 10 );
-    toolbar->setChildSpacing( 10 );
-    toolbar->setChildVertAlign( Control::ALIGN_CENTER );
-    toolbar->setAbsorbEvents( true );
-    toolbar->setVertAlign( Control::ALIGN_TOP );    
-    return toolbar;    
-}
-
-struct AddVertsModeHandler : public ControlEventHandler
-{
-    AddVertsModeHandler()        
-    {
-    }
-
-    void onClick( Control* control, int mouseButtonMask ) {
-
-        //remove the editor if it's valid
-        if (s_editor.valid())
-        {
-            s_editorsRoot->removeChild( s_editor.get() );
-            s_editor = NULL;
-
-            // Unset the stipple on the line
-            Style style = s_featureNode->getStyle();
-            style.get<LineSymbol>()->stroke()->stipple().unset();
-            s_featureNode->setStyle( style );            
-        }
-
-        //Add the new add point handler
-        if (!s_addPointHandler.valid())
-        {            
-            s_addPointHandler = new AddPointHandler( s_featureNode.get() );
-            s_addPointHandler->setIntersectionMask( 0x1 );
-            s_viewer->addEventHandler( s_addPointHandler.get() );
-        }        
-    }
-};
-
-struct EditModeHandler : public ControlEventHandler
-{
-    EditModeHandler()        
-    { 
-    }
-
-    void onClick( Control* control, int mouseButtonMask ) {
-        
-        //Remove the add point handler if it's valid
-        if (s_addPointHandler.valid())
-        {            
-            osgEarth::removeEventHandler( s_viewer, s_addPointHandler.get() );
-            s_addPointHandler = NULL;
-        }        
-
-        if (!s_editor.valid())
-        {            
-            Style style = s_featureNode->getStyle();
-            style.getOrCreate<LineSymbol>()->stroke()->stipple() = 0x00FF;                        
-            s_featureNode->setStyle( style );            
-            s_editor = new FeatureEditor( s_featureNode );
-            s_editorsRoot->addChild( s_editor.get() );            
-        }
-    }    
-};
-
-struct ChangeStyleHandler : public ControlEventHandler
-{
-    ChangeStyleHandler(const Style &style) 
-        : _style( style )
-    {
-        //nop
-    }
-
-    void onClick( Control* control, int mouseButtonMask ) {
-        s_featureNode->setStyle( _style );        
-    }
-
-    Style _style;  
-};
-
-Style buildStyle( const osg::Vec4 &color, float width )
-{
-    // Define a style for the feature data. Since we are going to render the
-    // vectors as lines, configure the line symbolizer:
-    Style style;
-
-    LineSymbol* ls = style.getOrCreateSymbol<LineSymbol>();
-    ls->stroke()->color() = color;
-    ls->stroke()->width() = width;        
-    
-    AltitudeSymbol* as = style.getOrCreate<AltitudeSymbol>();
-    as->clamping() = AltitudeSymbol::CLAMP_TO_TERRAIN;
-    as->technique() = AltitudeSymbol::TECHNIQUE_DRAPE;
-
-    style.getOrCreate<PolygonSymbol>()->fill()->color() = Color::Red;
-
-    RenderSymbol* rs = style.getOrCreateSymbol<RenderSymbol>();
-    rs->depthOffset()->enabled() = true;
-    rs->depthOffset()->minBias() = 1000;
-
-    return style;    
-}
-
-//
-// NOTE: run this sample from the repo/tests directory.
-//
-int main(int argc, char** argv)
-{
-    osg::ArgumentParser arguments(&argc,argv);
-
-    osgViewer::Viewer viewer(arguments);
-    s_viewer = &viewer;
-
-    // Start by creating the map:
-    s_mapNode = MapNode::load(arguments);
-    if ( !s_mapNode )
-    {
-        Map* map = new Map();
-
-        // Start with a basemap imagery layer; we'll be using the GDAL driver
-        // to load a local GeoTIFF file:
-        GDALOptions basemapOpt;
-        basemapOpt.url() = "../data/world.tif";
-        map->addImageLayer( new ImageLayer( ImageLayerOptions("basemap", basemapOpt) ) );
-
-        // That's it, the map is ready; now create a MapNode to render the Map:
-        MapNodeOptions mapNodeOptions;
-        mapNodeOptions.enableLighting() = false;
-
-        s_mapNode = new MapNode( map, mapNodeOptions );
-    }
-    s_mapNode->setNodeMask( 0x01 );    
-
-        
-    // Define a style for the feature data.
-    Style style = buildStyle( Color::Yellow, 2.0f );    
-
-    //LineString* line = new LineString();    
-    Geometry* geom = GeometryUtils::geometryFromWKT("POLYGON((191.026667 87.63333,114.75 78,89.5 77.333336,81.833336 75.333336,70.683334 74.5,70.916664 73.666664,68.666664 73.666664,66.291664 71.505,57.65 71.166664,58 73.9,48.616665 73,49.198334 71.43,49.5 70.5,43.266666 68.666664,32.083332 71.5,32.083332 74,35 74,35 81,32 81,32 90,191.026667 87.63333))");
-    OE_NOTICE << "Geometry " << GeometryUtils::geometryToWKT(geom) << std::endl;
-    Feature* feature = new Feature(geom, s_mapNode->getMapSRS(), Style(), s_fid++);
-    s_featureNode = new FeatureNode( s_mapNode, feature );    
-    s_featureNode->setStyle( style );
-    
-    s_editorsRoot = new osg::Group;
-
-    s_root = new osg::Group;
-    s_root->addChild( s_mapNode.get() );
-    s_root->addChild( s_featureNode.get() );
-    s_root->addChild( s_editorsRoot.get() );
-
-
-    //Setup the controls
-    ControlCanvas* canvas = ControlCanvas::getOrCreate( &viewer );
-    s_root->addChild( canvas );
-    Grid *toolbar = createToolBar( );
-    canvas->addControl( toolbar );
-    canvas->setNodeMask( 0x1 << 1 );
-
-    int col = 0;
-    LabelControl* addVerts = new LabelControl("Add Verts");
-    toolbar->setControl(col++, 0, addVerts );    
-    addVerts->addEventHandler( new AddVertsModeHandler());
-    
-    LabelControl* edit = new LabelControl("Edit");
-    toolbar->setControl(col++, 0, edit );    
-    edit->addEventHandler(new EditModeHandler());
-
-    unsigned int row = 0;
-    Grid *styleBar = createToolBar( );
-    styleBar->setPosition(0, 50);
-    canvas->addControl( styleBar );
-    
-    //Make a list of styles
-    styleBar->setControl(0, row++, new LabelControl("Styles") );    
-
-    unsigned int numStyles = 8;
-    for (unsigned int i = 0; i < numStyles; ++i)
-    {
-        float w = 50;
-        osg::Vec4 color = randomColor();
-
-        float widths[3] = {2, 4, 8};
-
-        unsigned int r = row++;
-        for (unsigned int j = 0; j < 3; j++) 
-        {
-            Control* l = new Control();            
-            l->setBackColor( color );
-            l->addEventHandler(new ChangeStyleHandler(buildStyle( color, widths[j] ) ));
-            l->setSize(w,5 * widths[j]);
-            styleBar->setControl(j, r, l);
-        }
-    }
-   
-    
-    viewer.setSceneData( s_root.get() );
-    viewer.setCameraManipulator( new EarthManipulator() );
-
-    if ( s_mapNode )
-        viewer.getCamera()->addCullCallback( new osgEarth::Util::AutoClipPlaneCullCallback(s_mapNode) );
-
-    // add some stock OSG handlers:
-    viewer.addEventHandler(new osgViewer::StatsHandler());
-    viewer.addEventHandler(new osgViewer::WindowSizeHandler());
-    viewer.addEventHandler(new osgGA::StateSetManipulator(viewer.getCamera()->getOrCreateStateSet()));
-
-    return viewer.run();
-}
diff --git a/src/applications/osgearth_featurefilter/osgearth_featurefilter.cpp b/src/applications/osgearth_featurefilter/osgearth_featurefilter.cpp
index 068c035..94e4521 100644
--- a/src/applications/osgearth_featurefilter/osgearth_featurefilter.cpp
+++ b/src/applications/osgearth_featurefilter/osgearth_featurefilter.cpp
@@ -25,6 +25,7 @@
 #include <osgEarthUtil/EarthManipulator>
 #include <osgEarthUtil/ExampleResources>
 #include <osgEarthFeatures/Filter>
+#include <osgEarthFeatures/FilterContext>
 
 #define LC "[viewer] "
 
@@ -83,8 +84,6 @@ main(int argc, char** argv)
 {    
     //Run this example with the the feature_custom_filters.earth file in the tests directory for a simple example
     osg::ArgumentParser arguments(&argc,argv);
-    if ( arguments.read("--stencil") )
-        osg::DisplaySettings::instance()->setMinimumNumStencilBits( 8 );
 
     // create a viewer:
     osgViewer::Viewer viewer(arguments);
diff --git a/src/applications/osgearth_featureinfo/osgearth_featureinfo.cpp b/src/applications/osgearth_featureinfo/osgearth_featureinfo.cpp
index db4fd2f..382d68b 100644
--- a/src/applications/osgearth_featureinfo/osgearth_featureinfo.cpp
+++ b/src/applications/osgearth_featureinfo/osgearth_featureinfo.cpp
@@ -21,15 +21,15 @@
 */
 
 #include <osg/Notify>
-
 #include <osgEarthDrivers/feature_ogr/OGRFeatureOptions>
 
+#include <osgEarthFeatures/GeometryUtils>
+#include <osgEarthFeatures/FeatureCursor>
+
 using namespace osgEarth::Features;
 using namespace osgEarth::Drivers;
 using namespace osgEarth::Symbology;
 
-#include <osgEarthFeatures/GeometryUtils>
-
 std::string attributeTypeToString( AttributeType type )
 {
     switch (type)
diff --git a/src/applications/osgearth_featuremanip/osgearth_featuremanip.cpp b/src/applications/osgearth_featuremanip/osgearth_featuremanip.cpp
deleted file mode 100644
index cd2cadf..0000000
--- a/src/applications/osgearth_featuremanip/osgearth_featuremanip.cpp
+++ /dev/null
@@ -1,168 +0,0 @@
-/* -*-c++-*- */
-/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
-* http://osgearth.org
-*
-* osgEarth is free software; you can redistribute it and/or modify
-* it under the terms of the GNU Lesser General Public License as published by
-* the Free Software Foundation; either version 2 of the License, or
-* (at your option) any later version.
-*
-* This program is distributed in the hope that it will be useful,
-* but WITHOUT ANY WARRANTY; without even the implied warranty of
-* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-* GNU Lesser General Public License for more details.
-*
-* You should have received a copy of the GNU Lesser General Public License
-* along with this program.  If not, see <http://www.gnu.org/licenses/>
-*/
-
-#include <osg/Notify>
-#include <osgGA/StateSetManipulator>
-#include <osgGA/GUIEventHandler>
-#include <osgViewer/Viewer>
-#include <osgViewer/ViewerEventHandlers>
-#include <osgEarth/MapNode>
-#include <osgEarthUtil/EarthManipulator>
-#include <osgEarthUtil/ExampleResources>
-#include <osgEarthUtil/Controls>
-
-#include <osgEarthUtil/FeatureManipTool>
-
-#define LC "[feature_manip] "
-
-using namespace osgEarth::Util;
-using namespace osgEarth::Util::Controls;
-
-//------------------------------------------------------------------------
-
-static FeatureManipTool* s_manipTool;
-
-static VBox* s_state_normal;
-static VBox* s_state_active;
-
-// Callback to toggle the visibility of the save/cancel buttons based on tool state
-struct ToggleUIStateCallback : public FeatureQueryTool::Callback
-{
-    // called when a valid feature is found under the mouse coords
-    virtual void onHit( FeatureSourceIndexNode* index, FeatureID fid, const EventArgs& args )
-    {
-        s_state_active->setVisible( true );
-    }
-
-    // called when no feature is found under the mouse coords
-    virtual void onMiss( const EventArgs& args )
-    {
-        s_state_active->setVisible( false );
-    }
-};
-
-
-// Cancels the manipulation when user clicks "cancel"
-struct OnCancel : public ControlEventHandler
-{
-    void onClick( Control* control )
-    {
-        s_manipTool->cancel();
-        s_state_active->setVisible( false );
-    }
-};
-
-
-// Commits the manipulation when user clicks "save"
-struct OnSave : public ControlEventHandler
-{
-    void onClick( Control* saveButton )
-    {
-        s_manipTool->commit();
-        s_state_active->setVisible( false );
-    }
-};
-
-
-// creaes a simple user interface for the manip demo
-Control*
-createUI()
-{
-    VBox* vbox = new VBox();
-    vbox->addControl( new LabelControl("Feature Manipulator Demo", Color::Yellow) );
-
-    s_state_normal = vbox->addControl(new VBox());
-    s_state_normal->addControl( new LabelControl("Shift-click on a feature to enter edit mode.") );
-    
-    s_state_active = vbox->addControl(new VBox());
-    s_state_active->setVisible( false );
-    s_state_active->addControl( new LabelControl("Drag the handles to position or rotation the feature.") );
-    
-    HBox* buttons = s_state_active->addControl(new HBox());
-    
-    LabelControl* cancel = buttons->addControl(new LabelControl("cancel"));
-    cancel->setBackColor(Color(Color::White,0.5));
-    cancel->setActiveColor(Color::Blue);
-    cancel->addEventHandler(new OnCancel());
-    cancel->setPadding( 5.0f );
-    cancel->setVertFill( true );
-
-    LabelControl* save = buttons->addControl(new LabelControl("save"));
-    save->setBackColor(Color(Color::White,0.5));
-    save->setActiveColor(Color::Blue);
-    save->addEventHandler(new OnSave());
-    save->setPadding( 5.0f );
-    save->setMargin(Control::SIDE_LEFT, 20.0f);
-    save->setVertFill( true );
-
-    vbox->setMargin( Control::SIDE_BOTTOM, 15.0f );
-    return vbox;
-} 
-
-//------------------------------------------------------------------------
-
-int
-main(int argc, char** argv)
-{
-    osg::ArgumentParser arguments(&argc,argv);
-    if ( arguments.read("--stencil") )
-        osg::DisplaySettings::instance()->setMinimumNumStencilBits( 8 );
-
-    // a basic OSG viewer
-    osgViewer::Viewer viewer(arguments);
-
-    // install our default manipulator (do this before using MapNodeHelper)
-    viewer.setCameraManipulator( new EarthManipulator() );
-
-    // load an earth file, and support all or our example command-line options
-    // and earth file <external> tags
-    osg::Group* root = MapNodeHelper().load( arguments, &viewer, createUI() );
-    if ( root )
-    {
-        viewer.setSceneData( root );
-
-        // configure the near/far so we don't clip things that are up close
-        viewer.getCamera()->setNearFarRatio(0.00002);
-
-        // add some stock OSG handlers:
-        viewer.addEventHandler(new osgViewer::StatsHandler());
-        viewer.addEventHandler(new osgViewer::WindowSizeHandler());
-        viewer.addEventHandler(new osgViewer::ThreadingHandler());
-        viewer.addEventHandler(new osgViewer::LODScaleHandler());
-        viewer.addEventHandler(new osgGA::StateSetManipulator(viewer.getCamera()->getOrCreateStateSet()));
-
-        MapNode* mapNode = MapNode::findMapNode( root );
-        if ( mapNode )
-        {
-            // install the Feature Manipulation tool.
-            s_manipTool = new FeatureManipTool( mapNode, true );
-            viewer.addEventHandler( s_manipTool );
-
-            s_manipTool->addCallback( new ToggleUIStateCallback() );
-        }
-
-        return viewer.run();
-    }
-    else
-    {
-        OE_NOTICE 
-            << "\nUsage: " << argv[0] << " file.earth" << std::endl
-            << MapNodeHelper().usage() << std::endl;
-    }
-}
diff --git a/src/applications/osgearth_featurequery/osgearth_featurequery.cpp b/src/applications/osgearth_featurequery/osgearth_featurequery.cpp
index cc447f4..6d4522d 100644
--- a/src/applications/osgearth_featurequery/osgearth_featurequery.cpp
+++ b/src/applications/osgearth_featurequery/osgearth_featurequery.cpp
@@ -24,19 +24,20 @@
 #include <osgViewer/Viewer>
 #include <osgViewer/ViewerEventHandlers>
 #include <osgEarth/MapNode>
-#include <osgEarth/ShaderLoader>
-#include <osgEarth/VirtualProgram>
 #include <osgEarth/Registry>
 #include <osgEarth/ObjectIndex>
 #include <osgEarthUtil/EarthManipulator>
 #include <osgEarthUtil/ExampleResources>
 #include <osgEarthUtil/Controls>
-#include <osgEarthUtil/FeatureQueryTool>
+#include <osgEarthUtil/RTTPicker>
+#include <osgEarthFeatures/Feature>
+#include <osgEarthFeatures/FeatureIndex>
 
 #define LC "[feature_query] "
 
 using namespace osgEarth::Util;
 using namespace osgEarth::Util::Controls;
+using namespace osgEarth::Features;
 
 //-----------------------------------------------------------------------
 
@@ -60,19 +61,19 @@ Container* createUI()
  * user interface grid control.
  */
 
-class ReadoutCallback : public FeatureQueryTool::Callback
+class ReadoutCallback : public RTTPicker::Callback
 {
 public:
     ReadoutCallback(ControlCanvas* container) : _lastFID( ~0 )
     {
         _grid = new Grid();
-        _grid->setBackColor( Color(Color::Black,0.7f) );
+        _grid->setBackColor( osg::Vec4(0,0,0,0.7f) );
         container->addControl( _grid );
     }
 
     void onHit(ObjectID id)
     {
-        FeatureIndex* index = Registry::objectIndex()->get<FeatureIndex>( id );
+        FeatureIndex* index = Registry::objectIndex()->get<FeatureIndex>(id).get();
         Feature* feature = index ? index->getFeature( id ) : 0L;
         if ( feature && feature->getFID() != _lastFID )
         {
@@ -135,13 +136,13 @@ main(int argc, char** argv)
         if ( mapNode )
         {
             // Install the query tool.
-            FeatureQueryTool* tool = new FeatureQueryTool();
-            viewer.addEventHandler( tool );
-            tool->addChild( mapNode );
+            RTTPicker* picker = new RTTPicker();
+            viewer.addEventHandler( picker );
+            picker->addChild( mapNode );
 
             // Install a readout for feature metadata.
             ControlCanvas* canvas = ControlCanvas::getOrCreate(&viewer);
-            tool->setDefaultCallback( new ReadoutCallback(canvas) );
+            picker->setDefaultCallback( new ReadoutCallback(canvas) );
         }
 
         return viewer.run();
diff --git a/src/applications/osgearth_features/osgearth_features.cpp b/src/applications/osgearth_features/osgearth_features.cpp
index a5c10fb..51fd52d 100644
--- a/src/applications/osgearth_features/osgearth_features.cpp
+++ b/src/applications/osgearth_features/osgearth_features.cpp
@@ -26,13 +26,16 @@
 #include <osgViewer/ViewerEventHandlers>
 #include <osgEarth/Map>
 #include <osgEarth/MapNode>
+#include <osgEarth/ImageLayer>
+#include <osgEarth/ModelLayer>
 #include <osgEarthUtil/ExampleResources>
 #include <osgEarthUtil/EarthManipulator>
 #include <osgEarthUtil/AutoClipPlaneHandler>
 
 #include <osgEarthSymbology/Style>
-#include <osgEarthFeatures/ConvertTypeFilter>
+#include <osgEarthFeatures/FeatureModelLayer>
 
+#include <osgEarthDrivers/engine_rex/RexTerrainEngineOptions>
 #include <osgEarthDrivers/gdal/GDALOptions>
 #include <osgEarthDrivers/feature_ogr/OGRFeatureOptions>
 #include <osgEarthDrivers/agglite/AGGLiteOptions>
@@ -82,16 +85,16 @@ int main(int argc, char** argv)
 
     // Start with a basemap imagery layer; we'll be using the GDAL driver
     // to load a local GeoTIFF file:
-    GDALOptions basemapOpt;
-    basemapOpt.url() = "../data/world.tif";
-    map->addImageLayer( new ImageLayer( ImageLayerOptions("basemap", basemapOpt) ) );
-
+    GDALOptions basemap;
+    basemap.url() = "../data/world.tif";
+    map->addLayer( new ImageLayer(ImageLayerOptions("basemap", basemap)));
+    
     // Next we add a feature layer. 
-    OGRFeatureOptions featureOptions;
+    OGRFeatureOptions ogrData;
     if ( !useMem )
     {
         // Configures the feature driver to load the vectors from a shapefile:
-        featureOptions.url() = "../data/world.shp";
+        ogrData.url() = "../data/world.shp";
     }
     else
     {
@@ -101,9 +104,15 @@ int main(int argc, char** argv)
         line->push_back( osg::Vec3d(-120, 20, 0) );
         line->push_back( osg::Vec3d(-120, 60, 0) );
         line->push_back( osg::Vec3d(-60, 60, 0) );
-        featureOptions.geometry() = line;
+        ogrData.geometry() = line;
     }
 
+    // Make a feature source layer and add it to the Map:
+    FeatureSourceLayerOptions ogrLayer;
+    ogrLayer.name() = "vector-data";
+    ogrLayer.featureSource() = ogrData;
+    map->addLayer(new FeatureSourceLayer(ogrLayer));
+
     // Define a style for the feature data. Since we are going to render the
     // vectors as lines, configure the line symbolizer:
     Style style;
@@ -113,8 +122,11 @@ int main(int argc, char** argv)
     ls->stroke()->width() = 2.0f;
 
     // That's it, the map is ready; now create a MapNode to render the Map:
+    osgEarth::Drivers::RexTerrainEngine::RexTerrainEngineOptions rex;
+
     MapNodeOptions mapNodeOptions;
     mapNodeOptions.enableLighting() = false;
+    mapNodeOptions.setTerrainOptions(rex);
     MapNode* mapNode = new MapNode( map, mapNodeOptions );
 
     osg::Group* root = new osg::Group();
@@ -128,21 +140,21 @@ int main(int argc, char** argv)
     if (useRaster)
     {
         AGGLiteOptions rasterOptions;
-        rasterOptions.featureOptions() = featureOptions;
+        rasterOptions.featureOptions() = ogrData;
         rasterOptions.styles() = new StyleSheet();
         rasterOptions.styles()->addStyle( style );
-        map->addImageLayer( new ImageLayer("my features", rasterOptions) );
+        map->addLayer(new ImageLayer("My Features", rasterOptions) );
     }
     else //if (useGeom || useOverlay)
     {
-        FeatureGeomModelOptions geomOptions;
-        geomOptions.featureOptions() = featureOptions;
-        geomOptions.styles() = new StyleSheet();
-        geomOptions.styles()->addStyle( style );
-        geomOptions.enableLighting() = false;
-
-        ModelLayerOptions layerOptions( "my features", geomOptions );
-        map->addModelLayer( new ModelLayer(layerOptions) );
+        FeatureModelLayerOptions fml;
+        fml.name() = "My Features";
+        fml.featureSourceLayer() = "vector-data";
+        fml.styles() = new StyleSheet();
+        fml.styles()->addStyle(style);
+        fml.enableLighting() = false;
+
+        map->addLayer(new FeatureModelLayer(fml));
     }
 
     if ( useLabels )
@@ -155,19 +167,20 @@ int main(int argc, char** argv)
         TextSymbol* text = labelStyle.getOrCreateSymbol<TextSymbol>();
         text->content() = StringExpression( "[cntry_name]" );
         text->priority() = NumericExpression( "[pop_cntry]" );
-        text->removeDuplicateLabels() = true;
         text->size() = 16.0f;
         text->alignment() = TextSymbol::ALIGN_CENTER_CENTER;
         text->fill()->color() = Color::White;
         text->halo()->color() = Color::DarkGray;
 
         // and configure a model layer:
-        FeatureGeomModelOptions geomOptions;
-        geomOptions.featureOptions() = featureOptions;
-        geomOptions.styles() = new StyleSheet();
-        geomOptions.styles()->addStyle( labelStyle );
-
-        map->addModelLayer( new ModelLayer("labels", geomOptions) );
+        FeatureModelLayerOptions fml;
+        fml.name() = "Labels";
+        fml.featureSourceLayer() = "vector-data";
+        //fml.featureSource() = featureOptions;
+        fml.styles() = new StyleSheet();
+        fml.styles()->addStyle( labelStyle );
+
+        map->addLayer(new FeatureModelLayer(fml));
     }
 
     
diff --git a/src/applications/osgearth_fog/osgearth_fog.cpp b/src/applications/osgearth_fog/osgearth_fog.cpp
deleted file mode 100644
index d85eeac..0000000
--- a/src/applications/osgearth_fog/osgearth_fog.cpp
+++ /dev/null
@@ -1,100 +0,0 @@
-/* -*-c++-*- */
-/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2016 Pelican Mapping
-* http://osgearth.org
-*
-* osgEarth is free software; you can redistribute it and/or modify
-* it under the terms of the GNU Lesser General Public License as published by
-* the Free Software Foundation; either version 2 of the License, or
-* (at your option) any later version.
-*
-* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
-* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
-* IN THE SOFTWARE.
-*
-* You should have received a copy of the GNU Lesser General Public License
-* along with this program.  If not, see <http://www.gnu.org/licenses/>
-*/
-
-#include <osg/Notify>
-#include <osg/MatrixTransform>
-#include <osgGA/TrackballManipulator>
-#include <osgViewer/Viewer>
-#include <osgEarth/Registry>
-#include <osgDB/ReadFile>
-
-#include <osgEarthUtil/Fog>
-#include <osg/Fog>
-
-using namespace osgEarth;
-using namespace osgEarth::Util;
-
-int
-main(int argc, char** argv)
-{
-    osg::ArgumentParser arguments(&argc,argv);
-
-    // create a viewer:
-    osgViewer::Viewer viewer(arguments);
-
-    osg::Group* root = new osg::Group();    
-
-    // Setup a Fog state attribute    
-    osg::Fog* fog = new osg::Fog;            
-    fog->setColor( viewer.getCamera()->getClearColor() );                
-    fog->setDensity( 0.02 );    
-    fog->setMode(osg::Fog::LINEAR);
-    fog->setStart(5.0);
-    fog->setEnd(50.0);
-    root->getOrCreateStateSet()->setAttributeAndModes( fog, osg::StateAttribute::ON );                
-    
-    // Attach the FogCallback to keep the Fog uniforms up to date.
-    fog->setUpdateCallback(new FogCallback()); 
-
-    // Add the regular cow.
-    root->addChild(osgDB::readNodeFile("cow.osg"));
-
-    // Add a shader based cow to the right for comparison.
-    osg::MatrixTransform* mt = new osg::MatrixTransform;    
-    osg::Node* cowShader = osgDB::readNodeFile("cow.osg.10,0,0.trans");    
-    osgEarth::Registry::instance()->shaderGenerator().run(cowShader);    
-    
-    // Attach the fog effect so fog will take effect.
-    FogEffect* fogEffect = new FogEffect;
-    fogEffect->attach( cowShader->getOrCreateStateSet() );
-    mt->addChild( cowShader );
-    root->addChild(mt);
-
-    viewer.setCameraManipulator(new osgGA::TrackballManipulator());
-    
-    viewer.setSceneData( root );
-
-    while (!viewer.done())
-    {        
-        // Change fog modes ever 200 frames.
-        if (viewer.getFrameStamp()->getFrameNumber() % 200 == 0)
-        {
-            if (fog->getMode() == osg::Fog::LINEAR)
-            {
-                fog->setMode(osg::Fog::EXP);
-                OE_NOTICE << "switching to osg::Fog::EXP" << std::endl;
-            }
-            else if (fog->getMode() == osg::Fog::EXP)
-            {
-                fog->setMode(osg::Fog::EXP2);
-                OE_NOTICE << "switching to osg::Fog::EXP2" << std::endl;
-            }
-            else
-            {
-                fog->setMode(osg::Fog::LINEAR);
-                OE_NOTICE << "switching to osg::Fog::LINEAR" << std::endl;
-            }
-        }
-        viewer.frame();
-    }
-    return 0;
-}
diff --git a/src/applications/osgearth_graticule/osgearth_graticule.cpp b/src/applications/osgearth_graticule/osgearth_graticule.cpp
index 45164fd..eaa9901 100644
--- a/src/applications/osgearth_graticule/osgearth_graticule.cpp
+++ b/src/applications/osgearth_graticule/osgearth_graticule.cpp
@@ -34,9 +34,10 @@
 #include <osgEarthUtil/GeodeticGraticule>
 #include <osgEarthUtil/MGRSGraticule>
 #include <osgEarthUtil/UTMGraticule>
-#include <osgEarthUtil/GraticuleNode>
+#include <osgEarthUtil/GARSGraticule>
 
 using namespace osgEarth::Util;
+using namespace osgEarth::Annotation;
 
 int
 usage( const std::string& msg )
@@ -44,45 +45,15 @@ usage( const std::string& msg )
     OE_NOTICE 
         << msg << std::endl
         << "USAGE: osgearth_graticule [options] file.earth" << std::endl
-        << "   --geodetic            : display a geodetic (lat/long) graticule" << std::endl
+        << "   --geodetic            : display a Lat/Long graticule" << std::endl
         << "   --utm                 : display a UTM graticule" << std::endl
         << "   --mgrs                : display an MGRS graticule" << std::endl
-        << "   --shader              : display a geodetic graticule using the glsl shaders" << std::endl;
+        << "   --gars                : display a GARS graticule" << std::endl;
     return -1;
 }
 
 //------------------------------------------------------------------------
 
-struct ToggleGraticuleHandler : public ControlEventHandler
-{
-    ToggleGraticuleHandler( GraticuleNode* graticule ) : _graticule( graticule ) { }
-
-    void onValueChanged( Control* control, bool value )
-    {
-        _graticule->setVisible( value );
-    }
-
-    GraticuleNode* _graticule;
-};
-
-struct OffsetGraticuleHandler : public ControlEventHandler
-{
-    OffsetGraticuleHandler( GraticuleNode* graticule, const osg::Vec2f& offset ) :
-        _graticule( graticule ),
-        _offset(offset)
-    {
-        //nop
-    }
-
-    void onClick( Control* control, const osg::Vec2f& pos, int mouseButtonMask )
-    {
-        _graticule->setCenterOffset( _graticule->getCenterOffset() + _offset );
-    }
-
-    osg::Vec2f _offset;
-    GraticuleNode* _graticule;
-};
-
 int
 main(int argc, char** argv)
 {
@@ -93,8 +64,7 @@ main(int argc, char** argv)
     bool isUTM = arguments.read("--utm");
     bool isMGRS = arguments.read("--mgrs");
     bool isGeodetic = arguments.read("--geodetic");
-
-    bool isShader = !isUTM && !isMGRS && !isGeodetic;
+    bool isGARS = arguments.read("--gars");
 
     // load the .earth file from the command line.
     MapNode* mapNode = MapNode::load( arguments );
@@ -108,36 +78,31 @@ main(int argc, char** argv)
     osg::Group* root = new osg::Group();
     root->addChild( mapNode );
 
-    GraticuleNode* graticuleNode = 0;
-
     Formatter* formatter = 0L;
     if ( isUTM )
     {
-        UTMGraticule* gr = new UTMGraticule( mapNode );
-        root->addChild( gr );
+        UTMGraticule* gr = new UTMGraticule();
+        mapNode->getMap()->addLayer(gr);
         formatter = new MGRSFormatter();
     }
     else if ( isMGRS )
     {
-        MGRSGraticule* gr = new MGRSGraticule( mapNode );
-        root->addChild( gr );
+        MGRSGraticule* gr = new MGRSGraticule();
+        mapNode->getMap()->addLayer(gr);
         formatter = new MGRSFormatter();
     }
-    else if ( isGeodetic )
+    else if ( isGARS )
     {
-        GeodeticGraticule* gr = new GeodeticGraticule( mapNode );
-        GeodeticGraticuleOptions o = gr->getOptions();
-        o.lineStyle()->getOrCreate<LineSymbol>()->stroke()->color().set(1,0,0,1);
-        gr->setOptions( o );
-        root->addChild( gr );
+        GARSGraticule* gr = new GARSGraticule();
+        mapNode->getMap()->addLayer(gr);
         formatter = new LatLongFormatter();
     }
-    else
+    else // if ( isGeodetic )
     {
-        graticuleNode = new GraticuleNode( mapNode );
-        root->addChild( graticuleNode );
+        GeodeticGraticule* gr = new GeodeticGraticule();
+        mapNode->getMap()->addLayer(gr);
+        formatter = new LatLongFormatter();
     }
-
    
     // mouse coordinate readout:
     ControlCanvas* canvas = new ControlCanvas();
@@ -145,51 +110,9 @@ main(int argc, char** argv)
     VBox* vbox = new VBox();
     canvas->addControl( vbox );
 
-
     LabelControl* readout = new LabelControl();
     vbox->addControl( readout );
 
-    if (graticuleNode)
-    {
-        HBox* toggleBox = vbox->addControl( new HBox() );
-        toggleBox->setChildSpacing( 5 );
-        CheckBoxControl* toggleCheckBox = new CheckBoxControl( true );
-        toggleCheckBox->addEventHandler( new ToggleGraticuleHandler( graticuleNode ) );
-        toggleBox->addControl( toggleCheckBox );
-        LabelControl* labelControl = new LabelControl( "Show Graticule" );
-        labelControl->setFontSize( 24.0f );
-        toggleBox->addControl( labelControl  );
-
-        HBox* offsetBox = vbox->addControl( new HBox() );
-        offsetBox->setChildSpacing( 5 );
-        osg::Vec4 activeColor(1,.3,.3,1);
-
-        offsetBox->addControl(new LabelControl("Adjust Labels"));
-
-        double adj = 10.0;
-        LabelControl* left = new LabelControl("Left");
-        left->addEventHandler(new OffsetGraticuleHandler(graticuleNode, osg::Vec2f(-adj, 0.0)) );
-        offsetBox->addControl(left);
-        left->setActiveColor(activeColor);
-
-        LabelControl* right = new LabelControl("Right");
-        right->addEventHandler(new OffsetGraticuleHandler(graticuleNode, osg::Vec2f(adj, 0.0)) );
-        offsetBox->addControl(right);
-        right->setActiveColor(activeColor);
-
-        LabelControl* down = new LabelControl("Down");
-        down->addEventHandler(new OffsetGraticuleHandler(graticuleNode, osg::Vec2f(0.0, -adj)) );
-        offsetBox->addControl(down);
-        down->setActiveColor(activeColor);
-
-        LabelControl* up = new LabelControl("Up");
-        up->addEventHandler(new OffsetGraticuleHandler(graticuleNode, osg::Vec2f(0.0, adj)) );
-        offsetBox->addControl(up);
-        up->setActiveColor(activeColor);
-
-
-    }
-
     MouseCoordsTool* tool = new MouseCoordsTool( mapNode );
     tool->addCallback( new MouseCoordsLabelCallback(readout, formatter) );
     viewer.addEventHandler( tool );
diff --git a/src/applications/osgearth_horizon/osgearth_horizon.cpp b/src/applications/osgearth_horizon/osgearth_horizon.cpp
index 60f85bb..31702a2 100644
--- a/src/applications/osgearth_horizon/osgearth_horizon.cpp
+++ b/src/applications/osgearth_horizon/osgearth_horizon.cpp
@@ -45,7 +45,7 @@ int
 usage(const char* name)
 {
     OE_NOTICE 
-        << "\nUsage: " << name << " file.earth" << std::endl
+        << "\nUsage: " << name << " file.earth --activity" << std::endl
         << MapNodeHelper().usage() << std::endl;
 
     return 0;
@@ -111,6 +111,9 @@ main(int argc, char** argv)
     if ( arguments.read("--help") )
         return usage(argv[0]);
 
+    if (arguments.find("--activity") < 0)
+        return usage(argv[0]);
+
     // create a viewer:
     osgViewer::Viewer viewer(arguments);
 
diff --git a/src/applications/osgearth_fog/CMakeLists.txt b/src/applications/osgearth_htm/CMakeLists.txt
similarity index 72%
rename from src/applications/osgearth_fog/CMakeLists.txt
rename to src/applications/osgearth_htm/CMakeLists.txt
index 7e66e05..5b91143 100644
--- a/src/applications/osgearth_fog/CMakeLists.txt
+++ b/src/applications/osgearth_htm/CMakeLists.txt
@@ -1,7 +1,7 @@
 INCLUDE_DIRECTORIES(${OSG_INCLUDE_DIRS} )
 SET(TARGET_LIBRARIES_VARS OSG_LIBRARY OSGDB_LIBRARY OSGUTIL_LIBRARY OSGVIEWER_LIBRARY OPENTHREADS_LIBRARY)
 
-SET(TARGET_SRC osgearth_fog.cpp )
+SET(TARGET_SRC osgearth_htm.cpp )
 
 #### end var setup  ###
-SETUP_APPLICATION(osgearth_fog)
\ No newline at end of file
+SETUP_APPLICATION(osgearth_htm)
\ No newline at end of file
diff --git a/src/applications/osgearth_viewer/osgearth_viewer.cpp b/src/applications/osgearth_htm/osgearth_htm.cpp
similarity index 52%
copy from src/applications/osgearth_viewer/osgearth_viewer.cpp
copy to src/applications/osgearth_htm/osgearth_htm.cpp
index 2e4b118..2ed06c8 100644
--- a/src/applications/osgearth_viewer/osgearth_viewer.cpp
+++ b/src/applications/osgearth_htm/osgearth_htm.cpp
@@ -24,74 +24,95 @@
 #include <osgEarth/Notify>
 #include <osgEarthUtil/EarthManipulator>
 #include <osgEarthUtil/ExampleResources>
-
-#include <osgEarth/Cache>
-#include <osgEarthDrivers/cache_filesystem/FileSystemCache>
+#include <osgEarth/MapNode>
+#include <osgEarth/ThreadingUtils>
+#include <osgEarth/Metrics>
+#include <iostream>
+#include <osgEarthUtil/HTM>
+#include <osgEarthAnnotation/PlaceNode>
+#include <osgEarth/Random>
 
 #define LC "[viewer] "
 
 using namespace osgEarth;
 using namespace osgEarth::Util;
+using namespace osgEarth::Annotation;
 
 int
-usage(const char* name)
+usage(const char* name, const char* msg =NULL)
 {
     OE_NOTICE 
-        << "\nUsage: " << name << " file.earth" << std::endl
+        << (msg ? msg : "")
+        << "\nUsage: " << name << " file.earth --model <file> [--num <number>] [--debug]" << std::endl
         << MapNodeHelper().usage() << std::endl;
 
     return 0;
 }
 
+
 int
 main(int argc, char** argv)
 {
     osg::ArgumentParser arguments(&argc,argv);
 
-    // help?
     if ( arguments.read("--help") )
         return usage(argv[0]);
 
-    float vfov = -1.0f;
-    arguments.read("--vfov", vfov);
-
-    // create a viewer:
+    // Viewer setup
     osgViewer::Viewer viewer(arguments);
-
-    // Tell the database pager to not modify the unref settings
     viewer.getDatabasePager()->setUnrefImageDataAfterApplyPolicy( true, false );
-
-    // thread-safe initialization of the OSG wrapper manager. Calling this here
-    // prevents the "unsupported wrapper" messages from OSG
     osgDB::Registry::instance()->getObjectWrapperManager()->findWrapper("osg::Image");
-
-    // install our default manipulator (do this before calling load)
     viewer.setCameraManipulator( new EarthManipulator(arguments) );
-
-    // disable the small-feature culling
     viewer.getCamera()->setSmallFeatureCullingPixelSize(-1.0f);
-
-    // set a near/far ratio that is smaller than the default. This allows us to get
-    // closer to the ground without near clipping. If you need more, use --logdepth
     viewer.getCamera()->setNearFarRatio(0.0001);
 
-    if ( vfov > 0.0 )
-    {
-        double fov, ar, n, f;
-        viewer.getCamera()->getProjectionMatrixAsPerspective(fov, ar, n, f);
-        viewer.getCamera()->setProjectionMatrixAsPerspective(vfov, ar, n, f);
-    }
+    std::string modelPath;
+    if (arguments.read("--model", modelPath) == false)
+        return usage(argv[0]);
 
-    // load an earth file, and support all or our example command-line options
-    // and earth file <external> tags    
+    osg::ref_ptr<osg::Node> model = osgDB::readRefNodeFile(modelPath);
+    if (model.valid() == false)
+        return usage(argv[0], "Cannot load model file");
+
+    int numObjects = 10000;
+    arguments.read("--num", numObjects);
+
+    bool debug = arguments.read("--debug");
+
+    // Load the earth file
     osg::Node* node = MapNodeHelper().load(arguments, &viewer);
-    if ( node )
+    if (!node)
+        return usage(argv[0], "Cannot load earth file");
+
+    viewer.setSceneData( node );
+    MapNode* mapNode = MapNode::get(node);
+
+    // Randomly place object instances around the US.
+    const SpatialReference* wgs84 = SpatialReference::get("wgs84");
+    Random prng;
+    HTMGroup* htm = new HTMGroup();
+    htm->setMaximumObjectsPerCell(250);
+    htm->setMaximumCellSize(500000);
+    htm->setMinimumCellSize(25000);
+    htm->setRangeFactor(5);
+    mapNode->addChild(htm);
+        
+    for (unsigned i = 0; i < numObjects; ++i)
     {
-        viewer.setSceneData( node );
-        viewer.run();
-    }
-    else
-    {
-        return usage(argv[0]);
+        GeoTransform* xform = new GeoTransform();
+        xform->addChild(model.get());
+
+        double lon = -115 + prng.next() * 40;
+        double lat = 25 + prng.next() * 25;
+
+        xform->setPosition(GeoPoint(wgs84, lon, lat, 0, ALTMODE_ABSOLUTE));
+
+        htm->addChild(xform);
+
+        if (i%1000 == 0)
+            std::cout << "\r" << i << "/" << numObjects << std::flush;
     }
+    std::cout << std::endl;
+
+    return viewer.run();
 }
diff --git a/src/applications/osgearth_imageoverlay/osgearth_imageoverlay.cpp b/src/applications/osgearth_imageoverlay/osgearth_imageoverlay.cpp
index 8435073..9d25134 100644
--- a/src/applications/osgearth_imageoverlay/osgearth_imageoverlay.cpp
+++ b/src/applications/osgearth_imageoverlay/osgearth_imageoverlay.cpp
@@ -31,6 +31,7 @@
 #include <osgEarthUtil/Controls>
 #include <osgEarth/Utils>
 #include <osgEarth/VirtualProgram>
+#include <osgEarth/FileUtils>
 
 #include <osg/ImageStream>
 #include <osgDB/FileNameUtils>
@@ -196,8 +197,8 @@ main(int argc, char** argv)
     bool moveVert = arguments.read("--vert");
 
     // load the .earth file from the command line.
-    osg::Node* earthNode = osgDB::readNodeFiles( arguments );
-    if (!earthNode)
+    osg::ref_ptr<osg::Node> earthNode = osgDB::readNodeFiles( arguments );
+    if (!earthNode.valid())
         return usage( "Unable to load earth model." );
 
     osgViewer::Viewer viewer(arguments);
@@ -206,14 +207,14 @@ main(int argc, char** argv)
     viewer.setCameraManipulator( manip );
 
     osg::Group* root = new osg::Group();
-    root->addChild( earthNode );
+    root->addChild( earthNode.get() );
 
     //Create the control panel
     root->addChild( createControlPanel(&viewer) );
 
     viewer.setSceneData( root );
     
-    osgEarth::MapNode* mapNode = osgEarth::MapNode::findMapNode( earthNode );
+    osgEarth::MapNode* mapNode = osgEarth::MapNode::findMapNode( earthNode.get() );
     if ( mapNode )
     {
 
@@ -221,10 +222,10 @@ main(int argc, char** argv)
         {
             std::string imageFile = imageFiles[i];
             //Read the image file and play it if it's a movie
-            osg::Image* image = osgDB::readImageFile(imageFile);
-            if (image)
+            osg::ref_ptr<osg::Image> image = osgDB::readRefImageFile(imageFile);
+            if (image.valid())
             {
-                osg::ImageStream* is = dynamic_cast<osg::ImageStream*>(image);
+                osg::ImageStream* is = dynamic_cast<osg::ImageStream*>(image.get());
                 if (is)
                 {
                     is->play();
@@ -234,19 +235,19 @@ main(int argc, char** argv)
             //Create a new ImageOverlay and set it's bounds
             //ImageOverlay* overlay = new ImageOverlay(mapNode->getMap()->getProfile()->getSRS()->getEllipsoid(), image);        
             ImageOverlay* overlay = new ImageOverlay(mapNode);
-            overlay->setImage( image );
+            overlay->setImage( image.get() );
             overlay->setBounds(imageBounds[i]);
             
-            root->addChild( overlay );
+            mapNode->addChild( overlay );
 
 
             //Create a new ImageOverlayEditor and set it's node mask to 0 to hide it initially
             osg::Node* editor = new ImageOverlayEditor( overlay, moveVert);
             editor->setNodeMask( 0 );
-            root->addChild( editor );      
+            mapNode->addChild( editor );      
             
             // Add an image preview
-            ImageControl* imageCon = new ImageControl( image );
+            ImageControl* imageCon = new ImageControl( image.get() );
             imageCon->setSize( 64, 64 );
             imageCon->setVertAlign( Control::ALIGN_CENTER );
             s_layerBox->setControl( 0, i, imageCon );            
diff --git a/src/applications/osgearth_featureeditor/CMakeLists.txt b/src/applications/osgearth_infinitescroll/CMakeLists.txt
similarity index 66%
copy from src/applications/osgearth_featureeditor/CMakeLists.txt
copy to src/applications/osgearth_infinitescroll/CMakeLists.txt
index b35afd6..cdf4228 100644
--- a/src/applications/osgearth_featureeditor/CMakeLists.txt
+++ b/src/applications/osgearth_infinitescroll/CMakeLists.txt
@@ -1,8 +1,7 @@
 INCLUDE_DIRECTORIES(${OSG_INCLUDE_DIRS} )
-
 SET(TARGET_LIBRARIES_VARS OSG_LIBRARY OSGDB_LIBRARY OSGUTIL_LIBRARY OSGVIEWER_LIBRARY OPENTHREADS_LIBRARY)
 
-SET(TARGET_SRC osgearth_featureeditor.cpp )
+SET(TARGET_SRC osgearth_infinitescroll.cpp )
 
 #### end var setup  ###
-SETUP_APPLICATION(osgearth_featureeditor)
\ No newline at end of file
+SETUP_APPLICATION(osgearth_infinitescroll)
\ No newline at end of file
diff --git a/src/applications/osgearth_infinitescroll/osgearth_infinitescroll.cpp b/src/applications/osgearth_infinitescroll/osgearth_infinitescroll.cpp
new file mode 100644
index 0000000..4bce3f6
--- /dev/null
+++ b/src/applications/osgearth_infinitescroll/osgearth_infinitescroll.cpp
@@ -0,0 +1,207 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+* Copyright 2016 Pelican Mapping
+* http://osgearth.org
+*
+* osgEarth is free software; you can redistribute it and/or modify
+* it under the terms of the GNU Lesser General Public License as published by
+* the Free Software Foundation; either version 2 of the License, or
+* (at your option) any later version.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+* IN THE SOFTWARE.
+*
+* You should have received a copy of the GNU Lesser General Public License
+* along with this program.  If not, see <http://www.gnu.org/licenses/>
+*/
+
+#include <osgViewer/Viewer>
+#include <osgEarth/Notify>
+#include <osgEarthUtil/EarthManipulator>
+#include <osgEarthUtil/ExampleResources>
+#include <osgEarth/MapNode>
+#include <osgEarth/ThreadingUtils>
+#include <osgEarth/Metrics>
+#include <iostream>
+
+#define LC "[viewer] "
+
+using namespace osgEarth;
+using namespace osgEarth::Util;
+
+int
+usage(const char* name)
+{
+    OE_NOTICE
+        << "\nUsage: " << name << " file.earth" << std::endl
+        << MapNodeHelper().usage() << std::endl;
+
+    return 0;
+}
+
+
+int
+main(int argc, char** argv)
+{
+    osg::ArgumentParser arguments(&argc,argv);
+
+    // help?
+    if ( arguments.read("--help") )
+        return usage(argv[0]);
+
+    float vfov = -1.0f;
+    arguments.read("--vfov", vfov);
+
+
+
+    // create a viewer:
+    osgViewer::Viewer viewer(arguments);
+
+    // Tell the database pager to not modify the unref settings
+    viewer.getDatabasePager()->setUnrefImageDataAfterApplyPolicy( true, false );
+
+    // thread-safe initialization of the OSG wrapper manager. Calling this here
+    // prevents the "unsupported wrapper" messages from OSG
+    osgDB::Registry::instance()->getObjectWrapperManager()->findWrapper("osg::Image");
+
+    // install our default manipulator (do this before calling load)
+    osg::ref_ptr< EarthManipulator > manipulator = new EarthManipulator(arguments);
+    viewer.setCameraManipulator( manipulator );
+
+    // disable the small-feature culling
+    viewer.getCamera()->setSmallFeatureCullingPixelSize(-1.0f);
+
+    // set a near/far ratio that is smaller than the default. This allows us to get
+    // closer to the ground without near clipping. If you need more, use --logdepth
+    viewer.getCamera()->setNearFarRatio(0.0001);
+
+    if ( vfov > 0.0 )
+    {
+        double fov, ar, n, f;
+        viewer.getCamera()->getProjectionMatrixAsPerspective(fov, ar, n, f);
+        viewer.getCamera()->setProjectionMatrixAsPerspective(vfov, ar, n, f);
+    }
+
+    // load an earth file, and support all or our example command-line options
+    // and earth file <external> tags
+    osg::ref_ptr< osg::Node > node = MapNodeHelper().load(arguments, &viewer);
+
+    osg::ref_ptr< MapNode > mapNode = MapNode::findMapNode(node.get());
+
+    if ( mapNode.valid() )
+    {
+        if (mapNode->isGeocentric())
+        {
+            OE_NOTICE << "Please run this example with a projected earth file" << std::endl;
+            return 1;
+        }
+        GeoExtent mapExtent = mapNode->getMap()->getProfile()->getExtent();
+
+        //Disable the middle mouse by default, which is rotate.  This will keep us in 2D mode.
+        manipulator->getSettings()->bindMouse(osgEarth::Util::EarthManipulator::ACTION_NULL, osgGA::GUIEventAdapter::MIDDLE_MOUSE_BUTTON, 0);
+
+        // Compute a sensible max range so that the user can't zoom out too far to where we need more than 3 transforms.
+        double maxDim = osg::maximum(mapExtent.width(), mapExtent.height());
+        double range = ((0.5 * maxDim) / 0.267949849);
+        manipulator->getSettings()->setMinMaxDistance(0.0, range);
+
+        osg::Group* root = new osg::Group;
+
+        // We're going to draw the map three times so that we can provide an infinite view scrolling left to right.
+        
+        // The centerMatrix is centered around the eye point.
+        osg::MatrixTransform* centerMatrix = new osg::MatrixTransform;
+        centerMatrix->addChild( mapNode );
+        root->addChild( centerMatrix );
+
+
+        // The left matrix is to the left of the center matrix
+        osg::MatrixTransform* leftMatrix = new osg::MatrixTransform;
+        leftMatrix->addChild( mapNode );
+        root->addChild( leftMatrix );
+
+        // The right matrix is to the right of the center matrix
+        osg::MatrixTransform* rightMatrix = new osg::MatrixTransform;
+        rightMatrix->addChild( mapNode );
+        root->addChild( rightMatrix );
+
+        viewer.setSceneData( root );
+
+        while (!viewer.done())
+        {
+            // Get the current viewpoint from the EarthManipulator
+            Viewpoint vp = manipulator->getViewpoint();
+            double eyeX = vp.focalPoint()->x();
+
+            GeoPoint focalPoint = *vp.focalPoint();
+
+            // Adjust the focal point if the user is trying to too far north or south.
+            if (focalPoint.y() > mapExtent.yMax())
+            {
+                focalPoint.y() = mapExtent.yMax();
+                vp.focalPoint() = focalPoint;
+                manipulator->setViewpoint( vp );
+            }
+            else if (focalPoint.y() < mapExtent.yMin())
+            {
+                focalPoint.y() = mapExtent.yMin();
+                vp.focalPoint() = focalPoint;
+                manipulator->setViewpoint( vp );
+            }
+                                    
+            GeoExtent centerExtent =  mapExtent;
+            
+            // Figure out which direction we need to shift the map extent 
+            float direction = 0.0;
+            if (eyeX < mapExtent.xMin())
+            {
+                // Move to the left
+                direction = -1.0;
+            }
+            else if (eyeX > mapExtent.xMax())
+            {
+                // Move to the right
+                direction = 1.0;
+            }
+
+
+            // Shift the center extent so that it's centered around the eye point.
+            float offset = 0.0;
+
+            if (direction != 0.0)
+            {
+                while (true)
+                {
+                    centerExtent = GeoExtent(centerExtent.getSRS(),
+                                   mapExtent.xMin() + offset, mapExtent.yMin(), 
+                                   mapExtent.xMax() + offset, mapExtent.yMax());
+                    if (eyeX >= centerExtent.xMin() && eyeX <= centerExtent.xMax())
+                    {
+                        break;
+                    }
+
+                    offset += direction * centerExtent.width();
+                }
+            }
+
+            // Update the matrix transforms.
+            centerMatrix->setMatrix(osg::Matrixd::translate(offset, 0.0, 0.0));
+            leftMatrix->setMatrix(osg::Matrixd::translate(offset - mapExtent.width(), 0.0, 0.0));
+            rightMatrix->setMatrix(osg::Matrixd::translate(offset + mapExtent.width(), 0.0, 0.0));
+          
+            viewer.frame();
+        }
+        
+    }
+    else
+    {
+        return usage(argv[0]);
+    }
+
+    return 0;
+}
diff --git a/src/applications/osgearth_featuremanip/CMakeLists.txt b/src/applications/osgearth_lights/CMakeLists.txt
similarity index 67%
rename from src/applications/osgearth_featuremanip/CMakeLists.txt
rename to src/applications/osgearth_lights/CMakeLists.txt
index 7627241..6574558 100644
--- a/src/applications/osgearth_featuremanip/CMakeLists.txt
+++ b/src/applications/osgearth_lights/CMakeLists.txt
@@ -1,7 +1,7 @@
 INCLUDE_DIRECTORIES(${OSG_INCLUDE_DIRS} )
 SET(TARGET_LIBRARIES_VARS OSG_LIBRARY OSGDB_LIBRARY OSGUTIL_LIBRARY OSGVIEWER_LIBRARY OPENTHREADS_LIBRARY)
 
-SET(TARGET_SRC osgearth_featuremanip.cpp )
+SET(TARGET_SRC osgearth_lights.cpp )
 
 #### end var setup  ###
-SETUP_APPLICATION(osgearth_featuremanip)
\ No newline at end of file
+SETUP_APPLICATION(osgearth_lights)
\ No newline at end of file
diff --git a/src/applications/osgearth_lights/osgearth_lights.cpp b/src/applications/osgearth_lights/osgearth_lights.cpp
new file mode 100644
index 0000000..7acf6d9
--- /dev/null
+++ b/src/applications/osgearth_lights/osgearth_lights.cpp
@@ -0,0 +1,244 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+* Copyright 2015 Pelican Mapping
+* http://osgearth.org
+*
+* osgEarth is free software; you can redistribute it and/or modify
+* it under the terms of the GNU Lesser General Public License as published by
+* the Free Software Foundation; either version 2 of the License, or
+* (at your option) any later version.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+* IN THE SOFTWARE.
+*
+* You should have received a copy of the GNU Lesser General Public License
+* along with this program.  If not, see <http://www.gnu.org/licenses/>
+*/
+
+/**
+ * Lights test. This application is for testing the LightSource support in osgEarth.
+ */
+#include <osgViewer/Viewer>
+#include <osgEarth/Notify>
+#include <osgEarth/Lighting>
+#include <osgEarth/PhongLightingEffect>
+#include <osgEarth/NodeUtils>
+#include <osgEarthUtil/EarthManipulator>
+#include <osgEarthUtil/ExampleResources>
+#include <osgEarthUtil/Ephemeris>
+#include <osgEarthUtil/Shadowing>
+
+#define LC "[lights] "
+
+using namespace osgEarth;
+using namespace osgEarth::Util;
+
+int
+usage(const char* name)
+{
+    OE_NOTICE 
+        << "\nUsage: " << name << " file.earth" << std::endl
+        << MapNodeHelper().usage() << std::endl;
+
+    return 0;
+}
+
+// converts a double-precision Vec3d to an equivalent single-precision Vec4f position
+// as needed for light positions.
+osg::Vec4
+worldToVec4(const osg::Vec3d& ecef)
+{
+    osg::Vec4 result(0.0f, 0.0f, 0.0f, 1.0f);
+    osg::Vec3d d = ecef;
+    while (d.length() > 1e6)
+    {
+        d *= 0.1;
+        result.w() *= 0.1;
+    }
+    return osg::Vec4(d.x(), d.y(), d.z(), result.w());
+}
+
+osg::Vec4
+randomColor()
+{
+    float r = (float)rand() / (float)RAND_MAX;
+    float g = (float)rand() / (float)RAND_MAX;
+    float b = (float)rand() / (float)RAND_MAX;
+    return osg::Vec4(r,g,b,1.0f);
+}
+
+
+osg::Group*
+addLights(osg::View* view, osg::Node* root, int lightNum)
+{
+    MapNode* mapNode = MapNode::get(root);
+    const SpatialReference* mapsrs = mapNode->getMapSRS();
+    const SpatialReference* geosrs = mapsrs->getGeographicSRS();
+    
+    osg::Vec3d world;
+    osg::Group* lights = new osg::Group();
+
+    // Add a directional light that simulates the sun - but skip this if a sky
+    // was already added in the earth file.
+    if (lightNum == 0)
+    {
+        Ephemeris e;
+        DateTime dt(2016, 8, 10, 14.0);
+        world = e.getSunPositionECEF(dt);
+
+        osg::Light* sun = new osg::Light(lightNum++);
+        world.normalize();
+        sun->setPosition(osg::Vec4d(world, 0.0));
+
+        sun->setAmbient(osg::Vec4(0.2, 0.2, 0.2, 1.0));
+        sun->setDiffuse(osg::Vec4(1.0, 1.0, 0.9, 1.0));
+
+        osg::LightSource* sunLS = new osg::LightSource();
+        sunLS->setLight(sun);
+
+        lights->addChild( sunLS );
+
+        ShadowCaster* caster = osgEarth::findTopMostNodeOfType<ShadowCaster>(root);
+        if (caster)
+        {
+            OE_INFO << "Found a shadow caster!\n";
+            caster->setLight(sun);
+        }
+    }
+
+#if 1
+    // A red spot light. A spot light has a real position in space 
+    // and points in a specific direciton. The Cutoff and Exponent
+    // properties control the cone angle and sharpness, respectively
+    {
+        GeoPoint p(geosrs, -121, 34, 5000000., ALTMODE_ABSOLUTE);
+        p.toWorld(world);
+
+        osg::Light* spot = new osg::Light(lightNum++);    
+        spot->setPosition(worldToVec4(world));
+        spot->setAmbient(osg::Vec4(0,0.2,0,1));
+        spot->setDiffuse(osg::Vec4(1,0,0,1));
+        spot->setSpotCutoff(20.0f);
+        spot->setSpotExponent(100.0f);
+
+        // point straight down at the map:
+        world.normalize();
+        spot->setDirection(-world);
+
+        osg::LightSource* spotLS = new osg::LightSource();
+        spotLS->setLight(spot);
+
+        lights->addChild( spotLS );
+    }
+
+    // A green point light. A Point light lives at a real location in 
+    // space and lights equally in all directions.
+    {
+        GeoPoint p(geosrs, -45, -35, 1000000., ALTMODE_ABSOLUTE);
+        p.toWorld(world);
+
+        osg::Light* point = new osg::Light(lightNum++);
+        point->setPosition(worldToVec4(world));
+        point->setAmbient(osg::Vec4(0,0,0,1));
+        point->setDiffuse(osg::Vec4(1.0, 1.0, 0.0,1));
+
+        osg::LightSource* pointLS = new osg::LightSource();
+        pointLS->setLight(point);
+
+        lights->addChild( pointLS );
+    }
+#endif
+
+    // Generate the necessary uniforms for the shaders.
+    GenerateGL3LightingUniforms gen;
+    lights->accept(gen);
+
+    return lights;
+}
+
+
+
+int
+main(int argc, char** argv)
+{
+    osg::ArgumentParser arguments(&argc,argv);
+
+    // help?
+    if ( arguments.read("--help") )
+        return usage(argv[0]);
+
+    // create a viewer:
+    osgViewer::Viewer viewer(arguments);
+
+    // Whether to test updating material
+    bool update = arguments.read("--update");
+
+    // Tell the database pager to not modify the unref settings
+    viewer.getDatabasePager()->setUnrefImageDataAfterApplyPolicy( true, false );
+
+    // install our default manipulator (do this before calling load)
+    viewer.setCameraManipulator( new EarthManipulator(arguments) );
+
+    // disable the small-feature culling
+    viewer.getCamera()->setSmallFeatureCullingPixelSize(-1.0f);
+
+    viewer.setLightingMode(viewer.NO_LIGHT);
+
+    // load an earth file, and support all or our example command-line options
+    osg::ref_ptr<osg::Node> node = MapNodeHelper().load(arguments, &viewer);
+    if (node.valid())
+    {
+        MapNode* mapNode = MapNode::get(node.get());
+        if ( !mapNode )
+            return -1;
+        
+        // Example of a custom material for the terrain.
+        osg::ref_ptr< osg::Material > material = 0;
+        if (update)
+        {
+            OE_NOTICE << "Custom material" << std::endl;
+            material = new osg::Material;
+            material->setDiffuse(osg::Material::FRONT, osg::Vec4(1,1,1,1));        
+            material->setAmbient(osg::Material::FRONT, osg::Vec4(1,1,1,1));
+            // Attach our StateAttributeCallback so that uniforms are updated.
+            material->setUpdateCallback(new MaterialCallback());
+            mapNode->getOrCreateStateSet()->setAttributeAndModes(material);
+        }
+
+        // Does a Sky already exist (loaded from the earth file)?
+        SkyNode* sky = osgEarth::findTopMostNodeOfType<SkyNode>(node.get());
+        if (!sky)
+        {
+            // Add phong lighting.
+            PhongLightingEffect* phong = new PhongLightingEffect();
+            phong->attach(node->getOrCreateStateSet());
+        }
+
+        osg::Group* lights = addLights(&viewer, node.get(), sky?1:0);
+
+        mapNode->addChild(lights);
+        
+        viewer.setSceneData(node.get()); 
+        while (!viewer.done())
+        {         
+            if (viewer.getFrameStamp()->getFrameNumber() % 100 == 0)
+            {
+                if (material)
+                {
+                    material->setDiffuse(osg::Material::FRONT, randomColor());
+                }
+            }
+            viewer.frame();
+        }
+        return 0;
+    }
+    else
+    {
+        return usage(argv[0]);
+    }
+}
diff --git a/src/applications/osgearth_los/osgearth_los.cpp b/src/applications/osgearth_los/osgearth_los.cpp
index 5373f3d..87c3f80 100644
--- a/src/applications/osgearth_los/osgearth_los.cpp
+++ b/src/applications/osgearth_los/osgearth_los.cpp
@@ -34,6 +34,8 @@
 #include <osg/io_utils>
 #include <osg/MatrixTransform>
 #include <osg/Depth>
+#include <osgEarth/TerrainTileNode>
+#include <osgEarth/FileUtils>
 
 using namespace osgEarth;
 using namespace osgEarth::Util;
@@ -103,6 +105,35 @@ osg::Node* createPlane(osg::Node* node, const GeoPoint& pos, const SpatialRefere
     return positioner;
 }
 
+class CacheExtentNodeVisitor : public osg::NodeVisitor
+{
+public:
+    CacheExtentNodeVisitor(GeoExtent& extent):
+      osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN ),
+          _extent(extent)
+      {
+      }
+
+      void apply(osg::Node& node)
+      {
+          TerrainTileNode* tile = dynamic_cast<TerrainTileNode*>(&node);
+          if (tile && tile->getKey().valid())
+          {              
+              if (tile->getKey().getExtent().intersects(_extent) && tile->getKey().getLevelOfDetail() < 11)
+              {
+                  // Set this tile to not expire.
+                  tile->setMinimumExpirationTime(DBL_MAX);
+                  OE_NOTICE << "Preloading children for " << tile->getKey().str() << std::endl;
+                  tile->loadChildren();
+              }
+          }          
+          traverse(node);
+      }
+
+      GeoExtent _extent;
+};
+
+
 int
 main(int argc, char** argv)
 {
@@ -110,8 +141,8 @@ main(int argc, char** argv)
     osgViewer::Viewer viewer(arguments);
 
     // load the .earth file from the command line.
-    osg::Node* earthNode = osgDB::readNodeFiles( arguments );
-    if (!earthNode)
+    osg::ref_ptr<osg::Node> earthNode = osgDB::readNodeFiles( arguments );
+    if (!earthNode.valid())
     {
         OE_NOTICE << "Unable to load earth model" << std::endl;
         return 1;
@@ -119,7 +150,7 @@ main(int argc, char** argv)
 
     osg::Group* root = new osg::Group();
 
-    osgEarth::MapNode * mapNode = osgEarth::MapNode::findMapNode( earthNode );
+    osgEarth::MapNode * mapNode = osgEarth::MapNode::findMapNode( earthNode.get() );
     if (!mapNode)
     {
         OE_NOTICE << "Could not find MapNode " << std::endl;
@@ -184,11 +215,11 @@ main(int argc, char** argv)
     losGroup->addChild( radialRelEditor );
 
     //Load a plane model.  
-    osg::ref_ptr< osg::Node >  plane = osgDB::readNodeFile("../data/cessna.osgb.5,5,5.scale");
+    osg::ref_ptr< osg::Node >  plane = osgDB::readRefNodeFile("../data/cessna.osgb.5,5,5.scale");
 
     //Create 2 moving planes
-    osg::Node* plane1 = createPlane(plane, GeoPoint(geoSRS, -121.656, 46.0935, 4133.06, ALTMODE_ABSOLUTE), mapSRS, 5000, 20);
-    osg::Node* plane2 = createPlane(plane, GeoPoint(geoSRS, -121.321, 46.2589, 1390.09, ALTMODE_ABSOLUTE), mapSRS, 3000, 5);
+    osg::Node* plane1 = createPlane(plane.get(), GeoPoint(geoSRS, -121.656, 46.0935, 4133.06, ALTMODE_ABSOLUTE), mapSRS, 5000, 20);
+    osg::Node* plane2 = createPlane(plane.get(), GeoPoint(geoSRS, -121.321, 46.2589, 1390.09, ALTMODE_ABSOLUTE), mapSRS, 3000, 5);
     root->addChild( plane1 );
     root->addChild( plane2 );
 
@@ -199,7 +230,7 @@ main(int argc, char** argv)
     tetheredLOS->setUpdateCallback( new LineOfSightTether( plane1, plane2 ) );
 
     //Create another plane and attach a RadialLineOfSightNode to it using the RadialLineOfSightTether
-    osg::Node* plane3 = createPlane(plane, GeoPoint(geoSRS, -121.463, 46.3548, 1348.71, ALTMODE_ABSOLUTE), mapSRS, 10000, 5);
+    osg::Node* plane3 = createPlane(plane.get(), GeoPoint(geoSRS, -121.463, 46.3548, 1348.71, ALTMODE_ABSOLUTE), mapSRS, 10000, 5);
     losGroup->addChild( plane3 );
     RadialLineOfSightNode* tetheredRadial = new RadialLineOfSightNode( mapNode );
     tetheredRadial->setRadius( 5000 );
@@ -229,5 +260,21 @@ main(int argc, char** argv)
     viewer.addEventHandler(new osgViewer::LODScaleHandler());
     viewer.addEventHandler(new osgGA::StateSetManipulator(viewer.getCamera()->getOrCreateStateSet()));
 
-    return viewer.run();
+    /*
+    TerrainTileNodeVisitor v;
+    root->accept(v);
+    */
+
+    GeoPoint center(geoSRS, -121.656, 46.0935, 4133.06, ALTMODE_ABSOLUTE);
+
+    GeoExtent extent(geoSRS, center.x() - 0.5, center.y() - 0.5, center.x() + 0.5, center.y() + 0.5);
+    CacheExtentNodeVisitor v(extent);
+
+    root->accept(v);
+
+    while (!viewer.done())
+    {        
+        viewer.frame();
+    }
+    return 0;
 }
diff --git a/src/applications/osgearth_manip/osgearth_manip.cpp b/src/applications/osgearth_manip/osgearth_manip.cpp
index b1cc2b5..9e89536 100644
--- a/src/applications/osgearth_manip/osgearth_manip.cpp
+++ b/src/applications/osgearth_manip/osgearth_manip.cpp
@@ -40,6 +40,7 @@
 #include <osgEarthUtil/Controls>
 #include <osgEarthUtil/ExampleResources>
 #include <osgEarthUtil/LogarithmicDepthBuffer>
+#include <osgEarthUtil/ViewFitter>
 #include <osgEarthAnnotation/AnnotationUtils>
 #include <osgEarthAnnotation/LabelNode>
 #include <osgEarthSymbology/Style>
@@ -520,6 +521,48 @@ namespace
         osg::ref_ptr<EarthManipulator> _manip;
         double _vfov, _ar, _zn, _zf;
     };
+    
+    struct FitViewToPoints : public osgGA::GUIEventHandler
+    {
+        std::vector<GeoPoint> _points;
+        const SpatialReference* _mapSRS;
+
+        FitViewToPoints(char key, EarthManipulator* manip, const SpatialReference* mapSRS)
+            : _key(key), _manip(manip), _mapSRS(mapSRS)
+        {
+            // Set up a list of control points
+            const SpatialReference* srs = SpatialReference::get("wgs84");
+            _points.push_back(GeoPoint(srs, -120, 30, 0));
+            _points.push_back(GeoPoint(srs, -100, 45, 0));
+        }
+
+        bool handle(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa)
+        {
+            if (ea.getEventType() == ea.KEYDOWN && ea.getKey() == _key)
+            {
+                ViewFitter fitter(_mapSRS, aa.asView()->getCamera());
+                fitter.setBuffer( 100000.0 );
+                Viewpoint vp;
+                if (fitter.createViewpoint(_points, vp))
+                {
+                    _manip->setViewpoint(vp);
+                    aa.requestRedraw();
+                }
+                return true;
+            }
+            return false;
+        }
+
+        void getUsage(osg::ApplicationUsage& usage) const
+        {
+            using namespace std;
+            usage.addKeyboardMouseBinding(string(1, _key), string("FitViewToPoints"));
+        }
+
+        char _key;
+        osg::ref_ptr<EarthManipulator> _manip;
+    };
+        
 
     /**
      * A simple simulator that moves an object around the Earth. We use this to
@@ -638,23 +681,23 @@ int main(int argc, char** argv)
     osgEarth::MapNode* mapNode = osgEarth::MapNode::findMapNode( earthNode );
 
     // user model?
-    osg::Node* model = 0L;
+    osg::ref_ptr<osg::Node> model;
     std::string modelFile;
     if (arguments.read("--model", modelFile))
-        model = osgDB::readNodeFile(modelFile + ".osgearth_shadergen");
+        model = osgDB::readRefNodeFile(modelFile + ".osgearth_shadergen");
 
     osg::Group* sims = new osg::Group();
     root->addChild( sims );
 
     // Simulator for tethering:
-    Simulator* sim1 = new Simulator(sims, manip, mapNode, model, "Thing 1", '8');
+    Simulator* sim1 = new Simulator(sims, manip, mapNode, model.get(), "Thing 1", '8');
     sim1->_lat0 = 55.0;
     sim1->_lon0 = 45.0;
     sim1->_lat1 = -55.0;
     sim1->_lon1 = -45.0;
     viewer.addEventHandler(sim1);
 
-    Simulator* sim2 = new Simulator(sims, manip, mapNode, model, "Thing 2", '9');
+    Simulator* sim2 = new Simulator(sims, manip, mapNode, model.get(), "Thing 2", '9');
     sim2->_name = "Thing 2";
     sim2->_lat0 = 54.0;
     sim2->_lon0 = 45.0;
@@ -697,6 +740,8 @@ int main(int argc, char** argv)
     viewer.addEventHandler(new ToggleLDB('L'));
     viewer.addEventHandler(new ToggleSSL(sims, ')'));
 
+    viewer.addEventHandler(new FitViewToPoints('j', manip, mapNode->getMapSRS()));
+
     viewer.getCamera()->setSmallFeatureCullingPixelSize(-1.0f);
 
     while(!viewer.done())
diff --git a/src/applications/osgearth_map/osgearth_map.cpp b/src/applications/osgearth_map/osgearth_map.cpp
index 5e7a833..1b150d1 100644
--- a/src/applications/osgearth_map/osgearth_map.cpp
+++ b/src/applications/osgearth_map/osgearth_map.cpp
@@ -26,11 +26,15 @@
 #include <osgViewer/Viewer>
 #include <osgViewer/ViewerEventHandlers>
 #include <osgEarth/MapNode>
+#include <osgEarth/ImageLayer>
+#include <osgEarth/GeoTransform>
 #include <osgEarthUtil/EarthManipulator>
 #include <osgEarthUtil/AutoClipPlaneHandler>
 #include <osgEarthUtil/Controls>
 #include <osgEarthSymbology/Color>
 #include <osgEarthDrivers/tms/TMSOptions>
+#include <osgEarthDrivers/wms/WMSOptions>
+#include <osgEarthDrivers/gdal/GDALOptions>
 
 using namespace osgEarth;
 using namespace osgEarth::Drivers;
@@ -44,22 +48,49 @@ main(int argc, char** argv)
 {
     osg::ArgumentParser arguments(&argc,argv);
 
-    // create the map.
+    // create the empty map.
     Map* map = new Map();
 
-    // add a TMS imager layer:
+    // add a TMS imagery layer:
     TMSOptions imagery;
     imagery.url() = "http://readymap.org/readymap/tiles/1.0.0/7/";
-    map->addImageLayer( new ImageLayer("Imagery", imagery) );
+    map->addLayer( new ImageLayer("ReadyMap Imagery", imagery) );
 
     // add a TMS elevation layer:
     TMSOptions elevation;
-    elevation.url() = "http://readymap.org/readymap/tiles/1.0.0/9/";
-    map->addElevationLayer( new ElevationLayer("Elevation", elevation) );
+    elevation.url() = "http://readymap.org/readymap/tiles/1.0.0/116/";
+    map->addLayer( new ElevationLayer("ReadyMap Elevation", elevation) );
+    
+    // add a local GeoTIFF inset layer:
+    GDALOptions gdal;
+    gdal.url() = "../data/boston-inset.tif";
+    map->addLayer(new ImageLayer("Boston", gdal));
+
+    // add a WMS radar layer with transparency, and disable caching since
+    // this layer updates on the server periodically.
+    WMSOptions wms;
+    wms.url() = "http://mesonet.agron.iastate.edu/cgi-bin/wms/nexrad/n0r.cgi";
+    wms.format() = "png";
+    wms.layers() = "nexrad-n0r";
+    wms.srs() = "EPSG:4326";
+    wms.transparent() = true;
+    ImageLayerOptions wmsLayerOptions("WMS NEXRAD", wms);
+    wmsLayerOptions.cachePolicy() = CachePolicy::NO_CACHE;
+    map->addLayer(new ImageLayer(wmsLayerOptions));
 
     // make the map scene graph:
     MapNode* node = new MapNode( map );
 
+    // put a model on the map atop Pike's Peak, Colorado, USA
+    osg::ref_ptr<osg::Node> model = osgDB::readRefNodeFile("../data/red_flag.osg.10000.scale.osgearth_shadergen");
+    if (model.valid())
+    {
+        GeoTransform* xform = new GeoTransform();
+        xform->addChild(model.get());
+        xform->setPosition(GeoPoint(map->getSRS()->getGeographicSRS(), -105.042292, 38.840829));
+        node->addChild(xform);
+    }
+
     // initialize a viewer:
     osgViewer::Viewer viewer(arguments);
     viewer.setCameraManipulator( new EarthManipulator );
@@ -74,4 +105,4 @@ main(int argc, char** argv)
     viewer.addEventHandler(new osgViewer::HelpHandler(arguments.getApplicationUsage()));
 
     return viewer.run();
-}
+}
\ No newline at end of file
diff --git a/src/applications/osgearth_minimap/osgearth_minimap.cpp b/src/applications/osgearth_minimap/osgearth_minimap.cpp
index 40c5e7d..57d1737 100644
--- a/src/applications/osgearth_minimap/osgearth_minimap.cpp
+++ b/src/applications/osgearth_minimap/osgearth_minimap.cpp
@@ -28,6 +28,7 @@
 #include <osgEarthAnnotation/FeatureNode>
 #include <osgViewer/CompositeViewer>
 #include <osgEarthDrivers/gdal/GDALOptions>
+#include <osgEarth/ImageLayer>
 
 #define LC "[viewer] "
 
@@ -48,7 +49,7 @@ MapNode* makeMiniMapNode( ) {
 
     GDALOptions basemapOpt;
     basemapOpt.url() = "../data/world.tif";
-    map->addImageLayer( new ImageLayer( ImageLayerOptions("basemap", basemapOpt) ) );
+    map->addLayer( new ImageLayer( ImageLayerOptions("basemap", basemapOpt) ) );
 
     // That's it, the map is ready; now create a MapNode to render the Map:
     MapNodeOptions mapNodeOptions;
@@ -147,7 +148,6 @@ osgEarth::GeoExtent getExtent(osgViewer::View* view)
     double maxLat = osg::clampBelow(center.y() + radiusDegrees, 90.0);
 
     osgEarth::GeoExtent extent(srs, minLon, minLat, maxLon, maxLat);
-    extent.normalize();
 
     return extent;
 }
@@ -156,8 +156,6 @@ int
 main(int argc, char** argv)
 {
     osg::ArgumentParser arguments(&argc,argv);
-    if ( arguments.read("--stencil") )
-        osg::DisplaySettings::instance()->setMinimumNumStencilBits( 8 );
 
     //Setup a CompositeViewer
     osgViewer::CompositeViewer viewer(arguments);
diff --git a/src/applications/osgearth_mrt/osgearth_mrt.cpp b/src/applications/osgearth_mrt/osgearth_mrt.cpp
index 2b7bf92..4ecbda9 100644
--- a/src/applications/osgearth_mrt/osgearth_mrt.cpp
+++ b/src/applications/osgearth_mrt/osgearth_mrt.cpp
@@ -126,61 +126,50 @@ createFramebufferPass(App& app)
     osg::StateSet* stateset = quad->getOrCreateStateSet();
 
     static const char* vertSource =
-        "varying vec4 texcoord;\n"
+        "out vec4 texcoord;\n"
         "void effect_vert(inout vec4 vertexView)\n"
         "{\n"
         "    texcoord = gl_MultiTexCoord0; \n"
         "}\n";
 
+    // fragment shader that performs edge detection and tints edges red.
     static const char* fragSource =
         "#version " GLSL_VERSION_STR "\n"
         "#extension GL_ARB_texture_rectangle : enable\n"
         "uniform sampler2DRect gcolor;\n"
         "uniform sampler2DRect gnormal;\n"
         "uniform sampler2DRect gdepth;\n"
-        "varying vec4 texcoord;\n"
+        "uniform float osg_FrameTime;\n"
+        "in vec4 texcoord;\n"
 
         "void effect_frag(inout vec4 color)\n"
         "{\n"
-        "    color = texture2DRect(gcolor, texcoord.st); \n"
-        "    float depth = texture2DRect(gdepth, texcoord.st).r; \n"
-        "    vec3 normal = texture2DRect(gnormal,texcoord.st).xyz *2.0-1.0; \n"
+        "    color = texture(gcolor, texcoord.st); \n"
+        "    float depth = texture(gdepth, texcoord.st).r; \n"
+        "    vec3 normal = texture(gnormal,texcoord.st).xyz *2.0-1.0; \n"
 
         // sample radius in pixels:
-        "    float e = 5.0; \n"
+        "    float e = 25.0 * sin(osg_FrameTime); \n"
 
         // sample the normals around our pixel and find the approximate
         // deviation from our center normal:
         "    vec3 avgNormal =\n"
-        "       texture2DRect(gnormal, texcoord.st+vec2( e, e)).xyz + \n"
-        "       texture2DRect(gnormal, texcoord.st+vec2(-e, e)).xyz + \n"
-        "       texture2DRect(gnormal, texcoord.st+vec2(-e,-e)).xyz + \n"
-        "       texture2DRect(gnormal, texcoord.st+vec2( e,-e)).xyz + \n"
-        "       texture2DRect(gnormal, texcoord.st+vec2( 0, e)).xyz + \n"
-        "       texture2DRect(gnormal, texcoord.st+vec2( e, 0)).xyz + \n"
-        "       texture2DRect(gnormal, texcoord.st+vec2( 0,-e)).xyz + \n"
-        "       texture2DRect(gnormal, texcoord.st+vec2(-e, 0)).xyz;  \n"
+        "       texture(gnormal, texcoord.st+vec2( e, e)).xyz + \n"
+        "       texture(gnormal, texcoord.st+vec2(-e, e)).xyz + \n"
+        "       texture(gnormal, texcoord.st+vec2(-e,-e)).xyz + \n"
+        "       texture(gnormal, texcoord.st+vec2( e,-e)).xyz + \n"
+        "       texture(gnormal, texcoord.st+vec2( 0, e)).xyz + \n"
+        "       texture(gnormal, texcoord.st+vec2( e, 0)).xyz + \n"
+        "       texture(gnormal, texcoord.st+vec2( 0,-e)).xyz + \n"
+        "       texture(gnormal, texcoord.st+vec2(-e, 0)).xyz;  \n"
         "    avgNormal = normalize((avgNormal/8.0)*2.0-1.0); \n"
+
+        // average deviation from normal:
         "    float deviation = clamp(dot(normal, avgNormal),0.0,1.0); \n"
 
-        // set a blur factor based on the normal deviation, so that we
-        // blur more around edges.
+        // use that to tint the pixel red:
         "    e = 2.5 * (1.0-deviation); \n"
-
-        "    vec4 blurColor = \n"
-        "       color + \n"
-        "       texture2DRect(gcolor, texcoord.st+vec2( e, e)) + \n"
-        "       texture2DRect(gcolor, texcoord.st+vec2(-e, e)) + \n"
-        "       texture2DRect(gcolor, texcoord.st+vec2(-e,-e)) + \n"
-        "       texture2DRect(gcolor, texcoord.st+vec2( e,-e)) + \n"
-        "       texture2DRect(gcolor, texcoord.st+vec2( 0, e)) + \n"
-        "       texture2DRect(gcolor, texcoord.st+vec2( e, 0)) + \n"
-        "       texture2DRect(gcolor, texcoord.st+vec2( 0,-e)) + \n"
-        "       texture2DRect(gcolor, texcoord.st+vec2(-e, 0));  \n"
-        "    blurColor /= 9.0; \n"
-
-        // blur the color and darken the edges at the same time
-        "    color.rgb = blurColor.rgb * deviation; \n"
+        "    color.rgb = color.rgb + vec3(e,0,0);\n"
         "}\n";
 
     VirtualProgram* vp = VirtualProgram::getOrCreate(stateset);
diff --git a/src/applications/osgearth_featureeditor/CMakeLists.txt b/src/applications/osgearth_noisegen/CMakeLists.txt
similarity index 67%
rename from src/applications/osgearth_featureeditor/CMakeLists.txt
rename to src/applications/osgearth_noisegen/CMakeLists.txt
index b35afd6..324469d 100644
--- a/src/applications/osgearth_featureeditor/CMakeLists.txt
+++ b/src/applications/osgearth_noisegen/CMakeLists.txt
@@ -1,8 +1,7 @@
 INCLUDE_DIRECTORIES(${OSG_INCLUDE_DIRS} )
-
 SET(TARGET_LIBRARIES_VARS OSG_LIBRARY OSGDB_LIBRARY OSGUTIL_LIBRARY OSGVIEWER_LIBRARY OPENTHREADS_LIBRARY)
 
-SET(TARGET_SRC osgearth_featureeditor.cpp )
+SET(TARGET_SRC osgearth_noisegen.cpp )
 
 #### end var setup  ###
-SETUP_APPLICATION(osgearth_featureeditor)
\ No newline at end of file
+SETUP_APPLICATION(osgearth_noisegen)
\ No newline at end of file
diff --git a/src/applications/osgearth_map/osgearth_map.cpp b/src/applications/osgearth_noisegen/osgearth_noisegen.cpp
similarity index 57%
copy from src/applications/osgearth_map/osgearth_map.cpp
copy to src/applications/osgearth_noisegen/osgearth_noisegen.cpp
index 5e7a833..c18826c 100644
--- a/src/applications/osgearth_map/osgearth_map.cpp
+++ b/src/applications/osgearth_noisegen/osgearth_noisegen.cpp
@@ -26,16 +26,31 @@
 #include <osgViewer/Viewer>
 #include <osgViewer/ViewerEventHandlers>
 #include <osgEarth/MapNode>
-#include <osgEarthUtil/EarthManipulator>
+#include <osgEarth/ImageLayer>
+#include <osgEarth/SimplexNoise>
 #include <osgEarthUtil/AutoClipPlaneHandler>
 #include <osgEarthUtil/Controls>
 #include <osgEarthSymbology/Color>
 #include <osgEarthDrivers/tms/TMSOptions>
+#include <osgEarthDrivers/wms/WMSOptions>
+#include <osgEarthDrivers/gdal/GDALOptions>
+#include <osgDB/WriteFile>
+#include <osg/Image>
 
 using namespace osgEarth;
 using namespace osgEarth::Drivers;
 using namespace osgEarth::Util;
 
+int usage()
+{
+    OE_WARN << "\n"
+        "osgearth_noisegen --size n             ; image dimension\n"
+        "                  --out string         ; output filename\n"
+        "                  [--frequency n]      ; default = 16\n"
+        "                  [--octaves n]        ; default = 12\n"
+        ;
+    return -1;
+}
 /**
  * How to create a simple osgEarth map and display it.
  */
@@ -44,34 +59,25 @@ main(int argc, char** argv)
 {
     osg::ArgumentParser arguments(&argc,argv);
 
-    // create the map.
-    Map* map = new Map();
-
-    // add a TMS imager layer:
-    TMSOptions imagery;
-    imagery.url() = "http://readymap.org/readymap/tiles/1.0.0/7/";
-    map->addImageLayer( new ImageLayer("Imagery", imagery) );
-
-    // add a TMS elevation layer:
-    TMSOptions elevation;
-    elevation.url() = "http://readymap.org/readymap/tiles/1.0.0/9/";
-    map->addElevationLayer( new ElevationLayer("Elevation", elevation) );
+    int dim;
+    if (!arguments.read("--size", dim))
+        return usage();
 
-    // make the map scene graph:
-    MapNode* node = new MapNode( map );
+    std::string out;
+    if (!arguments.read("--out", out))
+        return usage();
 
-    // initialize a viewer:
-    osgViewer::Viewer viewer(arguments);
-    viewer.setCameraManipulator( new EarthManipulator );
-    viewer.setSceneData( node );
+    double freq = 16.0;
+    arguments.read("--frequency", freq);
 
-    // add some stock OSG handlers:
-    viewer.addEventHandler(new osgViewer::StatsHandler());
-    viewer.addEventHandler(new osgViewer::WindowSizeHandler());
-    viewer.addEventHandler(new osgViewer::ThreadingHandler());
-    viewer.addEventHandler(new osgViewer::LODScaleHandler());
-    viewer.addEventHandler(new osgGA::StateSetManipulator(viewer.getCamera()->getOrCreateStateSet()));
-    viewer.addEventHandler(new osgViewer::HelpHandler(arguments.getApplicationUsage()));
+    int octaves = 12;
+    arguments.read("--octaves", octaves);
 
-    return viewer.run();
+    SimplexNoise noise;
+    noise.setFrequency(freq);
+    noise.setOctaves(octaves);
+    noise.setNormalize(true);
+    osg::Image* image = noise.createSeamlessImage(dim);
+    osgDB::writeImageFile(*image, out);
+    return 0;
 }
diff --git a/src/applications/osgearth_occlusionculling/osgearth_occlusionculling.cpp b/src/applications/osgearth_occlusionculling/osgearth_occlusionculling.cpp
index d1622e8..5d25ba1 100644
--- a/src/applications/osgearth_occlusionculling/osgearth_occlusionculling.cpp
+++ b/src/applications/osgearth_occlusionculling/osgearth_occlusionculling.cpp
@@ -107,7 +107,7 @@ main(int argc, char** argv)
 
     //Create a bunch of placemarks around Mt Rainer so we can actually get some elevation
     {
-        osg::Image* pin = osgDB::readImageFile( "../data/placemark32.png" );
+        osg::ref_ptr<osg::Image> pin = osgDB::readRefImageFile( "../data/placemark32.png" );
 
         double centerLat =  46.840866;
         double centerLon = -121.769846;
@@ -122,7 +122,7 @@ main(int argc, char** argv)
         {
             double lat = minLat + height * (rand() * 1.0)/(RAND_MAX-1);
             double lon = minLon + width * (rand() * 1.0)/(RAND_MAX-1);        
-            PlaceNode* place = new PlaceNode(mapNode, GeoPoint(geoSRS, lon, lat), pin, "Placemark", placeStyle);
+            PlaceNode* place = new PlaceNode(mapNode, GeoPoint(geoSRS, lon, lat), pin.get(), "Placemark", placeStyle);
             //Enable occlusion culling.  This will hide placemarks that are hidden behind terrain.
             //This makes use of the OcclusionCullingCallback in CullingUtils.
             place->setOcclusionCulling( true );
diff --git a/src/applications/osgearth_package/osgearth_package.cpp b/src/applications/osgearth_package/osgearth_package.cpp
index eea47d4..cc2b786 100644
--- a/src/applications/osgearth_package/osgearth_package.cpp
+++ b/src/applications/osgearth_package/osgearth_package.cpp
@@ -29,7 +29,13 @@
 #include <osgEarth/StringUtils>
 #include <osgEarth/HTTPClient>
 #include <osgEarth/TileVisitor>
+#include <osgEarth/ImageLayer>
+#include <osgEarth/ElevationLayer>
+
+#include <osgEarthFeatures/FeatureCursor>
+
 #include <osgEarthUtil/TMSPackager>
+
 #include <osgEarthDrivers/feature_ogr/OGRFeatureOptions>
 #include <osgEarthDrivers/tms/TMSOptions>
 
@@ -306,7 +312,7 @@ makeTMS( osg::ArgumentParser& args )
 
     if (verbose)
     {
-        visitor->setProgressCallback( progress );
+        visitor->setProgressCallback( progress.get() );
     }
 
     visitor->setMinLevel( minLevel );
@@ -324,10 +330,10 @@ makeTMS( osg::ArgumentParser& args )
     // Setup a TMSPackager with all the options.
     TMSPackager packager;
     packager.setExtension(extension);
-    packager.setVisitor(visitor);
+    packager.setVisitor(visitor.get());
     packager.setDestination(rootFolder);    
     packager.setElevationPixelDepth(elevationPixelDepth);
-    packager.setWriteOptions(options);    
+    packager.setWriteOptions(options.get());    
     packager.setOverwrite(overwrite);
     packager.setKeepEmpties(keepEmpties);
     packager.setApplyAlphaMask(applyAlphaMask);
@@ -338,7 +344,7 @@ makeTMS( osg::ArgumentParser& args )
     if( !outEarth.empty() )
     {
         // copy the options from the source map first
-        outMap = new Map( map->getInitialMapOptions() );
+        outMap = new Map(); //new Map( map->getInitialMapOptions() );
     }
 
     std::string outEarthFile = osgDB::concatPaths( rootFolder, osgDB::getSimpleFileName( outEarth ) );
@@ -347,7 +353,7 @@ makeTMS( osg::ArgumentParser& args )
     // Package an individual image layer
     if (imageLayerIndex >= 0)
     {        
-        ImageLayer* layer = map->getImageLayerAt(imageLayerIndex);
+        ImageLayer* layer = map->getLayerAt<ImageLayer>(imageLayerIndex);
         if (layer)
         {
             packager.run(layer, map);
@@ -365,7 +371,7 @@ makeTMS( osg::ArgumentParser& args )
     // Package an individual elevation layer
     else if (elevationLayerIndex >= 0)
     {        
-        ElevationLayer* layer = map->getElevationLayerAt(elevationLayerIndex);
+        ElevationLayer* layer = map->getLayerAt<ElevationLayer>(elevationLayerIndex);
         if (layer)
         {
             packager.run(layer, map);
@@ -381,11 +387,14 @@ makeTMS( osg::ArgumentParser& args )
         }
     }
     else
-    {        
+    {
+        ImageLayerVector imageLayers;
+        map->getLayers(imageLayers);
+
         // Package all the ImageLayer's
-        for (unsigned int i = 0; i < map->getNumImageLayers(); i++)
+        for (unsigned int i = 0; i < imageLayers.size(); i++)
         {            
-            ImageLayer* layer = map->getImageLayerAt(i);        
+            ImageLayer* layer = imageLayers[i].get();
             OE_NOTICE << "Packaging " << layer->getName() << std::endl;
             osg::Timer_t start = osg::Timer::instance()->tick();
             packager.run(layer, map);
@@ -412,17 +421,18 @@ makeTMS( osg::ArgumentParser& args )
                     outEarthFile );
 
                 ImageLayerOptions layerOptions( packager.getLayerName(), tms );
-                layerOptions.mergeConfig( layer->getInitialOptions().getConfig( true ) );
-                layerOptions.cachePolicy() = CachePolicy::NO_CACHE;
 
-                outMap->addImageLayer( new ImageLayer( layerOptions ) );
+                outMap->addLayer( new ImageLayer( layerOptions ) );
             }
         }    
 
         // Package all the ElevationLayer's
-        for (unsigned int i = 0; i < map->getNumElevationLayers(); i++)
+        ElevationLayerVector elevationLayers;
+        map->getLayers(elevationLayers);
+
+        for (unsigned int i = 0; i < elevationLayers.size(); i++)
         {            
-            ElevationLayer* layer = map->getElevationLayerAt(i);        
+            ElevationLayer* layer = elevationLayers[i].get();
             OE_NOTICE << "Packaging " << layer->getName() << std::endl;
             osg::Timer_t start = osg::Timer::instance()->tick();
             packager.run(layer, map);
@@ -448,10 +458,8 @@ makeTMS( osg::ArgumentParser& args )
                     outEarthFile );
 
                 ElevationLayerOptions layerOptions( packager.getLayerName(), tms );
-                layerOptions.mergeConfig( layer->getInitialOptions().getConfig( true ) );
-                layerOptions.cachePolicy() = CachePolicy::NO_CACHE;
 
-                outMap->addElevationLayer( new ElevationLayer( layerOptions ) );
+                outMap->addLayer( new ElevationLayer( layerOptions ) );
             }
         }
 
diff --git a/src/applications/osgearth_package_qt/CMakeLists.txt b/src/applications/osgearth_package_qt/CMakeLists.txt
index 5b9ccb1..32c92f7 100644
--- a/src/applications/osgearth_package_qt/CMakeLists.txt
+++ b/src/applications/osgearth_package_qt/CMakeLists.txt
@@ -24,12 +24,14 @@ IF(Qt5Widgets_FOUND)
         QT5_WRAP_CPP( MOC_SRC ${MOC_HDR} OPTIONS "-f${MOC_HDR_ABS}" )
         LIST( APPEND MOC_SRCS ${MOC_SRC} )
     ENDFOREACH()
+    SET(OSGEARTH_QT_LIBRARY osgEarthQt5)
 ELSE()
     INCLUDE( ${QT_USE_FILE} )
     QT4_ADD_RESOURCES( LIB_RC_SRCS ${LIB_QT_RCS} )
     QT4_WRAP_UI( UI_HDRS ${UI_FILES} )
     QT4_WRAP_CPP( UI_SRCS ${UI_HDRS} )
     QT4_WRAP_CPP( MOC_SRCS ${MOC_HDRS} OPTIONS "-f" )
+    SET(OSGEARTH_QT_LIBRARY osgEarthQt)
 ENDIF()
 
 SET(TARGET_H
@@ -56,11 +58,15 @@ SET(TARGET_SRC
 )
 
 SET(TARGET_ADDED_LIBRARIES
-    osgEarthQt
+    ${OSGEARTH_QT_LIBRARY}
     ${QT_QTCORE_LIBRARY}
     ${QT_QTGUI_LIBRARY}
     ${QT_QTOPENGL_LIBRARY}
 )
 
 #### end var setup  ###
+IF(Qt5Widgets_FOUND)
+SETUP_APPLICATION(osgearth_package_qt5)
+ELSE(Qt5Widgets_FOUND)
 SETUP_APPLICATION(osgearth_package_qt)
+ENDIF(Qt5Widgets_FOUND)
diff --git a/src/applications/osgearth_package_qt/ExportDialog.cpp b/src/applications/osgearth_package_qt/ExportDialog.cpp
index 57a5ffe..3f98f84 100644
--- a/src/applications/osgearth_package_qt/ExportDialog.cpp
+++ b/src/applications/osgearth_package_qt/ExportDialog.cpp
@@ -137,6 +137,9 @@ void ExportDialog::updateEstimate()
         est.addExtent(GeoExtent(_mapNode->getMapSRS(), _bounds));
     }    
 
+    TerrainLayerVector terrainLayers;
+    _mapNode->getMap()->getLayers(terrainLayers);
+
     int maxLevel = 10;
     if (maxLevelEnabled())
     {
@@ -146,9 +149,9 @@ void ExportDialog::updateEstimate()
     {
         // Determine the max level from the layers
         maxLevel = 0;
-        for (unsigned int i = 0; i < _mapNode->getMap()->getNumImageLayers(); i++)
+        for (unsigned int i = 0; i < terrainLayers.size(); i++)
         {
-            osgEarth::ImageLayer* layer = _mapNode->getMap()->getImageLayerAt(i);
+            osgEarth::TerrainLayer* layer = terrainLayers[i].get();
             if (layer)
             {
                 osgEarth::TileSource* ts = layer->getTileSource();
@@ -165,24 +168,24 @@ void ExportDialog::updateEstimate()
             }
         }
 
-        for (unsigned int i = 0; i < _mapNode->getMap()->getNumElevationLayers(); i++)
-        {
-            osgEarth::ElevationLayer* layer = _mapNode->getMap()->getElevationLayerAt(i);
-            if (layer)
-            {
-                osgEarth::TileSource* ts = layer->getTileSource();
-                if (ts)
-                {
-                    for (DataExtentList::iterator itr = ts->getDataExtents().begin(); itr != ts->getDataExtents().end(); itr++)
-                    {
-                        if (itr->maxLevel().isSet() && itr->maxLevel().value() > maxLevel)
-                        {
-                            maxLevel = itr->maxLevel().value();
-                        }
-                    }
-                }
-            }
-        }
+        //for (unsigned int i = 0; i < _mapNode->getMap()->getNumElevationLayers(); i++)
+        //{
+        //    osgEarth::ElevationLayer* layer = _mapNode->getMap()->getElevationLayerAt(i);
+        //    if (layer)
+        //    {
+        //        osgEarth::TileSource* ts = layer->getTileSource();
+        //        if (ts)
+        //        {
+        //            for (DataExtentList::iterator itr = ts->getDataExtents().begin(); itr != ts->getDataExtents().end(); itr++)
+        //            {
+        //                if (itr->maxLevel().isSet() && itr->maxLevel().value() > maxLevel)
+        //                {
+        //                    maxLevel = itr->maxLevel().value();
+        //                }
+        //            }
+        //        }
+        //    }
+        //}
     }
 
     est.setMaxLevel(maxLevel);    
@@ -197,7 +200,7 @@ void ExportDialog::updateEstimate()
     }
 
     // Adjust everything by the # of layers
-    unsigned int numLayers = _mapNode->getMap()->getNumImageLayers() + _mapNode->getMap()->getNumElevationLayers();
+    unsigned int numLayers = terrainLayers.size(); //_mapNode->getMap()->getNumImageLayers() + _mapNode->getMap()->getNumElevationLayers();
     totalSeconds *= (double)numLayers;
     unsigned int numTiles = est.getNumTiles() * numLayers;
     double sizeMB = est.getSizeInMB() * (double)numLayers;
diff --git a/src/applications/osgearth_package_qt/PackageQtMainWindow b/src/applications/osgearth_package_qt/PackageQtMainWindow
index 15d5629..9bba197 100644
--- a/src/applications/osgearth_package_qt/PackageQtMainWindow
+++ b/src/applications/osgearth_package_qt/PackageQtMainWindow
@@ -141,7 +141,7 @@ private slots:
           std::string fileName = osgDB::getSimpleFileName(filePath.toStdString());
           osg::ref_ptr<osgEarth::ImageLayer> newLayer = new osgEarth::ImageLayer(osgEarth::ImageLayerOptions(fileName, layerOpt));
 
-          _manager->map()->addImageLayer(newLayer.get());
+          _manager->map()->addLayer(newLayer.get());
         }
       }
 
@@ -171,14 +171,16 @@ private slots:
             pathSet = true;
           }
 
-          osgEarth::Drivers::GDALOptions layerOpt;
-          layerOpt.url() = osgEarth::URI(filePath.toStdString());
+          osgEarth::Drivers::GDALOptions gdal;
+          gdal.url() = osgEarth::URI(filePath.toStdString());
+          std::string fileName = osgDB::getSimpleFileName(filePath.toStdString());
+
+          osgEarth::ElevationLayerOptions layerOpt(fileName, gdal);
           layerOpt.tileSize() = 15;
           
-          std::string fileName = osgDB::getSimpleFileName(filePath.toStdString());
-          osg::ref_ptr<osgEarth::ElevationLayer> newLayer = new osgEarth::ElevationLayer(osgEarth::ElevationLayerOptions(fileName, layerOpt));
+          osg::ref_ptr<osgEarth::ElevationLayer> newLayer = new osgEarth::ElevationLayer(layerOpt);
 
-          _manager->map()->addElevationLayer(newLayer.get());
+          _manager->map()->addLayer(newLayer.get());
         }
       }
 
diff --git a/src/applications/osgearth_package_qt/TMSExporter.cpp b/src/applications/osgearth_package_qt/TMSExporter.cpp
index 94bd8aa..5a7d024 100644
--- a/src/applications/osgearth_package_qt/TMSExporter.cpp
+++ b/src/applications/osgearth_package_qt/TMSExporter.cpp
@@ -30,6 +30,7 @@
 #include <osgEarth/Common>
 #include <osgEarth/Map>
 #include <osgEarth/MapNode>
+#include <osgEarth/ImageLayer>
 #include <osgEarth/Registry>
 #include <osgEarth/StringUtils>
 #include <osgEarth/FileUtils>
@@ -156,19 +157,25 @@ int TMSExporter::exportTMS(MapNode* mapNode, const std::string& earthFilePath, c
     packager.getTileVisitor()->setMaxLevel(_maxLevel);
 
     // Compute the total number of layers we are going to operate on.
-    unsigned int totalLayers = map->getNumImageLayers() + map->getNumElevationLayers();  
+    ImageLayerVector imageLayers;
+    map->getLayers(imageLayers);
+
+    ElevationLayerVector elevationLayers;
+    map->getLayers(elevationLayers);
+
+    unsigned int totalLayers = imageLayers.size() + elevationLayers.size();
 
     unsigned int layerNum = 1;
 
     // Package each image layer
-    for (unsigned int i = 0; i < map->getNumImageLayers(); i++)
+    for (unsigned int i = 0; i < imageLayers.size(); i++)
     {            
         // Don't continue if the export has been canceled
         if (_progress->isCanceled())
         {
             break;
         }
-        osg::ref_ptr< ImageLayer > layer = map->getImageLayerAt(i);      
+        osg::ref_ptr< ImageLayer > layer = imageLayers[i].get();
         std::stringstream buf;
         buf << "Packaging " << layer->getName() << " (" << layerNum << " of " << totalLayers << ")";
         OE_NOTICE << buf.str() << std::endl;
@@ -186,16 +193,16 @@ int TMSExporter::exportTMS(MapNode* mapNode, const std::string& earthFilePath, c
                 outEarthFile );
 
             ImageLayerOptions layerOptions( packager.getLayerName(), tms );
-            layerOptions.mergeConfig( layer->getInitialOptions().getConfig( true ) );
+            layerOptions.mergeConfig( layer->options().getConfig() );
             layerOptions.cachePolicy() = CachePolicy::NO_CACHE;
 
-            outMap->addImageLayer( new ImageLayer( layerOptions ) );
+            outMap->addLayer( new ImageLayer( layerOptions ) );
         }
         layerNum++;
     }
 
     // Package each elevation layer
-    for (unsigned int i = 0; i < map->getNumElevationLayers(); i++)
+    for (unsigned int i = 0; i < elevationLayers.size(); i++)
     {
         // Don't continue if the export has been canceled
         if (_progress->isCanceled())
@@ -203,7 +210,7 @@ int TMSExporter::exportTMS(MapNode* mapNode, const std::string& earthFilePath, c
             break;
         }
 
-        osg::ref_ptr< ElevationLayer > layer = map->getElevationLayerAt(i);      
+        osg::ref_ptr< ElevationLayer > layer = elevationLayers[i].get();
         std::stringstream buf;
         buf << "Packaging " << layer->getName() << " (" << layerNum << " of " << totalLayers << ")";
         OE_NOTICE << buf.str() << std::endl;
@@ -222,10 +229,10 @@ int TMSExporter::exportTMS(MapNode* mapNode, const std::string& earthFilePath, c
                 outEarthFile );
 
             ElevationLayerOptions layerOptions( packager.getLayerName(), tms );
-            layerOptions.mergeConfig( layer->getInitialOptions().getConfig( true ) );
+            layerOptions.mergeConfig( layer->options().getConfig() );
             layerOptions.cachePolicy() = CachePolicy::NO_CACHE;
 
-            outMap->addElevationLayer( new ElevationLayer( layerOptions ) );
+            outMap->addLayer( new ElevationLayer( layerOptions ) );
         }
 
         layerNum++;
diff --git a/src/applications/osgearth_pagingtest/osgearth_pagingtest.cpp b/src/applications/osgearth_pagingtest/osgearth_pagingtest.cpp
index 1e22070..327dd0c 100644
--- a/src/applications/osgearth_pagingtest/osgearth_pagingtest.cpp
+++ b/src/applications/osgearth_pagingtest/osgearth_pagingtest.cpp
@@ -123,7 +123,7 @@ public:
                   style = itr->second;
               }
 
-              FeatureNode* featureNode = new FeatureNode(_mapNode, features, style, GeometryCompilerOptions(), _styleSheet.get() );
+              FeatureNode* featureNode = new FeatureNode(_mapNode.get(), features, style, GeometryCompilerOptions(), _styleSheet.get() );
               return featureNode;
           }
           else
@@ -142,7 +142,7 @@ public:
 
       StyleSheet* getStyleSheet() const
       {
-          return _styleSheet;
+          return _styleSheet.get();
       }
 
       void setStyleSheet(StyleSheet* styleSheet)
@@ -273,7 +273,7 @@ int main(int argc, char** argv)
                 return -1;
             }
 
-            FeaturePager* featurePager = new FeaturePager(features, getStyle(randomColor(), 0.0), mapNode);
+            FeaturePager* featurePager = new FeaturePager(features.get(), getStyle(randomColor(), 0.0), mapNode);
 
             // Style 13 is where the full resolution data comes, in so use a fancy textured and extruded style
             // a style for the building data:
diff --git a/src/applications/osgearth_pick/osgearth_pick.cpp b/src/applications/osgearth_pick/osgearth_pick.cpp
index 6f75fc6..a9dcbec 100644
--- a/src/applications/osgearth_pick/osgearth_pick.cpp
+++ b/src/applications/osgearth_pick/osgearth_pick.cpp
@@ -46,102 +46,69 @@ using namespace osgEarth::Annotation;
 
 namespace ui = osgEarth::Util::Controls;
 
-static ui::LabelControl* s_fidLabel;
-static ui::LabelControl* s_nameLabel;
-static osg::Uniform*     s_highlightUniform;
-
 //-----------------------------------------------------------------------
 
-// Tests the (old) intersection-based picker.
-struct TestIsectPicker : public osgGA::GUIEventHandler
+//! Application-wide data.
+struct App
 {
-    bool handle(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa)
-    {
-        if ( ea.getEventType() == ea.RELEASE )
-        {
-            IntersectionPicker picker(dynamic_cast<osgViewer::View*>(aa.asView()));
-            IntersectionPicker::Hits hits;
-            if(picker.pick(ea.getX(), ea.getY(), hits)) {
-                std::set<ObjectID> oids;
-                if (picker.getObjectIDs(hits, oids)) {
-                    ObjectIndex* index = Registry::objectIndex();
-                    ObjectID oid = *oids.begin();
-                    osg::ref_ptr<FeatureIndex> fi = index->get<FeatureIndex>(oid);
-                    if ( fi.valid() ) {
-                        OE_NOTICE << "IsectPicker: found OID " << oid << "\n";
-                        Feature* f = fi->getFeature(oid);
-                        if ( f ) {
-                            OE_NOTICE << "...feature ID = " << f->getFID() << "\n";
-                        }
-                    }      
-                    osg::ref_ptr<Feature> f = index->get<Feature>(oid);
-                    if ( f.valid() ) {
-                        OE_NOTICE << "IsectPicker: found OID " << oid << "\n";
-                        OE_NOTICE << "...feature ID = " << f->getFID() << "\n";
-                    }
-                    osg::ref_ptr<AnnotationNode> a = index->get<AnnotationNode>(oid);
-                    if ( a ) {
-                        OE_NOTICE << "IsectPicker: found annotation " << a->getName() << "\n";
-                    }
-                }
-                else {
-                    OE_NOTICE << "IsectPicker: picked, but no OIDs\n";
-                }
-            }
-            else {
-                OE_NOTICE << "IsectPicker: no intersect\n";
-            }
-        }
-        return false;
-    }
-};
+    App(osg::ArgumentParser& args) : viewer(args), mainView(NULL), rttView(NULL), mapNode(NULL), picker(NULL) { }
 
+    osgViewer::CompositeViewer viewer;
+    osgViewer::View* mainView;
+    osgViewer::View* rttView;
+    osgEarth::MapNode* mapNode;
+    osgEarth::Util::RTTPicker* picker;
 
-//-----------------------------------------------------------------------
+    ui::LabelControl* fidLabel;
+    ui::LabelControl* nameLabel;
+    osg::Uniform*     highlightUniform;
+};
 
-/**
- * Callback that you install on the RTTPicker.
- */
+
+//! Callback that you install on the RTTPicker.
 struct MyPickCallback : public RTTPicker::Callback
 {
+    App& _app;
+    MyPickCallback(App& app) : _app(app) { }
+
     void onHit(ObjectID id)
     {
         // First see whether it's a feature:
-        FeatureIndex* index = Registry::objectIndex()->get<FeatureIndex>( id );
+        FeatureIndex* index = Registry::objectIndex()->get<FeatureIndex>(id).get();
         Feature* feature = index ? index->getFeature( id ) : 0L;
 
         if ( feature )
         {
-            s_fidLabel->setText( Stringify() << "Feature ID = " << feature->getFID() << " (oid = " << id << ")" );
-            s_nameLabel->setText( Stringify() << "Name = " << feature->getString("name") );
+            _app.fidLabel->setText( Stringify() << "Feature ID = " << feature->getFID() << " (oid = " << id << ")" );
+            _app.nameLabel->setText( Stringify() << "Name = " << feature->getString("name") );
         }
 
         else
         {
             // Check whether it's an annotation:
-            AnnotationNode* anno = Registry::objectIndex()->get<AnnotationNode>( id );
+            AnnotationNode* anno = Registry::objectIndex()->get<AnnotationNode>(id).get();
             if ( anno )
             {
-                s_fidLabel->setText( Stringify() << "ObjectID = " << id );
-                s_nameLabel->setName( Stringify() << "Name = " << anno->getName() );
+                _app.fidLabel->setText( Stringify() << "ObjectID = " << id );
+                _app.nameLabel->setName( Stringify() << "Name = " << anno->getName() );
             }
 
             // None of the above.. clear.
             else
             {
-                s_fidLabel->setText( Stringify() << "unknown oid = " << id );
-                s_nameLabel->setText( " " );
+                _app.fidLabel->setText( Stringify() << "unknown oid = " << id );
+                _app.nameLabel->setText( " " );
             }
         }
 
-        s_highlightUniform->set( id );
+        _app.highlightUniform->set( id );
     }
 
     void onMiss()
     {
-        s_fidLabel->setText( "No pick." );
-        s_nameLabel->setText( " " );
-        s_highlightUniform->set( 0u );
+        _app.fidLabel->setText( "No pick." );
+        _app.nameLabel->setText( " " );
+        _app.highlightUniform->set( 0u );
     }
 
     // pick whenever the mouse moves.
@@ -174,8 +141,11 @@ const char* highlightFrag =
     "        color.rgb = mix(color.rgb, clamp(vec3(0.5,2.0,2.0)*(1.0-color.rgb), 0.0, 1.0), 0.5); \n"
     "} \n";
 
-void installHighlighter(osg::StateSet* stateSet, int attrLocation)
+void installHighlighter(App& app)
 {
+    osg::StateSet* stateSet = app.mapNode->getOrCreateStateSet();
+    int attrLocation = Registry::objectIndex()->getObjectIDAttribLocation();
+
     // This shader program will highlight the selected object.
     VirtualProgram* vp = VirtualProgram::getOrCreate(stateSet);
     vp->setFunction( "checkForHighlight",  highlightVert, ShaderComp::LOCATION_VERTEX_CLIP );
@@ -185,8 +155,8 @@ void installHighlighter(osg::StateSet* stateSet, int attrLocation)
     Registry::objectIndex()->loadShaders( vp );
 
     // A uniform that will tell the shader which object to highlight:
-    s_highlightUniform = new osg::Uniform("objectid_to_highlight", 0u);
-    stateSet->addUniform( s_highlightUniform );
+    app.highlightUniform = new osg::Uniform("objectid_to_highlight", 0u);
+    stateSet->addUniform(app.highlightUniform );
 }
 
 //------------------------------------------------------------------------
@@ -203,18 +173,18 @@ setupRTTView(osgViewer::View* view, osg::Texture* rttTex)
     view->getCamera()->setViewMatrixAsLookAt(osg::Vec3d(0,-1,0), osg::Vec3d(0,0,0), osg::Vec3d(0,0,1));
     view->getCamera()->setProjectionResizePolicy(osg::Camera::FIXED);
 
-    osg::Vec3Array* v = new osg::Vec3Array(4);
-    (*v)[0].set(-.5,0,-.5); (*v)[1].set(.5,0,-.5); (*v)[2].set(.5,0,.5); (*v)[3].set(-.5,0,.5);
+    osg::Vec3Array* v = new osg::Vec3Array(6);
+    (*v)[0].set(-.5,0,-.5); (*v)[1].set(.5,0,-.5); (*v)[2].set(.5,0,.5); (*v)[3].set((*v)[2]); (*v)[4].set(-.5,0,.5);(*v)[5].set((*v)[0]);
 
-    osg::Vec2Array* t = new osg::Vec2Array(4);
-    (*t)[0].set(0,0); (*t)[1].set(1,0); (*t)[2].set(1,1); (*t)[3].set(0,1);
+    osg::Vec2Array* t = new osg::Vec2Array(6);
+    (*t)[0].set(0,0); (*t)[1].set(1,0); (*t)[2].set(1,1); (*t)[3].set((*t)[2]); (*t)[4].set(0,1); (*t)[5].set((*t)[0]);
 
     osg::Geometry* g = new osg::Geometry();
     g->setUseVertexBufferObjects(true);
     g->setUseDisplayList(false);
     g->setVertexArray( v );
     g->setTexCoordArray( 0, t );
-    g->addPrimitiveSet( new osg::DrawArrays(GL_QUADS, 0, 4) );
+    g->addPrimitiveSet( new osg::DrawArrays(GL_TRIANGLES, 0, 6) );
 
     osg::Geode* geode = new osg::Geode();
     geode->addDrawable( g );
@@ -230,13 +200,71 @@ setupRTTView(osgViewer::View* view, osg::Texture* rttTex)
     stateSet->setMode(GL_CULL_FACE, 0);
     stateSet->setAttributeAndModes(new osg::BlendFunc(GL_ONE, GL_ZERO), 1);
     
-    const char* fs = "void swap(inout vec4 c) { c.rgba = c==vec4(0)? vec4(1) : vec4(vec3((c.r+c.g+c.b+c.a)/4.0),1); }\n";
+    const char* fs =
+    "#version " GLSL_VERSION_STR "\n"
+    "void swap(inout vec4 c) { c.rgba = c==vec4(0)? vec4(1) : vec4(vec3((c.r+c.g+c.b+c.a)/4.0),1); }\n";
     osgEarth::Registry::shaderGenerator().run(geode);
     VirtualProgram::getOrCreate(geode->getOrCreateStateSet())->setFunction("swap", fs, ShaderComp::LOCATION_FRAGMENT_COLORING);
 
     view->setSceneData( geode );
 }
 
+void startPicker(App& app)
+{
+    // Note! Must stop and restart threading when removing the picker
+    // because it changes the OSG View/Slave configuration.
+    app.viewer.stopThreading();
+
+    app.picker = new RTTPicker();
+    app.mainView->addEventHandler(app.picker);
+
+    // add the graph that will be picked.
+    app.picker->addChild(app.mapNode);
+
+    // install a callback that controls the picker and listens for hits.
+    app.picker->setDefaultCallback(new MyPickCallback(app));
+
+    // Make a view that lets us see what the picker sees.
+    if (app.rttView == NULL)
+    {
+        app.rttView = new osgViewer::View();
+        app.rttView->getCamera()->setGraphicsContext(app.mainView->getCamera()->getGraphicsContext());
+        app.rttView->getCamera()->setSmallFeatureCullingPixelSize(-1.0f);
+        app.viewer.addView(app.rttView);    
+    }
+    setupRTTView(app.rttView, app.picker->getOrCreateTexture(app.mainView));
+    app.rttView->getCamera()->setNodeMask(~0);
+
+    app.viewer.startThreading();
+}
+
+void stopPicker(App& app)
+{
+    // Note! Must stop and restart threading when removing the picker
+    // because it changes the OSG View/Slave configuration.
+    app.viewer.stopThreading();
+
+    //app.viewer.removeView(app.rttView);
+    app.rttView->getCamera()->setNodeMask(0);
+    app.mainView->removeEventHandler(app.picker);
+    app.picker = 0L;
+
+    app.viewer.startThreading();
+}
+
+struct TogglePicker : public ui::ControlEventHandler
+{
+    App& _app;
+    TogglePicker(App& app) : _app(app) { }
+    void onClick(Control* button)
+    {
+        if (_app.picker == 0L)
+            startPicker(_app);
+        else
+            stopPicker(_app);
+    }
+};
+
 //-----------------------------------------------------------------------
 
 int
@@ -255,20 +283,16 @@ main(int argc, char** argv)
     if ( arguments.read("--help") )
         return usage(argv[0]);
 
-    // create a viewer that will hold 2 viewports.
-    osgViewer::CompositeViewer viewer(arguments);
+    App app(arguments);
 
-    osgViewer::View* mainView = new osgViewer::View();
-    mainView->setUpViewInWindow(30, 30, 1024, 1024, 0);
-    mainView->getCamera()->setSmallFeatureCullingPixelSize(-1.0f);
-
-    viewer.addView(mainView);
-
-    // Tell the database pager to not modify the unref settings
-    mainView->getDatabasePager()->setUnrefImageDataAfterApplyPolicy( false, false );
+    app.mainView = new osgViewer::View();
+    app.mainView->setUpViewInWindow(30, 30, 1024, 1024, 0);
+    app.mainView->getCamera()->setSmallFeatureCullingPixelSize(-1.0f);
+    
+    app.viewer.addView(app.mainView);
 
-    // install our default manipulator (do this before calling load)
-    mainView->setCameraManipulator( new EarthManipulator() );    
+    app.mainView->getDatabasePager()->setUnrefImageDataAfterApplyPolicy( false, false );
+    app.mainView->setCameraManipulator( new EarthManipulator() );    
 
     // Made some UI components:
     ui::VBox* uiContainer = new ui::VBox();
@@ -277,42 +301,28 @@ main(int argc, char** argv)
     uiContainer->setBackColor(0,0,0,0.8);
 
     uiContainer->addControl( new ui::LabelControl("RTT Picker Test", osg::Vec4(1,1,0,1)) );
-    s_fidLabel = new ui::LabelControl("---");
-    uiContainer->addControl( s_fidLabel );
-    s_nameLabel = uiContainer->addControl( new ui::LabelControl( "---" ) );
+    uiContainer->addControl( new ui::ButtonControl("Toggle picker", new TogglePicker(app)) );
+    app.fidLabel = new ui::LabelControl("---");
+    uiContainer->addControl( app.fidLabel );
+    app.nameLabel = uiContainer->addControl( new ui::LabelControl( "---" ) );
 
     // Load up the earth file.
-    osg::Node* node = MapNodeHelper().load( arguments, mainView, uiContainer );
+    osg::Node* node = MapNodeHelper().load( arguments, app.mainView, uiContainer );
     if ( node )
     {
-        mainView->setSceneData( node );    
-        mainView->addEventHandler( new TestIsectPicker() );
-
-        // create a picker of the specified size.
-        RTTPicker* picker = new RTTPicker();
-        mainView->addEventHandler( picker );
-
-        // add the graph that will be picked.
-        picker->addChild( MapNode::get(node) );
+        app.mainView->setSceneData( node );
 
-        // install a callback that controls the picker and listens for hits.
-        picker->setDefaultCallback( new MyPickCallback() );
+        app.mapNode = MapNode::get(node);
 
-        // Make a view that lets us see what the picker sees.
-        osgViewer::View* rttView = new osgViewer::View();
-        rttView->getCamera()->setGraphicsContext( mainView->getCamera()->getGraphicsContext() );
-        rttView->getCamera()->setSmallFeatureCullingPixelSize(-1.0f);
-        viewer.addView( rttView );
-        setupRTTView( rttView, picker->getOrCreateTexture(mainView) );
+        // start with a picker running
+        startPicker(app);
 
         // Hightlight features as we pick'em.
-        installHighlighter(
-            MapNode::get(node)->getOrCreateStateSet(),
-            Registry::objectIndex()->getObjectIDAttribLocation() );
+        installHighlighter(app);
 
-        mainView->getCamera()->setName( "Main view" );
+        app.mainView->getCamera()->setName( "Main view" );
 
-        return viewer.run();
+        return app.viewer.run();
     }
     else
     {
diff --git a/src/applications/osgearth_qt_simple/CMakeLists.txt b/src/applications/osgearth_qt_simple/CMakeLists.txt
index 17f55aa..7b92078 100644
--- a/src/applications/osgearth_qt_simple/CMakeLists.txt
+++ b/src/applications/osgearth_qt_simple/CMakeLists.txt
@@ -14,10 +14,12 @@ IF(Qt5Widgets_FOUND)
         QT5_WRAP_CPP( MOC_SRC ${MOC_HDR} OPTIONS "-f${MOC_HDR_ABS}" )
         LIST( APPEND MOC_SRCS ${MOC_SRC} )
     ENDFOREACH()
+    SET(OSGEARTH_QT_LIBRARY osgEarthQt5)
 ENDIF()
 
 IF(QT4_FOUND)
     QT4_WRAP_CPP( MOC_SRCS ${MOC_HDRS} OPTIONS "-f" )
+    SET(OSGEARTH_QT_LIBRARY osgEarthQt)
 ENDIF()
 
 SET(TARGET_H
@@ -31,11 +33,15 @@ SET(TARGET_SRC
 )
 
 SET(TARGET_ADDED_LIBRARIES
-    osgEarthQt
+    ${OSGEARTH_QT_LIBRARY}
     ${QT_QTCORE_LIBRARY}
     ${QT_QTGUI_LIBRARY}
     ${QT_QTOPENGL_LIBRARY}
 )
 
 #### end var setup  ###
+IF(Qt5Widgets_FOUND)
+SETUP_APPLICATION(osgearth_qt5_simple)
+ELSE(Qt5Widgets_FOUND)
 SETUP_APPLICATION(osgearth_qt_simple)
+ENDIF(Qt5Widgets_FOUND)
diff --git a/src/applications/osgearth_qt_windows/CMakeLists.txt b/src/applications/osgearth_qt_windows/CMakeLists.txt
index 597f6e6..4409994 100644
--- a/src/applications/osgearth_qt_windows/CMakeLists.txt
+++ b/src/applications/osgearth_qt_windows/CMakeLists.txt
@@ -10,9 +10,11 @@ IF(Qt5Widgets_FOUND)
         QT5_WRAP_CPP( MOC_SRC ${MOC_HDR} OPTIONS "-f${MOC_HDR_ABS}" )
         LIST( APPEND MOC_SRCS ${MOC_SRC} )
     ENDFOREACH()
+    SET(OSGEARTH_QT_LIBRARY osgEarthQt5)
 ELSE()
     INCLUDE( ${QT_USE_FILE} )
     QT4_WRAP_CPP( MOC_SRCS ${MOC_HDRS} OPTIONS "-f" )
+    SET(OSGEARTH_QT_LIBRARY osgEarthQt)
 ENDIF()
 
 SET(TARGET_H
@@ -26,11 +28,15 @@ SET(TARGET_SRC
 )
 
 SET(TARGET_ADDED_LIBRARIES
-    osgEarthQt
+    ${OSGEARTH_QT_LIBRARY}
     ${QT_QTCORE_LIBRARY}
     ${QT_QTGUI_LIBRARY}
     ${QT_QTOPENGL_LIBRARY}
 )
 
 #### end var setup  ###
+IF(Qt5Widgets_FOUND)
+SETUP_APPLICATION(osgearth_qt5_windows)
+ELSE(Qt5Widgets_FOUND)
 SETUP_APPLICATION(osgearth_qt_windows)
+ENDIF(Qt5Widgets_FOUND)
diff --git a/src/applications/osgearth_qt_windows/osgearth_qt_windows.cpp b/src/applications/osgearth_qt_windows/osgearth_qt_windows.cpp
index d4f7a6a..a7c03c3 100644
--- a/src/applications/osgearth_qt_windows/osgearth_qt_windows.cpp
+++ b/src/applications/osgearth_qt_windows/osgearth_qt_windows.cpp
@@ -31,6 +31,7 @@
 #include <osgEarthUtil/EarthManipulator>
 #include <osgEarthQt/ViewWidget>
 #include <osgEarth/Random>
+#include <osgEarth/FileUtils>
 #include <QApplication>
 #include <QDialog>
 #include <QMainWindow>
@@ -147,8 +148,8 @@ main(int argc, char** argv)
         return usage("Help", args);
 
     // load something
-    osg::Node* node = osgDB::readNodeFiles( args );
-    if ( !node )
+    osg::ref_ptr<osg::Node> node = osgDB::readNodeFiles( args );
+    if (!node.valid())
         return usage("Can't load a scene!", args);
 
 
@@ -161,7 +162,7 @@ main(int argc, char** argv)
     QApplication q(argc, argv);
 
     // fire up our controller:
-    MyMainWindow win( args, node );
+    MyMainWindow win( args, node.get() );
 
     // A button for adding new views:
     QPushButton* addButton = new AddButton( &win, "Add a view" );
diff --git a/src/applications/osgearth_seed/osgearth_seed.cpp b/src/applications/osgearth_seed/osgearth_seed.cpp
index 788ed56..314cd15 100644
--- a/src/applications/osgearth_seed/osgearth_seed.cpp
+++ b/src/applications/osgearth_seed/osgearth_seed.cpp
@@ -31,10 +31,15 @@
 #include <osgEarth/CacheSeed>
 #include <osgEarth/MapNode>
 #include <osgEarth/Registry>
-#include <osgEarthDrivers/feature_ogr/OGRFeatureOptions>
 #include <osgEarth/FileUtils>
-
+#include <osgEarth/ImageLayer>
+#include <osgEarth/ElevationLayer>
 #include <osgEarth/TileVisitor>
+#include <osgEarth/FileUtils>
+
+#include <osgEarthFeatures/FeatureCursor>
+
+#include <osgEarthDrivers/feature_ogr/OGRFeatureOptions>
 
 #include <iostream>
 #include <sstream>
@@ -108,7 +113,7 @@ int message( const std::string& msg )
 }
 
 int
-    seed( osg::ArgumentParser& args )
+seed( osg::ArgumentParser& args )
 {    
     osgDB::Registry::instance()->getReaderWriterForExtension("png");
     osgDB::Registry::instance()->getReaderWriterForExtension("jpg");
@@ -289,7 +294,7 @@ int
     
     if (verbose)
     {
-        visitor->setProgressCallback( progress );
+        visitor->setProgressCallback( progress.get() );
     }
 
     if ( minLevel >= 0 )
@@ -315,12 +320,12 @@ int
     // They want to seed an image layer
     if (imageLayerIndex >= 0)
     {
-        osg::ref_ptr< ImageLayer > layer = map->getImageLayerAt( imageLayerIndex );
+        osg::ref_ptr< ImageLayer > layer = map->getLayerAt<ImageLayer>( imageLayerIndex );
         if (layer)
         {
             OE_NOTICE << "Seeding single layer " << layer->getName() << std::endl;
             osg::Timer_t start = osg::Timer::instance()->tick();        
-            seeder.run(layer, map);
+            seeder.run(layer.get(), map);
             osg::Timer_t end = osg::Timer::instance()->tick();
             if (verbose)
             {
@@ -337,12 +342,12 @@ int
     // They want to seed an elevation layer
     else if (elevationLayerIndex >= 0)
     {
-        osg::ref_ptr< ElevationLayer > layer = map->getElevationLayerAt( elevationLayerIndex );
+        osg::ref_ptr< ElevationLayer > layer = map->getLayerAt<ElevationLayer>( elevationLayerIndex );
         if (layer)
         {
             OE_NOTICE << "Seeding single layer " << layer->getName() << std::endl;
             osg::Timer_t start = osg::Timer::instance()->tick();        
-            seeder.run(layer, map);
+            seeder.run(layer.get(), map);
             osg::Timer_t end = osg::Timer::instance()->tick();
             if (verbose)
             {
@@ -357,11 +362,14 @@ int
     }
     // They want to seed the entire map
     else
-    {                
+    {
+        TerrainLayerVector terrainLayers;
+        map->getLayers(terrainLayers);
+
         // Seed all the map layers
-        for (unsigned int i = 0; i < map->getNumImageLayers(); ++i)
+        for (unsigned int i = 0; i < terrainLayers.size(); ++i)
         {            
-            osg::ref_ptr< ImageLayer > layer = map->getImageLayerAt(i);
+            osg::ref_ptr< TerrainLayer > layer = terrainLayers[i].get();
             OE_NOTICE << "Seeding layer" << layer->getName() << std::endl;            
             osg::Timer_t start = osg::Timer::instance()->tick();
             seeder.run(layer.get(), map);            
@@ -372,18 +380,18 @@ int
             }                
         }
 
-        for (unsigned int i = 0; i < map->getNumElevationLayers(); ++i)
-        {
-            osg::ref_ptr< ElevationLayer > layer = map->getElevationLayerAt(i);
-            OE_NOTICE << "Seeding layer" << layer->getName() << std::endl;
-            osg::Timer_t start = osg::Timer::instance()->tick();
-            seeder.run(layer.get(), map);            
-            osg::Timer_t end = osg::Timer::instance()->tick();
-            if (verbose)
-            {
-                OE_NOTICE << "Completed seeding layer " << layer->getName() << " in " << prettyPrintTime( osg::Timer::instance()->delta_s( start, end ) ) << std::endl;
-            }                
-        }        
+        //for (unsigned int i = 0; i < map->getNumElevationLayers(); ++i)
+        //{
+        //    osg::ref_ptr< ElevationLayer > layer = map->getElevationLayerAt(i);
+        //    OE_NOTICE << "Seeding layer" << layer->getName() << std::endl;
+        //    osg::Timer_t start = osg::Timer::instance()->tick();
+        //    seeder.run(layer.get(), map);            
+        //    osg::Timer_t end = osg::Timer::instance()->tick();
+        //    if (verbose)
+        //    {
+        //        OE_NOTICE << "Completed seeding layer " << layer->getName() << " in " << prettyPrintTime( osg::Timer::instance()->delta_s( start, end ) ) << std::endl;
+        //    }                
+        //}        
     }    
 
     return 0;
@@ -412,8 +420,9 @@ int list( osg::ArgumentParser& args )
     MapFrame mapf( mapNode->getMap() );
 
     TerrainLayerVector layers;
-    std::copy( mapf.imageLayers().begin(), mapf.imageLayers().end(), std::back_inserter(layers) );
-    std::copy( mapf.elevationLayers().begin(), mapf.elevationLayers().end(), std::back_inserter(layers) );
+    mapf.getLayers(layers);
+    //std::copy( mapf.imageLayers().begin(), mapf.imageLayers().end(), std::back_inserter(layers) );
+    //std::copy( mapf.elevationLayers().begin(), mapf.elevationLayers().end(), std::back_inserter(layers) );
 
     for( TerrainLayerVector::iterator i =layers.begin(); i != layers.end(); ++i )
     {
@@ -471,7 +480,7 @@ purge( osg::ArgumentParser& args )
 
 
     ImageLayerVector imageLayers;
-    map->getImageLayers( imageLayers );
+    map->getLayers( imageLayers );
     for( ImageLayerVector::const_iterator i = imageLayers.begin(); i != imageLayers.end(); ++i )
     {
         ImageLayer* layer = i->get();
@@ -498,7 +507,7 @@ purge( osg::ArgumentParser& args )
     }
 
     ElevationLayerVector elevationLayers;
-    map->getElevationLayers( elevationLayers );
+    map->getLayers( elevationLayers );
     for( ElevationLayerVector::const_iterator i = elevationLayers.begin(); i != elevationLayers.end(); ++i )
     {
         ElevationLayer* layer = i->get();
diff --git a/src/applications/osgearth_sequencecontrol/osgearth_sequencecontrol.cpp b/src/applications/osgearth_sequencecontrol/osgearth_sequencecontrol.cpp
index fde3348..f44642f 100644
--- a/src/applications/osgearth_sequencecontrol/osgearth_sequencecontrol.cpp
+++ b/src/applications/osgearth_sequencecontrol/osgearth_sequencecontrol.cpp
@@ -23,6 +23,7 @@
 #include <osgViewer/Viewer>
 #include <osgEarth/MapNode>
 #include <osgEarth/TimeControl>
+#include <osgEarth/ImageLayer>
 #include <osgEarthUtil/ExampleResources>
 #include <osgEarthUtil/EarthManipulator>
 
@@ -78,7 +79,7 @@ main(int argc, char** argv)
     // find the first layer with sequence control.
     bool found = false;
     osgEarth::ImageLayerVector layers;
-    mapNode->getMap()->getImageLayers( layers );
+    mapNode->getMap()->getLayers( layers );
     for( osgEarth::ImageLayerVector::const_iterator i = layers.begin(); i != layers.end(); ++i )
     {
         // get the sequence control interface for the layer, if there is one.
diff --git a/src/applications/osgearth_shadercomp/osgearth_shadercomp.cpp b/src/applications/osgearth_shadercomp/osgearth_shadercomp.cpp
index d744fb0..b29b49a 100644
--- a/src/applications/osgearth_shadercomp/osgearth_shadercomp.cpp
+++ b/src/applications/osgearth_shadercomp/osgearth_shadercomp.cpp
@@ -40,6 +40,7 @@
 #include <osgEarth/Registry>
 #include <osgEarth/Capabilities>
 #include <osgEarth/ShaderUtils>
+#include <osgEarth/FileUtils>
 #include <osgEarthUtil/Controls>
 
 using namespace osgEarth;
@@ -94,7 +95,7 @@ namespace TEST_1
 {
     char s_hazeVertShader[] =
         "#version " GLSL_VERSION_STR "\n"
-        "varying vec3 v_pos; \n"
+        "out vec3 v_pos; \n"
         "void setup_haze(inout vec4 VertexVIEW) \n"
         "{ \n"
         "    v_pos = vec3(VertexVIEW); \n"
@@ -102,7 +103,7 @@ namespace TEST_1
 
     char s_hazeFragShader[] =
         "#version " GLSL_VERSION_STR "\n"
-        "varying vec3 v_pos; \n"
+        "in vec3 v_pos; \n"
         "void apply_haze(inout vec4 color) \n"
         "{ \n"
         "    float dist = clamp( length(v_pos)/1e7, 0.0, 0.75 ); \n"
@@ -457,8 +458,8 @@ namespace TEST_8
         OE_NOTICE << "Wrote to out.osgt" << std::endl;
 
         node = 0L;
-        node = osgDB::readNodeFile("out.osgt");
-        if (!node) {
+        node = osgDB::readRefNodeFile("out.osgt");
+        if (!node.valid()) {
             OE_WARN << "Readback failed!!" << std::endl;
             exit(0);
         }
@@ -621,35 +622,35 @@ int main(int argc, char** argv)
 
     if ( test1 || test2 || test3 || test4 || test6 )
     {
-        osg::Node* earthNode = osgDB::readNodeFile( "gdal_tiff.earth" );
-        if (!earthNode)
+        osg::ref_ptr<osg::Node> earthNode = osgDB::readRefNodeFile( "simple.earth" );
+        if (!earthNode.valid())
         {
             return usage( "Unable to load earth model." );
         }
 
         if ( test1 )
         {
-            root->addChild( TEST_1::run(earthNode) );
+            root->addChild( TEST_1::run(earthNode.get()) );
             if (ui) label->setText( "Function injection test: the map appears hazy at high altitude." );
         }
         else if ( test2 )
         {
-            root->addChild( TEST_2::run(earthNode) );
+            root->addChild( TEST_2::run(earthNode.get()) );
             if (ui) label->setText( "Accept callback test: the map turns red when viewport width > 1000" );
         }
         else if ( test3 )
         {
-            root->addChild( TEST_3::run(earthNode) );
+            root->addChild( TEST_3::run(earthNode.get()) );
             if (ui) label->setText( "Shader LOD test: the map turns red between 500K and 1M meters altitude" );
         }
         else if ( test4 )
         {
-            root->addChild( TEST_4::run(earthNode) );
+            root->addChild( TEST_4::run(earthNode.get()) );
             if (ui) label->setText("Memory management test; monitor memory for stability");
         }
         else if ( test6 )
         {
-            root->addChild( TEST_6::run(earthNode) );
+            root->addChild( TEST_6::run(earthNode.get()) );
             if (ui) label->setText("State Memory Stack test; top row, both=blue. bottom left=red, bottom right=normal.");
         }
         
@@ -673,13 +674,13 @@ int main(int argc, char** argv)
     }
     else if (test9)
     {
-        osg::Node* earthNode = osgDB::readNodeFile( "readymap.earth" );
-        if (!earthNode)
+        osg::ref_ptr<osg::Node> earthNode = osgDB::readRefNodeFile( "readymap.earth" );
+        if (!earthNode.valid())
         {
             return usage( "Unable to load earth model." );
         }
         
-        root->addChild(TEST_9::run(earthNode));
+        root->addChild(TEST_9::run(earthNode.get()));
         if (ui) label->setText("DP Shader Test - see code comments");
         viewer.setCameraManipulator( new osgEarth::Util::EarthManipulator() );
     }
diff --git a/src/applications/osgearth_shadergen/osgearth_shadergen.cpp b/src/applications/osgearth_shadergen/osgearth_shadergen.cpp
index 9960fce..444c3f1 100644
--- a/src/applications/osgearth_shadergen/osgearth_shadergen.cpp
+++ b/src/applications/osgearth_shadergen/osgearth_shadergen.cpp
@@ -58,10 +58,10 @@ main(int argc, char** argv)
     cache->setMaxSize(INT_MAX);
 
     // load the file
-    osg::Node* node = osgDB::readNodeFile(
+    osg::ref_ptr<osg::Node> node = osgDB::readRefNodeFile(
         Stringify() << argv[1] << ".osgearth_shadergen" );
 
-    if ( !node )
+    if (!node.valid())
         return usage(argv[0]);
 
     if ( cache )
@@ -75,7 +75,7 @@ main(int argc, char** argv)
         osgUtil::Optimizer::VERTEX_POSTTRANSFORM );
 #endif
 
-    viewer.setSceneData( node );
+    viewer.setSceneData( node.get() );
     viewer.addEventHandler(new osgViewer::StatsHandler());
     viewer.addEventHandler(new osgViewer::WindowSizeHandler());
     viewer.addEventHandler(new osgViewer::ThreadingHandler());
diff --git a/src/applications/osgearth_sharedlayer/osgearth_sharedlayer.cpp b/src/applications/osgearth_sharedlayer/osgearth_sharedlayer.cpp
index 02a6dce..7e2b5af 100644
--- a/src/applications/osgearth_sharedlayer/osgearth_sharedlayer.cpp
+++ b/src/applications/osgearth_sharedlayer/osgearth_sharedlayer.cpp
@@ -86,7 +86,7 @@ public:
         // Make a vertex shader that will access the texture coordinates
         // for our shared layer.
         std::string vs = Stringify()
-            << "varying vec4 mask_layer_texc; \n"
+            << "out vec4 mask_layer_texc; \n"
             << "void my_filter_vertex(inout vec4 VertexMODEL) \n"
             << "{ \n"
             << "    mask_layer_texc = gl_MultiTexCoord" << unit << "; \n"
@@ -97,10 +97,10 @@ public:
         // the shared "mask" layer exceed a certain alpha value.
         std::string fs =
             "uniform sampler2D mask_layer_tex; \n"
-            "varying vec4 mask_layer_texc; \n"
+            "in vec4 mask_layer_texc; \n"
             "void my_color_filter(inout vec4 color) \n"
             "{ \n"
-            "    vec4 mask_texel = texture2D(mask_layer_tex, mask_layer_texc.st); \n"
+            "    vec4 mask_texel = texture(mask_layer_tex, mask_layer_texc.st); \n"
             "    if ( mask_texel.a >= 0.5 ) \n"
             "    { \n"
             "        color.r = 1.0; \n"
@@ -155,8 +155,8 @@ int main(int argc, char** argv)
 
     // create a new map and add our two layers.
     MapNode* mapnode = new MapNode();
-    mapnode->getMap()->addImageLayer( imagery );
-    mapnode->getMap()->addImageLayer( sharedLayer );
+    mapnode->getMap()->addLayer( imagery );
+    mapnode->getMap()->addLayer( sharedLayer );
 
     // make a custom color-filter shader that will modulate the imagery
     // using the texture from the shared layer. (Using a ColorFilter 
diff --git a/src/applications/osgearth_silverlining/osgearth_silverlining.cpp b/src/applications/osgearth_silverlining/osgearth_silverlining.cpp
index f808e00..0696029 100644
--- a/src/applications/osgearth_silverlining/osgearth_silverlining.cpp
+++ b/src/applications/osgearth_silverlining/osgearth_silverlining.cpp
@@ -22,6 +22,7 @@
 #include <osgEarthUtil/EarthManipulator>
 #include <osgEarthUtil/Controls>
 #include <osgEarthSilverLining/SilverLiningNode>
+#include <osgEarth/NodeUtils>
 
 #define LC "[osgearth_silverlining] "
 
diff --git a/src/applications/osgearth_splat/osgearth_splat.cpp b/src/applications/osgearth_splat/osgearth_splat.cpp
index e429cde..beb50e1 100644
--- a/src/applications/osgearth_splat/osgearth_splat.cpp
+++ b/src/applications/osgearth_splat/osgearth_splat.cpp
@@ -21,115 +21,194 @@
 */
 
 #include <osgViewer/Viewer>
-#include <osgEarth/Notify>
-#include <osgEarth/VirtualProgram>
-#include <osgEarthUtil/EarthManipulator>
+
+#include <osgEarth/MapNode>
+#include <osgEarth/Registry>
+#include <osgEarth/LandCoverLayer>
+
+#include <osgEarthSplat/SplatLayer>
+#include <osgEarthSplat/GroundCoverLayer>
+
+#include <osgEarthDrivers/gdal/GDALOptions>
+
 #include <osgEarthUtil/ExampleResources>
+#include <osgEarthUtil/EarthManipulator>
 
-#include <osgEarthSplat/LandCoverTerrainEffect>
-#include <osgEarthSplat/SplatExtension>
+#include <osgEarthSymbology/BillboardSymbol>
 
-#define LC "[viewer] "
+
+#define LC "[splat] "
 
 using namespace osgEarth;
 using namespace osgEarth::Util;
 using namespace osgEarth::Splat;
+using namespace osgEarth::Drivers;
+using namespace osgEarth::Symbology;
 
 int
-usage(const char* name)
-{
-    OE_NOTICE 
-        << "\nUsage: " << name << " file.earth" << std::endl
-        << MapNodeHelper().usage() << std::endl;
-
-    return 0;
+failed(const std::string& s) {
+    OE_WARN << "FAILED: " << s << "\n";
+    return -1;
 }
 
-const char* color_landcover =
-    "#version 120\n"
-    "void color_landcover(inout vec4 color) \n"
-    "{ \n"
-    "    color = vec4(1,0,0,1);\n"
-    "} \n";
-
 int
 main(int argc, char** argv)
 {
     osg::ArgumentParser arguments(&argc,argv);
+    bool fromXML = arguments.find("--xml") >= 0;
 
-    // help?
-    if ( arguments.read("--help") )
-        return usage(argv[0]);
-
-    // create a viewer:
-    osgViewer::Viewer viewer(arguments);
-
-    // Tell the database pager to not modify the unref settings
-    viewer.getDatabasePager()->setUnrefImageDataAfterApplyPolicy( false, false );
-
-    // install our default manipulator (do this before calling load)
-    viewer.setCameraManipulator( new EarthManipulator(arguments) );
-
-    // disable the small-feature culling
-    viewer.getCamera()->setSmallFeatureCullingPixelSize(-1.0f);
-
-    // set a near/far ratio that is smaller than the default. This allows us to get
-    // closer to the ground without near clipping. If you need more, use --logdepth
-    viewer.getCamera()->setNearFarRatio(0.0001);
+    // Create a land cover dictionary.
+    LandCoverDictionary* dictionary;
 
-    // load an earth file, and support all or our example command-line options
-    // and earth file <external> tags    
-    osg::Node* node = MapNodeHelper().load( arguments, &viewer );
-    if ( node )
+    if (fromXML)
     {
+        LandCoverDictionaryOptions options;
+        if (options.loadFromXML("../data/land_cover_dictionary.xml") == false)
+            return failed("Cannot find XML land cover dictionary");
+        
+        dictionary = new LandCoverDictionary(options);
+    }
+    else
+    {
+        dictionary = new LandCoverDictionary();
+        dictionary->setName("Land Cover Dictionary");
+        dictionary->addClass("forest");
+        dictionary->addClass("cropland");
+        dictionary->addClass("grassland");
+        dictionary->addClass("savanna");
+        dictionary->addClass("swamp");
+        dictionary->addClass("desert");
+        dictionary->addClass("rock");
+        dictionary->addClass("water");    
+        dictionary->addClass("tundra");
+        dictionary->addClass("urban");
+    }
 
-        // Get the MapNode
-        MapNode* mapNode = MapNode::findMapNode( node );
-
-        // Find the Splat Extension
-        SplatExtension* splatExtension = mapNode->getExtension<SplatExtension>();
-        if (splatExtension)
-        {
-            OE_NOTICE << "Found Splat Extension" << std::endl;
-        }
-
-        LandCoverTerrainEffect* landCoverEffect = mapNode->getTerrainEngine()->getEffect<LandCoverTerrainEffect>();
-        if (landCoverEffect)
-        {
-            OE_NOTICE << "Found landcover terrain effect" << std::endl;
-
-            for (Zones::const_iterator zoneItr = landCoverEffect->getZones().begin();
-                zoneItr != landCoverEffect->getZones().end();
-                ++zoneItr)
-            {
-                // Get the StateSet for each of the LandCoverLayers
-                for (LandCoverLayers::iterator landCoverItr = zoneItr->get()->getLandCover()->getLayers().begin();
-                    landCoverItr != zoneItr->get()->getLandCover()->getLayers().end();
-                    ++landCoverItr)
-                {
-                    // Get the stateset for the layer.
-                    osg::StateSet* stateset = landCoverItr->get()->getOrCreateStateSet();
-
-                    // Get the VirtualProgram for this layer.
-                    VirtualProgram* vp = VirtualProgram::getOrCreate(stateset);
-
-                    // Make the "tree" layer all red.
-                    if (landCoverItr->get()->getName() == "trees")
-                    {                                         
-                        vp->setFunction( "color_landcover", color_landcover, ShaderComp::LOCATION_FRAGMENT_LIGHTING);
-                    }
-                }
-            }
-        }
-
-        viewer.setSceneData( node );
-        while(!viewer.done())
-        {
-            viewer.frame();
-        }
+    // Create the data source for our land cover data and
+    // map each value to a class in the dictionary.
+    // This example uses the ESA GLOBCOVER data set from
+    // http://due.esrin.esa.int/page_globcover.php
+    GDALOptions coverageDriver;
+    coverageDriver.url() = "H:/data/esa/GLOBCOVER_L4_200901_200912_V2.3_Ant_tiled.tif"; 
+    coverageDriver.profile() = ProfileOptions("global-geodetic");
+
+    LandCoverCoverageLayerOptions coverage;
+    coverage.driver() = coverageDriver;
+    coverage.warp() = 0.035;
+    if (fromXML)
+    {
+        if (coverage.loadMappingsFromXML("../data/land_cover_ESA_GLOBCOVER.xml") == false)
+            return failed("Cannot find coverage mappings XML\n");
     }
     else
     {
-        return usage(argv[0]);
+        coverage.map(11, "cropland");
+        coverage.map(14, "cropland");
+        coverage.map(20, "cropland");
+        coverage.map(30, "cropland");
+        coverage.map(40, "forest");
+        coverage.map(50, "forest");
+        coverage.map(60, "forest");
+        coverage.map(70, "forest");
+        coverage.map(80, "forest");
+        coverage.map(90, "forest");
+        coverage.map(100, "forest");
+        coverage.map(110, "grassland");
+        coverage.map(120, "grassland");
+        coverage.map(130, "savanna");
+        coverage.map(140, "savanna");
+        coverage.map(150, "savanna");
+        coverage.map(160, "swamp");
+        coverage.map(170, "swamp");
+        coverage.map(180, "swamp");
+        coverage.map(190, "urban");
+        coverage.map(200, "desert");
+        coverage.map(210, "water");
+        coverage.map(220, "tundra");
+        coverage.map(230, "water");
     }
+
+    // Create the land cover layer for the map:
+    LandCoverLayer* landCover = new LandCoverLayer();
+    landCover->setName("LandCover");
+    landCover->options().cachePolicy() = CachePolicy::NO_CACHE;
+    landCover->options().coverages().push_back(coverage);
+    landCover->options().maxDataLevel() = 15u;
+
+    // Next, load the definitions that map land cover classes to actual textures.
+    Surface* surface = new Surface();
+    SplatCatalog* catalog = SplatCatalog::read("../data/splat/splat_catalog.xml");
+    if (catalog == 0L)
+        return failed("Reading splat catalog");
+    surface->setCatalog(catalog);
+
+    // The zone designates the geographic area over which to apply the surface.
+    // At least one zone is required and by default it covers the entire map.
+    Zone* splatZone = new Zone();
+    splatZone->setSurface(surface);
+    
+    // Create an imagery splatting layer that uses the configured land cover.
+    SplatLayer* splatLayer = new SplatLayer();
+    splatLayer->setName("Splat imagery");
+    splatLayer->options().cachePolicy() = CachePolicy::NO_CACHE;
+    splatLayer->setLandCoverDictionary(dictionary);
+    splatLayer->setLandCoverLayer(landCover);
+    splatLayer->zones().push_back(splatZone);
+
+
+    // Now, the trees:
+
+    // Load a tree image and make a billboard symbol from it:
+    osg::ref_ptr<osg::Image> tree = URI("../data/splat/pine2.png").getImage();
+    if (tree.valid() == false)
+        return failed("Loading tree image");
+
+    BillboardSymbol* treeSymbol = new BillboardSymbol();
+    treeSymbol->setImage(tree.get());
+    treeSymbol->width() = 12.0f;
+    treeSymbol->height() = 16.0f;
+
+    // Add this symbol to a "frest" biome.
+    GroundCoverBiomeOptions forestBiome;
+    forestBiome.biomeClasses() = "forest";
+    forestBiome.symbols().push_back(treeSymbol);
+    
+    // Assemble the ground cover coniguration:
+    GroundCoverOptions treeOptions;
+    treeOptions.biomes().push_back(forestBiome);
+    treeOptions.maxDistance() = 15000.0;
+    treeOptions.density() = 4.0;
+    treeOptions.fill() = 0.85;
+    treeOptions.brightness() = 2.0;
+    treeOptions.contrast() = 1.0;
+    GroundCover* trees = new GroundCover(treeOptions);
+
+    Zone* treeZone = new Zone();
+    treeZone->setGroundCover(trees);
+
+    // Now, create a ground cover layer for some trees.
+    GroundCoverLayer* treeLayer = new GroundCoverLayer();
+    treeLayer->setName("Ground cover");
+    treeLayer->options().lod() = 13;
+    treeLayer->setLandCoverDictionary(dictionary);
+    treeLayer->setLandCoverLayer(landCover);
+    treeLayer->zones().push_back(treeZone);
+
+
+    // Assemble the Map.
+    Map* map = new Map();
+    map->addLayer(dictionary);
+    map->addLayer(landCover);
+    map->addLayer(splatLayer);
+    map->addLayer(treeLayer);
+
+    // Activate the REX terrain engine (required for splatting)
+    osgEarth::Registry::instance()->overrideTerrainEngineDriverName() = "rex";
+
+    // create a viewer:
+    osgViewer::Viewer viewer(arguments);
+    viewer.getDatabasePager()->setUnrefImageDataAfterApplyPolicy( false, false );
+    viewer.setCameraManipulator( new EarthManipulator(arguments) );
+    viewer.setSceneData(new MapNode(map));
+    return viewer.run();
 }
diff --git a/src/applications/osgearth_terrainprofile/osgearth_terrainprofile.cpp b/src/applications/osgearth_terrainprofile/osgearth_terrainprofile.cpp
index 9c80a95..399f798 100644
--- a/src/applications/osgearth_terrainprofile/osgearth_terrainprofile.cpp
+++ b/src/applications/osgearth_terrainprofile/osgearth_terrainprofile.cpp
@@ -32,6 +32,7 @@
 #include <osgEarthUtil/TerrainProfile>
 #include <osgEarth/GeoMath>
 #include <osgEarth/Registry>
+#include <osgEarth/FileUtils>
 #include <osgEarthFeatures/Feature>
 #include <osgEarthAnnotation/FeatureNode>
 #include <osgText/Text>
@@ -348,8 +349,8 @@ main(int argc, char** argv)
     osgViewer::Viewer viewer(arguments);
 
     // load the .earth file from the command line.
-    osg::Node* earthNode = osgDB::readNodeFiles( arguments );
-    if (!earthNode)
+    osg::ref_ptr<osg::Node> earthNode = osgDB::readNodeFiles( arguments );
+    if (!earthNode.valid())
     {
         OE_NOTICE << "Unable to load earth model" << std::endl;
         return 1;
@@ -357,7 +358,7 @@ main(int argc, char** argv)
 
     osg::Group* root = new osg::Group();
 
-    osgEarth::MapNode * mapNode = osgEarth::MapNode::findMapNode( earthNode );
+    osgEarth::MapNode * mapNode = osgEarth::MapNode::findMapNode( earthNode.get() );
     if (!mapNode)
     {
         OE_NOTICE << "Could not find MapNode " << std::endl;
@@ -390,7 +391,7 @@ main(int argc, char** argv)
 
     viewer.getCamera()->addCullCallback( new AutoClipPlaneCullCallback(mapNode));
 
-    viewer.addEventHandler( new DrawProfileEventHandler( mapNode, root, calculator ) );
+    viewer.addEventHandler( new DrawProfileEventHandler( mapNode, root, calculator.get() ) );
 
     viewer.setSceneData( root );    
 
diff --git a/src/applications/osgearth_tfs/osgearth_tfs.cpp b/src/applications/osgearth_tfs/osgearth_tfs.cpp
index 45bd00f..f28fc1a 100644
--- a/src/applications/osgearth_tfs/osgearth_tfs.cpp
+++ b/src/applications/osgearth_tfs/osgearth_tfs.cpp
@@ -236,7 +236,7 @@ int main(int argc, char** argv)
     packager.setDestSRS( destSRS );
     packager.setLod0Extent(ext);
 
-    packager.package( features, destination, layer, description );
+    packager.package( features.get(), destination, layer, description );
     osg::Timer_t endTime = osg::Timer::instance()->tick();
     OE_NOTICE << "Completed in " << osg::Timer::instance()->delta_s( startTime, endTime ) << " s " << std::endl;
 
diff --git a/src/applications/osgearth_tilesource/osgearth_tilesource.cpp b/src/applications/osgearth_tilesource/osgearth_tilesource.cpp
index d583b8b..aa3ae1a 100644
--- a/src/applications/osgearth_tilesource/osgearth_tilesource.cpp
+++ b/src/applications/osgearth_tilesource/osgearth_tilesource.cpp
@@ -25,6 +25,7 @@
 #include <osgViewer/ViewerEventHandlers>
 #include <osgEarth/Map>
 #include <osgEarth/MapNode>
+#include <osgEarth/ImageLayer>
 #include <osgEarth/Registry>
 #include <osgEarthSymbology/Geometry>
 #include <osgEarthSymbology/GeometryRasterizer>
@@ -105,7 +106,7 @@ int main(int argc, char** argv)
     ImageLayerOptions options( "custom" );
     CustomTileSource* tileSource = new CustomTileSource();
     tileSource->open();
-    map->addImageLayer( new ImageLayer(options, tileSource) );
+    map->addLayer( new ImageLayer(options, tileSource) );
 
     // That's it, the map is ready; now create a MapNode to render the Map:
     MapNode* mapNode = new MapNode( map );
diff --git a/src/applications/osgearth_toc/osgearth_toc.cpp b/src/applications/osgearth_toc/osgearth_toc.cpp
index 6f07547..bd54a86 100644
--- a/src/applications/osgearth_toc/osgearth_toc.cpp
+++ b/src/applications/osgearth_toc/osgearth_toc.cpp
@@ -23,36 +23,45 @@
 #include <osgEarth/MapFrame>
 #include <osgEarth/MapNode>
 #include <osgEarth/MapModelChange>
+#include <osgEarth/ElevationPool>
 #include <osgEarthUtil/EarthManipulator>
 #include <osgEarthUtil/Controls>
 #include <osgEarthUtil/ExampleResources>
+#include <osgEarthUtil/ViewFitter>
 #include <osgViewer/Viewer>
 #include <osgViewer/ViewerEventHandlers>
 #include <osgGA/StateSetManipulator>
 #include <osgDB/ReadFile>
 
 using namespace osgEarth;
+using namespace osgEarth::Util;
 using namespace osgEarth::Util::Controls;
 
 void createControlPanel( osgViewer::View* );
 void updateControlPanel();
 
 static osg::ref_ptr<Map> s_activeMap;
-static osg::ref_ptr<Map> s_inactiveMap;
 static Grid* s_masterGrid;
-static Grid* s_imageBox;
-static Grid* s_elevationBox;
-static Grid* s_modelBox;
+static Grid* s_activeBox;
+static Grid* s_inactiveBox;
 static bool s_updateRequired = true;
-static MapModelChange::ActionType s_changeAction;
+static MapModelChange s_change;
+static EarthManipulator* s_manip;
+static osgViewer::View* s_view;
+
+typedef std::map<std::string, ConfigOptions> InactiveLayers;
+static InactiveLayers _inactive;
 
 //------------------------------------------------------------------------
 
+void updateControlPanel();
+
+
 struct MyMapListener : public MapCallback
 {
     void onMapModelChanged( const MapModelChange& change ) {
         s_updateRequired = true;
-        s_changeAction = change.getAction();
+        s_change = change;
     }
 };
 
@@ -67,8 +76,7 @@ struct UpdateOperation : public osg::Operation
             updateControlPanel();
             s_updateRequired = false;
 
-            if (s_changeAction == MapModelChange::ADD_ELEVATION_LAYER ||
-                s_changeAction == MapModelChange::REMOVE_ELEVATION_LAYER)
+            if (s_change.getElevationLayer())
             {
                 OE_NOTICE << "Dirtying model layers.\n";
                 dirtyModelLayers();
@@ -78,22 +86,47 @@ struct UpdateOperation : public osg::Operation
 
     void dirtyModelLayers()
     {
-        for(unsigned i=0; i<s_activeMap->getNumModelLayers(); ++i)
+        ModelLayerVector modelLayers;
+        s_activeMap->getLayers(modelLayers);
+
+        for(unsigned i=0; i<modelLayers.size(); ++i)
         {
-            ModelSource* ms = s_activeMap->getModelLayerAt(i)->getModelSource();
+            ModelSource* ms = modelLayers[i]->getModelSource();
             if ( ms )
             {
                 ms->dirty();
             }
             else
             {
-                OE_NOTICE << s_activeMap->getModelLayerAt(i)->getName()
+                OE_NOTICE << modelLayers[i]->getName()
                     << " has no model source.\n";
             }
         }
     }
 };
 
+
+struct DumpElevation : public osgGA::GUIEventHandler
+{
+    DumpElevation(MapNode* mapNode, char c) : _mapNode(mapNode), _c(c) { }
+    bool handle(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa, osg::Object*, osg::NodeVisitor*)
+    {
+        if (ea.getEventType() == ea.KEYDOWN && ea.getKey() == _c)
+        {
+            osg::Vec3d world;
+            _mapNode->getTerrain()->getWorldCoordsUnderMouse(aa.asView(), ea.getX(), ea.getY(), world);
+            GeoPoint coords;
+            coords.fromWorld(s_activeMap->getSRS(), world);
+            osg::ref_ptr<ElevationEnvelope> env = s_activeMap->getElevationPool()->createEnvelope(s_activeMap->getSRS(), 23u);
+            float ep_elev = env->getElevation(coords.x(), coords.y());
+            OE_NOTICE << "Elevations under mouse. EP=" << ep_elev << "\n";
+        }
+        return false;
+    }
+    char _c;
+    MapNode* _mapNode;
+};
+
 //------------------------------------------------------------------------
 
 int
@@ -103,9 +136,13 @@ main( int argc, char** argv )
 
     // configure the viewer.
     osgViewer::Viewer viewer( arguments );
+    s_view = &viewer;
 
     // install a motion model
-    viewer.setCameraManipulator( new osgEarth::Util::EarthManipulator() );
+    viewer.setCameraManipulator( s_manip = new osgEarth::Util::EarthManipulator() );
+
+    // disable the small-feature culling (so text will work)
+    viewer.getCamera()->setSmallFeatureCullingPixelSize(-1.0f);
 
     // Load an earth file 
     osg::Node* loaded = osgEarth::Util::MapNodeHelper().load(arguments, &viewer);
@@ -119,10 +156,6 @@ main( int argc, char** argv )
     s_activeMap = mapNode->getMap();
     s_activeMap->addMapCallback( new MyMapListener() );
 
-    // a Map to hold inactive layers (layers that have been removed from the displayed Map)
-    s_inactiveMap = new Map();
-    s_inactiveMap->addMapCallback( new MyMapListener() );
-
     osg::Group* root = new osg::Group();
 
     // install the control panel
@@ -137,112 +170,93 @@ main( int argc, char** argv )
     // install our control panel updater
     viewer.addUpdateOperation( new UpdateOperation() );
 
+    viewer.addEventHandler(new DumpElevation(mapNode, 'E'));
+
     viewer.run();
 }
 
 //------------------------------------------------------------------------
 
-struct LayerVisibleHandler : public ControlEventHandler
+struct ToggleLayerVisibility : public ControlEventHandler
 {
-    LayerVisibleHandler( TerrainLayer* layer ) : _layer(layer) { }
+    ToggleLayerVisibility( VisibleLayer* layer ) : _layer(layer) { }
     void onValueChanged( Control* control, bool value )
     {
         _layer->setVisible( value );
     }
-    TerrainLayer* _layer;
+    VisibleLayer* _layer;
 };
 
 struct LayerOpacityHandler : public ControlEventHandler
 {
-    LayerOpacityHandler( ImageLayer* layer ) : _layer(layer) { }
+    LayerOpacityHandler( VisibleLayer* layer ) : _layer(layer) { }
     void onValueChanged( Control* control, float value )
     {
         _layer->setOpacity( value );
     }
-    ImageLayer* _layer;
-};
-
-struct ModelLayerVisibleHandler : public ControlEventHandler
-{
-    ModelLayerVisibleHandler( ModelLayer* layer ) : _layer(layer) { }
-    void onValueChanged( Control* control, bool value )
-    {
-        _layer->setVisible( value );
-    }
-    ModelLayer* _layer;
-};
-
-struct ModelLayerOpacityHandler : public ControlEventHandler
-{
-    ModelLayerOpacityHandler( ModelLayer* layer ) : _layer(layer) { }
-    void onValueChanged( Control* control, float value )
-    {
-        _layer->setOpacity( value );
-    }
-    ModelLayer* _layer;
+    VisibleLayer* _layer;
 };
 
 struct AddLayerHandler : public ControlEventHandler
 {
-    AddLayerHandler( TerrainLayer* layer ) : _layer(layer) { }
-    void onClick( Control* control, int mouseButtonMask ) {
+    AddLayerHandler(const ConfigOptions& lc) : _lc(lc) { }
 
-        ImageLayer* imageLayer = dynamic_cast< ImageLayer*>( _layer.get() );
-        ElevationLayer* elevationLayer = dynamic_cast< ElevationLayer*>( _layer.get() );
-
-        if (imageLayer)
-        {
-            s_inactiveMap->removeImageLayer( imageLayer );
-            s_activeMap->addImageLayer( imageLayer );
-        }
-        else
+    void onClick( Control* control, int mouseButtonMask )
+    {
+        Layer* layer = Layer::create(_lc);
+        if (layer)
         {
-            s_inactiveMap->removeElevationLayer( elevationLayer );
-            s_activeMap->addElevationLayer( elevationLayer );
+            s_activeMap->addLayer(layer);
+            _inactive.erase(layer->getName());
         }
     }
-    osg::ref_ptr<TerrainLayer> _layer;
+
+    ConfigOptions _lc;
 };
 
 struct RemoveLayerHandler : public ControlEventHandler
 {
-    RemoveLayerHandler( TerrainLayer* layer ) : _layer(layer) { }
-    void onClick( Control* control, int mouseButtonMask ) {        
-        ImageLayer* imageLayer = dynamic_cast< ImageLayer*>( _layer.get() );
-        ElevationLayer* elevationLayer = dynamic_cast< ElevationLayer*>( _layer.get() );
+    RemoveLayerHandler( Layer* layer ) : _layer(layer) { }
 
-        if (imageLayer)
-        {
-            s_inactiveMap->addImageLayer( imageLayer );
-            s_activeMap->removeImageLayer( imageLayer );
-        }
-        else
-        {
-            s_inactiveMap->addElevationLayer( elevationLayer );
-            s_activeMap->removeElevationLayer( elevationLayer );
-        }
+    void onClick( Control* control, int mouseButtonMask )
+    {
+        _inactive[_layer->getName()] = _layer->getConfig(); // save it
+        s_activeMap->removeLayer(_layer.get()); // and remove it
     }
-    osg::ref_ptr<TerrainLayer> _layer;
+    osg::ref_ptr<Layer> _layer;
 };
 
 struct MoveLayerHandler : public ControlEventHandler
 {
-    MoveLayerHandler( TerrainLayer* layer, int newIndex ) : _layer(layer), _newIndex(newIndex) { }
-    void onClick( Control* control, int mouseButtonMask ) {
-        ImageLayer* imageLayer = dynamic_cast< ImageLayer*>( _layer );
-        ElevationLayer* elevationLayer = dynamic_cast< ElevationLayer*>( _layer );
+    MoveLayerHandler( Layer* layer, int newIndex ) : _layer(layer), _newIndex(newIndex) { }
+    void onClick( Control* control, int mouseButtonMask )
+    {
+        s_activeMap->moveLayer(_layer, _newIndex);
+    }
+    Layer* _layer;
+    int _newIndex;
+};
 
-        if (imageLayer)
-        {
-            s_activeMap->moveImageLayer( imageLayer, _newIndex );
-        }
-        else
+struct ZoomLayerHandler : public ControlEventHandler
+{
+    ZoomLayerHandler(Layer* layer) : _layer(layer) { }
+    void onClick(Control* control)
+    {
+        const GeoExtent& extent = _layer->getExtent();
+        if (extent.isValid())
         {
-            s_activeMap->moveElevationLayer( elevationLayer, _newIndex );
+            ViewFitter fitter(s_activeMap->getSRS(), s_view->getCamera());
+            std::vector<GeoPoint> points;
+            points.push_back(GeoPoint(extent.getSRS(), extent.west(), extent.south()));
+            points.push_back(GeoPoint(extent.getSRS(), extent.east(), extent.north()));
+            Viewpoint vp;
+            if (fitter.createViewpoint(points, vp))
+            {
+                s_manip->setViewpoint(vp, 2.0);
+            }
         }
     }
-    TerrainLayer* _layer;
-    int _newIndex;
+    Layer* _layer;
 };
 
 //------------------------------------------------------------------------
@@ -254,82 +268,109 @@ createControlPanel( osgViewer::View* view )
     ControlCanvas* canvas = ControlCanvas::getOrCreate( view );
 
     s_masterGrid = new Grid();
-    s_masterGrid->setBackColor(0,0,0,0.5);
-    s_masterGrid->setMargin( 10 );
-    s_masterGrid->setPadding( 10 );
+    s_masterGrid->setMargin( 5 );
+    s_masterGrid->setPadding( 5 );
     s_masterGrid->setChildSpacing( 10 );
     s_masterGrid->setChildVertAlign( Control::ALIGN_CENTER );
     s_masterGrid->setAbsorbEvents( true );
     s_masterGrid->setVertAlign( Control::ALIGN_TOP );
 
-    //The image layers
-    s_imageBox = new Grid();
-    s_imageBox->setBackColor(0,0,0,0.5);
-    s_imageBox->setMargin( 10 );
-    s_imageBox->setPadding( 10 );
-    s_imageBox->setChildSpacing( 10 );
-    s_imageBox->setChildVertAlign( Control::ALIGN_CENTER );
-    s_imageBox->setAbsorbEvents( true );
-    s_imageBox->setVertAlign( Control::ALIGN_TOP );
-    s_masterGrid->setControl( 0, 0, s_imageBox );
-
-    //the elevation layers
-    s_elevationBox = new Grid();
-    s_elevationBox->setBackColor(0,0,0,0.5);
-    s_elevationBox->setMargin( 10 );
-    s_elevationBox->setPadding( 10 );
-    s_elevationBox->setChildSpacing( 10 );
-    s_elevationBox->setChildVertAlign( Control::ALIGN_CENTER );
-    s_elevationBox->setAbsorbEvents( true );
-    s_elevationBox->setVertAlign( Control::ALIGN_TOP );
-    s_masterGrid->setControl( 1, 0, s_elevationBox );
-
-    //The image layers
-    s_modelBox = new Grid();
-    s_modelBox->setBackColor(0,0,0,0.5);
-    s_modelBox->setMargin( 10 );
-    s_modelBox->setPadding( 10 );
-    s_modelBox->setChildSpacing( 10 );
-    s_modelBox->setChildVertAlign( Control::ALIGN_CENTER );
-    s_modelBox->setAbsorbEvents( true );
-    s_modelBox->setVertAlign( Control::ALIGN_TOP );
-    s_masterGrid->setControl( 2, 0, s_modelBox );
+    //The Map layers
+    s_activeBox = new Grid();
+    s_activeBox->setBackColor(0,0,0,0.5);
+    s_activeBox->setMargin( 10 );
+    s_activeBox->setPadding( 10 );
+    s_activeBox->setChildSpacing( 10 );
+    s_activeBox->setChildVertAlign( Control::ALIGN_CENTER );
+    s_activeBox->setAbsorbEvents( true );
+    s_activeBox->setVertAlign( Control::ALIGN_TOP );
+    s_masterGrid->setControl( 0, 0, s_activeBox );
+
+    //the removed layers
+    s_inactiveBox = new Grid();
+    s_inactiveBox->setBackColor(0,0,0,0.5);
+    s_inactiveBox->setMargin( 10 );
+    s_inactiveBox->setPadding( 10 );
+    s_inactiveBox->setChildSpacing( 10 );
+    s_inactiveBox->setChildVertAlign( Control::ALIGN_CENTER );
+    s_inactiveBox->setAbsorbEvents( true );
+    s_inactiveBox->setVertAlign( Control::ALIGN_TOP );
+    s_masterGrid->setControl( 0, 1, s_inactiveBox );
 
     canvas->addControl( s_masterGrid );
 }
 
 void
-createLayerItem( Grid* grid, int gridRow, int layerIndex, int numLayers, TerrainLayer* layer, bool isActive )
+addLayerItem( Grid* grid, int layerIndex, int numLayers, Layer* layer, bool isActive )
 {
     int gridCol = 0;
+    int gridRow = grid->getNumRows();
+
+    VisibleLayer* visibleLayer = dynamic_cast<VisibleLayer*>(layer);
+
+    // only show layers that derive from VisibleLayer
+    if (!visibleLayer)
+        return;
+
+    ImageLayer* imageLayer = dynamic_cast<ImageLayer*>(layer);
+
+    // don't show hidden coverage layers
+    if (imageLayer && imageLayer->isCoverage() && !imageLayer->getVisible())
+        return;
+    
+    ElevationLayer* elevationLayer = dynamic_cast<ElevationLayer*>(layer);
 
     // a checkbox to enable/disable the layer:
-    CheckBoxControl* enabled = new CheckBoxControl( layer->getVisible() );
-    enabled->addEventHandler( new LayerVisibleHandler(layer) );
-    grid->setControl( gridCol++, gridRow, enabled );
+    if (visibleLayer && layer->getEnabled() && !(imageLayer && imageLayer->isCoverage()))
+    {
+        CheckBoxControl* enabled = new CheckBoxControl( visibleLayer->getVisible() );
+        enabled->addEventHandler( new ToggleLayerVisibility(visibleLayer) );
+        grid->setControl( gridCol, gridRow, enabled );
+    }
+    gridCol++;
 
     // the layer name
     LabelControl* name = new LabelControl( layer->getName() );
+    if (!layer->getEnabled())
+        name->setForeColor(osg::Vec4f(1,1,1,0.35));
     grid->setControl( gridCol, gridRow, name );
     gridCol++;
 
-    ImageLayer* imageLayer = dynamic_cast< ImageLayer* > (layer );
-    if (imageLayer)
+    // layer type
+    std::string typeName = typeid(*layer).name();
+    typeName = typeName.substr(typeName.find_last_of(":")+1);
+    LabelControl* typeLabel = new LabelControl(typeName, osg::Vec4(.5,.7,.5,1));
+    grid->setControl( gridCol, gridRow, typeLabel );
+    gridCol++;
+
+    // status indicator
+    LabelControl* statusLabel =
+        layer->getStatus().isError() ? new LabelControl("[error]", osg::Vec4(1,0,0,1)) :
+        !layer->getEnabled()?          new LabelControl("[disabled]", osg::Vec4(1,1,1,0.35)) :
+                                       new LabelControl("[ok]", osg::Vec4(0,1,0,1)) ;
+    grid->setControl( gridCol, gridRow, statusLabel );
+    gridCol++;
+
+    if (visibleLayer && !elevationLayer && visibleLayer->getEnabled())
     {
         // an opacity slider
-        HSliderControl* opacity = new HSliderControl( 0.0f, 1.0f, imageLayer->getOpacity() );
+        HSliderControl* opacity = new HSliderControl( 0.0f, 1.0f, visibleLayer->getOpacity() );
         opacity->setWidth( 125 );
         opacity->setHeight( 12 );
-        opacity->addEventHandler( new LayerOpacityHandler(imageLayer) );
+        opacity->addEventHandler( new LayerOpacityHandler(visibleLayer) );
         grid->setControl( gridCol, gridRow, opacity );
     }
     gridCol++;
 
-    // status indicator
-    LabelControl* statusLabel = layer->getStatus().isOK()
-        ? new LabelControl("[ok]", osg::Vec4(0,1,0,1))
-        : new LabelControl("[error]", osg::Vec4(1,0,0,1));
-    grid->setControl( gridCol, gridRow, statusLabel );
+    // zoom button
+    if (layer->getExtent().isValid())
+    {
+        LabelControl* zoomButton = new LabelControl("GO", 14);
+        zoomButton->setBackColor( .4,.4,.4,1 );
+        zoomButton->setActiveColor( .8,0,0,1 );
+        zoomButton->addEventHandler( new ZoomLayerHandler(layer) );
+        grid->setControl( gridCol, gridRow, zoomButton );
+    }
     gridCol++;
 
     // move buttons
@@ -358,37 +399,33 @@ createLayerItem( Grid* grid, int gridRow, int layerIndex, int numLayers, Terrain
     addRemove->setHorizAlign( Control::ALIGN_CENTER );
     addRemove->setBackColor( .4,.4,.4,1 );
     addRemove->setActiveColor( .8,0,0,1 );
-    if ( isActive )
-        addRemove->addEventHandler( new RemoveLayerHandler(layer) );
-    else
-        addRemove->addEventHandler( new AddLayerHandler(layer) );
+    addRemove->addEventHandler( new RemoveLayerHandler(layer) );
+
     grid->setControl( gridCol, gridRow, addRemove );
     gridCol++;
+
+    if (layer->getStatus().isError())
+    {
+        grid->setControl(gridCol, gridRow, new LabelControl(layer->getStatus().message(), osg::Vec4(1,.2,.2,1)));
+    }
 }
 
 void
-createModelLayerItem( Grid* grid, int gridRow, ModelLayer* layer, bool isActive )
+createInactiveLayerItem( Grid* grid, int gridRow, const std::string& name, const ConfigOptions& lc )
 {
-    // a checkbox to enable/disable the layer:
-    CheckBoxControl* enabled = new CheckBoxControl( layer->getVisible() );
-    enabled->addEventHandler( new ModelLayerVisibleHandler(layer) );
-    grid->setControl( 0, gridRow, enabled );
+    int gridCol = 0;
 
     // the layer name
-    LabelControl* name = new LabelControl( layer->getName() );
-    grid->setControl( 1, gridRow, name );
-
-    LabelControl* statusLabel = layer->getStatus().isOK()
-        ? new LabelControl("[ok]", osg::Vec4(0, 1, 0, 1))
-        : new LabelControl("[error]", osg::Vec4(1, 0, 0, 1));
-    grid->setControl(2, gridRow, statusLabel);
-
-    // an opacity slider
-    HSliderControl* opacity = new HSliderControl( 0.0f, 1.0f, layer->getOpacity() );
-    opacity->setWidth( 125 );
-    opacity->setHeight( 12 );
-    opacity->addEventHandler( new ModelLayerOpacityHandler(layer) );
-    grid->setControl( 3, gridRow, opacity );
+    LabelControl* nameLabel = new LabelControl( name );
+    grid->setControl( gridCol, gridRow, nameLabel );
+    gridCol++;
+    
+    LabelControl* addRemove = new LabelControl( "ADD", 14 );
+    addRemove->setHorizAlign( Control::ALIGN_CENTER );
+    addRemove->setBackColor( .4,.4,.4,1 );
+    addRemove->setActiveColor( .8,0,0,1 );
+    addRemove->addEventHandler( new AddLayerHandler(lc) );
+    grid->setControl( gridCol, gridRow, addRemove );
 }
 
 void
@@ -397,69 +434,39 @@ updateControlPanel()
     // erase all child controls and just rebuild them b/c we're lazy.
 
     //Rebuild all the image layers    
-    s_imageBox->clearControls();
+    s_activeBox->clearControls();
 
     int row = 0;
 
-    LabelControl* activeLabel = new LabelControl( "Image Layers", 20, osg::Vec4f(1,1,0,1) );
-    s_imageBox->setControl( 1, row++, activeLabel );
+    LabelControl* activeLabel = new LabelControl( "Map Layers", 20, osg::Vec4f(1,1,0,1) );
+    s_activeBox->setControl( 1, row++, activeLabel );
 
     // the active map layers:
-    MapFrame mapf( s_activeMap.get(), Map::ENTIRE_MODEL );
-    int layerNum = mapf.imageLayers().size()-1;
-    for( ImageLayerVector::const_reverse_iterator i = mapf.imageLayers().rbegin(); i != mapf.imageLayers().rend(); ++i )
-        createLayerItem( s_imageBox, row++, layerNum--, mapf.imageLayers().size(), i->get(), true );
+    MapFrame mapf( s_activeMap.get() );
 
-    MapFrame mapf2( s_inactiveMap.get() );
-    if ( mapf2.imageLayers().size() > 0 )
+    const LayerVector& layers = mapf.layers();
+    for (int i = layers.size()-1; i >= 0; --i)
     {
-        LabelControl* inactiveLabel = new LabelControl( "Removed:", 18, osg::Vec4f(1,1,0,1) );
-        s_imageBox->setControl( 1, row++, inactiveLabel );
+        Layer* layer = layers[i].get();
+        addLayerItem(s_activeBox, i, layers.size(), layer, true);
 
-        for( unsigned int i=0; i<mapf2.imageLayers().size(); ++i )
+        if (layer->getStatus().isError())
         {
-            createLayerItem( s_imageBox, row++, -1, -1, mapf2.getImageLayerAt(i), false );
+            OE_WARN << layer->getName() << " : " << layer->getStatus().toString() << "\n";
         }
     }
 
+    // inactive layers:
+    s_inactiveBox->clearControls();
 
-
-
-    //Rebuild the elevation layers
-    s_elevationBox->clearControls();
-
-    row = 0;
-
-    activeLabel = new LabelControl( "Elevation Layers", 20, osg::Vec4f(1,1,0,1) );
-    s_elevationBox->setControl( 1, row++, activeLabel );
-
-    // the active map layers:
-    layerNum = mapf.elevationLayers().size()-1;
-    for( ElevationLayerVector::const_reverse_iterator i = mapf.elevationLayers().rbegin(); i != mapf.elevationLayers().rend(); ++i )
-        createLayerItem( s_elevationBox, row++, layerNum--, mapf.elevationLayers().size(), i->get(), true );
-
-    if ( mapf2.elevationLayers().size() > 0 )
+    if (!_inactive.empty())
     {
-        LabelControl* inactiveLabel = new LabelControl( "Removed:", 18, osg::Vec4f(1,1,0,1) );
-        s_elevationBox->setControl( 1, row++, inactiveLabel );
-
-        for( unsigned int i=0; i<mapf2.elevationLayers().size(); ++i )
+        s_inactiveBox->setControl(0, row++, new LabelControl("Removed:", 18, osg::Vec4f(1,1,0,1)));
+        for (InactiveLayers::const_iterator i = _inactive.begin(); i != _inactive.end(); ++i)
         {
-            createLayerItem( s_elevationBox, row++, -1, -1, mapf2.getElevationLayerAt(i), false );
+            createInactiveLayerItem(s_inactiveBox, row++, i->first, i->second);
         }
     }
 
-
-
-    //Rebuild the model layers
-    s_modelBox->clearControls();
-
-    row = 0;
-
-    activeLabel = new LabelControl( "Model Layers", 20, osg::Vec4f(1,1,0,1) );
-    s_modelBox->setControl( 1, row++, activeLabel );
-
-    // the active map layers:
-    for( ModelLayerVector::const_reverse_iterator i = mapf.modelLayers().rbegin(); i != mapf.modelLayers().rend(); ++i )
-        createModelLayerItem( s_modelBox, row++, i->get(), true );
+    s_inactiveBox->setVisible(!_inactive.empty());
 }
diff --git a/src/applications/osgearth_tracks/osgearth_tracks.cpp b/src/applications/osgearth_tracks/osgearth_tracks.cpp
index 4eb0fb9..c94632f 100644
--- a/src/applications/osgearth_tracks/osgearth_tracks.cpp
+++ b/src/applications/osgearth_tracks/osgearth_tracks.cpp
@@ -169,7 +169,7 @@ void
 createTrackNodes( MapNode* mapNode, osg::Group* parent, const TrackNodeFieldSchema& schema, TrackSims& sims )
 {
     // load an icon to use:
-    osg::ref_ptr<osg::Image> srcImage = osgDB::readImageFile( ICON_URL );
+    osg::ref_ptr<osg::Image> srcImage = osgDB::readRefImageFile( ICON_URL );
     osg::ref_ptr<osg::Image> image;
     ImageUtils::resizeImage( srcImage.get(), ICON_SIZE, ICON_SIZE, image );
 
@@ -184,7 +184,7 @@ createTrackNodes( MapNode* mapNode, osg::Group* parent, const TrackNodeFieldSche
 
         GeoPoint pos(geoSRS, lon0, lat0);
 
-        TrackNode* track = new TrackNode(mapNode, pos, image, schema);
+        TrackNode* track = new TrackNode(mapNode, pos, image.get(), schema);
 
         track->setFieldValue( FIELD_NAME,     Stringify() << "Track:" << i );
         track->setFieldValue( FIELD_POSITION, Stringify() << s_format(pos) );
diff --git a/src/applications/osgearth_transform/osgearth_transform.cpp b/src/applications/osgearth_transform/osgearth_transform.cpp
index 2549d35..b3457d9 100644
--- a/src/applications/osgearth_transform/osgearth_transform.cpp
+++ b/src/applications/osgearth_transform/osgearth_transform.cpp
@@ -64,13 +64,16 @@ struct App
     ui::HSliderControl* uiHeading;
     ui::HSliderControl* uiPitch;
     ui::HSliderControl* uiRoll;
+    ui::CheckBoxControl* uiRelativeZ;
 
     void apply()
     {
+        AltitudeMode altMode = uiRelativeZ->getValue() ? ALTMODE_RELATIVE : ALTMODE_ABSOLUTE;
+
         GeoPoint pos(
             srs,
             uiLon->getValue(), uiLat->getValue(), uiAlt->getValue(),
-            ALTMODE_ABSOLUTE);
+            altMode);
 
         geo->setPosition( pos );
 
@@ -92,9 +95,19 @@ struct Apply : public ui::ControlEventHandler
     App& _app;
 };
 
+struct ZeroAlt : public ui::ControlEventHandler {
+    ZeroAlt(App& app) : _app(app) { }
+    void onClick(ui::Control* control) {
+        _app.uiAlt->setValue(0.0f);
+        _app.apply();
+    }
+    App& _app;
+};
+
 ui::Control* makeUI(App& app)
 {
     ui::Grid* grid = new ui::Grid();
+    grid->setBackColor(0,0,0,0.5);
 
     grid->setControl(0, 0, new ui::LabelControl("Lat:"));
     grid->setControl(0, 1, new ui::LabelControl("Long:"));
@@ -102,15 +115,25 @@ ui::Control* makeUI(App& app)
     grid->setControl(0, 3, new ui::LabelControl("Heading:"));
     grid->setControl(0, 4, new ui::LabelControl("Pitch:"));
     grid->setControl(0, 5, new ui::LabelControl("Roll:"));
-
-    app.uiLat = grid->setControl(1, 0, new ui::HSliderControl(-80.0f, 80.0f, 42.0f, new Apply(app)));
-    app.uiLon = grid->setControl(1, 1, new ui::HSliderControl(-180.0f, 180.0f, 7.0f, new Apply(app)));
-    app.uiAlt = grid->setControl(1, 2, new ui::HSliderControl(50000.0f, 500000.0f, 250000.0f, new Apply(app)));
+    grid->setControl(0, 6, new ui::LabelControl("Relative Z:"));
+
+    app.uiLat = grid->setControl(1, 0, new ui::HSliderControl(40.0f, 50.0f, 44.7433f, new Apply(app)));
+    grid->setControl(2, 0, new LabelControl(app.uiLat));
+    app.uiLon = grid->setControl(1, 1, new ui::HSliderControl(6.0f, 8.0f, 7.0f, new Apply(app)));
+    grid->setControl(2, 1, new LabelControl(app.uiLon));
+    app.uiAlt = grid->setControl(1, 2, new ui::HSliderControl(-3000.0f, 100000.0f, 25000.0f, new Apply(app)));
+    grid->setControl(2, 2, new LabelControl(app.uiAlt));
+    grid->setControl(3, 2, new ButtonControl("Zero", new ZeroAlt(app)));
     app.uiHeading = grid->setControl(1, 3, new ui::HSliderControl(-180.0f, 180.0f, 0.0f, new Apply(app)));
+    grid->setControl(2, 3, new LabelControl(app.uiHeading));
     app.uiPitch   = grid->setControl(1, 4, new ui::HSliderControl(-90.0f, 90.0f, 0.0f, new Apply(app)));
+    grid->setControl(2, 4, new LabelControl(app.uiPitch));
     app.uiRoll    = grid->setControl(1, 5, new ui::HSliderControl(-180.0f, 180.0f, 0.0f, new Apply(app)));
+    grid->setControl(2, 5, new LabelControl(app.uiRoll));
+    app.uiRelativeZ = grid->setControl(1, 6, new ui::CheckBoxControl(true, new Apply(app))); app.uiRelativeZ->setWidth(15.0f);
+    grid->setControl(2, 6, new LabelControl(app.uiRelativeZ));
 
-    app.uiLat->setHorizFill(true, 300.0f);
+    app.uiLat->setHorizFill(true, 700.0f);
     return grid;
 }
 
@@ -136,8 +159,8 @@ main(int argc, char** argv)
 
     // load the model file into the local coordinate frame, which will be
     // +X=east, +Y=north, +Z=up.
-    osg::Node* model = osgDB::readNodeFile("../data/axes.osgt.(1000).scale");
-    if ( !model )
+    osg::ref_ptr<osg::Node> model = osgDB::readRefNodeFile("../data/axes.osgt.(1000).scale.osgearth_shadergen");
+    if (!model.valid())
         return usage(argv[0]);
 
     osg::Group* root = new osg::Group();
@@ -146,12 +169,13 @@ main(int argc, char** argv)
     App app;
     app.srs = mapNode->getMapSRS();
     app.geo = new GeoTransform();
-    app.geo->setTerrain( mapNode->getTerrain() );
     app.pat = new osg::PositionAttitudeTransform();
-    app.pat->addChild( model );
+    app.pat->addChild( model.get() );
     app.geo->addChild( app.pat );
 
-    root->addChild( app.geo );
+    // Place your GeoTransform under the map node and it will automatically support clamping.
+    // If you don't do this, you must call setTerrain to get terrain clamping.
+    mapNode->addChild( app.geo );
     
     viewer.setSceneData( root );
     viewer.getCamera()->setNearFarRatio(0.00002);
diff --git a/src/applications/osgearth_triton/osgearth_triton.cpp b/src/applications/osgearth_triton/osgearth_triton.cpp
index d36e320..8e8fc5a 100644
--- a/src/applications/osgearth_triton/osgearth_triton.cpp
+++ b/src/applications/osgearth_triton/osgearth_triton.cpp
@@ -18,21 +18,22 @@
 */
 #include <osgViewer/Viewer>
 #include <osgDB/FileNameUtils>
+#include <osgEarth/NodeUtils>
 #include <osgEarthUtil/EarthManipulator>
 #include <osgEarthUtil/ExampleResources>
 #include <osgEarthUtil/Controls>
 #include <osgEarthTriton/TritonNode>
 
-#define LC "[osgearth_sundog] "
+#define LC "[osgearth_triton] "
 
 using namespace osgEarth;
 using namespace osgEarth::Util;
 using namespace osgEarth::Triton;
 namespace ui = osgEarth::Util::Controls;
 
+
 struct Settings
 {
-    TritonNode* triton;
     optional<double> chop;
     optional<double> seaState;
     optional<float> alpha;
@@ -51,13 +52,98 @@ struct Settings
             seaState.clear();
         }
 
-        if (alpha.isSet())
+        //if (alpha.isSet())
+        //{
+        //    triton->setAlpha( alpha.get() );
+        //}
+    }
+};
+
+class TritonCallback : public osgEarth::Triton::Callback
+{
+public:
+    TritonCallback(Settings& settings) : _settings(settings) { }
+
+    void onInitialize(Environment& env, Ocean& ocean)
+    {
+        //todo
+    }
+
+    void onDrawOcean(Environment& env, Ocean& ocean)
+    {
+        _settings.apply(env, ocean);
+    }
+
+    Settings& _settings;
+};
+
+
+struct App
+{
+    App()
+    {
+        triton = NULL;
+        mapNode = NULL;        
+    }
+
+    MapNode*    mapNode;
+    TritonNode* triton;
+    Settings    settings;
+
+    osg::Group* getAttachPoint()
+    {
+        return mapNode;
+        //SkyNode* sky = osgEarth::findTopMostNodeOfType<SkyNode>(mapNode);
+        //if (sky)
+        //    return sky;
+        //else
+        //    return mapNode;
+    }
+
+    void addTriton()
+    {
+        // Create TritonNode from TritonOptions
+        osgEarth::Triton::TritonOptions tritonOptions;
+        tritonOptions.user()        = "my_user_name";
+        tritonOptions.licenseCode() = "my_license_code";
+        tritonOptions.maxAltitude() = 10000;
+        tritonOptions.useHeightMap() = true;
+
+        const char* ev_t = ::getenv("TRITON_PATH");
+        if ( ev_t )
         {
-            triton->setAlpha( alpha.get() );
+            tritonOptions.resourcePath() = osgDB::concatPaths(
+                std::string(ev_t),
+                "Resources" );
+
+            OE_INFO << LC 
+                << "Setting resource path to << " << tritonOptions.resourcePath().get()
+                << std::endl;
         }
+        else
+        {
+            OE_WARN << LC
+                << "No resource path! Triton might not initialize properly. "
+                << "Consider setting the TRITON_PATH environment variable."
+                << std::endl;
+        }
+
+
+        triton = new TritonNode(tritonOptions, new TritonCallback(settings));
+        triton->setMapNode(mapNode);
+
+        getAttachPoint()->addChild(triton);
+    }
+
+    void removeTriton()
+    {
+        getAttachPoint()->removeChild(triton);
+        triton = 0L;
     }
 };
-Settings s_settings;
+
+App s_app;
+
 
 
 template<typename T> struct Set : public ui::ControlEventHandler
@@ -67,6 +153,15 @@ template<typename T> struct Set : public ui::ControlEventHandler
     void onValueChanged(ui::Control*, double value) { _var = value; }
 };
 
+struct Toggle : public ui::ControlEventHandler
+{
+    void onValueChanged(ui::Control*, bool value) {
+        if (s_app.triton)
+            s_app.removeTriton();
+        else
+            s_app.addTriton();
+    }
+};
 
 Container* createUI()
 {
@@ -75,33 +170,22 @@ Container* createUI()
     Grid* grid = box->addControl(new Grid());
     int r=0;
     grid->setControl(0, r, new LabelControl("Chop"));
-    grid->setControl(1, r, new HSliderControl(0, 3, 0, new Set<double>(s_settings.chop)));
+    grid->setControl(1, r, new HSliderControl(0, 3, 0, new Set<double>(s_app.settings.chop)));
     ++r;
     grid->setControl(0, r, new LabelControl("Sea State"));
-    grid->setControl(1, r, new HSliderControl(0, 12, 5, new Set<double>(s_settings.seaState)));
+    grid->setControl(1, r, new HSliderControl(0, 12, 5, new Set<double>(s_app.settings.seaState)));
     ++r;  
     grid->setControl(0, r, new LabelControl("Alpha"));
-    grid->setControl(1, r, new HSliderControl(0, 1.0, 1.0, new Set<float>(s_settings.alpha)));
+    grid->setControl(1, r, new HSliderControl(0, 1.0, 1.0, new Set<float>(s_app.settings.alpha)));
     ++r;
+    grid->setControl(0, r, new LabelControl("Toggle"));
+    grid->setControl(1, r, new CheckBoxControl(false, new Toggle()));
+
     grid->getControl(1, r-1)->setHorizFill(true,200);
 
     return box;
 }
 
-class TritonCallback : public osgEarth::Triton::Callback
-{
-public:
-    void onInitialize(Environment& env, Ocean& ocean)
-    {
-        //todo
-    }
-
-    void onDrawOcean(Environment& env, Ocean& ocean)
-    {
-        s_settings.apply(env, ocean);
-    }
-};
-
 
 int
 usage(const char* name)
@@ -141,44 +225,8 @@ main(int argc, char** argv)
 
         viewer.setSceneData( node );
 
-        MapNode* mapNode = MapNode::findMapNode( node );
-
-        // Create TritonNode from TritonOptions
-        osgEarth::Triton::TritonOptions tritonOptions;
-        tritonOptions.user()        = "my_user_name";
-        tritonOptions.licenseCode() = "my_license_code";
-        tritonOptions.maxAltitude() = 10000;
-
-        const char* ev_t = ::getenv("TRITON_PATH");
-        if ( ev_t )
-        {
-            tritonOptions.resourcePath() = osgDB::concatPaths(
-                std::string(ev_t),
-                "Resources" );
-
-            OE_INFO << LC 
-                << "Setting resource path to << " << tritonOptions.resourcePath().get()
-                << std::endl;
-        }
-        else
-        {
-            OE_WARN << LC
-                << "No resource path! Triton might not initialize properly. "
-                << "Consider setting the TRITON_PATH environment variable."
-                << std::endl;
-        }
-
-        s_settings.triton = new TritonNode(
-            mapNode,
-            tritonOptions,
-            new TritonCallback() );
-
-        // Insert under a sky if there is one.
-        SkyNode* sky = osgEarth::findTopMostNodeOfType<SkyNode>(node);
-        if (sky)
-            sky->addChild( s_settings.triton );
-        else
-            node->addChild( s_settings.triton );
+        s_app.mapNode = MapNode::get( node );
+        //s_app.addTriton();
 
         return viewer.run();
     }
diff --git a/src/applications/osgearth_clamp/CMakeLists.txt b/src/applications/osgearth_video/CMakeLists.txt
similarity index 71%
rename from src/applications/osgearth_clamp/CMakeLists.txt
rename to src/applications/osgearth_video/CMakeLists.txt
index 8bf1af5..f4e7c80 100644
--- a/src/applications/osgearth_clamp/CMakeLists.txt
+++ b/src/applications/osgearth_video/CMakeLists.txt
@@ -1,7 +1,7 @@
 INCLUDE_DIRECTORIES(${OSG_INCLUDE_DIRS} )
 SET(TARGET_LIBRARIES_VARS OSG_LIBRARY OSGDB_LIBRARY OSGUTIL_LIBRARY OSGVIEWER_LIBRARY OPENTHREADS_LIBRARY)
 
-SET(TARGET_SRC osgearth_clamp.cpp )
+SET(TARGET_SRC osgearth_video.cpp )
 
 #### end var setup  ###
-SETUP_APPLICATION(osgearth_clamp)
\ No newline at end of file
+SETUP_APPLICATION(osgearth_video)
\ No newline at end of file
diff --git a/src/applications/osgearth_map/osgearth_map.cpp b/src/applications/osgearth_video/osgearth_video.cpp
similarity index 73%
copy from src/applications/osgearth_map/osgearth_map.cpp
copy to src/applications/osgearth_video/osgearth_video.cpp
index 5e7a833..70f1bc8 100644
--- a/src/applications/osgearth_map/osgearth_map.cpp
+++ b/src/applications/osgearth_video/osgearth_video.cpp
@@ -26,11 +26,17 @@
 #include <osgViewer/Viewer>
 #include <osgViewer/ViewerEventHandlers>
 #include <osgEarth/MapNode>
+#include <osgEarth/Registry>
+#include <osgEarth/ImageLayer>
+#include <osgEarth/VideoLayer>
 #include <osgEarthUtil/EarthManipulator>
 #include <osgEarthUtil/AutoClipPlaneHandler>
 #include <osgEarthUtil/Controls>
 #include <osgEarthSymbology/Color>
 #include <osgEarthDrivers/tms/TMSOptions>
+#include <osgEarthDrivers/wms/WMSOptions>
+#include <osgEarthDrivers/gdal/GDALOptions>
+#include <osg/ImageStream>
 
 using namespace osgEarth;
 using namespace osgEarth::Drivers;
@@ -44,18 +50,32 @@ main(int argc, char** argv)
 {
     osg::ArgumentParser arguments(&argc,argv);
 
-    // create the map.
+    // create the empty map.
     Map* map = new Map();
 
-    // add a TMS imager layer:
+    // add a TMS imagery layer:
     TMSOptions imagery;
-    imagery.url() = "http://readymap.org/readymap/tiles/1.0.0/7/";
-    map->addImageLayer( new ImageLayer("Imagery", imagery) );
+    imagery.url() = "http://readymap.org/readymap/tiles/1.0.0/22/";
+    map->addLayer( new ImageLayer("ReadyMap Imagery", imagery) );
 
     // add a TMS elevation layer:
     TMSOptions elevation;
-    elevation.url() = "http://readymap.org/readymap/tiles/1.0.0/9/";
-    map->addElevationLayer( new ElevationLayer("Elevation", elevation) );
+    elevation.url() = "http://readymap.org/readymap/tiles/1.0.0/116/";
+    map->addLayer( new ElevationLayer("ReadyMap Elevation", elevation) );
+   
+    // Load command line arguments as videos.
+    for(int pos=1;pos<arguments.argc();++pos)
+    {
+        if (!arguments.isOption(pos))
+        {
+            std::string filename = arguments[ pos ];
+            OE_NOTICE << "Loading " << filename << std::endl;
+            VideoLayerOptions opt;
+            opt.url() = filename;
+            VideoLayer* layer = new VideoLayer(opt);
+            map->addLayer(layer);
+        }
+    }
 
     // make the map scene graph:
     MapNode* node = new MapNode( map );
@@ -74,4 +94,4 @@ main(int argc, char** argv)
     viewer.addEventHandler(new osgViewer::HelpHandler(arguments.getApplicationUsage()));
 
     return viewer.run();
-}
+}
\ No newline at end of file
diff --git a/src/applications/osgearth_viewer/osgearth_viewer.cpp b/src/applications/osgearth_viewer/osgearth_viewer.cpp
index 2e4b118..4e9f57c 100644
--- a/src/applications/osgearth_viewer/osgearth_viewer.cpp
+++ b/src/applications/osgearth_viewer/osgearth_viewer.cpp
@@ -24,9 +24,10 @@
 #include <osgEarth/Notify>
 #include <osgEarthUtil/EarthManipulator>
 #include <osgEarthUtil/ExampleResources>
-
-#include <osgEarth/Cache>
-#include <osgEarthDrivers/cache_filesystem/FileSystemCache>
+#include <osgEarth/MapNode>
+#include <osgEarth/ThreadingUtils>
+#include <osgEarth/Metrics>
+#include <iostream>
 
 #define LC "[viewer] "
 
@@ -40,9 +41,11 @@ usage(const char* name)
         << "\nUsage: " << name << " file.earth" << std::endl
         << MapNodeHelper().usage() << std::endl;
 
+    getchar();
     return 0;
 }
 
+
 int
 main(int argc, char** argv)
 {
@@ -55,6 +58,8 @@ main(int argc, char** argv)
     float vfov = -1.0f;
     arguments.read("--vfov", vfov);
 
+    
+
     // create a viewer:
     osgViewer::Viewer viewer(arguments);
 
@@ -88,10 +93,12 @@ main(int argc, char** argv)
     if ( node )
     {
         viewer.setSceneData( node );
-        viewer.run();
+        Metrics::run(viewer);
     }
     else
     {
         return usage(argv[0]);
     }
+
+    return 0;
 }
diff --git a/src/applications/osgearth_viewerIOS/osgEarthViewerIOS/AppDelegate.h b/src/applications/osgearth_viewerIOS/AppDelegate.h
similarity index 100%
rename from src/applications/osgearth_viewerIOS/osgEarthViewerIOS/AppDelegate.h
rename to src/applications/osgearth_viewerIOS/AppDelegate.h
diff --git a/src/applications/osgearth_viewerIOS/osgEarthViewerIOS/AppDelegate.m b/src/applications/osgearth_viewerIOS/AppDelegate.mm
similarity index 100%
rename from src/applications/osgearth_viewerIOS/osgEarthViewerIOS/AppDelegate.m
rename to src/applications/osgearth_viewerIOS/AppDelegate.mm
diff --git a/src/applications/osgearth_viewerIOS/CMakeLists.txt b/src/applications/osgearth_viewerIOS/CMakeLists.txt
new file mode 100644
index 0000000..6a9780a
--- /dev/null
+++ b/src/applications/osgearth_viewerIOS/CMakeLists.txt
@@ -0,0 +1,143 @@
+MACRO(LINK_OSG_STATIC_PLUGINS)
+    FOREACH(LINKLIB ${OSG_STATIC_PLUGINS})
+        SET(OSG_PLUGINS_PATH "${OSG_DIR}/lib")
+        TARGET_LINK_LIBRARIES(${TARGET_TARGETNAME} optimized "${OSG_PLUGINS_PATH}/lib${LINKLIB}.a" debug "${OSG_PLUGINS_PATH}/lib${LINKLIB}${CMAKE_DEBUG_POSTFIX}.a")
+    ENDFOREACH(LINKLIB)
+ENDMACRO(LINK_OSG_STATIC_PLUGINS)
+
+INCLUDE_DIRECTORIES(${OSG_INCLUDE_DIRS} )
+
+SET(TARGET_LIBRARIES_VARS OPENTHREADS_LIBRARY OSG_LIBRARY OSGDB_LIBRARY OSGUTIL_LIBRARY OSGGA_LIBRARY OSGVIEWER_LIBRARY
+                          OSGANIMATION_LIBRARY OSGFX_LIBRARY OSGPARTICLE_LIBRARY OSGSHADOW_LIBRARY OSGSIM_LIBRARY OSGTERRAIN_LIBRARY OSGTEXT_LIBRARY
+                          CURL_LIBRARY FREETYPE_LIBRARY GDAL_LIBRARY PROJ_LIBRARY GEOS_LIBRARY SQLITE3_LIBRARY)
+
+SET(TARGET_COMMON_LIBRARIES ${TARGET_COMMON_LIBRARIES} osgEarthUtil)
+
+SET(TARGET_ADDED_LIBRARIES osgdb_osgearth_agglite
+osgdb_osgearth_arcgis
+osgdb_osgearth_arcgis_map_cache
+osgdb_osgearth_bing
+osgdb_osgearth_bumpmap
+osgdb_osgearth_cache_filesystem
+osgdb_osgearth_colorramp
+osgdb_osgearth_debug
+osgdb_osgearth_detail
+osgdb_earth
+osgdb_osgearth_engine_byo
+osgdb_osgearth_engine_mp
+osgdb_osgearth_engine_rex
+osgdb_osgearth_feature_elevation
+osgdb_osgearth_feature_ogr
+osgdb_osgearth_feature_raster
+osgdb_osgearth_feature_tfs
+osgdb_osgearth_feature_wfs
+osgdb_osgearth_feature_xyz
+osgdb_osgearth_featurefilter_intersect
+osgdb_osgearth_featurefilter_join
+osgdb_osgearth_gdal
+osgdb_kml
+osgdb_osgearth_label_annotation
+osgdb_osgearth_mapinspector
+osgdb_osgearth_mask_feature
+osgdb_osgearth_mbtiles
+osgdb_osgearth_model_feature_geom
+osgdb_osgearth_model_simple
+osgdb_osgearth_monitor
+osgdb_osgearth_noise
+osgdb_osgearth_ocean_simple
+osgdb_osgearth_osg
+osgdb_osgearth_quadkey
+osgdb_osgearth_refresh
+osgdb_osgearth_scriptengine_javascript
+osgdb_osgearth_sky_gl
+osgdb_osgearth_sky_simple
+osgdb_osgearth_skyview
+osgdb_osgearth_splat_mask
+osgdb_template
+osgdb_osgearth_template_matclass
+osgdb_osgearth_terrainshader
+osgdb_osgearth_tilecache
+osgdb_osgearth_tileindex
+osgdb_osgearth_tileservice
+osgdb_osgearth_tms
+osgdb_osgearth_vdatum_egm84
+osgdb_osgearth_vdatum_egm96
+osgdb_osgearth_vdatum_egm2008
+osgdb_osgearth_viewpoints
+osgdb_osgearth_vpb
+osgdb_osgearth_wcs
+osgdb_osgearth_wms
+osgdb_osgearth_xyz
+osgdb_osgearth_yahoo)
+
+SET(OSG_STATIC_PLUGINS osgdb_tiff osgdb_rgb osgdb_imageio osgdb_curl osgdb_zip osgdb_freetype osgdb_osg osgdb_ive osgdb_obj osgdb_shp osgdb_openflight osgdb_rot osgdb_scale osgdb_trans)
+
+SET(OSG_STATIC_PLUGINS ${OSG_STATIC_PLUGINS}
+    osgdb_deprecated_osg osgdb_deprecated_osganimation osgdb_deprecated_osgfx
+    osgdb_deprecated_osgparticle osgdb_deprecated_osgshadow osgdb_deprecated_osgsim
+    osgdb_deprecated_osgterrain osgdb_deprecated_osgtext osgdb_deprecated_osgviewer
+    osgdb_deprecated_osgvolume
+)
+
+
+SET(OSG_STATIC_PLUGINS ${OSG_STATIC_PLUGINS}
+    osgdb_serializers_osg osgdb_serializers_osganimation osgdb_serializers_osgfx
+    osgdb_serializers_osgmanipulator osgdb_serializers_osgparticle
+    osgdb_serializers_osgshadow osgdb_serializers_osgsim osgdb_serializers_osgterrain
+    osgdb_serializers_osgtext osgdb_serializers_osgutil osgdb_serializers_osgviewer
+    osgdb_serializers_osgvolume
+)
+
+SET(RESOURCE_FILES  
+    StartViewerController.xib
+    ViewController.xib
+    ${CMAKE_SOURCE_DIR}/tests/readymap.earth
+)
+
+SET(TARGET_SRC
+    AppDelegate.h
+    AppDelegate.mm
+    ViewController.h
+    ViewController.mm
+    StartViewerController.h
+    StartViewerController.mm
+    main.mm
+    osgPlugins.h
+    osgEarthViewerIOS-Info.plist
+    ${RESOURCE_FILES}
+)
+
+
+#backup setting
+SET(TMP_OSGEARTH_BUILD_APPLICATION_BUNDLES {$OSGEARTH_BUILD_APPLICATION_BUNDLES}) # make sure its an app bundle
+SET(OSGEARTH_BUILD_APPLICATION_BUNDLES TRUE)
+
+SET(TMP_VPB_BUILD_APPLICATION_BUNDLES {$VPB_BUILD_APPLICATION_BUNDLES}) # make sure its an app bundle
+SET(VPB_BUILD_APPLICATION_BUNDLES TRUE)
+
+SET(TMP_CMAKE_OSX_ARCHITECTURES {$CMAKE_OSX_ARCHITECTURES}) # for now exclude armv7s as freetypes is missing it
+SET(CMAKE_OSX_ARCHITECTURES "armv7;arm64")
+
+SET(MACOSX_DEPLOYMENT_TARGET, ${IPHONE_VERSION_MIN})
+
+SETUP_EXAMPLE(osgearth_viewer_ios)
+LINK_OSG_STATIC_PLUGINS()
+
+SET_TARGET_PROPERTIES(${TARGET_TARGETNAME} PROPERTIES XCODE_ATTRIBUTE_PRODUCT_TYPE "com.apple.product-type.application"
+                                                                 XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "iPhone Developer"
+                                                                 XCODE_ATTRIBUTE_IPHONEOS_DEPLOYMENT_TARGET ${IPHONE_VERSION_MIN}
+                                                                 XCODE_ATTRIBUTE_TARGETED_DEVICE_FAMILY "1,2"
+                                                                 XCODE_ATTRIBUTE_ENABLE_BITCODE "NO")
+SET(CMAKE_XCODE_ATTRIBUTE_DEBUG_INFORMATION_FORMAT "dwarf")
+SET_SOURCE_FILES_PROPERTIES(${RESOURCE_FILES} PROPERTIES MACOSX_PACKAGE_LOCATION Resources)
+
+
+SET(CMAKE_EXE_LINKER_FLAGS "-framework QuartzCore -framework Foundation -framework UIKit 
+-framework ImageIO -framework CoreImage -framework Accelerate -framework MobileCoreServices -framework CoreGraphics 
+-framework JavaScriptCore -framework Security -lsqlite3 -licucore -lz -liconv -lbz2")
+
+#restore setting
+SET(OSGEARTH_BUILD_APPLICATION_BUNDLES {$TMP_OSGEARTH_BUILD_APPLICATION_BUNDLES})
+SET(VPB_BUILD_APPLICATION_BUNDLES {$TMP_VPB_BUILD_APPLICATION_BUNDLES})
+
+SET(CMAKE_OSX_ARCHITECTURES {$TMP_CMAKE_OSX_ARCHITECTURES})
diff --git a/src/applications/osgearth_viewerIOS/osgEarthViewerIOS/MultiTouchManipulator/EarthMultiTouchManipulator.cpp b/src/applications/osgearth_viewerIOS/EarthMultiTouchManipulator.cpp
similarity index 100%
rename from src/applications/osgearth_viewerIOS/osgEarthViewerIOS/MultiTouchManipulator/EarthMultiTouchManipulator.cpp
rename to src/applications/osgearth_viewerIOS/EarthMultiTouchManipulator.cpp
diff --git a/src/applications/osgearth_viewerIOS/osgEarthViewerIOS/MultiTouchManipulator/EarthMultiTouchManipulator.h b/src/applications/osgearth_viewerIOS/EarthMultiTouchManipulator.h
similarity index 100%
rename from src/applications/osgearth_viewerIOS/osgEarthViewerIOS/MultiTouchManipulator/EarthMultiTouchManipulator.h
rename to src/applications/osgearth_viewerIOS/EarthMultiTouchManipulator.h
diff --git a/src/applications/osgearth_viewerIOS/osgEarthViewerIOS/StartViewerController.h b/src/applications/osgearth_viewerIOS/StartViewerController.h
similarity index 100%
rename from src/applications/osgearth_viewerIOS/osgEarthViewerIOS/StartViewerController.h
rename to src/applications/osgearth_viewerIOS/StartViewerController.h
diff --git a/src/applications/osgearth_viewerIOS/osgEarthViewerIOS/StartViewerController.m b/src/applications/osgearth_viewerIOS/StartViewerController.mm
similarity index 80%
rename from src/applications/osgearth_viewerIOS/osgEarthViewerIOS/StartViewerController.m
rename to src/applications/osgearth_viewerIOS/StartViewerController.mm
index 517121c..a133bdb 100644
--- a/src/applications/osgearth_viewerIOS/osgEarthViewerIOS/StartViewerController.m
+++ b/src/applications/osgearth_viewerIOS/StartViewerController.mm
@@ -11,6 +11,8 @@
 #include <osgDB/FileUtils>
 #include <osgDB/FileNameUtils>
 
+static NSString* s_autoloadFile = @""; //readymap_flat.earth";
+
 @interface StartViewerController ()
 
 @end
@@ -43,7 +45,8 @@
     fileArray = [[NSMutableArray alloc] init];
     
     std::string fullPath = osgDB::findDataFile("tests/readymap.earth");
-    
+    if(fullPath.empty()) fullPath = osgDB::findDataFile("readymap.earth");
+
     osgDB::DirectoryContents dirContents = osgDB::getDirectoryContents(osgDB::getFilePath(fullPath));
     for(unsigned int i=0; i<dirContents.size(); i++){
         //OSG_ALWAYS << "Dir item: " << dirContents[i] << std::endl;
@@ -52,9 +55,14 @@
             [fileArray addObject:nsFile];
         }
     }
-     
-    currentSelection = [fileArray count]-1;
-    [pickerView selectRow:currentSelection inComponent:0 animated:NO];  
+    
+    if([fileArray count] > 0) {
+        currentSelection = [fileArray count]-1;
+        [pickerView selectRow:currentSelection inComponent:0 animated:NO];
+    }
+
+    if(s_autoloadFile != nil && [s_autoloadFile length] > 0)
+        [self loadEarthView:s_autoloadFile];
 }
 
 - (void)viewDidUnload
@@ -72,12 +80,17 @@
 
 -(IBAction)onStartViewer
 {
-    /*if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone) {
-     self.osgEarthViewController = [[[ViewController alloc] initWithNibName:@"ViewController_iPhone" bundle:nil] autorelease];
-     } else {
-     self.osgEarthViewController = [[[ViewController alloc] initWithNibName:@"ViewController_iPad" bundle:nil] autorelease];
-     }*/
-    self.osgEarthViewController = [[ViewController alloc] intWithFileName:[fileArray objectAtIndex:currentSelection]];
+    [self loadEarthView:[fileArray objectAtIndex:currentSelection]];
+}
+
+-(void) loadEarthView:(NSString*)aFile
+{
+    if(self.osgEarthViewController != nil) {
+        [self.osgEarthViewController release];
+        self.osgEarthViewController = nil;
+    }
+    
+    self.osgEarthViewController = [[ViewController alloc] intWithFileName:aFile];
     [self.osgEarthViewController startAnimation]; 
     [self.view addSubview:self.osgEarthViewController.view];
 }
diff --git a/src/applications/osgearth_viewerIOS/osgEarthViewerIOS/StartViewerController.xib b/src/applications/osgearth_viewerIOS/StartViewerController.xib
similarity index 100%
rename from src/applications/osgearth_viewerIOS/osgEarthViewerIOS/StartViewerController.xib
rename to src/applications/osgearth_viewerIOS/StartViewerController.xib
diff --git a/src/applications/osgearth_viewerIOS/osgEarthViewerIOS/ViewController.h b/src/applications/osgearth_viewerIOS/ViewController.h
similarity index 92%
rename from src/applications/osgearth_viewerIOS/osgEarthViewerIOS/ViewController.h
rename to src/applications/osgearth_viewerIOS/ViewController.h
index 1f5e80f..6455c65 100644
--- a/src/applications/osgearth_viewerIOS/osgEarthViewerIOS/ViewController.h
+++ b/src/applications/osgearth_viewerIOS/ViewController.h
@@ -25,4 +25,6 @@
 - (void)startAnimation;
 - (void)stopAnimation;
 
+- (IBAction)onBackClicked:(id)sender;
+
 @end
diff --git a/src/applications/osgearth_viewerIOS/osgEarthViewerIOS/ViewController.m b/src/applications/osgearth_viewerIOS/ViewController.mm
similarity index 65%
rename from src/applications/osgearth_viewerIOS/osgEarthViewerIOS/ViewController.m
rename to src/applications/osgearth_viewerIOS/ViewController.mm
index 80d7630..c09bc69 100644
--- a/src/applications/osgearth_viewerIOS/osgEarthViewerIOS/ViewController.m
+++ b/src/applications/osgearth_viewerIOS/ViewController.mm
@@ -9,13 +9,14 @@
 
 #include "osgPlugins.h"
 
+#include <osg/Material>
 #include <osgDB/FileUtils>
 #include <osgDB/FileNameUtils>
 
 #include <osgViewer/api/IOS/GraphicsWindowIOS>
 
 #include <osgEarth/Viewpoint>
-#include <osgEarthUtil/SkyNode>
+#include <osgEarthUtil/Sky>
 #include <osgEarthUtil/EarthManipulator>
 #include <osgEarthUtil/ExampleResources>
 
@@ -34,7 +35,8 @@ using namespace osgEarth::Util;
 
 - (id)intWithFileName:(NSString*)file
 {
-    self = [super init];
+    //self = [super init];
+    self = [super initWithNibName:@"ViewController" bundle:nil];
     if(self){
         
         _file = [file cStringUsingEncoding:NSASCIIStringEncoding];
@@ -53,18 +55,6 @@ using namespace osgEarth::Util;
 }
 
 - (void)loadOsgEarthDemoScene{
-
-    // install our default manipulator (do this before calling load)
-    //    _viewer->setCameraManipulator( new osgEarth::Util::EarthManipulator() );
-    
-    // This chunk inverts the Y axis.
-    osgEarth::Util::EarthManipulator* manip = new osgEarth::Util::EarthManipulator();
-    //osgEarth::Util::EarthManipulator::ActionOptions options;
-    //options.add(osgEarth::Util::EarthManipulator::OPTION_SCALE_Y, -1.0);
-    //manip->getSettings()->bindMouse(osgEarth::Util::EarthManipulator::ACTION_EARTH_DRAG, osgGA::GUIEventAdapter::LEFT_MOUSE_BUTTON, 0L, options);
-    manip->getSettings()->setThrowingEnabled(true);
-    manip->getSettings()->setThrowDecayRate(0.1);
-    _viewer->setCameraManipulator( manip );
     
     osg::Node* node = osgDB::readNodeFile(osgDB::findDataFile("tests/" + _file));
     if ( !node )
@@ -80,34 +70,10 @@ using namespace osgEarth::Util;
         return;
     }
     
-    // warn about not having an earth manip
-    osgEarth::Util::EarthManipulator* manip_temp = dynamic_cast<osgEarth::Util::EarthManipulator*>(_viewer->getCameraManipulator());
-    if ( manip_temp == 0L )
-    {
-        OSG_WARN << "Helper used before installing an EarthManipulator" << std::endl;
-    }
-    
     // a root node to hold everything:
     osg::Group* root = new osg::Group();
     root->addChild( mapNode.get() );
 
-    
-    //have to add these
-    osg::Material* material = new osg::Material();
-    material->setAmbient(osg::Material::FRONT, osg::Vec4(0.4,0.4,0.4,1.0));
-    material->setDiffuse(osg::Material::FRONT, osg::Vec4(0.9,0.9,0.9,1.0));
-    material->setSpecular(osg::Material::FRONT, osg::Vec4(0.4,0.4,0.4,1.0));
-    root->getOrCreateStateSet()->setAttribute(material); //lighting doesn't work without a material for some reason
-    root->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE);//comment out to disable lighting
-    
-    double hours = 12.0f;
-    float ambientBrightness = 1.0f;
-    osgEarth::Util::SkyNode* sky = new osgEarth::Util::SkyNode( mapNode->getMap() );
-    sky->setAmbientBrightness( ambientBrightness );
-    sky->setDateTime( 1984, 11, 8, hours );
-    sky->attach( _viewer, 0 );
-    root->addChild( sky );
-    
     _viewer->setSceneData( root );
 }
 
@@ -120,9 +86,19 @@ using namespace osgEarth::Util;
     
     setenv("GDAL_DATA", dataPath.c_str(), 1);
     
-    osg::setNotifyLevel(osg::DEBUG_FP);
+    osg::setNotifyLevel(osg::FATAL);
     osgEarth::setNotifyLevel(osg::DEBUG_FP);
     
+    // thread-safe initialization of the OSG wrapper manager. Calling this here
+    // prevents the "unsupported wrapper" messages from OSG
+    osgDB::Registry::instance()->getObjectWrapperManager()->findWrapper("osg::Image");
+    
+    // ensure tiff plugin is used over imageio
+    osgDB::Registry::instance()->addFileExtensionAlias("tiff", "tiff");
+    osgDB::Registry::instance()->addFileExtensionAlias("tif", "tiff");
+    
+    osg::DisplaySettings::instance()->setVertexBufferHint(osg::DisplaySettings::VertexBufferHint::VERTEX_BUFFER_OBJECT);
+    
     //get screen scale
     UIScreen* screen = [UIScreen mainScreen];
     float scale = 1.0f;
@@ -138,6 +114,8 @@ using namespace osgEarth::Util;
     
     //create the viewer
 	_viewer = new osgViewer::Viewer();
+    // just do single threaded
+    _viewer->setThreadingModel(osgViewer::ViewerBase::SingleThreaded);
     
 	// Setup the traits parameters
 	traits->x = 0;
@@ -148,7 +126,7 @@ using namespace osgEarth::Util;
 	traits->alpha = 8;
     //traits->samples = 4;
     //traits->sampleBuffers = 2;
-	traits->stencil = 0;
+	traits->stencil = 8;
 	traits->windowDecoration = false;
 	traits->doubleBuffer = true;
 	traits->sharedContext = 0;
@@ -163,6 +141,9 @@ using namespace osgEarth::Util;
 	if(graphicsContext)
 	{
         _viewer->getCamera()->setGraphicsContext(graphicsContext);
+        _viewer->realize();
+        osgViewer::GraphicsWindowIOS* osgWindow = dynamic_cast<osgViewer::GraphicsWindowIOS*>(_viewer->getCamera()->getGraphicsContext());
+        if(osgWindow) [self.view sendSubviewToBack:(UIView*)osgWindow->getView()];
     }
     
     _viewer->getCamera()->setViewport(new osg::Viewport(0, 0, traits->width, traits->height));
@@ -173,23 +154,26 @@ using namespace osgEarth::Util;
                                                            0.1f, 10000.0f);
 
 
+    // Tell the database pager to not modify the unref settings
+    _viewer->getDatabasePager()->setUnrefImageDataAfterApplyPolicy( true, false );
+
+
+    // install our default manipulator
+    osgEarth::Util::EarthManipulator* manip = new osgEarth::Util::EarthManipulator();
+    //manip->getSettings()->setThrowingEnabled(true);
+    //manip->getSettings()->setThrowDecayRate(0.1);
+    _viewer->setCameraManipulator( manip );
+
+    // disable the small-feature culling
+    _viewer->getCamera()->setSmallFeatureCullingPixelSize(-1.0f);
+
+    // set a near/far ratio that is smaller than the default. This allows us to get
+    // closer to the ground without near clipping. If you need more, use --logdepth
+    _viewer->getCamera()->setNearFarRatio(0.0001);
+
     //load
     [self loadOsgEarthDemoScene];
-    
-    // configure the near/far so we don't clip things that are up close
-    _viewer->getCamera()->setNearFarRatio(0.00002);
-    
-    //optimize viewer and db pager
-    _viewer->setThreadingModel(osgViewer::ViewerBase::SingleThreaded);
-    _viewer->getCamera()->setLODScale(_viewer->getCamera()->getLODScale()/2.0);
-    
-    // osgEarth benefits from pre-compilation of GL objects in the pager. In newer versions of
-    // OSG, this activates OSG's IncrementalCompileOpeartion in order to avoid frame breaks.
-//    _viewer->getDatabasePager()->setDoPreCompile( true );
-//   _viewer->getDatabasePager()->setTargetMaximumNumberOfPageLOD(0);
-//    _viewer->getDatabasePager()->setUnrefImageDataAfterApplyPolicy(true,true);
 
-  
     _isAnimating=false;
     [self startAnimation];
     
@@ -239,11 +223,18 @@ using namespace osgEarth::Util;
     }
 }
 
+- (IBAction)onBackClicked:(id)sender {
+    [self stopAnimation];
+    _viewer->done();
+    //_viewer->stopThreading();
+    //_viewer->getDatabasePager()->cancel();
+    [self.view removeFromSuperview];
+}
+
 
 - (void)update:(CADisplayLink *)sender
 {
-    //
-    _viewer->frame();
+    if(_viewer != NULL && _isAnimating) _viewer->frame();
 }
 
 
diff --git a/src/applications/osgearth_viewerIOS/ViewController.xib b/src/applications/osgearth_viewerIOS/ViewController.xib
new file mode 100644
index 0000000..0fb382c
--- /dev/null
+++ b/src/applications/osgearth_viewerIOS/ViewController.xib
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.iPad.XIB" version="3.0" toolsVersion="11762" systemVersion="15G1004" targetRuntime="iOS.CocoaTouch.iPad" propertyAccessControl="none" colorMatched="YES">
+    <device id="ipad9_7" orientation="portrait">
+        <adaptation id="fullscreen"/>
+    </device>
+    <dependencies>
+        <deployment identifier="iOS"/>
+        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="11757"/>
+        <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
+    </dependencies>
+    <objects>
+        <placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="ViewController">
+            <connections>
+                <outlet property="view" destination="1" id="3"/>
+            </connections>
+        </placeholder>
+        <placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
+        <view contentMode="scaleToFill" id="1">
+            <rect key="frame" x="0.0" y="0.0" width="768" height="1024"/>
+            <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+            <subviews>
+                <button opaque="NO" contentMode="scaleToFill" misplaced="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" id="asM-JQ-Lh3">
+                    <rect key="frame" x="26" y="20" width="34" height="30"/>
+                    <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
+                    <state key="normal" title="Back"/>
+                    <connections>
+                        <action selector="onBackClicked:" destination="-1" eventType="touchUpInside" id="ftb-OI-onZ"/>
+                    </connections>
+                </button>
+            </subviews>
+            <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
+        </view>
+    </objects>
+    <simulatedMetricsContainer key="defaultSimulatedMetrics">
+        <simulatedStatusBarMetrics key="statusBar"/>
+        <simulatedOrientationMetrics key="orientation"/>
+        <simulatedScreenMetrics key="destination"/>
+    </simulatedMetricsContainer>
+</document>
diff --git a/src/applications/osgearth_viewerIOS/osgEarthViewerIOS/main.m b/src/applications/osgearth_viewerIOS/main.mm
similarity index 100%
rename from src/applications/osgearth_viewerIOS/osgEarthViewerIOS/main.m
rename to src/applications/osgearth_viewerIOS/main.mm
diff --git a/src/applications/osgearth_viewerIOS/osgEarthViewerIOS/osgEarthViewerIOS-Info.plist b/src/applications/osgearth_viewerIOS/osgEarthViewerIOS-Info.plist
similarity index 100%
rename from src/applications/osgearth_viewerIOS/osgEarthViewerIOS/osgEarthViewerIOS-Info.plist
rename to src/applications/osgearth_viewerIOS/osgEarthViewerIOS-Info.plist
diff --git a/src/applications/osgearth_viewerIOS/osgEarthViewerIOS.xcodeproj/project.pbxproj b/src/applications/osgearth_viewerIOS/osgEarthViewerIOS.xcodeproj/project.pbxproj
deleted file mode 100644
index d95596c..0000000
--- a/src/applications/osgearth_viewerIOS/osgEarthViewerIOS.xcodeproj/project.pbxproj
+++ /dev/null
@@ -1,1066 +0,0 @@
-// !$*UTF8*$!
-{
-	archiveVersion = 1;
-	classes = {
-	};
-	objectVersion = 46;
-	objects = {
-
-/* Begin PBXBuildFile section */
-		1F823FFE173C1D20003B519D /* libosgdb_osgearth_engine_mp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 1F823FFD173C1D20003B519D /* libosgdb_osgearth_engine_mp.a */; };
-		1F8566A817666C29005BCD2B /* libosgdb_osgearth_scriptengine_javascriptcore.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 1F8566A717666C29005BCD2B /* libosgdb_osgearth_scriptengine_javascriptcore.a */; };
-		1F8566AA17666E67005BCD2B /* libJavaScriptCore.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 1F8566A917666E67005BCD2B /* libJavaScriptCore.a */; };
-		1F8566AC17666EC1005BCD2B /* libicucore.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 1F8566AB17666EC1005BCD2B /* libicucore.dylib */; };
-		1FA5C122177B608000A161FF /* gdal_data in Resources */ = {isa = PBXBuildFile; fileRef = 1FA5C121177B608000A161FF /* gdal_data */; };
-		90283E5615C6FB3E00620EEF /* tests in Resources */ = {isa = PBXBuildFile; fileRef = 90283E5415C6FB3E00620EEF /* tests */; };
-		90283E5715C6FB3E00620EEF /* data in Resources */ = {isa = PBXBuildFile; fileRef = 90283E5515C6FB3E00620EEF /* data */; };
-		90283E5D15C7091A00620EEF /* libosgdb_osgearth_engine_quadtree.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 90283E5C15C7091A00620EEF /* libosgdb_osgearth_engine_quadtree.a */; };
-		903B45DB15C0DE9F00F7702B /* EarthMultiTouchManipulator.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 903B45D915C0DE9F00F7702B /* EarthMultiTouchManipulator.cpp */; };
-		9048FB2415FA9DE50012C900 /* libosgdb_tiff.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 9048FB2315FA9DE50012C900 /* libosgdb_tiff.a */; };
-		904E22771691CC42002D66FD /* Accelerate.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 904E22751691CC42002D66FD /* Accelerate.framework */; };
-		904E22781691CC42002D66FD /* MobileCoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 904E22761691CC42002D66FD /* MobileCoreServices.framework */; };
-		9051000115B1EDFD00D9ABD3 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9051000015B1EDFD00D9ABD3 /* Foundation.framework */; };
-		9051000315B1EDFD00D9ABD3 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9051000215B1EDFD00D9ABD3 /* CoreGraphics.framework */; };
-		9051000715B1EDFD00D9ABD3 /* OpenGLES.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9051000615B1EDFD00D9ABD3 /* OpenGLES.framework */; };
-		9051000D15B1EDFD00D9ABD3 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 9051000B15B1EDFD00D9ABD3 /* InfoPlist.strings */; };
-		9051000F15B1EDFD00D9ABD3 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 9051000E15B1EDFD00D9ABD3 /* main.m */; };
-		9051001315B1EDFD00D9ABD3 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 9051001215B1EDFD00D9ABD3 /* AppDelegate.m */; };
-		9051001A15B1EDFD00D9ABD3 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 9051001915B1EDFD00D9ABD3 /* ViewController.m */; };
-		9051001D15B1EDFD00D9ABD3 /* ViewController_iPhone.xib in Resources */ = {isa = PBXBuildFile; fileRef = 9051001B15B1EDFD00D9ABD3 /* ViewController_iPhone.xib */; };
-		9051002015B1EDFD00D9ABD3 /* ViewController_iPad.xib in Resources */ = {isa = PBXBuildFile; fileRef = 9051001E15B1EDFD00D9ABD3 /* ViewController_iPad.xib */; };
-		905100C315B20AC600D9ABD3 /* ImageIO.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 905100C215B20AC600D9ABD3 /* ImageIO.framework */; };
-		905100C515B20AD100D9ABD3 /* CoreImage.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 905100C415B20AD100D9ABD3 /* CoreImage.framework */; settings = {ATTRIBUTES = (Weak, ); }; };
-		905100CB15B2101E00D9ABD3 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 905100CA15B2101E00D9ABD3 /* QuartzCore.framework */; };
-		905100CD15B217A800D9ABD3 /* libc++.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 905100CC15B217A800D9ABD3 /* libc++.dylib */; };
-		905100CF15B217B500D9ABD3 /* libz.1.1.3.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 905100CE15B217B500D9ABD3 /* libz.1.1.3.dylib */; };
-		905100D115B2185000D9ABD3 /* libiconv.2.4.0.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 905100D015B2185000D9ABD3 /* libiconv.2.4.0.dylib */; };
-		9051FFFF15B1EDFD00D9ABD3 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9051FFFE15B1EDFD00D9ABD3 /* UIKit.framework */; };
-		907D033915B86F8700575110 /* libOpenThreads.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D02DC15B86F8700575110 /* libOpenThreads.a */; };
-		907D033A15B86F8700575110 /* libosg.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D02DD15B86F8700575110 /* libosg.a */; };
-		907D033B15B86F8700575110 /* libosgAnimation.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D02DE15B86F8700575110 /* libosgAnimation.a */; };
-		907D033C15B86F8700575110 /* libosgdb_3dc.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D02DF15B86F8700575110 /* libosgdb_3dc.a */; };
-		907D033D15B86F8700575110 /* libosgdb_3ds.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D02E015B86F8700575110 /* libosgdb_3ds.a */; };
-		907D033E15B86F8700575110 /* libosgdb_ac.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D02E115B86F8700575110 /* libosgdb_ac.a */; };
-		907D033F15B86F8700575110 /* libosgdb_bmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D02E215B86F8700575110 /* libosgdb_bmp.a */; };
-		907D034015B86F8700575110 /* libosgdb_bsp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D02E315B86F8700575110 /* libosgdb_bsp.a */; };
-		907D034115B86F8700575110 /* libosgdb_bvh.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D02E415B86F8700575110 /* libosgdb_bvh.a */; };
-		907D034215B86F8700575110 /* libosgdb_cfg.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D02E515B86F8700575110 /* libosgdb_cfg.a */; };
-		907D034315B86F8700575110 /* libosgdb_curl.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D02E615B86F8700575110 /* libosgdb_curl.a */; };
-		907D034415B86F8700575110 /* libosgdb_dds.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D02E715B86F8700575110 /* libosgdb_dds.a */; };
-		907D034515B86F8700575110 /* libosgdb_deprecated_osg.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D02E815B86F8700575110 /* libosgdb_deprecated_osg.a */; };
-		907D034615B86F8700575110 /* libosgdb_deprecated_osganimation.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D02E915B86F8700575110 /* libosgdb_deprecated_osganimation.a */; };
-		907D034715B86F8700575110 /* libosgdb_deprecated_osgfx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D02EA15B86F8700575110 /* libosgdb_deprecated_osgfx.a */; };
-		907D034815B86F8700575110 /* libosgdb_deprecated_osgparticle.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D02EB15B86F8700575110 /* libosgdb_deprecated_osgparticle.a */; };
-		907D034915B86F8700575110 /* libosgdb_deprecated_osgshadow.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D02EC15B86F8700575110 /* libosgdb_deprecated_osgshadow.a */; };
-		907D034A15B86F8700575110 /* libosgdb_deprecated_osgsim.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D02ED15B86F8700575110 /* libosgdb_deprecated_osgsim.a */; };
-		907D034B15B86F8700575110 /* libosgdb_deprecated_osgterrain.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D02EE15B86F8700575110 /* libosgdb_deprecated_osgterrain.a */; };
-		907D034C15B86F8700575110 /* libosgdb_deprecated_osgtext.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D02EF15B86F8700575110 /* libosgdb_deprecated_osgtext.a */; };
-		907D034D15B86F8700575110 /* libosgdb_deprecated_osgviewer.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D02F015B86F8700575110 /* libosgdb_deprecated_osgviewer.a */; };
-		907D034E15B86F8700575110 /* libosgdb_deprecated_osgvolume.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D02F115B86F8700575110 /* libosgdb_deprecated_osgvolume.a */; };
-		907D034F15B86F8700575110 /* libosgdb_deprecated_osgwidget.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D02F215B86F8700575110 /* libosgdb_deprecated_osgwidget.a */; };
-		907D035015B86F8700575110 /* libosgdb_dot.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D02F315B86F8700575110 /* libosgdb_dot.a */; };
-		907D035115B86F8700575110 /* libosgdb_dw.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D02F415B86F8700575110 /* libosgdb_dw.a */; };
-		907D035215B86F8700575110 /* libosgdb_dxf.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D02F515B86F8700575110 /* libosgdb_dxf.a */; };
-		907D035315B86F8700575110 /* libosgdb_freetype.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D02F615B86F8700575110 /* libosgdb_freetype.a */; };
-		907D035415B86F8700575110 /* libosgdb_gdal.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D02F715B86F8700575110 /* libosgdb_gdal.a */; };
-		907D035515B86F8700575110 /* libosgdb_geo.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D02F815B86F8700575110 /* libosgdb_geo.a */; };
-		907D035615B86F8700575110 /* libosgdb_glsl.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D02F915B86F8700575110 /* libosgdb_glsl.a */; };
-		907D035715B86F8700575110 /* libosgdb_gz.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D02FA15B86F8700575110 /* libosgdb_gz.a */; };
-		907D035815B86F8700575110 /* libosgdb_hdr.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D02FB15B86F8700575110 /* libosgdb_hdr.a */; };
-		907D035915B86F8700575110 /* libosgdb_imageio.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D02FC15B86F8700575110 /* libosgdb_imageio.a */; };
-		907D035A15B86F8700575110 /* libosgdb_ive.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D02FD15B86F8700575110 /* libosgdb_ive.a */; };
-		907D035B15B86F8700575110 /* libosgdb_logo.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D02FE15B86F8700575110 /* libosgdb_logo.a */; };
-		907D035C15B86F8700575110 /* libosgdb_lwo.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D02FF15B86F8700575110 /* libosgdb_lwo.a */; };
-		907D035D15B86F8700575110 /* libosgdb_lws.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D030015B86F8700575110 /* libosgdb_lws.a */; };
-		907D035E15B86F8700575110 /* libosgdb_md2.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D030115B86F8700575110 /* libosgdb_md2.a */; };
-		907D035F15B86F8700575110 /* libosgdb_mdl.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D030215B86F8700575110 /* libosgdb_mdl.a */; };
-		907D036015B86F8700575110 /* libosgdb_normals.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D030315B86F8700575110 /* libosgdb_normals.a */; };
-		907D036115B86F8700575110 /* libosgdb_obj.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D030415B86F8700575110 /* libosgdb_obj.a */; };
-		907D036215B86F8700575110 /* libosgdb_ogr.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D030515B86F8700575110 /* libosgdb_ogr.a */; };
-		907D036315B86F8700575110 /* libosgdb_openflight.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D030615B86F8700575110 /* libosgdb_openflight.a */; };
-		907D036415B86F8700575110 /* libosgdb_osg.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D030715B86F8700575110 /* libosgdb_osg.a */; };
-		907D036515B86F8700575110 /* libosgdb_osga.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D030815B86F8700575110 /* libosgdb_osga.a */; };
-		907D036615B86F8700575110 /* libosgdb_osgshadow.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D030915B86F8700575110 /* libosgdb_osgshadow.a */; };
-		907D036715B86F8700575110 /* libosgdb_osgterrain.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D030A15B86F8700575110 /* libosgdb_osgterrain.a */; };
-		907D036815B86F8700575110 /* libosgdb_osgtgz.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D030B15B86F8700575110 /* libosgdb_osgtgz.a */; };
-		907D036915B86F8700575110 /* libosgdb_osgviewer.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D030C15B86F8700575110 /* libosgdb_osgviewer.a */; };
-		907D036A15B86F8700575110 /* libosgdb_p3d.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D030D15B86F8700575110 /* libosgdb_p3d.a */; };
-		907D036B15B86F8700575110 /* libosgdb_pic.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D030E15B86F8700575110 /* libosgdb_pic.a */; };
-		907D036C15B86F8700575110 /* libosgdb_ply.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D030F15B86F8700575110 /* libosgdb_ply.a */; };
-		907D036D15B86F8700575110 /* libosgdb_pnm.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D031015B86F8700575110 /* libosgdb_pnm.a */; };
-		907D036E15B86F8700575110 /* libosgdb_pov.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D031115B86F8700575110 /* libosgdb_pov.a */; };
-		907D036F15B86F8700575110 /* libosgdb_pvr.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D031215B86F8700575110 /* libosgdb_pvr.a */; };
-		907D037015B86F8700575110 /* libosgdb_revisions.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D031315B86F8700575110 /* libosgdb_revisions.a */; };
-		907D037115B86F8700575110 /* libosgdb_rgb.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D031415B86F8700575110 /* libosgdb_rgb.a */; };
-		907D037215B86F8700575110 /* libosgdb_rot.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D031515B86F8700575110 /* libosgdb_rot.a */; };
-		907D037315B86F8700575110 /* libosgdb_scale.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D031615B86F8700575110 /* libosgdb_scale.a */; };
-		907D037415B86F8700575110 /* libosgdb_serializers_osg.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D031715B86F8700575110 /* libosgdb_serializers_osg.a */; };
-		907D037515B86F8700575110 /* libosgdb_serializers_osganimation.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D031815B86F8700575110 /* libosgdb_serializers_osganimation.a */; };
-		907D037615B86F8700575110 /* libosgdb_serializers_osgfx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D031915B86F8700575110 /* libosgdb_serializers_osgfx.a */; };
-		907D037715B86F8700575110 /* libosgdb_serializers_osgmanipulator.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D031A15B86F8700575110 /* libosgdb_serializers_osgmanipulator.a */; };
-		907D037815B86F8700575110 /* libosgdb_serializers_osgparticle.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D031B15B86F8700575110 /* libosgdb_serializers_osgparticle.a */; };
-		907D037915B86F8700575110 /* libosgdb_serializers_osgshadow.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D031C15B86F8700575110 /* libosgdb_serializers_osgshadow.a */; };
-		907D037A15B86F8700575110 /* libosgdb_serializers_osgsim.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D031D15B86F8700575110 /* libosgdb_serializers_osgsim.a */; };
-		907D037B15B86F8700575110 /* libosgdb_serializers_osgterrain.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D031E15B86F8700575110 /* libosgdb_serializers_osgterrain.a */; };
-		907D037C15B86F8700575110 /* libosgdb_serializers_osgtext.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D031F15B86F8700575110 /* libosgdb_serializers_osgtext.a */; };
-		907D037D15B86F8700575110 /* libosgdb_serializers_osgvolume.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D032015B86F8700575110 /* libosgdb_serializers_osgvolume.a */; };
-		907D037E15B86F8700575110 /* libosgdb_shp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D032115B86F8700575110 /* libosgdb_shp.a */; };
-		907D037F15B86F8700575110 /* libosgdb_stl.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D032215B86F8700575110 /* libosgdb_stl.a */; };
-		907D038015B86F8700575110 /* libosgdb_tga.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D032315B86F8700575110 /* libosgdb_tga.a */; };
-		907D038115B86F8700575110 /* libosgdb_tgz.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D032415B86F8700575110 /* libosgdb_tgz.a */; };
-		907D038215B86F8700575110 /* libosgdb_trans.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D032515B86F8700575110 /* libosgdb_trans.a */; };
-		907D038315B86F8700575110 /* libosgdb_txf.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D032615B86F8700575110 /* libosgdb_txf.a */; };
-		907D038415B86F8700575110 /* libosgdb_txp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D032715B86F8700575110 /* libosgdb_txp.a */; };
-		907D038515B86F8700575110 /* libosgdb_vtf.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D032815B86F8700575110 /* libosgdb_vtf.a */; };
-		907D038615B86F8700575110 /* libosgdb_x.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D032915B86F8700575110 /* libosgdb_x.a */; };
-		907D038715B86F8700575110 /* libosgdb_zip.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D032A15B86F8700575110 /* libosgdb_zip.a */; };
-		907D038815B86F8700575110 /* libosgDB.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D032B15B86F8700575110 /* libosgDB.a */; };
-		907D038915B86F8700575110 /* libosgFX.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D032C15B86F8700575110 /* libosgFX.a */; };
-		907D038A15B86F8700575110 /* libosgGA.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D032D15B86F8700575110 /* libosgGA.a */; };
-		907D038B15B86F8700575110 /* libosgManipulator.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D032E15B86F8700575110 /* libosgManipulator.a */; };
-		907D038C15B86F8700575110 /* libosgParticle.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D032F15B86F8700575110 /* libosgParticle.a */; };
-		907D038D15B86F8700575110 /* libosgPresentation.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D033015B86F8700575110 /* libosgPresentation.a */; };
-		907D038E15B86F8700575110 /* libosgShadow.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D033115B86F8700575110 /* libosgShadow.a */; };
-		907D038F15B86F8700575110 /* libosgSim.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D033215B86F8700575110 /* libosgSim.a */; };
-		907D039015B86F8700575110 /* libosgTerrain.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D033315B86F8700575110 /* libosgTerrain.a */; };
-		907D039115B86F8700575110 /* libosgText.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D033415B86F8700575110 /* libosgText.a */; };
-		907D039215B86F8700575110 /* libosgUtil.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D033515B86F8700575110 /* libosgUtil.a */; };
-		907D039315B86F8700575110 /* libosgViewer.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D033615B86F8700575110 /* libosgViewer.a */; };
-		907D039415B86F8700575110 /* libosgVolume.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D033715B86F8700575110 /* libosgVolume.a */; };
-		907D039515B86F8700575110 /* libosgWidget.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D033815B86F8700575110 /* libosgWidget.a */; };
-		907D03BD15B86F9E00575110 /* libosgdb_kml.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D039715B86F9E00575110 /* libosgdb_kml.a */; };
-		907D03BE15B86F9E00575110 /* libosgdb_osgearth_feature_wfs.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D039815B86F9E00575110 /* libosgdb_osgearth_feature_wfs.a */; };
-		907D03BF15B86F9E00575110 /* libosgdb_osgearth_feature_tfs.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D039915B86F9E00575110 /* libosgdb_osgearth_feature_tfs.a */; };
-		907D03C015B86F9E00575110 /* libosgdb_osgearth_tms.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D039A15B86F9E00575110 /* libosgdb_osgearth_tms.a */; };
-		907D03C115B86F9E00575110 /* libosgdb_osgearth_wms.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D039B15B86F9E00575110 /* libosgdb_osgearth_wms.a */; };
-		907D03C215B86F9E00575110 /* libosgdb_osgearth_label_overlay.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D039C15B86F9E00575110 /* libosgdb_osgearth_label_overlay.a */; };
-		907D03C315B86F9E00575110 /* libosgdb_osgearth_xyz.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D039D15B86F9E00575110 /* libosgdb_osgearth_xyz.a */; };
-		907D03C415B86F9E00575110 /* libosgEarthUtil.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D039E15B86F9E00575110 /* libosgEarthUtil.a */; };
-		907D03C515B86F9E00575110 /* libosgdb_osgearth_label_annotation.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D039F15B86F9E00575110 /* libosgdb_osgearth_label_annotation.a */; };
-		907D03C615B86F9E00575110 /* libosgdb_osgearth_mask_feature.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D03A015B86F9E00575110 /* libosgdb_osgearth_mask_feature.a */; };
-		907D03C715B86F9E00575110 /* libosgdb_osgearth_model_feature_geom.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D03A115B86F9E00575110 /* libosgdb_osgearth_model_feature_geom.a */; };
-		907D03C815B86F9E00575110 /* libosgEarthAnnotation.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D03A215B86F9E00575110 /* libosgEarthAnnotation.a */; };
-		907D03C915B86F9E00575110 /* libosgdb_osgearth_agglite.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D03A315B86F9E00575110 /* libosgdb_osgearth_agglite.a */; };
-		907D03CA15B86F9E00575110 /* libosgdb_osgearth_feature_ogr.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D03A415B86F9E00575110 /* libosgdb_osgearth_feature_ogr.a */; };
-		907D03CB15B86F9E00575110 /* libosgdb_osgearth_model_feature_stencil.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D03A515B86F9E00575110 /* libosgdb_osgearth_model_feature_stencil.a */; };
-		907D03CC15B86F9E00575110 /* libosgdb_osgearth_vdatum_egm2008.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D03A615B86F9E00575110 /* libosgdb_osgearth_vdatum_egm2008.a */; };
-		907D03CD15B86F9E00575110 /* libosgdb_osgearth_model_simple.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D03A715B86F9E00575110 /* libosgdb_osgearth_model_simple.a */; };
-		907D03CE15B86F9E00575110 /* libosgdb_osgearth_engine_osgterrain.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D03A815B86F9E00575110 /* libosgdb_osgearth_engine_osgterrain.a */; };
-		907D03CF15B86F9E00575110 /* libosgEarthFeatures.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D03A915B86F9E00575110 /* libosgEarthFeatures.a */; };
-		907D03D015B86F9E00575110 /* libosgdb_osgearth_vdatum_egm96.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D03AA15B86F9E00575110 /* libosgdb_osgearth_vdatum_egm96.a */; };
-		907D03D115B86F9E00575110 /* libosgdb_osgearth_ocean_surface.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D03AB15B86F9E00575110 /* libosgdb_osgearth_ocean_surface.a */; };
-		907D03D215B86F9E00575110 /* libosgdb_osgearth_debug.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D03AC15B86F9E00575110 /* libosgdb_osgearth_debug.a */; };
-		907D03D315B86F9E00575110 /* libosgdb_osgearth_mbtiles.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D03AD15B86F9E00575110 /* libosgdb_osgearth_mbtiles.a */; };
-		907D03D415B86F9E00575110 /* libosgdb_osgearth_vdatum_egm84.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D03AE15B86F9E00575110 /* libosgdb_osgearth_vdatum_egm84.a */; };
-		907D03D515B86F9E00575110 /* libosgdb_osgearth_tileservice.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D03AF15B86F9E00575110 /* libosgdb_osgearth_tileservice.a */; };
-		907D03D615B86F9E00575110 /* libosgdb_osgearth_yahoo.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D03B015B86F9E00575110 /* libosgdb_osgearth_yahoo.a */; };
-		907D03D715B86F9E00575110 /* libosgdb_osgearth_arcgis_map_cache.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D03B115B86F9E00575110 /* libosgdb_osgearth_arcgis_map_cache.a */; };
-		907D03D815B86F9E00575110 /* libosgdb_osgearth_tilecache.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D03B215B86F9E00575110 /* libosgdb_osgearth_tilecache.a */; };
-		907D03D915B86F9E00575110 /* libosgdb_osgearth_wcs.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D03B315B86F9E00575110 /* libosgdb_osgearth_wcs.a */; };
-		907D03DA15B86F9E00575110 /* libosgEarthSymbology.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D03B415B86F9E00575110 /* libosgEarthSymbology.a */; };
-		907D03DB15B86F9F00575110 /* libosgdb_osgearth_gdal.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D03B515B86F9E00575110 /* libosgdb_osgearth_gdal.a */; };
-		907D03DC15B86F9F00575110 /* libosgdb_osgearth_refresh.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D03B615B86F9E00575110 /* libosgdb_osgearth_refresh.a */; };
-		907D03DD15B86F9F00575110 /* libosgdb_osgearth_vpb.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D03B715B86F9E00575110 /* libosgdb_osgearth_vpb.a */; };
-		907D03DE15B86F9F00575110 /* libosgdb_earth.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D03B815B86F9E00575110 /* libosgdb_earth.a */; };
-		907D03DF15B86F9F00575110 /* libosgdb_osgearth_cache_filesystem.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D03B915B86F9E00575110 /* libosgdb_osgearth_cache_filesystem.a */; };
-		907D03E015B86F9F00575110 /* libosgdb_osgearth_arcgis.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D03BA15B86F9E00575110 /* libosgdb_osgearth_arcgis.a */; };
-		907D03E115B86F9F00575110 /* libosgdb_osgearth_osg.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D03BB15B86F9E00575110 /* libosgdb_osgearth_osg.a */; };
-		907D03E215B86F9F00575110 /* libosgEarth.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D03BC15B86F9E00575110 /* libosgEarth.a */; };
-		907D03FA15B8C31A00575110 /* libsqlite3.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D03F915B8C31A00575110 /* libsqlite3.dylib */; };
-		907D0A8E15B8CEBE00575110 /* libGEOS_3.2.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D0A8D15B8CEBE00575110 /* libGEOS_3.2.a */; };
-		907D0A9115B8DDAA00575110 /* GLES2ShaderGenVisitor.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 907D0A8F15B8DDAA00575110 /* GLES2ShaderGenVisitor.cpp */; };
-		90A0DD6D15B7BAF9004FACEE /* libFreeType_iphone_universal.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 90A0DD6C15B7BAF9004FACEE /* libFreeType_iphone_universal.a */; };
-		90A0DD6F15B7BB50004FACEE /* libproj.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 90A0DD6E15B7BB50004FACEE /* libproj.a */; };
-		90A0DD7115B7BB64004FACEE /* libgdal.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 90A0DD7015B7BB64004FACEE /* libgdal.a */; };
-		90A0DD7615B7BBA4004FACEE /* libcurl.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 90A0DD7515B7BBA4004FACEE /* libcurl.a */; };
-		90B8676615C8894900F5CDC3 /* StartViewerController.m in Sources */ = {isa = PBXBuildFile; fileRef = 90B8676415C8894900F5CDC3 /* StartViewerController.m */; };
-		90B8676715C8894900F5CDC3 /* StartViewerController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 90B8676515C8894900F5CDC3 /* StartViewerController.xib */; };
-		90DABDDC15CEFF9700D0F609 /* moon_1024x512.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 90DABDDB15CEFF9700D0F609 /* moon_1024x512.jpg */; };
-/* End PBXBuildFile section */
-
-/* Begin PBXFileReference section */
-		1F823FFD173C1D20003B519D /* libosgdb_osgearth_engine_mp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgdb_osgearth_engine_mp.a; path = ../../../lib/Release/libosgdb_osgearth_engine_mp.a; sourceTree = "<group>"; };
-		1F8566A717666C29005BCD2B /* libosgdb_osgearth_scriptengine_javascriptcore.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgdb_osgearth_scriptengine_javascriptcore.a; path = ../../../lib/Release/libosgdb_osgearth_scriptengine_javascriptcore.a; sourceTree = "<group>"; };
-		1F8566A917666E67005BCD2B /* libJavaScriptCore.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libJavaScriptCore.a; path = ../../../../3rdParty/all/libJavaScriptCore.a; sourceTree = "<group>"; };
-		1F8566AB17666EC1005BCD2B /* libicucore.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libicucore.dylib; path = usr/lib/libicucore.dylib; sourceTree = SDKROOT; };
-		1FA5C121177B608000A161FF /* gdal_data */ = {isa = PBXFileReference; lastKnownFileType = folder; name = gdal_data; path = ../../../../../3rdParty/gdal_data; sourceTree = "<group>"; };
-		90283E5415C6FB3E00620EEF /* tests */ = {isa = PBXFileReference; lastKnownFileType = folder; name = tests; path = ../../../../tests; sourceTree = "<group>"; };
-		90283E5515C6FB3E00620EEF /* data */ = {isa = PBXFileReference; lastKnownFileType = folder; name = data; path = ../../../../data; sourceTree = "<group>"; };
-		90283E5C15C7091A00620EEF /* libosgdb_osgearth_engine_quadtree.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgdb_osgearth_engine_quadtree.a; path = ../../../lib/Release/libosgdb_osgearth_engine_quadtree.a; sourceTree = "<group>"; };
-		903B45D915C0DE9F00F7702B /* EarthMultiTouchManipulator.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = EarthMultiTouchManipulator.cpp; sourceTree = "<group>"; };
-		903B45DA15C0DE9F00F7702B /* EarthMultiTouchManipulator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = EarthMultiTouchManipulator.h; sourceTree = "<group>"; };
-		9048FB2315FA9DE50012C900 /* libosgdb_tiff.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgdb_tiff.a; path = "../../../../osg-ios/lib/libosgdb_tiff.a"; sourceTree = "<group>"; };
-		904E22751691CC42002D66FD /* Accelerate.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Accelerate.framework; path = System/Library/Frameworks/Accelerate.framework; sourceTree = SDKROOT; };
-		904E22761691CC42002D66FD /* MobileCoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MobileCoreServices.framework; path = System/Library/Frameworks/MobileCoreServices.framework; sourceTree = SDKROOT; };
-		9051000015B1EDFD00D9ABD3 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
-		9051000215B1EDFD00D9ABD3 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; };
-		9051000615B1EDFD00D9ABD3 /* OpenGLES.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OpenGLES.framework; path = System/Library/Frameworks/OpenGLES.framework; sourceTree = SDKROOT; };
-		9051000A15B1EDFD00D9ABD3 /* osgEarthViewerIOS-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "osgEarthViewerIOS-Info.plist"; sourceTree = "<group>"; };
-		9051000C15B1EDFD00D9ABD3 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = "<group>"; };
-		9051000E15B1EDFD00D9ABD3 /* main.m */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.objcpp; path = main.m; sourceTree = "<group>"; };
-		9051001015B1EDFD00D9ABD3 /* osgEarthViewerIOS-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "osgEarthViewerIOS-Prefix.pch"; sourceTree = "<group>"; };
-		9051001115B1EDFD00D9ABD3 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = "<group>"; };
-		9051001215B1EDFD00D9ABD3 /* AppDelegate.m */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.objcpp; path = AppDelegate.m; sourceTree = "<group>"; };
-		9051001815B1EDFD00D9ABD3 /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = "<group>"; };
-		9051001915B1EDFD00D9ABD3 /* ViewController.m */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.objcpp; path = ViewController.m; sourceTree = "<group>"; };
-		9051001C15B1EDFD00D9ABD3 /* en */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = en; path = en.lproj/ViewController_iPhone.xib; sourceTree = "<group>"; };
-		9051001F15B1EDFD00D9ABD3 /* en */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = en; path = en.lproj/ViewController_iPad.xib; sourceTree = "<group>"; };
-		905100C215B20AC600D9ABD3 /* ImageIO.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ImageIO.framework; path = System/Library/Frameworks/ImageIO.framework; sourceTree = SDKROOT; };
-		905100C415B20AD100D9ABD3 /* CoreImage.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreImage.framework; path = System/Library/Frameworks/CoreImage.framework; sourceTree = SDKROOT; };
-		905100C615B20B1D00D9ABD3 /* osgPlugins.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = osgPlugins.h; sourceTree = "<group>"; };
-		905100CA15B2101E00D9ABD3 /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; };
-		905100CC15B217A800D9ABD3 /* libc++.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = "libc++.dylib"; path = "usr/lib/libc++.dylib"; sourceTree = SDKROOT; };
-		905100CE15B217B500D9ABD3 /* libz.1.1.3.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libz.1.1.3.dylib; path = usr/lib/libz.1.1.3.dylib; sourceTree = SDKROOT; };
-		905100D015B2185000D9ABD3 /* libiconv.2.4.0.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libiconv.2.4.0.dylib; path = usr/lib/libiconv.2.4.0.dylib; sourceTree = SDKROOT; };
-		9051FFFA15B1EDFD00D9ABD3 /* osgEarth.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = osgEarth.app; sourceTree = BUILT_PRODUCTS_DIR; };
-		9051FFFE15B1EDFD00D9ABD3 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; };
-		907D02DC15B86F8700575110 /* libOpenThreads.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libOpenThreads.a; path = "../../../../osg-ios/lib/libOpenThreads.a"; sourceTree = "<group>"; };
-		907D02DD15B86F8700575110 /* libosg.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosg.a; path = "../../../../osg-ios/lib/libosg.a"; sourceTree = "<group>"; };
-		907D02DE15B86F8700575110 /* libosgAnimation.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgAnimation.a; path = "../../../../osg-ios/lib/libosgAnimation.a"; sourceTree = "<group>"; };
-		907D02DF15B86F8700575110 /* libosgdb_3dc.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgdb_3dc.a; path = "../../../../osg-ios/lib/libosgdb_3dc.a"; sourceTree = "<group>"; };
-		907D02E015B86F8700575110 /* libosgdb_3ds.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgdb_3ds.a; path = "../../../../osg-ios/lib/libosgdb_3ds.a"; sourceTree = "<group>"; };
-		907D02E115B86F8700575110 /* libosgdb_ac.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgdb_ac.a; path = "../../../../osg-ios/lib/libosgdb_ac.a"; sourceTree = "<group>"; };
-		907D02E215B86F8700575110 /* libosgdb_bmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgdb_bmp.a; path = "../../../../osg-ios/lib/libosgdb_bmp.a"; sourceTree = "<group>"; };
-		907D02E315B86F8700575110 /* libosgdb_bsp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgdb_bsp.a; path = "../../../../osg-ios/lib/libosgdb_bsp.a"; sourceTree = "<group>"; };
-		907D02E415B86F8700575110 /* libosgdb_bvh.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgdb_bvh.a; path = "../../../../osg-ios/lib/libosgdb_bvh.a"; sourceTree = "<group>"; };
-		907D02E515B86F8700575110 /* libosgdb_cfg.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgdb_cfg.a; path = "../../../../osg-ios/lib/libosgdb_cfg.a"; sourceTree = "<group>"; };
-		907D02E615B86F8700575110 /* libosgdb_curl.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgdb_curl.a; path = "../../../../osg-ios/lib/libosgdb_curl.a"; sourceTree = "<group>"; };
-		907D02E715B86F8700575110 /* libosgdb_dds.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgdb_dds.a; path = "../../../../osg-ios/lib/libosgdb_dds.a"; sourceTree = "<group>"; };
-		907D02E815B86F8700575110 /* libosgdb_deprecated_osg.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgdb_deprecated_osg.a; path = "../../../../osg-ios/lib/libosgdb_deprecated_osg.a"; sourceTree = "<group>"; };
-		907D02E915B86F8700575110 /* libosgdb_deprecated_osganimation.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgdb_deprecated_osganimation.a; path = "../../../../osg-ios/lib/libosgdb_deprecated_osganimation.a"; sourceTree = "<group>"; };
-		907D02EA15B86F8700575110 /* libosgdb_deprecated_osgfx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgdb_deprecated_osgfx.a; path = "../../../../osg-ios/lib/libosgdb_deprecated_osgfx.a"; sourceTree = "<group>"; };
-		907D02EB15B86F8700575110 /* libosgdb_deprecated_osgparticle.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgdb_deprecated_osgparticle.a; path = "../../../../osg-ios/lib/libosgdb_deprecated_osgparticle.a"; sourceTree = "<group>"; };
-		907D02EC15B86F8700575110 /* libosgdb_deprecated_osgshadow.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgdb_deprecated_osgshadow.a; path = "../../../../osg-ios/lib/libosgdb_deprecated_osgshadow.a"; sourceTree = "<group>"; };
-		907D02ED15B86F8700575110 /* libosgdb_deprecated_osgsim.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgdb_deprecated_osgsim.a; path = "../../../../osg-ios/lib/libosgdb_deprecated_osgsim.a"; sourceTree = "<group>"; };
-		907D02EE15B86F8700575110 /* libosgdb_deprecated_osgterrain.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgdb_deprecated_osgterrain.a; path = "../../../../osg-ios/lib/libosgdb_deprecated_osgterrain.a"; sourceTree = "<group>"; };
-		907D02EF15B86F8700575110 /* libosgdb_deprecated_osgtext.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgdb_deprecated_osgtext.a; path = "../../../../osg-ios/lib/libosgdb_deprecated_osgtext.a"; sourceTree = "<group>"; };
-		907D02F015B86F8700575110 /* libosgdb_deprecated_osgviewer.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgdb_deprecated_osgviewer.a; path = "../../../../osg-ios/lib/libosgdb_deprecated_osgviewer.a"; sourceTree = "<group>"; };
-		907D02F115B86F8700575110 /* libosgdb_deprecated_osgvolume.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgdb_deprecated_osgvolume.a; path = "../../../../osg-ios/lib/libosgdb_deprecated_osgvolume.a"; sourceTree = "<group>"; };
-		907D02F215B86F8700575110 /* libosgdb_deprecated_osgwidget.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgdb_deprecated_osgwidget.a; path = "../../../../osg-ios/lib/libosgdb_deprecated_osgwidget.a"; sourceTree = "<group>"; };
-		907D02F315B86F8700575110 /* libosgdb_dot.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgdb_dot.a; path = "../../../../osg-ios/lib/libosgdb_dot.a"; sourceTree = "<group>"; };
-		907D02F415B86F8700575110 /* libosgdb_dw.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgdb_dw.a; path = "../../../../osg-ios/lib/libosgdb_dw.a"; sourceTree = "<group>"; };
-		907D02F515B86F8700575110 /* libosgdb_dxf.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgdb_dxf.a; path = "../../../../osg-ios/lib/libosgdb_dxf.a"; sourceTree = "<group>"; };
-		907D02F615B86F8700575110 /* libosgdb_freetype.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgdb_freetype.a; path = "../../../../osg-ios/lib/libosgdb_freetype.a"; sourceTree = "<group>"; };
-		907D02F715B86F8700575110 /* libosgdb_gdal.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgdb_gdal.a; path = "../../../../osg-ios/lib/libosgdb_gdal.a"; sourceTree = "<group>"; };
-		907D02F815B86F8700575110 /* libosgdb_geo.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgdb_geo.a; path = "../../../../osg-ios/lib/libosgdb_geo.a"; sourceTree = "<group>"; };
-		907D02F915B86F8700575110 /* libosgdb_glsl.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgdb_glsl.a; path = "../../../../osg-ios/lib/libosgdb_glsl.a"; sourceTree = "<group>"; };
-		907D02FA15B86F8700575110 /* libosgdb_gz.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgdb_gz.a; path = "../../../../osg-ios/lib/libosgdb_gz.a"; sourceTree = "<group>"; };
-		907D02FB15B86F8700575110 /* libosgdb_hdr.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgdb_hdr.a; path = "../../../../osg-ios/lib/libosgdb_hdr.a"; sourceTree = "<group>"; };
-		907D02FC15B86F8700575110 /* libosgdb_imageio.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgdb_imageio.a; path = "../../../../osg-ios/lib/libosgdb_imageio.a"; sourceTree = "<group>"; };
-		907D02FD15B86F8700575110 /* libosgdb_ive.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgdb_ive.a; path = "../../../../osg-ios/lib/libosgdb_ive.a"; sourceTree = "<group>"; };
-		907D02FE15B86F8700575110 /* libosgdb_logo.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgdb_logo.a; path = "../../../../osg-ios/lib/libosgdb_logo.a"; sourceTree = "<group>"; };
-		907D02FF15B86F8700575110 /* libosgdb_lwo.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgdb_lwo.a; path = "../../../../osg-ios/lib/libosgdb_lwo.a"; sourceTree = "<group>"; };
-		907D030015B86F8700575110 /* libosgdb_lws.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgdb_lws.a; path = "../../../../osg-ios/lib/libosgdb_lws.a"; sourceTree = "<group>"; };
-		907D030115B86F8700575110 /* libosgdb_md2.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgdb_md2.a; path = "../../../../osg-ios/lib/libosgdb_md2.a"; sourceTree = "<group>"; };
-		907D030215B86F8700575110 /* libosgdb_mdl.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgdb_mdl.a; path = "../../../../osg-ios/lib/libosgdb_mdl.a"; sourceTree = "<group>"; };
-		907D030315B86F8700575110 /* libosgdb_normals.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgdb_normals.a; path = "../../../../osg-ios/lib/libosgdb_normals.a"; sourceTree = "<group>"; };
-		907D030415B86F8700575110 /* libosgdb_obj.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgdb_obj.a; path = "../../../../osg-ios/lib/libosgdb_obj.a"; sourceTree = "<group>"; };
-		907D030515B86F8700575110 /* libosgdb_ogr.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgdb_ogr.a; path = "../../../../osg-ios/lib/libosgdb_ogr.a"; sourceTree = "<group>"; };
-		907D030615B86F8700575110 /* libosgdb_openflight.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgdb_openflight.a; path = "../../../../osg-ios/lib/libosgdb_openflight.a"; sourceTree = "<group>"; };
-		907D030715B86F8700575110 /* libosgdb_osg.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgdb_osg.a; path = "../../../../osg-ios/lib/libosgdb_osg.a"; sourceTree = "<group>"; };
-		907D030815B86F8700575110 /* libosgdb_osga.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgdb_osga.a; path = "../../../../osg-ios/lib/libosgdb_osga.a"; sourceTree = "<group>"; };
-		907D030915B86F8700575110 /* libosgdb_osgshadow.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgdb_osgshadow.a; path = "../../../../osg-ios/lib/libosgdb_osgshadow.a"; sourceTree = "<group>"; };
-		907D030A15B86F8700575110 /* libosgdb_osgterrain.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgdb_osgterrain.a; path = "../../../../osg-ios/lib/libosgdb_osgterrain.a"; sourceTree = "<group>"; };
-		907D030B15B86F8700575110 /* libosgdb_osgtgz.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgdb_osgtgz.a; path = "../../../../osg-ios/lib/libosgdb_osgtgz.a"; sourceTree = "<group>"; };
-		907D030C15B86F8700575110 /* libosgdb_osgviewer.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgdb_osgviewer.a; path = "../../../../osg-ios/lib/libosgdb_osgviewer.a"; sourceTree = "<group>"; };
-		907D030D15B86F8700575110 /* libosgdb_p3d.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgdb_p3d.a; path = "../../../../osg-ios/lib/libosgdb_p3d.a"; sourceTree = "<group>"; };
-		907D030E15B86F8700575110 /* libosgdb_pic.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgdb_pic.a; path = "../../../../osg-ios/lib/libosgdb_pic.a"; sourceTree = "<group>"; };
-		907D030F15B86F8700575110 /* libosgdb_ply.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgdb_ply.a; path = "../../../../osg-ios/lib/libosgdb_ply.a"; sourceTree = "<group>"; };
-		907D031015B86F8700575110 /* libosgdb_pnm.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgdb_pnm.a; path = "../../../../osg-ios/lib/libosgdb_pnm.a"; sourceTree = "<group>"; };
-		907D031115B86F8700575110 /* libosgdb_pov.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgdb_pov.a; path = "../../../../osg-ios/lib/libosgdb_pov.a"; sourceTree = "<group>"; };
-		907D031215B86F8700575110 /* libosgdb_pvr.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgdb_pvr.a; path = "../../../../osg-ios/lib/libosgdb_pvr.a"; sourceTree = "<group>"; };
-		907D031315B86F8700575110 /* libosgdb_revisions.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgdb_revisions.a; path = "../../../../osg-ios/lib/libosgdb_revisions.a"; sourceTree = "<group>"; };
-		907D031415B86F8700575110 /* libosgdb_rgb.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgdb_rgb.a; path = "../../../../osg-ios/lib/libosgdb_rgb.a"; sourceTree = "<group>"; };
-		907D031515B86F8700575110 /* libosgdb_rot.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgdb_rot.a; path = "../../../../osg-ios/lib/libosgdb_rot.a"; sourceTree = "<group>"; };
-		907D031615B86F8700575110 /* libosgdb_scale.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgdb_scale.a; path = "../../../../osg-ios/lib/libosgdb_scale.a"; sourceTree = "<group>"; };
-		907D031715B86F8700575110 /* libosgdb_serializers_osg.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgdb_serializers_osg.a; path = "../../../../osg-ios/lib/libosgdb_serializers_osg.a"; sourceTree = "<group>"; };
-		907D031815B86F8700575110 /* libosgdb_serializers_osganimation.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgdb_serializers_osganimation.a; path = "../../../../osg-ios/lib/libosgdb_serializers_osganimation.a"; sourceTree = "<group>"; };
-		907D031915B86F8700575110 /* libosgdb_serializers_osgfx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgdb_serializers_osgfx.a; path = "../../../../osg-ios/lib/libosgdb_serializers_osgfx.a"; sourceTree = "<group>"; };
-		907D031A15B86F8700575110 /* libosgdb_serializers_osgmanipulator.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgdb_serializers_osgmanipulator.a; path = "../../../../osg-ios/lib/libosgdb_serializers_osgmanipulator.a"; sourceTree = "<group>"; };
-		907D031B15B86F8700575110 /* libosgdb_serializers_osgparticle.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgdb_serializers_osgparticle.a; path = "../../../../osg-ios/lib/libosgdb_serializers_osgparticle.a"; sourceTree = "<group>"; };
-		907D031C15B86F8700575110 /* libosgdb_serializers_osgshadow.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgdb_serializers_osgshadow.a; path = "../../../../osg-ios/lib/libosgdb_serializers_osgshadow.a"; sourceTree = "<group>"; };
-		907D031D15B86F8700575110 /* libosgdb_serializers_osgsim.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgdb_serializers_osgsim.a; path = "../../../../osg-ios/lib/libosgdb_serializers_osgsim.a"; sourceTree = "<group>"; };
-		907D031E15B86F8700575110 /* libosgdb_serializers_osgterrain.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgdb_serializers_osgterrain.a; path = "../../../../osg-ios/lib/libosgdb_serializers_osgterrain.a"; sourceTree = "<group>"; };
-		907D031F15B86F8700575110 /* libosgdb_serializers_osgtext.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgdb_serializers_osgtext.a; path = "../../../../osg-ios/lib/libosgdb_serializers_osgtext.a"; sourceTree = "<group>"; };
-		907D032015B86F8700575110 /* libosgdb_serializers_osgvolume.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgdb_serializers_osgvolume.a; path = "../../../../osg-ios/lib/libosgdb_serializers_osgvolume.a"; sourceTree = "<group>"; };
-		907D032115B86F8700575110 /* libosgdb_shp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgdb_shp.a; path = "../../../../osg-ios/lib/libosgdb_shp.a"; sourceTree = "<group>"; };
-		907D032215B86F8700575110 /* libosgdb_stl.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgdb_stl.a; path = "../../../../osg-ios/lib/libosgdb_stl.a"; sourceTree = "<group>"; };
-		907D032315B86F8700575110 /* libosgdb_tga.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgdb_tga.a; path = "../../../../osg-ios/lib/libosgdb_tga.a"; sourceTree = "<group>"; };
-		907D032415B86F8700575110 /* libosgdb_tgz.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgdb_tgz.a; path = "../../../../osg-ios/lib/libosgdb_tgz.a"; sourceTree = "<group>"; };
-		907D032515B86F8700575110 /* libosgdb_trans.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgdb_trans.a; path = "../../../../osg-ios/lib/libosgdb_trans.a"; sourceTree = "<group>"; };
-		907D032615B86F8700575110 /* libosgdb_txf.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgdb_txf.a; path = "../../../../osg-ios/lib/libosgdb_txf.a"; sourceTree = "<group>"; };
-		907D032715B86F8700575110 /* libosgdb_txp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgdb_txp.a; path = "../../../../osg-ios/lib/libosgdb_txp.a"; sourceTree = "<group>"; };
-		907D032815B86F8700575110 /* libosgdb_vtf.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgdb_vtf.a; path = "../../../../osg-ios/lib/libosgdb_vtf.a"; sourceTree = "<group>"; };
-		907D032915B86F8700575110 /* libosgdb_x.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgdb_x.a; path = "../../../../osg-ios/lib/libosgdb_x.a"; sourceTree = "<group>"; };
-		907D032A15B86F8700575110 /* libosgdb_zip.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgdb_zip.a; path = "../../../../osg-ios/lib/libosgdb_zip.a"; sourceTree = "<group>"; };
-		907D032B15B86F8700575110 /* libosgDB.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgDB.a; path = "../../../../osg-ios/lib/libosgDB.a"; sourceTree = "<group>"; };
-		907D032C15B86F8700575110 /* libosgFX.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgFX.a; path = "../../../../osg-ios/lib/libosgFX.a"; sourceTree = "<group>"; };
-		907D032D15B86F8700575110 /* libosgGA.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgGA.a; path = "../../../../osg-ios/lib/libosgGA.a"; sourceTree = "<group>"; };
-		907D032E15B86F8700575110 /* libosgManipulator.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgManipulator.a; path = "../../../../osg-ios/lib/libosgManipulator.a"; sourceTree = "<group>"; };
-		907D032F15B86F8700575110 /* libosgParticle.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgParticle.a; path = "../../../../osg-ios/lib/libosgParticle.a"; sourceTree = "<group>"; };
-		907D033015B86F8700575110 /* libosgPresentation.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgPresentation.a; path = "../../../../osg-ios/lib/libosgPresentation.a"; sourceTree = "<group>"; };
-		907D033115B86F8700575110 /* libosgShadow.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgShadow.a; path = "../../../../osg-ios/lib/libosgShadow.a"; sourceTree = "<group>"; };
-		907D033215B86F8700575110 /* libosgSim.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgSim.a; path = "../../../../osg-ios/lib/libosgSim.a"; sourceTree = "<group>"; };
-		907D033315B86F8700575110 /* libosgTerrain.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgTerrain.a; path = "../../../../osg-ios/lib/libosgTerrain.a"; sourceTree = "<group>"; };
-		907D033415B86F8700575110 /* libosgText.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgText.a; path = "../../../../osg-ios/lib/libosgText.a"; sourceTree = "<group>"; };
-		907D033515B86F8700575110 /* libosgUtil.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgUtil.a; path = "../../../../osg-ios/lib/libosgUtil.a"; sourceTree = "<group>"; };
-		907D033615B86F8700575110 /* libosgViewer.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgViewer.a; path = "../../../../osg-ios/lib/libosgViewer.a"; sourceTree = "<group>"; };
-		907D033715B86F8700575110 /* libosgVolume.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgVolume.a; path = "../../../../osg-ios/lib/libosgVolume.a"; sourceTree = "<group>"; };
-		907D033815B86F8700575110 /* libosgWidget.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgWidget.a; path = "../../../../osg-ios/lib/libosgWidget.a"; sourceTree = "<group>"; };
-		907D039715B86F9E00575110 /* libosgdb_kml.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgdb_kml.a; path = ../../../lib/Release/libosgdb_kml.a; sourceTree = "<group>"; };
-		907D039815B86F9E00575110 /* libosgdb_osgearth_feature_wfs.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgdb_osgearth_feature_wfs.a; path = ../../../lib/Release/libosgdb_osgearth_feature_wfs.a; sourceTree = "<group>"; };
-		907D039915B86F9E00575110 /* libosgdb_osgearth_feature_tfs.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgdb_osgearth_feature_tfs.a; path = ../../../lib/Release/libosgdb_osgearth_feature_tfs.a; sourceTree = "<group>"; };
-		907D039A15B86F9E00575110 /* libosgdb_osgearth_tms.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgdb_osgearth_tms.a; path = ../../../lib/Release/libosgdb_osgearth_tms.a; sourceTree = "<group>"; };
-		907D039B15B86F9E00575110 /* libosgdb_osgearth_wms.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgdb_osgearth_wms.a; path = ../../../lib/Release/libosgdb_osgearth_wms.a; sourceTree = "<group>"; };
-		907D039C15B86F9E00575110 /* libosgdb_osgearth_label_overlay.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgdb_osgearth_label_overlay.a; path = ../../../lib/Release/libosgdb_osgearth_label_overlay.a; sourceTree = "<group>"; };
-		907D039D15B86F9E00575110 /* libosgdb_osgearth_xyz.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgdb_osgearth_xyz.a; path = ../../../lib/Release/libosgdb_osgearth_xyz.a; sourceTree = "<group>"; };
-		907D039E15B86F9E00575110 /* libosgEarthUtil.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgEarthUtil.a; path = ../../../lib/Release/libosgEarthUtil.a; sourceTree = "<group>"; };
-		907D039F15B86F9E00575110 /* libosgdb_osgearth_label_annotation.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgdb_osgearth_label_annotation.a; path = ../../../lib/Release/libosgdb_osgearth_label_annotation.a; sourceTree = "<group>"; };
-		907D03A015B86F9E00575110 /* libosgdb_osgearth_mask_feature.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgdb_osgearth_mask_feature.a; path = ../../../lib/Release/libosgdb_osgearth_mask_feature.a; sourceTree = "<group>"; };
-		907D03A115B86F9E00575110 /* libosgdb_osgearth_model_feature_geom.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgdb_osgearth_model_feature_geom.a; path = ../../../lib/Release/libosgdb_osgearth_model_feature_geom.a; sourceTree = "<group>"; };
-		907D03A215B86F9E00575110 /* libosgEarthAnnotation.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgEarthAnnotation.a; path = ../../../lib/Release/libosgEarthAnnotation.a; sourceTree = "<group>"; };
-		907D03A315B86F9E00575110 /* libosgdb_osgearth_agglite.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgdb_osgearth_agglite.a; path = ../../../lib/Release/libosgdb_osgearth_agglite.a; sourceTree = "<group>"; };
-		907D03A415B86F9E00575110 /* libosgdb_osgearth_feature_ogr.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgdb_osgearth_feature_ogr.a; path = ../../../lib/Release/libosgdb_osgearth_feature_ogr.a; sourceTree = "<group>"; };
-		907D03A515B86F9E00575110 /* libosgdb_osgearth_model_feature_stencil.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgdb_osgearth_model_feature_stencil.a; path = ../../../lib/Release/libosgdb_osgearth_model_feature_stencil.a; sourceTree = "<group>"; };
-		907D03A615B86F9E00575110 /* libosgdb_osgearth_vdatum_egm2008.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgdb_osgearth_vdatum_egm2008.a; path = ../../../lib/Release/libosgdb_osgearth_vdatum_egm2008.a; sourceTree = "<group>"; };
-		907D03A715B86F9E00575110 /* libosgdb_osgearth_model_simple.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgdb_osgearth_model_simple.a; path = ../../../lib/Release/libosgdb_osgearth_model_simple.a; sourceTree = "<group>"; };
-		907D03A815B86F9E00575110 /* libosgdb_osgearth_engine_osgterrain.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgdb_osgearth_engine_osgterrain.a; path = ../../../lib/Release/libosgdb_osgearth_engine_osgterrain.a; sourceTree = "<group>"; };
-		907D03A915B86F9E00575110 /* libosgEarthFeatures.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgEarthFeatures.a; path = ../../../lib/Release/libosgEarthFeatures.a; sourceTree = "<group>"; };
-		907D03AA15B86F9E00575110 /* libosgdb_osgearth_vdatum_egm96.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgdb_osgearth_vdatum_egm96.a; path = ../../../lib/Release/libosgdb_osgearth_vdatum_egm96.a; sourceTree = "<group>"; };
-		907D03AB15B86F9E00575110 /* libosgdb_osgearth_ocean_surface.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgdb_osgearth_ocean_surface.a; path = ../../../lib/Release/libosgdb_osgearth_ocean_surface.a; sourceTree = "<group>"; };
-		907D03AC15B86F9E00575110 /* libosgdb_osgearth_debug.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgdb_osgearth_debug.a; path = ../../../lib/Release/libosgdb_osgearth_debug.a; sourceTree = "<group>"; };
-		907D03AD15B86F9E00575110 /* libosgdb_osgearth_mbtiles.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgdb_osgearth_mbtiles.a; path = ../../../lib/Release/libosgdb_osgearth_mbtiles.a; sourceTree = "<group>"; };
-		907D03AE15B86F9E00575110 /* libosgdb_osgearth_vdatum_egm84.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgdb_osgearth_vdatum_egm84.a; path = ../../../lib/Release/libosgdb_osgearth_vdatum_egm84.a; sourceTree = "<group>"; };
-		907D03AF15B86F9E00575110 /* libosgdb_osgearth_tileservice.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgdb_osgearth_tileservice.a; path = ../../../lib/Release/libosgdb_osgearth_tileservice.a; sourceTree = "<group>"; };
-		907D03B015B86F9E00575110 /* libosgdb_osgearth_yahoo.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgdb_osgearth_yahoo.a; path = ../../../lib/Release/libosgdb_osgearth_yahoo.a; sourceTree = "<group>"; };
-		907D03B115B86F9E00575110 /* libosgdb_osgearth_arcgis_map_cache.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgdb_osgearth_arcgis_map_cache.a; path = ../../../lib/Release/libosgdb_osgearth_arcgis_map_cache.a; sourceTree = "<group>"; };
-		907D03B215B86F9E00575110 /* libosgdb_osgearth_tilecache.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgdb_osgearth_tilecache.a; path = ../../../lib/Release/libosgdb_osgearth_tilecache.a; sourceTree = "<group>"; };
-		907D03B315B86F9E00575110 /* libosgdb_osgearth_wcs.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgdb_osgearth_wcs.a; path = ../../../lib/Release/libosgdb_osgearth_wcs.a; sourceTree = "<group>"; };
-		907D03B415B86F9E00575110 /* libosgEarthSymbology.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgEarthSymbology.a; path = ../../../lib/Release/libosgEarthSymbology.a; sourceTree = "<group>"; };
-		907D03B515B86F9E00575110 /* libosgdb_osgearth_gdal.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgdb_osgearth_gdal.a; path = ../../../lib/Release/libosgdb_osgearth_gdal.a; sourceTree = "<group>"; };
-		907D03B615B86F9E00575110 /* libosgdb_osgearth_refresh.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgdb_osgearth_refresh.a; path = ../../../lib/Release/libosgdb_osgearth_refresh.a; sourceTree = "<group>"; };
-		907D03B715B86F9E00575110 /* libosgdb_osgearth_vpb.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgdb_osgearth_vpb.a; path = ../../../lib/Release/libosgdb_osgearth_vpb.a; sourceTree = "<group>"; };
-		907D03B815B86F9E00575110 /* libosgdb_earth.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgdb_earth.a; path = ../../../lib/Release/libosgdb_earth.a; sourceTree = "<group>"; };
-		907D03B915B86F9E00575110 /* libosgdb_osgearth_cache_filesystem.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgdb_osgearth_cache_filesystem.a; path = ../../../lib/Release/libosgdb_osgearth_cache_filesystem.a; sourceTree = "<group>"; };
-		907D03BA15B86F9E00575110 /* libosgdb_osgearth_arcgis.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgdb_osgearth_arcgis.a; path = ../../../lib/Release/libosgdb_osgearth_arcgis.a; sourceTree = "<group>"; };
-		907D03BB15B86F9E00575110 /* libosgdb_osgearth_osg.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgdb_osgearth_osg.a; path = ../../../lib/Release/libosgdb_osgearth_osg.a; sourceTree = "<group>"; };
-		907D03BC15B86F9E00575110 /* libosgEarth.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgEarth.a; path = ../../../lib/Release/libosgEarth.a; sourceTree = "<group>"; };
-		907D03F915B8C31A00575110 /* libsqlite3.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libsqlite3.dylib; path = usr/lib/libsqlite3.dylib; sourceTree = SDKROOT; };
-		907D0A8D15B8CEBE00575110 /* libGEOS_3.2.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libGEOS_3.2.a; path = "../../../../3rdParty/geos-ios-device/lib/libGEOS_3.2.a"; sourceTree = "<group>"; };
-		907D0A8F15B8DDAA00575110 /* GLES2ShaderGenVisitor.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = GLES2ShaderGenVisitor.cpp; path = ShaderGen/GLES2ShaderGenVisitor.cpp; sourceTree = "<group>"; };
-		907D0A9015B8DDAA00575110 /* GLES2ShaderGenVisitor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = GLES2ShaderGenVisitor.h; path = ShaderGen/GLES2ShaderGenVisitor.h; sourceTree = "<group>"; };
-		90A0DD6C15B7BAF9004FACEE /* libFreeType_iphone_universal.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libFreeType_iphone_universal.a; sourceTree = "<group>"; };
-		90A0DD6E15B7BB50004FACEE /* libproj.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libproj.a; sourceTree = "<group>"; };
-		90A0DD7015B7BB64004FACEE /* libgdal.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgdal.a; sourceTree = "<group>"; };
-		90A0DD7515B7BBA4004FACEE /* libcurl.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libcurl.a; sourceTree = "<group>"; };
-		90B8676315C8894900F5CDC3 /* StartViewerController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = StartViewerController.h; sourceTree = "<group>"; };
-		90B8676415C8894900F5CDC3 /* StartViewerController.m */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.objcpp; fileEncoding = 4; path = StartViewerController.m; sourceTree = "<group>"; };
-		90B8676515C8894900F5CDC3 /* StartViewerController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = StartViewerController.xib; sourceTree = "<group>"; };
-		90DABDDB15CEFF9700D0F609 /* moon_1024x512.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; name = moon_1024x512.jpg; path = ../../../../data/moon_1024x512.jpg; sourceTree = "<group>"; };
-/* End PBXFileReference section */
-
-/* Begin PBXFrameworksBuildPhase section */
-		9051FFF715B1EDFD00D9ABD3 /* Frameworks */ = {
-			isa = PBXFrameworksBuildPhase;
-			buildActionMask = 2147483647;
-			files = (
-				1F8566AC17666EC1005BCD2B /* libicucore.dylib in Frameworks */,
-				1F8566AA17666E67005BCD2B /* libJavaScriptCore.a in Frameworks */,
-				1F8566A817666C29005BCD2B /* libosgdb_osgearth_scriptengine_javascriptcore.a in Frameworks */,
-				1F823FFE173C1D20003B519D /* libosgdb_osgearth_engine_mp.a in Frameworks */,
-				904E22771691CC42002D66FD /* Accelerate.framework in Frameworks */,
-				904E22781691CC42002D66FD /* MobileCoreServices.framework in Frameworks */,
-				907D037E15B86F8700575110 /* libosgdb_shp.a in Frameworks */,
-				9048FB2415FA9DE50012C900 /* libosgdb_tiff.a in Frameworks */,
-				907D03FA15B8C31A00575110 /* libsqlite3.dylib in Frameworks */,
-				90A0DD7615B7BBA4004FACEE /* libcurl.a in Frameworks */,
-				90A0DD7115B7BB64004FACEE /* libgdal.a in Frameworks */,
-				90A0DD6F15B7BB50004FACEE /* libproj.a in Frameworks */,
-				90A0DD6D15B7BAF9004FACEE /* libFreeType_iphone_universal.a in Frameworks */,
-				905100D115B2185000D9ABD3 /* libiconv.2.4.0.dylib in Frameworks */,
-				905100CF15B217B500D9ABD3 /* libz.1.1.3.dylib in Frameworks */,
-				905100CD15B217A800D9ABD3 /* libc++.dylib in Frameworks */,
-				905100CB15B2101E00D9ABD3 /* QuartzCore.framework in Frameworks */,
-				905100C515B20AD100D9ABD3 /* CoreImage.framework in Frameworks */,
-				905100C315B20AC600D9ABD3 /* ImageIO.framework in Frameworks */,
-				9051FFFF15B1EDFD00D9ABD3 /* UIKit.framework in Frameworks */,
-				9051000115B1EDFD00D9ABD3 /* Foundation.framework in Frameworks */,
-				9051000315B1EDFD00D9ABD3 /* CoreGraphics.framework in Frameworks */,
-				9051000715B1EDFD00D9ABD3 /* OpenGLES.framework in Frameworks */,
-				907D033915B86F8700575110 /* libOpenThreads.a in Frameworks */,
-				907D033A15B86F8700575110 /* libosg.a in Frameworks */,
-				907D033B15B86F8700575110 /* libosgAnimation.a in Frameworks */,
-				907D033C15B86F8700575110 /* libosgdb_3dc.a in Frameworks */,
-				907D033D15B86F8700575110 /* libosgdb_3ds.a in Frameworks */,
-				907D033E15B86F8700575110 /* libosgdb_ac.a in Frameworks */,
-				907D033F15B86F8700575110 /* libosgdb_bmp.a in Frameworks */,
-				907D034015B86F8700575110 /* libosgdb_bsp.a in Frameworks */,
-				907D034115B86F8700575110 /* libosgdb_bvh.a in Frameworks */,
-				907D034215B86F8700575110 /* libosgdb_cfg.a in Frameworks */,
-				907D034315B86F8700575110 /* libosgdb_curl.a in Frameworks */,
-				907D034415B86F8700575110 /* libosgdb_dds.a in Frameworks */,
-				907D034515B86F8700575110 /* libosgdb_deprecated_osg.a in Frameworks */,
-				907D034615B86F8700575110 /* libosgdb_deprecated_osganimation.a in Frameworks */,
-				907D034715B86F8700575110 /* libosgdb_deprecated_osgfx.a in Frameworks */,
-				907D034815B86F8700575110 /* libosgdb_deprecated_osgparticle.a in Frameworks */,
-				907D034915B86F8700575110 /* libosgdb_deprecated_osgshadow.a in Frameworks */,
-				907D034A15B86F8700575110 /* libosgdb_deprecated_osgsim.a in Frameworks */,
-				907D034B15B86F8700575110 /* libosgdb_deprecated_osgterrain.a in Frameworks */,
-				907D034C15B86F8700575110 /* libosgdb_deprecated_osgtext.a in Frameworks */,
-				907D034D15B86F8700575110 /* libosgdb_deprecated_osgviewer.a in Frameworks */,
-				907D034E15B86F8700575110 /* libosgdb_deprecated_osgvolume.a in Frameworks */,
-				907D034F15B86F8700575110 /* libosgdb_deprecated_osgwidget.a in Frameworks */,
-				907D035015B86F8700575110 /* libosgdb_dot.a in Frameworks */,
-				907D035115B86F8700575110 /* libosgdb_dw.a in Frameworks */,
-				907D035215B86F8700575110 /* libosgdb_dxf.a in Frameworks */,
-				907D035315B86F8700575110 /* libosgdb_freetype.a in Frameworks */,
-				907D035415B86F8700575110 /* libosgdb_gdal.a in Frameworks */,
-				907D035515B86F8700575110 /* libosgdb_geo.a in Frameworks */,
-				907D035615B86F8700575110 /* libosgdb_glsl.a in Frameworks */,
-				907D035715B86F8700575110 /* libosgdb_gz.a in Frameworks */,
-				907D035815B86F8700575110 /* libosgdb_hdr.a in Frameworks */,
-				907D035915B86F8700575110 /* libosgdb_imageio.a in Frameworks */,
-				907D035A15B86F8700575110 /* libosgdb_ive.a in Frameworks */,
-				907D035B15B86F8700575110 /* libosgdb_logo.a in Frameworks */,
-				907D035C15B86F8700575110 /* libosgdb_lwo.a in Frameworks */,
-				907D035D15B86F8700575110 /* libosgdb_lws.a in Frameworks */,
-				907D035E15B86F8700575110 /* libosgdb_md2.a in Frameworks */,
-				907D035F15B86F8700575110 /* libosgdb_mdl.a in Frameworks */,
-				907D036015B86F8700575110 /* libosgdb_normals.a in Frameworks */,
-				907D036115B86F8700575110 /* libosgdb_obj.a in Frameworks */,
-				907D036215B86F8700575110 /* libosgdb_ogr.a in Frameworks */,
-				907D036315B86F8700575110 /* libosgdb_openflight.a in Frameworks */,
-				907D036415B86F8700575110 /* libosgdb_osg.a in Frameworks */,
-				907D036515B86F8700575110 /* libosgdb_osga.a in Frameworks */,
-				907D036615B86F8700575110 /* libosgdb_osgshadow.a in Frameworks */,
-				907D036715B86F8700575110 /* libosgdb_osgterrain.a in Frameworks */,
-				907D036815B86F8700575110 /* libosgdb_osgtgz.a in Frameworks */,
-				907D036915B86F8700575110 /* libosgdb_osgviewer.a in Frameworks */,
-				907D036A15B86F8700575110 /* libosgdb_p3d.a in Frameworks */,
-				907D036B15B86F8700575110 /* libosgdb_pic.a in Frameworks */,
-				907D036C15B86F8700575110 /* libosgdb_ply.a in Frameworks */,
-				907D036D15B86F8700575110 /* libosgdb_pnm.a in Frameworks */,
-				907D036E15B86F8700575110 /* libosgdb_pov.a in Frameworks */,
-				907D036F15B86F8700575110 /* libosgdb_pvr.a in Frameworks */,
-				907D037015B86F8700575110 /* libosgdb_revisions.a in Frameworks */,
-				907D037115B86F8700575110 /* libosgdb_rgb.a in Frameworks */,
-				907D037215B86F8700575110 /* libosgdb_rot.a in Frameworks */,
-				907D037315B86F8700575110 /* libosgdb_scale.a in Frameworks */,
-				907D037415B86F8700575110 /* libosgdb_serializers_osg.a in Frameworks */,
-				907D037515B86F8700575110 /* libosgdb_serializers_osganimation.a in Frameworks */,
-				907D037615B86F8700575110 /* libosgdb_serializers_osgfx.a in Frameworks */,
-				907D037715B86F8700575110 /* libosgdb_serializers_osgmanipulator.a in Frameworks */,
-				907D037815B86F8700575110 /* libosgdb_serializers_osgparticle.a in Frameworks */,
-				907D037915B86F8700575110 /* libosgdb_serializers_osgshadow.a in Frameworks */,
-				907D037A15B86F8700575110 /* libosgdb_serializers_osgsim.a in Frameworks */,
-				907D037B15B86F8700575110 /* libosgdb_serializers_osgterrain.a in Frameworks */,
-				907D037C15B86F8700575110 /* libosgdb_serializers_osgtext.a in Frameworks */,
-				907D037D15B86F8700575110 /* libosgdb_serializers_osgvolume.a in Frameworks */,
-				907D037F15B86F8700575110 /* libosgdb_stl.a in Frameworks */,
-				907D038015B86F8700575110 /* libosgdb_tga.a in Frameworks */,
-				907D038115B86F8700575110 /* libosgdb_tgz.a in Frameworks */,
-				907D038215B86F8700575110 /* libosgdb_trans.a in Frameworks */,
-				907D038315B86F8700575110 /* libosgdb_txf.a in Frameworks */,
-				907D038415B86F8700575110 /* libosgdb_txp.a in Frameworks */,
-				907D038515B86F8700575110 /* libosgdb_vtf.a in Frameworks */,
-				907D038615B86F8700575110 /* libosgdb_x.a in Frameworks */,
-				907D038715B86F8700575110 /* libosgdb_zip.a in Frameworks */,
-				907D038815B86F8700575110 /* libosgDB.a in Frameworks */,
-				907D038915B86F8700575110 /* libosgFX.a in Frameworks */,
-				907D038A15B86F8700575110 /* libosgGA.a in Frameworks */,
-				907D038B15B86F8700575110 /* libosgManipulator.a in Frameworks */,
-				907D038C15B86F8700575110 /* libosgParticle.a in Frameworks */,
-				907D038D15B86F8700575110 /* libosgPresentation.a in Frameworks */,
-				907D038E15B86F8700575110 /* libosgShadow.a in Frameworks */,
-				907D038F15B86F8700575110 /* libosgSim.a in Frameworks */,
-				907D039015B86F8700575110 /* libosgTerrain.a in Frameworks */,
-				907D039115B86F8700575110 /* libosgText.a in Frameworks */,
-				907D039215B86F8700575110 /* libosgUtil.a in Frameworks */,
-				907D039315B86F8700575110 /* libosgViewer.a in Frameworks */,
-				907D039415B86F8700575110 /* libosgVolume.a in Frameworks */,
-				907D039515B86F8700575110 /* libosgWidget.a in Frameworks */,
-				907D03BD15B86F9E00575110 /* libosgdb_kml.a in Frameworks */,
-				907D03BE15B86F9E00575110 /* libosgdb_osgearth_feature_wfs.a in Frameworks */,
-				907D03BF15B86F9E00575110 /* libosgdb_osgearth_feature_tfs.a in Frameworks */,
-				907D03C015B86F9E00575110 /* libosgdb_osgearth_tms.a in Frameworks */,
-				907D03C115B86F9E00575110 /* libosgdb_osgearth_wms.a in Frameworks */,
-				907D03C215B86F9E00575110 /* libosgdb_osgearth_label_overlay.a in Frameworks */,
-				907D03C315B86F9E00575110 /* libosgdb_osgearth_xyz.a in Frameworks */,
-				907D03C415B86F9E00575110 /* libosgEarthUtil.a in Frameworks */,
-				907D03C515B86F9E00575110 /* libosgdb_osgearth_label_annotation.a in Frameworks */,
-				907D03C615B86F9E00575110 /* libosgdb_osgearth_mask_feature.a in Frameworks */,
-				907D03C715B86F9E00575110 /* libosgdb_osgearth_model_feature_geom.a in Frameworks */,
-				907D03C815B86F9E00575110 /* libosgEarthAnnotation.a in Frameworks */,
-				907D03C915B86F9E00575110 /* libosgdb_osgearth_agglite.a in Frameworks */,
-				907D03CA15B86F9E00575110 /* libosgdb_osgearth_feature_ogr.a in Frameworks */,
-				907D03CB15B86F9E00575110 /* libosgdb_osgearth_model_feature_stencil.a in Frameworks */,
-				907D03CC15B86F9E00575110 /* libosgdb_osgearth_vdatum_egm2008.a in Frameworks */,
-				907D03CD15B86F9E00575110 /* libosgdb_osgearth_model_simple.a in Frameworks */,
-				907D03CE15B86F9E00575110 /* libosgdb_osgearth_engine_osgterrain.a in Frameworks */,
-				907D03CF15B86F9E00575110 /* libosgEarthFeatures.a in Frameworks */,
-				907D03D015B86F9E00575110 /* libosgdb_osgearth_vdatum_egm96.a in Frameworks */,
-				907D03D115B86F9E00575110 /* libosgdb_osgearth_ocean_surface.a in Frameworks */,
-				907D03D215B86F9E00575110 /* libosgdb_osgearth_debug.a in Frameworks */,
-				907D03D315B86F9E00575110 /* libosgdb_osgearth_mbtiles.a in Frameworks */,
-				907D03D415B86F9E00575110 /* libosgdb_osgearth_vdatum_egm84.a in Frameworks */,
-				907D03D515B86F9E00575110 /* libosgdb_osgearth_tileservice.a in Frameworks */,
-				907D03D615B86F9E00575110 /* libosgdb_osgearth_yahoo.a in Frameworks */,
-				907D03D715B86F9E00575110 /* libosgdb_osgearth_arcgis_map_cache.a in Frameworks */,
-				907D03D815B86F9E00575110 /* libosgdb_osgearth_tilecache.a in Frameworks */,
-				907D03D915B86F9E00575110 /* libosgdb_osgearth_wcs.a in Frameworks */,
-				907D03DA15B86F9E00575110 /* libosgEarthSymbology.a in Frameworks */,
-				907D03DB15B86F9F00575110 /* libosgdb_osgearth_gdal.a in Frameworks */,
-				907D03DC15B86F9F00575110 /* libosgdb_osgearth_refresh.a in Frameworks */,
-				907D03DD15B86F9F00575110 /* libosgdb_osgearth_vpb.a in Frameworks */,
-				907D03DE15B86F9F00575110 /* libosgdb_earth.a in Frameworks */,
-				907D03DF15B86F9F00575110 /* libosgdb_osgearth_cache_filesystem.a in Frameworks */,
-				907D03E015B86F9F00575110 /* libosgdb_osgearth_arcgis.a in Frameworks */,
-				907D03E115B86F9F00575110 /* libosgdb_osgearth_osg.a in Frameworks */,
-				907D03E215B86F9F00575110 /* libosgEarth.a in Frameworks */,
-				907D0A8E15B8CEBE00575110 /* libGEOS_3.2.a in Frameworks */,
-				90283E5D15C7091A00620EEF /* libosgdb_osgearth_engine_quadtree.a in Frameworks */,
-			);
-			runOnlyForDeploymentPostprocessing = 0;
-		};
-/* End PBXFrameworksBuildPhase section */
-
-/* Begin PBXGroup section */
-		9051000815B1EDFD00D9ABD3 /* osgEarthViewerIOS */ = {
-			isa = PBXGroup;
-			children = (
-				90B8676915C8895100F5CDC3 /* StartView */,
-				908C793D15BB0B4E001CFA5E /* MultiTouchManipulator */,
-				907D0A9215B8DDAF00575110 /* ShaderGen */,
-				905100C615B20B1D00D9ABD3 /* osgPlugins.h */,
-				9051001115B1EDFD00D9ABD3 /* AppDelegate.h */,
-				9051001215B1EDFD00D9ABD3 /* AppDelegate.m */,
-				9051001815B1EDFD00D9ABD3 /* ViewController.h */,
-				9051001915B1EDFD00D9ABD3 /* ViewController.m */,
-				9051001B15B1EDFD00D9ABD3 /* ViewController_iPhone.xib */,
-				9051001E15B1EDFD00D9ABD3 /* ViewController_iPad.xib */,
-				9051000915B1EDFD00D9ABD3 /* Supporting Files */,
-			);
-			path = osgEarthViewerIOS;
-			sourceTree = "<group>";
-		};
-		9051000915B1EDFD00D9ABD3 /* Supporting Files */ = {
-			isa = PBXGroup;
-			children = (
-				9051000A15B1EDFD00D9ABD3 /* osgEarthViewerIOS-Info.plist */,
-				9051000B15B1EDFD00D9ABD3 /* InfoPlist.strings */,
-				9051000E15B1EDFD00D9ABD3 /* main.m */,
-				9051001015B1EDFD00D9ABD3 /* osgEarthViewerIOS-Prefix.pch */,
-			);
-			name = "Supporting Files";
-			sourceTree = "<group>";
-		};
-		905100D215B21FFD00D9ABD3 /* Resources */ = {
-			isa = PBXGroup;
-			children = (
-				90DABDDB15CEFF9700D0F609 /* moon_1024x512.jpg */,
-				1FA5C121177B608000A161FF /* gdal_data */,
-				90283E5415C6FB3E00620EEF /* tests */,
-				90283E5515C6FB3E00620EEF /* data */,
-			);
-			name = Resources;
-			path = osgEarthViewerIOS;
-			sourceTree = "<group>";
-		};
-		9051FFEF15B1EDFD00D9ABD3 = {
-			isa = PBXGroup;
-			children = (
-				1F8566AB17666EC1005BCD2B /* libicucore.dylib */,
-				1F8566A917666E67005BCD2B /* libJavaScriptCore.a */,
-				1F8566A717666C29005BCD2B /* libosgdb_osgearth_scriptengine_javascriptcore.a */,
-				1F823FFD173C1D20003B519D /* libosgdb_osgearth_engine_mp.a */,
-				9051000815B1EDFD00D9ABD3 /* osgEarthViewerIOS */,
-				905100D215B21FFD00D9ABD3 /* Resources */,
-				90A0DD7715B7BBAB004FACEE /* Libs */,
-				9051FFFB15B1EDFD00D9ABD3 /* Products */,
-			);
-			sourceTree = "<group>";
-		};
-		9051FFFB15B1EDFD00D9ABD3 /* Products */ = {
-			isa = PBXGroup;
-			children = (
-				9051FFFA15B1EDFD00D9ABD3 /* osgEarth.app */,
-			);
-			name = Products;
-			sourceTree = "<group>";
-		};
-		9051FFFD15B1EDFD00D9ABD3 /* Frameworks */ = {
-			isa = PBXGroup;
-			children = (
-				904E22751691CC42002D66FD /* Accelerate.framework */,
-				904E22761691CC42002D66FD /* MobileCoreServices.framework */,
-				905100CA15B2101E00D9ABD3 /* QuartzCore.framework */,
-				905100C415B20AD100D9ABD3 /* CoreImage.framework */,
-				905100C215B20AC600D9ABD3 /* ImageIO.framework */,
-				9051FFFE15B1EDFD00D9ABD3 /* UIKit.framework */,
-				9051000015B1EDFD00D9ABD3 /* Foundation.framework */,
-				9051000215B1EDFD00D9ABD3 /* CoreGraphics.framework */,
-				9051000615B1EDFD00D9ABD3 /* OpenGLES.framework */,
-			);
-			name = Frameworks;
-			sourceTree = "<group>";
-		};
-		907D02DB15B86F7100575110 /* osg */ = {
-			isa = PBXGroup;
-			children = (
-				907D02DC15B86F8700575110 /* libOpenThreads.a */,
-				907D02DD15B86F8700575110 /* libosg.a */,
-				907D02DE15B86F8700575110 /* libosgAnimation.a */,
-				907D02DF15B86F8700575110 /* libosgdb_3dc.a */,
-				907D02E015B86F8700575110 /* libosgdb_3ds.a */,
-				907D02E115B86F8700575110 /* libosgdb_ac.a */,
-				907D02E215B86F8700575110 /* libosgdb_bmp.a */,
-				907D02E315B86F8700575110 /* libosgdb_bsp.a */,
-				907D02E415B86F8700575110 /* libosgdb_bvh.a */,
-				907D02E515B86F8700575110 /* libosgdb_cfg.a */,
-				907D02E615B86F8700575110 /* libosgdb_curl.a */,
-				907D02E715B86F8700575110 /* libosgdb_dds.a */,
-				9048FB2315FA9DE50012C900 /* libosgdb_tiff.a */,
-				907D02E815B86F8700575110 /* libosgdb_deprecated_osg.a */,
-				907D02E915B86F8700575110 /* libosgdb_deprecated_osganimation.a */,
-				907D02EA15B86F8700575110 /* libosgdb_deprecated_osgfx.a */,
-				907D02EB15B86F8700575110 /* libosgdb_deprecated_osgparticle.a */,
-				907D02EC15B86F8700575110 /* libosgdb_deprecated_osgshadow.a */,
-				907D02ED15B86F8700575110 /* libosgdb_deprecated_osgsim.a */,
-				907D02EE15B86F8700575110 /* libosgdb_deprecated_osgterrain.a */,
-				907D02EF15B86F8700575110 /* libosgdb_deprecated_osgtext.a */,
-				907D02F015B86F8700575110 /* libosgdb_deprecated_osgviewer.a */,
-				907D02F115B86F8700575110 /* libosgdb_deprecated_osgvolume.a */,
-				907D02F215B86F8700575110 /* libosgdb_deprecated_osgwidget.a */,
-				907D02F315B86F8700575110 /* libosgdb_dot.a */,
-				907D02F415B86F8700575110 /* libosgdb_dw.a */,
-				907D02F515B86F8700575110 /* libosgdb_dxf.a */,
-				907D02F615B86F8700575110 /* libosgdb_freetype.a */,
-				907D02F715B86F8700575110 /* libosgdb_gdal.a */,
-				907D02F815B86F8700575110 /* libosgdb_geo.a */,
-				907D02F915B86F8700575110 /* libosgdb_glsl.a */,
-				907D02FA15B86F8700575110 /* libosgdb_gz.a */,
-				907D02FB15B86F8700575110 /* libosgdb_hdr.a */,
-				907D02FC15B86F8700575110 /* libosgdb_imageio.a */,
-				907D02FD15B86F8700575110 /* libosgdb_ive.a */,
-				907D02FE15B86F8700575110 /* libosgdb_logo.a */,
-				907D02FF15B86F8700575110 /* libosgdb_lwo.a */,
-				907D030015B86F8700575110 /* libosgdb_lws.a */,
-				907D030115B86F8700575110 /* libosgdb_md2.a */,
-				907D030215B86F8700575110 /* libosgdb_mdl.a */,
-				907D030315B86F8700575110 /* libosgdb_normals.a */,
-				907D030415B86F8700575110 /* libosgdb_obj.a */,
-				907D030515B86F8700575110 /* libosgdb_ogr.a */,
-				907D030615B86F8700575110 /* libosgdb_openflight.a */,
-				907D030715B86F8700575110 /* libosgdb_osg.a */,
-				907D030815B86F8700575110 /* libosgdb_osga.a */,
-				907D030915B86F8700575110 /* libosgdb_osgshadow.a */,
-				907D030A15B86F8700575110 /* libosgdb_osgterrain.a */,
-				907D030B15B86F8700575110 /* libosgdb_osgtgz.a */,
-				907D030C15B86F8700575110 /* libosgdb_osgviewer.a */,
-				907D030D15B86F8700575110 /* libosgdb_p3d.a */,
-				907D030E15B86F8700575110 /* libosgdb_pic.a */,
-				907D030F15B86F8700575110 /* libosgdb_ply.a */,
-				907D031015B86F8700575110 /* libosgdb_pnm.a */,
-				907D031115B86F8700575110 /* libosgdb_pov.a */,
-				907D031215B86F8700575110 /* libosgdb_pvr.a */,
-				907D031315B86F8700575110 /* libosgdb_revisions.a */,
-				907D031415B86F8700575110 /* libosgdb_rgb.a */,
-				907D031515B86F8700575110 /* libosgdb_rot.a */,
-				907D031615B86F8700575110 /* libosgdb_scale.a */,
-				907D031715B86F8700575110 /* libosgdb_serializers_osg.a */,
-				907D031815B86F8700575110 /* libosgdb_serializers_osganimation.a */,
-				907D031915B86F8700575110 /* libosgdb_serializers_osgfx.a */,
-				907D031A15B86F8700575110 /* libosgdb_serializers_osgmanipulator.a */,
-				907D031B15B86F8700575110 /* libosgdb_serializers_osgparticle.a */,
-				907D031C15B86F8700575110 /* libosgdb_serializers_osgshadow.a */,
-				907D031D15B86F8700575110 /* libosgdb_serializers_osgsim.a */,
-				907D031E15B86F8700575110 /* libosgdb_serializers_osgterrain.a */,
-				907D031F15B86F8700575110 /* libosgdb_serializers_osgtext.a */,
-				907D032015B86F8700575110 /* libosgdb_serializers_osgvolume.a */,
-				907D032115B86F8700575110 /* libosgdb_shp.a */,
-				907D032215B86F8700575110 /* libosgdb_stl.a */,
-				907D032315B86F8700575110 /* libosgdb_tga.a */,
-				907D032415B86F8700575110 /* libosgdb_tgz.a */,
-				907D032515B86F8700575110 /* libosgdb_trans.a */,
-				907D032615B86F8700575110 /* libosgdb_txf.a */,
-				907D032715B86F8700575110 /* libosgdb_txp.a */,
-				907D032815B86F8700575110 /* libosgdb_vtf.a */,
-				907D032915B86F8700575110 /* libosgdb_x.a */,
-				907D032A15B86F8700575110 /* libosgdb_zip.a */,
-				907D032B15B86F8700575110 /* libosgDB.a */,
-				907D032C15B86F8700575110 /* libosgFX.a */,
-				907D032D15B86F8700575110 /* libosgGA.a */,
-				907D032E15B86F8700575110 /* libosgManipulator.a */,
-				907D032F15B86F8700575110 /* libosgParticle.a */,
-				907D033015B86F8700575110 /* libosgPresentation.a */,
-				907D033115B86F8700575110 /* libosgShadow.a */,
-				907D033215B86F8700575110 /* libosgSim.a */,
-				907D033315B86F8700575110 /* libosgTerrain.a */,
-				907D033415B86F8700575110 /* libosgText.a */,
-				907D033515B86F8700575110 /* libosgUtil.a */,
-				907D033615B86F8700575110 /* libosgViewer.a */,
-				907D033715B86F8700575110 /* libosgVolume.a */,
-				907D033815B86F8700575110 /* libosgWidget.a */,
-			);
-			name = osg;
-			sourceTree = "<group>";
-		};
-		907D039615B86F8C00575110 /* osgEarth */ = {
-			isa = PBXGroup;
-			children = (
-				907D039715B86F9E00575110 /* libosgdb_kml.a */,
-				907D039815B86F9E00575110 /* libosgdb_osgearth_feature_wfs.a */,
-				907D039915B86F9E00575110 /* libosgdb_osgearth_feature_tfs.a */,
-				907D039A15B86F9E00575110 /* libosgdb_osgearth_tms.a */,
-				907D039B15B86F9E00575110 /* libosgdb_osgearth_wms.a */,
-				907D039C15B86F9E00575110 /* libosgdb_osgearth_label_overlay.a */,
-				907D039D15B86F9E00575110 /* libosgdb_osgearth_xyz.a */,
-				907D039E15B86F9E00575110 /* libosgEarthUtil.a */,
-				907D039F15B86F9E00575110 /* libosgdb_osgearth_label_annotation.a */,
-				907D03A015B86F9E00575110 /* libosgdb_osgearth_mask_feature.a */,
-				907D03A115B86F9E00575110 /* libosgdb_osgearth_model_feature_geom.a */,
-				907D03A215B86F9E00575110 /* libosgEarthAnnotation.a */,
-				907D03A315B86F9E00575110 /* libosgdb_osgearth_agglite.a */,
-				907D03A415B86F9E00575110 /* libosgdb_osgearth_feature_ogr.a */,
-				907D03A515B86F9E00575110 /* libosgdb_osgearth_model_feature_stencil.a */,
-				907D03A615B86F9E00575110 /* libosgdb_osgearth_vdatum_egm2008.a */,
-				907D03A715B86F9E00575110 /* libosgdb_osgearth_model_simple.a */,
-				907D03A815B86F9E00575110 /* libosgdb_osgearth_engine_osgterrain.a */,
-				90283E5C15C7091A00620EEF /* libosgdb_osgearth_engine_quadtree.a */,
-				907D03A915B86F9E00575110 /* libosgEarthFeatures.a */,
-				907D03AA15B86F9E00575110 /* libosgdb_osgearth_vdatum_egm96.a */,
-				907D03AB15B86F9E00575110 /* libosgdb_osgearth_ocean_surface.a */,
-				907D03AC15B86F9E00575110 /* libosgdb_osgearth_debug.a */,
-				907D03AD15B86F9E00575110 /* libosgdb_osgearth_mbtiles.a */,
-				907D03AE15B86F9E00575110 /* libosgdb_osgearth_vdatum_egm84.a */,
-				907D03AF15B86F9E00575110 /* libosgdb_osgearth_tileservice.a */,
-				907D03B015B86F9E00575110 /* libosgdb_osgearth_yahoo.a */,
-				907D03B115B86F9E00575110 /* libosgdb_osgearth_arcgis_map_cache.a */,
-				907D03B215B86F9E00575110 /* libosgdb_osgearth_tilecache.a */,
-				907D03B315B86F9E00575110 /* libosgdb_osgearth_wcs.a */,
-				907D03B415B86F9E00575110 /* libosgEarthSymbology.a */,
-				907D03B515B86F9E00575110 /* libosgdb_osgearth_gdal.a */,
-				907D03B615B86F9E00575110 /* libosgdb_osgearth_refresh.a */,
-				907D03B715B86F9E00575110 /* libosgdb_osgearth_vpb.a */,
-				907D03B815B86F9E00575110 /* libosgdb_earth.a */,
-				907D03B915B86F9E00575110 /* libosgdb_osgearth_cache_filesystem.a */,
-				907D03BA15B86F9E00575110 /* libosgdb_osgearth_arcgis.a */,
-				907D03BB15B86F9E00575110 /* libosgdb_osgearth_osg.a */,
-				907D03BC15B86F9E00575110 /* libosgEarth.a */,
-			);
-			name = osgEarth;
-			sourceTree = "<group>";
-		};
-		907D0A9215B8DDAF00575110 /* ShaderGen */ = {
-			isa = PBXGroup;
-			children = (
-				907D0A8F15B8DDAA00575110 /* GLES2ShaderGenVisitor.cpp */,
-				907D0A9015B8DDAA00575110 /* GLES2ShaderGenVisitor.h */,
-			);
-			name = ShaderGen;
-			sourceTree = "<group>";
-		};
-		908C793D15BB0B4E001CFA5E /* MultiTouchManipulator */ = {
-			isa = PBXGroup;
-			children = (
-				903B45D915C0DE9F00F7702B /* EarthMultiTouchManipulator.cpp */,
-				903B45DA15C0DE9F00F7702B /* EarthMultiTouchManipulator.h */,
-			);
-			path = MultiTouchManipulator;
-			sourceTree = "<group>";
-		};
-		90A0DD7215B7BB68004FACEE /* gdal */ = {
-			isa = PBXGroup;
-			children = (
-				90A0DD7015B7BB64004FACEE /* libgdal.a */,
-			);
-			name = gdal;
-			path = "../../../../3rdParty/gdal-ios-device/lib";
-			sourceTree = "<group>";
-		};
-		90A0DD7315B7BB72004FACEE /* freetype */ = {
-			isa = PBXGroup;
-			children = (
-				90A0DD6C15B7BAF9004FACEE /* libFreeType_iphone_universal.a */,
-			);
-			name = freetype;
-			path = "../../../../3rdParty/freetype-ios-universal/lib";
-			sourceTree = "<group>";
-		};
-		90A0DD7415B7BB7C004FACEE /* proj4 */ = {
-			isa = PBXGroup;
-			children = (
-				90A0DD6E15B7BB50004FACEE /* libproj.a */,
-			);
-			name = proj4;
-			path = "../../../../3rdParty/proj4-ios-device/lib";
-			sourceTree = "<group>";
-		};
-		90A0DD7715B7BBAB004FACEE /* Libs */ = {
-			isa = PBXGroup;
-			children = (
-				907D0A8D15B8CEBE00575110 /* libGEOS_3.2.a */,
-				907D039615B86F8C00575110 /* osgEarth */,
-				907D02DB15B86F7100575110 /* osg */,
-				90A0DD7815B7BBCE004FACEE /* curl */,
-				90A0DD7415B7BB7C004FACEE /* proj4 */,
-				90A0DD7315B7BB72004FACEE /* freetype */,
-				90A0DD7215B7BB68004FACEE /* gdal */,
-				905100D015B2185000D9ABD3 /* libiconv.2.4.0.dylib */,
-				905100CE15B217B500D9ABD3 /* libz.1.1.3.dylib */,
-				905100CC15B217A800D9ABD3 /* libc++.dylib */,
-				907D03F915B8C31A00575110 /* libsqlite3.dylib */,
-				9051FFFD15B1EDFD00D9ABD3 /* Frameworks */,
-			);
-			name = Libs;
-			sourceTree = "<group>";
-		};
-		90A0DD7815B7BBCE004FACEE /* curl */ = {
-			isa = PBXGroup;
-			children = (
-				90A0DD7515B7BBA4004FACEE /* libcurl.a */,
-			);
-			name = curl;
-			path = "../../../../3rdParty/curl-ios-device/lib";
-			sourceTree = "<group>";
-		};
-		90B8676915C8895100F5CDC3 /* StartView */ = {
-			isa = PBXGroup;
-			children = (
-				90B8676315C8894900F5CDC3 /* StartViewerController.h */,
-				90B8676415C8894900F5CDC3 /* StartViewerController.m */,
-				90B8676515C8894900F5CDC3 /* StartViewerController.xib */,
-			);
-			name = StartView;
-			sourceTree = "<group>";
-		};
-/* End PBXGroup section */
-
-/* Begin PBXNativeTarget section */
-		9051FFF915B1EDFD00D9ABD3 /* osgEarthViewerIOS */ = {
-			isa = PBXNativeTarget;
-			buildConfigurationList = 9051002315B1EDFD00D9ABD3 /* Build configuration list for PBXNativeTarget "osgEarthViewerIOS" */;
-			buildPhases = (
-				9051FFF615B1EDFD00D9ABD3 /* Sources */,
-				9051FFF715B1EDFD00D9ABD3 /* Frameworks */,
-				9051FFF815B1EDFD00D9ABD3 /* Resources */,
-			);
-			buildRules = (
-			);
-			dependencies = (
-			);
-			name = osgEarthViewerIOS;
-			productName = osgEarthViewerIOS;
-			productReference = 9051FFFA15B1EDFD00D9ABD3 /* osgEarth.app */;
-			productType = "com.apple.product-type.application";
-		};
-/* End PBXNativeTarget section */
-
-/* Begin PBXProject section */
-		9051FFF115B1EDFD00D9ABD3 /* Project object */ = {
-			isa = PBXProject;
-			attributes = {
-				LastUpgradeCheck = 0430;
-			};
-			buildConfigurationList = 9051FFF415B1EDFD00D9ABD3 /* Build configuration list for PBXProject "osgEarthViewerIOS" */;
-			compatibilityVersion = "Xcode 3.2";
-			developmentRegion = English;
-			hasScannedForEncodings = 0;
-			knownRegions = (
-				en,
-				English,
-			);
-			mainGroup = 9051FFEF15B1EDFD00D9ABD3;
-			productRefGroup = 9051FFFB15B1EDFD00D9ABD3 /* Products */;
-			projectDirPath = "";
-			projectRoot = "";
-			targets = (
-				9051FFF915B1EDFD00D9ABD3 /* osgEarthViewerIOS */,
-			);
-		};
-/* End PBXProject section */
-
-/* Begin PBXResourcesBuildPhase section */
-		9051FFF815B1EDFD00D9ABD3 /* Resources */ = {
-			isa = PBXResourcesBuildPhase;
-			buildActionMask = 2147483647;
-			files = (
-				9051000D15B1EDFD00D9ABD3 /* InfoPlist.strings in Resources */,
-				9051001D15B1EDFD00D9ABD3 /* ViewController_iPhone.xib in Resources */,
-				9051002015B1EDFD00D9ABD3 /* ViewController_iPad.xib in Resources */,
-				90283E5615C6FB3E00620EEF /* tests in Resources */,
-				90283E5715C6FB3E00620EEF /* data in Resources */,
-				90B8676715C8894900F5CDC3 /* StartViewerController.xib in Resources */,
-				90DABDDC15CEFF9700D0F609 /* moon_1024x512.jpg in Resources */,
-				1FA5C122177B608000A161FF /* gdal_data in Resources */,
-			);
-			runOnlyForDeploymentPostprocessing = 0;
-		};
-/* End PBXResourcesBuildPhase section */
-
-/* Begin PBXSourcesBuildPhase section */
-		9051FFF615B1EDFD00D9ABD3 /* Sources */ = {
-			isa = PBXSourcesBuildPhase;
-			buildActionMask = 2147483647;
-			files = (
-				9051000F15B1EDFD00D9ABD3 /* main.m in Sources */,
-				9051001315B1EDFD00D9ABD3 /* AppDelegate.m in Sources */,
-				9051001A15B1EDFD00D9ABD3 /* ViewController.m in Sources */,
-				907D0A9115B8DDAA00575110 /* GLES2ShaderGenVisitor.cpp in Sources */,
-				903B45DB15C0DE9F00F7702B /* EarthMultiTouchManipulator.cpp in Sources */,
-				90B8676615C8894900F5CDC3 /* StartViewerController.m in Sources */,
-			);
-			runOnlyForDeploymentPostprocessing = 0;
-		};
-/* End PBXSourcesBuildPhase section */
-
-/* Begin PBXVariantGroup section */
-		9051000B15B1EDFD00D9ABD3 /* InfoPlist.strings */ = {
-			isa = PBXVariantGroup;
-			children = (
-				9051000C15B1EDFD00D9ABD3 /* en */,
-			);
-			name = InfoPlist.strings;
-			sourceTree = "<group>";
-		};
-		9051001B15B1EDFD00D9ABD3 /* ViewController_iPhone.xib */ = {
-			isa = PBXVariantGroup;
-			children = (
-				9051001C15B1EDFD00D9ABD3 /* en */,
-			);
-			name = ViewController_iPhone.xib;
-			sourceTree = "<group>";
-		};
-		9051001E15B1EDFD00D9ABD3 /* ViewController_iPad.xib */ = {
-			isa = PBXVariantGroup;
-			children = (
-				9051001F15B1EDFD00D9ABD3 /* en */,
-			);
-			name = ViewController_iPad.xib;
-			sourceTree = "<group>";
-		};
-/* End PBXVariantGroup section */
-
-/* Begin XCBuildConfiguration section */
-		9051002115B1EDFD00D9ABD3 /* Debug */ = {
-			isa = XCBuildConfiguration;
-			buildSettings = {
-				ALWAYS_SEARCH_USER_PATHS = NO;
-				ARCHS = "$(ARCHS_STANDARD_32_BIT)";
-				"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
-				COPY_PHASE_STRIP = NO;
-				GCC_C_LANGUAGE_STANDARD = gnu99;
-				GCC_DYNAMIC_NO_PIC = NO;
-				GCC_OPTIMIZATION_LEVEL = 0;
-				GCC_PREPROCESSOR_DEFINITIONS = (
-					"DEBUG=1",
-					"$(inherited)",
-				);
-				GCC_SYMBOLS_PRIVATE_EXTERN = NO;
-				GCC_VERSION = com.apple.compilers.llvm.clang.1_0;
-				GCC_WARN_ABOUT_RETURN_TYPE = YES;
-				GCC_WARN_UNINITIALIZED_AUTOS = YES;
-				GCC_WARN_UNUSED_VARIABLE = YES;
-				IPHONEOS_DEPLOYMENT_TARGET = 5.1;
-				SDKROOT = iphoneos;
-				TARGETED_DEVICE_FAMILY = "1,2";
-			};
-			name = Debug;
-		};
-		9051002215B1EDFD00D9ABD3 /* Release */ = {
-			isa = XCBuildConfiguration;
-			buildSettings = {
-				ALWAYS_SEARCH_USER_PATHS = NO;
-				ARCHS = "$(ARCHS_STANDARD_32_BIT)";
-				"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
-				COPY_PHASE_STRIP = YES;
-				GCC_C_LANGUAGE_STANDARD = gnu99;
-				GCC_VERSION = com.apple.compilers.llvm.clang.1_0;
-				GCC_WARN_ABOUT_RETURN_TYPE = YES;
-				GCC_WARN_UNINITIALIZED_AUTOS = YES;
-				GCC_WARN_UNUSED_VARIABLE = YES;
-				IPHONEOS_DEPLOYMENT_TARGET = 5.1;
-				OTHER_CFLAGS = "-DNS_BLOCK_ASSERTIONS=1";
-				SDKROOT = iphoneos;
-				TARGETED_DEVICE_FAMILY = "1,2";
-				VALIDATE_PRODUCT = YES;
-			};
-			name = Release;
-		};
-		9051002415B1EDFD00D9ABD3 /* Debug */ = {
-			isa = XCBuildConfiguration;
-			buildSettings = {
-				"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer: Jefferey Smith (KN8HX7RLT9)";
-				GCC_PRECOMPILE_PREFIX_HEADER = YES;
-				GCC_PREFIX_HEADER = "osgEarthViewerIOS/osgEarthViewerIOS-Prefix.pch";
-				"GCC_THUMB_SUPPORT[arch=armv6]" = "";
-				HEADER_SEARCH_PATHS = (
-					../../,
-					../../../../osg/OpenSceneGraph/include,
-				);
-				INFOPLIST_FILE = "osgEarthViewerIOS/osgEarthViewerIOS-Info.plist";
-				IPHONEOS_DEPLOYMENT_TARGET = 5.1;
-				LIBRARY_SEARCH_PATHS = (
-					"\"$(SRCROOT)/../../../../3rdParty/all\"",
-					"\"$(SRCROOT)/../../../lib/Release\"",
-					"\"$(SRCROOT)/../../../../osg/OpenSceneGraph/lib\"",
-				);
-				PRODUCT_NAME = osgEarth;
-				"PROVISIONING_PROFILE[sdk=iphoneos*]" = "B0AE9D26-0AD5-4EE2-ADC5-8559501E74A9";
-				VALID_ARCHS = armv7;
-				WRAPPER_EXTENSION = app;
-			};
-			name = Debug;
-		};
-		9051002515B1EDFD00D9ABD3 /* Release */ = {
-			isa = XCBuildConfiguration;
-			buildSettings = {
-				"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution: Pelican Ventures, Inc";
-				DEBUG_INFORMATION_FORMAT = dwarf;
-				GCC_GENERATE_DEBUGGING_SYMBOLS = NO;
-				GCC_PRECOMPILE_PREFIX_HEADER = YES;
-				GCC_PREFIX_HEADER = "osgEarthViewerIOS/osgEarthViewerIOS-Prefix.pch";
-				"GCC_THUMB_SUPPORT[arch=armv6]" = "";
-				HEADER_SEARCH_PATHS = (
-					../../,
-					../../../../osg/OpenSceneGraph/include,
-				);
-				INFOPLIST_FILE = "osgEarthViewerIOS/osgEarthViewerIOS-Info.plist";
-				IPHONEOS_DEPLOYMENT_TARGET = 5.1;
-				LIBRARY_SEARCH_PATHS = (
-					"\"$(SRCROOT)/../../../../3rdParty/all\"",
-					"\"$(SRCROOT)/../../../lib/Release\"",
-					"\"$(SRCROOT)/../../../../osg/OpenSceneGraph/lib\"",
-				);
-				PRODUCT_NAME = osgEarth;
-				"PROVISIONING_PROFILE[sdk=iphoneos*]" = "3948EA51-CDCD-4B29-A73F-67C55D186433";
-				VALID_ARCHS = armv7;
-				WRAPPER_EXTENSION = app;
-			};
-			name = Release;
-		};
-/* End XCBuildConfiguration section */
-
-/* Begin XCConfigurationList section */
-		9051002315B1EDFD00D9ABD3 /* Build configuration list for PBXNativeTarget "osgEarthViewerIOS" */ = {
-			isa = XCConfigurationList;
-			buildConfigurations = (
-				9051002415B1EDFD00D9ABD3 /* Debug */,
-				9051002515B1EDFD00D9ABD3 /* Release */,
-			);
-			defaultConfigurationIsVisible = 0;
-			defaultConfigurationName = Release;
-		};
-		9051FFF415B1EDFD00D9ABD3 /* Build configuration list for PBXProject "osgEarthViewerIOS" */ = {
-			isa = XCConfigurationList;
-			buildConfigurations = (
-				9051002115B1EDFD00D9ABD3 /* Debug */,
-				9051002215B1EDFD00D9ABD3 /* Release */,
-			);
-			defaultConfigurationIsVisible = 0;
-			defaultConfigurationName = Release;
-		};
-/* End XCConfigurationList section */
-	};
-	rootObject = 9051FFF115B1EDFD00D9ABD3 /* Project object */;
-}
diff --git a/src/applications/osgearth_viewerIOS/osgEarthViewerIOS.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/src/applications/osgearth_viewerIOS/osgEarthViewerIOS.xcodeproj/project.xcworkspace/contents.xcworkspacedata
deleted file mode 100644
index 0901c6b..0000000
--- a/src/applications/osgearth_viewerIOS/osgEarthViewerIOS.xcodeproj/project.xcworkspace/contents.xcworkspacedata
+++ /dev/null
@@ -1,7 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<Workspace
-   version = "1.0">
-   <FileRef
-      location = "self:osgEarthViewerIOS.xcodeproj">
-   </FileRef>
-</Workspace>
diff --git a/src/applications/osgearth_viewerIOS/osgEarthViewerIOS/ShaderGen/GLES2ShaderGenVisitor.cpp b/src/applications/osgearth_viewerIOS/osgEarthViewerIOS/ShaderGen/GLES2ShaderGenVisitor.cpp
deleted file mode 100755
index a2043f1..0000000
--- a/src/applications/osgearth_viewerIOS/osgEarthViewerIOS/ShaderGen/GLES2ShaderGenVisitor.cpp
+++ /dev/null
@@ -1,581 +0,0 @@
-/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2009 Robert Osfield 
- *
- * This library is open source and may be redistributed and/or modified under  
- * the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or 
- * (at your option) any later version.  The full license is in LICENSE file
- * included with this distribution, and on the openscenegraph.org website.
- * 
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
- * OpenSceneGraph Public License for more details.
- */
-
-/**
- * \brief    Shader generator framework.
- * \author   Maciej Krol
- */
-
-#include "GLES2ShaderGenVisitor.h"
-#include <osg/Geode>
-#include <osg/Geometry> // for ShaderGenVisitor::update
-#include <osg/Fog>
-#include <osg/Material>
-#include <sstream>
-
-#ifndef WIN32
-#define SHADER_COMPAT \
-"#ifndef GL_ES\n" \
-"#if (__VERSION__ <= 110)\n" \
-"#define lowp\n" \
-"#define mediump\n" \
-"#define highp\n" \
-"#endif\n" \
-"#endif\n"
-#else
-#define SHADER_COMPAT ""
-#endif
-
-
-using namespace osgUtil;
-
-namespace osgUtil
-{
-    
-    /// State extended by mode/attribute accessors
-    class StateEx : public osg::State
-    {
-    public:
-        StateEx() : State() {}
-        
-        osg::StateAttribute::GLModeValue getMode(osg::StateAttribute::GLMode mode,
-                                                 osg::StateAttribute::GLModeValue def = osg::StateAttribute::INHERIT) const
-        {
-            return getMode(_modeMap, mode, def);
-        }
-        
-        osg::StateAttribute *getAttribute(osg::StateAttribute::Type type, unsigned int member = 0) const
-        {
-            return getAttribute(_attributeMap, type, member);
-        }
-        
-        osg::StateAttribute::GLModeValue getTextureMode(unsigned int unit,
-                                                        osg::StateAttribute::GLMode mode,
-                                                        osg::StateAttribute::GLModeValue def = osg::StateAttribute::INHERIT) const
-        {
-            return unit < _textureModeMapList.size() ? getMode(_textureModeMapList[unit], mode, def) : def;
-        }
-        
-        osg::StateAttribute *getTextureAttribute(unsigned int unit, osg::StateAttribute::Type type) const
-        {
-            return unit < _textureAttributeMapList.size() ? getAttribute(_textureAttributeMapList[unit], type, 0) : 0;
-        }
-        
-        osg::Uniform *getUniform(const std::string& name) const
-        {
-            UniformMap::const_iterator it = _uniformMap.find(name);
-            return it != _uniformMap.end() ? 
-            const_cast<osg::Uniform *>(it->second.uniformVec.back().first) : 0;
-        }
-        
-    protected:
-        
-        osg::StateAttribute::GLModeValue getMode(const ModeMap &modeMap,
-                                                 osg::StateAttribute::GLMode mode, 
-                                                 osg::StateAttribute::GLModeValue def = osg::StateAttribute::INHERIT) const
-        {
-            ModeMap::const_iterator it = modeMap.find(mode);
-            return (it != modeMap.end() && it->second.valueVec.size()) ? it->second.valueVec.back() : def;
-        }
-        
-        osg::StateAttribute *getAttribute(const AttributeMap &attributeMap,
-                                          osg::StateAttribute::Type type, unsigned int member = 0) const
-        {
-            AttributeMap::const_iterator it = attributeMap.find(std::make_pair(type, member));
-            return (it != attributeMap.end() && it->second.attributeVec.size()) ? 
-            const_cast<osg::StateAttribute*>(it->second.attributeVec.back().first) : 0;
-        }
-    };
-    
-}
-
-void GLES2ShaderGenCache::setStateSet(int stateMask, osg::StateSet *stateSet)
-{
-    OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_mutex);
-    _stateSetMap[stateMask] = stateSet;
-}
-
-osg::StateSet* GLES2ShaderGenCache::getStateSet(int stateMask) const
-{
-    OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_mutex);
-    StateSetMap::const_iterator it = _stateSetMap.find(stateMask);
-    return (it != _stateSetMap.end()) ? it->second.get() : 0;
-}
-
-osg::StateSet* GLES2ShaderGenCache::getOrCreateStateSet(int stateMask)
-{
-    OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_mutex);
-    StateSetMap::iterator it = _stateSetMap.find(stateMask);
-    if (it == _stateSetMap.end())
-    {
-        osg::StateSet *stateSet = createStateSet(stateMask);
-        _stateSetMap.insert(it, StateSetMap::value_type(stateMask, stateSet));
-        return stateSet;
-    }
-    return it->second.get();
-}
-
-osg::StateSet* GLES2ShaderGenCache::createStateSet(int stateMask) const
-{
-    osg::StateSet *stateSet = new osg::StateSet;
-    osg::Program *program = new osg::Program;
-    stateSet->setAttribute(program);
-    
-    std::ostringstream vert;
-    std::ostringstream frag;
-    
-    //first add the shader compat defines so that we don't have issues with using precision stuff
-    vert << SHADER_COMPAT;
-    frag << SHADER_COMPAT;
-    
-    // write varyings
-    if ((stateMask & LIGHTING) && !(stateMask & NORMAL_MAP))
-    {
-        vert << "varying highp vec3 normalDir;\n";
-    }
-    
-    if (stateMask & (LIGHTING | NORMAL_MAP))
-    {
-        vert << "struct osg_LightSourceParameters {"
-        << "    mediump vec4  ambient;"
-        << "    mediump vec4  diffuse;"
-        << "    mediump vec4  specular;"
-        << "    mediump vec4  position;"
-        << "    mediump vec4  halfVector;"
-        << "    mediump vec3  spotDirection;" 
-        << "    mediump float  spotExponent;"
-        << "    mediump float  spotCutoff;"
-        << "    mediump float  spotCosCutoff;" 
-        << "    mediump float  constantAttenuation;"
-        << "    mediump float  linearAttenuation;"
-        << "    mediump float  quadraticAttenuation;" 
-        << "};\n"
-        << "uniform osg_LightSourceParameters osg_LightSource[" << 1 << "];\n"
-        
-        << "struct  osg_LightProducts {"
-        << "    mediump vec4  ambient;"
-        << "    mediump vec4  diffuse;"
-        << "    mediump vec4  specular;"
-        << "};\n"
-        << "uniform osg_LightProducts osg_FrontLightProduct[" << 1 << "];\n"
-        
-        << "varying highp vec3 lightDir;\n";
-    }
-    
-    if (stateMask & (LIGHTING | NORMAL_MAP | FOG))
-    {
-        vert << "varying highp vec3 viewDir;\n";
-    }
-    
-    //add texcoord varying if using gles2 as built in gl_TexCoord does not exist,
-    //also no gl_FrontColor so we will define vColor
-#ifndef OSG_GL_FIXED_FUNCTION_AVAILABLE
-    if (stateMask & (DIFFUSE_MAP | NORMAL_MAP))
-    {
-        vert << "varying mediump vec4 texCoord0;\n";
-    }
-    vert << "varying mediump vec4 vColor;\n";
-#endif
-    
-    // copy varying to fragment shader
-    frag << vert.str();
-    
-    //add our material replacment uniforms for non fixed function versions
-#ifndef OSG_GL_FIXED_FUNCTION_AVAILABLE
-    if (stateMask & (LIGHTING | NORMAL_MAP))
-    {
-        frag <<
-        "struct osgMaterial{\n"\
-        "  mediump vec4 ambient;\n"\
-        "  mediump vec4 diffuse;\n"\
-        "  mediump vec4 specular;\n"\
-        "  mediump float shine;\n"\
-        "};\n"\
-        
-        "uniform osgMaterial osg_Material;\n";
-    }
-#endif
-    
-
-    // write uniforms and attributes
-    int unit = 0;
-    
-    //add the replacements for gl matricies and verticies
-#ifndef OSG_GL_FIXED_FUNCTION_AVAILABLE
-    //vert << "attribute vec4 gl_Vertex;\n";
-    vert << "attribute vec4 osg_Color;\n";
-    vert << "uniform mat4 osg_ModelViewProjectionMatrix;\n";
-#endif
-    
-    if (stateMask & DIFFUSE_MAP)
-    {
-        osg::Uniform *diffuseMap = new osg::Uniform("diffuseMap", unit++);
-        stateSet->addUniform(diffuseMap);
-        frag << "uniform sampler2D diffuseMap;\n";
-    }
-    
-    if (stateMask & NORMAL_MAP)
-    {
-        osg::Uniform *normalMap = new osg::Uniform("normalMap", unit++);
-        stateSet->addUniform(normalMap);
-        frag << "uniform sampler2D normalMap;\n";
-        program->addBindAttribLocation("tangent", 6);
-        vert << "attribute vec3 tangent;\n";
-    }
-    
-    
-#ifndef OSG_GL_FIXED_FUNCTION_AVAILABLE
-
-    //non fixed function texturing
-    if (stateMask & (DIFFUSE_MAP | NORMAL_MAP))
-    {
-        vert << "attribute vec4 osg_MultiTexCoord0;\n";
-    }
-    
-    //non fixed function normal info
-    if (stateMask & (LIGHTING | NORMAL_MAP))
-    {
-        //vert << "uniform mat4 osg_ModelViewMatrix;\n";
-        //vert << "attribute vec3 osg_Normal;\n";
-        //vert << "uniform mat3 osg_NormalMatrix;\n";        
-    }
-#endif
-    
-    vert << "\n"\
-    "void main()\n"\
-    "{\n"\
-    
-    //ftransform does not exist in gles2 (need to see if it's still in GL3)
-#ifdef OSG_GL_FIXED_FUNCTION_AVAILABLE
-    "  gl_Position = ftransform();\n";
-#else
-    "  gl_Position = osg_ModelViewProjectionMatrix * gl_Vertex;\n";
-#endif
-    
-    if (stateMask & (DIFFUSE_MAP | NORMAL_MAP))
-    {
-        //gles2 does not have built in gl_TexCoord varying
-#ifdef OSG_GL_FIXED_FUNCTION_AVAILABLE
-        vert << "  gl_TexCoord[0] = gl_MultiTexCoord0;\n";
-#else
-        vert << "  texCoord0 = osg_MultiTexCoord0;\n";
-#endif
-    }
-    
-    //
-    
-    if (stateMask & NORMAL_MAP)
-    {
-        //gl_NormalMatrix etc should be replaced automatically
-        vert << 
-        "  highp vec3 n = gl_NormalMatrix * gl_Normal;\n"\
-        "  highp vec3 t = gl_NormalMatrix * tangent;\n"\
-        "  highp vec3 b = cross(n, t);\n"\
-        "  highp vec3 dir = -vec3(gl_ModelViewMatrix * gl_Vertex);\n"\
-        "  viewDir.x = dot(dir, t);\n"\
-        "  viewDir.y = dot(dir, b);\n"\
-        "  viewDir.z = dot(dir, n);\n";
-//use fixed light pos for now where gl_LightSource is not avaliable
-#ifdef OSG_GL_FIXED_FUNCTION_AVAILABLE 
-        vert << "  vec4 lpos = gl_LightSource[0].position;\n";
-#else
-        vert << "  highp vec4 lpos = osg_LightSource[0].position;\n";
-#endif
-        vert << 
-        "  if (lpos.w == 0.0)\n"\
-        "    dir = lpos.xyz;\n"\
-        "  else\n"\
-        "    dir += lpos.xyz;\n"\
-        "  lightDir.x = dot(dir, t);\n"\
-        "  lightDir.y = dot(dir, b);\n"\
-        "  lightDir.z = dot(dir, n);\n";
-    }
-    else if (stateMask & LIGHTING)
-    {
-        vert << 
-        "  normalDir = gl_NormalMatrix * gl_Normal;\n"\
-        "  highp vec3 dir = -vec3(gl_ModelViewMatrix * gl_Vertex);\n"\
-        "  viewDir = dir;\n";
-//use fixed light pos for now where gl_LightSource is not avaliable
-#ifdef OSG_GL_FIXED_FUNCTION_AVAILABLE
-        vert << "  vec4 lpos = gl_LightSource[0].position;\n";
-#else
-        vert << "  vec4 lpos = osg_LightSource[0].position;\n";
-#endif
-        vert <<
-        "  if (lpos.w == 0.0)\n"\
-        "    lightDir = lpos.xyz;\n"\
-        "  else\n"\
-        "    lightDir = lpos.xyz + dir;\n";
-    }
-    else if (stateMask & FOG)
-    {
-        vert << "  viewDir = -vec3(gl_ModelViewMatrix * gl_Vertex);\n";
-#ifdef OSG_GL_FIXED_FUNCTION_AVAILABLE
-        vert << "  gl_FrontColor = gl_Color;\n";
-#else
-        vert << "  vColor = osg_Color;\n";
-#endif
-    }
-    else
-    {
-#ifdef OSG_GL_FIXED_FUNCTION_AVAILABLE
-        vert << "  gl_FrontColor = gl_Color;\n";
-#else
-        vert << "  vColor = osg_Color;\n";
-#endif
-    }
-    
-    vert << "}\n";
-    
-    frag << "\n"\
-    "void main()\n"\
-    "{\n";
-    
-    if (stateMask & DIFFUSE_MAP)
-    {
-#ifdef OSG_GL_FIXED_FUNCTION_AVAILABLE
-        frag << "  vec4 base = texture2D(diffuseMap, gl_TexCoord[0].st);\n";
-#else
-        frag << "  mediump vec4 base = texture2D(diffuseMap, texCoord0.st);\n";
-#endif
-    }
-    else
-    {
-        frag << "  mediump vec4 base = vec4(1.0);\n";
-    }
-    
-    if (stateMask & NORMAL_MAP)
-    {
-#ifdef OSG_GL_FIXED_FUNCTION_AVAILABLE
-        frag << "  vec3 normalDir = texture2D(normalMap, gl_TexCoord[0].st).xyz*2.0-1.0;\n";
-#else
-        frag << "  mediump vec3 normalDir = texture2D(normalMap, texCoord0.st).xyz*2.0-1.0;\n";        
-        //frag << " normalDir.g = -normalDir.g;\n";
-#endif
-    }
-    
-    if (stateMask & (LIGHTING | NORMAL_MAP))
-    {
-//for now we will just have two versions of the below, once we have access to lights
-//then we can completely replace use of gl_FrontLightModelProduct
-#ifdef OSG_GL_FIXED_FUNCTION_AVAILABLE
-        frag << 
-        "  highp vec3 nd = normalize(normalDir);\n"\
-        "  highp vec3 ld = normalize(lightDir);\n"\
-        "  highp vec3 vd = normalize(viewDir);\n"\
-        "  mediump vec4 color = gl_FrontLightModelProduct.sceneColor;\n"\
-        "  color += gl_FrontLightProduct[0].ambient;\n"\
-        "  mediump float diff = max(dot(ld, nd), 0.0);\n"\
-        "  color += gl_FrontLightProduct[0].diffuse * diff;\n"\
-        "  color *= base;\n"\
-        "  if (diff > 0.0)\n"\
-        "  {\n"\
-        "    highp vec3 halfDir = normalize(ld+vd);\n"\
-        "    color.rgb += base.a * gl_FrontLightProduct[0].specular.rgb * \n"\
-        "      pow(max(dot(halfDir, nd), 0.0), gl_FrontMaterial.shininess);\n"\
-        "  }\n";
-#else
-        frag << 
-        "  highp vec3 nd = normalize(normalDir);\n"\
-        "  highp vec3 ld = normalize(lightDir);\n"\
-        "  highp vec3 vd = normalize(viewDir);\n"\
-        "  mediump vec4 color = vec4(0.01,0.01,0.01,1.0);\n"\
-        "  color += osg_FrontLightProduct[0].ambient;\n"\
-        "  mediump float diff = max(dot(ld, nd), 0.0);\n"\
-        "  color += osg_FrontLightProduct[0].diffuse * diff;\n"\
-        "  color *= base;\n"\
-        "  if (diff > 0.0)\n"\
-        "  {\n"\
-        "    highp vec3 halfDir = normalize(ld+vd);\n"\
-        "    color.rgb += base.a * osg_FrontLightProduct[0].specular.rgb * \n"\
-        "      pow(max(dot(halfDir, nd), 0.0), osg_Material.shine);\n"\
-        "  }\n";       
-#endif
-    }
-    else
-    {
-        frag << "  mediump vec4 color = base;\n";
-    }
-    
-    if (!(stateMask & LIGHTING))
-    {
-#ifdef OSG_GL_FIXED_FUNCTION_AVAILABLE
-        frag << "  color *= gl_Color;\n";
-#else
-        frag << "  color *= vColor;\n";
-#endif
-    }
-    
-    if (stateMask & FOG)
-    {
-        frag << 
-        "  float d2 = dot(viewDir, viewDir);//gl_FragCoord.z/gl_FragCoord.w;\n"\
-        "  float f = exp2(-1.442695*gl_Fog.density*gl_Fog.density*d2);\n"\
-        "  color.rgb = mix(gl_Fog.color.rgb, color.rgb, clamp(f, 0.0, 1.0));\n";
-    }
-    
-    frag << "  gl_FragColor = color;\n";
-    frag << "}\n";
-    
-    std::string vertstr = vert.str();
-    std::string fragstr = frag.str();
-    
-    OSG_DEBUG << "ShaderGenCache Vertex shader:\n" << vertstr << std::endl;
-    OSG_DEBUG << "ShaderGenCache Fragment shader:\n" << fragstr << std::endl;
-    
-    program->addShader(new osg::Shader(osg::Shader::VERTEX, vertstr));
-    program->addShader(new osg::Shader(osg::Shader::FRAGMENT, fragstr));
-    
-    return stateSet;
-}
-
-GLES2ShaderGenVisitor::GLES2ShaderGenVisitor() : 
-NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN),
-_stateCache(new GLES2ShaderGenCache),
-_state(new StateEx)
-{
-}
-
-GLES2ShaderGenVisitor::GLES2ShaderGenVisitor(GLES2ShaderGenCache *stateCache) : 
-NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN),
-_stateCache(stateCache),
-_state(new StateEx)
-{
-}
-
-void GLES2ShaderGenVisitor::setRootStateSet(osg::StateSet *stateSet)
-{
-    if (_rootStateSet.valid())
-        _state->removeStateSet(0);
-    _rootStateSet = stateSet;
-    if (_rootStateSet.valid())
-        _state->pushStateSet(_rootStateSet.get());
-}
-
-void GLES2ShaderGenVisitor::reset()
-{
-    _state->popAllStateSets();
-    if (_rootStateSet.valid())
-        _state->pushStateSet(_rootStateSet.get());
-}
-
-void GLES2ShaderGenVisitor::apply(osg::Node &node)
-{
-    osg::StateSet *stateSet = node.getStateSet();
-    
-    if (stateSet)
-        _state->pushStateSet(stateSet);
-    
-    traverse(node);
-    
-    if (stateSet)
-        _state->popStateSet();
-}
-
-void GLES2ShaderGenVisitor::apply(osg::Geode &geode)
-{
-    osg::StateSet *stateSet = geode.getStateSet();
-    if (stateSet)
-        _state->pushStateSet(stateSet);
-    
-    for (unsigned int i=0; i<geode.getNumDrawables(); ++i)
-    {
-        osg::Drawable *drawable = geode.getDrawable(i);
-        osg::StateSet *ss = drawable->getStateSet();
-        if (ss)
-            _state->pushStateSet(ss);
-        
-        update(drawable);
-        
-        if (ss)
-            _state->popStateSet();
-    }
-    
-    if (stateSet)
-        _state->popStateSet();
-}
-
-void GLES2ShaderGenVisitor::update(osg::Drawable *drawable)
-{
-    // update only geometry due to compatibility issues with user defined drawables
-    osg::Geometry *geometry = drawable->asGeometry();
-#if 1
-    if (!geometry)
-        return;
-#endif
-    
-    StateEx *state = static_cast<StateEx *>(_state.get());
-    // skip nodes without state sets
-    if (state->getStateSetStackSize() == (_rootStateSet.valid() ? 1u : 0u))
-        return;
-    
-    // skip state sets with already attached programs
-    if (state->getAttribute(osg::StateAttribute::PROGRAM))
-        return;
-    
-    int stateMask = 0;
-    //if (state->getMode(GL_BLEND) & osg::StateAttribute::ON)
-    //    stateMask |= ShaderGen::BLEND;
-    if (state->getMode(GL_LIGHTING) & osg::StateAttribute::ON)
-        stateMask |= GLES2ShaderGenCache::LIGHTING;
-    if (state->getMode(GL_FOG) & osg::StateAttribute::ON)
-        stateMask |= GLES2ShaderGenCache::FOG;
-    if (state->getTextureAttribute(0, osg::StateAttribute::TEXTURE))
-        stateMask |= GLES2ShaderGenCache::DIFFUSE_MAP;
-    
-    if (state->getTextureAttribute(1, osg::StateAttribute::TEXTURE) && geometry!=0 &&
-        geometry->getVertexAttribArray(6)) //tangent
-        stateMask |= GLES2ShaderGenCache::NORMAL_MAP;
-    
-    // Get program and uniforms for accumulated state.
-    osg::StateSet *progss = _stateCache->getOrCreateStateSet(stateMask);
-    // Set program and uniforms to the last state set.
-    osg::StateSet *ss = const_cast<osg::StateSet *>(state->getStateSetStack().back());
-    ss->setAttribute(progss->getAttribute(osg::StateAttribute::PROGRAM));
-    ss->setUniformList(progss->getUniformList());
-    
-    //Edit, for now we will pinch the Material colors and bind as uniforms for non fixed function to replace gl_Front Material
-#ifndef OSG_GL_FIXED_FUNCTION_AVAILABLE
-    osg::Material* mat = dynamic_cast<osg::Material*>(ss->getAttribute(osg::StateAttribute::MATERIAL));
-    if(mat){
-        ss->addUniform(new osg::Uniform("osg_Material.ambient", mat->getAmbient(osg::Material::FRONT)));
-        ss->addUniform(new osg::Uniform("osg_Material.diffuse", mat->getDiffuse(osg::Material::FRONT)));
-        ss->addUniform(new osg::Uniform("osg_Material.specular", mat->getSpecular(osg::Material::FRONT)));
-        ss->addUniform(new osg::Uniform("osg_Material.shine", mat->getShininess(osg::Material::FRONT)));
-        ss->removeAttribute(osg::StateAttribute::MATERIAL);
-    }else{
-        //if no material then setup some reasonable defaults
-        ss->addUniform(new osg::Uniform("osg_Material.ambient", osg::Vec4(0.2f,0.2f,0.2f,1.0f)));
-        ss->addUniform(new osg::Uniform("osg_Material.diffuse", osg::Vec4(0.8f,0.8f,0.8f,1.0f)));
-        ss->addUniform(new osg::Uniform("osg_Material.specular", osg::Vec4(1.0f,1.0f,1.0f,1.0f)));
-        ss->addUniform(new osg::Uniform("osg_Material.shine", 16.0f));
-    }
-#endif
-    
-    
-    // remove any modes that won't be appropriate when using shaders
-    if ((stateMask & GLES2ShaderGenCache::LIGHTING)!=0)
-    {
-        ss->removeMode(GL_LIGHTING);
-        ss->removeMode(GL_LIGHT0);
-    }
-    if ((stateMask & GLES2ShaderGenCache::FOG)!=0)
-    {
-        ss->removeMode(GL_FOG);
-    }
-    if ((stateMask & GLES2ShaderGenCache::DIFFUSE_MAP)!=0) ss->removeTextureMode(0, GL_TEXTURE_2D);
-    if ((stateMask & GLES2ShaderGenCache::NORMAL_MAP)!=0) ss->removeTextureMode(1, GL_TEXTURE_2D);
-}
-
-
diff --git a/src/applications/osgearth_viewerIOS/osgEarthViewerIOS/ShaderGen/GLES2ShaderGenVisitor.h b/src/applications/osgearth_viewerIOS/osgEarthViewerIOS/ShaderGen/GLES2ShaderGenVisitor.h
deleted file mode 100755
index ace8e3a..0000000
--- a/src/applications/osgearth_viewerIOS/osgEarthViewerIOS/ShaderGen/GLES2ShaderGenVisitor.h
+++ /dev/null
@@ -1,84 +0,0 @@
-/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2009 Robert Osfield 
- *
- * This library is open source and may be redistributed and/or modified under  
- * the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or 
- * (at your option) any later version.  The full license is in LICENSE file
- * included with this distribution, and on the openscenegraph.org website.
- * 
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
- * OpenSceneGraph Public License for more details.
- */
-
-/**
- * \brief    Shader generator framework.
- * \author   Maciej Krol
- */
-
-#ifndef OSGUTIL_GLES2SHADER_STATE_
-#define OSGUTIL_GLES2SHADER_STATE_ 1
-
-#include <osgUtil/Export>
-#include <osg/NodeVisitor>
-#include <osg/State>
-
-namespace osgUtil
-{
-    
-    class GLES2ShaderGenCache : public osg::Referenced
-    {
-    public:
-        enum StateMask
-        {
-            BLEND = 1,
-            LIGHTING = 2,
-            FOG = 4,
-            DIFFUSE_MAP = 8, //< Texture in unit 0
-            NORMAL_MAP = 16  //< Texture in unit 1 and vertex attribute array 6
-        };
-        
-        typedef std::map<int, osg::ref_ptr<osg::StateSet> > StateSetMap;
-        
-        GLES2ShaderGenCache() {};
-        
-        void setStateSet(int stateMask, osg::StateSet *program);
-        osg::StateSet *getStateSet(int stateMask) const;
-        osg::StateSet *getOrCreateStateSet(int stateMask);
-        
-    protected:
-        osg::StateSet *createStateSet(int stateMask) const;
-        mutable OpenThreads::Mutex _mutex;
-        StateSetMap _stateSetMap;
-        
-    };
-    
-    class OSGUTIL_EXPORT GLES2ShaderGenVisitor : public osg::NodeVisitor
-    {
-    public:
-        GLES2ShaderGenVisitor();
-        GLES2ShaderGenVisitor(GLES2ShaderGenCache *stateCache);
-        
-        void setStateCache(GLES2ShaderGenCache *stateCache) { _stateCache = stateCache; }
-        GLES2ShaderGenCache *getStateCache() const { return _stateCache.get(); }
-        
-        /// Top level state set applied as the first one.
-        void setRootStateSet(osg::StateSet *stateSet);
-        osg::StateSet *getRootStateSet() const { return _rootStateSet.get(); }
-        
-        void apply(osg::Node &node);
-        void apply(osg::Geode &geode);
-        
-        void reset();
-        
-    protected:
-        void update(osg::Drawable *drawable);
-        
-        osg::ref_ptr<GLES2ShaderGenCache> _stateCache;
-        osg::ref_ptr<osg::State> _state;
-        osg::ref_ptr<osg::StateSet> _rootStateSet;
-    };
-    
-}
-
-#endif
\ No newline at end of file
diff --git a/src/applications/osgearth_viewerIOS/osgEarthViewerIOS/ShaderGen/ShaderGenScene.h b/src/applications/osgearth_viewerIOS/osgEarthViewerIOS/ShaderGen/ShaderGenScene.h
deleted file mode 100755
index 527f6bf..0000000
--- a/src/applications/osgearth_viewerIOS/osgEarthViewerIOS/ShaderGen/ShaderGenScene.h
+++ /dev/null
@@ -1,227 +0,0 @@
-#pragma once
-
-#include <osg/ShapeDrawable>
-#include <osg/Texture2D>
-#include <osg/MatrixTransform>
-#include <osgDB/ReadFile>
-#include <osgDB/FileUtils>
-#include <osgUtil/TangentSpaceGenerator>
-
-#include "GLES2ShaderGenVisitor.h"
-
-class GenerateTangentsVisitor : public osg::NodeVisitor
-{
-public:
-    std::vector<osg::Geode*> _geodesList;
-    GenerateTangentsVisitor()
-    : osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN)
-    {
-    }
-    virtual void apply(osg::Geode& geode)
-    {
-        _geodesList.push_back(&geode);
-        //loop the geoms
-        for(unsigned int i=0; i<geode.getNumDrawables(); i++)
-        {
-            //cast drawable to geometry
-            osg::Geometry* geom = geode.getDrawable(i)->asGeometry();
-            
-            if(geom)
-            {
-                //check if the geom already has the vectors
-                if( (geom->getVertexAttribArray(6) == 0) && (geom->getVertexAttribArray(7) == 0) )
-                {
-                    
-                    osgUtil::TangentSpaceGenerator* tangGen = new osgUtil::TangentSpaceGenerator();
-                    tangGen->generate(geom, 0);
-                    
-                    if(tangGen)
-                    {
-                        osg::Vec4Array* tangentArray = tangGen->getTangentArray(); 
-                        osg::Vec4Array* biNormalArray = tangGen->getBinormalArray();
-                        
-                        int size = tangentArray->size();
-                        int sizeb = biNormalArray->size();
-                        
-                        if( (size>0) && (sizeb>0))
-                        {
-                            geom->setVertexAttribArray(6, tangentArray);
-                            geom->setVertexAttribBinding(6, osg::Geometry::BIND_PER_VERTEX);  
-                            
-                            //geom->setVertexAttribArray(7, biNormalArray);
-                            //geom->setVertexAttribBinding(7, osg::Geometry::BIND_PER_VERTEX);  
-                        }
-                    }
-                }
-            }
-        }
-        traverse(geode);
-    }
-protected:
-    
-};
-
-class ShaderGenScene
-{
-public:
-    ShaderGenScene(){
-        
-    }
-    virtual ~ShaderGenScene(){
-    }
-    
-    static osg::MatrixTransform* CreateScene(){
-        osg::MatrixTransform* root = new osg::MatrixTransform();
-        //move whole scene in front of camera
-        root->setMatrix(osg::Matrix::translate(osg::Vec3(0.0f,0.0f,-100.0f)));
-
-        //create each type with an offset
-        float size = 20.0f;
-        float offset = size+1.0f;
-        
-        //top row
-        root->addChild(ShaderGenScene::CreateColoredShape(osg::Vec3(-offset,offset,0.0f), size));
-        
-        root->addChild(ShaderGenScene::CreateColoredLitShape(osg::Vec3(0.0f,offset,0.0f), size));
-        
-        root->addChild(ShaderGenScene::CreateTexturedShape(osg::Vec3(offset,offset,0.0f), size));
-        
-        //bottom row
-        root->addChild(ShaderGenScene::CreateTexturedLitShape(osg::Vec3(-offset,-offset,0.0f), size));
-        
-        root->addChild(ShaderGenScene::CreateColoredNormalMappedShape(osg::Vec3(0.0f,-offset,0.0f), size));
-        
-        root->addChild(ShaderGenScene::CreateTexturedNormalMappedShape(osg::Vec3(offset,-offset,0.0f), size));
-        
-        //apply shader gen to entire root
-        //osgUtil::GLES2ShaderGenVisitor shaderGen;
-        //root->accept(shaderGen);
-        
-        return root;
-    }
-    
-    static osg::MatrixTransform* CreateColoredShape(osg::Vec3 offset, float size){
-        osg::Sphere* sphere = new osg::Sphere();
-        sphere->setRadius(size*0.5f);
-        osg::ShapeDrawable* shape = new osg::ShapeDrawable(sphere);
-        osg::Geode* geode = new osg::Geode();
-        geode->addDrawable(shape);
-        
-        osg::StateSet* state = geode->getOrCreateStateSet();
-        state->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
-        
-        osg::MatrixTransform* transform = new osg::MatrixTransform();
-        transform->setMatrix(osg::Matrix::translate(offset));
-        transform->addChild(geode);
-        return transform;
-    }
-    
-    static osg::MatrixTransform* CreateColoredLitShape(osg::Vec3 offset, float size){
-        osg::Sphere* sphere = new osg::Sphere();
-        sphere->setRadius(size*0.5f);
-        osg::ShapeDrawable* shape = new osg::ShapeDrawable(sphere);
-        osg::Geode* geode = new osg::Geode();
-        geode->addDrawable(shape);
-        
-        osg::StateSet* state = geode->getOrCreateStateSet();
-        state->setMode(GL_LIGHTING, osg::StateAttribute::ON);
-        
-        osg::MatrixTransform* transform = new osg::MatrixTransform();
-        transform->setMatrix(osg::Matrix::translate(offset));
-        transform->addChild(geode);
-        return transform;
-    }
-    
-    static osg::MatrixTransform* CreateTexturedShape(osg::Vec3 offset, float size){
-        osg::Sphere* sphere = new osg::Sphere();
-        sphere->setRadius(size*0.5f);
-        osg::ShapeDrawable* shape = new osg::ShapeDrawable(sphere);
-        osg::Geode* geode = new osg::Geode();
-        geode->addDrawable(shape);
-        
-        osg::StateSet* state = geode->getOrCreateStateSet();
-        state->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
-        state->setTextureAttributeAndModes(0, new osg::Texture2D(osgDB::readImageFile(osgDB::findDataFile("Images/diffuse.png"))), osg::StateAttribute::ON);
-        
-        osg::MatrixTransform* transform = new osg::MatrixTransform();
-        transform->setMatrix(osg::Matrix::translate(offset));
-        transform->addChild(geode);
-        return transform;
-    } 
-    
-    static osg::MatrixTransform* CreateTexturedLitShape(osg::Vec3 offset, float size){
-        osg::Sphere* sphere = new osg::Sphere();
-        sphere->setRadius(size*0.5f);
-        osg::ShapeDrawable* shape = new osg::ShapeDrawable(sphere);
-        osg::Geode* geode = new osg::Geode();
-        geode->addDrawable(shape);
-        
-        osg::StateSet* state = geode->getOrCreateStateSet();
-        state->setMode(GL_LIGHTING, osg::StateAttribute::ON);
-        state->setTextureAttributeAndModes(0, new osg::Texture2D(osgDB::readImageFile(osgDB::findDataFile("Images/diffuse.png"))), osg::StateAttribute::ON);
-        
-        osg::MatrixTransform* transform = new osg::MatrixTransform();
-        transform->setMatrix(osg::Matrix::translate(offset));
-        transform->addChild(geode);
-        return transform;
-    } 
-
-    static osg::MatrixTransform* CreateColoredNormalMappedShape(osg::Vec3 offset, float size){
-        osg::Node* model = osgDB::readNodeFile(osgDB::findDataFile("Models/sphere.osg"));
-        if(!model){
-            OSG_FATAL << "ERROR: Failed to load model 'Models/sphere.osg' no normal mapping example avaliable." << std::endl;
-            return false;
-        }
-        
-        //generate tangent vectors
-        GenerateTangentsVisitor genTangents;
-        model->accept(genTangents);
-        
-        osg::StateSet* state = model->getOrCreateStateSet();
-        state->setMode(GL_LIGHTING, osg::StateAttribute::ON);
-        //state->setTextureAttributeAndModes(0, new osg::Texture2D(osgDB::readImageFile(osgDB::findDataFile("Images/diffuse.png"))), osg::StateAttribute::ON);
-        state->setTextureAttributeAndModes(1, new osg::Texture2D(osgDB::readImageFile(osgDB::findDataFile("Images/normal.png"))), osg::StateAttribute::ON);
-        
-        //make sure the model fits the scene scale
-        float radius = model->computeBound().radius();
-        OSG_FATAL << "RADIUS: " << radius << std::endl;
-        float scale = (size)/radius;
-        
-        
-        osg::MatrixTransform* transform = new osg::MatrixTransform();
-        transform->setMatrix(osg::Matrix::rotate(osg::DegreesToRadians(-90.0f), osg::Vec3(1,0,0)) * osg::Matrix::scale(osg::Vec3(scale,scale,scale)) * osg::Matrix::translate(offset));
-        transform->addChild(model);
-        return transform;
-    } 
-    
-    static osg::MatrixTransform* CreateTexturedNormalMappedShape(osg::Vec3 offset, float size){
-        osg::Node* model = osgDB::readNodeFile(osgDB::findDataFile("Models/sphere.osg"));
-        if(!model){
-            OSG_FATAL << "ERROR: Failed to load model 'Models/sphere.osg' no normal mapping example avaliable." << std::endl;
-            return false;
-        }
-        
-        //generate tangent vectors
-        GenerateTangentsVisitor genTangents;
-        model->accept(genTangents);
-
-        osg::StateSet* state = model->getOrCreateStateSet();
-        state->setMode(GL_LIGHTING, osg::StateAttribute::ON);
-        state->setTextureAttributeAndModes(0, new osg::Texture2D(osgDB::readImageFile(osgDB::findDataFile("Images/diffuse.png"))), osg::StateAttribute::ON);
-        state->setTextureAttributeAndModes(1, new osg::Texture2D(osgDB::readImageFile(osgDB::findDataFile("Images/normal.png"))), osg::StateAttribute::ON);
-
-        //make sure the model fits the scene scale
-        float radius = model->computeBound().radius();
-        OSG_FATAL << "RADIUS: " << radius << std::endl;
-        float scale = (size)/radius;
-
-        
-        osg::MatrixTransform* transform = new osg::MatrixTransform();
-        transform->setMatrix(osg::Matrix::rotate(osg::DegreesToRadians(-90.0f), osg::Vec3(1,0,0)) * osg::Matrix::scale(osg::Vec3(scale,scale,scale)) * osg::Matrix::translate(offset));
-        transform->addChild(model);
-        return transform;
-    } 
-    
-protected:
-
-};
\ No newline at end of file
diff --git a/src/applications/osgearth_viewerIOS/osgEarthViewerIOS/en.lproj/InfoPlist.strings b/src/applications/osgearth_viewerIOS/osgEarthViewerIOS/en.lproj/InfoPlist.strings
deleted file mode 100644
index 477b28f..0000000
--- a/src/applications/osgearth_viewerIOS/osgEarthViewerIOS/en.lproj/InfoPlist.strings
+++ /dev/null
@@ -1,2 +0,0 @@
-/* Localized versions of Info.plist keys */
-
diff --git a/src/applications/osgearth_viewerIOS/osgEarthViewerIOS/en.lproj/ViewController_iPad.xib b/src/applications/osgearth_viewerIOS/osgEarthViewerIOS/en.lproj/ViewController_iPad.xib
deleted file mode 100644
index 83de253..0000000
--- a/src/applications/osgearth_viewerIOS/osgEarthViewerIOS/en.lproj/ViewController_iPad.xib
+++ /dev/null
@@ -1,125 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<archive type="com.apple.InterfaceBuilder3.CocoaTouch.iPad.XIB" version="8.00">
-	<data>
-		<int key="IBDocument.SystemTarget">1296</int>
-		<string key="IBDocument.SystemVersion">11D50b</string>
-		<string key="IBDocument.InterfaceBuilderVersion">2182</string>
-		<string key="IBDocument.AppKitVersion">1138.32</string>
-		<string key="IBDocument.HIToolboxVersion">568.00</string>
-		<object class="NSMutableDictionary" key="IBDocument.PluginVersions">
-			<string key="NS.key.0">com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
-			<string key="NS.object.0">1181</string>
-		</object>
-		<array key="IBDocument.IntegratedClassDependencies">
-			<string>IBProxyObject</string>
-			<string>IBUIView</string>
-		</array>
-		<array key="IBDocument.PluginDependencies">
-			<string>com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
-		</array>
-		<object class="NSMutableDictionary" key="IBDocument.Metadata">
-			<string key="NS.key.0">PluginDependencyRecalculationVersion</string>
-			<integer value="1" key="NS.object.0"/>
-		</object>
-		<array class="NSMutableArray" key="IBDocument.RootObjects" id="1000">
-			<object class="IBProxyObject" id="372490531">
-				<string key="IBProxiedObjectIdentifier">IBFilesOwner</string>
-				<string key="targetRuntimeIdentifier">IBIPadFramework</string>
-			</object>
-			<object class="IBProxyObject" id="975951072">
-				<string key="IBProxiedObjectIdentifier">IBFirstResponder</string>
-				<string key="targetRuntimeIdentifier">IBIPadFramework</string>
-			</object>
-			<object class="IBUIView" id="191373211">
-				<reference key="NSNextResponder"/>
-				<int key="NSvFlags">274</int>
-				<string key="NSFrame">{{0, 20}, {768, 1004}}</string>
-				<reference key="NSSuperview"/>
-				<reference key="NSWindow"/>
-				<reference key="NSNextKeyView"/>
-				<object class="NSColor" key="IBUIBackgroundColor">
-					<int key="NSColorSpace">3</int>
-					<bytes key="NSWhite">MQA</bytes>
-					<object class="NSColorSpace" key="NSCustomColorSpace">
-						<int key="NSID">2</int>
-					</object>
-				</object>
-				<object class="IBUISimulatedStatusBarMetrics" key="IBUISimulatedStatusBarMetrics">
-					<int key="IBUIStatusBarStyle">2</int>
-				</object>
-				<string key="targetRuntimeIdentifier">IBIPadFramework</string>
-			</object>
-		</array>
-		<object class="IBObjectContainer" key="IBDocument.Objects">
-			<array class="NSMutableArray" key="connectionRecords">
-				<object class="IBConnectionRecord">
-					<object class="IBCocoaTouchOutletConnection" key="connection">
-						<string key="label">view</string>
-						<reference key="source" ref="372490531"/>
-						<reference key="destination" ref="191373211"/>
-					</object>
-					<int key="connectionID">3</int>
-				</object>
-			</array>
-			<object class="IBMutableOrderedSet" key="objectRecords">
-				<array key="orderedObjects">
-					<object class="IBObjectRecord">
-						<int key="objectID">0</int>
-						<array key="object" id="0"/>
-						<reference key="children" ref="1000"/>
-						<nil key="parent"/>
-					</object>
-					<object class="IBObjectRecord">
-						<int key="objectID">1</int>
-						<reference key="object" ref="191373211"/>
-						<reference key="parent" ref="0"/>
-					</object>
-					<object class="IBObjectRecord">
-						<int key="objectID">-1</int>
-						<reference key="object" ref="372490531"/>
-						<reference key="parent" ref="0"/>
-						<string key="objectName">File's Owner</string>
-					</object>
-					<object class="IBObjectRecord">
-						<int key="objectID">-2</int>
-						<reference key="object" ref="975951072"/>
-						<reference key="parent" ref="0"/>
-					</object>
-				</array>
-			</object>
-			<dictionary class="NSMutableDictionary" key="flattenedProperties">
-				<string key="-1.CustomClassName">ViewController</string>
-				<string key="-1.IBPluginDependency">com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
-				<string key="-2.CustomClassName">UIResponder</string>
-				<string key="-2.IBPluginDependency">com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
-				<string key="1.IBPluginDependency">com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
-			</dictionary>
-			<dictionary class="NSMutableDictionary" key="unlocalizedProperties"/>
-			<nil key="activeLocalization"/>
-			<dictionary class="NSMutableDictionary" key="localizations"/>
-			<nil key="sourceID"/>
-			<int key="maxID">3</int>
-		</object>
-		<object class="IBClassDescriber" key="IBDocument.Classes">
-			<array class="NSMutableArray" key="referencedPartialClassDescriptions">
-				<object class="IBPartialClassDescription">
-					<string key="className">ViewController</string>
-					<string key="superclassName">UIViewController</string>
-					<object class="IBClassDescriptionSource" key="sourceIdentifier">
-						<string key="majorKey">IBProjectSource</string>
-						<string key="minorKey">./Classes/ViewController.h</string>
-					</object>
-				</object>
-			</array>
-		</object>
-		<int key="IBDocument.localizationMode">0</int>
-		<string key="IBDocument.TargetRuntimeIdentifier">IBIPadFramework</string>
-		<object class="NSMutableDictionary" key="IBDocument.PluginDeclaredDependencyDefaults">
-			<string key="NS.key.0">com.apple.InterfaceBuilder.CocoaTouchPlugin.iPhoneOS</string>
-			<real value="1296" key="NS.object.0"/>
-		</object>
-		<bool key="IBDocument.PluginDeclaredDependenciesTrackSystemTargetVersion">YES</bool>
-		<int key="IBDocument.defaultPropertyAccessControl">3</int>
-		<string key="IBCocoaTouchPluginVersion">1181</string>
-	</data>
-</archive>
diff --git a/src/applications/osgearth_viewerIOS/osgEarthViewerIOS/en.lproj/ViewController_iPhone.xib b/src/applications/osgearth_viewerIOS/osgEarthViewerIOS/en.lproj/ViewController_iPhone.xib
deleted file mode 100644
index 78d0b27..0000000
--- a/src/applications/osgearth_viewerIOS/osgEarthViewerIOS/en.lproj/ViewController_iPhone.xib
+++ /dev/null
@@ -1,124 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<archive type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="8.00">
-	<data>
-		<int key="IBDocument.SystemTarget">1296</int>
-		<string key="IBDocument.SystemVersion">11E53</string>
-		<string key="IBDocument.InterfaceBuilderVersion">2182</string>
-		<string key="IBDocument.AppKitVersion">1138.47</string>
-		<string key="IBDocument.HIToolboxVersion">569.00</string>
-		<object class="NSMutableDictionary" key="IBDocument.PluginVersions">
-			<string key="NS.key.0">com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
-			<string key="NS.object.0">1181</string>
-		</object>
-		<array key="IBDocument.IntegratedClassDependencies">
-			<string>IBProxyObject</string>
-			<string>IBUIView</string>
-		</array>
-		<array key="IBDocument.PluginDependencies">
-			<string>com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
-		</array>
-		<object class="NSMutableDictionary" key="IBDocument.Metadata">
-			<string key="NS.key.0">PluginDependencyRecalculationVersion</string>
-			<integer value="1" key="NS.object.0"/>
-		</object>
-		<array class="NSMutableArray" key="IBDocument.RootObjects" id="1000">
-			<object class="IBProxyObject" id="841351856">
-				<string key="IBProxiedObjectIdentifier">IBFilesOwner</string>
-				<string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
-			</object>
-			<object class="IBProxyObject" id="371349661">
-				<string key="IBProxiedObjectIdentifier">IBFirstResponder</string>
-				<string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
-			</object>
-			<object class="IBUIView" id="184854543">
-				<reference key="NSNextResponder"/>
-				<int key="NSvFlags">274</int>
-				<string key="NSFrameSize">{320, 460}</string>
-				<reference key="NSSuperview"/>
-				<reference key="NSWindow"/>
-				<reference key="NSNextKeyView"/>
-				<object class="NSColor" key="IBUIBackgroundColor">
-					<int key="NSColorSpace">3</int>
-					<bytes key="NSWhite">MQA</bytes>
-					<object class="NSColorSpace" key="NSCustomColorSpace">
-						<int key="NSID">2</int>
-					</object>
-				</object>
-				<bool key="IBUIClearsContextBeforeDrawing">NO</bool>
-				<string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
-			</object>
-		</array>
-		<object class="IBObjectContainer" key="IBDocument.Objects">
-			<array class="NSMutableArray" key="connectionRecords">
-				<object class="IBConnectionRecord">
-					<object class="IBCocoaTouchOutletConnection" key="connection">
-						<string key="label">view</string>
-						<reference key="source" ref="841351856"/>
-						<reference key="destination" ref="184854543"/>
-					</object>
-					<int key="connectionID">3</int>
-				</object>
-			</array>
-			<object class="IBMutableOrderedSet" key="objectRecords">
-				<array key="orderedObjects">
-					<object class="IBObjectRecord">
-						<int key="objectID">0</int>
-						<array key="object" id="0"/>
-						<reference key="children" ref="1000"/>
-						<nil key="parent"/>
-					</object>
-					<object class="IBObjectRecord">
-						<int key="objectID">-1</int>
-						<reference key="object" ref="841351856"/>
-						<reference key="parent" ref="0"/>
-						<string key="objectName">File's Owner</string>
-					</object>
-					<object class="IBObjectRecord">
-						<int key="objectID">-2</int>
-						<reference key="object" ref="371349661"/>
-						<reference key="parent" ref="0"/>
-					</object>
-					<object class="IBObjectRecord">
-						<int key="objectID">2</int>
-						<reference key="object" ref="184854543"/>
-						<array class="NSMutableArray" key="children"/>
-						<reference key="parent" ref="0"/>
-					</object>
-				</array>
-			</object>
-			<dictionary class="NSMutableDictionary" key="flattenedProperties">
-				<string key="-1.CustomClassName">ViewController</string>
-				<string key="-1.IBPluginDependency">com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
-				<string key="-2.CustomClassName">UIResponder</string>
-				<string key="-2.IBPluginDependency">com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
-				<string key="2.IBPluginDependency">com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
-			</dictionary>
-			<dictionary class="NSMutableDictionary" key="unlocalizedProperties"/>
-			<nil key="activeLocalization"/>
-			<dictionary class="NSMutableDictionary" key="localizations"/>
-			<nil key="sourceID"/>
-			<int key="maxID">6</int>
-		</object>
-		<object class="IBClassDescriber" key="IBDocument.Classes">
-			<array class="NSMutableArray" key="referencedPartialClassDescriptions">
-				<object class="IBPartialClassDescription">
-					<string key="className">ViewController</string>
-					<string key="superclassName">UIViewController</string>
-					<object class="IBClassDescriptionSource" key="sourceIdentifier">
-						<string key="majorKey">IBProjectSource</string>
-						<string key="minorKey">./Classes/ViewController.h</string>
-					</object>
-				</object>
-			</array>
-		</object>
-		<int key="IBDocument.localizationMode">0</int>
-		<string key="IBDocument.TargetRuntimeIdentifier">IBCocoaTouchFramework</string>
-		<object class="NSMutableDictionary" key="IBDocument.PluginDeclaredDependencyDefaults">
-			<string key="NS.key.0">com.apple.InterfaceBuilder.CocoaTouchPlugin.iPhoneOS</string>
-			<real value="1296" key="NS.object.0"/>
-		</object>
-		<bool key="IBDocument.PluginDeclaredDependenciesTrackSystemTargetVersion">YES</bool>
-		<int key="IBDocument.defaultPropertyAccessControl">3</int>
-		<string key="IBCocoaTouchPluginVersion">1181</string>
-	</data>
-</archive>
diff --git a/src/applications/osgearth_viewerIOS/osgEarthViewerIOS/osgEarthViewerIOS-Prefix.pch b/src/applications/osgearth_viewerIOS/osgEarthViewerIOS/osgEarthViewerIOS-Prefix.pch
deleted file mode 100644
index 15e6a77..0000000
--- a/src/applications/osgearth_viewerIOS/osgEarthViewerIOS/osgEarthViewerIOS-Prefix.pch
+++ /dev/null
@@ -1,14 +0,0 @@
-//
-// Prefix header for all source files of the 'osgEarthViewerIOS' target in the 'osgEarthViewerIOS' project
-//
-
-#import <Availability.h>
-
-#ifndef __IPHONE_5_0
-#warning "This project uses features only available in iOS SDK 5.0 and later."
-#endif
-
-#ifdef __OBJC__
-    #import <UIKit/UIKit.h>
-    #import <Foundation/Foundation.h>
-#endif
diff --git a/src/applications/osgearth_viewerIOS/osgEarthViewerIOS/osgPlugins.h b/src/applications/osgearth_viewerIOS/osgEarthViewerIOS/osgPlugins.h
deleted file mode 100755
index 0dddc1c..0000000
--- a/src/applications/osgearth_viewerIOS/osgEarthViewerIOS/osgPlugins.h
+++ /dev/null
@@ -1,84 +0,0 @@
-#pragma once 
-
-//This file is used to force the linking of the osg and hogbox plugins
-//as we our doing a static build we can't depend on the loading of the
-//dynamic libs to add the plugins to the registries
-
-#include <osgViewer/GraphicsWindow>
-#include <osgDB/Registry>
-
-//windowing system
-#ifndef ANDROID
-USE_GRAPICSWINDOW_IMPLEMENTATION(IOS)
-#endif
-
-
-//osg plugins
-
-USE_OSGPLUGIN(OpenFlight)
-USE_OSGPLUGIN(obj)
-USE_OSGPLUGIN(shp)
-USE_OSGPLUGIN(ive)
-
-//depreceated osg format
-USE_OSGPLUGIN(osg)
-USE_DOTOSGWRAPPER_LIBRARY(osg)
-
-
-USE_OSGPLUGIN(osg2)
-USE_SERIALIZER_WRAPPER_LIBRARY(osg)
-USE_SERIALIZER_WRAPPER_LIBRARY(osgAnimation)
-
-USE_OSGPLUGIN(rot)
-USE_OSGPLUGIN(scale)
-USE_OSGPLUGIN(trans)
-
-
-//image files
-#ifndef ANDROID
-USE_OSGPLUGIN(tiff)
-USE_OSGPLUGIN(imageio)
-#else
-USE_OSGPLUGIN(png)
-USE_OSGPLUGIN(jpeg)
-#endif
-
-USE_OSGPLUGIN(zip)
-USE_OSGPLUGIN(curl)
-USE_OSGPLUGIN(freetype)
-
-
-USE_OSGPLUGIN(kml)
-USE_OSGPLUGIN(osgearth_feature_wfs)
-USE_OSGPLUGIN(osgearth_feature_tfs)
-USE_OSGPLUGIN(osgearth_tms)
-USE_OSGPLUGIN(osgearth_wms)
-USE_OSGPLUGIN(osgearth_label_overlay)
-USE_OSGPLUGIN(osgearth_xyz)
-USE_OSGPLUGIN(osgearth_label_annotation)
-USE_OSGPLUGIN(osgearth_mask_feature)
-USE_OSGPLUGIN(osgearth_model_feature_geom)
-USE_OSGPLUGIN(osgearth_agglite)
-USE_OSGPLUGIN(osgearth_feature_ogr)
-USE_OSGPLUGIN(osgearth_model_feature_stencil)
-USE_OSGPLUGIN(osgearth_vdatum_egm2008)
-USE_OSGPLUGIN(osgearth_model_simple)
-//USE_OSGPLUGIN(osgearth_engine_osgterrain)
-USE_OSGPLUGIN(osgearth_engine_quadtree)
-USE_OSGPLUGIN(osgearth_engine_mp)
-USE_OSGPLUGIN(osgearth_vdatum_egm96)
-USE_OSGPLUGIN(osgearth_ocean_surface)
-USE_OSGPLUGIN(osgearth_debug)
-USE_OSGPLUGIN(osgearth_mbtiles)
-USE_OSGPLUGIN(osgearth_vdatum_egm84)
-USE_OSGPLUGIN(osgearth_tileservice)
-USE_OSGPLUGIN(osgearth_yahoo)
-USE_OSGPLUGIN(osgearth_arcgis_map_cache)
-USE_OSGPLUGIN(osgearth_tilecache)
-USE_OSGPLUGIN(osgearth_wcs)
-USE_OSGPLUGIN(osgearth_gdal)
-USE_OSGPLUGIN(earth)
-USE_OSGPLUGIN(osgearth_cache_filesystem)
-USE_OSGPLUGIN(osgearth_arcgis)
-USE_OSGPLUGIN(osgearth_osg)
-USE_OSGPLUGIN(osgearth_scriptengine_javascriptcore)
diff --git a/src/applications/osgearth_viewerIOS/osgEarthViewerIOS/osgearth_viewerIOS.cpp b/src/applications/osgearth_viewerIOS/osgEarthViewerIOS/osgearth_viewerIOS.cpp
deleted file mode 100755
index 2e970bf..0000000
--- a/src/applications/osgearth_viewerIOS/osgEarthViewerIOS/osgearth_viewerIOS.cpp
+++ /dev/null
@@ -1,66 +0,0 @@
-/* -*-c++-*- */
-/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2012 Pelican Mapping
-* http://osgearth.org
-*
-* osgEarth is free software; you can redistribute it and/or modify
-* it under the terms of the GNU Lesser General Public License as published by
-* the Free Software Foundation; either version 2 of the License, or
-* (at your option) any later version.
-*
-* This program is distributed in the hope that it will be useful,
-* but WITHOUT ANY WARRANTY; without even the implied warranty of
-* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-* GNU Lesser General Public License for more details.
-*
-* You should have received a copy of the GNU Lesser General Public License
-* along with this program.  If not, see <http://www.gnu.org/licenses/>
-*/
-
-#include <osg/Notify>
-#include <osgViewer/Viewer>
-#include <osgEarthUtil/EarthManipulator>
-#include <osgEarthUtil/ExampleResources>
-
-#define LC "[viewer] "
-
-using namespace osgEarth::Util;
-
-//------------------------------------------------------------------------
-
-int
-main(int argc, char** argv)
-{
-    osg::ArgumentParser arguments(&argc,argv);
-    if ( arguments.read("--stencil") )
-        osg::DisplaySettings::instance()->setMinimumNumStencilBits( 8 );
-
-    // create a viewer:
-    osgViewer::Viewer viewer(arguments);
-
-    // install our default manipulator (do this before calling load)
-    viewer.setCameraManipulator( new EarthManipulator() );
-
-    // load an earth file, and support all or our example command-line options
-    // and earth file <external> tags
-    osg::Node* node = MapNodeHelper().load( arguments, &viewer );
-    if ( node )
-    {
-        viewer.setSceneData( node );
-
-        // configure the near/far so we don't clip things that are up close
-        viewer.getCamera()->setNearFarRatio(0.00002);
-
-        // osgEarth benefits from pre-compilation of GL objects in the pager. In newer versions of
-        // OSG, this activates OSG's IncrementalCompileOpeartion in order to avoid frame breaks.
-        viewer.getDatabasePager()->setDoPreCompile( true );
-
-        return viewer.run();
-    }
-    else
-    {
-        OE_NOTICE 
-            << "\nUsage: " << argv[0] << " file.earth" << std::endl
-            << MapNodeHelper().usage() << std::endl;
-    }
-}
diff --git a/src/applications/osgearth_viewerIOS/osgPlugins.h b/src/applications/osgearth_viewerIOS/osgPlugins.h
new file mode 100755
index 0000000..89f93a9
--- /dev/null
+++ b/src/applications/osgearth_viewerIOS/osgPlugins.h
@@ -0,0 +1,206 @@
+#pragma once 
+
+//This file is used to force the linking of the osg plugins
+//as we our doing a static build we can't depend on the loading of the
+//dynamic libs to add the plugins to the registries
+
+#include <osgViewer/GraphicsWindow>
+#include <osgDB/Registry>
+#include <osgEarth/ColorFilter>
+#include <osgEarthFeatures/Filter>
+#include <osgEarthSymbology/Symbol>
+#include <osgEarthAnnotation/AnnotationRegistry>
+
+//windowing system
+#ifndef ANDROID
+USE_GRAPICSWINDOW_IMPLEMENTATION(IOS)
+#endif
+
+
+//osg plugins
+USE_OSGPLUGIN(zip)
+USE_OSGPLUGIN(curl)
+USE_OSGPLUGIN(freetype)
+
+USE_OSGPLUGIN(tiff)
+USE_OSGPLUGIN(rgb)
+#ifndef ANDROID
+USE_OSGPLUGIN(imageio)
+#else
+USE_OSGPLUGIN(png)
+USE_OSGPLUGIN(jpeg)
+USE_OSGPLUGIN(bmp);
+#endif
+
+USE_OSGPLUGIN(OpenFlight)
+USE_OSGPLUGIN(obj)
+USE_OSGPLUGIN(shp)
+//USE_OSGPLUGIN(ive)
+
+USE_OSGPLUGIN(osg)
+USE_DOTOSGWRAPPER_LIBRARY(osg)
+//USE_DOTOSGWRAPPER_LIBRARY(osgAnimation)
+USE_DOTOSGWRAPPER_LIBRARY(osgFX)
+//USE_DOTOSGWRAPPER_LIBRARY(osgParticle)
+USE_DOTOSGWRAPPER_LIBRARY(osgShadow)
+USE_DOTOSGWRAPPER_LIBRARY(osgSim)
+USE_DOTOSGWRAPPER_LIBRARY(osgTerrain)
+USE_DOTOSGWRAPPER_LIBRARY(osgText)
+USE_DOTOSGWRAPPER_LIBRARY(osgViewer)
+//USE_DOTOSGWRAPPER_LIBRARY(osgVolume)
+
+USE_OSGPLUGIN(osg2)
+USE_SERIALIZER_WRAPPER_LIBRARY(osg)
+//USE_SERIALIZER_WRAPPER_LIBRARY(osgAnimation)
+USE_SERIALIZER_WRAPPER_LIBRARY(osgFX)
+USE_SERIALIZER_WRAPPER_LIBRARY(osgManipulator)
+//USE_SERIALIZER_WRAPPER_LIBRARY(osgParticle)
+USE_SERIALIZER_WRAPPER_LIBRARY(osgShadow)
+USE_SERIALIZER_WRAPPER_LIBRARY(osgSim)
+USE_SERIALIZER_WRAPPER_LIBRARY(osgTerrain)
+USE_SERIALIZER_WRAPPER_LIBRARY(osgText)
+//USE_SERIALIZER_WRAPPER_LIBRARY(osgUtil)
+//USE_SERIALIZER_WRAPPER_LIBRARY(osgViewer)
+//USE_SERIALIZER_WRAPPER_LIBRARY(osgVolume)
+
+USE_OSGPLUGIN(rot)
+USE_OSGPLUGIN(scale)
+USE_OSGPLUGIN(trans)
+
+// osgearth plugins
+USE_OSGPLUGIN(osgearth_agglite)
+USE_OSGPLUGIN(osgearth_arcgis)
+USE_OSGPLUGIN(osgearth_arcgis_map_cache)
+USE_OSGPLUGIN(osgearth_bing)
+USE_OSGPLUGIN(osgearth_bumpmap)
+USE_OSGPLUGIN(osgearth_cache_filesystem)
+USE_OSGPLUGIN(osgearth_colorramp)
+USE_OSGPLUGIN(osgearth_debug)
+USE_OSGPLUGIN(osgearth_detail)
+USE_OSGPLUGIN(earth)
+USE_OSGPLUGIN(osgearth_engine_byo)
+USE_OSGPLUGIN(osgearth_engine_mp)
+USE_OSGPLUGIN(osgearth_engine_rex)
+USE_OSGPLUGIN(osgearth_feature_elevation)
+USE_OSGPLUGIN(osgearth_feature_ogr)
+USE_OSGPLUGIN(osgearth_feature_raster)
+USE_OSGPLUGIN(osgearth_feature_tfs)
+USE_OSGPLUGIN(osgearth_feature_wfs)
+USE_OSGPLUGIN(osgearth_feature_xyz)
+USE_OSGPLUGIN(osgearth_featurefilter_intersect)
+USE_OSGPLUGIN(osgearth_featurefilter_join)
+USE_OSGPLUGIN(osgearth_gdal)
+USE_OSGPLUGIN(kml)
+USE_OSGPLUGIN(osgearth_label_annotation)
+USE_OSGPLUGIN(osgearth_mapinspector)
+USE_OSGPLUGIN(osgearth_mask_feature)
+USE_OSGPLUGIN(osgearth_mbtiles)
+USE_OSGPLUGIN(osgearth_model_feature_geom)
+USE_OSGPLUGIN(osgearth_model_simple)
+USE_OSGPLUGIN(osgearth_monitor)
+USE_OSGPLUGIN(osgearth_noise)
+USE_OSGPLUGIN(osgearth_ocean_simple)
+USE_OSGPLUGIN(osgearth_osg)
+USE_OSGPLUGIN(osgearth_quadkey)
+USE_OSGPLUGIN(osgearth_refresh)
+USE_OSGPLUGIN(osgearth_scriptengine_javascript)
+USE_OSGPLUGIN(osgearth_sky_gl)
+USE_OSGPLUGIN(osgearth_sky_simple)
+USE_OSGPLUGIN(osgearth_skyview)
+USE_OSGPLUGIN(osgearth_splat_mask)
+USE_OSGPLUGIN(template)
+USE_OSGPLUGIN(osgearth_template_matclass)
+USE_OSGPLUGIN(osgearth_terrainshader)
+USE_OSGPLUGIN(osgearth_tilecache)
+USE_OSGPLUGIN(osgearth_tileindex)
+USE_OSGPLUGIN(osgearth_tileservice)
+USE_OSGPLUGIN(osgearth_tms)
+USE_OSGPLUGIN(osgearth_vdatum_egm84)
+USE_OSGPLUGIN(osgearth_vdatum_egm96)
+USE_OSGPLUGIN(osgearth_vdatum_egm2008)
+USE_OSGPLUGIN(osgearth_viewpoints)
+USE_OSGPLUGIN(osgearth_vpb)
+USE_OSGPLUGIN(osgearth_wcs)
+USE_OSGPLUGIN(osgearth_wms)
+USE_OSGPLUGIN(osgearth_xyz)
+USE_OSGPLUGIN(osgearth_yahoo)
+
+// extensions
+//USE_OSGPLUGIN(osgearth_annotations)
+//USE_OSGPLUGIN(osgearth_bumpmap)
+USE_OSGPLUGIN(osgearth_bump_map)
+USE_OSGPLUGIN(osgearth_contourmap)
+USE_OSGPLUGIN(osgearth_contour_map)
+//USE_OSGPLUGIN(osgearth_sky_gl)
+//USE_OSGPLUGIN(osgearth_graticule)
+USE_OSGPLUGIN(osgearth_lod_blending)
+//USE_OSGPLUGIN(osgearth_mapinspector)
+//USE_OSGPLUGIN(osgearth_mgrs_graticule)
+//USE_OSGPLUGIN(osgearth_noise)
+USE_OSGPLUGIN(osgearth_screen_space_layout)
+USE_OSGPLUGIN(osgearth_decluttering)
+//USE_OSGPLUGIN(osgearth_ocean_simple)
+//USE_OSGPLUGIN(osgearth_sky_simple)
+//USE_OSGPLUGIN(osgearth_splat)
+//USE_OSGPLUGIN(osgearth_terrainshader)
+//USE_OSGPLUGIN(osgearth_utm_graticule)
+
+// annotations
+USE_OSGEARTH_ANNOTATION(circle)
+USE_OSGEARTH_ANNOTATION(ellipse)
+USE_OSGEARTH_ANNOTATION(feature)
+USE_OSGEARTH_ANNOTATION(imageoverlay)
+USE_OSGEARTH_ANNOTATION(label)
+USE_OSGEARTH_ANNOTATION(local_geometry)
+USE_OSGEARTH_ANNOTATION(place)
+USE_OSGEARTH_ANNOTATION(rectangle)
+
+// layers
+USE_OSGEARTH_LAYER(annotations)
+USE_OSGEARTH_LAYER(elevation)
+USE_OSGEARTH_LAYER(feature_mask)
+USE_OSGEARTH_LAYER(feature_model)
+USE_OSGEARTH_LAYER(feature_source)
+USE_OSGEARTH_LAYER(flattened_elevation)
+USE_OSGEARTH_LAYER(fractal_elevation)
+USE_OSGEARTH_LAYER(image)
+USE_OSGEARTH_LAYER(land_cover_dictionary)
+USE_OSGEARTH_LAYER(land_cover)
+USE_OSGEARTH_LAYER(model)
+USE_OSGEARTH_LAYER(multi_elevation)
+//USE_OSGEARTH_LAYER(road_surface)
+USE_OSGEARTH_LAYER(simple_ocean)
+USE_OSGEARTH_LAYER(video)
+
+// simple symbols
+USE_OSGEARTH_SIMPLE_SYMBOL(altitude)
+USE_OSGEARTH_SIMPLE_SYMBOL(bbox)
+USE_OSGEARTH_SIMPLE_SYMBOL(billboard)
+USE_OSGEARTH_SIMPLE_SYMBOL(coverage)
+USE_OSGEARTH_SIMPLE_SYMBOL(extrusion)
+USE_OSGEARTH_SIMPLE_SYMBOL(icon)
+USE_OSGEARTH_SIMPLE_SYMBOL(line)
+USE_OSGEARTH_SIMPLE_SYMBOL(marker)
+USE_OSGEARTH_SIMPLE_SYMBOL(model)
+USE_OSGEARTH_SIMPLE_SYMBOL(point)
+USE_OSGEARTH_SIMPLE_SYMBOL(polygon)
+USE_OSGEARTH_SIMPLE_SYMBOL(render)
+USE_OSGEARTH_SIMPLE_SYMBOL(skin)
+USE_OSGEARTH_SIMPLE_SYMBOL(text)
+
+// simple feature filters
+USE_OSGEARTH_SIMPLE_FEATUREFILTER(buffer)
+USE_OSGEARTH_SIMPLE_FEATUREFILTER(convert)
+USE_OSGEARTH_SIMPLE_FEATUREFILTER(resample)
+USE_OSGEARTH_SIMPLE_FEATUREFILTER(script)
+
+// color filters
+USE_OSGEARTH_COLORFILTER(brightness_contrast)
+USE_OSGEARTH_COLORFILTER(chroma_key)
+USE_OSGEARTH_COLORFILTER(cmyk)
+USE_OSGEARTH_COLORFILTER(gamma)
+USE_OSGEARTH_COLORFILTER(glsl)
+USE_OSGEARTH_COLORFILTER(hsl)
+USE_OSGEARTH_COLORFILTER(night)
+USE_OSGEARTH_COLORFILTER(rgb)
+
diff --git a/src/applications/osgearth_wfs/osgearth_wfs.cpp b/src/applications/osgearth_wfs/osgearth_wfs.cpp
index 351703b..7105550 100644
--- a/src/applications/osgearth_wfs/osgearth_wfs.cpp
+++ b/src/applications/osgearth_wfs/osgearth_wfs.cpp
@@ -32,13 +32,14 @@
 #include <osgEarthDrivers/feature_wfs/WFSFeatureOptions>
 
 // include for feature geometry model renderer:
-#include <osgEarthDrivers/model_feature_geom/FeatureGeomModelOptions>
+#include <osgEarthAnnotation/FeatureNode>
 #include <osgEarthSymbology/Style>
 
 
 #define LC "[wfs example] "
 
 using namespace osgEarth;
+using namespace osgEarth::Annotation;
 using namespace osgEarth::Util;
 using namespace osgEarth::Features;
 using namespace osgEarth::Symbology;
@@ -67,6 +68,14 @@ main(int argc, char** argv)
     viewer.getCamera()->setSmallFeatureCullingPixelSize(-1.0f);
     viewer.getCamera()->setNearFarRatio(0.00002);
 
+    // Get the bounds from the command line.
+    Bounds bounds;
+    double xmin = DBL_MAX, ymin = DBL_MAX, xmax = DBL_MIN, ymax = DBL_MIN;
+    while (arguments.read("--bounds", xmin, ymin, xmax, ymax))
+    {
+        bounds.xMin() = xmin, bounds.yMin() = ymin, bounds.xMax() = xmax, bounds.yMax() = ymax;
+    }        
+
     // load an earth file, and support all or our example command-line options
     // and earth file <external> tags    
     osg::Node* node = MapNodeHelper().load( arguments, &viewer );
@@ -78,34 +87,35 @@ main(int argc, char** argv)
 
         // Create the WFS driver:
         osgEarth::Drivers::WFSFeatureOptions wfs;
-        wfs.url()          = osgEarth::URI("http://demo.opengeo.org/geoserver/wfs"); 
-        wfs.typeName()     = "states"; 
-        wfs.outputFormat() = "json";     // JSON or GML
-
-        // Configure a rendering style:
+        wfs.url()          = osgEarth::URI("http://demo.mapserver.org/cgi-bin/wfs"); 
+        wfs.typeName()     = "cities"; 
+        wfs.outputFormat() = "gml2";     // JSON or GML
+
+        // Create the feature source from the options
+        osg::ref_ptr< FeatureSource > featureSource = FeatureSourceFactory::create(wfs);
+        Status s = featureSource->open();
+
+        // Set the query with the bounds if one was specified.
+        Query query;
+        if (bounds.isValid())
+        {
+            query.bounds() = bounds;
+        }
+
+        // Get the features
+        osg::ref_ptr< FeatureCursor > cursor = featureSource->createFeatureCursor(query);
+        FeatureList features;
+        cursor->fill(features);
+        OE_NOTICE << "Got " << features.size() << " features" << std::endl;
+
+        // Create a style
         Style style;
-        style.setName( "states" ); 
-
-        LineSymbol* line = style.getOrCreate<LineSymbol>(); 
-        line->stroke()->color() = Color::Yellow; 
-        line->stroke()->width() = 5.0f;
-        line->stroke()->widthUnits() = Units::PIXELS;
-
-        AltitudeSymbol* alt = style.getOrCreate<AltitudeSymbol>();
-        alt->clamping()  = AltitudeSymbol::CLAMP_TO_TERRAIN;
-        alt->technique() = AltitudeSymbol::TECHNIQUE_DRAPE;
-
-        // Configure a model layer to render the features:
-        osgEarth::Drivers::FeatureGeomModelOptions geom; 
-        geom.featureOptions() = wfs;
-        geom.styles()         = new StyleSheet(); 
-        geom.styles()->addStyle(style); 
-
-        // Make the new layer and add it to the map.
-        ModelLayerOptions layerOptions("states", geom); 
-        ModelLayer* layer = new ModelLayer(layerOptions); 
-        mapNode->getMap()->addModelLayer(layer);
-        
+        style.getOrCreateSymbol<TextSymbol>()->content() = StringExpression("[NAME]");
+
+        // Create the FeatureNode with the features and the style.
+        osg::ref_ptr< FeatureNode > featureNode = new FeatureNode(mapNode, features, style);
+        mapNode->addChild(featureNode.get());                
+
         viewer.setSceneData( node );
         return viewer.run();
     }
diff --git a/src/osgEarth/AlphaEffect b/src/osgEarth/AlphaEffect
deleted file mode 100644
index 4c8b277..0000000
--- a/src/osgEarth/AlphaEffect
+++ /dev/null
@@ -1,73 +0,0 @@
-/* -*-c++-*- */
-/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2012 Pelican Mapping
-* http://osgearth.org
-*
-* osgEarth is free software; you can redistribute it and/or modify
-* it under the terms of the GNU Lesser General Public License as published by
-* the Free Software Foundation; either version 2 of the License, or
-* (at your option) any later version.
-*
-* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
-* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
-* IN THE SOFTWARE.
-*
-* You should have received a copy of the GNU Lesser General Public License
-* along with this program.  If not, see <http://www.gnu.org/licenses/>
-*/
-#ifndef OSGEARTH_ALPHA_EFFECT_H
-#define OSGEARTH_ALPHA_EFFECT_H
-
-#include <osgEarth/Common>
-#include <osg/StateSet>
-#include <osg/Uniform>
-#include <osg/observer_ptr>
-
-namespace osgEarth
-{
-    /**
-     * Shader effect that lets you adjust the alpha channel.
-     */
-    class OSGEARTH_EXPORT AlphaEffect : public osg::Referenced
-    {
-    public:
-        /** constructs a new effect */
-        AlphaEffect();
-
-        /** contructs a new effect and attaches it to a stateset. */
-        AlphaEffect(osg::StateSet* stateset);
-
-    public:
-        /** The alpha channel value [0..1] */
-        void setAlpha(float value);
-        float getAlpha() const;
-
-    public:
-        /** attach this effect to a stateset. */
-        void attach(osg::StateSet* stateset);
-
-        /** detach this effect from any attached statesets. */
-        void detach();
-        /** detach this effect from a stateset. */
-        void detach(osg::StateSet* stateset);
-
-    protected:
-        virtual ~AlphaEffect();
-
-        typedef std::list< osg::observer_ptr<osg::StateSet> > StateSetList;
-
-        bool                       _installed;
-        StateSetList               _statesets;
-        osg::ref_ptr<osg::Uniform> _alphaUniform;
-
-        void init();
-        void install(osg::StateSet*);
-    };
-
-} // namespace osgEarth::Util
-
-#endif // OSGEARTH_ALPHA_EFFECT_H
diff --git a/src/osgEarth/AlphaEffect.cpp b/src/osgEarth/AlphaEffect.cpp
deleted file mode 100644
index 1dbb5e6..0000000
--- a/src/osgEarth/AlphaEffect.cpp
+++ /dev/null
@@ -1,151 +0,0 @@
-/* -*-c++-*- */
-/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2016 Pelican Mapping
-* http://osgearth.org
-*
-* osgEarth is free software; you can redistribute it and/or modify
-* it under the terms of the GNU Lesser General Public License as published by
-* the Free Software Foundation; either version 2 of the License, or
-* (at your option) any later version.
-*
-* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
-* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
-* IN THE SOFTWARE.
-*
-* You should have received a copy of the GNU Lesser General Public License
-* along with this program.  If not, see <http://www.gnu.org/licenses/>
-*/
-
-#include <osgEarth/AlphaEffect>
-#include <osgEarth/StringUtils>
-#include <osgEarth/VirtualProgram>
-#include <osgEarth/Registry>
-#include <osgEarth/Capabilities>
-#include <osgEarth/Shaders>
-
-using namespace osgEarth;
-
-#define LC "[AlphaEffect] "
-
-AlphaEffect::AlphaEffect()
-{
-    init();
-}
-
-AlphaEffect::AlphaEffect(osg::StateSet* stateset)
-{
-    init();
-    attach( stateset );
-}
-
-void
-AlphaEffect::init()
-{
-    _installed = false;
-    _alphaUniform = new osg::Uniform(osg::Uniform::FLOAT, "oe_alphaEffect_alpha");
-    _alphaUniform->set( 1.0f );
-}
-
-AlphaEffect::~AlphaEffect()
-{
-    detach();
-}
-
-void
-AlphaEffect::setAlpha(float value)
-{
-    float oldValue;
-    _alphaUniform->get(oldValue);
-
-    if (value != oldValue)
-    {        
-        _alphaUniform->set( value );
-
-        if (!_installed)
-        {
-            for (StateSetList::iterator it = _statesets.begin(); it != _statesets.end(); ++it)
-            {
-                osg::ref_ptr<osg::StateSet> stateset;
-                if ((*it).lock(stateset))
-                {
-                    install(stateset.get());
-                }
-            }
-            _installed = true;
-        }
-    }
-}
-
-float
-AlphaEffect::getAlpha() const
-{
-    float value = 1.0f;
-    _alphaUniform->get(value);
-    return value;
-}
-
-void
-AlphaEffect::attach(osg::StateSet* stateset)
-{
-    if ( stateset )
-    {
-        _statesets.push_back(stateset);
-        if (_installed)
-        {
-            install(stateset);
-        }
-    }
-}
-
-void
-AlphaEffect::detach()
-{
-    if (_installed)
-    {
-        for (StateSetList::iterator it = _statesets.begin(); it != _statesets.end(); ++it)
-        {
-            osg::ref_ptr<osg::StateSet> stateset;
-            if ( (*it).lock(stateset) )
-            {
-                detach( stateset );
-                (*it) = 0L;
-            }
-        }
-    }
-    _statesets.clear();
-}
-
-void
-AlphaEffect::detach(osg::StateSet* stateset)
-{
-    if ( stateset && _installed )
-    {
-        stateset->removeUniform( _alphaUniform.get() );
-        VirtualProgram* vp = VirtualProgram::get( stateset );
-        if ( vp )
-        {
-            Shaders pkg;
-            pkg.unload( vp, pkg.AlphaEffectFragment );
-        }
-    }
-}
-
-void
-AlphaEffect::install(osg::StateSet* stateset)
-{
-    if (stateset)
-    {
-        VirtualProgram* vp = VirtualProgram::getOrCreate(stateset);
-        if (vp)
-        {
-            vp->setName( "osgEarth.AlphaEffect" );
-            Shaders pkg;
-            pkg.load(vp, pkg.AlphaEffectFragment);
-            stateset->addUniform(_alphaUniform.get());
-        }
-    }
-}
\ No newline at end of file
diff --git a/src/osgEarth/AlphaEffect.frag.glsl b/src/osgEarth/AlphaEffect.frag.glsl
deleted file mode 100644
index 1c90358..0000000
--- a/src/osgEarth/AlphaEffect.frag.glsl
+++ /dev/null
@@ -1,12 +0,0 @@
-#version $GLSL_VERSION_STR
-
-#pragma vp_entryPoint oe_alphaEffect_frag
-#pragma vp_location   fragment_coloring
-#pragma vp_order      0.5
-
-uniform float oe_alphaEffect_alpha;
-
-void oe_alphaEffect_frag(inout vec4 color)
-{
-    color = color * oe_alphaEffect_alpha;
-}
diff --git a/src/osgEarth/AutoScale.cpp b/src/osgEarth/AutoScale.cpp
deleted file mode 100644
index 7dbdbe5..0000000
--- a/src/osgEarth/AutoScale.cpp
+++ /dev/null
@@ -1,138 +0,0 @@
-/* -*-c++-*- */
-/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2016 Pelican Mapping
- * http://osgearth.org
- *
- * osgEarth is free software; you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>
- */
-#include <osgEarth/AutoScale>
-#include <osgEarth/ThreadingUtils>
-#include <osgEarth/Utils>
-#include <osgEarth/VirtualProgram>
-#include <osgEarth/Registry>
-#include <osgEarth/ShaderFactory>
-#include <osgUtil/RenderBin>
-
-#define LC "[AutoScale] "
-
-using namespace osgEarth;
-
-#define AUTO_SCALE_BIN_NAME "osgEarth::AutoScale"
-const std::string osgEarth::AUTO_SCALE_BIN = AUTO_SCALE_BIN_NAME;
-
-namespace
-{
-    const char* vs =
-        "#version " GLSL_VERSION_STR "\n"
-        GLSL_DEFAULT_PRECISION_FLOAT "\n"
-
-        "uniform float oe_autoscale_zp; \n"
-
-        "void oe_autoscale_vertex( inout vec4 VertexVIEW ) \n"
-        "{ \n"
-        "    float z       = -VertexVIEW.z; \n"
-
-        "    vec4  cp       = gl_ModelViewMatrix * vec4(0.0,0.0,0.0,1.0); \n" // control point into view space
-        "    float d        = length(cp.xyz); \n"
-        "    vec3  cpn      = cp.xyz/d; \n"
-        "    vec3  off      = VertexVIEW.xyz - cp.xyz; \n"
-
-        "    float dp = (d * oe_autoscale_zp) / z; \n"
-        "    cp.xyz   = cpn * dp; \n"
-
-        "    VertexVIEW.z *= (oe_autoscale_zp/z); \n"
-        "    VertexVIEW.xy = cp.xy + off.xy; \n"
-
-        "    vec3 push      = normalize(VertexVIEW.xyz); \n"
-        "    VertexVIEW.xyz = push * z; \n"
-        "} \n";
-
-
-    class AutoScaleRenderBin : public osgUtil::RenderBin
-    {
-    public:
-        osg::ref_ptr<osg::Uniform>  _zp;
-
-        // support cloning (from RenderBin):
-        virtual osg::Object* cloneType() const { return new AutoScaleRenderBin(); }
-        virtual osg::Object* clone(const osg::CopyOp& copyop) const { return new AutoScaleRenderBin(*this,copyop); } // note only implements a clone of type.
-        virtual bool isSameKindAs(const osg::Object* obj) const { return dynamic_cast<const AutoScaleRenderBin*>(obj)!=0L; }
-        virtual const char* libraryName() const { return "osgEarth"; }
-        virtual const char* className() const { return "AutoScaleRenderBin"; }
-
-
-        // constructs the prototype for this render bin.
-        AutoScaleRenderBin() : osgUtil::RenderBin()
-        {
-            //OE_NOTICE << LC << "AUTOSCALE: created bin." << std::endl;
-
-            this->setName( osgEarth::AUTO_SCALE_BIN );
-
-            _stateset = new osg::StateSet();
-
-            VirtualProgram* vp = VirtualProgram::getOrCreate(_stateset.get());
-            vp->setFunction( "oe_autoscale_vertex", vs, ShaderComp::LOCATION_VERTEX_VIEW, 0L, 0.5f );
-
-            _zp = _stateset->getOrCreateUniform("oe_autoscale_zp", osg::Uniform::FLOAT);
-        }
-
-        AutoScaleRenderBin( const AutoScaleRenderBin& rhs, const osg::CopyOp& op )
-            : osgUtil::RenderBin( rhs, op ),
-              _zp      ( rhs._zp.get() )
-        {
-            //nop
-            //OE_NOTICE << LC << "AUTOSCALE: cloned bin." << std::endl;
-        }
-
-        /**
-         * Draws a bin. Most of this code is copied from osgUtil::RenderBin::drawImplementation.
-         * The modifications are (a) skipping code to render child bins, (b) setting a bin-global
-         * projection matrix in orthographic space, and (c) calling our custom "renderLeaf()" method 
-         * instead of RenderLeaf::render()
-         * (override)
-         */
-        void drawImplementation( osg::RenderInfo& renderInfo, osgUtil::RenderLeaf*& previous)
-        {
-            //OE_NOTICE << LC << "AUTOSCALE: leaves = " << getNumLeaves(this) << std::endl;
-
-            // apply a window-space projection matrix.
-            const osg::Viewport* vp = renderInfo.getCurrentCamera()->getViewport();
-            if ( vp )
-            {
-                float vpw = 0.5f*(float)vp->width();
-
-                double fovy, aspectRatio, n, f;
-                renderInfo.getCurrentCamera()->getProjectionMatrixAsPerspective(fovy, aspectRatio, n, f);
-                float hfovd2 = (float)(0.5*fovy*aspectRatio);
-
-                _zp->set( vpw / tanf(osg::DegreesToRadians(hfovd2)) );
-            }
-    
-            osgUtil::RenderBin::drawImplementation(renderInfo, previous);
-        }
-
-        // debugging - counts the total # of leaves in this bin and its children
-        static unsigned getNumLeaves(osgUtil::RenderBin* bin)
-        {
-            unsigned here = bin->getRenderLeafList().size();
-            for( RenderBinList::iterator i = bin->getRenderBinList().begin(); i != bin->getRenderBinList().end(); ++i )
-                here += getNumLeaves( i->second.get() );
-            return here;
-        }
-    };
-}
-
-/** static registration of the bin */
-//extern "C" void osgEarth_AutoScaleBin_registration(void) {}
-//static osgEarthRegisterRenderBinProxy<AutoScaleRenderBin> s_regbin(AUTO_SCALE_BIN_NAME);
diff --git a/src/osgEarth/Bounds.cpp b/src/osgEarth/Bounds.cpp
index 3f5a8b0..a7e9700 100644
--- a/src/osgEarth/Bounds.cpp
+++ b/src/osgEarth/Bounds.cpp
@@ -147,7 +147,7 @@ Bounds::radius2d() const {
 
 double
 Bounds::area2d() const {
-    return width() * height();
+    return isValid() ? width() * height() : -1.0;
 }
 
 std::string
diff --git a/src/osgEarth/CMakeLists.txt b/src/osgEarth/CMakeLists.txt
index 4104e34..b4afa62 100644
--- a/src/osgEarth/CMakeLists.txt
+++ b/src/osgEarth/CMakeLists.txt
@@ -4,9 +4,18 @@ ELSE (DYNAMIC_OSGEARTH)
     ADD_DEFINITIONS(-DOSGEARTH_LIBRARY_STATIC)
 ENDIF(DYNAMIC_OSGEARTH)
 
+# CURL options
 OPTION(CURL_IS_STATIC "on if curl is a static lib " ON)
 MARK_AS_ADVANCED(CURL_IS_STATIC)
 
+# NVTT mipmap generation 
+option(OSGEARTH_ENABLE_NVTT_CPU_MIPMAPS "Use NVTT, if available, to generate texture mipmaps on the CPU" OFF)
+mark_as_advanced(OSGEARTH_ENABLE_NVTT_CPU_MIPMAPS)
+if(OSGEARTH_ENABLE_NVTT_CPU_MIPMAPS)
+    add_definitions(-DOSGEARTH_ENABLE_NVTT_CPU_MIPMAPS)
+endif(OSGEARTH_ENABLE_NVTT_CPU_MIPMAPS)
+
+# TinyXML options
 ADD_DEFINITIONS(-DTIXML_USE_STL)
 
 # Builds the HTTPClient with WIN_INET instead of CURL
@@ -29,14 +38,15 @@ ENDIF(WIN32)
 SET(LIB_NAME osgEarth)
 
 set(TARGET_GLSL
-    AlphaEffect.frag.glsl
     DepthOffset.vert.glsl
     Draping.vert.glsl
     Draping.frag.glsl
     GPUClamping.vert.glsl
     GPUClamping.vert.lib.glsl
     GPUClamping.frag.glsl
-    Instancing.vert.glsl)
+    Instancing.vert.glsl
+    PhongLighting.vert.glsl
+    PhongLighting.frag.glsl)
 
 set(SHADERS_CPP "${CMAKE_CURRENT_BINARY_DIR}/AutoGenShaders.cpp")
 
@@ -51,8 +61,6 @@ configure_shaders(
 SET(HEADER_PATH ${OSGEARTH_SOURCE_DIR}/include/${LIB_NAME})
 
 SET(LIB_PUBLIC_HEADERS
-    AlphaEffect
-    AutoScale
     Bounds
     Cache
     CacheEstimator
@@ -73,7 +81,6 @@ SET(LIB_PUBLIC_HEADERS
     DateTime
     DateTimeRange
     DepthOffset
-    DPLineSegmentIntersector
     DrapeableNode
     DrapingCullSet
     DrapingTechnique
@@ -81,7 +88,9 @@ SET(LIB_PUBLIC_HEADERS
     ECEF
     ElevationLayer
     ElevationLOD
+    ElevationPool
     ElevationQuery
+    Endian
     Export
     Extension
     FadeEffect
@@ -100,9 +109,14 @@ SET(LIB_PUBLIC_HEADERS
     ImageMosaic
     ImageToHeightFieldConverter
     ImageUtils
+    IntersectionPicker
     IOTypes
     JsonUtils
+    LandCover
+    LandCoverLayer
     Layer
+    LayerListener
+    Lighting
     LineFunctor
     Locators
     LocalTangentPlane
@@ -120,6 +134,8 @@ SET(LIB_PUBLIC_HEADERS
     MaskSource
     Memory
     MemCache
+    MetaTile
+    Metrics
     ModelLayer
     ModelSource
     NativeProgramAdapter
@@ -128,19 +144,20 @@ SET(LIB_PUBLIC_HEADERS
     optional
     ObjectIndex
     OverlayDecorator
-    OverlayNode
+    PagedNode
+    PatchLayer
     PhongLightingEffect
     Picker
-    IntersectionPicker
+    PluginLoader
     PrimitiveIntersector
     Profile
     Profiler
     Progress
-    QuadTree
     Random
     Registry
     ResourceReleaser
     Revisioning
+    SceneGraphCallback
     ScreenSpaceLayout
     Shaders
     ShaderFactory
@@ -149,6 +166,7 @@ SET(LIB_PUBLIC_HEADERS
     ShaderUtils
     Shadowing
     SharedSARepo
+    SimplexNoise
     SpatialReference
     StateSetCache
     StateSetLOD
@@ -161,15 +179,15 @@ SET(LIB_PUBLIC_HEADERS
     TerrainOptions
     TerrainEngineNode
     TerrainEngineRequirements
+    TerrainResources
     TerrainTileModel
     TerrainTileModelFactory
     TerrainTileNode
     TileKeyDataStore
-    TilePatchCallback
     Tessellator
-    TextureCompositor
     TileKey
     TileHandler
+    TileRasterizer
     TileSource
     TileVisitor
     TimeControl
@@ -180,8 +198,11 @@ SET(LIB_PUBLIC_HEADERS
     Utils
     Version
     VerticalDatum
+    VideoLayer
     Viewpoint
     VirtualProgram
+    VisibleLayer
+    WrapperLayer
     XmlUtils
 )
 
@@ -190,7 +211,7 @@ IF (NOT TINYXML_FOUND)
     SET(LIB_PUBLIC_HEADERS
         ${LIB_PUBLIC_HEADERS}
         tinystr.h
-        tinyxml.h 
+        tinyxml.h
     )
 ENDIF (NOT TINYXML_FOUND)
 
@@ -207,19 +228,17 @@ ENDIF (NOT TINYXML_FOUND)
 SET(VERSION_GIT_SOURCE "")
 IF (OSGEARTH_EMBED_GIT_SHA)
 	ADD_DEFINITIONS(-DOSGEARTH_EMBED_GIT_SHA)
-	
+
 	# auto-generate the VersionGit file to include the git SHA1 variable.
 	configure_file(
 		"${CMAKE_CURRENT_SOURCE_DIR}/VersionGit.cpp.in"
-		"${CMAKE_CURRENT_BINARY_DIR}/VersionGit.cpp" 
+		"${CMAKE_CURRENT_BINARY_DIR}/VersionGit.cpp"
 		@ONLY)
-    
-	set(VERSION_GIT_SOURCE "${CMAKE_CURRENT_BINARY_DIR}/VersionGit.cpp")	
+
+	set(VERSION_GIT_SOURCE "${CMAKE_CURRENT_BINARY_DIR}/VersionGit.cpp")
 ENDIF (OSGEARTH_EMBED_GIT_SHA)
 
 set(TARGET_SRC
-    AlphaEffect.cpp
-    AutoScale.cpp
     Bounds.cpp
     Cache.cpp
     CacheBin.cpp
@@ -238,7 +257,6 @@ set(TARGET_SRC
     DateTime.cpp
     DateTimeRange.cpp
     DepthOffset.cpp
-    DPLineSegmentIntersector.cpp
     DrapeableNode.cpp
     DrapingCullSet.cpp
     DrapingTechnique.cpp
@@ -246,6 +264,7 @@ set(TARGET_SRC
     ECEF.cpp
     ElevationLayer.cpp
     ElevationLOD.cpp
+    ElevationPool.cpp
     ElevationQuery.cpp
     Extension.cpp
     FadeEffect.cpp
@@ -263,9 +282,13 @@ set(TARGET_SRC
     ImageMosaic.cpp
     ImageToHeightFieldConverter.cpp
     ImageUtils.cpp
+    IntersectionPicker.cpp
     IOTypes.cpp
     JsonUtils.cpp
+    LandCover.cpp
+    LandCoverLayer.cpp
     Layer.cpp
+    Lighting.cpp
     Locators.cpp
     LocalTangentPlane.cpp
     Map.cpp
@@ -280,6 +303,8 @@ set(TARGET_SRC
     MaskSource.cpp
     MemCache.cpp
     Memory.cpp
+    MetaTile.cpp
+    Metrics.cpp
     MimeTypes.cpp
     ModelLayer.cpp
     ModelSource.cpp
@@ -287,24 +312,25 @@ set(TARGET_SRC
     Notify.cpp
     ObjectIndex.cpp
     OverlayDecorator.cpp
-    OverlayNode.cpp
+    PagedNode.cpp
+    PatchLayer.cpp
     PhongLightingEffect.cpp
-    IntersectionPicker.cpp
     PrimitiveIntersector.cpp
     Profile.cpp
     Profiler.cpp
     Progress.cpp
-    QuadTree.cpp
     Random.cpp
     Registry.cpp
     ResourceReleaser.cpp
     Revisioning.cpp
+    SceneGraphCallback.cpp
     ScreenSpaceLayout.cpp
     ShaderFactory.cpp
     ShaderGenerator.cpp
     ShaderLoader.cpp
     ShaderUtils.cpp
     Shadowing.cpp
+    SimplexNoise.cpp
     SpatialReference.cpp
     StateSetCache.cpp
     StateSetLOD.cpp
@@ -315,14 +341,14 @@ set(TARGET_SRC
     TerrainLayer.cpp
     TerrainOptions.cpp
     TerrainEngineNode.cpp
+    TerrainResources.cpp
     TerrainTileModel.cpp
     TerrainTileModelFactory.cpp
     Tessellator.cpp
     TextureBufferSerializer.cpp
-    TextureCompositor.cpp
     TileKey.cpp
     TileHandler.cpp
-    TilePatchCallback.cpp
+    TileRasterizer.cpp
     TileVisitor.cpp
     TileSource.cpp
     TimeControl.cpp
@@ -333,8 +359,10 @@ set(TARGET_SRC
     Utils.cpp
     Version.cpp
     VerticalDatum.cpp
+    VideoLayer.cpp
     Viewpoint.cpp
     VirtualProgram.cpp
+    VisibleLayer.cpp
     XmlUtils.cpp
     ${SHADERS_CPP} )
 
diff --git a/src/osgEarth/Cache b/src/osgEarth/Cache
index 10d36c6..cab05e6 100644
--- a/src/osgEarth/Cache
+++ b/src/osgEarth/Cache
@@ -36,6 +36,8 @@
 #define OSGEARTH_ENV_CACHE_DRIVER  "OSGEARTH_CACHE_DRIVER"
 #define OSGEARTH_ENV_CACHE_PATH    "OSGEARTH_CACHE_PATH"
 #define OSGEARTH_ENV_CACHE_ONLY    "OSGEARTH_CACHE_ONLY"
+#define OSGEARTH_ENV_DEFAULT_COMPRESSOR "OSGEARTH_DEFAULT_COMPRESSOR"
+
 #define OSGEARTH_ENV_NO_CACHE      "OSGEARTH_NO_CACHE"
 #define OSGEARTH_ENV_CACHE_MAX_AGE "OSGEARTH_CACHE_MAX_AGE"
 
@@ -69,7 +71,7 @@ namespace osgEarth
         /** Whether this object support an active cache. */
         bool isCacheEnabled() const;
         bool isCacheDisabled() const { return !isCacheEnabled(); }
-        
+
         /** Cache used by these settings */
         Cache* getCache() const { return _cache.get(); }
         void setCache(Cache* cache) { _cache = cache; }
@@ -86,9 +88,11 @@ namespace osgEarth
         /** Integrates an outside cache policy with the one in these settings. This method
           * also takes care of global (registry) override policy. */
         void integrateCachePolicy(const optional<CachePolicy>& policy);
-        
-        /** Get/Set the settings in a read-options structure. */
+
+        /** Extracts the settings from a read-options structure. */
         static CacheSettings* get(const osgDB::Options* readOptions);
+
+        /** Stores this instance in an Options data container. Extract using get(). */
         void store(osgDB::Options* readOptions);
 
         /** for debugging */
@@ -113,8 +117,8 @@ namespace osgEarth
     public:
         CacheOptions( const ConfigOptions& options =ConfigOptions() )
             : DriverConfigOptions( options )
-        { 
-            fromConfig( _conf ); 
+        {
+            fromConfig( _conf );
         }
 
         /** dtor */
@@ -127,7 +131,7 @@ namespace osgEarth
         }
 
         virtual void mergeConfig( const Config& conf ) {
-            ConfigOptions::mergeConfig( conf );            
+            ConfigOptions::mergeConfig( conf );
             fromConfig( conf );
         }
 
@@ -167,7 +171,7 @@ namespace osgEarth
          */
         CacheBin* getBin( const std::string& name );
 
-        /** 
+        /**
          * Gets the default caching bin within this cache. This may or may not
          * be supported by the implementation, so be sure to check the result
          * before using it.
@@ -187,7 +191,7 @@ namespace osgEarth
          */
         virtual void removeBin( CacheBin* bin );
 
-        /** 
+        /**
          * Gets an Options structure representing this cache's configuration.
          */
         const CacheOptions& getCacheOptions() const { return _options; }
@@ -232,7 +236,7 @@ namespace osgEarth
 
 //----------------------------------------------------------------------
 
-    /** 
+    /**
      * Factory class that can load and instantiate a Cache implementation based on the
      * information in the CacheOptions settings.
      */
diff --git a/src/osgEarth/Cache.cpp b/src/osgEarth/Cache.cpp
index aacb299..1c19ef7 100644
--- a/src/osgEarth/Cache.cpp
+++ b/src/osgEarth/Cache.cpp
@@ -151,7 +151,7 @@ Cache::removeBin( CacheBin* bin )
 Cache*
 CacheFactory::create( const CacheOptions& options )
 {
-    osg::ref_ptr<Cache> result =0L;
+    osg::ref_ptr<Cache> result;
     OE_DEBUG << LC << "Initializing cache of type \"" << options.getDriver() << "\"" << std::endl;
 
     if ( options.getDriver().empty() )
@@ -168,8 +168,8 @@ CacheFactory::create( const CacheOptions& options )
         rwopt->setPluginData( CACHE_OPTIONS_TAG, (void*)&options );
 
         std::string driverExt = std::string(".osgearth_cache_") + options.getDriver();
-        osgDB::ReaderWriter::ReadResult rr = osgDB::readObjectFile( driverExt, rwopt.get() );
-        result = dynamic_cast<Cache*>( rr.getObject() );
+        osg::ref_ptr<osg::Object> object = osgDB::readRefObjectFile( driverExt, rwopt.get() );
+        result = dynamic_cast<Cache*>( object.release() );
         if ( !result.valid() )
         {
             OE_WARN << LC << "Failed to load cache plugin for type \"" << options.getDriver() << "\"" << std::endl;
diff --git a/src/osgEarth/CacheBin.cpp b/src/osgEarth/CacheBin.cpp
index a2faf82..a5082a7 100644
--- a/src/osgEarth/CacheBin.cpp
+++ b/src/osgEarth/CacheBin.cpp
@@ -32,6 +32,21 @@
 
 using namespace osgEarth;
 
+
+// serializer for osg::DummyObject (not present in OSG)
+// We need this because the osgDB::DatabasePager will sometimes
+// add a DummyObject to textures that it finds in paged objects.
+namespace osg
+{
+    REGISTER_OBJECT_WRAPPER(DummyObject,
+                            new osg::DummyObject,
+                            osg::DummyObject,
+                            "osg::DummyObject")
+    {
+        //nop
+    }
+}
+
 namespace
 {
 #undef  LC
@@ -131,6 +146,8 @@ namespace
                         {              
                             tex->setUnRefImageDataAfterApply(false);               
 
+#if 0 // took this out in favor of the osg::DummyObject serializer above.
+
                             // OSG's DatabasePager attaches "marker objects" to Textures' UserData when it runs a
                             // FindCompileableGLObjectsVisitor. This operation is not thread-safe; it doesn't
                             // account for the possibility that the texture may already be in use elsewhere.
@@ -144,7 +161,6 @@ namespace
                             // This "hack" prevents a crash in OSG 3.4.0 when trying to modify and then write
                             // serialize the scene graph containing these shared texture objects.
                             // Kudos to Jason B for figuring this one out.
-
                             osg::Texture* texClone = osg::clone(tex, osg::CopyOp::SHALLOW_COPY);
                             if ( texClone )
                             {
@@ -165,6 +181,7 @@ namespace
                             {
                                 OE_WARN << LC << "Texture clone failed.\n";
                             }
+#endif
                         }
                         else
                         {
diff --git a/src/osgEarth/CacheEstimator.cpp b/src/osgEarth/CacheEstimator.cpp
index e5ba765..7ae1552 100644
--- a/src/osgEarth/CacheEstimator.cpp
+++ b/src/osgEarth/CacheEstimator.cpp
@@ -50,8 +50,6 @@ CacheEstimator::getNumTiles() const
 
     for (unsigned int level = _minLevel; level <= _maxLevel; level++)
     {
-        double coverageRatio = 0.0;
-
         if (_extents.empty())
         {
             unsigned int wide, high;
@@ -63,7 +61,6 @@ CacheEstimator::getNumTiles() const
             for (std::vector< GeoExtent >::const_iterator itr = _extents.begin(); itr != _extents.end(); ++itr)
             {
                 const GeoExtent& extent = *itr;
-                double boundsArea = extent.area();
 
                 TileKey ll = _profile->createTileKey(extent.xMin(), extent.yMin(), level);
                 TileKey ur = _profile->createTileKey(extent.xMax(), extent.yMax(), level);
diff --git a/src/osgEarth/CachePolicy b/src/osgEarth/CachePolicy
index 720184b..fb72ab4 100644
--- a/src/osgEarth/CachePolicy
+++ b/src/osgEarth/CachePolicy
@@ -66,12 +66,6 @@ namespace osgEarth
         /** constructs a CachePolicy from a config options */
         CachePolicy( const Config& conf );
 
-        /** construct a cache policy be reading it from an osgDB::Options */
-        //static optional<CachePolicy> get(const osgDB::Options* options);
-
-        /** Stores this cache policy in a DB Options. */
-        //void store( osgDB::Options* options ) const;
-
         /** Merges any set properties in another CP into this one, override existing values. */
         void mergeAndOverride(const CachePolicy& rhs);
         void mergeAndOverride(const optional<CachePolicy>& rhs);
@@ -79,9 +73,7 @@ namespace osgEarth
         /** Gets the oldest timestamp for which to accept a cache record */
         TimeStamp getMinAcceptTime() const;
 
-        /**
-         * Determine whether the given timestamp is considered to be expired based on this CachePolicy
-         */
+        /** Whether the given timestamp is considered to be expired based on this CachePolicy */
         bool isExpired(TimeStamp lastModified) const;
 
         /** dtor */
diff --git a/src/osgEarth/CachePolicy.cpp b/src/osgEarth/CachePolicy.cpp
index ab89349..b6194a6 100644
--- a/src/osgEarth/CachePolicy.cpp
+++ b/src/osgEarth/CachePolicy.cpp
@@ -63,31 +63,6 @@ _minTime( rhs._minTime )
     //nop
 }
 
-//optional<CachePolicy>
-//CachePolicy::get(const osgDB::Options* readOptions)
-//{
-//    optional<CachePolicy> policy;
-//    if (readOptions)
-//    {
-//        CacheSettings* settings = CacheSettings::get(readOptions);
-//        if (settings)
-//        {
-//            policy = settings->cachePolicy().get();
-//        }
-//    }
-//    return policy;
-//}
-
-//void
-//CachePolicy::store(osgDB::Options* dbOptions) const
-//{
-//    if ( dbOptions )
-//    {
-//        Config conf = getConfig();
-//        dbOptions->setPluginStringData( "osgEarth::CachePolicy", conf.toJSON() );
-//    }
-//}
-
 void
 CachePolicy::mergeAndOverride(const CachePolicy& rhs)
 {
diff --git a/src/osgEarth/CacheSeed b/src/osgEarth/CacheSeed
index 11d179a..da642d3 100644
--- a/src/osgEarth/CacheSeed
+++ b/src/osgEarth/CacheSeed
@@ -24,20 +24,21 @@
 #define OSGEARTH_CACHE_SEED_H 1
 
 #include <osgEarth/Common>
-#include <osgEarth/Map>
 #include <osgEarth/TileKey>
 #include <osgEarth/TileVisitor>
 
 
 namespace osgEarth
 {
+    class Map;
+
     /**
     * A TileHandler that caches tiles for the given layer.
     */
     class OSGEARTH_EXPORT CacheTileHandler : public TileHandler
     {
     public:
-        CacheTileHandler( TerrainLayer* layer, Map* map );
+        CacheTileHandler( TerrainLayer* layer, const Map* map );
         virtual bool handleTile( const TileKey& key, const TileVisitor& tv );
         virtual bool hasData( const TileKey& key ) const;
 
@@ -45,7 +46,7 @@ namespace osgEarth
 
     protected:
         osg::ref_ptr< TerrainLayer > _layer;
-        osg::ref_ptr< Map > _map;
+        osg::ref_ptr< const Map > _map;
     };    
 
     /**
@@ -69,7 +70,7 @@ namespace osgEarth
         /**
         * Seeds a TerrainLayer
         */
-        void run(TerrainLayer* layer, Map* map );
+        void run(TerrainLayer* layer, const Map* map );
 
 
     protected:
diff --git a/src/osgEarth/CacheSeed.cpp b/src/osgEarth/CacheSeed.cpp
index 58f1b99..693ad15 100644
--- a/src/osgEarth/CacheSeed.cpp
+++ b/src/osgEarth/CacheSeed.cpp
@@ -23,6 +23,9 @@
 #include <osgEarth/CacheSeed>
 #include <osgEarth/CacheEstimator>
 #include <osgEarth/MapFrame>
+#include <osgEarth/Map>
+#include <osgEarth/ImageLayer>
+#include <osgEarth/ElevationLayer>
 #include <OpenThreads/ScopedLock>
 #include <limits.h>
 
@@ -31,7 +34,7 @@
 using namespace osgEarth;
 using namespace OpenThreads;
 
-CacheTileHandler::CacheTileHandler( TerrainLayer* layer, Map* map ):
+CacheTileHandler::CacheTileHandler( TerrainLayer* layer, const Map* map ):
 _layer( layer ),
 _map( map )
 {
@@ -53,7 +56,7 @@ bool CacheTileHandler::handleTile(const TileKey& key, const TileVisitor& tv)
     }
     else if (elevationLayer )
     {
-        GeoHeightField hf = elevationLayer->createHeightField( key );
+        GeoHeightField hf = elevationLayer->createHeightField(key, 0L);
         if (hf.valid())
         {                
             return true;
@@ -62,7 +65,7 @@ bool CacheTileHandler::handleTile(const TileKey& key, const TileVisitor& tv)
 
     // If we didn't produce a result but the key isn't within range then we should continue to 
     // traverse the children b/c a min level was set.
-    if (!_layer->isKeyInRange(key))
+    if (!_layer->isKeyInLegalRange(key))
     {
         return true;
     }
@@ -72,41 +75,26 @@ bool CacheTileHandler::handleTile(const TileKey& key, const TileVisitor& tv)
 
 bool CacheTileHandler::hasData( const TileKey& key ) const
 {
-    TileSource* ts = _layer->getTileSource();
-    if (ts)
-    {
-        return ts->hasData(key);
-    }
-    return true;
+    return _layer->mayHaveData(key);
 }
 
 std::string CacheTileHandler::getProcessString() const
 {
+    std::stringstream buf;
     ImageLayer* imageLayer = dynamic_cast< ImageLayer* >( _layer.get() );
     ElevationLayer* elevationLayer = dynamic_cast< ElevationLayer* >( _layer.get() );    
 
-    std::stringstream buf;
-    buf << "osgearth_cache --seed ";
-    if (imageLayer)
-    {        
-        for (int i = 0; i < _map->getNumImageLayers(); i++)
+    unsigned index = _map->getIndexOfLayer(_layer.get());
+    if (index < _map->getNumLayers())
+    {
+        buf << "osgearth_cache --seed ";
+        if (imageLayer)
         {
-            if (imageLayer == _map->getImageLayerAt(i))
-            {
-                buf << " --image " << i << " ";
-                break;
-            }
+            buf << " --image " << index << " ";
         }
-    }
-    else if (elevationLayer)
-    {
-        for (int i = 0; i < _map->getNumElevationLayers(); i++)
+        else if (elevationLayer)
         {
-            if (elevationLayer == _map->getElevationLayerAt(i))
-            {
-                buf << " --elevation " << i << " ";
-                break;
-            }
+            buf << " --elevation " << index << " ";
         }
     }
     return buf.str();
@@ -131,7 +119,7 @@ void CacheSeed::setVisitor(TileVisitor* visitor)
     _visitor = visitor;
 }
 
-void CacheSeed::run( TerrainLayer* layer, Map* map )
+void CacheSeed::run( TerrainLayer* layer, const Map* map )
 {
     _visitor->setTileHandler( new CacheTileHandler( layer, map ) );
     _visitor->run( map->getProfile() );
diff --git a/src/osgEarth/Capabilities b/src/osgEarth/Capabilities
index b3bf905..bc0ea1e 100644
--- a/src/osgEarth/Capabilities
+++ b/src/osgEarth/Capabilities
@@ -33,7 +33,7 @@ namespace osgEarth
 
     /**
      * Stores information about the hardware and graphics system capbilities.
-     * The osgEarth::Registry stores a singleton Capabilities object that you can 
+     * The osgEarth::Registry stores a singleton Capabilities object that you can
      * use to determine what your system supports.
      */
     class OSGEARTH_EXPORT Capabilities : public osg::Referenced
@@ -64,13 +64,13 @@ namespace osgEarth
         int getDepthBufferBits() const { return _depthBits; }
 
         /** whether the GPU supports shaders. */
-        bool supportsGLSL() const { 
+        bool supportsGLSL() const {
             return _supportsGLSL; }
-        
+
         /** whether the GPU supports a minimum GLSL version */
-        bool supportsGLSL(float minimumVersion) const { 
+        bool supportsGLSL(float minimumVersion) const {
             return _supportsGLSL && _GLSLversion >= minimumVersion; }
-        
+
         /** whether the GPU supports a minimum GLSL version (as an int; e.g. 1.2 => "120") */
         bool supportsGLSL(unsigned minVersionInt) const {
             return _supportsGLSL && ((unsigned)(_GLSLversion*100.0f)) >= minVersionInt; }
@@ -79,7 +79,7 @@ namespace osgEarth
         float getGLSLVersion() const { return _GLSLversion; }
 
         /** GLSL version as an integer x 100. I.e.: GLSL 1.4 => 140. */
-        unsigned getGLSLVersionInt() const { return (unsigned)(_GLSLversion*100.0f); }
+        unsigned getGLSLVersionInt() const { return (unsigned)(_GLSLversion*100.0f + 0.5f); }
 
         /** Are we running OpenGLES? */
         bool isGLES() const { return _isGLES; }
@@ -140,7 +140,7 @@ namespace osgEarth
 
         /** whether the GPU supports writing to the depth fragment */
         bool supportsFragDepthWrite() const { return _supportsFragDepthWrite; }
-        
+
         /** whether the GPU supports a texture compression scheme */
         bool supportsTextureCompression(const osg::Texture::InternalFormatMode& mode) const;
 
diff --git a/src/osgEarth/Capabilities.cpp b/src/osgEarth/Capabilities.cpp
index 5f48302..c258a94 100644
--- a/src/osgEarth/Capabilities.cpp
+++ b/src/osgEarth/Capabilities.cpp
@@ -17,6 +17,7 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>
  */
 #include <osgEarth/Capabilities>
+#include <osgEarth/Version>
 #include <osg/FragmentProgram>
 #include <osg/GraphicsContext>
 #include <osg/GL>
@@ -55,6 +56,8 @@ struct MyGraphicsContext
         traits->doubleBuffer = false;
         traits->sharedContext = 0;
         traits->pbuffer = false;
+        traits->glContextVersion = osg::DisplaySettings::instance()->getGLContextVersion();
+        traits->glContextProfileMask = osg::DisplaySettings::instance()->getGLContextProfileMask();
 
         // Intel graphics adapters dont' support pbuffers, and some of their drivers crash when
         // you try to create them. So by default we will only use the unmapped/pbuffer method
@@ -151,7 +154,7 @@ _maxTextureBufferSize   ( 0 )
     _numProcessors = OpenThreads::GetNumberOfProcessors();
 
     // GLES compile?
-#if (defined(OSG_GLES1_AVAILABLE) || defined(OSG_GLES2_AVAILABLE))
+#if (defined(OSG_GLES1_AVAILABLE) || defined(OSG_GLES2_AVAILABLE) || defined(OSG_GLES3_AVAILABLE))
     _isGLES = true;
 #else
     _isGLES = false;
@@ -165,6 +168,8 @@ _maxTextureBufferSize   ( 0 )
         osg::GraphicsContext* gc = mgc._gc.get();
         unsigned int id = gc->getState()->getContextID();
         const osg::GL2Extensions* GL2 = osg::GL2Extensions::Get( id, true );
+
+        OE_INFO << LC << "osgEarth Version: " << osgEarthGetVersion() << std::endl;
         
         if ( ::getenv("OSGEARTH_NO_GLSL") )
         {
@@ -173,11 +178,7 @@ _maxTextureBufferSize   ( 0 )
         }
         else
         {
-#if OSG_MIN_VERSION_REQUIRED(3,3,3)
             _supportsGLSL = GL2->isGlslSupported;
-#else
-			_supportsGLSL = GL2->isGlslSupported();
-#endif
         }
 
         OE_INFO << LC << "Detected hardware capabilities:" << std::endl;
@@ -191,24 +192,31 @@ _maxTextureBufferSize   ( 0 )
         _version = std::string( reinterpret_cast<const char*>(glGetString(GL_VERSION)) );
         OE_INFO << LC << "  Version = " << _version << std::endl;
 
+#if !defined(OSG_GLES2_AVAILABLE) && !defined(OSG_GLES3_AVAILABLE)
         glGetIntegerv( GL_MAX_TEXTURE_UNITS, &_maxFFPTextureUnits );
         //OE_INFO << LC << "  Max FFP texture units = " << _maxFFPTextureUnits << std::endl;
+#endif
 
         glGetIntegerv( GL_MAX_TEXTURE_IMAGE_UNITS_ARB, &_maxGPUTextureUnits );
         OE_INFO << LC << "  Max GPU texture units = " << _maxGPUTextureUnits << std::endl;
 
+#if !defined(OSG_GLES2_AVAILABLE) && !defined(OSG_GLES3_AVAILABLE)
         glGetIntegerv( GL_MAX_TEXTURE_COORDS_ARB, &_maxGPUTextureCoordSets );
+#else
+        _maxGPUTextureCoordSets = _maxGPUTextureUnits;
+#endif
         OE_INFO << LC << "  Max GPU texture coord indices = " << _maxGPUTextureCoordSets << std::endl;
 
         glGetIntegerv( GL_MAX_VERTEX_ATTRIBS, &_maxGPUAttribs );
         OE_INFO << LC << "  Max GPU attributes = " << _maxGPUAttribs << std::endl;
 
+#if !(defined(OSG_GL3_AVAILABLE))
         glGetIntegerv( GL_DEPTH_BITS, &_depthBits );
         OE_INFO << LC << "  Depth buffer bits = " << _depthBits << std::endl;
-
+#endif
         
         glGetIntegerv( GL_MAX_TEXTURE_SIZE, &_maxTextureSize );
-#if !(defined(OSG_GLES1_AVAILABLE) || defined(OSG_GLES2_AVAILABLE))
+#if !defined(OSG_GLES1_AVAILABLE) && !defined(OSG_GLES2_AVAILABLE) && !defined(OSG_GLES3_AVAILABLE)
         // Use the texture-proxy method to determine the maximum texture size 
         for( int s = _maxTextureSize; s > 2; s >>= 1 )
         {
@@ -224,7 +232,6 @@ _maxTextureBufferSize   ( 0 )
 #endif
         OE_INFO << LC << "  Max texture size = " << _maxTextureSize << std::endl;
 
-        //PORT at tom, what effect will this have?
 #ifdef OSG_GL_FIXED_FUNCTION_AVAILABLE
         glGetIntegerv( GL_MAX_LIGHTS, &_maxLights );
 #else
@@ -236,11 +243,7 @@ _maxTextureBufferSize   ( 0 )
 
         if ( _supportsGLSL )
         {
-#if OSG_MIN_VERSION_REQUIRED(3,3,3)
 			_GLSLversion = GL2->glslLanguageVersion;
-#else
-            _GLSLversion = GL2->getLanguageVersion();
-#endif
             OE_INFO << LC << "  GLSL Version = " << getGLSLVersionInt() << std::endl;
         }
 
@@ -291,13 +294,22 @@ _maxTextureBufferSize   ( 0 )
             _supportsUniformBufferObjects = false;
         }
 
+#if !defined(OSG_GLES3_AVAILABLE)
         _supportsNonPowerOfTwoTextures =
             osg::isGLExtensionSupported( id, "GL_ARB_texture_non_power_of_two" );
+#else
+        _supportsNonPowerOfTwoTextures = true;
+#endif
         OE_INFO << LC << "  NPOT textures = " << SAYBOOL(_supportsNonPowerOfTwoTextures) << std::endl;
 
+
+#if !defined(OSG_GLES3_AVAILABLE)
         _supportsTextureBuffer = 
             osg::isGLExtensionOrVersionSupported( id, "GL_ARB_texture_buffer_object", 3.0 ) ||
             osg::isGLExtensionOrVersionSupported( id, "GL_EXT_texture_buffer_object", 3.0 );
+#else
+        _supportsTextureBuffer = false;
+#endif
 
         if ( _supportsTextureBuffer )
         {
@@ -315,16 +327,13 @@ _maxTextureBufferSize   ( 0 )
         OE_INFO << LC << "  Transform feedback = " << SAYBOOL(supportsTransformFeedback) << "\n";
 
 
-        // Writing to gl_FragDepth is not supported under GLES:
+        // Writing to gl_FragDepth is not supported under GLES, is supported under gles3
 #if (defined(OSG_GLES1_AVAILABLE) || defined(OSG_GLES2_AVAILABLE))
         _supportsFragDepthWrite = false;
 #else
         _supportsFragDepthWrite = true;
 #endif
 
-        //_supportsTexture2DLod = osg::isGLExtensionSupported( id, "GL_ARB_shader_texture_lod" );
-        //OE_INFO << LC << "  texture2DLod = " << SAYBOOL(_supportsTexture2DLod) << std::endl;
-
         // NVIDIA:
         bool isNVIDIA = _vendor.find("NVIDIA") == 0;
 
diff --git a/src/osgEarth/ClampableNode b/src/osgEarth/ClampableNode
index f093dbe..cdd6ca2 100644
--- a/src/osgEarth/ClampableNode
+++ b/src/osgEarth/ClampableNode
@@ -21,58 +21,33 @@
 #define OSGEARTH_CLAMPABLE_NODE_H 1
 
 #include <osgEarth/Common>
-#include <osgEarth/DepthOffset>
-#include <osgEarth/OverlayNode>
 #include <osg/Group>
+#include <osg/observer_ptr>
 
 namespace osgEarth
 {
     class MapNode;
 
     /**
-     * Node graph that can be "clamped" on the MapNode terrain
-     * using the overlay decorator.
-     *
-     * Usage: Create this node and put it anywhere in the scene graph. The
-     * subgraph of this node will be draped on the MapNode's terrain.
+     * Node graph that will be GPU-clamped on the terrain, if supported.
+     * This group must be a descendant of a MapNode.
      */
-    class OSGEARTH_EXPORT ClampableNode : public OverlayNode, public DepthOffsetInterface
+    class OSGEARTH_EXPORT ClampableNode : public osg::Group
     {
     public:
-        /**
-         * Constructs a new clampable node.
-         */
-        ClampableNode( MapNode* mapNode, bool clamped =true );
-
-
-    public: // DepthOffsetInterface
-
-        /** Sets the depth offsetting options. See DepthOffset */
-        void setDepthOffsetOptions( const DepthOffsetOptions& options );
-
-        /** Gets the depth offsetting options. See DepthOffset */
-        const DepthOffsetOptions& getDepthOffsetOptions() const;
+        //! Constructs a new clampable node.
+        ClampableNode();
 
     public: // osg::Node
 
-        virtual osg::BoundingSphere computeBound() const;
-
         virtual void traverse(osg::NodeVisitor& nv);
 
-    public: // backwards-compatibility
-
-        /** @deprecated */
-        void setClamped( bool value ) { setActive(value); }
-        /** @deprecated */
-        bool getClamped() const { return getActive(); }
-
     protected:
         void setUniforms();
         void dirty();
-        void scheduleUpdate();
 
-        DepthOffsetAdapter _adapter;
-        bool               _updatePending;
+        bool _mapNodeUpdateRequested;
+        osg::observer_ptr<MapNode> _mapNode;
 
         virtual ~ClampableNode() { }
     };
diff --git a/src/osgEarth/ClampableNode.cpp b/src/osgEarth/ClampableNode.cpp
index a9981bc..3df99d5 100644
--- a/src/osgEarth/ClampableNode.cpp
+++ b/src/osgEarth/ClampableNode.cpp
@@ -22,76 +22,116 @@
 #include <osgEarth/DepthOffset>
 #include <osgEarth/OverlayDecorator>
 #include <osgEarth/MapNode>
+#include <osgEarth/NodeUtils>
 
 #define LC "[ClampableNode] "
 
 using namespace osgEarth;
 
-//------------------------------------------------------------------------
 
-namespace
-{
-    static osg::Group* getTechniqueGroup(MapNode* m)
-    {
-        return m ? m->getOverlayDecorator()->getGroup<ClampingTechnique>() : 0L;
-    }
-}
-
-//------------------------------------------------------------------------
-
-ClampableNode::ClampableNode( MapNode* mapNode, bool active ) :
-OverlayNode( mapNode, active, &getTechniqueGroup ),
-_updatePending( false )
+ClampableNode::ClampableNode() :
+_mapNodeUpdateRequested(true)
 {
-    _adapter.setGraph( this );
+    // bounding box culling doesn't work on clampable geometry
+    // since the GPU will be moving verts. So, disable the default culling
+    // for this node so we can out own culling in traverse().
+    setCullingActive(false);
 
-    if ( _adapter.isDirty() )
-        _adapter.recalculate();
+    // for the mapnode update:
+    ADJUST_UPDATE_TRAV_COUNT(this, +1);
 }
 
 void
-ClampableNode::setDepthOffsetOptions(const DepthOffsetOptions& options)
+ClampableNode::traverse(osg::NodeVisitor& nv)
 {
-    _adapter.setDepthOffsetOptions(options);
-    if ( _adapter.isDirty() && !_updatePending )
-        scheduleUpdate();
-}
+    if ( nv.getVisitorType() == nv.CULL_VISITOR )
+    {
+        // Lock a reference to the map node:
+        osg::ref_ptr<MapNode> mapNode;
+        if (_mapNode.lock(mapNode))
+        {
+            osgUtil::CullVisitor* cv = dynamic_cast<osgUtil::CullVisitor*>(&nv);
 
-const DepthOffsetOptions&
-ClampableNode::getDepthOffsetOptions() const
-{
-    return _adapter.getDepthOffsetOptions();
-}
+            // Custom culling. Since clamped geometry can start far outside of the 
+            // view frustum, normal culling won't work. Instead, project the
+            // bounding sphere upwards (along its up vector) on to the center plane
+            // of the view frustum. Then cull it based on the new simulated location.
+            osg::RefMatrix* MV = cv->getModelViewMatrix();
+            osg::Matrix MVinverse;
+            MVinverse.invert(*MV);
 
-void
-ClampableNode::scheduleUpdate()
-{
-    if ( !_updatePending && getDepthOffsetOptions().enabled() == true )
-    {
-        ADJUST_UPDATE_TRAV_COUNT(this, 1);
-        _updatePending = true;
+            // Actual bounds of geometry:
+            osg::BoundingSphere bs = getBound();
+
+            // Find any two points on the bounding sphere's up-vector
+            // and transform them into view space:
+            osg::Vec3d p0 = bs.center() * (*MV);
+            osg::Vec3d p1 =
+                mapNode->isGeocentric() ? (bs.center() * 2.0 * (*MV)) :
+                                          (bs.center() + osg::Vec3d(0, 0, bs.radius())) * (*MV);
+
+            // Center plane of the view frustum (in view space)
+            static osg::Vec3d v0(0, 0, 0);  // point on the plane
+            static osg::Vec3d n(0, 1, 0);   // normal vector to the plane
+
+            // Find the intersection of the up vector and the center plane
+            // and then transform the result back into world space for culling.
+            osg::Vec3d w = p0 - v0;
+            osg::Vec3d u = p1 - p0;
+            double t = (-n * w) / (n * u);
+            bs.center() = (p0 + u*t) * MVinverse;
+
+            if (cv->isCulled(bs) == false)
+            {
+                // Passed the cull test, so put this node in the clamping cull set.
+                ClampingCullSet& cullSet = mapNode->getClampingManager()->get( cv->getCurrentCamera() );
+                cullSet.push( this, cv->getNodePath(), nv.getFrameStamp() );
+            }
+        }
     }
-}
 
-osg::BoundingSphere
-ClampableNode::computeBound() const
-{
-    static Threading::Mutex s_mutex;
+    else if (nv.getVisitorType() == nv.UPDATE_VISITOR)
+    {        
+        if (_mapNodeUpdateRequested)
+        {
+            if (_mapNode.valid() == false)
+            {
+                _mapNode = osgEarth::findInNodePath<MapNode>(nv);
+            }
+
+            if (_mapNode.valid())
+            {
+                _mapNodeUpdateRequested = false;
+                ADJUST_UPDATE_TRAV_COUNT(this, -1);
+            }
+        }
+
+        osg::Group::traverse(nv);
+    }
+    else
     {
-        Threading::ScopedMutexLock lock(s_mutex);
-        const_cast<ClampableNode*>(this)->scheduleUpdate();
+        osg::Group::traverse(nv);
     }
-    return OverlayNode::computeBound();
 }
 
-void
-ClampableNode::traverse(osg::NodeVisitor& nv)
+
+//...........................................................................
+
+#undef  LC
+#define LC "[ClampableNode Serializer] "
+
+#include <osgDB/ObjectWrapper>
+#include <osgDB/InputStream>
+#include <osgDB/OutputStream>
+
+namespace
 {
-    if ( _updatePending && nv.getVisitorType() == nv.UPDATE_VISITOR )
+    REGISTER_OBJECT_WRAPPER(
+        ClampableNode,
+        new osgEarth::ClampableNode,
+        osgEarth::ClampableNode,
+        "osg::Object osg::Node osg::Group osgEarth::ClampableNode")
     {
-        _adapter.recalculate();
-        ADJUST_UPDATE_TRAV_COUNT( this, -1 );
-        _updatePending = false;
+        //nop
     }
-    OverlayNode::traverse( nv );
 }
diff --git a/src/osgEarth/Clamping b/src/osgEarth/Clamping
index d45d1c5..a75afd5 100644
--- a/src/osgEarth/Clamping
+++ b/src/osgEarth/Clamping
@@ -23,8 +23,12 @@
 #define OSGEARTH_CLAMPING_H
 
 #include <osgEarth/Common>
+#include <osgEarth/Containers>
+#include <osgEarth/ClampableNode>
 #include <osg/Node>
 #include <osg/Geometry>
+#include <osg/ObserverNodePath>
+#include <osg/Camera>
 
 namespace osgEarth
 {
@@ -40,9 +44,15 @@ namespace osgEarth
      */
     class OSGEARTH_EXPORT Clamping
     {
-    public:
+    public: // mutable
+
         // GLSL attribute binding location for clamping vertex attributes
-        static const int AnchorAttrLocation;
+        static int AnchorAttrLocation;
+
+        // GLSL attribute binding location for clamping vertex heights
+        static int HeightsAttrLocation;
+
+    public: // non-mutable
 
         // Name of the clamping anchor vertex attribute
         static const char* AnchorAttrName;
@@ -54,9 +64,6 @@ namespace osgEarth
         // Name of the uniform used to apply an altitude offset to clamped geometry
         static const char* AltitudeOffsetUniformName;
 
-        // GLSL attribute binding location for clamping vertex heights
-        static const int HeightsAttrLocation;
-
         // Name of the clamping heights array attribute
         static const char* HeightsAttrName;
 
@@ -66,6 +73,8 @@ namespace osgEarth
         static const float ClampToAnchor;
         static const float ClampToGround;
 
+    public: // utility methods
+
         // Installs a uniform on the stateset indicating that geometry has the
         // clamping vertex attribute installed.
         static void installHasAttrsUniform(
@@ -83,6 +92,60 @@ namespace osgEarth
             osg::Geometry*   geom,
             osg::FloatArray* heights );
     };
+    
+
+    /**
+     * Culling set for tracking groups whose contents should be GPU-clamped
+     * The ClampableNode class inserts scene graphs into this objects during
+     * the CULL traversal; then the ClampingTechnique traverses those graphs.
+     */
+    class OSGEARTH_INTERNAL ClampingCullSet
+    {
+    public:
+        struct Entry
+        {            
+            osg::ref_ptr<osg::Group>     _node;
+            osg::ref_ptr<osg::RefMatrix> _matrix;
+            osg::ObserverNodePath        _path;
+            int                          _frame;
+        };
+
+    public:
+        ClampingCullSet();
+        ~ClampingCullSet() { } // not virtual
+
+        /** Pushes a node and its matrix into the cull set */
+        void push(ClampableNode* node, const osg::NodePath& path, const osg::FrameStamp* stamp);
+
+        /** Runs a node visitor on the cull set, optionally popping as it goes along. */
+        void accept(osg::NodeVisitor& nv);
+
+        /** Bounds of this set */
+        const osg::BoundingSphere& getBound() const { return _bs; }
+
+        /** Number of elements in the set */
+        unsigned size() const { return _entries.size(); }
+
+    private:
+        std::vector<Entry>  _entries;
+        osg::BoundingSphere _bs;
+        bool                _frameCulled;
+    };
+
+    /**
+     * Houses all the active ClampingCullSets under an osgEarth TerrainEngine.
+     * ClampableNode will use this object to gain access to a ClampingCullSet
+     * during the CULL traversal.
+     */
+    class OSGEARTH_INTERNAL ClampingManager
+    {
+    public:
+        //! Gets the cull set associated with a camera.
+        ClampingCullSet& get(const osg::Camera*);
+
+    private:
+        PerObjectFastMap<const osg::Camera*, ClampingCullSet> _sets;
+    };
 }
 
 #endif // OSGEARTH_CLAMPING_H
diff --git a/src/osgEarth/Clamping.cpp b/src/osgEarth/Clamping.cpp
index a89535c..7d29a87 100644
--- a/src/osgEarth/Clamping.cpp
+++ b/src/osgEarth/Clamping.cpp
@@ -20,19 +20,23 @@
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
 */
 #include <osgEarth/Clamping>
+#include <osgEarth/CullingUtils>
+#include <osgEarth/Registry>
 #include <osg/Drawable>
 #include <osg/NodeVisitor>
 #include <osg/Geode>
 #include <osg/Geometry>
+#include <osgUtil/CullVisitor>
 
 using namespace osgEarth;
 
-const int   Clamping::AnchorAttrLocation        = osg::Drawable::ATTRIBUTE_6;
+int         Clamping::AnchorAttrLocation        = osg::Drawable::ATTRIBUTE_6;
+int         Clamping::HeightsAttrLocation       = osg::Drawable::FOG_COORDS;
+
 const char* Clamping::AnchorAttrName            = "oe_clamp_attrs";
 const char* Clamping::HasAttrsUniformName       = "oe_clamp_hasAttrs";
 const char* Clamping::AltitudeOffsetUniformName = "oe_clamp_altitudeOffset";
 
-const int   Clamping::HeightsAttrLocation       = osg::Drawable::FOG_COORDS;
 const char* Clamping::HeightsAttrName           = "oe_clamp_height";
 
 const float Clamping::ClampToAnchor = 1.0f;
@@ -72,12 +76,10 @@ namespace
             }
         }
 
-        void apply(osg::Geode& geode) 
+        void apply(osg::Drawable& drawable)
         {
-            for(unsigned i=0; i<geode.getNumDrawables(); ++i)
-            {
-                apply( geode.getDrawable(i)->asGeometry() );
-            }
+            osg::Geometry* geom = drawable.asGeometry();
+            if (geom) apply(geom);
         }
     };
 }
@@ -108,7 +110,8 @@ Clamping::installHasAttrsUniform(osg::StateSet* stateset)
 {
     if ( stateset )
     {
-        stateset->addUniform( new osg::Uniform(Clamping::HasAttrsUniformName, true) );
+        stateset->setDefine("OE_CLAMP_HAS_ATTRIBUTES");
+        //stateset->addUniform( new osg::Uniform(Clamping::HasAttrsUniformName, true) );
     }
 }
 
@@ -122,3 +125,132 @@ Clamping::setHeights(osg::Geometry* geom, osg::FloatArray* hats)
         geom->setVertexAttribNormalize( HeightsAttrLocation, false );
     }
 }
+
+
+
+#undef LC
+#define LC "[ClampingCullSet] "
+
+ClampingCullSet::ClampingCullSet() :
+_frameCulled( true )
+{
+    // nop
+}
+
+void
+ClampingCullSet::push(ClampableNode* node, const osg::NodePath& path, const osg::FrameStamp* fs)
+{
+    // Reset the set if this is the first push after a cull.
+    if ( _frameCulled )
+    {
+        _frameCulled = false;
+        _entries.clear();
+        _bs.init();
+    }
+
+    _entries.push_back( Entry() );
+    Entry& entry = _entries.back();
+    entry._node = node;
+    entry._path.setNodePath( path );
+    entry._matrix = new osg::RefMatrix( osg::computeLocalToWorld(path) );
+    entry._frame = fs ? fs->getFrameNumber() : 0;
+    _bs.expandBy( osg::BoundingSphere(
+        node->getBound().center() * (*entry._matrix.get()),
+        node->getBound().radius() ));
+
+    OE_DEBUG << LC << "Pushed " << node << " on frame " << entry._frame << std::endl;
+}
+
+void
+ClampingCullSet::accept(osg::NodeVisitor& nv)
+{
+    if ( nv.getVisitorType() == nv.CULL_VISITOR )
+    {
+        ProxyCullVisitor* cv = dynamic_cast<ProxyCullVisitor*>(&nv);
+
+        // We will use the visitor's path to prevent doubely-applying the statesets
+        // of common ancestors
+        const osg::NodePath& nvPath = nv.getNodePath();
+
+        int frame = nv.getFrameStamp() ? nv.getFrameStamp()->getFrameNumber() : 0u;
+
+        unsigned passed = 0u;
+
+        for( std::vector<Entry>::iterator entry = _entries.begin(); entry != _entries.end(); ++entry )
+        {
+            if ( frame - entry->_frame > 1 )
+                continue;
+
+            // If there's an active (non-identity matrix), apply it
+            if ( entry->_matrix.valid() )
+            {
+                entry->_matrix->postMult( *cv->getModelViewMatrix() );
+                cv->pushModelViewMatrix( entry->_matrix.get(), osg::Transform::RELATIVE_RF );
+            }
+
+            // After pushing the matrix, we can perform the culling bounds test.
+            if (!cv->isCulledByProxyFrustum(*entry->_node.get()))
+            {
+                // Apply the statesets in the entry's node path, but skip over the ones that are
+                // shared with the current visitor's path since they are already in effect.
+                // Count them so we can pop them later.
+                int numStateSets = 0;
+                osg::RefNodePath nodePath;
+                if ( entry->_path.getRefNodePath(nodePath) )
+                {
+                    for(unsigned i=0; i<nodePath.size(); ++i)
+                    {
+                        if (nodePath[i].valid())
+                        {
+                            if (i >= nvPath.size() || nvPath[i] != nodePath[i].get())
+                            {
+                                osg::StateSet* stateSet = nodePath[i]->getStateSet();
+                                if ( stateSet )
+                                {
+                                    cv->getCullVisitor()->pushStateSet( stateSet );
+                                    ++numStateSets;
+                                }
+                            }
+                        }
+                    }
+                }
+
+                // Cull the DrapeableNode's children (but not the DrapeableNode itself!)
+                for(unsigned i=0; i<entry->_node->getNumChildren(); ++i)
+                {
+                    entry->_node->getChild(i)->accept( nv );
+                }
+            
+                // pop the same number we pushed
+                for(int i=0; i<numStateSets; ++i)
+                {
+                    cv->getCullVisitor()->popStateSet();
+                }
+
+                ++passed;
+            }
+
+            // pop the model view:
+            if ( entry->_matrix.valid() )
+            {
+                cv->popModelViewMatrix();
+            }
+            
+            //Registry::instance()->startActivity("ClampingCullSet", Stringify() << std::hex << this << std::dec << " / " << passed << "/" << _entries.size());
+        }
+
+        // mark this set so it will reset for the next frame
+        _frameCulled = true;
+    }
+}
+
+ClampingCullSet&
+ClampingManager::get(const osg::Camera* cam)
+{
+    // Known issue: it is possible for a draping cull set to be "orphaned" - this
+    // would happen if the cull set were populated and then not used. This is a
+    // very unlikely scenario (because the scene graph would have to change mid-cull)
+    // but nevertheless possible.
+    //Registry::instance()->startActivity("ClampingManager", Stringify() << _sets.size());
+    return _sets.get(cam);
+}
\ No newline at end of file
diff --git a/src/osgEarth/ClampingTechnique b/src/osgEarth/ClampingTechnique
index 6be9095..1889975 100644
--- a/src/osgEarth/ClampingTechnique
+++ b/src/osgEarth/ClampingTechnique
@@ -24,8 +24,9 @@
 
 #include <osgEarth/Common>
 #include <osgEarth/OverlayDecorator>
+#include <osgEarth/Clamping>
 
-#define OSGEARTH_CLAMPING_BIN           "osgEarth::ClampingBin"
+#define OSGEARTH_CLAMPING_BIN "osgEarth::ClampingBin"
 
 namespace osgEarth
 {
@@ -93,6 +94,9 @@ namespace osgEarth
         void onInstall( TerrainEngineNode* engine );
 
         void onUninstall( TerrainEngineNode* engine );
+        
+        const osg::BoundingSphere& getBound(
+            OverlayDecorator::TechRTTParams& params) const;
 
     protected:
         virtual ~ClampingTechnique() { }
@@ -102,6 +106,10 @@ namespace osgEarth
         optional<int>      _textureSize;
         TerrainEngineNode* _engine;
 
+        mutable ClampingManager _clampingManager;
+        ClampingManager& getClampingManager() { return _clampingManager; }
+        friend class MapNode;
+
     private:
         void setUpCamera(OverlayDecorator::TechRTTParams& params);
     };
diff --git a/src/osgEarth/ClampingTechnique.cpp b/src/osgEarth/ClampingTechnique.cpp
index 25116d7..f5b9735 100644
--- a/src/osgEarth/ClampingTechnique.cpp
+++ b/src/osgEarth/ClampingTechnique.cpp
@@ -36,16 +36,11 @@
 #include <osg/ValueObject>
 #include <osg/Timer>
 
-#include <osgDB/WriteFile>
-
 #define LC "[ClampingTechnique] "
 
 //#define OE_TEST if (_dumpRequested) OE_INFO << std::setprecision(9)
 #define OE_TEST OE_NULL
 
-//#define USE_RENDER_BIN 1
-#undef USE_RENDER_BIN
-
 //#define DUMP_RTT_IMAGE 1
 //#undef DUMP_RTT_IMAGE
 
@@ -55,14 +50,9 @@ using namespace osgEarth;
 
 //---------------------------------------------------------------------------
 
+#ifdef TIME_RTT_CAMERA
 namespace
 {
-    osg::Group* s_providerImpl(MapNode* mapNode)
-    {
-        return mapNode ? mapNode->getOverlayDecorator()->getGroup<ClampingTechnique>() : 0L;
-    }
-
-#ifdef TIME_RTT_CAMERA
     static osg::Timer_t t0, t1;
     struct RttIn : public osg::Camera::DrawCallback {
         void operator()(osg::RenderInfo& r) const {
@@ -75,10 +65,8 @@ namespace
             OE_NOTICE << "RTT = " << osg::Timer::instance()->delta_m(t0, t1) << "ms" << std::endl;
         }
     };
-#endif
 }
-
-ClampingTechnique::TechniqueProvider ClampingTechnique::Provider = s_providerImpl;
+#endif
 
 //---------------------------------------------------------------------------
 
@@ -120,79 +108,6 @@ namespace
 #endif
 }
 
-#ifdef USE_RENDER_BIN
-
-//---------------------------------------------------------------------------
-// Custom bin for clamping.
-
-namespace
-{
-    struct ClampingRenderBin : public osgUtil::RenderBin
-    {
-        struct PerViewData // : public osg::Referenced
-        {
-            osg::observer_ptr<LocalPerViewData> _techData;
-        };
-
-        // shared across ALL render bin instances.
-        typedef Threading::PerObjectMap<osg::Camera*, PerViewData> PerViewDataMap;
-        PerViewDataMap* _pvd; 
-
-        // support cloning (from RenderBin):
-        virtual osg::Object* cloneType() const { return new ClampingRenderBin(); }
-        virtual osg::Object* clone(const osg::CopyOp& copyop) const { return new ClampingRenderBin(*this,copyop); } // note only implements a clone of type.
-        virtual bool isSameKindAs(const osg::Object* obj) const { return dynamic_cast<const ClampingRenderBin*>(obj)!=0L; }
-        virtual const char* libraryName() const { return "osgEarth"; }
-        virtual const char* className() const { return "ClampingRenderBin"; }
-
-
-        // constructs the prototype for this render bin.
-        ClampingRenderBin() : osgUtil::RenderBin()
-        {
-            this->setName( OSGEARTH_CLAMPING_BIN );
-            _pvd = new PerViewDataMap();
-        }
-
-        ClampingRenderBin( const ClampingRenderBin& rhs, const osg::CopyOp& op )
-            : osgUtil::RenderBin( rhs, op ), _pvd( rhs._pvd )
-        {
-            // zero out the stateset...dont' want to share that!
-            _stateset = 0L;
-        }
-
-        // override.
-        void sortImplementation()
-        {
-            copyLeavesFromStateGraphListToRenderLeafList();
-        }
-
-        // override.
-        void drawImplementation(osg::RenderInfo& renderInfo, osgUtil::RenderLeaf*& previous)
-        {
-            // find and initialize the state set for this camera.
-            if ( !_stateset.valid() )
-            {
-                osg::Camera* camera = renderInfo.getCurrentCamera();
-                PerViewData& data = _pvd->get(camera);
-                if ( data._techData.valid() )
-                {
-                    _stateset = data._techData->_groupStateSet.get();
-                    LocalPerViewData* local = static_cast<LocalPerViewData*>(data._techData.get());
-                    local->_renderLeafCount = _renderLeafList.size();
-                }
-            }
-
-            osgUtil::RenderBin::drawImplementation( renderInfo, previous );
-        }
-    };
-}
-
-/** the static registration. */
-extern "C" void osgEarth_clamping_bin_registration(void) {}
-static osgEarthRegisterRenderBinProxy<ClampingRenderBin> s_regbin(OSGEARTH_CLAMPING_BIN);
-
-#endif // USE_RENDER_BIN
-
 //---------------------------------------------------------------------------
 
 ClampingTechnique::ClampingTechnique() :
@@ -210,12 +125,7 @@ _engine(0L)
 bool
 ClampingTechnique::hasData(OverlayDecorator::TechRTTParams& params) const
 {
-#ifdef USE_RENDER_BIN
-    //TODO: reconsider
-    return true;
-#else
-    return params._group->getNumChildren() > 0;
-#endif
+    return getBound(params).valid();
 }
 
 
@@ -228,6 +138,8 @@ ClampingTechnique::reestablish(TerrainEngineNode* engine)
 void
 ClampingTechnique::setUpCamera(OverlayDecorator::TechRTTParams& params)
 {
+    OE_INFO << LC << "Using texture size = " << _textureSize.get() << std::endl;
+
     // To store technique-specific per-view info:
     LocalPerViewData* local = new LocalPerViewData();
     params._techniqueData = local;
@@ -246,8 +158,9 @@ ClampingTechnique::setUpCamera(OverlayDecorator::TechRTTParams& params)
     local->_rttTexture->setWrap( osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE );
     //local->_rttTexture->setBorderColor( osg::Vec4(0,0,0,1) );
 
-    // set up the RTT camera:
+    // set up the RTT camera for rendering a depth map of the terrain:
     params._rttCamera = new osg::Camera();
+    params._rttCamera->setName("GPU Clamping");
     params._rttCamera->setReferenceFrame( osg::Camera::ABSOLUTE_RF_INHERIT_VIEWPOINT );
     params._rttCamera->setClearDepth( 1.0 );
     params._rttCamera->setClearMask( GL_DEPTH_BUFFER_BIT );
@@ -312,6 +225,9 @@ ClampingTechnique::setUpCamera(OverlayDecorator::TechRTTParams& params)
 
     local->_groupStateSet->setRenderingHint( osg::StateSet::TRANSPARENT_BIN );
 
+    // set a define so the shaders know we are running GPU clamping.
+    local->_groupStateSet->setDefine("OE_GPU_CLAMPING");
+
     // uniform for the horizon distance (== max clamping distance)
     local->_horizonDistance2Uniform = local->_groupStateSet->getOrCreateUniform(
         "oe_clamp_horizonDistance2",
@@ -376,29 +292,15 @@ ClampingTechnique::preCullTerrain(OverlayDecorator::TechRTTParams& params,
     if ( !params._rttCamera.valid() && hasData(params) )
     {
         setUpCamera( params );
-
-#ifdef USE_RENDER_BIN
-
-        // store our camera's stateset in the perview data.
-        ClampingRenderBin* bin = dynamic_cast<ClampingRenderBin*>( osgUtil::RenderBin::getRenderBinPrototype(OSGEARTH_CLAMPING_BIN) );
-        if ( bin )
-        {
-            ClampingRenderBin::PerViewData& data = bin->_pvd->get( cv->getCurrentCamera() );
-            LocalPerViewData* local = static_cast<LocalPerViewData*>(params._techniqueData.get());
-            data._techData = local;
-        }
-        else
-        {
-            OE_WARN << LC << "Odd, no prototype found for the clamping bin." << std::endl;
-        }
-
-#endif
     }
-
-#ifdef TIME_RTT_CAMERA
-#endif
 }
 
+       
+const osg::BoundingSphere&
+ClampingTechnique::getBound(OverlayDecorator::TechRTTParams& params) const
+{
+    return _clampingManager.get(params._mainCamera).getBound();
+}
 
 void
 ClampingTechnique::cullOverlayGroup(OverlayDecorator::TechRTTParams& params,
@@ -461,24 +363,21 @@ ClampingTechnique::cullOverlayGroup(OverlayDecorator::TechRTTParams& params,
         local._depthClipToCamViewUniform->set( depthClipToCameraView );
 #endif
 
-        if ( params._group->getNumChildren() > 0 )
-        {
-            // traverse the overlay nodes, applying the clamping shader.
-            cv->pushStateSet( local._groupStateSet.get() );
+        // traverse the overlay nodes, applying the clamping shader.
+        cv->pushStateSet(local._groupStateSet.get());
 
-            // Since the vertex shader is moving the verts to clamp them to the terrain,
-            // OSG will not be able to properly cull the geometry. (Specifically: OSG may
-            // cull geometry which is invisible when NOT clamped, but becomes visible after
-            // GPU clamping.) We work around that by using a Proxy cull visitor that will 
-            // use the RTT camera's matrixes for frustum culling (instead of the main camera's).
-            ProxyCullVisitor pcv( cv, params._rttProjMatrix, params._rttViewMatrix );
+        // Since the vertex shader is moving the verts to clamp them to the terrain,
+        // OSG will not be able to properly cull the geometry. (Specifically: OSG may
+        // cull geometry which is invisible when NOT clamped, but becomes visible after
+        // GPU clamping.) We work around that by using a Proxy cull visitor that will 
+        // use the RTT camera's matrixes for frustum culling (instead of the main camera's).
+        ProxyCullVisitor pcv(cv, params._rttProjMatrix, params._rttViewMatrix);
 
-            // cull the clampable geometry.
-            params._group->accept( pcv );
+        // cull the clampable geometry.
+        getClampingManager().get(cv->getCurrentCamera()).accept(pcv);
 
-            // done; pop the clamping shaders.
-            cv->popStateSet();
-        }
+        // done; pop the clamping shaders.
+        cv->popStateSet();
     }
 }
 
@@ -503,8 +402,6 @@ ClampingTechnique::onInstall( TerrainEngineNode* engine )
     {
         unsigned maxSize = Registry::capabilities().getMaxFastTextureSize();
         _textureSize.init( osg::minimum( 4096u, maxSize ) );
-
-        OE_INFO << LC << "Using texture size = " << *_textureSize << std::endl;
     }
 }
 
diff --git a/src/osgEarth/ColorFilter b/src/osgEarth/ColorFilter
index 29d160a..c47bdcd 100644
--- a/src/osgEarth/ColorFilter
+++ b/src/osgEarth/ColorFilter
@@ -31,6 +31,9 @@ namespace osgEarth
      * You can install a chain of ColorFilters on an ImageLayer and the shaders
      * will post-process the layer's color (after texturing but before lighting)
      * using custom shader code.
+     *
+     * NOTE: ColorFilter will probably be deprecated at some point down the line.
+     * Consider using a Layer shader instead.
      */
     class /*header-only*/ ColorFilter : public osg::Referenced
     {
@@ -130,7 +133,12 @@ namespace osgEarth
 
     // Macro used to register new annotation types.
 #define OSGEARTH_REGISTER_COLORFILTER( KEY, CLASSNAME ) \
+    extern "C" void osgearth_colorfilter_##KEY(void) {} \
     static osgEarth::ColorFilterRegistrationProxy< CLASSNAME > s_osgEarthColorFilterRegistrationProxy##KEY( #KEY )
+    
+#define USE_OSGEARTH_COLORFILTER( KEY ) \
+    extern "C" void osgearth_colorfilter_##KEY(void); \
+    static osgDB::PluginFunctionProxy proxy_osgearth_colorfilter_##KEY(osgearth_colorfilter_##KEY);
 
 } // namespace osgEarth
 
diff --git a/src/osgEarth/CompositeTileSource.cpp b/src/osgEarth/CompositeTileSource.cpp
index 8864753..f528802 100644
--- a/src/osgEarth/CompositeTileSource.cpp
+++ b/src/osgEarth/CompositeTileSource.cpp
@@ -56,7 +56,7 @@ CompositeTileSourceOptions::add( const ElevationLayerOptions& options )
 Config 
 CompositeTileSourceOptions::getConfig() const
 {    
-    Config conf = TileSourceOptions::newConfig();
+    Config conf = TileSourceOptions::getConfig();
 
     for( ComponentVector::const_iterator i = _components.begin(); i != _components.end(); ++i )
     {
@@ -155,7 +155,7 @@ CompositeTileSource::createImage(const TileKey&    key,
     {
         ImageLayer* layer = itr->get();
         ImageInfo imageInfo;
-        imageInfo.dataInExtents = layer->getTileSource()->hasDataInExtent( key.getExtent() );
+        imageInfo.dataInExtents = layer->mayHaveDataInExtent(key.getExtent());
         imageInfo.opacity = layer->getOpacity();
 
         if (imageInfo.dataInExtents)
@@ -165,6 +165,13 @@ CompositeTileSource::createImage(const TileKey&    key,
             {
                 imageInfo.image = image.getImage();
             }
+
+            // If the progress got cancelled or it needs a retry then return NULL to prevent this tile from being built and cached with incomplete or partial data.
+            if (progress && (progress->isCanceled() || progress->needsRetry()))
+            {
+                OE_DEBUG << LC << " createImage was cancelled or needs retry for " << key.str() << std::endl;
+                return 0L;
+            }
         }
 
         images.push_back(imageInfo);
@@ -205,6 +212,14 @@ CompositeTileSource::createImage(const TileKey&    key,
                     {
                         break;
                     }
+
+                    // If the progress got cancelled or it needs a retry then return NULL to prevent this tile from being built and cached with incomplete or partial data.
+                    if (progress && (progress->isCanceled() || progress->needsRetry()))
+                    {
+                        OE_DEBUG << LC << " createImage was cancelled or needs retry for " << key.str() << std::endl;
+                        return 0L;
+                    }
+
                     parentKey = parentKey.createParentKey();
                 }
 
@@ -279,8 +294,7 @@ osg::HeightField* CompositeTileSource::createHeightField(
             const TileKey&        key,
             ProgressCallback*     progress )
 {    
-    unsigned int size = *getOptions().tileSize();    
-    bool hae = false;
+    unsigned size = getPixelsPerTile(); //int size = *getOptions().tileSize();    
     osg::ref_ptr< osg::HeightField > heightField = new osg::HeightField();
     heightField->allocate(size, size);
 
@@ -291,7 +305,7 @@ osg::HeightField* CompositeTileSource::createHeightField(
     }  
 
     // Populate the heightfield and return it if it's valid
-    if (_elevationLayers.populateHeightField(heightField.get(), key, 0, INTERP_BILINEAR, progress))
+    if (_elevationLayers.populateHeightFieldAndNormalMap(heightField.get(), 0L, key, 0, INTERP_BILINEAR, progress))
     {                
         return heightField.release();
     }
@@ -319,7 +333,7 @@ CompositeTileSource::add( ImageLayer* layer )
     _imageLayers.push_back( layer );
     CompositeTileSourceOptions::Component comp;
     comp._layer = layer;
-    comp._imageLayerOptions = layer->getImageLayerOptions();
+    comp._imageLayerOptions = layer->options();
     _options._components.push_back( comp );    
 
     return true;
@@ -343,7 +357,7 @@ CompositeTileSource::add( ElevationLayer* layer )
     _elevationLayers.push_back( layer );
     CompositeTileSourceOptions::Component comp;
     comp._layer = layer;
-    comp._elevationLayerOptions = layer->getElevationLayerOptions();
+    comp._elevationLayerOptions = layer->options();
     _options._components.push_back( comp );    
 
     return true;
@@ -357,6 +371,8 @@ CompositeTileSource::initialize(const osgDB::Options* dbOptions)
 
     osg::ref_ptr<const Profile> profile = getProfile();
 
+    bool dataExtentsValid = true;
+
     for(CompositeTileSourceOptions::ComponentVector::iterator i = _options._components.begin();
         i != _options._components.end(); )
     {        
@@ -370,13 +386,13 @@ CompositeTileSource::initialize(const osgDB::Options* dbOptions)
             Status status = layer->open();
             if (status.isOK())
             {
-                i->_layer = layer;
-                _imageLayers.push_back( layer );
-                OE_INFO << LC << " .. added image layer " << layer->getName() << " (" << i->_imageLayerOptions->driver()->getDriver() << ")\n";
+                i->_layer = layer.get();
+                _imageLayers.push_back( layer.get() );
+                OE_INFO << LC << "Added image layer " << layer->getName() << " (" << i->_imageLayerOptions->driver()->getDriver() << ")\n";
             }
             else
             {
-                OE_WARN << LC << "Could not open image layer (" << layer->getName() << ") ... " << status.message() << std::endl;
+                OE_DEBUG << LC << "Could not open image layer (" << layer->getName() << ") ... " << status.message() << std::endl;
             }            
         }
         else if (i->_elevationLayerOptions.isSet() && !i->_layer.valid())
@@ -390,7 +406,8 @@ CompositeTileSource::initialize(const osgDB::Options* dbOptions)
             if (status.isOK())
             {
                 i->_layer = layer;
-                _elevationLayers.push_back( layer.get() );                
+                _elevationLayers.push_back( layer.get() );   
+                OE_INFO << LC << "Added elevation layer " << layer->getName() << " (" << i->_elevationLayerOptions->driver()->getDriver() << ")\n";             
             }
             else
             {
@@ -400,7 +417,7 @@ CompositeTileSource::initialize(const osgDB::Options* dbOptions)
 
         if ( !i->_layer.valid() )
         {
-            OE_WARN << LC << "A component has no valid TerrainLayer ... removing." << std::endl;
+            OE_DEBUG << LC << "A component has no valid TerrainLayer ... removing." << std::endl;            
             i = _options._components.erase( i );
         }
         else
@@ -416,23 +433,42 @@ CompositeTileSource::initialize(const osgDB::Options* dbOptions)
             _dynamic = _dynamic || source->isDynamic();
             
             // gather extents                        
-            const DataExtentList& extents = source->getDataExtents();            
-            for( DataExtentList::const_iterator j = extents.begin(); j != extents.end(); ++j )
-            {                
-                // Convert the data extent to the profile that is actually used by this TileSource
-                DataExtent dataExtent = *j;                
-                GeoExtent ext = dataExtent.transform(profile->getSRS());
-                unsigned int minLevel = 0;
-                unsigned int maxLevel = profile->getEquivalentLOD( source->getProfile(), *dataExtent.maxLevel() );                                        
-                dataExtent = DataExtent(ext, minLevel, maxLevel);                                
-                getDataExtents().push_back( dataExtent );
-            }          
+            const DataExtentList& extents = source->getDataExtents();  
+
+            // If even one of the layers' data extents is unknown, the entire composite
+            // must have unknown data extents:
+            if (extents.empty())
+            {
+                dataExtentsValid = false;
+                getDataExtents().clear();
+            }
+
+            if (dataExtentsValid)
+            {
+                for( DataExtentList::const_iterator j = extents.begin(); j != extents.end(); ++j )
+                {                
+                    // Convert the data extent to the profile that is actually used by this TileSource
+                    DataExtent dataExtent = *j;                
+                    GeoExtent ext = dataExtent.transform(profile->getSRS());
+                    unsigned int minLevel = 0;
+                    unsigned int maxLevel = profile->getEquivalentLOD( source->getProfile(), *dataExtent.maxLevel() );                                        
+                    dataExtent = DataExtent(ext, minLevel, maxLevel);                                
+                    getDataExtents().push_back( dataExtent );
+                }
+            }            
         }
 
         ++i;
     }
 
-    // set the new profile that was derived from the components
+    // If there is no profile set by the user or by a component, fall back
+    // on a default profile. This will allow the Layer to continue to operate
+    // off the cache even if all components fail to initialize for some reason.
+    if (profile.valid() == false)
+    {
+        profile = Profile::create("global-geodetic");
+    }
+
     setProfile( profile.get() );
 
     _initialized = true;
diff --git a/src/osgEarth/Config b/src/osgEarth/Config
index fda8ed3..0feeddc 100644
--- a/src/osgEarth/Config
+++ b/src/osgEarth/Config
@@ -155,8 +155,9 @@ namespace osgEarth
             }
         }
 
-        /** Copy of the first child with the given key */
-        Config child( const std::string& key ) const;
+        /** First child with the given key */
+        //Config child( const std::string& key ) const;
+        const Config& child( const std::string& key ) const;
 
         /** Pointer to the first child with the given key, or NULL if none exist */
         const Config* child_ptr( const std::string& key ) const;
@@ -245,22 +246,30 @@ namespace osgEarth
         /** Adds or replaces a value as a child, but only if it is set */
         template<typename T>
         void updateIfSet( const std::string& key, const optional<T>& opt ) {
+            remove(key);
             if ( opt.isSet() ) {
-                remove(key);
                 add( key, osgEarth::toString<T>( opt.value() ) );
             }
         }
+        template<typename T>
+        void set( const std::string& key, const optional<T>& opt ) {
+            updateIfSet(key, opt);
+        }
         
         /** Adds or replaces a referenced object as a child, but only if it is set */
         template<typename T>
         void updateObjIfSet( const std::string& key, const osg::ref_ptr<T>& opt ) {
+            remove(key);
             if ( opt.valid() ) {
-                remove(key);
                 Config conf = opt->getConfig();
                 conf.key() = key;
                 add( conf );
             }
         }
+        template<typename T>
+        void setObj( const std::string& key, const osg::ref_ptr<T>& opt ) {
+            updateObjIfSet(key, opt);
+        }
         
         /** Adds or replaces an object as a child, but only if it is set */
         template<typename T>
@@ -272,15 +281,25 @@ namespace osgEarth
                 add( conf );
             }
         }
+        template<typename T>
+        void setObj( const std::string& key, const optional<T>& obj ) {
+            updateObjIfSet(key, obj);
+        }
         
         /** Adds or replaces an enumeration value as a child, but only if it is set */
         template<typename X, typename Y>
         void updateIfSet( const std::string& key, const std::string& val, const optional<X>& target, const Y& targetValue ) {
-            if ( target.isSetTo( targetValue ) ) {
+            if (target.isSetTo(targetValue)) {
                 remove(key);
                 add( key, val );
             }
         }
+
+        // If target is set to targetValue, set key to val.
+        template<typename X, typename Y>
+        void set( const std::string& key, const std::string& val, const optional<X>& target, const Y& targetValue ) {
+            updateIfSet(key, val, target, targetValue);
+        }
         
         /** Adds or replaces value as a child */
         template<typename T>
@@ -293,6 +312,10 @@ namespace osgEarth
             remove(conf.key());
             add( conf );
         }
+        template<typename T>
+        void set( const std::string& key, const T& value ) {
+            update( key, value );
+        }
 
         /** Adds or replaces an object as a child */
         template<typename T>
@@ -302,11 +325,9 @@ namespace osgEarth
             conf.key() = key;
             add( conf );
         }
-
-        /** Same as update */
         template<typename T>
-        void set( const std::string& key, const T& value ) {
-            update( key, value );
+        void setObj( const std::string& key, const T& value ) {
+            updateObj(key, value);
         }
 
         /** Whether this object has the key OR has a child with the key */
@@ -466,6 +487,16 @@ namespace osgEarth
     }
 
     template<> inline
+    void Config::set<Config>( const std::string& key, const optional<Config>& opt ) {
+        remove(key);
+        if ( opt.isSet() ) {
+            Config conf = opt.value();
+            conf.key() = key;
+            add( conf );
+        }
+    }
+
+    template<> inline
     bool Config::getIfSet<Config>( const std::string& key, optional<Config>& output ) const {
         if ( hasChild( key ) ) {
             output = child(key);
@@ -529,7 +560,7 @@ namespace osgEarth
         ConfigOptions( const Config& conf =Config() )
             : _conf( conf ) { }
         ConfigOptions( const ConfigOptions& rhs )
-            : _conf( rhs.getConfig() ) { }
+            : _conf( rhs.getConfig() ) { } //rhs._conf ) { }
 
         virtual ~ConfigOptions();
         
@@ -548,11 +579,7 @@ namespace osgEarth
             mergeConfig( rhs.getConfig() );
         }
 
-        virtual Config getConfig() const { return _conf; }
-
-        virtual Config getConfig( bool isolate ) const { return isolate ? newConfig() : _conf; }
-
-        Config newConfig() const { Config c; c.setReferrer(referrer()); return c; }
+        virtual Config getConfig() const;
 
         bool empty() const { return _conf.empty(); }
 
@@ -584,11 +611,6 @@ namespace osgEarth
             conf.set("driver", _driver);
             return conf;
         }
-        virtual Config getConfig( bool isolate ) const {
-            Config conf = ConfigOptions::getConfig( isolate );
-            conf.set("driver", _driver);
-            return conf;
-        }
 
         virtual void mergeConfig( const Config& conf ) {
             ConfigOptions::mergeConfig(conf);
diff --git a/src/osgEarth/Config.cpp b/src/osgEarth/Config.cpp
index e29ec36..52b6b92 100644
--- a/src/osgEarth/Config.cpp
+++ b/src/osgEarth/Config.cpp
@@ -45,7 +45,8 @@ Config::setReferrer( const std::string& referrer )
         return;
 
     std::string absReferrer;
-    if( !osgDB::containsServerAddress( referrer ) ) {
+    if( !osgDB::containsServerAddress( referrer ) && !osgDB::isAbsolutePath( referrer ) ) {
+
         absReferrer = osgEarth::getAbsolutePath( referrer );
 
         if( osgEarth::isRelativePath( absReferrer ) )
@@ -80,6 +81,21 @@ Config::fromXML( std::istream& in )
     return xml.valid();
 }
 
+#if 1
+const Config&
+Config::child( const std::string& childName ) const
+{
+    for( ConfigSet::const_iterator i = _children.begin(); i != _children.end(); i++ ) {
+        if ( i->key() == childName )
+            return *i;
+    }
+    static Config s_emptyConf;
+    return s_emptyConf;
+    //Config emptyConf;
+    //emptyConf.setReferrer( _referrer );
+    //return emptyConf;
+}
+#else
 Config
 Config::child( const std::string& childName ) const
 {
@@ -87,11 +103,11 @@ Config::child( const std::string& childName ) const
         if ( i->key() == childName )
             return *i;
     }
-
     Config emptyConf;
     emptyConf.setReferrer( _referrer );
     return emptyConf;
 }
+#endif
 
 const Config*
 Config::child_ptr( const std::string& childName ) const
@@ -169,6 +185,16 @@ ConfigOptions::~ConfigOptions()
 {
 }
 
+Config
+ConfigOptions::getConfig() const
+{
+    // iniialize with the raw original conf. subclass getConfig's can 
+    // override the values there.
+    Config conf = _conf;
+    conf.setReferrer(referrer());
+    return conf;
+}
+
 /****************************************************************/
 DriverConfigOptions::~DriverConfigOptions()
 {
diff --git a/src/osgEarth/Containers b/src/osgEarth/Containers
index 881b03c..a340aec 100644
--- a/src/osgEarth/Containers
+++ b/src/osgEarth/Containers
@@ -314,14 +314,14 @@ namespace osgEarth
 
     public:
         LRUCache( unsigned max =100 ) : _max(max), _threadsafe(false) {
-            _buf = _max/10;
             _queries = 0;
             _hits = 0;
+            setMaxSize_impl(max);
         }
         LRUCache( bool threadsafe, unsigned max =100 ) : _max(max), _threadsafe(threadsafe) {
-            _buf = _max/10;
             _queries = 0;
             _hits = 0;
+            setMaxSize_impl(max);
         }
 
         /** dtor */
@@ -467,8 +467,8 @@ namespace osgEarth
         }
 
         void setMaxSize_impl( unsigned max ) {
-            _max = max;
-            _buf = max/10;
+            _max = std::max(max,10u);
+            _buf = _max/10u;
             while( _map.size() > _max ) {
                 const K& key = _lru.front();
                 _map.erase( key );
@@ -685,33 +685,42 @@ namespace osgEarth
     template<typename KEY, typename DATA>
     struct PerObjectFastMap
     {
+        struct Functor {
+            virtual void operator()(DATA& data) =0;
+        };
+
         DATA& get(KEY k)
         {
-            {
-                osgEarth::Threading::ScopedReadLock readLock(_mutex);
-                typename osgEarth::fast_map<KEY,DATA>::iterator i = _data.find(k);
-                if ( i != _data.end() )
-                    return i->second;
-            }
-            {
-                osgEarth::Threading::ScopedWriteLock lock(_mutex);
-                typename osgEarth::fast_map<KEY,DATA>::iterator i = _data.find(k);
-                if ( i != _data.end() )
-                    return i->second;
-                else
-                    return _data[k];
-            }
+            osgEarth::Threading::ScopedMutexLock lock(_mutex);
+            typename osgEarth::fast_map<KEY,DATA>::iterator i = _data.find(k);
+            if ( i != _data.end() )
+                return i->second;
+            else
+                return _data[k];
         }
 
         void remove(KEY k)
         {
-            osgEarth::Threading::ScopedWriteLock exclusive(_mutex);
+            osgEarth::Threading::ScopedMutexLock lock(_mutex);
             _data.erase( k );
         }
 
+        void forEach(Functor& functor)
+        {
+            osgEarth::Threading::ScopedMutexLock lock(_mutex);
+            for (typename fast_map<KEY, DATA>::iterator i = _data.begin(); i != _data.end(); ++i)
+                functor.operator()(i->second);
+        }
+
+        unsigned size() const
+        {
+            osgEarth::Threading::ScopedMutexLock lock(_mutex);
+            return _data.size();
+        }
+
     private:
-        osgEarth::fast_map<KEY,DATA>        _data;
-        osgEarth::Threading::ReadWriteMutex _mutex;
+        osgEarth::fast_map<KEY,DATA> _data;
+        mutable osgEarth::Threading::Mutex _mutex;
     };
 
     /** Template for thread safe per-object data storage */
@@ -890,6 +899,54 @@ namespace osgEarth
         std::set<osg::observer_ptr<T> >      _data;
         osgEarth::Threading::ReadWriteMutex  _mutex;
     };
+    
+
+    // borrowed from osg::buffered_object. Auto-resizing array.
+    template<class T>
+    class AutoArray
+    {
+    public:
+        inline AutoArray() { }
+
+        inline AutoArray(unsigned int size) : _array(size) { }
+
+        AutoArray& operator = (const AutoArray& rhs)
+        {
+            _array = rhs._array;
+            return *this;
+        }
+
+        inline void setAllElementsTo(const T& t) { std::fill(_array.begin(), _array.end(), t); }
+        inline void clear() { _array.clear(); }
+        inline bool empty() const { return _array.empty(); }
+        inline unsigned int size() const { return _array.size(); }
+        inline void resize(unsigned int newSize) { _array.resize(newSize); }
+
+        inline T& operator[] (unsigned int pos)
+        {
+            if (_array.size() <= pos)
+                _array.resize(pos + 1);
+
+            return _array[pos];
+        }
+
+        inline const T& operator[] (unsigned int pos) const
+        {
+            // automatically resize array.
+            if (_array.size() <= pos)
+                _array.resize(pos + 1);
+
+            return _array[pos];
+        }
+
+        inline T& back() { return _array[size()-1]; }
+        inline const T& back() const { return _array[size()-1]; }
+
+    protected:
+
+        mutable std::vector<T> _array;
+    };
+
 }
 
 #endif // OSGEARTH_CONTAINERS_H
diff --git a/src/osgEarth/Cube b/src/osgEarth/Cube
index 1345bcc..67b3ae1 100644
--- a/src/osgEarth/Cube
+++ b/src/osgEarth/Cube
@@ -144,7 +144,14 @@ namespace osgEarth
     protected: // SpatialReference overrides
 
         void _init();
-
+        
+        bool transformInFaceExtentToMBR(
+            const SpatialReference* to_srs,
+            int                     face,
+            double&                 in_out_xmin,
+            double&                 in_out_ymin,
+            double&                 in_out_xmax,
+            double&                 in_out_ymax ) const;
     };
 
     /**
diff --git a/src/osgEarth/Cube.cpp b/src/osgEarth/Cube.cpp
index c47970b..a6d55c5 100644
--- a/src/osgEarth/Cube.cpp
+++ b/src/osgEarth/Cube.cpp
@@ -527,6 +527,57 @@ CubeSpatialReference::transformExtentToMBR(const SpatialReference* to_srs,
                                            double&                 in_out_xmax,
                                            double&                 in_out_ymax ) const
 {
+    // input bounds:
+    Bounds inBounds(in_out_xmin, in_out_ymin, in_out_xmax, in_out_ymax);
+
+    Bounds outBounds;
+
+    // for each CUBE face, find the intersection of the input bounds and that face.
+    for (int face = 0; face < 6; ++face)
+    {
+        Bounds faceBounds( (double)(face), 0.0, (double)(face+1), 1.0);
+
+        Bounds intersection = faceBounds.intersectionWith(inBounds);
+
+        // if they intersect (with a non-zero area; abutting doesn't count in this case)
+        // transform the intersection and include in the result.
+        if (intersection.isValid() && intersection.area2d() > 0.0)
+        {
+            double
+                xmin = intersection.xMin(), ymin = intersection.yMin(),
+                xmax = intersection.xMax(), ymax = intersection.yMax();
+
+            if (transformInFaceExtentToMBR(to_srs, face, xmin, ymin, xmax, ymax))
+            {
+                outBounds.expandBy(Bounds(xmin, ymin, xmax, ymax));
+            }
+        }
+    }
+
+    if (outBounds.valid())
+    {
+        in_out_xmin = outBounds.xMin();
+        in_out_ymin = outBounds.yMin();
+        in_out_xmax = outBounds.xMax();
+        in_out_ymax = outBounds.yMax();
+        return true;
+    }
+    else
+    {
+        return false;
+    }
+}
+
+bool
+CubeSpatialReference::transformInFaceExtentToMBR(const SpatialReference* to_srs,
+                                                 int                     face,
+                                                 double&                 in_out_xmin,
+                                                 double&                 in_out_ymin,
+                                                 double&                 in_out_xmax,
+                                                 double&                 in_out_ymax ) const
+{
+    
+
     // note: this method only works when the extent is isolated to one face of the cube. If you
     // want to transform an artibrary extent, you need to break it up into separate extents for
     // each cube face.
@@ -535,7 +586,7 @@ CubeSpatialReference::transformExtentToMBR(const SpatialReference* to_srs,
     double face_xmin = in_out_xmin, face_ymin = in_out_ymin;
     double face_xmax = in_out_xmax, face_ymax = in_out_ymax;
 
-    int face;
+    //int face;
     CubeUtils::cubeToFace( face_xmin, face_ymin, face_xmax, face_ymax, face );
 
     // for equatorial faces, the normal transformation process will suffice (since it will call into
diff --git a/src/osgEarth/CullingUtils b/src/osgEarth/CullingUtils
index e6fd86f..c26ef49 100644
--- a/src/osgEarth/CullingUtils
+++ b/src/osgEarth/CullingUtils
@@ -89,6 +89,7 @@ namespace osgEarth
     
     /**
      * A simple culling-plane callback (a simpler version of ClusterCullingCallback)
+     * @deprecated
      */
     struct OSGEARTH_EXPORT CullNodeByNormal : public osg::NodeCallback {
         osg::Vec3d _normal;
@@ -96,6 +97,7 @@ namespace osgEarth
         void operator()(osg::Node* node, osg::NodeVisitor* nv);
     };
 
+    // @deprecated
     struct CullDrawableByNormal : public osg::Drawable::CullCallback {
         osg::Vec3d _normal;
         CullDrawableByNormal( const osg::Vec3d& normal ) : _normal(normal) { }
@@ -104,6 +106,7 @@ namespace osgEarth
         }
     };
 
+    // @deprecated
     struct OSGEARTH_EXPORT CullNodeByFrameNumber : public osg::NodeCallback {
         unsigned _frame;
         CullNodeByFrameNumber() : _frame(0) { }
@@ -113,10 +116,12 @@ namespace osgEarth
         }
     };
 
+    // @deprecated
     struct DisableSubgraphCulling : public osg::NodeCallback {
         void operator()(osg::Node* n, osg::NodeVisitor* v);
     };
 
+    // @deprecated
     struct StaticBound : public osg::Node::ComputeBoundingSphereCallback {
         osg::BoundingSphere _bs;
         StaticBound(const osg::BoundingSphere& bs) : _bs(bs) { }
@@ -125,6 +130,7 @@ namespace osgEarth
 
     // a cull callback that prevents objects from being included in the near/fear clip
     // plane calculates that OSG does.
+    // @deprecated
     struct OSGEARTH_EXPORT DoNotComputeNearFarCullCallback : public osg::NodeCallback
     {
         void operator()(osg::Node* node, osg::NodeVisitor* nv);
@@ -195,6 +201,9 @@ namespace osgEarth
         // access to the underlying cull visitor.
         osgUtil::CullVisitor* getCullVisitor() { return _cv; }
 
+        bool isCulledByProxyFrustum(osg::Node& node);
+        bool isCulledByProxyFrustum(const osg::BoundingBox& bbox);
+
     public: // proxy functions:
         osg::Vec3 getEyePoint() const;
         osg::Vec3 getViewPoint() const;
@@ -202,18 +211,17 @@ namespace osgEarth
         float getDistanceFromEyePoint(const osg::Vec3& pos, bool useLODScale) const;
         float getDistanceToViewPoint(const osg::Vec3& pos, bool useLODScale) const;
 
-    protected: // custom culling functions:
-
-        bool isCulledByProxyFrustum(osg::Node& node);
-        bool isCulledByProxyFrustum(const osg::BoundingBox& bbox);
+    protected:
         
         osgUtil::CullVisitor::value_type distance(const osg::Vec3& coord,const osg::Matrix& matrix);
 
         void handle_cull_callbacks_and_traverse(osg::Node& node);
 
         void apply(osg::Node& node);
-        void apply(osg::Transform& node);
-        void apply(osg::Geode& node);
+        void apply(osg::Transform& xform);
+        void apply(osg::Geode& geode);
+        void apply(osg::Drawable& drawable);
+        void apply(osg::LOD& lod);
     };
 
 
@@ -277,6 +285,30 @@ namespace osgEarth
     {
         Config dumpRenderBin(osgUtil::RenderBin* bin) const;
     };
+
+
+    /**
+     * Cull callback for cameras that sets the oe_ViewportSize uniform.
+     */
+    class OSGEARTH_EXPORT InstallViewportSizeUniform : public osg::NodeCallback
+    {
+    public:
+        void operator()(osg::Node* node, osg::NodeVisitor* nv) {
+            osgUtil::CullVisitor* cv = dynamic_cast<osgUtil::CullVisitor*>(nv);
+            const osg::Camera* camera = cv->getCurrentCamera();
+            osg::ref_ptr<osg::StateSet> ss;
+            if (camera && camera->getViewport()) {
+                ss = new osg::StateSet();
+                ss->addUniform(new osg::Uniform("oe_ViewportSize", osg::Vec2f(
+                    camera->getViewport()->width(),
+                    camera->getViewport()->height())));
+                cv->pushStateSet(ss.get());
+            }
+            traverse(node, nv);
+            if (ss.valid())
+                cv->popStateSet();
+        }
+    };
 }
 
 #endif // OSGEARTH_CULLING_UTILS_H
diff --git a/src/osgEarth/CullingUtils.cpp b/src/osgEarth/CullingUtils.cpp
index 1440a24..7115861 100644
--- a/src/osgEarth/CullingUtils.cpp
+++ b/src/osgEarth/CullingUtils.cpp
@@ -19,7 +19,7 @@
 #include <osgEarth/CullingUtils>
 #include <osgEarth/LineFunctor>
 #include <osgEarth/VirtualProgram>
-#include <osgEarth/DPLineSegmentIntersector>
+#include <osgUtil/LineSegmentIntersector>
 #include <osgEarth/GeoData>
 #include <osgEarth/Utils>
 #include <osg/ClusterCullingCallback>
@@ -678,7 +678,7 @@ void OcclusionCullingCallback::operator()(osg::Node* node, osg::NodeVisitor* nv)
                         osg::Vec3d vec = end-start; vec.normalize();
                         end -= vec*1.0;
 
-                        DPLineSegmentIntersector* i = new DPLineSegmentIntersector( start, end );
+                        osgUtil::LineSegmentIntersector* i = new osgUtil::LineSegmentIntersector( start, end );
                         i->setIntersectionLimit( osgUtil::Intersector::LIMIT_NEAREST );
                         osgUtil::IntersectionVisitor iv;
                         iv.setIntersector( i );
@@ -800,15 +800,9 @@ ProxyCullVisitor::distance(const osg::Vec3& coord,const osg::Matrix& matrix)
 void 
 ProxyCullVisitor::handle_cull_callbacks_and_traverse(osg::Node& node)
 {
-#if OSG_VERSION_GREATER_THAN(3,3,1)
     osg::Callback* callback = node.getCullCallback();
     if (callback) callback->run(&node, this);
     else traverse(node);
-#else
-    osg::NodeCallback* callback = node.getCullCallback();
-    if (callback) (*callback)(&node,this);
-    else traverse(node);
-#endif
 }
 
 void 
@@ -874,53 +868,58 @@ ProxyCullVisitor::apply(osg::Transform& node)
 void 
 ProxyCullVisitor::apply(osg::Geode& node)
 {
-    //OE_INFO << "Geode!" << std::endl;
+    ProxyCullVisitor::apply(static_cast<osg::Node&>(node));
+}
 
-    if ( isCulledByProxyFrustum(node) )
+void 
+ProxyCullVisitor::apply(osg::LOD& node)
+{
+    ProxyCullVisitor::apply(static_cast<osg::Node&>(node));
+}
+
+void
+ProxyCullVisitor::apply(osg::Drawable& drawable)
+{
+    if ( isCulledByProxyFrustum(drawable) )
         return;
 
-    _cv->pushOntoNodePath( &node );
+    _cv->pushOntoNodePath( &drawable );
 
     // push the node's state.
-    osg::StateSet* node_state = node.getStateSet();
+    osg::StateSet* node_state = drawable.getStateSet();
     if (node_state) _cv->pushStateSet(node_state);
 
-    // traverse any call callbacks and traverse any children.
-    handle_cull_callbacks_and_traverse(node);
-
     osg::RefMatrix& matrix = *_cv->getModelViewMatrix();
-    for(unsigned int i=0;i<node.getNumDrawables();++i)
-    {
-        osg::Drawable* drawable = node.getDrawable(i);
-        const osg::BoundingBox& bb = Utils::getBoundingBox(drawable);
+    const osg::BoundingBox& bb = Utils::getBoundingBox(&drawable);
 
-        if( drawable->getCullCallback() )
-        {
-#if OSG_VERSION_GREATER_THAN(3,3,1)
-            if( drawable->getCullCallback()->run(drawable, _cv) == true )
-                continue;
-#else
-            if( drawable->getCullCallback()->cull( _cv, drawable, &_cv->getRenderInfo() ) == true )
-                continue;
-#endif
-        }
+    bool culledOut = false;
 
-        //else
-        {
-            if (node.isCullingActive() && isCulledByProxyFrustum(bb)) continue;
-        }
+    if( drawable.getCullCallback() )
+    {
+        if (drawable.getCullCallback()->run(&drawable, _cv) == true)
+            culledOut = true;
+    }
+
+    if (!culledOut)
+    {
+        if (drawable.isCullingActive() && isCulledByProxyFrustum(bb)) 
+            culledOut = true;
+    }
 
 
-        if ( _cv->getComputeNearFarMode() && bb.valid())
-        {
-            if (!_cv->updateCalculatedNearFar(matrix,*drawable,false)) continue;
-        }
+    if ( !culledOut && _cv->getComputeNearFarMode() && bb.valid())
+    {
+        if (!_cv->updateCalculatedNearFar(matrix,drawable,false))
+            culledOut = true;
+    }
 
+    if (!culledOut)
+    {
         // need to track how push/pops there are, so we can unravel the stack correctly.
         unsigned int numPopStateSetRequired = 0;
 
         // push the geoset's state on the geostate stack.
-        osg::StateSet* stateset = drawable->getStateSet();
+        osg::StateSet* stateset = drawable.getStateSet();
         if (stateset)
         {
             ++numPopStateSetRequired;
@@ -954,14 +953,13 @@ ProxyCullVisitor::apply(osg::Geode& node)
         }
         else
         {
-            _cv->addDrawableAndDepth(drawable,&matrix,depth);
+            _cv->addDrawableAndDepth(&drawable,&matrix,depth);
         }
 
         for(unsigned int i=0;i< numPopStateSetRequired; ++i)
         {
             _cv->popStateSet();
         }
-
     }
 
     // pop the node's state off the geostate stack.
@@ -978,7 +976,7 @@ namespace
         "#version " GLSL_VERSION_STR "\n"
         GLSL_DEFAULT_PRECISION_FLOAT "\n"
         "uniform mat4 osg_ViewMatrix; \n"
-        "varying float oe_horizon_alpha; \n"
+        "out float oe_horizon_alpha; \n"
         "void oe_horizon_vertex(inout vec4 VertexVIEW) \n"
         "{ \n"
         "    const float scale     = 0.001; \n"                 // scale factor keeps dots&crosses in SP range
@@ -1002,7 +1000,7 @@ namespace
     const char* horizon_fs =
         "#version " GLSL_VERSION_STR "\n"
         GLSL_DEFAULT_PRECISION_FLOAT "\n"
-        "varying float oe_horizon_alpha; \n"
+        "in float oe_horizon_alpha; \n"
         "void oe_horizon_fragment(inout vec4 color) \n"
         "{ \n"
         "    color.a *= oe_horizon_alpha; \n"
diff --git a/src/osgEarth/DPLineSegmentIntersector b/src/osgEarth/DPLineSegmentIntersector
index 2ce1a5d..ace9bb3 100644
--- a/src/osgEarth/DPLineSegmentIntersector
+++ b/src/osgEarth/DPLineSegmentIntersector
@@ -28,17 +28,17 @@ namespace osgEarth
      * A double-precision version of the osgUtil::LineSegmentIntersector.
      * Use this instead of the OSG one when working in geocentric space.
      */
-    class OSGEARTH_EXPORT DPLineSegmentIntersector : public osgUtil::LineSegmentIntersector
+    class OSGEARTH_EXPORT osgUtil::LineSegmentIntersector : public osgUtil::LineSegmentIntersector
     {
     public:
-        DPLineSegmentIntersector(const osgUtil::Intersector::CoordinateFrame& cf, const osg::Vec3d& start, const osg::Vec3d& end)
+        osgUtil::LineSegmentIntersector(const osgUtil::Intersector::CoordinateFrame& cf, const osg::Vec3d& start, const osg::Vec3d& end)
             : osgUtil::LineSegmentIntersector(cf, start, end) { }
 
-        DPLineSegmentIntersector(const osg::Vec3d& start, const osg::Vec3d& end)
+        osgUtil::LineSegmentIntersector(const osg::Vec3d& start, const osg::Vec3d& end)
             : osgUtil::LineSegmentIntersector(start, end) { }
 
         /** dtor */
-        virtual ~DPLineSegmentIntersector() { }
+        virtual ~osgUtil::LineSegmentIntersector() { }
 
     public: // overrides
         
diff --git a/src/osgEarth/DPLineSegmentIntersector.cpp b/src/osgEarth/DPLineSegmentIntersector.cpp
index 1d151a7..462bca4 100644
--- a/src/osgEarth/DPLineSegmentIntersector.cpp
+++ b/src/osgEarth/DPLineSegmentIntersector.cpp
@@ -16,7 +16,7 @@
  * You should have received a copy of the GNU Lesser General Public License
  * along with this program.  If not, see <http://www.gnu.org/licenses/>
  */
-#include <osgEarth/DPLineSegmentIntersector>
+#include <osgEarth/osgUtil::LineSegmentIntersector>
 #include <osgEarth/Utils>
 #include <osg/TriangleFunctor>
 #include <osg/KdTree>
@@ -209,12 +209,12 @@ namespace
 //----------------------------------------------------------------------------    
 
 osgUtil::Intersector* 
-DPLineSegmentIntersector::clone(osgUtil::IntersectionVisitor& iv)
+osgUtil::LineSegmentIntersector::clone(osgUtil::IntersectionVisitor& iv)
 {
     if (_coordinateFrame==MODEL && iv.getModelMatrix()==0)
     {
         //GW: changed the next line
-        osg::ref_ptr<DPLineSegmentIntersector> lsi = new DPLineSegmentIntersector(_start, _end);
+        osg::ref_ptr<osgUtil::LineSegmentIntersector> lsi = new osgUtil::LineSegmentIntersector(_start, _end);
         lsi->_parent = this;
         lsi->_intersectionLimit = this->_intersectionLimit;
         return lsi.release();
@@ -249,7 +249,7 @@ DPLineSegmentIntersector::clone(osgUtil::IntersectionVisitor& iv)
     inverse.invert(matrix);
 
     //GW: changed the next line
-    osg::ref_ptr<DPLineSegmentIntersector> lsi = new DPLineSegmentIntersector(_start * inverse, _end * inverse);
+    osg::ref_ptr<osgUtil::LineSegmentIntersector> lsi = new osgUtil::LineSegmentIntersector(_start * inverse, _end * inverse);
     lsi->_parent = this;
     lsi->_intersectionLimit = this->_intersectionLimit;
     return lsi.release();
@@ -259,7 +259,7 @@ void intersectWithKdTree(osgUtil::IntersectionVisitor& iv, osg::Drawable* drawab
     , const osg::Vec3d s, const osg::Vec3d e
     , const osg::Vec3d _start, const osg::Vec3d _end
     , osg::KdTree* kdTree
-    , DPLineSegmentIntersector& intersector)
+    , osgUtil::LineSegmentIntersector& intersector)
 {
     osg::KdTree::LineSegmentIntersections intersections;
     intersections.reserve(4);
@@ -321,7 +321,7 @@ void intersectWithQuadTree(osgUtil::IntersectionVisitor& iv, osg::Drawable* draw
     , const osg::Vec3d s, const osg::Vec3d e
     , const osg::Vec3d _start, const osg::Vec3d _end
     , QuadTree* quadTree
-    , DPLineSegmentIntersector& intersector)
+    , osgUtil::LineSegmentIntersector& intersector)
 {
     QuadTree::LineSegmentIntersections intersections;
     intersections.reserve(4);
@@ -379,7 +379,7 @@ void intersectWithQuadTree(osgUtil::IntersectionVisitor& iv, osg::Drawable* draw
     }
 }
 void
-DPLineSegmentIntersector::intersect(osgUtil::IntersectionVisitor& iv, osg::Drawable* drawable)
+osgUtil::LineSegmentIntersector::intersect(osgUtil::IntersectionVisitor& iv, osg::Drawable* drawable)
 {
     if (reachedLimit()) return;
 
diff --git a/src/osgEarth/DateTime b/src/osgEarth/DateTime
index 1f1b5a3..1d38bbb 100644
--- a/src/osgEarth/DateTime
+++ b/src/osgEarth/DateTime
@@ -53,7 +53,7 @@ namespace osgEarth
         /** DateTime from UTC seconds since the epoch */
         DateTime(TimeStamp utc);
 
-        /** DateTime from year, month, date, hours */
+        /** DateTime from year, month [1-12], date [1-31], hours [0-24) */
         DateTime(int year, int month, int day, double hours);
 
         /** DateTime from an ISO 8601 string */
diff --git a/src/osgEarth/DateTimeRange b/src/osgEarth/DateTimeRange
index a1c7bd0..61adc20 100644
--- a/src/osgEarth/DateTimeRange
+++ b/src/osgEarth/DateTimeRange
@@ -27,19 +27,32 @@
 
 namespace osgEarth
 {
+    /**
+     * Two DateTime objects representing a temporal range.
+     */
     class OSGEARTH_EXPORT DateTimeRange
     {
     public:
+        //! Start of date/time range
         optional<DateTime>& begin() { return _begin; }
+        //! Start of date/time range
         const optional<DateTime>& begin() const { return _begin; }
 
+        //! End of date/time range
         optional<DateTime>& end() { return _end; }
+        //! End of date/time range
         const optional<DateTime>& end() const { return _end; }
 
+        //! Expand the range to include a date/time
         void expandBy(const DateTime& other);
+
+        //! Expand the range to include another date/time range
         void expandBy(const DateTimeRange& other);
 
+        //! True is the given date/time falls within this range
         bool intersects(const DateTime& other) const;
+
+        //! True is the given range falls within this range
         bool intersects(const DateTimeRange& other) const;
 
     private:
diff --git a/src/osgEarth/DepthOffset b/src/osgEarth/DepthOffset
index 4b4ed6a..cab2e63 100644
--- a/src/osgEarth/DepthOffset
+++ b/src/osgEarth/DepthOffset
@@ -22,6 +22,7 @@
 
 #include <osgEarth/Common>
 #include <osgEarth/Config>
+#include <osgEarth/Units>
 #include <osg/Group>
 #include <osg/Program>
 #include <osg/Uniform>
@@ -51,8 +52,8 @@
 namespace osgEarth
 {
     /**
-    * Depth Offsetting options.
-    */
+     * Depth Offsetting options.
+     */
     class OSGEARTH_EXPORT DepthOffsetOptions
     {
     public:
@@ -64,20 +65,20 @@ namespace osgEarth
         const optional<bool>& enabled() const { return _enabled; }
 
         /** depth bias (in meters) applied at the minimum camera range. */
-        optional<float>& minBias() { return _minBias; }
-        const optional<float>& minBias() const { return _minBias; }
+        optional<Distance>& minBias() { return _minBias; }
+        const optional<Distance>& minBias() const { return _minBias; }
 
         /** depth bias (in meters) applied at the maximum camera range. */
-        optional<float>& maxBias() { return _maxBias; }
-        const optional<float>& maxBias() const { return _maxBias; }
+        optional<Distance>& maxBias() { return _maxBias; }
+        const optional<Distance>& maxBias() const { return _maxBias; }
 
         /** camera range (in meters) at which to apply the minimum depth bias. */
-        optional<float>& minRange() { return _minRange; }
-        const optional<float>& minRange() const { return _minRange; }
+        optional<Distance>& minRange() { return _minRange; }
+        const optional<Distance>& minRange() const { return _minRange; }
 
         /** camera range (in meters) at which to apply the maximum depth bias. */
-        optional<float>& maxRange() { return _maxRange; }
-        const optional<float>& maxRange() const { return _maxRange; }
+        optional<Distance>& maxRange() { return _maxRange; }
+        const optional<Distance>& maxRange() const { return _maxRange; }
 
         /** automatic calculation of the minRange based on geometry analysis */
         optional<bool>& automatic() { return _auto; }
@@ -88,10 +89,10 @@ namespace osgEarth
 
     private:
         optional<bool>  _enabled;
-        optional<float> _minBias;
-        optional<float> _maxBias;
-        optional<float> _minRange;
-        optional<float> _maxRange;
+        optional<Distance> _minBias;
+        optional<Distance> _maxBias;
+        optional<Distance> _minRange;
+        optional<Distance> _maxRange;
         optional<bool>  _auto;
     };
 
@@ -142,8 +143,6 @@ namespace osgEarth
         bool                         _supported;
         bool                         _dirty;
         osg::observer_ptr<osg::Node> _graph;
-        //osg::ref_ptr<osg::Uniform>   _biasUniform;
-        //osg::ref_ptr<osg::Uniform>   _rangeUniform;
         osg::ref_ptr<osg::Uniform>   _minBiasUniform, _maxBiasUniform;
         osg::ref_ptr<osg::Uniform>   _minRangeUniform, _maxRangeUniform;
         DepthOffsetOptions           _options;
diff --git a/src/osgEarth/DepthOffset.cpp b/src/osgEarth/DepthOffset.cpp
index 0ff7b01..c6bdae5 100644
--- a/src/osgEarth/DepthOffset.cpp
+++ b/src/osgEarth/DepthOffset.cpp
@@ -28,6 +28,7 @@
 
 #include <osg/Geode>
 #include <osg/Geometry>
+#include <osg/Depth>
 
 #define LC "[DepthOffset] "
 
@@ -167,8 +168,7 @@ DepthOffsetAdapter::setGraph(osg::Node* graph)
         (graphChanging || (_options.enabled() == false));
 
     bool install =
-        (graph && graphChanging ) || 
-        (graph && (_options.enabled() == true));
+        (graph && graphChanging && _options.enabled() == true);
 
     // shader package:
     Shaders shaders;
@@ -185,6 +185,8 @@ DepthOffsetAdapter::setGraph(osg::Node* graph)
         s->removeUniform( _maxRangeUniform.get() );
         
         shaders.unload( VirtualProgram::get(s), shaders.DepthOffsetVertex );
+
+        s->removeAttribute(osg::StateAttribute::DEPTH);
     }
 
     if ( install )
@@ -193,12 +195,19 @@ DepthOffsetAdapter::setGraph(osg::Node* graph)
 
         // install uniforms and shaders.
         osg::StateSet* s = graph->getOrCreateStateSet();
+
+        // so the stateset doesn't get merged by a state set optimizer
+        s->setDataVariance(s->DYNAMIC);
+
         s->addUniform( _minBiasUniform.get() );
         s->addUniform( _maxBiasUniform.get() );
         s->addUniform( _minRangeUniform.get() );
         s->addUniform( _maxRangeUniform.get() );
         
-        shaders.load(VirtualProgram::getOrCreate(s), shaders.DepthOffsetVertex);        
+        shaders.load(VirtualProgram::getOrCreate(s), shaders.DepthOffsetVertex);    
+
+        // disable depth writes
+        s->setAttributeAndModes(new osg::Depth(osg::Depth::LEQUAL, 0.0, 1.0, false), 1);
     }
 
     if ( graphChanging )
@@ -216,17 +225,10 @@ DepthOffsetAdapter::updateUniforms()
 {
     if ( !_supported ) return;
 
-    _minBiasUniform->set( *_options.minBias() );
-    _maxBiasUniform->set( *_options.maxBias() );
-    _minRangeUniform->set( *_options.minRange() );
-    _maxRangeUniform->set( *_options.maxRange() );
-
-    if ( _options.enabled() == true )
-    {
-        OE_TEST << LC 
-            << "bias=[" << *_options.minBias() << ", " << *_options.maxBias() << "] ... "
-            << "range=[" << *_options.minRange() << ", " << *_options.maxRange() << "]" << std::endl;
-    }
+    _minBiasUniform->set( (float)_options.minBias()->as(Units::METERS) );
+    _maxBiasUniform->set( (float)_options.maxBias()->as(Units::METERS) );
+    _minRangeUniform->set( (float)_options.minRange()->as(Units::METERS) );
+    _maxRangeUniform->set( (float)_options.maxRange()->as(Units::METERS) );
 }
 
 void 
diff --git a/src/osgEarth/DepthOffset.vert.glsl b/src/osgEarth/DepthOffset.vert.glsl
index 2ddee8c..2f2d760 100644
--- a/src/osgEarth/DepthOffset.vert.glsl
+++ b/src/osgEarth/DepthOffset.vert.glsl
@@ -10,6 +10,10 @@ uniform float oe_depthOffset_maxBias;
 uniform float oe_depthOffset_minRange;
 uniform float oe_depthOffset_maxRange;
 
+//uniform mat4 gl_ProjectionMatrix;
+
+float oe_depthOffset_biasClip;
+
 void oe_depthOffset_vertex(inout vec4 vertexView)
 {
     // calculate range to target:
@@ -22,9 +26,18 @@ void oe_depthOffset_vertex(inout vec4 vertexView)
 	// clamp the bias to 1/2 of the range of the vertex. We don't want to 
     // pull the vertex TOO close to the camera and certainly not behind it.
     bias = min(bias, range*0.5);
+    bias = min(bias, oe_depthOffset_maxBias);
+
+    //vec4 p0 = gl_ProjectionMatrix * vec4(0,0,0,1);
+    //vec4 p1 = gl_ProjectionMatrix * vec4(0,0,-bias,1);
+    //oe_depthOffset_biasClip = distance(p0, p1);
+    //oe_depthOffset_biasClip = p1.z;
+
+    //vec4 refPointClip = gl_ProjectionMatrix * vec4(0.0, 0.0, -bias, 1.0);
+    //oe_depthOffset_biasClip = refPointClip.z;
 
     //   pull the vertex towards the camera.
     vec3 pullVec = normalize(vertexView.xyz);
     vec3 simVert3 = vertexView.xyz - pullVec*bias;
     vertexView = vec4(simVert3, 1.0);
-}
\ No newline at end of file
+}
diff --git a/src/osgEarth/DrapeableNode b/src/osgEarth/DrapeableNode
index c0f2bbc..4b89bdf 100644
--- a/src/osgEarth/DrapeableNode
+++ b/src/osgEarth/DrapeableNode
@@ -22,20 +22,19 @@
 
 #include <osgEarth/Common>
 #include <osgEarth/optional>
+#include <osgEarth/MapNode>
+#include <osgEarth/MapNodeObserver>
 #include <osg/Group>
 
 namespace osgEarth
 {
-    class MapNode;
-
     /**
-     * Base class for a graph that can be "draped" on the MapNode terrain
-     * using the overlay decorator.
+     * Base class for a graph that can be "draped" on the terrain using
+     * projective texturing.
      *
-     * Usage: Create this node and put it anywhere in the scene graph. The
-     * subgraph of this node will be draped on the MapNode's terrain.
+     * Usage: Create this node and place it as a descendant of a MapNode.
      */
-    class OSGEARTH_EXPORT DrapeableNode : public osg::Group
+    class OSGEARTH_EXPORT DrapeableNode : public osg::Group, public MapNodeObserver
     {
     public:
         META_Object(osgEarth, DrapeableNode);
@@ -58,12 +57,18 @@ namespace osgEarth
 
         virtual void traverse(osg::NodeVisitor& nv);
 
+    public: // osgEarth::MapNodeObserver
+
+        virtual void setMapNode(MapNode* mapNode) { _mapNode = mapNode; }
+        virtual MapNode* getMapNode() { return _mapNode.get(); }
 
     protected:
         /** dtor */
         virtual ~DrapeableNode() { }
 
         bool _drapingEnabled;
+        bool _updateRequested;
+        osg::observer_ptr<MapNode> _mapNode;
     };
 
 } // namespace osgEarth
diff --git a/src/osgEarth/DrapeableNode.cpp b/src/osgEarth/DrapeableNode.cpp
index 1f6b488..26ef03c 100644
--- a/src/osgEarth/DrapeableNode.cpp
+++ b/src/osgEarth/DrapeableNode.cpp
@@ -21,6 +21,9 @@
 #include <osgEarth/DrapingCullSet>
 #include <osgEarth/Registry>
 #include <osgEarth/CullingUtils>
+#include <osgEarth/MapNode>
+#include <osgEarth/NodeUtils>
+#include <osgEarth/TerrainEngineNode>
 
 #define LC "[DrapeableNode] "
 
@@ -28,7 +31,8 @@ using namespace osgEarth;
 
 
 DrapeableNode::DrapeableNode() :
-_drapingEnabled( true )
+_drapingEnabled( true ),
+_updateRequested( true )
 {
     // Unfortunetly, there's no way to return a correct bounding sphere for
     // the node since the draping will move it to the ground. The bounds
@@ -36,6 +40,9 @@ _drapingEnabled( true )
     // have to ensure that this node makes it into the draping cull set so it
     // can be frustum-culled at the proper time.
     setCullingActive( !_drapingEnabled );
+
+    // activate an update traversal to find the MapNode
+    ADJUST_UPDATE_TRAV_COUNT(this, +1);
 }
 
 DrapeableNode::DrapeableNode(const DrapeableNode& rhs, const osg::CopyOp& copy) :
@@ -59,16 +66,37 @@ DrapeableNode::traverse(osg::NodeVisitor& nv)
 {
     if ( _drapingEnabled && nv.getVisitorType() == nv.CULL_VISITOR )
     {
-        // access the cull visitor:
-        osgUtil::CullVisitor* cv = Culling::asCullVisitor(nv);
-
         // find the cull set for this camera:
-        DrapingCullSet& cullSet = DrapingCullSet::get( cv->getCurrentCamera() );
-        cullSet.push( this, cv->getNodePath(), nv.getFrameStamp() );
+        osg::ref_ptr<MapNode> mapNode;
+        if (_mapNode.lock(mapNode))
+        {
+            osgUtil::CullVisitor* cv = Culling::asCullVisitor(nv);
+            DrapingCullSet& cullSet = mapNode->getDrapingManager()->get( cv->getCurrentCamera() );
+            cullSet.push( this, cv->getNodePath(), nv.getFrameStamp() );
+        }
+    }
+
+    else if (_drapingEnabled && nv.getVisitorType() == nv.UPDATE_VISITOR)
+    {        
+        if (_updateRequested)
+        {
+            if (_mapNode.valid() == false)
+            {
+                _mapNode = osgEarth::findInNodePath<MapNode>(nv);
+            }
+
+            if (_mapNode.valid())
+            {
+                _updateRequested = false;
+                ADJUST_UPDATE_TRAV_COUNT(this, -1);
+            }
+        }
+
+        osg::Group::traverse(nv);
     }
     else
     {
-        osg::Group::traverse( nv );
+        osg::Group::traverse(nv);
     }
 }
 
diff --git a/src/osgEarth/Draping.frag.glsl b/src/osgEarth/Draping.frag.glsl
index 45da38b..e07ea49 100644
--- a/src/osgEarth/Draping.frag.glsl
+++ b/src/osgEarth/Draping.frag.glsl
@@ -5,15 +5,18 @@ $GLSL_DEFAULT_PRECISION_FLOAT
 #pragma vp_location   fragment_coloring
 #pragma vp_order      0.6
 
-uniform bool oe_isPickCamera;
+#pragma import_defines(OE_IS_PICK_CAMERA)
+
 uniform sampler2D oe_overlay_tex;
 in vec4 oe_overlay_texcoord;
 
 void oe_overlay_fragment(inout vec4 color)
 {
-    vec4 texel = texture2DProj(oe_overlay_tex, oe_overlay_texcoord);
-    vec4 blendedTexel = vec4( mix( color.rgb, texel.rgb, texel.a ), color.a);
+    vec4 texel = textureProj(oe_overlay_tex, oe_overlay_texcoord);
 
-    float pick = oe_isPickCamera? 1.0 : 0.0;
-    color = mix(blendedTexel, texel, pick);
+#ifdef OE_IS_PICK_CAMERA
+    color = texel;
+#else
+    color = vec4( mix( color.rgb, texel.rgb, texel.a ), color.a);
+#endif
 }
diff --git a/src/osgEarth/Draping.vert.glsl b/src/osgEarth/Draping.vert.glsl
index ce8c4b1..d123bc2 100644
--- a/src/osgEarth/Draping.vert.glsl
+++ b/src/osgEarth/Draping.vert.glsl
@@ -12,4 +12,4 @@ out vec4 oe_overlay_texcoord;
 void oe_overlay_vertex(inout vec4 vertexVIEW)
 {
     oe_overlay_texcoord = oe_overlay_texmatrix * vertexVIEW;
-}
\ No newline at end of file
+}
diff --git a/src/osgEarth/DrapingCullSet b/src/osgEarth/DrapingCullSet
index c9db48a..4b4e23c 100644
--- a/src/osgEarth/DrapingCullSet
+++ b/src/osgEarth/DrapingCullSet
@@ -25,6 +25,7 @@
 #include <osgEarth/Common>
 #include <osgEarth/Containers>
 #include <osgEarth/DrapeableNode>
+#include <osgEarth/ThreadingUtils>
 #include <osg/Camera>
 #include <osg/ObserverNodePath>
 
@@ -33,14 +34,11 @@ namespace osgEarth
     /**
      * Culling set for tracking groups whose contents should be "draped",
      * i.e. rendered to a texture and projected onto the terrain.
+     * Internal object - do not use directly. Use a DrapeableNode instead.
      */
-    class OSGEARTH_EXPORT DrapingCullSet
+    class OSGEARTH_INTERNAL DrapingCullSet
     {
     public:
-        /** Gets the per-thread cull set associated with a camera. */
-        static DrapingCullSet& get(const osg::Camera*);
-
-    public:
         struct Entry
         {            
             osg::ref_ptr<osg::Group>     _node;
@@ -71,6 +69,20 @@ namespace osgEarth
         bool                _frameCulled;
     };
 
+    /**
+     * Houses all the active DrapingCullSets under an osgEarth TerrainEngine.
+     * Internal node - so not use directly.
+     */
+    class OSGEARTH_INTERNAL DrapingManager
+    {
+    public:
+        //! Gets the draping cull set associated with a camera.
+        DrapingCullSet& get(const osg::Camera*);
+
+    private:
+        PerObjectFastMap<const osg::Camera*, DrapingCullSet> _sets;
+    };
+
 } // namespace osgEarth
 
 #endif // OSGEARTH_DRAPING_CULL_SET
diff --git a/src/osgEarth/DrapingCullSet.cpp b/src/osgEarth/DrapingCullSet.cpp
index 0d07dae..96d375b 100644
--- a/src/osgEarth/DrapingCullSet.cpp
+++ b/src/osgEarth/DrapingCullSet.cpp
@@ -27,20 +27,19 @@
 using namespace osgEarth;
 
 
-
-
 DrapingCullSet&
-DrapingCullSet::get(const osg::Camera* cam)
-{    
-    static PerObjectFastMap<const osg::Camera*, DrapingCullSet> sets;
-
+DrapingManager::get(const osg::Camera* cam)
+{
     // Known issue: it is possible for a draping cull set to be "orphaned" - this
     // would happen if the cull set were populated and then not used. This is a
     // very unlikely scenario (because the scene graph would have to change mid-cull)
     // but nevertheless possible.
-    return sets.get(cam);
+    return _sets.get(cam);
 }
 
+//............................................................................
+
+
 DrapingCullSet::DrapingCullSet() :
 _frameCulled( true )
 {
@@ -64,7 +63,9 @@ DrapingCullSet::push(DrapeableNode* node, const osg::NodePath& path, const osg::
     entry._path.setNodePath( path );
     entry._matrix = new osg::RefMatrix( osg::computeLocalToWorld(path) );
     entry._frame = fs ? fs->getFrameNumber() : 0;
-    _bs.expandBy( node->getBound() );
+    _bs.expandBy( osg::BoundingSphere(
+        node->getBound().center() * (*entry._matrix.get()),
+        node->getBound().radius() ));
 }
 
 void
@@ -144,12 +145,4 @@ DrapingCullSet::accept(osg::NodeVisitor& nv)
         // mark this set so it will reset for the next frame
         _frameCulled = true;
     }
-
-    else
-    {
-        for( std::vector<Entry>::iterator entry = _entries.begin(); entry != _entries.end(); ++entry )
-        {
-            
-        }
-    }
 }
diff --git a/src/osgEarth/DrapingTechnique b/src/osgEarth/DrapingTechnique
index 827894e..7ad477f 100644
--- a/src/osgEarth/DrapingTechnique
+++ b/src/osgEarth/DrapingTechnique
@@ -25,6 +25,7 @@
 #include <osgEarth/Common>
 #include <osgEarth/OverlayDecorator>
 #include <osgEarth/DrapeableNode>
+#include <osgEarth/DrapingCullSet>
 #include <osg/TexGenNode>
 #include <osg/Uniform>
 #include <vector>
@@ -42,7 +43,7 @@ namespace osgEarth
      * This class is similar in scope to osgSim::OverlayNode, but is optimized
      * for use with osgEarth and geocentric terrains.
      */
-    class OSGEARTH_EXPORT DrapingTechnique : public OverlayTechnique
+    class OSGEARTH_INTERNAL DrapingTechnique : public OverlayTechnique
     {
     public:
         DrapingTechnique();
@@ -100,6 +101,8 @@ namespace osgEarth
         virtual bool hasData(
             OverlayDecorator::TechRTTParams& params) const;
 
+        virtual bool optimizeToVisibleBound() const { return true; }
+
         void preCullTerrain(
             OverlayDecorator::TechRTTParams& params,
             osgUtil::CullVisitor*            cv );
@@ -127,6 +130,10 @@ namespace osgEarth
         bool                          _attachStencil;
         double                        _maxFarNearRatio;
 
+        mutable DrapingManager _drapingManager;
+        DrapingManager& getDrapingManager() { return _drapingManager; }
+        friend class MapNode;
+
         struct TechData : public osg::Referenced
         {
             osg::ref_ptr<osg::Uniform> _texGenUniform;
diff --git a/src/osgEarth/DrapingTechnique.cpp b/src/osgEarth/DrapingTechnique.cpp
index e211e4b..a83c288 100644
--- a/src/osgEarth/DrapingTechnique.cpp
+++ b/src/osgEarth/DrapingTechnique.cpp
@@ -27,6 +27,7 @@
 #include <osgEarth/VirtualProgram>
 #include <osgEarth/Shaders>
 #include <osgEarth/CullingUtils>
+#include <osgEarth/Lighting>
 
 #include <osg/BlendFunc>
 #include <osg/TexGen>
@@ -54,7 +55,7 @@ namespace
     class DrapingCamera : public osg::Camera
     {
     public:
-        DrapingCamera() : osg::Camera(), _camera(0L)
+        DrapingCamera(DrapingManager& dm) : osg::Camera(), _dm(dm), _camera(0L)
         {
             setCullingActive( false );
         }
@@ -68,13 +69,14 @@ namespace
         }
 
         void traverse(osg::NodeVisitor& nv)
-        {            
-            DrapingCullSet& cullSet = DrapingCullSet::get(_camera);
+        {
+            DrapingCullSet& cullSet = _dm.get(_camera);
             cullSet.accept( nv );
         }
 
     protected:
         virtual ~DrapingCamera() { }
+        DrapingManager& _dm;
         const osg::Camera* _camera;
     };
 
@@ -133,9 +135,7 @@ namespace
 
     // Experimental.
     void optimizeProjectionMatrix(OverlayDecorator::TechRTTParams& params, double maxFarNearRatio)
-    {
-        LocalPerViewData& local = *static_cast<LocalPerViewData*>(params._techniqueData.get());
-        
+    {        
         // t0,t1,t2,t3 will form a polygon that tightly fits the
         // main camera's frustum. Texture near the camera will get
         // more resolution then texture far away.
@@ -346,56 +346,43 @@ DrapingTechnique::hasData(OverlayDecorator::TechRTTParams& params) const
     return getBound(params).valid();
 }
 
-#if OSG_MIN_VERSION_REQUIRED(3,4,0)
-
-// Customized texture class will disable texture filtering when rendering under a pick camera.
-class DrapingTexture : public osg::Texture2D
+namespace
 {
-public:
-    virtual void apply(osg::State& state) const {
-        osg::State::UniformMap::const_iterator i = state.getUniformMap().find("oe_isPickCamera");
-        bool isPickCamera = false;
-        if (i != state.getUniformMap().end())
+    // Customized texture class will disable texture filtering when rendering under a pick camera.
+    class DrapingTexture : public osg::Texture2D
+    {
+    public:
+        virtual void apply(osg::State& state) const
         {
-            if (!i->second.uniformVec.empty())
+            const osg::StateSet::DefineList& defines = state.getDefineMap().currentDefines;
+            if (defines.find("OE_IS_PICK_CAMERA") != defines.end())
             {
-                i->second.uniformVec.back().first->get(isPickCamera);
+                FilterMode minFilter = _min_filter;
+                FilterMode magFilter = _mag_filter;
+                DrapingTexture* ncThis = const_cast<DrapingTexture*>(this);
+                ncThis->_min_filter = NEAREST;
+                ncThis->_mag_filter = NEAREST;
+                ncThis->dirtyTextureParameters();
+                osg::Texture2D::apply(state);
+                ncThis->_min_filter = minFilter;
+                ncThis->_mag_filter = magFilter;
+                ncThis->dirtyTextureParameters();
+            }
+            else
+            {
+                osg::Texture2D::apply(state);
             }
         }
-
-        if (isPickCamera)
-        {
-            FilterMode minFilter = _min_filter;
-            FilterMode magFilter = _mag_filter;
-            DrapingTexture* ncThis = const_cast<DrapingTexture*>(this);
-            ncThis->_min_filter = NEAREST;
-            ncThis->_mag_filter = NEAREST;
-            ncThis->dirtyTextureParameters();
-            osg::Texture2D::apply(state);
-            ncThis->_min_filter = minFilter;
-            ncThis->_mag_filter = magFilter;
-            ncThis->dirtyTextureParameters();
-        }
-        else
-        {
-            osg::Texture2D::apply(state);
-        }
-    }
-};
-
-#endif
+    };
+}
 
 void
 DrapingTechnique::setUpCamera(OverlayDecorator::TechRTTParams& params)
 {
-    // create the projected texture:
+    OE_INFO << LC << "Using texture size = " << _textureSize.get() << std::endl;
 
-#if OSG_MIN_VERSION_REQUIRED(3,4,0)
+    // create the projected texture:
     osg::Texture2D* projTexture = new DrapingTexture(); 
-#else 
-    osg::Texture2D* projTexture = new osg::Texture2D();
-    OE_WARN << LC << "RTT Picking of draped geometry may not work propertly under OSG < 3.4" << std::endl;
-#endif
 
     projTexture->setTextureSize( *_textureSize, *_textureSize );
     projTexture->setInternalFormat( GL_RGBA );
@@ -409,7 +396,7 @@ DrapingTechnique::setUpCamera(OverlayDecorator::TechRTTParams& params)
     projTexture->setBorderColor( osg::Vec4(0,0,0,0) );
 
     // set up the RTT camera:
-    params._rttCamera = new DrapingCamera(); //new osg::Camera();
+    params._rttCamera = new DrapingCamera(_drapingManager);
     params._rttCamera->setClearColor( osg::Vec4f(0,0,0,0) );
     // this ref frame causes the RTT to inherit its viewpoint from above (in order to properly
     // process PagedLOD's etc. -- it doesn't affect the perspective of the RTT camera though)
@@ -430,7 +417,7 @@ DrapingTechnique::setUpCamera(OverlayDecorator::TechRTTParams& params)
         // a PBUFFER_RTT impl
         if ( Registry::capabilities().supportsDepthPackedStencilBuffer() )
         {
-#ifdef OSG_GLES2_AVAILABLE 
+#if defined(OSG_GLES2_AVAILABLE) || defined(OSG_GLES3_AVAILABLE)
             params._rttCamera->attach( osg::Camera::PACKED_DEPTH_STENCIL_BUFFER, GL_DEPTH24_STENCIL8_EXT );
 #else
             params._rttCamera->attach( osg::Camera::PACKED_DEPTH_STENCIL_BUFFER, GL_DEPTH_STENCIL_EXT );
@@ -449,13 +436,15 @@ DrapingTechnique::setUpCamera(OverlayDecorator::TechRTTParams& params)
         params._rttCamera->setClearMask( GL_COLOR_BUFFER_BIT );
     }
 
+
     // set up a StateSet for the RTT camera.
     osg::StateSet* rttStateSet = params._rttCamera->getOrCreateStateSet();
 
     osg::StateAttribute::OverrideValue forceOff =
         osg::StateAttribute::OFF | osg::StateAttribute::PROTECTED | osg::StateAttribute::OVERRIDE;
 
-    rttStateSet->addUniform( Registry::shaderFactory()->createUniformForGLMode(GL_LIGHTING, forceOff) );
+    rttStateSet->setDefine(OE_LIGHTING_DEFINE, forceOff);
+    //rttStateSet->addUniform( Registry::shaderFactory()->createUniformForGLMode(GL_LIGHTING, forceOff) );
     rttStateSet->setMode( GL_LIGHTING, forceOff );
     
     // activate blending within the RTT camera's FBO
@@ -489,7 +478,7 @@ DrapingTechnique::setUpCamera(OverlayDecorator::TechRTTParams& params)
     // overlay geometry is rendered with no depth testing, and in the order it's found in the
     // scene graph... until further notice.
     rttStateSet->setMode(GL_DEPTH_TEST, 0);
-    rttStateSet->setBinName( "TraversalOrderBin" );
+    rttStateSet->setRenderBinDetails(1, "TraversalOrderBin", osg::StateSet::OVERRIDE_PROTECTED_RENDERBIN_DETAILS );
 
     // add to the terrain stateset, i.e. the stateset that the OverlayDecorator will
     // apply to the terrain before cull-traversing it. This will activate the projective
@@ -512,6 +501,7 @@ DrapingTechnique::setUpCamera(OverlayDecorator::TechRTTParams& params)
         // alpha. We shall see.
         const char* warpClip =
             "#version " GLSL_VERSION_STR "\n"
+            GLSL_DEFAULT_PRECISION_FLOAT "\n"
             "void oe_overlay_warpClip(inout vec4 vclip) { \n"
             "    if (vclip.z > 1.0) vclip.z = vclip.w+1.0; \n"
             "} \n";
@@ -579,13 +569,17 @@ DrapingTechnique::preCullTerrain(OverlayDecorator::TechRTTParams& params,
     if ( !params._rttCamera.valid() && _textureUnit.isSet() )
     {
         setUpCamera( params );
+
+        // We do this so we can detect the RTT's camera's parent for 
+        // things like auto-scaling, picking, and so on.
+        params._rttCamera->setView(cv->getCurrentCamera()->getView());
     }
 }
        
 const osg::BoundingSphere&
 DrapingTechnique::getBound(OverlayDecorator::TechRTTParams& params) const
 {
-    return DrapingCullSet::get(params._mainCamera).getBound();
+    return _drapingManager.get(params._mainCamera).getBound();
 }
 
 void
@@ -712,7 +706,6 @@ DrapingTechnique::onInstall( TerrainEngineNode* engine )
         unsigned maxSize = Registry::capabilities().getMaxFastTextureSize();
         _textureSize.init( osg::minimum( 2048u, maxSize ) );
     }
-    OE_INFO << LC << "Using texture size = " << *_textureSize << std::endl;
 }
 
 void
diff --git a/src/osgEarth/DrawInstanced b/src/osgEarth/DrawInstanced
index f0a863d..00e2a14 100644
--- a/src/osgEarth/DrawInstanced
+++ b/src/osgEarth/DrawInstanced
@@ -69,8 +69,8 @@ namespace osgEarth
 
         protected:
             unsigned _numInstances;
-            bool     _optimize;
-            osg::ref_ptr<osg::Drawable::ComputeBoundingBoxCallback> _staticBBoxCallback;
+            osg::BoundingBox _bbox;
+            bool _optimize;
             std::list<osg::PrimitiveSet*> _primitiveSets;
         };
 
diff --git a/src/osgEarth/DrawInstanced.cpp b/src/osgEarth/DrawInstanced.cpp
index 13e1316..d9e373d 100644
--- a/src/osgEarth/DrawInstanced.cpp
+++ b/src/osgEarth/DrawInstanced.cpp
@@ -74,7 +74,6 @@ namespace
             osg::Vec3d centerView = bbox.center() * ri.getState()->getModelViewMatrix();
             float rangeToBS = (float)-centerView.z() - radius;
 
-#if OSG_MIN_VERSION_REQUIRED(3,3,0)
             // check for inherit mode (3.3.0+ only)
             osg::Camera* cam = ri.getCurrentCamera();
 
@@ -90,7 +89,6 @@ namespace
                     rangeToBS = (float)(-centerRefView.z() - radius);
                 }
             }
-#endif
 
             // these should obviously be programmable
             const float maxDistance = 2000.0f;
@@ -127,28 +125,7 @@ namespace
     };
 
     typedef std::map< osg::ref_ptr<osg::Node>, std::vector<ModelInstance> > ModelInstanceMap;
-    
-    /**
-     * Simple bbox callback to return a static bbox.
-     */
-    struct StaticBoundingBox : public osg::Drawable::ComputeBoundingBoxCallback
-    {
-        osg::BoundingBox _bbox;
-        StaticBoundingBox( const osg::BoundingBox& bbox ) : _bbox(bbox) { }
-        osg::BoundingBox computeBound(const osg::Drawable&) const { return _bbox; }
-    };
 
-    // assume x is positive
-    static int nextPowerOf2(int x)
-    {
-        --x;
-        x |= x >> 1;
-        x |= x >> 2;
-        x |= x >> 4;
-        x |= x >> 8;
-        x |= x >> 16;
-        return x+1;
-    }
 }
 
 //----------------------------------------------------------------------
@@ -158,12 +135,11 @@ ConvertToDrawInstanced::ConvertToDrawInstanced(unsigned                numInstan
                                                const osg::BoundingBox& bbox,
                                                bool                    optimize ) :
 _numInstances    ( numInstances ),
+_bbox(bbox),
 _optimize        ( optimize )
 {
     setTraversalMode( TRAVERSE_ALL_CHILDREN );
     setNodeMaskOverride( ~0 );
-
-    _staticBBoxCallback = new StaticBoundingBox(bbox);
 }
 
 
@@ -182,11 +158,7 @@ ConvertToDrawInstanced::apply( osg::Geode& geode )
                 geom->setUseVertexBufferObjects( true );
             }
 
-            if ( _staticBBoxCallback.valid() )
-            {
-                geom->setComputeBoundingBoxCallback( _staticBBoxCallback.get() ); 
-                geom->dirtyBound();
-            }
+            geom->setInitialBound(_bbox);
 
             // convert to use DrawInstanced
             for( unsigned p=0; p<geom->getNumPrimitiveSets(); ++p )
@@ -278,7 +250,7 @@ DrawInstanced::convertGraphToUseDrawInstanced( osg::Group* parent )
     // place a static bounding sphere on the graph since we intend to alter
     // the structure of the subgraph.
     const osg::BoundingSphere& bs = parent->getBound();
-    parent->setComputeBoundingSphereCallback( new StaticBound(bs) );
+    parent->setInitialBound(bs);
     parent->dirtyBound();
 
     ModelInstanceMap models;
@@ -319,8 +291,9 @@ DrawInstanced::convertGraphToUseDrawInstanced( osg::Group* parent )
 	int maxTBOSize = Registry::capabilities().getMaxTextureBufferSize();
 	// This is the total number of instances it can store
 	// We will iterate below. If the number of instances is larger than the buffer can store
-	// we make more tbos
-	int maxTBOInstancesSize = maxTBOSize/4;// 4 vec4s per matrix.
+    // we make more tbos
+    int matrixSize = 4 * 4 * sizeof(float); // 4 vec4's.
+    int maxTBOInstancesSize = maxTBOSize / matrixSize;
 
     // For each model:
     for( ModelInstanceMap::iterator i = models.begin(); i != models.end(); ++i )
@@ -351,7 +324,7 @@ DrawInstanced::convertGraphToUseDrawInstanced( osg::Group* parent )
 
 		if (instances.size()<maxTBOInstancesSize)
 		{
-			tboSize = nextPowerOf2(instances.size());
+			tboSize = instances.size();
 			numInstancesToStore = instances.size();
 		}
 		else
diff --git a/src/osgEarth/ElevationLOD b/src/osgEarth/ElevationLOD
index c072941..a0b9362 100644
--- a/src/osgEarth/ElevationLOD
+++ b/src/osgEarth/ElevationLOD
@@ -74,6 +74,9 @@ namespace osgEarth
         optional<float>  _maxRange;
     };
 
+    // AltitudeLOD is a better name.
+    typedef ElevationLOD AltitudeLOD;
+
 } // namespace osgEarth
 
 #endif // OSGEARTH_ELEVATIONLOD_H
diff --git a/src/osgEarth/ElevationLayer b/src/osgEarth/ElevationLayer
index 487049c..3e17c23 100644
--- a/src/osgEarth/ElevationLayer
+++ b/src/osgEarth/ElevationLayer
@@ -32,14 +32,22 @@ namespace osgEarth
     {
     public:
         /** Constructs new elevation layer options. */
-        ElevationLayerOptions( const ConfigOptions& options =ConfigOptions() );
+        ElevationLayerOptions();
+        
+        /** Deserializes new elevation layer options. */
+        ElevationLayerOptions(const ConfigOptions& options);
+
+        // Constructs new options with a layer name
+        ElevationLayerOptions(const std::string& name);
 
         /** Constructs new elevation layer options, given the underlying driver options. */
-        ElevationLayerOptions( const std::string& name, const TileSourceOptions& driverOptions );
+        ElevationLayerOptions(const std::string& name, const TileSourceOptions& driverOptions);
 
         /** dtor */
         virtual ~ElevationLayerOptions() { }
 
+    public:
+
         optional<bool>& offset() { return _offset; }
         const optional<bool>& offset() const { return _offset; }
 
@@ -48,8 +56,7 @@ namespace osgEarth
         const optional<ElevationNoDataPolicy>& noDataPolicy() const { return _noDataPolicy; }
 
     public:
-        virtual Config getConfig() const { return getConfig(false); }
-        virtual Config getConfig( bool isolate ) const;
+        virtual Config getConfig() const;
         virtual void mergeConfig( const Config& conf );
         
     private:
@@ -59,30 +66,28 @@ namespace osgEarth
         optional<bool>                  _offset;
         optional<ElevationNoDataPolicy> _noDataPolicy;
     };
+    
 
-    //--------------------------------------------------------------------
-
-    /**
-     * Callback for receiving notification of property changes on an ElevationLayer.
-     */
     struct ElevationLayerCallback : public TerrainLayerCallback
     {
-        //TODO
+        //EMPTY
+        typedef void (ElevationLayerCallback::*MethodPtr)(class ElevationLayer*);
     };
 
-    typedef void (ElevationLayerCallback::*ElevationLayerCallbackMethodPtr)(class ElevationLayer* layer);
-
-    typedef std::list< osg::ref_ptr<ElevationLayerCallback> > ElevationLayerCallbackList;
-
-
-    //--------------------------------------------------------------------
-
     /**
      * A map terrain layer containing elevation grid heightfields.
      */
     class OSGEARTH_EXPORT ElevationLayer : public TerrainLayer
     {
     public:
+        META_Layer(osgEarth, ElevationLayer, ElevationLayerOptions);
+
+        /**
+         * Constructs a blank Elevation Layer;
+         * Use options() to set up before calling open or adding to a Map.
+         */
+        ElevationLayer();
+
         /**
          * Constructs a new elevation layer with the specified options. It expects
          * the layer options to contain a reference to the neccesary driver options.
@@ -98,66 +103,78 @@ namespace osgEarth
         /**
          * Constructs a new elevation layer with the specified layer options and with a custom
          * TileSource instance created by the user.
+         *
+         * Note: the ElevationLayerOptions contains a driver() member for configuring a 
+         * TileSource. But in this constructor, you are passing in an existing TileSource,
+         * and thus the driver() member in ElevationLayerOptions will not be used.
          */
         ElevationLayer( const ElevationLayerOptions& options, TileSource* tileSource );
 
         /** dtor */
         virtual ~ElevationLayer() { }
 
-        /** Gets the initialization options with which the layer was created. */
-        const ElevationLayerOptions& getElevationLayerOptions() const { return _runtimeOptions; }
-        virtual const TerrainLayerOptions& getTerrainLayerRuntimeOptions() const { return _runtimeOptions; }
-
-        /** Adds a property notification callback to this layer */
-        void addCallback( ElevationLayerCallback* cb );
-
-        /** Removes a property notification callback from this layer */
-        void removeCallback( ElevationLayerCallback* cb );
-
     public: // methods
+        
+        /**
+         * Creates a GeoHeightField for this layer that corresponds to the extents and LOD 
+         * in the specified TileKey. The returned HeightField will always match the geospatial
+         * extents of that TileKey.
+         *
+         * @param key TileKey for which to create a heightfield.
+         */
+        GeoHeightField createHeightField(const TileKey& key);
 
         /**
          * Creates a GeoHeightField for this layer that corresponds to the extents and LOD 
          * in the specified TileKey. The returned HeightField will always match the geospatial
          * extents of that TileKey.
+         *
+         * @param key TileKey for which to create a heightfield.
+         * @param progress Callback for tracking progress and cancelation
          */
-        virtual GeoHeightField createHeightField(
-            const TileKey&    key,
-            ProgressCallback* progress =0L );
+        GeoHeightField createHeightField(const TileKey& key, ProgressCallback* progress);
 
         /**
          * Whether this layer contains offsets instead of absolute heights
          */
-        bool isOffset() const {
-            return _runtimeOptions.offset() == true;
-        }
+        bool isOffset() const;
+
+    protected: // Layer
+
+        virtual void init();
 
     protected:
-        
-        // creates a geoHF directly from the tile source
-        osg::HeightField* createHeightFieldFromTileSource( 
-            const TileKey&    key, 
-            ProgressCallback* progress);
 
-        // assembles tiles from a layer that is not in the same profile as the map, and
-        // returns a single tile in the map's profile.
-        osg::HeightField* assembleHeightFieldFromTileSource(
-            const TileKey&     key,
-            ProgressCallback*  progress );
+        // ctor called by a subclass that owns the options structure
+        ElevationLayer(ElevationLayerOptions* optionsPtr);
+
+        //! Subclass can override this by calling setTileSourceExpected(false).
+        //! You can create your own normal map, but usually ElevationLayer
+        //! will do this for you. Only do it if you really need to create
+        //! one by hand (for example, if you are compositing elevaition layers).
+        virtual void createImplementation(
+            const TileKey& key,
+            osg::ref_ptr<osg::HeightField>& out_hf,
+            osg::ref_ptr<NormalMap>& out_normalMap,
+            ProgressCallback* progress);
         
     private:
-        ElevationLayerOptions _runtimeOptions;
-
-        ElevationLayerCallbackList _callbacks;
-        virtual void fireCallback( TerrainLayerCallbackMethodPtr method );
-        virtual void fireCallback( ElevationLayerCallbackMethodPtr method );
 
         mutable osg::ref_ptr<TileSource::HeightFieldOperation> _preCacheOp;
 
         TileSource::HeightFieldOperation* getOrCreatePreCacheOp();
         Threading::Mutex _mutex;
+        
+        // creates a geoHF directly from the tile source
+        osg::HeightField* createHeightFieldFromTileSource( 
+            const TileKey&    key, 
+            ProgressCallback* progress);
 
-        void init();
+        void assembleHeightField(
+            const TileKey& key,
+            osg::ref_ptr<osg::HeightField>& out_hf,
+            osg::ref_ptr<NormalMap>& out_normalMap,
+            ProgressCallback* progress);
     };
 
 
@@ -171,8 +188,9 @@ namespace osgEarth
          * Populates an existing height field (hf must already exist) with height
          * values from the elevation layers.
          */
-        bool populateHeightField(
+        bool populateHeightFieldAndNormalMap(
             osg::HeightField*      hf,
+            NormalMap*             normalMap,
             const TileKey&         key,
             const Profile*         haeProfile,
             ElevationInterpolation interpolation,
diff --git a/src/osgEarth/ElevationLayer.cpp b/src/osgEarth/ElevationLayer.cpp
index ff33a80..24d4c85 100644
--- a/src/osgEarth/ElevationLayer.cpp
+++ b/src/osgEarth/ElevationLayer.cpp
@@ -21,6 +21,8 @@
 #include <osgEarth/HeightFieldUtils>
 #include <osgEarth/Progress>
 #include <osgEarth/MemCache>
+#include <osgEarth/Metrics>
+#include <osgEarth/ImageUtils>
 #include <osg/Version>
 #include <iterator>
 
@@ -29,16 +31,34 @@ using namespace OpenThreads;
 
 #define LC "[ElevationLayer] \"" << getName() << "\" : "
 
+namespace osgEarth {
+    REGISTER_OSGEARTH_LAYER(elevation, osgEarth::ElevationLayer);
+}
+
 //------------------------------------------------------------------------
 
-ElevationLayerOptions::ElevationLayerOptions( const ConfigOptions& options ) :
+ElevationLayerOptions::ElevationLayerOptions() :
+TerrainLayerOptions()
+{
+    setDefaults();
+    fromConfig(_conf);
+}
+
+ElevationLayerOptions::ElevationLayerOptions(const ConfigOptions& options) :
 TerrainLayerOptions( options )
 {
     setDefaults();
     fromConfig( _conf );
 }
 
-ElevationLayerOptions::ElevationLayerOptions( const std::string& name, const TileSourceOptions& driverOptions ) :
+ElevationLayerOptions::ElevationLayerOptions(const std::string& name) :
+TerrainLayerOptions( name )
+{
+    setDefaults();
+    fromConfig( _conf );
+}
+
+ElevationLayerOptions::ElevationLayerOptions(const std::string& name, const TileSourceOptions& driverOptions) :
 TerrainLayerOptions( name, driverOptions )
 {
     setDefaults();
@@ -53,13 +73,18 @@ ElevationLayerOptions::setDefaults()
 }
 
 Config
-ElevationLayerOptions::getConfig( bool isolate ) const
+ElevationLayerOptions::getConfig() const
 {
-    Config conf = TerrainLayerOptions::getConfig( isolate );
-    conf.updateIfSet("offset", _offset);
-    conf.updateIfSet("nodata_policy", "default",     _noDataPolicy, NODATA_INTERPOLATE );
-    conf.updateIfSet("nodata_policy", "interpolate", _noDataPolicy, NODATA_INTERPOLATE );
-    conf.updateIfSet("nodata_policy", "msl",         _noDataPolicy, NODATA_MSL );
+    Config conf = TerrainLayerOptions::getConfig();
+    conf.key() = "elevation";
+
+    conf.set("offset", _offset);
+    conf.set("nodata_policy", "default",     _noDataPolicy, NODATA_INTERPOLATE );
+    conf.set("nodata_policy", "interpolate", _noDataPolicy, NODATA_INTERPOLATE );
+    conf.set("nodata_policy", "msl",         _noDataPolicy, NODATA_MSL );
+
+    //if (driver().isSet())
+    //    conf.set("driver", driver()->getDriver());
 
     return conf;
 }
@@ -87,11 +112,11 @@ namespace
     // Opeartion that replaces invalid heights with the NO_DATA_VALUE marker.
     struct NormalizeNoDataValues : public TileSource::HeightFieldOperation
     {
-        NormalizeNoDataValues(TileSource* source)
+        NormalizeNoDataValues(TerrainLayer* layer)
         {
-            _noDataValue   = source->getNoDataValue();
-            _minValidValue = source->getMinValidValue();
-            _maxValidValue = source->getMaxValidValue();
+            _noDataValue   = layer->getNoDataValue();
+            _minValidValue = layer->getMinValidValue();
+            _maxValidValue = layer->getMaxValidValue();
         }
 
         void operator()(osg::ref_ptr<osg::HeightField>& hf)
@@ -102,8 +127,9 @@ namespace
                 for(osg::FloatArray::iterator i = values->begin(); i != values->end(); ++i)
                 {
                     float& value = *i;
-                    if ( osg::equivalent(value, _noDataValue) || value < _minValidValue || value > _maxValidValue )
+                    if ( osg::isNaN(value) || osg::equivalent(value, _noDataValue) || value < _minValidValue || value > _maxValidValue )
                     {
+                        OE_DEBUG << "Replaced " << value << " with NO_DATA_VALUE" << std::endl;
                         value = NO_DATA_VALUE;
                     }
                 } 
@@ -124,8 +150,8 @@ namespace
             return false;
         if (hf->getHeightList().size() != hf->getNumColumns() * hf->getNumRows())
             return false;
-        if (hf->getXInterval() < 1e-5 || hf->getYInterval() < 1e-5)
-            return false;
+        //if (hf->getXInterval() < 1e-5 || hf->getYInterval() < 1e-5)
+        //    return false;
         
         return true;
     }    
@@ -133,87 +159,91 @@ namespace
 
 //------------------------------------------------------------------------
 
-ElevationLayer::ElevationLayer( const ElevationLayerOptions& options ) :
-TerrainLayer   ( options, &_runtimeOptions ),
-_runtimeOptions( options )
+ElevationLayer::ElevationLayer() :
+TerrainLayer(&_optionsConcrete),
+_options(&_optionsConcrete)
 {
     init();
 }
 
-ElevationLayer::ElevationLayer( const std::string& name, const TileSourceOptions& driverOptions ) :
-TerrainLayer   ( ElevationLayerOptions(name, driverOptions), &_runtimeOptions ),
-_runtimeOptions( ElevationLayerOptions(name, driverOptions) )
+ElevationLayer::ElevationLayer(const ElevationLayerOptions& options) :
+TerrainLayer(&_optionsConcrete),
+_options(&_optionsConcrete),
+_optionsConcrete(options)
 {
     init();
 }
 
-ElevationLayer::ElevationLayer( const ElevationLayerOptions& options, TileSource* tileSource ) :
-TerrainLayer   ( options, &_runtimeOptions, tileSource ),
-_runtimeOptions( options )
+ElevationLayer::ElevationLayer(const std::string& name, const TileSourceOptions& driverOptions) :
+TerrainLayer(&_optionsConcrete),
+_options(&_optionsConcrete),
+_optionsConcrete(name, driverOptions)
 {
     init();
 }
 
-void
-ElevationLayer::init()
+ElevationLayer::ElevationLayer(const ElevationLayerOptions& options, TileSource* tileSource) :
+TerrainLayer(&_optionsConcrete, tileSource),
+_options(&_optionsConcrete),
+_optionsConcrete(options)
 {
-    TerrainLayer::init();
+    init();
 }
 
-void
-ElevationLayer::addCallback( ElevationLayerCallback* cb )
+ElevationLayer::ElevationLayer(ElevationLayerOptions* optionsPtr) :
+TerrainLayer(optionsPtr? optionsPtr : &_optionsConcrete),
+_options(optionsPtr? optionsPtr : &_optionsConcrete)
 {
-    _callbacks.push_back( cb );
+    //init(); // will be called by subclass.
 }
 
 void
-ElevationLayer::removeCallback( ElevationLayerCallback* cb )
+ElevationLayer::init()
 {
-    ElevationLayerCallbackList::iterator i = std::find( _callbacks.begin(), _callbacks.end(), cb );
-    if ( i != _callbacks.end() ) 
-        _callbacks.erase( i );
-}
+    TerrainLayer::init();
 
-void
-ElevationLayer::fireCallback( TerrainLayerCallbackMethodPtr method )
-{
-    for( ElevationLayerCallbackList::const_iterator i = _callbacks.begin(); i != _callbacks.end(); ++i )
-    {
-        ElevationLayerCallback* cb = i->get();
-        (cb->*method)( this );
-    }
+    // elevation layers do not render directly; rather, a composite of elevation data
+    // feeds the terrain engine to permute the mesh.
+    setRenderType(RENDERTYPE_NONE);
 }
 
-void
-ElevationLayer::fireCallback( ElevationLayerCallbackMethodPtr method )
+bool
+ElevationLayer::isOffset() const
 {
-    for( ElevationLayerCallbackList::const_iterator i = _callbacks.begin(); i != _callbacks.end(); ++i )
-    {
-        ElevationLayerCallback* cb = i->get();
-        (cb->*method)( this );
-    }
+    return options().offset().get();
 }
 
 TileSource::HeightFieldOperation*
 ElevationLayer::getOrCreatePreCacheOp()
 {
-    if ( !_preCacheOp.valid() && getTileSource() )
+    if ( !_preCacheOp.valid() )
     {
         Threading::ScopedMutexLock lock(_mutex);
         if ( !_preCacheOp.valid() )
         {
-            _preCacheOp = new NormalizeNoDataValues( getTileSource() );
+            _preCacheOp = new NormalizeNoDataValues(this);
         }
     }
     return _preCacheOp.get();
 }
 
+void
+ElevationLayer::createImplementation(const TileKey& key,
+                                     osg::ref_ptr<osg::HeightField>& out_hf,
+                                     osg::ref_ptr<NormalMap>& out_normalMap,
+                                     ProgressCallback* progress)
+{
+    out_hf = createHeightFieldFromTileSource(key, progress);
+    
+    // Do not create a normal map here. The populateHeightField method will
+    // create the normal map.
+}
 
 osg::HeightField*
 ElevationLayer::createHeightFieldFromTileSource(const TileKey&    key,
                                                 ProgressCallback* progress)
 {
-    osg::HeightField* result = 0L;
+    osg::ref_ptr<osg::HeightField> result;
 
     TileSource* source = getTileSource();
     if ( !source )
@@ -231,7 +261,7 @@ ElevationLayer::createHeightFieldFromTileSource(const TileKey&    key,
     if ( key.getProfile()->isHorizEquivalentTo( getProfile() ) )
     {
         // Only try to get data if the source actually has data
-        if ( !source->hasData(key) )
+        if (!mayHaveData(key))
         {
             OE_DEBUG << LC << "Source for layer has no data at " << key.str() << std::endl;
             return 0L;
@@ -242,7 +272,7 @@ ElevationLayer::createHeightFieldFromTileSource(const TileKey&    key,
    
         // If the result is good, we how have a heightfield but it's vertical values
         // are still relative to the tile source's vertical datum. Convert them.
-        if ( result )
+        if (result.valid())
         {
             if ( ! key.getExtent().getSRS()->isVertEquivalentTo( getProfile()->getSRS() ) )
             {
@@ -250,13 +280,13 @@ ElevationLayer::createHeightFieldFromTileSource(const TileKey&    key,
                     getProfile()->getSRS()->getVerticalDatum(),    // from
                     key.getExtent().getSRS()->getVerticalDatum(),  // to
                     key.getExtent(),
-                    result );
+                    result.get() );
             }
         }
         
         // Blacklist the tile if it is the same projection as the source and
         // we can't get it and it wasn't cancelled
-        if (result == 0L)
+        if (!result.valid())
         {
             if ( progress == 0L ||
                  ( !progress->isCanceled() && !progress->needsRetry() ) )
@@ -270,18 +300,21 @@ ElevationLayer::createHeightFieldFromTileSource(const TileKey&    key,
     else
     {
         // note: this method takes care of the vertical datum shift internally.
-        result = assembleHeightFieldFromTileSource( key, progress );
+        osg::ref_ptr<NormalMap> dummyNormalMap;
+        assembleHeightField( key, result, dummyNormalMap, progress );
     }
 
-    return result;
+    return result.release();
 }
 
 
-osg::HeightField*
-ElevationLayer::assembleHeightFieldFromTileSource(const TileKey&    key,
-                                                  ProgressCallback* progress)
+void
+ElevationLayer::assembleHeightField(const TileKey& key,
+                                    osg::ref_ptr<osg::HeightField>& out_hf,
+                                    osg::ref_ptr<NormalMap>& out_normalMap,
+                                    ProgressCallback* progress)
 {			
-    osg::HeightField* result = 0L;
+    //osg::HeightField* result = 0L;
 
     // Collect the heightfields for each of the intersecting tiles.
     GeoHeightFieldVector heightFields;
@@ -299,12 +332,15 @@ ElevationLayer::assembleHeightFieldFromTileSource(const TileKey&    key,
         {
             const TileKey& layerKey = intersectingTiles[i];
 
-            if ( isKeyInRange(layerKey) )
+            if ( isKeyInLegalRange(layerKey) )
             {
-                osg::HeightField* hf = createHeightFieldFromTileSource( layerKey, progress );
-                if ( hf )
+                osg::ref_ptr<osg::HeightField> hf;
+                osg::ref_ptr<NormalMap> normalMap;
+                createImplementation(layerKey, hf, normalMap, progress);
+                //osg::HeightField* hf = createHeightFieldImplementation( layerKey, progress );
+                if (hf.valid())
                 {
-                    heightFields.push_back( GeoHeightField(hf, layerKey.getExtent()) );
+                    heightFields.push_back( GeoHeightField(hf.get(), normalMap.get(), layerKey.getExtent()) );
                 }
             }
         }
@@ -327,8 +363,10 @@ ElevationLayer::assembleHeightFieldFromTileSource(const TileKey&    key,
         //Now sort the heightfields by resolution to make sure we're sampling the highest resolution one first.
         std::sort( heightFields.begin(), heightFields.end(), GeoHeightField::SortByResolutionFunctor());        
 
-        result = new osg::HeightField();
-        result->allocate(width, height);
+        out_hf = new osg::HeightField();
+        out_hf->allocate(width, height);
+
+        out_normalMap = new NormalMap(width, height);
 
         //Go ahead and set up the heightfield so we don't have to worry about it later
         double minx, miny, maxx, maxy;
@@ -346,30 +384,42 @@ ElevationLayer::assembleHeightFieldFromTileSource(const TileKey&    key,
 
                 //For each sample point, try each heightfield.  The first one with a valid elevation wins.
                 float elevation = NO_DATA_VALUE;
+                osg::Vec3 normal(0,0,1);
+
                 for (GeoHeightFieldVector::iterator itr = heightFields.begin(); itr != heightFields.end(); ++itr)
                 {
                     // get the elevation value, at the same time transforming it vertically into the 
                     // requesting key's vertical datum.
                     float e = 0.0;
-                    if (itr->getElevation(key.getExtent().getSRS(), x, y, INTERP_BILINEAR, key.getExtent().getSRS(), e))
+                    osg::Vec3 n;
+                    if (itr->getElevationAndNormal(key.getExtent().getSRS(), x, y, INTERP_BILINEAR, key.getExtent().getSRS(), e, n))
                     {
                         elevation = e;
+                        normal = n;
                         break;
                     }
                 }
-                result->setHeight( c, r, elevation );                
+                out_hf->setHeight( c, r, elevation );   
+                out_normalMap->set( c, r, normal );
             }
         }
     }
-
-    return result;
 }
 
+GeoHeightField
+ElevationLayer::createHeightField(const TileKey& key)
+{
+    return createHeightField(key, 0L);
+}
 
 GeoHeightField
 ElevationLayer::createHeightField(const TileKey&    key,
                                   ProgressCallback* progress )
 {
+    METRIC_SCOPED_EX("ElevationLayer::createHeightField", 2,
+                     "key", key.str().c_str(),
+                     "name", getName().c_str());
+
     if (getStatus().isError())
     {
         return GeoHeightField::INVALID;
@@ -383,6 +433,7 @@ ElevationLayer::createHeightField(const TileKey&    key,
 
     GeoHeightField result;
     osg::ref_ptr<osg::HeightField> hf;
+    osg::ref_ptr<NormalMap> normalMap;
 
     // Check the memory cache first
     bool fromMemCache = false;
@@ -410,8 +461,16 @@ ElevationLayer::createHeightField(const TileKey&    key,
         // See if there's a persistent cache.
         CacheBin* cacheBin = getCacheBin( key.getProfile() );
 
-        // validate that we have either a valid tile source, or we're cache-only.
-        if ( ! (getTileSource() || (policy.isCacheOnly() && cacheBin) ) )
+        // Can we continue? Only if either:
+        //  a) there is a valid tile source plugin;
+        //  b) a tile source is not expected, meaning the subclass overrides getHeightField; or
+        //  c) we are in cache-only mode and there is a valid cache bin.
+        bool canContinue =
+            getTileSource() ||
+            !isTileSourceExpected() ||
+            (policy.isCacheOnly() && cacheBin != 0L);
+
+        if (!canContinue)
         {
             disable("Error: layer does not have a valid TileSource, cannot create heightfield");
             return GeoHeightField::INVALID;
@@ -420,7 +479,7 @@ ElevationLayer::createHeightField(const TileKey&    key,
         // validate the existance of a valid layer profile.
         if ( !policy.isCacheOnly() && !getProfile() )
         {
-            disable("Could not establish a valid profile");
+            disable("Could not establish a valid profile.. did you set one?");
             return GeoHeightField::INVALID;
         }
 
@@ -437,7 +496,7 @@ ElevationLayer::createHeightField(const TileKey&    key,
             {            
                 bool expired = policy.isExpired(r.lastModifiedTime());
                 cachedHF = r.get<osg::HeightField>();
-                if ( cachedHF && validateHeightField(cachedHF) )
+                if ( cachedHF && validateHeightField(cachedHF.get()) )
                 {
                     if (!expired)
                     {
@@ -456,15 +515,27 @@ ElevationLayer::createHeightField(const TileKey&    key,
 
         if ( !hf.valid() )
         {
-            // bad tilesource? fail
-            if ( !getTileSource() || !getTileSource()->isOK() )
+            if ( !isKeyInLegalRange(key) )
                 return GeoHeightField::INVALID;
 
-            if ( !isKeyInRange(key) )
-                return GeoHeightField::INVALID;
+            // If no tile source is expected, create a height field by calling
+            // the raw inheritable method.
+            if (!isTileSourceExpected())
+            {
+                createImplementation(key, hf, normalMap, progress);
+                //hf = createHeightFieldImplementation(key, progress);
+            }
 
-            // build a HF from the TileSource.
-            hf = createHeightFieldFromTileSource( key, progress );
+            else
+            {
+                // bad tilesource? fail
+                if ( !getTileSource() || !getTileSource()->isOK() )
+                    return GeoHeightField::INVALID;
+
+                // build a HF from the TileSource.
+                //hf = createHeightFieldImplementation( key, progress );
+                createImplementation(key, hf, normalMap, progress);
+            }
 
             // validate it to make sure it's legal.
             if ( hf.valid() && !validateHeightField(hf.get()) )
@@ -479,7 +550,7 @@ ElevationLayer::createHeightField(const TileKey&    key,
                  !fromCache    &&
                  policy.isCacheWriteable() )
             {
-                cacheBin->write(cacheKey, hf, 0L);
+                cacheBin->write(cacheKey, hf.get(), 0L);
             }
 
             // We have an expired heightfield from the cache and no new data from the TileSource.  So just return the cached data.
@@ -507,7 +578,7 @@ ElevationLayer::createHeightField(const TileKey&    key,
 
         if ( hf.valid() )
         {
-            result = GeoHeightField( hf.get(), key.getExtent() );
+            result = GeoHeightField( hf.get(), normalMap.get(), key.getExtent() );
         }
     }
 
@@ -521,7 +592,7 @@ ElevationLayer::createHeightField(const TileKey&    key,
     // post-processing:
     if ( result.valid() )
     {
-        if ( _runtimeOptions.noDataPolicy() == NODATA_MSL )
+        if ( options().noDataPolicy() == NODATA_MSL )
         {
             // requested VDatum:
             const VerticalDatum* outputVDatum = key.getExtent().getSRS()->getVerticalDatum();
@@ -571,21 +642,145 @@ osg::MixinVector< osg::ref_ptr<ElevationLayer> >( rhs )
 namespace
 {
     typedef osg::ref_ptr<ElevationLayer>          RefElevationLayer;
-    typedef std::pair<RefElevationLayer, TileKey> LayerAndKey;
-    typedef std::vector<LayerAndKey>              LayerAndKeyVector;
+    struct LayerData {
+        RefElevationLayer layer;
+        TileKey key;
+        int index;
+    };
+    //typedef std::pair<RefElevationLayer, TileKey> LayerAndKey;
+    typedef std::vector<LayerData>              LayerDataVector;
+
+    //! Gets the normal vector for elevation data at column s, row t.
+    osg::Vec3 getNormal(const GeoExtent& extent, const osg::HeightField* hf, int s, int t)
+    {
+        int w = hf->getNumColumns();
+        int h = hf->getNumRows();
+
+        osg::Vec2d res(
+            extent.width() / (double)(w-1),
+            extent.height() / (double)(h-1));
+
+        float e = hf->getHeight(s, t);
+
+        double dx = res.x(), dy = res.y();
+
+        if (extent.getSRS()->isGeographic())
+        {
+            double R = extent.getSRS()->getEllipsoid()->getRadiusEquator();
+            double mPerDegAtEquator = (2.0 * osg::PI * R) / 360.0;
+            dy = dy * mPerDegAtEquator;
+            double lat = extent.yMin() + res.y()*(double)t;
+            dx = dx * mPerDegAtEquator * cos(osg::DegreesToRadians(lat));
+        }
+        
+        osg::Vec3d west(0, 0, e), east(0, 0, e), south(0, 0, e), north(0, 0, e);
+
+        if (s > 0)     west.set (-dx, 0, hf->getHeight(s-1, t));
+        if (s < w - 1) east.set ( dx, 0, hf->getHeight(s+1, t));
+        if (t > 0)     south.set(0, -dy, hf->getHeight(s, t-1));
+        if (t < h - 1) north.set(0,  dy, hf->getHeight(s, t+1));
+
+        osg::Vec3d normal = (east - west) ^ (north - south);
+        return normal;
+    }
+
+    //! Creates a normal map for heightfield "hf" and stores it in the
+    //! pre-allocated NormalMap.
+    //!
+    //! "deltaLOD" holds the difference in LODs between the heightfield itself and the LOD
+    //! from which the elevation value came. This will be positive when we had to "fall back" on 
+    //! lower LOD data to fetch an elevation value. When this happens we need to interpolate
+    //! between "real" normals instead of sampling them from the neighboring pixels, otherwise
+    //! ugly faceting will occur.
+    //!
+    //! Unfortunately, it there's an offset layer, this will update the deltaLOD and we will 
+    //! get faceting if the real elevation layer is from a lower LOD. The only solution to this
+    //! would be to sample the elevation data using a spline function instead of bilinear
+    //! interpolation -- but we would need to do that to a separate heightfield (especially for
+    //! normals) in order to maintain terrain correlation. Maybe someday.
+    void createNormalMap(const GeoExtent& extent, const osg::HeightField* hf, const osg::ShortArray* deltaLOD, NormalMap* normalMap)
+    {
+        int w = hf->getNumColumns();
+        int h = hf->getNumRows();
+
+        for (int t = 0; t < (int)hf->getNumRows(); ++t)
+        {
+            for (int s = 0; s<(int)hf->getNumColumns(); ++s)
+            {
+                int step = 1 << (*deltaLOD)[t*h + s];
+
+                osg::Vec3 normal;
+
+                // four corners:
+                int s0=s, s1=s, t0=t, t1=t;
+
+                if (step == 1)
+                {
+                    // Same LOD, simple query
+                    normal = getNormal(extent, hf, s, t);
+                }
+                else
+                {
+                    int s0 = std::max(s - (s % step), 0);
+                    int s1 = (s%step == 0)? s0 : std::min(s0+step, w-1);
+                    int t0 = std::max(t - (t % step), 0);
+                    int t1 = (t%step == 0)? t0 : std::min(t0+step, h-1);
+                    
+                    if (s0 == s1 && t0 == t1)
+                    {
+                        // on-pixel, simple query
+                        normal = getNormal(extent, hf, s0, t0);
+                    }
+                    else if (s0 == s1)
+                    {
+                        // same column; linear interpolate along row
+                        osg::Vec3 S = getNormal(extent, hf, s0, t0);
+                        osg::Vec3 N = getNormal(extent, hf, s0, t1);
+                        normal = S*(double)(t1 - t) + N*(double)(t - t0);
+                    }
+                    else if (t0 == t1)
+                    {
+                        // same row; linear interpolate along column
+                        osg::Vec3 W = getNormal(extent, hf, s0, t0);
+                        osg::Vec3 E = getNormal(extent, hf, s1, t0);
+                        normal = W*(double)(s1 - s) + E*(double)(s - s0);
+                    }
+                    else
+                    {
+                        // bilinear interpolate
+                        osg::Vec3 SW = getNormal(extent, hf, s0, t0);
+                        osg::Vec3 SE = getNormal(extent, hf, s1, t0);
+                        osg::Vec3 NW = getNormal(extent, hf, s0, t1);
+                        osg::Vec3 NE = getNormal(extent, hf, s1, t1);
+
+                        osg::Vec3 S = SW*(double)(s1 - s) + SE*(double)(s - s0);
+                        osg::Vec3 N = NW*(double)(s1 - s) + NE*(double)(s - s0);
+                        normal = S*(double)(t1 - t) + N*(double)(t - t0);
+                    }
+                }
+
+                normal.normalize();
+
+                normalMap->set(s, t, normal, 0.0f);
+            }
+        }
+    }
 }
 
 bool
-ElevationLayerVector::populateHeightField(osg::HeightField*      hf,
-                                          const TileKey&         key,
-                                          const Profile*         haeProfile,
-                                          ElevationInterpolation interpolation,
-                                          ProgressCallback*      progress ) const
+ElevationLayerVector::populateHeightFieldAndNormalMap(osg::HeightField*      hf,
+                                                      NormalMap*             normalMap,
+                                                      const TileKey&         key,
+                                                      const Profile*         haeProfile,
+                                                      ElevationInterpolation interpolation,
+                                                      ProgressCallback*      progress ) const
 {
     // heightfield must already exist.
     if ( !hf )
         return false;
 
+    METRIC_SCOPED("ElevationLayer.populateHeightField");
+
     // if the caller provided an "HAE map profile", he wants an HAE elevation grid even if
     // the map profile has a vertical datum. This is the usual case when building the 3D
     // terrain, for example. Construct a temporary key that doesn't have the vertical
@@ -597,16 +792,17 @@ ElevationLayerVector::populateHeightField(osg::HeightField*      hf,
     }
     
     // Collect the valid layers for this tile.
-    LayerAndKeyVector contenders;
-    LayerAndKeyVector offsets;
+    LayerDataVector contenders;
+    LayerDataVector offsets;
 
     // Track the number of layers that would return fallback data.
     unsigned numFallbackLayers = 0;
 
     // Check them in reverse order since the highest priority is last.
-    for(ElevationLayerVector::const_reverse_iterator i = this->rbegin(); i != this->rend(); ++i)
+    for (int i = size()-1; i>=0; --i)
+    //for(ElevationLayerVector::const_reverse_iterator i = this->rbegin(); i != this->rend(); ++i)
     {
-        ElevationLayer* layer = i->get();
+        ElevationLayer* layer = (*this)[i].get(); //i->get();
 
         if ( layer->getEnabled() && layer->getVisible() )
         {
@@ -618,44 +814,48 @@ ElevationLayerVector::populateHeightField(osg::HeightField*      hf,
             bool useLayer = true;
             TileKey bestKey( mappedKey );
 
-            // Is there a tilesource? If not we are cache-only and cannot reject the layer.
-            if ( layer->getTileSource() )
+            // Check whether the non-mapped key is valid according to the user's min/max level settings:
+            if ( !layer->isKeyInLegalRange(key) )
             {
-                // Check whether the non-mapped key is valid according to the user's min/max level settings:
-                if ( !layer->isKeyInRange(key) )
-                {
-                    useLayer = false;
-                }
+                useLayer = false;
+            }
                 
-
-                // Find the "best available" mapped key from the tile source:
-                else 
+            // Find the "best available" mapped key from the tile source:
+            else 
+            {
+                bestKey = layer->getBestAvailableTileKey(mappedKey);
+                if (bestKey.valid())
                 {
-                    if ( layer->getTileSource()->getBestAvailableTileKey(mappedKey, bestKey) )
+                    // If the bestKey is not the mappedKey, this layer is providing
+                    // fallback data (data at a lower resolution than requested)
+                    if ( mappedKey != bestKey )
                     {
-                        // If the bestKey is not the mappedKey, this layer is providing
-                        // fallback data (data at a lower resolution than requested)
-                        if ( mappedKey != bestKey )
-                        {
-                            numFallbackLayers++;
-                        }
-                    }
-                    else
-                    {
-                        useLayer = false;
+                        numFallbackLayers++;
                     }
                 }
+                else
+                {
+                    useLayer = false;
+                }
             }
 
             if ( useLayer )
             {
                 if ( layer->isOffset() )
                 {
-                    offsets.push_back( std::make_pair(layer, bestKey) );
+                    offsets.push_back(LayerData());
+                    LayerData& ld = offsets.back();
+                    ld.layer = layer;
+                    ld.key = bestKey;
+                    ld.index = i;
                 }
                 else
                 {
-                    contenders.push_back( std::make_pair(layer, bestKey) );
+                    contenders.push_back(LayerData());
+                    LayerData& ld = contenders.back();
+                    ld.layer = layer;
+                    ld.key = bestKey;
+                    ld.index = i;
                 }
             }
         }
@@ -680,13 +880,44 @@ ElevationLayerVector::populateHeightField(osg::HeightField*      hf,
     double   ymin       = key.getExtent().yMin();
     double   dx         = key.getExtent().width() / (double)(numColumns-1);
     double   dy         = key.getExtent().height() / (double)(numRows-1);
+
+#if 0
+    // If the incoming heightfield requests a positive border width, 
+    // we need to adjust the extents so that we request data outside the
+    // extent of the tile key:
+    unsigned border = hf->getBorderWidth();
+    if (border > 0u)
+    {
+        dx = key.getExtent().width() / (double)(numColumns - (border*2+1));
+        dy = key.getExtent().height() / (double)(numRows - (border*2+1));
+        xmin -= dx * (double)border;
+        ymin -= dy * (double)border;
+    }
+#endif
     
     // We will load the actual heightfields on demand. We might not need them all.
+#if 0
     GeoHeightFieldVector heightFields(contenders.size());
     GeoHeightFieldVector offsetFields(offsets.size());
     std::vector<bool>    heightFallback(contenders.size(), false);
     std::vector<bool>    heightFailed(contenders.size(), false);
     std::vector<bool>    offsetFailed(offsets.size(), false);
+#else
+    GeoHeightFieldVector heightFields[9];
+    GeoHeightFieldVector offsetFields[9]; //(offsets.size());
+    std::vector<bool>    heightFallback[9]; //(contenders.size(), false);
+    std::vector<bool>    heightFailed[9]; //(contenders.size(), false);
+    std::vector<bool>    offsetFailed[9]; //(offsets.size(), false);
+
+    for (int n = 0; n < 9; ++n)
+    {
+        heightFields[n].resize(contenders.size());
+        offsetFields[n].resize(offsets.size());
+        heightFallback[n].assign(9, false);
+        heightFailed[n].assign(9, false);
+        offsetFailed[n].assign(9, false);
+    }
+#endif
 
     // The maximum number of heightfields to keep in this local cache
     unsigned int maxHeightFields = 50;
@@ -697,9 +928,14 @@ ElevationLayerVector::populateHeightField(osg::HeightField*      hf,
     bool realData = false;
 
     unsigned int total = numColumns * numRows;
-    unsigned int completed = 0;
+
+    // query resolution interval (x, y) of each sample.
+    osg::ref_ptr<osg::ShortArray> deltaLOD = new osg::ShortArray(total);
+    
     int nodataCount = 0;
 
+    TileKey scratchKey; // Storage if a new key needs to be constructed
+
     for (unsigned c = 0; c < numColumns; ++c)
     {
         double x = xmin + (dx * (double)c);
@@ -708,67 +944,100 @@ ElevationLayerVector::populateHeightField(osg::HeightField*      hf,
             double y = ymin + (dy * (double)r);
 
             // Collect elevations from each layer as necessary.
-            bool resolved = false;
+            int resolvedIndex = -1;
+
+            osg::Vec3 normal_sum(0,0,0);
 
-            for(int i=0; i<contenders.size() && !resolved; ++i)
+            for(int i=0; i<contenders.size() && resolvedIndex<0; ++i)
             {
-                if ( heightFailed[i] )
+                ElevationLayer* layer = contenders[i].layer.get();                
+                TileKey& contenderKey = contenders[i].key;
+                int index = contenders[i].index;
+
+                // If there is a border, the edge points may not fall within the key extents 
+                // and we may need to fetch a neighboring key.
+
+                int n = 4; // index 4 is the center/default tile
+
+#if 0
+                if (border > 0u && !contenderKey.getExtent().contains(x, y))
+                {
+                    int dTx = x < contenderKey.getExtent().xMin() ? -1 : x > contenderKey.getExtent().xMax() ? +1 : 0;
+                    int dTy = y < contenderKey.getExtent().yMin() ? +1 : y > contenderKey.getExtent().yMax() ? -1 : 0;
+                    contenderKey = contenderKey.createNeighborKey(dTx, dTy);
+                    n = (dTy+1)*3 + (dTx+1);
+                }
+#endif
+
+                if ( heightFailed[n][i] )
                     continue;
 
-                ElevationLayer* layer = contenders[i].first.get();
+                TileKey* actualKey = &contenderKey;
 
-                GeoHeightField& layerHF = heightFields[i];
-                TileKey actualKey = contenders[i].second;
+                GeoHeightField& layerHF = heightFields[n][i];
 
                 if (!layerHF.valid())
                 {
                     // We couldn't get the heightfield from the cache, so try to create it.
                     // We also fallback on parent layers to make sure that we have data at the location even if it's fallback.
-                    while (!layerHF.valid() && actualKey.valid())
+                    while (!layerHF.valid() && actualKey->valid() && layer->isKeyInLegalRange(*actualKey))
                     {
-                        layerHF = layer->createHeightField(actualKey, progress);
+                        layerHF = layer->createHeightField(*actualKey, progress);
                         if (!layerHF.valid())
                         {
-                            actualKey = actualKey.createParentKey();
+                            if (actualKey != &scratchKey)
+                            {
+                                scratchKey = *actualKey;
+                                actualKey = &scratchKey;
+                            }
+                            *actualKey = actualKey->createParentKey();
                         }
                     }
 
                     // Mark this layer as fallback if necessary.
                     if (layerHF.valid())
                     {
-                        heightFallback[i] = actualKey != contenders[i].second;
+                        heightFallback[n][i] = (*actualKey != contenderKey); // actualKey != contenders[i].second;
                         numHeightFieldsInCache++;
                     }
                     else
                     {
-                        heightFailed[i] = true;
+                        heightFailed[n][i] = true;
                         continue;
                     }
                 }
 
                 if (layerHF.valid())
                 {
-                    bool isFallback = heightFallback[i];
+                    bool isFallback = heightFallback[n][i];
 
                     // We only have real data if this is not a fallback heightfield.
                     if (!isFallback)
                     {
                         realData = true;
                     }
-
+                    
                     float elevation;
                     if (layerHF.getElevation(keySRS, x, y, interpolation, keySRS, elevation))
                     {
                         if ( elevation != NO_DATA_VALUE )
                         {
-                            resolved = true;                    
+                            // remember the index so we can only apply offset layers that
+                            // sit on TOP of this layer.
+                            resolvedIndex = index;
+
                             hf->setHeight(c, r, elevation);
+
+                            if (deltaLOD)
+                            {
+                                (*deltaLOD)[r*numColumns + c] = key.getLOD() - actualKey->getLOD();
+                            }
                         }
                         else
                         {
                             ++nodataCount;
                         }
-                    }
+                    }                    
                 }
 
 
@@ -776,10 +1045,13 @@ ElevationLayerVector::populateHeightField(osg::HeightField*      hf,
                 if (numHeightFieldsInCache >= maxHeightFields)
                 {
                     //OE_NOTICE << "Clearing cache" << std::endl;
-                    for (unsigned int k = 0; k < heightFields.size(); k++)
+                    for (unsigned int j = 0; j < 9; ++j)
                     {
-                        heightFields[k] = GeoHeightField::INVALID;
-                        heightFallback[k] = false;
+                        for (unsigned int k = 0; k < heightFields[j].size(); k++)
+                        {
+                            heightFields[j][k] = GeoHeightField::INVALID;
+                            heightFallback[j][k] = false;
+                        }
                     }
                     numHeightFieldsInCache = 0;
                 }
@@ -787,18 +1059,40 @@ ElevationLayerVector::populateHeightField(osg::HeightField*      hf,
 
             for(int i=offsets.size()-1; i>=0; --i)
             {
-                if ( offsetFailed[i] )
+                // Only apply an offset layer if it sits on top of the resolved layer
+                // (or if there was no resolved layer).
+                if (resolvedIndex >= 0 && offsets[i].index < resolvedIndex)
                     continue;
 
-                GeoHeightField& layerHF = offsetFields[i];
+                TileKey &contenderKey = offsets[i].key;
+
+                // If there is a border, the edge points may not fall within the key extents 
+                // and we may need to fetch a neighboring key.
+
+                int n = 4; // index 4 is the center/default tile
+
+#if 0
+                if (border > 0u && !contenderKey.getExtent().contains(x, y))
+                {
+                    int dTx = x < contenderKey.getExtent().xMin() ? -1 : x > contenderKey.getExtent().xMax() ? +1 : 0;
+                    int dTy = y < contenderKey.getExtent().yMin() ? +1 : x > contenderKey.getExtent().yMax() ? -1 : 0;
+                    contenderKey = contenderKey.createNeighborKey(dTx, dTy);
+                    n = (dTy+1)*3 + (dTx+1);
+                }
+#endif
+                
+                if ( offsetFailed[n][i] == true )
+                    continue;
+
+                GeoHeightField& layerHF = offsetFields[n][i];
                 if ( !layerHF.valid() )
                 {
-                    ElevationLayer* offset = offsets[i].first.get();
+                    ElevationLayer* offset = offsets[i].layer.get();
 
-                    layerHF = offset->createHeightField(offsets[i].second, progress);
+                    layerHF = offset->createHeightField(contenderKey, progress);
                     if ( !layerHF.valid() )
                     {
-                        offsetFailed[i] = true;
+                        offsetFailed[n][i] = true;
                         continue;
                     }
                 }
@@ -811,11 +1105,24 @@ ElevationLayerVector::populateHeightField(osg::HeightField*      hf,
                     elevation != NO_DATA_VALUE)
                 {                    
                     hf->getHeight(c, r) += elevation;
+
+                    // Update the resolution tracker to account for the offset. Sadly this
+                    // will wipe out the resolution of the actual data, and might result in 
+                    // normal faceting. See the comments on "createNormalMap" for more info
+                    if (deltaLOD)
+                    {
+                        (*deltaLOD)[r*numColumns + c] = key.getLOD() - contenderKey.getLOD();
+                    }
                 }
             }
         }
     }
 
+    if (normalMap)
+    {
+        createNormalMap(key.getExtent(), hf, deltaLOD.get(), normalMap);
+    }
+
     // Return whether or not we actually read any real data
     return realData;
 }
diff --git a/src/osgEarth/ElevationPool b/src/osgEarth/ElevationPool
new file mode 100644
index 0000000..d3173ef
--- /dev/null
+++ b/src/osgEarth/ElevationPool
@@ -0,0 +1,266 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+ * Copyright 2008-2016 Pelican Mapping
+ * http://osgearth.org
+ *
+ * osgEarth is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+#ifndef OSGEARTH_ELEVATION_POOL_H
+#define OSGEARTH_ELEVATION_POOL_H
+
+#include <osgEarth/Common>
+#include <osgEarth/ElevationLayer>
+#include <osgEarth/MapFrame>
+#include <osgEarth/GeoData>
+#include <osgEarth/TileKey>
+#include <osgEarth/ThreadingUtils>
+#include <osg/Timer>
+#include <map>
+
+namespace osgEarth
+{
+    using namespace Threading;
+
+    // defined at the end of this file
+    class ElevationEnvelope;
+    class Map;
+
+    //! Result of an elevation query.
+    struct ElevationSample : public osg::Referenced
+    {
+        float elevation;
+        float resolution;
+        ElevationSample(float a, float b) : elevation(a), resolution(b) { }
+    };
+
+    /**
+     * A pool of elevation data that can be used to manage regional elevation
+     * data queries. To use this, call createEnvelope() and use that object
+     * to make your elevation queries at a particular LOD. The queries can
+     * be anywhere geographically, but the algorithm will optimize lots of
+     * queries in a particular area (like a terrain tile).
+     *
+     * Each Map contains an ElevationPool you can access for queries against
+     * that Map.
+     *
+     * ElevationEnvelope* envelope = pool->createEnvelope(srs, lod);
+     * float z = envelope->getElevation(point);
+     */
+    class OSGEARTH_EXPORT ElevationPool : public osg::Referenced
+    {
+    public:
+        /** ctor */
+        ElevationPool();
+
+        /** Sets the map from which to read elevation data. */
+        void setMap(const Map* map);
+
+        /**
+         * Sets a vector of ElevationLayers from which to read elevation data.
+         * Omit this is you just want to use all the elevation layes in the Map
+         * that you set with setMap().
+         */
+        void setElevationLayers(const ElevationLayerVector& layers);
+        const ElevationLayerVector& getElevationLayers() const { return _layers; }
+
+        /** Sets the dimension of heightfield tiles to read from the map */
+        void setTileSize(unsigned size);
+        unsigned getTileSize() const { return _tileSize; }
+
+        /** Creates a query envelope to use for elevation queries in a certain area. */
+        ElevationEnvelope* createEnvelope(const SpatialReference* srs, unsigned lod);
+
+        //! Queries the elevation at a GeoPoint for a given LOD.
+        Future<ElevationSample> getElevation(const GeoPoint& p, unsigned lod=23);
+
+        /** Maximum number of elevation tiles to cache */
+        void setMaxEntries(unsigned maxEntries) { _maxEntries = maxEntries; }
+        unsigned getMaxEntries() const          { return _maxEntries; }
+
+        /** Clears any cached tiles from the elevation pool. */
+        void clear();
+        
+        void stopThreading();
+
+    protected:
+
+        osg::observer_ptr<const osg::Referenced> _map;
+
+        ElevationLayerVector _layers;
+
+        enum Status
+        {
+            STATUS_EMPTY = 0u,
+            STATUS_IN_PROGRESS = 1u,
+            STATUS_AVAILABLE = 2u,
+            STATUS_FAIL = 3u
+        };
+
+        // Single elevation tile along with its load status
+        class Tile : public osg::Referenced
+        {
+        public:
+            Tile() : _status(STATUS_EMPTY) { }
+            TileKey             _key;           // key used to request this tile
+            Bounds              _bounds;
+            GeoHeightField      _hf;
+            OpenThreads::Atomic _status;
+            osg::Timer_t        _loadTime;
+        };
+
+        // Custom comparator for Tile that sorts Tiles in a set from
+        // highest resolution to lowest resolution; This ensures we are
+        // sampling overlapping tiles in the proper order
+        struct TileSortHiResToLoRes
+        {
+            bool operator()(
+                const osg::ref_ptr<Tile>& lhs,
+                const osg::ref_ptr<Tile>& rhs) const
+            {
+                // works because the KEY less-than function checks the LOD number
+                // first, which corresponds to the resolution. Higher LOD = higher res.
+                return rhs->_key < lhs->_key;
+            }
+        };
+                
+        // Keeps track of the most-recently-used tiles, in order from MRU (front)
+        // to LRU (back). Multiple pointers to the same Tile may exist in the MRU list.
+        // Once all pointers to a Tile disappear from the MRU (by popping off the back)
+        // the corresponding Tile observer in the Tiles data structure will go NULL
+        // and the Tile will destruct.
+        typedef std::list<osg::ref_ptr<Tile> > MRU;
+        MRU _mru;
+
+        // Cached set of tiles, sorted by TileKey. These are observer pointers; the 
+        // actual references are held in the MRU. That way when all pointers drop off
+        // the back of the MRU, the Tile is destroyed and the main observer goes to 
+        // NULL and is removed.
+        typedef std::map<TileKey, osg::observer_ptr<Tile> > Tiles;
+        Tiles _tiles;
+        Threading::Mutex  _tilesMutex;
+
+        // Track the number of entries in the MRU manually since std::list::size
+        // can be O(n) on some platforms
+        unsigned _entries;
+        unsigned _maxEntries;
+
+        // dimension of sampling heightfield
+        unsigned _tileSize;
+
+        // QuerySet is a collection of Tiles, sorted from high to low resolution,
+        // that a ElevationEnvelope uses for a terrain sampling opteration.
+        typedef std::set<osg::ref_ptr<Tile>, TileSortHiResToLoRes> QuerySet;
+
+        // Asynchronous elevation query operation
+        struct GetElevationOp : public osg::Operation {
+            GetElevationOp(ElevationPool*, const GeoPoint&, unsigned lod);
+            osg::observer_ptr<ElevationPool> _pool;
+            GeoPoint _point;
+            unsigned _lod;
+            Promise<ElevationSample> _promise;
+            void operator()(osg::Object*);
+        };
+        friend struct GetElevationOp;
+        osg::ref_ptr<osg::OperationQueue> _opQueue;
+        std::vector< osg::ref_ptr<osg::OperationThread> > _opThreads;
+
+        virtual ~ElevationPool();
+
+    protected:
+
+        // safely popluate the tile; called when Tile._status = IN_PROGRESS
+        bool fetchTileFromMap(const TileKey& key, MapFrame& frame, Tile* tile);
+        
+        // safely fetch a tile from the central repo, loading from map if necessary
+        bool getTile(const TileKey& key, MapFrame& frame, osg::ref_ptr<Tile>& output);
+        
+        // safely fetch a tile from the central repo, loading from map if necessary
+        bool tryTile(const TileKey& key, MapFrame& frame, osg::ref_ptr<Tile>& output);
+
+        // safely remove the oldest item on the MRU
+        void popMRU();
+
+        // clears and resets the pool.
+        void clearImpl();
+
+        friend class ElevationEnvelope;
+    };
+
+
+    /**
+     * Set of terrain tiles corresponding to a geographic extent that the pager
+     * uses to clamp a feature set. You cannot create this object directly;
+     * instead call ElevationPool::createEnvelope().
+     */
+    class OSGEARTH_EXPORT ElevationEnvelope : public osg::Referenced
+    {
+    public:
+
+        /**
+         * Gets a single elevation, or returns NO_DATA_VALUE upon failure.
+         * The Z value is ignored.
+         */
+        float getElevation(double x, double y);
+
+        /**
+         * Gets a single elevation along with the resolution of the data from
+         * which that sample was taken.
+         */
+        std::pair<float, float> getElevationAndResolution(double x, double y);
+
+        /**
+         * Gets a elevation value for each input point and puts them in output.
+         * Returns the number of successful elevations. Failed queries are set to
+         * NO_DATA_VALUE in the output vector.
+         */
+        unsigned getElevations(
+            const std::vector<osg::Vec3d>& input,
+            std::vector<float>& output);
+
+        /**
+         * Gets the elevation extrema over a collection of point data.
+         * Returns false if the points don't fall inside the envelope
+         */
+        bool getElevationExtrema(
+            const std::vector<osg::Vec3d>& points, 
+            float& out_min, float& out_max);
+
+        /**
+         * The SRS that this envelope expects query points to be in
+         */
+        const SpatialReference* getSRS() const;
+
+        /**
+         * LOD at which this envelope will try to sample the elevation
+         */
+        unsigned getLOD() const { return _lod; }
+
+    protected:
+        ElevationEnvelope();
+        virtual ~ElevationEnvelope();
+
+        ElevationPool::QuerySet _tiles;
+        osg::ref_ptr<const SpatialReference> _inputSRS;
+        unsigned _lod;
+        MapFrame _frame;
+        ElevationPool* _pool;
+        friend class ElevationPool;
+
+    private:
+        bool sample(double x, double y, float& out_elevation, float& out_resolution);
+    };
+
+} // namespace
+
+#endif // OSGEARTH_ELEVATION_POOL_H
diff --git a/src/osgEarth/ElevationPool.cpp b/src/osgEarth/ElevationPool.cpp
new file mode 100644
index 0000000..4606fb7
--- /dev/null
+++ b/src/osgEarth/ElevationPool.cpp
@@ -0,0 +1,513 @@
+
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+ * Copyright 2008-2016 Pelican Mapping
+ * http://osgearth.org
+ *
+ * osgEarth is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+#include <osgEarth/ElevationPool>
+#include <osgEarth/TileKey>
+#include <osgEarth/MapFrame>
+#include <osgEarth/Map>
+#include <osgEarth/Metrics>
+#include <osgEarth/Registry>
+#include <osg/Shape>
+
+using namespace osgEarth;
+
+#define LC "[ElevationPool] "
+
+#define OE_TEST OE_DEBUG
+
+
+ElevationPool::ElevationPool() :
+_entries(0u),
+_maxEntries( 128u ),
+_tileSize( 257u )
+{
+    //nop
+    //_opQueue = Registry::instance()->getAsyncOperationQueue();
+    if (!_opQueue.valid())
+    {
+        _opQueue = new osg::OperationQueue();
+        for (unsigned i=0; i<2; ++i)
+        {
+            osg::OperationThread* thread = new osg::OperationThread();
+            thread->setOperationQueue(_opQueue.get());
+            thread->start();
+            _opThreads.push_back(thread);
+        }
+    }
+}
+
+ElevationPool::~ElevationPool()
+{
+    stopThreading();
+}
+
+void
+ElevationPool::setMap(const Map* map)
+{
+    Threading::ScopedMutexLock lock(_tilesMutex);
+    _map = map;
+    clearImpl();
+}
+
+void
+ElevationPool::clear()
+{
+    Threading::ScopedMutexLock lock(_tilesMutex);
+    clearImpl();
+}
+
+void
+ElevationPool::stopThreading()
+{
+    _opQueue->releaseAllOperations();
+    
+    for (unsigned i = 0; i<_opThreads.size(); ++i)
+    _opThreads[i]->setDone(true);
+}
+
+void
+ElevationPool::setElevationLayers(const ElevationLayerVector& layers)
+{
+    Threading::ScopedMutexLock lock(_tilesMutex);
+    _layers = layers;
+    clearImpl();
+}
+
+void
+ElevationPool::setTileSize(unsigned value)
+{
+    Threading::ScopedMutexLock lock(_tilesMutex);
+    _tileSize = value;
+    clearImpl();
+}
+
+Future<ElevationSample>
+ElevationPool::getElevation(const GeoPoint& point, unsigned lod)
+{
+    GetElevationOp* op = new GetElevationOp(this, point, lod);
+    Future<ElevationSample> result = op->_promise.getFuture();
+    _opQueue->add(op);
+    return result;
+}
+
+ElevationPool::GetElevationOp::GetElevationOp(ElevationPool* pool, const GeoPoint& point, unsigned lod) :
+_pool(pool), _point(point), _lod(lod)
+{
+    //nop
+}
+
+void
+ElevationPool::GetElevationOp::operator()(osg::Object*)
+{
+    osg::ref_ptr<ElevationPool> pool;
+    if (!_promise.isAbandoned() && _pool.lock(pool))
+    {
+        osg::ref_ptr<ElevationEnvelope> env = pool->createEnvelope(_point.getSRS(), _lod);
+        std::pair<float, float> r = env->getElevationAndResolution(_point.x(), _point.y());
+        _promise.resolve(new ElevationSample(r.first, r.second));
+    }
+}
+
+bool
+ElevationPool::fetchTileFromMap(const TileKey& key, MapFrame& frame, Tile* tile)
+{
+    tile->_loadTime = osg::Timer::instance()->tick();
+
+    osg::ref_ptr<osg::HeightField> hf = new osg::HeightField();
+    hf->allocate( _tileSize, _tileSize );
+
+    // Initialize the heightfield to nodata
+    hf->getFloatArray()->assign( hf->getFloatArray()->size(), NO_DATA_VALUE );
+
+    TileKey keyToUse = key;
+    while( !tile->_hf.valid() && keyToUse.valid() )
+    {
+        bool ok;
+        if (_layers.empty())
+        {
+            OE_TEST << LC << "Populating from FULL MAP (" << keyToUse.str() << ")\n";
+            ok = frame.populateHeightField(hf, keyToUse, false /*heightsAsHAE*/, 0L);
+        }
+        else
+        {
+            OE_TEST << LC << "Populating from layers (" << keyToUse.str() << ")\n";
+            ok = _layers.populateHeightFieldAndNormalMap(hf.get(), 0L, keyToUse, 0L, INTERP_BILINEAR, 0L);
+        }
+
+        if (ok)
+        {
+            tile->_hf = GeoHeightField( hf.get(), keyToUse.getExtent() );
+            tile->_bounds = keyToUse.getExtent().bounds();
+        }
+        else
+        {
+            keyToUse = keyToUse.createParentKey();
+        }
+    }
+
+    return tile->_hf.valid();
+}
+
+void
+ElevationPool::popMRU()
+{
+    // first rememeber the key of the item we're about the pop:
+    TileKey key = _mru.back()->_key;
+
+    // establish a temporary observer on the item:
+    osg::observer_ptr<Tile> temp = _mru.back().get();
+
+    // pop the Tile from the MRU:
+    _mru.pop_back();
+
+    // if our observer went to NULL, we know there are no more pointers
+    // to that Tile in the MRU, and we can remove it from the main list:
+    if (!temp.valid())
+    {
+        _tiles.erase(key);
+    }
+}
+
+bool
+ElevationPool::tryTile(const TileKey& key, MapFrame& frame, osg::ref_ptr<Tile>& out)
+{
+    // first see whether the tile is available
+    _tilesMutex.lock();
+
+    // locate the tile in the local tile cache:
+    osg::observer_ptr<Tile>& tile_obs = _tiles[key];
+
+    osg::ref_ptr<Tile> tile;
+
+    // Get a safe pointer to it. If this is NULL, we need to create and
+    // fetch a new tile from the Map.
+    if (!tile_obs.lock(tile))
+    {
+        // a new tile; status -> EMPTY
+        tile = new Tile();
+        tile->_key = key;
+
+        // update the LRU:
+        _mru.push_front(tile.get());
+
+        // prune the MRU if necessary:
+        if (++_entries > _maxEntries )
+        {
+            popMRU();
+            --_entries;
+        }
+
+        // add to the main cache (after putting it on the LRU).
+        tile_obs = tile;
+    }
+       
+    // This means the tile object exists but has yet to be populated:
+    if ( tile->_status == STATUS_EMPTY )
+    {
+        OE_TEST << "  getTile(" << key.str() << ") -> fetch from map\n";
+        tile->_status.exchange(STATUS_IN_PROGRESS);
+        _tilesMutex.unlock();
+
+        bool ok = fetchTileFromMap(key, frame, tile.get());
+        tile->_status.exchange( ok ? STATUS_AVAILABLE : STATUS_FAIL );
+        
+        out = ok ? tile.get() : 0L;
+        return ok;
+    }
+
+    // This means the tile object is populated and available for use:
+    else if ( tile->_status == STATUS_AVAILABLE )
+    {
+        OE_TEST << "  getTile(" << key.str() << ") -> available\n";
+        out = tile.get();
+
+        // Mark this tile as recently used:
+        _mru.push_front(tile.get());
+
+        // prune the MRU if necessary
+        if (++_entries > _maxEntries)
+        {
+            popMRU();
+            --_entries;
+        }
+
+        _tilesMutex.unlock();
+        return true;
+    }
+
+    // This means the attempt to populate the tile with data failed.
+    else if ( tile->_status == STATUS_FAIL )
+    {
+        OE_TEST << "  getTile(" << key.str() << ") -> fail\n";
+        _tilesMutex.unlock();
+        out = 0L;
+        return false;
+    }
+
+    // This means tile data fetch is still in progress (in another thread)
+    // and the caller should check back later.
+    else //if ( tile->_status == STATUS_IN_PROGRESS )
+    {
+        OE_DEBUG << "  getTile(" << key.str() << ") -> in progress...waiting\n";
+        _tilesMutex.unlock();
+        out = 0L;
+        return true;            // out:NULL => check back later please.
+    }
+}
+
+void
+ElevationPool::clearImpl()
+{
+    // assumes the tiles lock is taken.
+    _tiles.clear();
+    _mru.clear();
+    _entries = 0u;
+}
+
+bool
+ElevationPool::getTile(const TileKey& key, MapFrame& frame, osg::ref_ptr<ElevationPool::Tile>& output)
+{
+    // Synchronize the MapFrame to its Map; if there's an update,
+    // clear out the internal cache and MRU.
+    if ( frame.needsSync() )
+    {
+        if (frame.sync())
+        {
+            // Probably unnecessary because the Map itself will clear the pool.
+            clear();
+        }
+    }
+   
+    OE_START_TIMER(get);
+
+    const double timeout = 30.0;
+    osg::ref_ptr<Tile> tile;
+    while( tryTile(key, frame, tile) && !tile.valid() && OE_GET_TIMER(get) < timeout)
+    {
+        // condition: another thread is working on fetching the tile from the map,
+        // so wait and try again later. Do this until we succeed or time out.
+        OpenThreads::Thread::YieldCurrentThread();
+    }
+
+    if ( !tile.valid() && OE_GET_TIMER(get) >= timeout )
+    {
+        // this means we timed out trying to fetch the map tile.
+        OE_TEST << LC << "Timout fetching tile " << key.str() << std::endl;
+    }
+
+    if ( tile.valid() )
+    {
+        if ( tile->_hf.valid() )
+        {
+            // got a valid tile, so push it to the query set.
+            output = tile.get();
+        }
+        else
+        {
+            OE_WARN << LC << "Got a tile with an invalid HF (" << key.str() << ")\n";
+        }
+    }
+
+    return tile.valid();
+}
+
+ElevationEnvelope*
+ElevationPool::createEnvelope(const SpatialReference* srs, unsigned lod)
+{
+    ElevationEnvelope* e = new ElevationEnvelope();
+    e->_inputSRS = srs;
+    osg::ref_ptr<const osg::Referenced> map;
+    if (_map.lock(map))
+        e->_frame.setMap(static_cast<const Map*>(map.get()));
+    e->_lod = lod;
+    e->_pool = this;
+    return e;
+}
+
+//........................................................................
+
+ElevationEnvelope::ElevationEnvelope() :
+_pool(0L)
+{
+    //nop
+}
+
+ElevationEnvelope::~ElevationEnvelope()
+{
+    //nop
+}
+
+bool
+ElevationEnvelope::sample(double x, double y, float& out_elevation, float& out_resolution)
+{
+    out_elevation = NO_DATA_VALUE;
+    out_resolution = 0.0f;
+    bool foundTile = false;
+
+    GeoPoint p(_inputSRS.get(), x, y, 0.0f, ALTMODE_ABSOLUTE);
+
+    if (p.transformInPlace(_frame.getProfile()->getSRS()))
+    {
+        // find the tile containing the point:
+        for(ElevationPool::QuerySet::const_iterator tile_ref = _tiles.begin();
+            tile_ref != _tiles.end();
+            ++tile_ref)
+        {
+            ElevationPool::Tile* tile = tile_ref->get();
+
+            if (tile->_bounds.contains(p.x(), p.y()))
+            {
+                foundTile = true;
+
+                // Found an intersecting tile; sample the elevation:
+                if (tile->_hf.getElevation(0L, p.x(), p.y(), INTERP_BILINEAR, 0L, out_elevation))
+                {
+                    out_resolution = tile->_hf.getXInterval();
+                    // got it; finished
+                    break;
+                }
+            }
+        }
+
+        // If we didn't find a tile containing the point, we need to ask the clamper
+        // for the tile so we can add it to the query set.
+        if (!foundTile)
+        {
+            TileKey key = _frame.getProfile()->createTileKey(p.x(), p.y(), _lod);
+            osg::ref_ptr<ElevationPool::Tile> tile;
+            if (_pool && _pool->getTile(key, _frame, tile))
+            {
+                // Got the new tile; put it in the query set:
+                _tiles.insert(tile.get());
+
+                // Then sample the elevation:
+                if (tile->_hf.getElevation(0L, p.x(), p.y(), INTERP_BILINEAR, 0L, out_elevation))
+                {
+                    out_resolution = 0.5*(tile->_hf.getXInterval() + tile->_hf.getYInterval());
+                }
+            }
+        }
+    }
+    else
+    {
+        OE_WARN << LC << "sample: xform failed" << std::endl;
+    }
+
+    // push the result, even if it was not found and it's NO_DATA_VALUE
+    return out_elevation != NO_DATA_VALUE;
+}
+
+float
+ElevationEnvelope::getElevation(double x, double y)
+{
+    METRIC_SCOPED("ElevationEnvelope::getElevation");
+    float elevation, resolution;
+    sample(x, y, elevation, resolution);
+    return elevation;
+}
+
+std::pair<float, float>
+ElevationEnvelope::getElevationAndResolution(double x, double y)
+{
+    METRIC_SCOPED("ElevationEnvelope::getElevationAndResolution");
+    float elevation, resolution;
+    sample(x, y, elevation, resolution);
+    return std::make_pair(elevation, resolution);
+}
+
+unsigned
+ElevationEnvelope::getElevations(const std::vector<osg::Vec3d>& input,
+                                 std::vector<float>& output)
+{
+    METRIC_SCOPED_EX("ElevationEnvelope::getElevations", 1, "num", toString(input.size()).c_str());
+
+    unsigned count = 0u;
+
+    output.reserve(input.size());
+    output.clear();
+
+    // for each input point:
+    for (std::vector<osg::Vec3d>::const_iterator v = input.begin(); v != input.end(); ++v)
+    {
+        float elevation, resolution;
+        sample(v->x(), v->y(), elevation, resolution);
+        output.push_back(elevation);
+        if (elevation != NO_DATA_VALUE)
+            ++count;
+    }
+
+    if (count < input.size())
+    {
+        OE_WARN << LC << "Issue: Envelope had failed samples" << std::endl;
+        for (ElevationPool::QuerySet::const_iterator tile_ref = _tiles.begin(); tile_ref != _tiles.end(); ++tile_ref)
+        {
+            ElevationPool::Tile* tile = tile_ref->get();
+            OE_WARN << LC << " ... tile " << tile->_bounds.toString() << std::endl;
+        }
+        OE_WARN << LC << std::endl;
+    }
+
+    return count;
+}
+
+bool
+ElevationEnvelope::getElevationExtrema(const std::vector<osg::Vec3d>& input,
+                                       float& min, float& max)
+{
+    if (input.empty())
+        return false;
+
+    min = FLT_MAX, max = -FLT_MAX;
+
+    osg::Vec3d centroid;
+
+    for (std::vector<osg::Vec3d>::const_iterator v = input.begin(); v != input.end(); ++v)
+    {
+        centroid += *v;
+
+        float elevation, resolution;
+        
+        if (sample(v->x(), v->y(), elevation, resolution))
+        {
+            if (elevation < min) min = elevation;
+            if (elevation > max) max = elevation;
+        }
+    }
+
+    // If none of the feature points clamped, try the feature centroid.
+    // It's possible (but improbable) that the feature encloses the envelope
+    if (min > max)
+    {
+        centroid /= input.size();
+
+        float elevation, resolution;
+        if (sample(centroid.x(), centroid.y(), elevation, resolution))
+        {
+            if (elevation < min) min = elevation;
+            if (elevation > max) max = elevation;
+        }
+    }
+
+    return (min <= max);
+}
+
+const SpatialReference*
+ElevationEnvelope::getSRS() const
+{
+    return _inputSRS.get();
+}
diff --git a/src/osgEarth/ElevationQuery b/src/osgEarth/ElevationQuery
index b3ab47f..f1f3831 100644
--- a/src/osgEarth/ElevationQuery
+++ b/src/osgEarth/ElevationQuery
@@ -20,43 +20,21 @@
 #define OSGEARTH_ELEVATION_QUERY_H 1
 
 #include <osgEarth/MapFrame>
+#include <osgEarth/ElevationPool>
 #include <osgEarth/Containers>
-#include <osgEarth/DPLineSegmentIntersector>
+#include <osgEarth/ModelLayer>
+#include <osgUtil/LineSegmentIntersector>
 
 namespace osgEarth
 {
-    class OSGEARTH_EXPORT ElevationQueryCacheReadCallback : public osgUtil::IntersectionVisitor::ReadCallback
-    {
-        public:
-            ElevationQueryCacheReadCallback();
-
-            void setMaximumNumOfFilesToCache(unsigned int maxNumFilesToCache) { _maxNumFilesToCache = maxNumFilesToCache; }
-            unsigned int  getMaximumNumOfFilesToCache() const { return _maxNumFilesToCache; }
-
-            void clearDatabaseCache();
-
-            void pruneUnusedDatabaseCache();
-
-#if OSG_VERSION_GREATER_OR_EQUAL(3,5,0)
-            virtual osg::ref_ptr<osg::Node> readNodeFile(const std::string& filename);
-#else
-            virtual osg::Node* readNodeFile(const std::string& filename);
-#endif
-
-        protected:
-
-            typedef std::map<std::string, osg::ref_ptr<osg::Node> > FileNameSceneMap;
-
-            unsigned int _maxNumFilesToCache;
-            OpenThreads::Mutex  _mutex;
-            FileNameSceneMap    _filenameSceneMap;
-    };
-
-
     /**
+     * @deprecated - Use ElevationPool instead, via Map::getElevationPool to
+     *    query elevation data, and use Terrain::getHeight to query the in-scene
+     *    terrain graph.
+     *
      * ElevationQuery (EQ) lets you query the elevation at any point on a map.
      *
-     * Rather than intersecting with a loaded scene graph, EQ uses the osgEarth
+     * Rather than intersecting with a loadeEd scene graph, EQ uses the osgEarth
      * engine to directly access the best terrain tile for elevation query. You
      * give it the DEM resolution at which you want an elevation point, and it will
      * access the necessary tile and sample it.
@@ -89,30 +67,31 @@ namespace osgEarth
          */
         ElevationQuery( const MapFrame& mapFrame );
 
-        /** dtor */
-        virtual ~ElevationQuery() { }
+        /**
+         * Sets a new map
+         */
+        void setMap(const Map* map);
 
-        /** Sets a new map frame */
+        /**
+         * Sets a new map frame
+         */
         void setMapFrame(const MapFrame& frame);
 
         /**
-         * Gets the terrain elevation at a point, given a terrain resolution.
+         * Gets the elevation data at a point, given a terrain resolution.
          *
          * @param point
          *      Coordinates for which to query elevation.
-         * @param out_elevation
-         *      Stores the elevation result in this variable upon success.
          * @param desiredResolution
          *      Optimal resolution of elevation data to use for the query (if available).
          *      Pass in 0 (zero) to use the best available resolution.
          * @param out_resolution
          *      Resolution of the resulting elevation value (if the method returns true).
          *
-         * @return True if the query succeeded, false upon failure.
+         * @return Sampled elevation value (or NO_DATA_VALUE)
          */
-        bool getElevation(
+        float getElevation(
             const GeoPoint& point,
-            double&         out_elevation,
             double          desiredResolution    =0.0,
             double*         out_actualResolution =0L );
 
@@ -134,85 +113,38 @@ namespace osgEarth
         bool getElevations(
             const std::vector<osg::Vec3d>& points,
             const SpatialReference*        pointsSRS,
-            std::vector<double>&           out_elevations,
-            double                         desiredResolution = 0.0 );
+            std::vector<float>&            out_elevations,
+            double                         desiredResolution =0.0 );
 
-        /**
-         * Whether a query should fall back on lower resolution data if no results
-         * are available at the requested resolution. Default is true.
-         */
-        void setFallBackOnNoData(bool value) { _fallBackOnNoData = value; }
-        bool getFallBackOnNoData() const { return _fallBackOnNoData; }
-
-        /**
-         * Sets the maximum cache size for elevation tiles.
-         */
-        void setMaxTilesToCache( int value );
-
-        /**
-         * Gets the maximum cache size for elevation tiles.
-         */
-        int getMaxTilesToCache() const;
-
-        /**
-        * Sets the maximum level override for elevation queries.
-        * A value of -1 turns off the override.
-        */
-        void setMaxLevelOverride(int maxLevelOverride);
-
-        /**
-        * Gets the maximum level override for elevation queries.
-        */
-        int getMaxLevelOverride() const;
-
-        /**
-         * Gets the average time per query
-         */
-        double getAverageQueryTime() const { return _queries > 0.0 ? _totalTime/_queries : 0.0; }
-
-        /**
-         * Gets the maximum level of data available at the given point.  If the layers have DataExtents provided they
-         * will be queried.  This allows certain areas on the earth to have higher levels of detail
-         * than others and avoids unnecessary queries where there isn't high resolution data available.
-         * Negative return value means there was no data available at the location.
-         */
-        int getMaxLevel(double x, double y, const SpatialReference* srs, const Profile* profile, unsigned tileSize) const;
+        /** dtor */
+        virtual ~ElevationQuery() { }
 
-        /** Clear the database cache.*/
-        void clearDatabaseCache() { if (_eqcrc.valid()) _eqcrc->clearDatabaseCache(); }
+    private:
+        // Map to query
+        MapFrame _mapf;
 
-        /** Set the ReadCallback that does the reading of external PagedLOD models, and caching of loaded subgraphs.
-          * Note, if you have multiple LineOfSight or HeightAboveTerrain objects in use at one time then you should share a single
-          * DatabaseCacheReadCallback between all of them. */
-        void setElevationQueryCacheReadCallback(ElevationQueryCacheReadCallback* eqcrc);
+        // Map model layers marked as terrain patches
+        std::vector<ModelLayer*> _patchLayers;
 
-        /** Get the ReadCallback that does the reading of external PagedLOD models, and caching of loaded subgraphs.*/
-        ElevationQueryCacheReadCallback* getElevationQueryCacheReadCallback() { return _eqcrc.get(); }
+        // Intersector to query the patch layers
+        osg::ref_ptr<osgUtil::LineSegmentIntersector> _patchLayersLSI;
 
-    private:
-        MapFrame     _mapf;
-        int          _maxLevelOverride;
-        bool         _fallBackOnNoData;
-
-        typedef LRUCache< TileKey, GeoHeightField > TileCache;
-        TileCache _cache;
-        double _queries;
-        double _totalTime;
-        std::vector<ModelLayer*> _patchLayers;
-        osg::ref_ptr<DPLineSegmentIntersector> _patchLayersLSI;
+        // Callback to force the intersector to materialize paged model nodes
+        osg::ref_ptr<osgUtil::IntersectionVisitor::ReadCallback> _ivrc;
 
-        osg::ref_ptr<ElevationQueryCacheReadCallback> _eqcrc;
+        // Active elevation sampler
+        osg::ref_ptr<ElevationEnvelope> _envelope;
 
     private:
-        void postCTOR();
+        void reset();
         void sync();
         void gatherPatchLayers();
 
         bool getElevationImpl(
             const GeoPoint& point,
-            double&         out_elevation,
+            float&          out_elevation,
             double          desiredResolution,
-            double*         out_actualResolution =0L );
+            double*         out_actualResolution );
     };
 
 } // namespace osgEarth
diff --git a/src/osgEarth/ElevationQuery.cpp b/src/osgEarth/ElevationQuery.cpp
index 39d8ea6..81a99a9 100644
--- a/src/osgEarth/ElevationQuery.cpp
+++ b/src/osgEarth/ElevationQuery.cpp
@@ -17,145 +17,57 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>
  */
 #include <osgEarth/ElevationQuery>
-#include <osgEarth/Locators>
-#include <osgEarth/HeightFieldUtils>
-#include <osgEarth/DPLineSegmentIntersector>
+#include <osgUtil/LineSegmentIntersector>
+#include <osgEarth/Map>
+#include <osgEarth/ElevationPool>
 #include <osgUtil/IntersectionVisitor>
+#include <osgSim/LineOfSight>
 
 #define LC "[ElevationQuery] "
 
 using namespace osgEarth;
-using namespace OpenThreads;
 
-namespace
-{
-    int nextPowerOf2(int x) {
-        --x;
-        x |= x >> 1;
-        x |= x >> 2;
-        x |= x >> 4;
-        x |= x >> 8;
-        x |= x >> 16;
-        return x+1;
-    }
-}
 
-ElevationQueryCacheReadCallback::ElevationQueryCacheReadCallback()
+ElevationQuery::ElevationQuery()
 {
-    _maxNumFilesToCache = 2000;
+    reset();
 }
 
-void ElevationQueryCacheReadCallback::clearDatabaseCache()
+ElevationQuery::ElevationQuery(const Map* map)
 {
-    OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_mutex);
-    _filenameSceneMap.clear();
+    setMap(map);
 }
 
-void ElevationQueryCacheReadCallback::pruneUnusedDatabaseCache()
+ElevationQuery::ElevationQuery(const MapFrame& mapFrame)
 {
+    setMapFrame(mapFrame);
 }
 
-#if OSG_VERSION_GREATER_OR_EQUAL(3,5,0)
-osg::ref_ptr<osg::Node> ElevationQueryCacheReadCallback::readNodeFile(const std::string& filename)
-#else
-osg::Node* ElevationQueryCacheReadCallback::readNodeFile(const std::string& filename)
-#endif
-{
-    // first check to see if file is already loaded.
-    {
-        OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_mutex);
-
-        FileNameSceneMap::iterator itr = _filenameSceneMap.find(filename);
-        if (itr != _filenameSceneMap.end())
-        {
-            OSG_INFO<<"Getting from cache "<<filename<<std::endl;
-
-            return itr->second.get();
-        }
-    }
-
-    // now load the file.
-    osg::ref_ptr<osg::Node> node = osgDB::readRefNodeFile(filename);
-
-    // insert into the cache.
-    if (node.valid())
-    {
-        OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_mutex);
-
-        if (_filenameSceneMap.size() < _maxNumFilesToCache)
-        {
-            OSG_INFO<<"Inserting into cache "<<filename<<std::endl;
-
-            _filenameSceneMap[filename] = node;
-        }
-        else
-        {
-            // for time being implement a crude search for a candidate to chuck out from the cache.
-            for(FileNameSceneMap::iterator itr = _filenameSceneMap.begin();
-                itr != _filenameSceneMap.end();
-                ++itr)
-            {
-                if (itr->second->referenceCount()==1)
-                {
-                    OSG_NOTICE<<"Erasing "<<itr->first<<std::endl;
-                    // found a node which is only referenced in the cache so we can discard it
-                    // and know that the actual memory will be released.
-                    _filenameSceneMap.erase(itr);
-                    break;
-                }
-            }
-            OSG_INFO<<"And the replacing with "<<filename<<std::endl;
-            _filenameSceneMap[filename] = node;
-        }
-    }
-
-#if OSG_VERSION_GREATER_OR_EQUAL(3,5,0)
-    return node;
-#else
-    return node.release();
-#endif
-}
-
-ElevationQuery::ElevationQuery()
-{
-    postCTOR();
-}
-
-ElevationQuery::ElevationQuery(const Map* map) :
-_mapf( map, (Map::ModelParts)(Map::TERRAIN_LAYERS | Map::MODEL_LAYERS) )
-{
-    postCTOR();
-}
-
-ElevationQuery::ElevationQuery(const MapFrame& mapFrame) :
-_mapf( mapFrame )
+void
+ElevationQuery::setMap(const Map* map)
 {
-    postCTOR();
+    _mapf.setMap(map);
+    reset();
 }
 
 void
 ElevationQuery::setMapFrame(const MapFrame& frame)
 {
     _mapf = frame;
-    postCTOR();
+    reset();
 }
 
 void
-ElevationQuery::postCTOR()
+ElevationQuery::reset()
 {
-    // defaults:
-    _maxLevelOverride = -1;
-    _queries          = 0.0;
-    _totalTime        = 0.0;
-    _fallBackOnNoData = false;
-    _cache.clear();
-    _cache.setMaxSize( 500 );
-
     // set read callback for IntersectionVisitor
-    setElevationQueryCacheReadCallback(new ElevationQueryCacheReadCallback);
+    _ivrc = new osgSim::DatabaseCacheReadCallback();
 
     // find terrain patch layers.
     gatherPatchLayers();
+
+    // clear any active envelope
+    _envelope = 0L;
 }
 
 void
@@ -164,8 +76,7 @@ ElevationQuery::sync()
     if ( _mapf.needsSync() )
     {
         _mapf.sync();
-        _cache.clear();
-        gatherPatchLayers();
+        reset();
     }
 }
 
@@ -174,8 +85,10 @@ ElevationQuery::gatherPatchLayers()
 {
     // cache a vector of terrain patch models.
     _patchLayers.clear();
-    for(ModelLayerVector::const_iterator i = _mapf.modelLayers().begin();
-        i != _mapf.modelLayers().end();
+    ModelLayerVector modelLayers;
+    _mapf.getLayers(modelLayers);
+    for(ModelLayerVector::const_iterator i = modelLayers.begin();
+        i != modelLayers.end();
         ++i)
     {
         if ( i->get()->isTerrainPatch() )
@@ -183,146 +96,27 @@ ElevationQuery::gatherPatchLayers()
     }
 }
 
-int
-ElevationQuery::getMaxLevel( double x, double y, const SpatialReference* srs, const Profile* profile, unsigned tileSize) const
-{
-    int targetTileSizePOT = nextPowerOf2((int)tileSize);
-
-    int maxLevel = -1;
-
-    for( ElevationLayerVector::const_iterator i = _mapf.elevationLayers().begin(); i != _mapf.elevationLayers().end(); ++i )
-    {
-        const ElevationLayer* layer = i->get();
-
-        // skip disabled layers
-        if ( !layer->getEnabled() || !layer->getVisible() )
-            continue;
-
-        optional<int> layerMaxLevel;
-
-        osgEarth::TileSource* ts = layer->getTileSource();
-        if ( ts )
-        {
-            // TileSource is good; check for optional data extents:
-            if ( ts->getDataExtents().size() > 0 )
-            {
-                osg::Vec3d tsCoord(x, y, 0);
-
-                const SpatialReference* tsSRS = ts->getProfile() ? ts->getProfile()->getSRS() : 0L;
-                if ( srs && tsSRS )
-                    srs->transform(tsCoord, tsSRS, tsCoord);
-                else
-                    tsSRS = srs;
-
-                for (osgEarth::DataExtentList::iterator j = ts->getDataExtents().begin(); j != ts->getDataExtents().end(); j++)
-                {
-                    if (j->maxLevel().isSet() &&
-                        (!layerMaxLevel.isSet() || j->maxLevel() > layerMaxLevel.get() )
-                        && j->contains( tsCoord.x(), tsCoord.y(), tsSRS ))
-                    {
-                        layerMaxLevel = j->maxLevel().value();
-                    }
-                }
-            }
-            else
-            {
-                // Just use the default max level.  Without any data extents we don't know the actual max
-                layerMaxLevel = (int)(*layer->getTerrainLayerRuntimeOptions().maxLevel());
-            }
-
-            // cap the max to the layer's express max level (if set).
-            if ( layerMaxLevel.isSet() && layer->getTerrainLayerRuntimeOptions().maxLevel().isSet() )
-            {
-                layerMaxLevel = std::min( layerMaxLevel.get(), (int)(*layer->getTerrainLayerRuntimeOptions().maxLevel()) );
-            }
-
-            // Need to convert the layer max of this TileSource to that of the actual profile
-            if ( layerMaxLevel.isSet() )
-            {
-                layerMaxLevel = profile->getEquivalentLOD( ts->getProfile(), layerMaxLevel.get() );
-            }
-        }
-        else
-        {
-            // no TileSource? probably in cache-only mode. Use the layer max (or its default).
-            layerMaxLevel = (int)(layer->getTerrainLayerRuntimeOptions().maxLevel().value());
-        }
 
-        // Adjust for the tile size resolution differential, if supported by the layer.
-        if ( layerMaxLevel.isSet() )
-        {
-			// TODO:  This native max resolution of the layer has already been computed here.
-			//        The following block attempts to compute a higher resolution to undo the resolution
-			//        mapping that populateHeightField will eventually do.  So for example, you might compute a maximum level of
-			//        10 here, and this will adjust it to 14 with the knowledge that populateHeightField will adjust the 14 back to 10.
-			//        The use of populateHeightField needs to be replaced by code that just works with the native resolution of the
-			//        layers instead.
-#if 1
-            int layerTileSize = layer->getTileSize();
-            if (layerTileSize > targetTileSizePOT)
-            {
-                int temp = std::max(targetTileSizePOT, 2);
-                while(temp < layerTileSize)
-                {
-                    temp *= 2;
-                    layerMaxLevel = layerMaxLevel.get() + 1;
-                }
-            }
-#endif
-
-            if (layerMaxLevel > maxLevel)
-            {
-                maxLevel = layerMaxLevel.get();
-            }
-        }
-    }
-
-    return maxLevel;
-}
-
-
-void
-ElevationQuery::setMaxTilesToCache( int value )
-{
-    _cache.setMaxSize( value );
-}
-
-int
-ElevationQuery::getMaxTilesToCache() const
-{
-    return _cache.getMaxSize();
-}
-
-void
-ElevationQuery::setMaxLevelOverride(int maxLevelOverride)
-{
-    _maxLevelOverride = maxLevelOverride;
-}
-
-int
-ElevationQuery::getMaxLevelOverride() const
+float
+ElevationQuery::getElevation(const GeoPoint& point,
+                             double          desiredResolution,
+                             double*         out_actualResolution)
 {
-    return _maxLevelOverride;
-}
+    float result = NO_DATA_VALUE;
 
-bool
-ElevationQuery::getElevation(const GeoPoint&         point,
-                             double&                 out_elevation,
-                             double                  desiredResolution,
-                             double*                 out_actualResolution)
-{
     sync();
     if ( point.altitudeMode() == ALTMODE_ABSOLUTE )
     {
-        return getElevationImpl( point, out_elevation, desiredResolution, out_actualResolution );
+        getElevationImpl( point, result, desiredResolution, out_actualResolution );
     }
     else
     {
         GeoPoint point_abs( point.getSRS(), point.x(), point.y(), 0.0, ALTMODE_ABSOLUTE );
-        return getElevationImpl( point_abs, out_elevation, desiredResolution, out_actualResolution );
+        getElevationImpl( point_abs, result, desiredResolution, out_actualResolution );
     }
-}
 
+    return result;
+}
 
 bool
 ElevationQuery::getElevations(std::vector<osg::Vec3d>& points,
@@ -333,11 +127,16 @@ ElevationQuery::getElevations(std::vector<osg::Vec3d>& points,
     sync();
     for( osg::Vec3dArray::iterator i = points.begin(); i != points.end(); ++i )
     {
-        double elevation;
+        float elevation;
         double z = (*i).z();
         GeoPoint p(pointsSRS, *i, ALTMODE_ABSOLUTE);
-        if ( getElevationImpl( p, elevation, desiredResolution ) )
+        if ( getElevationImpl(p, elevation, desiredResolution, 0L))
         {
+            if (elevation == NO_DATA_VALUE)
+            {
+                elevation = 0.0;
+            }
+
             (*i).z() = ignoreZ ? elevation : elevation + z;
         }
     }
@@ -347,16 +146,16 @@ ElevationQuery::getElevations(std::vector<osg::Vec3d>& points,
 bool
 ElevationQuery::getElevations(const std::vector<osg::Vec3d>& points,
                               const SpatialReference*        pointsSRS,
-                              std::vector<double>&           out_elevations,
+                              std::vector<float>&            out_elevations,
                               double                         desiredResolution )
 {
     sync();
     for( osg::Vec3dArray::const_iterator i = points.begin(); i != points.end(); ++i )
     {
-        double elevation;
+        float elevation;
         GeoPoint p(pointsSRS, *i, ALTMODE_ABSOLUTE);
 
-        if ( getElevationImpl(p, elevation, desiredResolution) )
+        if ( getElevationImpl(p, elevation, desiredResolution, 0L) )
         {
             out_elevations.push_back( elevation );
         }
@@ -369,8 +168,8 @@ ElevationQuery::getElevations(const std::vector<osg::Vec3d>& points,
 }
 
 bool
-ElevationQuery::getElevationImpl(const GeoPoint& point, /* abs */
-                                 double&         out_elevation,
+ElevationQuery::getElevationImpl(const GeoPoint& point,
+                                 float&          out_elevation,
                                  double          desiredResolution,
                                  double*         out_actualResolution)
 {
@@ -388,8 +187,8 @@ ElevationQuery::getElevationImpl(const GeoPoint& point, /* abs */
     {
         osgUtil::IntersectionVisitor iv;
 
-        if ( _eqcrc.valid() )
-            iv.setReadCallback(_eqcrc);
+        if ( _ivrc.valid() )
+            iv.setReadCallback(_ivrc.get());
 
         for(std::vector<ModelLayer*>::iterator i = _patchLayers.begin(); i != _patchLayers.end(); ++i)
         {
@@ -413,7 +212,7 @@ ElevationQuery::getElevationImpl(const GeoPoint& point, /* abs */
                     // first time through, set up the intersector on demand
                     if ( !_patchLayersLSI.valid() )
                     {
-                        _patchLayersLSI = new DPLineSegmentIntersector(start, end);
+                        _patchLayersLSI = new osgUtil::LineSegmentIntersector(start, end);
                         _patchLayersLSI->setIntersectionLimit( _patchLayersLSI->LIMIT_NEAREST );
                     }
                     else
@@ -435,7 +234,7 @@ ElevationQuery::getElevationImpl(const GeoPoint& point, /* abs */
                         // transform back to input SRS:
                         GeoPoint output;
                         output.fromWorld( point.getSRS(), isect );
-                        out_elevation = output.z();
+                        out_elevation = (float)output.z();
                         if ( out_actualResolution )
                             *out_actualResolution = 0.0;
 
@@ -453,118 +252,43 @@ ElevationQuery::getElevationImpl(const GeoPoint& point, /* abs */
     if ( _mapf.elevationLayers().empty() )
     {
         // this means there are no heightfields.
-        out_elevation = 0.0;
+        out_elevation = NO_DATA_VALUE;
         return true;
     }
 
     // tile size (resolution of elevation tiles)
-    unsigned tileSize = 33; // ???
+    unsigned tileSize = 257; // yes?
 
-    // This is the max resolution that we actually have data at this point
-    int bestAvailLevel = getMaxLevel( point.x(), point.y(), point.getSRS(), _mapf.getProfile(), tileSize );
-
-    // A negative value means that no data is avaialble at that point at any resolution.
-    if ( bestAvailLevel < 0 )
-    {
-        return false;
-    }
+    // default LOD:
+    unsigned lod = 23u;
 
+    // attempt to map the requested resolution to an LOD:
     if (desiredResolution > 0.0)
     {
-        int desiredLevel = _mapf.getProfile()->getLevelOfDetailForHorizResolution( desiredResolution, tileSize );
-        if (desiredLevel < bestAvailLevel)
-        {
-            bestAvailLevel = desiredLevel;
-        }
+        int level = _mapf.getProfile()->getLevelOfDetailForHorizResolution(desiredResolution, tileSize);
+        if ( level > 0 )
+            lod = level;
     }
 
-    //OE_NOTICE << LC << "Best available data level " << point.x() << ", " << point.y() << " = "  << bestAvailLevel << std::endl;
-
-    // transform the input coords to map coords:
-    GeoPoint mapPoint = point;
-    if ( point.isValid() && !point.getSRS()->isHorizEquivalentTo( _mapf.getProfile()->getSRS() ) )
-    {
-        mapPoint = point.transform(_mapf.getProfile()->getSRS());
-        if ( !mapPoint.isValid() )
-        {
-            OE_WARN << LC << "Fail: coord transform failed" << std::endl;
-            return false;
-        }
+    // do we need a new ElevationEnvelope?
+    if (!_envelope.valid() ||
+        !point.getSRS()->isHorizEquivalentTo(_envelope->getSRS()) ||
+        lod != _envelope->getLOD())
+    {        
+        _envelope = _mapf.getElevationPool()->createEnvelope(point.getSRS(), lod);
     }
 
-    // get the tilekey corresponding to the tile we need:
-    TileKey key = _mapf.getProfile()->createTileKey( mapPoint.x(), mapPoint.y(), bestAvailLevel );
-    if ( !key.valid() )
+    // sample the elevation, and if requested, the resolution as well:
+    if (out_actualResolution)
     {
-        OE_WARN << LC << "Fail: coords fall outside map" << std::endl;
-        return false;
+        std::pair<float, float> result = _envelope->getElevationAndResolution(point.x(), point.y());
+        out_elevation = result.first;
+        *out_actualResolution = result.second;
     }
-
-    bool result = false;
-
-    while ( !result && key.valid() )
+    else
     {
-        GeoHeightField geoHF;
-
-        // Try to get the hf from the cache
-        TileCache::Record record;
-        if ( _cache.get( key, record ) )
-        {
-            geoHF = record.value();
-        }
-        else
-        {
-            // Create it
-            osg::ref_ptr<osg::HeightField> hf = new osg::HeightField();
-            hf->allocate( tileSize, tileSize );
-
-            // Initialize the heightfield to nodata
-            hf->getFloatArray()->assign( hf->getFloatArray()->size(), NO_DATA_VALUE );
-
-            if (_mapf.populateHeightField(hf, key, false /*heightsAsHAE*/, 0L))
-            {
-                geoHF = GeoHeightField( hf.get(), key.getExtent() );
-                _cache.insert( key, geoHF );
-            }
-        }
-
-        if (geoHF.valid())
-        {
-            float elevation = 0.0f;
-            result = geoHF.getElevation( mapPoint.getSRS(), mapPoint.x(), mapPoint.y(), _mapf.getMapInfo().getElevationInterpolation(), mapPoint.getSRS(), elevation);
-            if (result && elevation != NO_DATA_VALUE)
-            {
-                out_elevation = (double)elevation;
-
-                // report the actual resolution of the heightfield.
-                if ( out_actualResolution )
-                    *out_actualResolution = geoHF.getXInterval();
-            }
-            else
-            {
-                result = false;
-            }
-        }
-
-        if ( geoHF.valid() && !_fallBackOnNoData )
-        {
-            break;
-        }
-        else if ( result == false )
-        {
-            key = key.createParentKey();
-        }
+        out_elevation = _envelope->getElevation(point.x(), point.y());
     }
 
-
-    osg::Timer_t end = osg::Timer::instance()->tick();
-    _queries++;
-    _totalTime += osg::Timer::instance()->delta_s( begin, end );
-
-    return result;
-}
-
-void ElevationQuery::setElevationQueryCacheReadCallback(ElevationQueryCacheReadCallback* eqcrc)
-{
-    _eqcrc = eqcrc;
+    return out_elevation != NO_DATA_VALUE;
 }
diff --git a/src/osgEarth/Endian b/src/osgEarth/Endian
new file mode 100644
index 0000000..cae9478
--- /dev/null
+++ b/src/osgEarth/Endian
@@ -0,0 +1,178 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+ * Copyright 2016 Pelican Mapping
+ * http://osgearth.org
+ *
+ * osgEarth is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+
+// "License": Public Domain
+// I, Mathias Panzenb�ck, place this file hereby into the public domain. Use it at your own risk for whatever you like.
+// In case there are jurisdictions that don't support putting things in the public domain you can also consider it to
+// be "dual licensed" under the BSD, MIT and Apache licenses, if you want to. This code is trivial anyway. Consider it
+// an example on how to get the endian conversion functions on different platforms.
+
+#ifndef OSGEARTH_PORTABLE_ENDIAN_H__
+#define OSGEARTH_PORTABLE_ENDIAN_H__
+
+#if (defined(_WIN16) || defined(_WIN32) || defined(_WIN64)) && !defined(__WINDOWS__)
+
+#   include <stdint.h>
+
+#	define __WINDOWS__
+
+#endif
+
+#if defined(__linux__) || defined(__CYGWIN__)
+
+#	include <endian.h>
+
+#elif defined(__APPLE__)
+
+#	include <libkern/OSByteOrder.h>
+
+#	define htobe16(x) OSSwapHostToBigInt16(x)
+#	define htole16(x) OSSwapHostToLittleInt16(x)
+#	define be16toh(x) OSSwapBigToHostInt16(x)
+#	define le16toh(x) OSSwapLittleToHostInt16(x)
+ 
+#	define htobe32(x) OSSwapHostToBigInt32(x)
+#	define htole32(x) OSSwapHostToLittleInt32(x)
+#	define be32toh(x) OSSwapBigToHostInt32(x)
+#	define le32toh(x) OSSwapLittleToHostInt32(x)
+ 
+#	define htobe64(x) OSSwapHostToBigInt64(x)
+#	define htole64(x) OSSwapHostToLittleInt64(x)
+#	define be64toh(x) OSSwapBigToHostInt64(x)
+#	define le64toh(x) OSSwapLittleToHostInt64(x)
+
+#	define __BYTE_ORDER    BYTE_ORDER
+#	define __BIG_ENDIAN    BIG_ENDIAN
+#	define __LITTLE_ENDIAN LITTLE_ENDIAN
+#	define __PDP_ENDIAN    PDP_ENDIAN
+
+#elif defined(__OpenBSD__)
+
+#	include <sys/endian.h>
+
+#elif defined(__NetBSD__) || defined(__FreeBSD__) || defined(__DragonFly__)
+
+#	include <sys/endian.h>
+
+#	define be16toh(x) betoh16(x)
+#	define le16toh(x) letoh16(x)
+
+#	define be32toh(x) betoh32(x)
+#	define le32toh(x) letoh32(x)
+
+#	define be64toh(x) betoh64(x)
+#	define le64toh(x) letoh64(x)
+
+#elif defined(__WINDOWS__)
+
+#	include <windows.h>
+
+#	if BYTE_ORDER == LITTLE_ENDIAN
+
+#		define htobe16(x) _byteswap_ushort(x)
+#		define htole16(x) (x)
+#		define be16toh(x) _byteswap_ushort(x)
+#		define le16toh(x) (x)
+ 
+#		define htobe32(x) _byteswap_ulong(x)
+#		define htole32(x) (x)
+#		define be32toh(x) _byteswap_ulong(x)
+#		define le32toh(x) (x)
+
+#		if defined(htonll) && defined(ntohll)
+#			define htobe64(x) htonll(x)
+#			define htole64(x) (x)
+#			define be64toh(x) ntohll(x)
+#			define le64toh(x) (x)
+#		else
+#			define htobe64(x) _byteswap_uint64(x)
+#			define htole64(x) (x)
+#			define be64toh(x) _byteswap_uint64(x)
+#			define le64toh(x) (x)
+#		endif
+
+#	elif BYTE_ORDER == BIG_ENDIAN
+
+		/* that would be xbox 360 */
+#		define htobe16(x) (x)
+#		define htole16(x) __builtin_bswap16(x)
+#		define be16toh(x) (x)
+#		define le16toh(x) __builtin_bswap16(x)
+ 
+#		define htobe32(x) (x)
+#		define htole32(x) __builtin_bswap32(x)
+#		define be32toh(x) (x)
+#		define le32toh(x) __builtin_bswap32(x)
+ 
+#		define htobe64(x) (x)
+#		define htole64(x) __builtin_bswap64(x)
+#		define be64toh(x) (x)
+#		define le64toh(x) __builtin_bswap64(x)
+
+#	else
+
+#		error byte order not supported
+
+#	endif
+
+#	define __BYTE_ORDER    BYTE_ORDER
+#	define __BIG_ENDIAN    BIG_ENDIAN
+#	define __LITTLE_ENDIAN LITTLE_ENDIAN
+#	define __PDP_ENDIAN    PDP_ENDIAN
+
+#else
+
+//#	error platform not supported
+#   define htobe16(X) X
+#   define htobe32(X) X
+#   define htobe64(X) X
+#   define be16toh(X) X
+#   define be32toh(X) X
+#   define be64toh(X) X
+
+#endif
+
+#define OE_ENCODE_SHORT(X)  htobe16(X)
+#define OE_ENCODE_INT(X)    htobe32(X)
+#define OE_ENCODE_LONG(X)   htobe32(X)
+
+inline uint32_t OE_ENCODE_FLOAT(float x) {
+    return htobe32(*(uint32_t*)(&x));
+}
+inline uint64_t OE_ENCODE_DOUBLE(double x) {
+    return htobe64(*(uint64_t*)(&x));
+}
+
+#define OE_DECODE_SHORT(X)  be16toh(X)
+#define OE_DECODE_INT(X)    be32toh(X)
+#define OE_DECODE_LONG(X)   be32toh(X)
+
+inline float OE_DECODE_FLOAT(uint32_t x) {
+    uint32_t temp = be32toh(x);
+    float r = *(float*)(&temp);
+    return r;
+}
+inline double OE_DECODE_DOUBLE(uint64_t x) {
+    uint64_t temp = be64toh(x);
+    double r = *(double*)(&temp);
+    return r;
+}
+
+
+#endif // OSGEARTH_PORTABLE_ENDIAN_H__
diff --git a/src/osgEarth/Export b/src/osgEarth/Export
index c793f99..3497808 100644
--- a/src/osgEarth/Export
+++ b/src/osgEarth/Export
@@ -52,6 +52,9 @@
     #  define OSGEARTH_EXPORT
 #endif  
 
+// package-portected indicator:
+#define OSGEARTH_INTERNAL
+
 // set up define for whether member templates are supported by VisualStudio compilers.
 #ifdef _MSC_VER
 # if (_MSC_VER >= 1300)
diff --git a/src/osgEarth/Extension b/src/osgEarth/Extension
index f4acc13..c3b7321 100644
--- a/src/osgEarth/Extension
+++ b/src/osgEarth/Extension
@@ -22,6 +22,7 @@
 
 #include <osgEarth/Common>
 #include <osgEarth/Config>
+#include <osgEarth/PluginLoader>
 #include <osg/Object>
 #include <osgDB/Options>
 #include <osgDB/ReaderWriter>
@@ -134,62 +135,10 @@ namespace osgEarth
     CLASS (const CLASS & rhs, const osg::CopyOp& op) { } \
     virtual ~ CLASS() { }
 
-    
-    template<class T>
-    class RegisterExtensionLoader
-    {
-        public:
-            RegisterExtensionLoader(const std::string& name)
-            {
-                if (osgDB::Registry::instance())
-                {
-                    _rw = new T(name);
-                    osgDB::Registry::instance()->addReaderWriter(_rw.get());
-                }
-            }
-
-            ~RegisterExtensionLoader()
-            {
-                if (osgDB::Registry::instance())
-                {
-                    osgDB::Registry::instance()->removeReaderWriter(_rw.get());
-                }
-            }
-
-            T* get() { return _rw.get(); }
-
-        protected:
-            osg::ref_ptr<T> _rw;
-    };
-
-    /**
-     * This template declares and registers an extension-loading plugin.
-     */
-    template<typename T>
-    class ExtensionLoader : public osgDB::ReaderWriter
-    {
-    public: // Plugin stuff
-        ExtensionLoader(const std::string& name) {
-            supportsExtension( name, name );
-        }
-
-        virtual ~ExtensionLoader() { }
-
-        ReadResult readObject(const std::string& filename, const osgDB::Options* dbOptions) const
-        {
-          if ( !acceptsExtension(osgDB::getLowerCaseFileExtension(filename)) )
-                return ReadResult::FILE_NOT_HANDLED;
-
-          return ReadResult( new T(Extension::getConfigOptions(dbOptions)) );
-        }
-    };
-
 } // namespace osgEarth
 
-
 #define REGISTER_OSGEARTH_EXTENSION(NAME,CLASS) \
     extern "C" void osgdb_##NAME(void) {} \
-    static osgEarth::RegisterExtensionLoader< osgEarth::ExtensionLoader<CLASS> > g_proxy_##CLASS_##NAME( #NAME );
-
+    static osgEarth::RegisterPluginLoader< osgEarth::PluginLoader<CLASS, osgEarth::Extension> > g_proxy_##CLASS_##NAME( #NAME );
 
 #endif
diff --git a/src/osgEarth/FadeEffect b/src/osgEarth/FadeEffect
index ec558f9..33b38a9 100644
--- a/src/osgEarth/FadeEffect
+++ b/src/osgEarth/FadeEffect
@@ -46,7 +46,7 @@ namespace osgEarth
         const optional<float>& maxRange() const { return _maxRange; }
 
         /** Distance over which to fade out geometry (from max range) */
-        optional<float>& attentuationDistance() { return _attenDist; }
+        optional<float>& attenuationDistance() { return _attenDist; }
         const optional<float>& attenuationDistance() const { return _attenDist; }
 
     public:
diff --git a/src/osgEarth/FadeEffect.cpp b/src/osgEarth/FadeEffect.cpp
index a32d0fb..f987e0f 100644
--- a/src/osgEarth/FadeEffect.cpp
+++ b/src/osgEarth/FadeEffect.cpp
@@ -60,7 +60,7 @@ namespace
         "uniform float oe_fadeeffect_attenDist; \n"
         "uniform float osg_FrameTime; \n"
 
-        "varying float oe_fadeeffect_opacity; \n"
+        "out float oe_fadeeffect_opacity; \n"
 
         "void oe_vertFadeEffect(inout vec4 VertexView) \n"
         "{ \n"
@@ -73,7 +73,7 @@ namespace
         "#version " GLSL_VERSION_STR "\n"
         GLSL_DEFAULT_PRECISION_FLOAT "\n"
 
-        "varying float oe_fadeeffect_opacity; \n"
+        "in float oe_fadeeffect_opacity; \n"
 
         "void oe_fragFadeEffect( inout vec4 color ) \n"
         "{ \n"
@@ -165,7 +165,7 @@ namespace
         "#version " GLSL_VERSION_STR "\n"
         GLSL_DEFAULT_PRECISION_FLOAT "\n"
 
-        "varying float oe_FadeLOD_opacity; \n"
+        "in float oe_FadeLOD_opacity; \n"
         "void oe_fragFadeLOD( inout vec4 color ) \n"
         "{ \n"
         "    color.a *= oe_FadeLOD_opacity; \n"
diff --git a/src/osgEarth/FileUtils.cpp b/src/osgEarth/FileUtils.cpp
index f556c83..3ff4902 100644
--- a/src/osgEarth/FileUtils.cpp
+++ b/src/osgEarth/FileUtils.cpp
@@ -20,6 +20,7 @@
 #include <osgEarth/FileUtils>
 #include <osgEarth/StringUtils>
 #include <osgEarth/DateTime>
+#include <osgEarth/ThreadingUtils>
 #include <osgDB/FileUtils>
 #include <osgDB/FileNameUtils>
 #include <osgDB/Registry>
@@ -158,63 +159,99 @@ bool osgEarth::isRelativePath(const std::string& fileName)
 
 std::string osgEarth::getFullPath(const std::string& relativeTo, const std::string &relativePath)
 {
+    // A cache, since this method uses osgDB::getRealPath which can be quite slow.
+    static Threading::Mutex s_cacheMutex;
+    typedef std::map<std::string, std::string> PathCache;
+    static PathCache s_cache;
+    //static float tries = 0, hits = 0;
+
+    std::string cacheKey = relativeTo + "&" + relativePath;
+
+    Threading::ScopedMutexLock lock(s_cacheMutex);
+
+    //tries += 1.0f;
+
+    PathCache::const_iterator i = s_cache.find(cacheKey);
+    if (i != s_cache.end())
+    {
+        //hits += 1.0f;
+        //OE_INFO << "size=" << s_cache.size() <<  " tries=" << tries << " hits=" << (100.*hits/tries) << std::endl;
+        return i->second;
+    }
+
+    // prevent the cache from growing unbounded
+    if (s_cache.size() >= 20000)
+        s_cache.clear();
+
+    // result that will go into the cache:
+    std::string result;
+
 	if (!isRelativePath(relativePath) || relativeTo.empty())
     {
         //OE_NOTICE << relativePath << " is not a relative path " << std::endl;
-        return relativePath;
+        result = relativePath;
     }
 
     //If they didn't specify a relative path, just return the relativeTo
-    if (relativePath.empty()) return relativeTo;
-
-
-    //Note:  Modified from VPB
+    else if (relativePath.empty())
+    {
+        result = relativeTo;
+    }
 
-    //Concatinate the paths together
-    std::string filename;
-    if ( !osgDB::containsServerAddress( relativeTo ) )
-        filename = osgDB::concatPaths( osgDB::getFilePath( osgDB::getRealPath( relativeTo )), relativePath);
     else
-        filename = osgDB::concatPaths( osgDB::getFilePath( relativeTo ), relativePath);
+    {
+        //Note:  Modified from VPB
+
+        //Concatinate the paths together
+        std::string filename;
+        if ( !osgDB::containsServerAddress( relativeTo ) )
+            filename = osgDB::concatPaths( osgDB::getFilePath( osgDB::getRealPath( relativeTo )), relativePath);
+        else
+            filename = osgDB::concatPaths( osgDB::getFilePath( relativeTo ), relativePath);
 
 
-    std::list<std::string> directories;
-    int start = 0;
-    for (unsigned int i = 0; i < filename.size(); ++i)
-    {
-        if (filename[i] == '\\' || filename[i] == '/')
+        std::list<std::string> directories;
+        int start = 0;
+        for (unsigned int i = 0; i < filename.size(); ++i)
         {
-            //Get the current directory
-            std::string dir = filename.substr(start, i-start);
-
-            if (dir != "..")
+            if (filename[i] == '\\' || filename[i] == '/')
             {
-                if (dir != ".")
+                //Get the current directory
+                std::string dir = filename.substr(start, i-start);
+
+                if (dir != "..")
                 {
-                  directories.push_back(dir);
+                    if (dir != ".")
+                    {
+                      directories.push_back(dir);
+                    }
                 }
+                else if (!directories.empty())
+                {
+                    directories.pop_back();
+                }
+                start = i + 1;
             }
-            else if (!directories.empty())
-            {
-                directories.pop_back();
-            }
-            start = i + 1;
         }
-    }
 
-    std::string path;
-    for (std::list<std::string>::iterator itr = directories.begin();
-         itr != directories.end();
-         ++itr)
-    {
-        path += *itr;
-        path += "/";
-    }
+        std::string path;
+        for (std::list<std::string>::iterator itr = directories.begin();
+             itr != directories.end();
+             ++itr)
+        {
+            path += *itr;
+            path += "/";
+        }
+
+        path += filename.substr(start, std::string::npos);
 
-    path += filename.substr(start, std::string::npos);
+        //OE_NOTICE << "FullPath " << path << std::endl;
+        result = path;
+    }
 
-    //OE_NOTICE << "FullPath " << path << std::endl;
-    return path;
+    // cache the result and return it.
+    s_cache[cacheKey] = result;
+    return result;
 }
 
 bool
diff --git a/src/osgEarth/GLSLChunker.cpp b/src/osgEarth/GLSLChunker.cpp
index b778f41..655f517 100644
--- a/src/osgEarth/GLSLChunker.cpp
+++ b/src/osgEarth/GLSLChunker.cpp
@@ -149,7 +149,7 @@ GLSLChunker::read(const std::string& input, Chunks& output) const
                     // if we were in a statement, the precense of an open-brace converts it to a FUNCTION
                     // unless we can detect that it's a struct.
                     if (type == Chunk::TYPE_STATEMENT) {
-                        if (tokens.empty() || tokens[0] != "struct") {
+                        if (tokens.empty() || (tokens[0] != "struct" && tokens[0].substr(0,6) != "layout")) {
                             type = Chunk::TYPE_FUNCTION;
                         }
                     }
diff --git a/src/osgEarth/GPUClamping.frag.glsl b/src/osgEarth/GPUClamping.frag.glsl
index 201178a..4a3ef16 100644
--- a/src/osgEarth/GPUClamping.frag.glsl
+++ b/src/osgEarth/GPUClamping.frag.glsl
@@ -1,4 +1,5 @@
 #version $GLSL_VERSION_STR
+$GLSL_DEFAULT_PRECISION_FLOAT
 
 #pragma vp_entryPoint oe_clamp_fragment
 #pragma vp_location   fragment_coloring
@@ -9,4 +10,4 @@ void oe_clamp_fragment(inout vec4 color)
 {
     // adjust the alpha component to "hide" geometry beyond the visible horizon.
     color.a *= oe_clamp_alpha;
-}
\ No newline at end of file
+}
diff --git a/src/osgEarth/GPUClamping.vert.glsl b/src/osgEarth/GPUClamping.vert.glsl
index c744e88..4a2c3fe 100644
--- a/src/osgEarth/GPUClamping.vert.glsl
+++ b/src/osgEarth/GPUClamping.vert.glsl
@@ -1,25 +1,86 @@
 #version $GLSL_VERSION_STR
+$GLSL_DEFAULT_PRECISION_FLOAT
 
 #pragma vp_entryPoint oe_clamp_vertex
 #pragma vp_location   vertex_view
 #pragma vp_order      0.5
-
+#pragma import_defines(OE_CLAMP_HAS_ATTRIBUTES, OE_GPU_LINES)
 #pragma include GPUClamping.vert.lib.glsl
 
+#ifdef OE_CLAMP_HAS_ATTRIBUTES
 in vec4 oe_clamp_attrs;     // vertex attribute
 in float oe_clamp_height;   // vertex attribute
+#endif
+
+#ifdef OE_GPU_LINES
+in vec3 oe_GPULines_prev;
+in vec3 oe_GPULines_next;
+vec4 oe_GPULines_prevViewClamped;
+vec4 oe_GPULines_nextViewClamped;
+#endif
 
 out float oe_clamp_alpha;
 
-uniform bool oe_clamp_hasAttrs;
 uniform bool oe_isGeocentric;
 uniform float oe_clamp_altitudeOffset;
 uniform float oe_clamp_horizonDistance2;
 
-void oe_clamp_vertex(inout vec4 vertexView)
+
+void oe_clamp_clampViewSpaceVertex(inout vec4 vertexView)
 {
-    const float ClampToAnchor = 1.0;
+#ifdef OE_CLAMP_HAS_ATTRIBUTES
+    bool relativeToAnchor = (oe_clamp_attrs.a == 1.0); // 1.0 = ClampToAnchor
+    float verticalOffset = oe_clamp_attrs.z;
+    float clampHeight = oe_clamp_height;
+
+    // if we are using the anchor point, xform it into view space to prepare
+    // for clamping. Force Z=0 for anchoring.
+    vec4 pointToClamp = relativeToAnchor ?
+        gl_ModelViewMatrix * vec4(oe_clamp_attrs.xy, 0.0, 1.0) :
+        vertexView;
+#else
+    bool relativeToAnchor = false;
+    float verticalOffset = 0.0;
+    vec4 pointToClamp = vertexView;
+    float clampHeight = 0.0;
+#endif
 
+    // clamp the point and remember it's depth:
+    vec4  clampedPoint;
+    float depth;
+    oe_getClampedViewVertex(pointToClamp, clampedPoint, depth);
+
+    float dh = verticalOffset + oe_clamp_altitudeOffset;
+
+    if (relativeToAnchor)
+    {
+        // if we are clamping relative to the anchor point, adjust the HAT based on the
+        // distance from the anchor point to the terrain. Since distance() is unsigned,
+        // we use the vector dot product to calculate whether to adjust up or down.
+        float dist = distance(pointToClamp, clampedPoint);
+        float dir = sign(dot(clampedPoint - pointToClamp, vertexView - pointToClamp));
+        dh += (dist * dir);
+    }
+    else
+    {
+        // if we are clamping to the terrain, the vertex becomes the
+        // clamped point
+        vertexView.xyz = clampedPoint.xyz;
+        dh += clampHeight;
+    }
+
+    // calculate the up vector along which clamping will occur (in either direction)
+    vec3 up;
+    oe_getClampingUpVector(up);
+    vertexView.xyz += up*dh;
+
+    // if the clamped depth value is near the far plane, suppress drawing
+    // to avoid rendering anomalies.
+    oe_clamp_alpha = 1.0 - step(0.9999, depth);
+}
+
+void oe_clamp_vertex(inout vec4 vertexView)
+{
     // check distance; alpha out if its beyone the horizon distance.
     oe_clamp_alpha = oe_isGeocentric ? 
         clamp(oe_clamp_horizonDistance2 - (vertexView.z*vertexView.z), 0.0, 1.0) :
@@ -29,47 +90,14 @@ void oe_clamp_vertex(inout vec4 vertexView)
     // note: no branch divergence in the vertex shader
     if ( oe_clamp_alpha > 0.0 )
     {
-        bool relativeToAnchor = oe_clamp_hasAttrs && (oe_clamp_attrs.a == ClampToAnchor);
-
-        float verticalOffset = oe_clamp_hasAttrs ? oe_clamp_attrs.z : 0.0;
-
-        // if we are using the anchor point, xform it into view space to prepare
-        // for clamping. Force Z=0 for anchoring.
-        vec4 pointToClamp = relativeToAnchor?
-            gl_ModelViewMatrix * vec4(oe_clamp_attrs.xy, 0.0, 1.0) :
-            vertexView;
-
-        // find the clamped point.
-        vec4  clampedPoint;
-        float depth;
-        oe_getClampedViewVertex(pointToClamp, clampedPoint, depth);
-
-        float dh = verticalOffset + oe_clamp_altitudeOffset;
-
-        if ( relativeToAnchor )
-        {
-            // if we are clamping relative to the anchor point, adjust the HAT based on the
-            // distance from the anchor point to the terrain. Since distance() is unsigned,
-            // we use the vector dot product to calculate whether to adjust up or down.
-            float dist = distance(pointToClamp, clampedPoint);
-            float dir  = sign(dot(clampedPoint-pointToClamp, vertexView-pointToClamp));
-            dh += (dist * dir);
-        }
-        else
-        {
-            // if we are clamping to the terrain, the vertex becomes the
-            // clamped point
-            vertexView.xyz = clampedPoint.xyz;
-            dh += oe_clamp_height;
-        }
-        
-        // calculate the up vector along which clamping will occur (in either direction)
-        vec3 up;
-        oe_getClampingUpVector( up );
-        vertexView.xyz += up*dh;
-
-        // if the clamped depth value is near the far plane, suppress drawing
-        // to avoid rendering anomalies.
-        oe_clamp_alpha = 1.0-step(0.9999, depth);
+        oe_clamp_clampViewSpaceVertex(vertexView);
+
+#ifdef OE_GPU_LINES
+        oe_GPULines_prevViewClamped = gl_ModelViewMatrix * vec4(oe_GPULines_prev, 1.0);
+        oe_clamp_clampViewSpaceVertex(oe_GPULines_prevViewClamped);
+
+        oe_GPULines_nextViewClamped = gl_ModelViewMatrix * vec4(oe_GPULines_next, 1.0);
+        oe_clamp_clampViewSpaceVertex(oe_GPULines_nextViewClamped);
+#endif
     }
 }
diff --git a/src/osgEarth/GPUClamping.vert.lib.glsl b/src/osgEarth/GPUClamping.vert.lib.glsl
index a8ca86e..581d1c9 100644
--- a/src/osgEarth/GPUClamping.vert.lib.glsl
+++ b/src/osgEarth/GPUClamping.vert.lib.glsl
@@ -18,7 +18,7 @@ void oe_getClampedViewVertex(in vec4 vertView, out vec4 out_clampedVertView, out
     vec4 vertDepthClip = oe_clamp_cameraView2depthClip * vertView;
 
     // sample the depth map
-    out_depth = texture2DProj( oe_clamp_depthTex, vertDepthClip ).r;
+    out_depth = textureProj( oe_clamp_depthTex, vertDepthClip ).r;
 
     // now transform into depth-view space so we can apply the height-above-ground:
     vec4 clampedVertDepthClip = vec4(vertDepthClip.x, vertDepthClip.y, out_depth, 1.0);
diff --git a/src/osgEarth/GeoCommon b/src/osgEarth/GeoCommon
index 2505b6e..42b8eab 100644
--- a/src/osgEarth/GeoCommon
+++ b/src/osgEarth/GeoCommon
@@ -23,11 +23,11 @@
 #include <limits.h>
 #include <cfloat>
 
-namespace osgEarth
-{
-    // Default normalized no-data value for elevation
+#undef  NO_DATA_VALUE
 #define NO_DATA_VALUE -FLT_MAX
 
+namespace osgEarth
+{
     /**
      * Types of interpolation between two geodetic locations.
      */
diff --git a/src/osgEarth/GeoData b/src/osgEarth/GeoData
index 9f4ee95..2089a5e 100644
--- a/src/osgEarth/GeoData
+++ b/src/osgEarth/GeoData
@@ -24,6 +24,7 @@
 #include <osgEarth/Bounds>
 #include <osgEarth/SpatialReference>
 #include <osgEarth/Units>
+#include <osgEarth/ImageUtils>
 
 #include <osg/Referenced>
 #include <osg/Image>
@@ -168,6 +169,11 @@ namespace osgEarth
         bool transform(const SpatialReference* outSRS, GeoPoint& output) const;
 
         /**
+         * Transforms this point in place to another SRS.
+         */
+        bool transformInPlace(const SpatialReference* srs);
+
+        /**
          * Transforms this geopoint's Z coordinate (in place)
          */
         bool transformZ(const AltitudeMode& altMode, const TerrainResolver* t );
@@ -241,6 +247,11 @@ namespace osgEarth
 
         Config getConfig() const;
 
+        /**
+         * Represent this point as a string
+         */
+        std::string toString() const;
+
     public:
         static GeoPoint INVALID;
 
@@ -337,24 +348,32 @@ namespace osgEarth
         /** dtor */
         virtual ~GeoExtent() { }
 
+        //! Set from the SW and NE corners.
+        void set(double west, double south, double east, double north);
+
         bool operator == ( const GeoExtent& rhs ) const;
         bool operator != ( const GeoExtent& rhs ) const;
 
         /** Gets the spatial reference system underlying this extent. */
         const SpatialReference* getSRS() const { return _srs.get(); }
 
-        double west() const { return _west; }
-        double east() const { return _east; }
-        double south() const { return _south; }
-        double north() const { return _north; }
+        //! Coordinates of the bounding edges, normalized for the lat/long frame if necessary
+        inline double west() const { return _west; }
+        inline double east() const { return normalizeX(_west+_width); }
+        inline double south() const { return _south; }
+        inline double north() const { return _south+_height; }
+
+        //! Coordinates of the bounds, NOT normalized to the lat/long frame.
+        inline double xMin() const { return _west; }
+        inline double xMax() const { return _west + _width; }
+        inline double yMin() const { return _south; }
+        inline double yMax() const { return _south + _height; }
 
-        double xMin() const { return west(); }
-        double xMax() const { return east(); }
-        double yMin() const { return south(); }
-        double yMax() const { return north(); }
+        //! East-to-west span of the extent
+        inline double width() const { return _width; }
 
-        double width() const;
-        double height() const;
+        //! North-to-south span of the extent
+        inline double height() const { return _height; }
 
         /**
          * Gets the centroid of the bounds
@@ -363,20 +382,14 @@ namespace osgEarth
         osg::Vec3d getCentroid() const { osg::Vec3d r; getCentroid(r.x(), r.y()); return r; }
         bool getCentroid( GeoPoint& output ) const;
 
-        /**
-         * Returns true is that extent is in a Geographic (lat/long) SRS that spans
-         * the antimedirian (180 degrees east/west).
-         */
+        //! True if the extent is geographic and crosses the 180 degree meridian.
         bool crossesAntimeridian() const;
 
-        /**
-         * Returns the raw bounds in a single function call
-         */
-        void getBounds(double &xmin, double &ymin, double &xmax, double &ymax) const;
+        //! Raw bounds of the extent (unnormalized)
+        void getBounds(double& xmin, double& ymin, double& xmax, double& ymax) const;
 
         /** True if this object defines a real, valid extent with positive area */
         bool isValid() const;
-        bool defined() const { return isValid(); }
         bool isInvalid() const { return !isValid(); }
 
         /**
@@ -445,7 +458,7 @@ namespace osgEarth
         /**
          * Gets a geo circle bounding this extent.
          */
-        const GeoCircle& getBoundingGeoCircle() const { return _circle; }
+        GeoCircle computeBoundingGeoCircle() const;
 
         /**
          * Grow this extent to include the specified point (which is assumed to be
@@ -487,30 +500,45 @@ namespace osgEarth
         double area() const;
 
         /**
-         * Normalize the longitude values in this GeoExtent
+         * Generate a polytope in world coordinates that bounds the extent.
+         * Return false if the extent it invalid.
          */
-        void normalize();
+        bool createPolytope(osg::Polytope& out) const;
 
         /**
-         * Given a longitude value, normalize it so that it exists within the GeoExtents longitude frame.
+         * Computes a scale/bias matrix that transforms parametric coordinates [0..1]
+         * from this extent into the target extent. Return false if the extents are
+         * incompatible (different SRS, etc.)
+         *
+         * For example, if this extent is 100m wide, and the target extent is
+         * 200m wide, the output matrix will have an x_scale = 0.5.
+         *
+         * Note! For the sake of efficiency, this method does NOT check for 
+         * validity nor for SRS equivalence -- so be sure to validate those beforehand.
+         * It also assumes the output matrix is preset to the identity.
          */
-        double normalizeLongitude( double longitude ) const;
+        bool createScaleBias(const GeoExtent& target, osg::Matrix& output) const;
 
         /**
-         * Generate a polytope in world coordinates that bounds the extent.
-         * Return false if the extent it invalid.
+         * Generates a BoundingSphere encompassing the extent and a vertical 
+         * volume, in world coordinates.
          */
-        bool createPolytope(osg::Polytope& out) const;
+        osg::BoundingSphered createWorldBoundingSphere(double minElev, double maxElev) const;
 
     public:
         static GeoExtent INVALID;
 
     private:
         osg::ref_ptr<const SpatialReference> _srs;
-        double _west, _east, _south, _north;
-        GeoCircle _circle;
+        double _west, _width, _south, _height;
+
+        double normalizeX( double longitude ) const;
 
-        void recomputeCircle();
+        void clamp();
+
+        bool isGeographic() const;
+
+        void setOriginAndSize(double west, double south, double width, double height);
     };
 
     /**
@@ -655,8 +683,30 @@ namespace osgEarth
 
     typedef std::vector<GeoImage> GeoImageVector;
 
+
+    class OSGEARTH_EXPORT NormalMap : public osg::Image
+    {
+    public:
+        NormalMap(unsigned s, unsigned t);
+
+        void set(unsigned s, unsigned t, const osg::Vec3& normal, float curvature =0.0f);
+
+        osg::Vec3 getNormal(unsigned s, unsigned t) const;
+
+        osg::Vec3 getNormalByUV(double u, double v) const;
+
+        float getCurvature(unsigned s, unsigned t) const;
+
+        virtual ~NormalMap();
+
+    private:
+        ImageUtils::PixelWriter* _write;
+        ImageUtils::PixelReader* _read;
+    };
+
+
     /**
-     * A georeferenced heightfield.
+     * A georeferenced heightfield and associated normal/curvature map.
      */
     class OSGEARTH_EXPORT GeoHeightField
     {
@@ -671,6 +721,15 @@ namespace osgEarth
             osg::HeightField* heightField,
             const GeoExtent&  extent );
 
+        //! Constructs a new GeoHeightField
+        //! @param[in] heightField Elevation grid
+        //! @param[in] normalMap   Normal/Curvature map
+        //! @param[in] extent      Geospatial extent of the data
+        GeoHeightField(
+            osg::HeightField* heightField,
+            NormalMap*        normalMap,
+            const GeoExtent&  extent);
+
         /** dtor */
         virtual ~GeoHeightField() { }
 
@@ -709,6 +768,21 @@ namespace osgEarth
             ElevationInterpolation  interp,
             const SpatialReference* srsWithOutputVerticalDatum,
             float&                  out_elevation ) const;
+
+        bool getElevationAndNormal(
+            const SpatialReference* inputSRS, 
+            double                  x,
+            double                  y,
+            ElevationInterpolation  interp,
+            const SpatialReference* srsWithOutputVerticalDatum,
+            float&                  out_elevation,
+            osg::Vec3&              out_normal ) const;
+
+        //! Gets the elevation at a point (must be in the same SRS; bilinear interpolation)
+        float getElevation(double x, double y) const;
+
+        //! Gets the normal at a point (must be in the same SRS; bilinear interpolation)
+        osg::Vec3 getNormal(double x, double y) const;
         
         /**
          * Subsamples the heightfield, returning a new heightfield corresponding to
@@ -737,6 +811,10 @@ namespace osgEarth
         const osg::HeightField* getHeightField() const;
         osg::HeightField* getHeightField();
 
+        //! Normal map
+        const NormalMap* getNormalMap() const;
+        NormalMap* getNormalMap();
+
         /**
          * Gets a pointer to the underlying OSG heightfield, and releases the internal reference.
          */
@@ -764,8 +842,11 @@ namespace osgEarth
 
     protected:
         osg::ref_ptr<osg::HeightField> _heightField;
+        osg::ref_ptr<NormalMap>        _normalMap;
         GeoExtent                      _extent;
         float                          _minHeight, _maxHeight;
+
+        void init();
     };
 
 	typedef std::vector<GeoHeightField> GeoHeightFieldVector;
diff --git a/src/osgEarth/GeoData.cpp b/src/osgEarth/GeoData.cpp
index ca9faf6..09f2ea5 100644
--- a/src/osgEarth/GeoData.cpp
+++ b/src/osgEarth/GeoData.cpp
@@ -28,6 +28,7 @@
 
 #include <osg/Notify>
 #include <osg/Timer>
+#include <osgShadow/ConvexPolyhedron>
 
 #include <gdal_priv.h>
 #include <gdalwarper.h>
@@ -38,74 +39,8 @@
 #include <iomanip>
 #include <cmath>
 
-#define LC "[GeoData] "
-
 using namespace osgEarth;
 
-
-namespace
-{
-    double s_cint( double x )
-    {
-        double dummy;
-        if (modf(x,&dummy) >= .5)
-            return x >= 0.0 ? ceil(x) : floor(x);
-        else
-            return x < 0.0 ? ceil(x) : floor(x);
-    }
-
-    double s_roundNplaces( double x, int n )
-    {
-        double off = pow(10.0, n);
-        return s_cint(x*off)/off;
-    }
-
-    double s_normalizeLongitude( double x, double minLon = -180.0, double maxLon = 180.0 )
-    {
-        double result = x;
-        while( result < minLon ) result += 360.;
-        while( result > maxLon ) result -= 360.;
-        return result;
-    }
-
-    bool s_crossesAntimeridian( double x0, double x1 )
-    {
-        return ((x0 < 0.0 && x1 > 0.0 && x0-x1 < -180.0) ||
-                (x1 < 0.0 && x0 > 0.0 && x1-x0 < -180.0));
-    }
-
-    double s_westToEastLongitudeDistance( double west, double east )
-    {
-        return west < east ? east-west : fmod(east,360.)-west;
-    }
-
-    /**
-     * Given a longitude value determine what longitude frame it is in.
-     * The base longitude frame is -180 to 180.  As values cross the antimeridian the frame is offset by 360 degrees.
-     */
-    void s_getLongitudeFrame( double longitude, double &minLongitude, double &maxLongitude)
-    {
-        minLongitude = -180.0;
-        maxLongitude =  180.0;
-
-        while ( longitude < minLongitude || longitude > maxLongitude)
-        {
-            if (longitude < minLongitude)
-            {
-                minLongitude -= 360.0;
-                maxLongitude -= 360.0;
-            }
-            else if (longitude > maxLongitude)
-            {
-                minLongitude += 360.0;
-                maxLongitude += 360.0;
-            }
-        }
-    }
-}
-
-//------------------------------------------------------------------------
-
 #undef  LC
 #define LC "[GeoPoint] "
 
@@ -319,6 +254,33 @@ GeoPoint::transform(const SpatialReference* outSRS) const
 }
 
 bool
+GeoPoint::transformInPlace(const SpatialReference* srs) 
+{
+    if ( isValid() && srs )
+    {
+        osg::Vec3d out;
+        if ( _altMode == ALTMODE_ABSOLUTE )
+        {
+            if ( _srs->transform(_p, srs, out) )
+            {
+                set(srs, out, ALTMODE_ABSOLUTE);
+                return true;
+            }
+        }
+        else // if ( _altMode == ALTMODE_RELATIVE )
+        {
+            if ( _srs->transform2D(_p.x(), _p.y(), srs, out.x(), out.y()) )
+            {
+                out.z() = _p.z();
+                set(srs, out, ALTMODE_RELATIVE);
+                return true;
+            }
+        }
+    }
+    return false;
+}
+
+bool
 GeoPoint::transformZ(const AltitudeMode& altMode, const TerrainResolver* terrain ) 
 {
     double z;
@@ -446,24 +408,26 @@ bool
 GeoPoint::createLocalToWorld( osg::Matrixd& out_l2w ) const
 {
     if ( !isValid() ) return false;
+    bool result = _srs->createLocalToWorld( _p, out_l2w );
     if ( _altMode != ALTMODE_ABSOLUTE )
     {
-        OE_WARN << LC << "ILLEGAL: called GeoPoint::createLocalToorld with AltitudeMode = RELATIVE_TO_TERRAIN" << std::endl;
+        OE_DEBUG << LC << "ILLEGAL: called GeoPoint::createLocalToWorld with AltitudeMode = RELATIVE_TO_TERRAIN" << std::endl;
         return false;
     }
-    return _srs->createLocalToWorld( _p, out_l2w );
+    return result;
 }
 
 bool
 GeoPoint::createWorldToLocal( osg::Matrixd& out_w2l ) const
 {
     if ( !isValid() ) return false;
+    bool result = _srs->createWorldToLocal( _p, out_w2l );
     if ( _altMode != ALTMODE_ABSOLUTE )
     {
-        OE_WARN << LC << "ILLEGAL: called GeoPoint::createWorldToLocal with AltitudeMode = RELATIVE_TO_TERRAIN" << std::endl;
+        OE_DEBUG << LC << "ILLEGAL: called GeoPoint::createWorldToLocal with AltitudeMode = RELATIVE_TO_TERRAIN" << std::endl;
         return false;
     }
-    return _srs->createWorldToLocal( _p, out_w2l );
+    return result;
 }
 
 bool
@@ -525,6 +489,15 @@ GeoPoint::distanceTo(const GeoPoint& rhs) const
     }
 }
 
+std::string
+GeoPoint::toString() const
+{
+    std::stringstream buf;
+    buf << "x=" << x() << ", y=" << y() << ", z=" << z() << "; m=" <<
+        (_altMode == ALTMODE_ABSOLUTE ? "abs" : "rel");
+    return buf.str();
+}
+
 
 //------------------------------------------------------------------------
 
@@ -617,67 +590,115 @@ GeoCircle::intersects( const GeoCircle& rhs ) const
 #undef  LC
 #define LC "[GeoExtent] "
 
+namespace {
+    bool is_valid(double n) {
+        return
+            osg::isNaN(n) == false &&
+            n != DBL_MAX &&
+            n != -DBL_MAX;
+    }
+}
+
 GeoExtent GeoExtent::INVALID = GeoExtent();
 
 
 GeoExtent::GeoExtent():
-_west   ( DBL_MAX ),
-_east   ( DBL_MAX ),
-_south  ( DBL_MAX ),
-_north  ( DBL_MAX )
+_west(0.0),
+_width(-1.0),
+_south(0.0),
+_height(-1.0)
 {
     //NOP - invalid
 }
 
 GeoExtent::GeoExtent(const SpatialReference* srs) :
-_srs    ( srs ),
-_west   ( DBL_MAX ),
-_east   ( DBL_MAX ),
-_south  ( DBL_MAX ),
-_north  ( DBL_MAX )
+_srs(srs),
+_west(0.0),
+_width(-1.0),
+_south(0.0),
+_height(-1.0)
 {
     //NOP - invalid
 }
 
 GeoExtent::GeoExtent(const SpatialReference* srs,
                      double west, double south, double east, double north ) :
-_srs    ( srs ),
-_west   ( west ),
-_east   ( east ),
-_south  ( south ),
-_north  ( north )
+_srs( srs )
 {
-    if ( isValid() )
-        recomputeCircle();
+    set(west, south, east, north);
 }
 
 
-GeoExtent::GeoExtent( const SpatialReference* srs, const Bounds& bounds ) :
-_srs    ( srs ),
-_west   ( bounds.xMin() ),
-_east   ( bounds.xMax() ),
-_south  ( bounds.yMin() ),
-_north  ( bounds.yMax() )
-{    
-    if ( isValid() )
-        recomputeCircle();
+GeoExtent::GeoExtent(const SpatialReference* srs, const Bounds& bounds) :
+_srs( srs )
+{
+    set(bounds.xMin(), bounds.yMin(), bounds.xMax(), bounds.yMax());
 }
 
-GeoExtent::GeoExtent( const GeoExtent& rhs ) :
-_srs   ( rhs._srs ),
-_east  ( rhs._east ),
-_west  ( rhs._west ),
-_south ( rhs._south ),
-_north ( rhs._north ),
-_circle( rhs._circle )
+GeoExtent::GeoExtent(const GeoExtent& rhs) :
+_srs(rhs._srs),
+_west(rhs._west),
+_width(rhs._width),
+_south(rhs._south),
+_height(rhs._height)
 {
     //NOP
 }
 
 bool
+GeoExtent::isGeographic() const
+{
+    return _srs.valid() && _srs->isGeographic();
+}
+
+void
+GeoExtent::set(double west, double south, double east, double north)
+{
+    // Validate input.
+    if (!is_valid(west) ||
+        !is_valid(south) ||
+        !is_valid(east) ||
+        !is_valid(north) ||
+        south > north)
+    {
+        _west = _south = 0.0;
+        _width = _height = -1.0;
+        return;
+    }
+
+    // In this method, east is always to the east of west!
+    // If it appears not to be, that means the extent crosses the antimeridian.
+    west = normalizeX(west);
+    double width = 0.0;
+    double height = 0.0;
+
+    if (isGeographic())
+    {
+        // ensure east >= west in a geographic frame.
+        while (east < west)
+            east += 360.0;
+    }
+    width = std::max(0.0, east - west);
+
+    height = std::max(0.0, north-south);
+
+    setOriginAndSize(west, south, width, height);
+}
+
+void
+GeoExtent::setOriginAndSize(double west, double south, double width, double height)
+{
+    _west = west;
+    _south = south;
+    _width = width;
+    _height = height;
+    clamp();
+}
+
+bool
 GeoExtent::getCentroid(GeoPoint& out) const
 {
-    out = GeoPoint(_srs, getCentroid(), ALTMODE_ABSOLUTE);
+    out = GeoPoint(_srs.get(), getCentroid(), ALTMODE_ABSOLUTE);
     return true;
 }
 
@@ -707,69 +728,39 @@ GeoExtent::operator != ( const GeoExtent& rhs ) const
 bool
 GeoExtent::isValid() const
 {
-    return 
-        _srs.valid()       && 
-        _east  != DBL_MAX  && _east  != -DBL_MAX &&
-        _west  != DBL_MAX  && _west  != -DBL_MAX &&
-        _north != DBL_MAX  && _north != -DBL_MAX &&
-        _south != DBL_MAX  && _south != -DBL_MAX;
-}
-
-double
-GeoExtent::width() const
-{    
-    return crossesAntimeridian() ?
-        (180.0-_west) + (_east+180.0) :
-        _east - _west;
-}
-
-double
-GeoExtent::height() const
-{
-    return _north - _south;
+    return _srs.valid() && _width >= 0.0 && _height >= 0.0;
 }
 
 bool
-GeoExtent::getCentroid( double& out_x, double& out_y ) const
+GeoExtent::getCentroid(double& out_x, double& out_y) const
 {
-    if ( isInvalid() ) return false;
+    if (isInvalid()) return false;
 
+    out_x = normalizeX(west() + 0.5*width());
     out_y = south() + 0.5*height();
-    out_x = west() + 0.5*width();
-
-    if ( _srs->isGeographic() )
-        out_x = normalizeLongitude( out_x );        
     return true;
 }
 
 bool
 GeoExtent::crossesAntimeridian() const
 {
-    return _srs.valid() && _srs->isGeographic() && east() < west();
+    return _srs.valid() && _srs->isGeographic() && east() < west(); //west()+width() > 180.0;
 }
 
 bool
-GeoExtent::splitAcrossAntimeridian( GeoExtent& out_west, GeoExtent& out_east ) const
+GeoExtent::splitAcrossAntimeridian(GeoExtent& out_west, GeoExtent& out_east) const
 {
-    bool success = false;
-
     if ( crossesAntimeridian() )
     {
-        double minLon, maxLon;
-        s_getLongitudeFrame( west(), minLon, maxLon );
-        out_west._srs   = _srs.get();
-        out_west._west  = west();
-        out_west._south = south();
-        out_west._east  = maxLon;
-        out_west._north = north();
+        double width_new;
 
-        out_east._srs   = _srs.get();
-        out_east._west  = minLon;
-        out_east._south = south();
-        out_east._east  = east();
-        out_east._north = north();
+        out_west = *this;
+        width_new = 180.0 - west();
+        out_west.setOriginAndSize(180.0 - width_new, south(), width_new, height());
 
-        success = true;
+        out_east = *this;
+        width_new = east() - (-180.0);
+        out_east.setOriginAndSize(-180.0, south(), width_new, height());
     }
     else if ( !_srs->isGeographic() )
     {
@@ -780,26 +771,26 @@ GeoExtent::splitAcrossAntimeridian( GeoExtent& out_west, GeoExtent& out_east ) c
         {
             out_west = w.transform( _srs.get() );
             out_east = e.transform( _srs.get() );
-            success = out_west.isValid() && out_east.isValid();
         }
     }
-    return success;
+
+    return out_west.isValid() && out_east.isValid();
 }
 
 GeoExtent
-GeoExtent::transform( const SpatialReference* to_srs ) const 
+GeoExtent::transform(const SpatialReference* to_srs) const 
 {       
-    //TODO: this probably doesn't work across the antimeridian
-    if ( _srs.valid() && to_srs )
+    //TODO: this may not work across the antimeridian - unit test required
+    if ( isValid() && to_srs )
     {
+        // do not normalize the X values here.
         double xmin = west(), ymin = south();
-        double xmax = east(), ymax = north();
+        double xmax = west() + width(), ymax = south() + height();
         
-        if ( _srs->transformExtentToMBR( to_srs, xmin, ymin, xmax, ymax ) )
+        if ( _srs->transformExtentToMBR(to_srs, xmin, ymin, xmax, ymax) )
         {
             return GeoExtent( to_srs, xmin, ymin, xmax, ymax );
         }
-
     }
     return GeoExtent::INVALID;
 }
@@ -817,69 +808,70 @@ GeoExtent::getBounds(double &xmin, double &ymin, double &xmax, double &ymax) con
 {
     xmin = west();
     ymin = south();
-    xmax = east();
-    ymax = north();
+    xmax = west() + width();
+    ymax = south() + height();
 }
 
 Bounds
 GeoExtent::bounds() const
 {
-    return Bounds( _west, _south, _east, _north );
+    double west, east, south, north;
+    getBounds(west, south, east, north);
+    return Bounds( west, south, east, north );
 }
 
 bool
 GeoExtent::contains(double x, double y, const SpatialReference* srs) const
 {
-    if ( isInvalid() )
+    if (isInvalid() || !is_valid(x) || !is_valid(y))
         return false;
 
     osg::Vec3d xy( x, y, 0 );
-    osg::Vec3d localxy = xy;
+    osg::Vec3d local = xy;
 
-    if (srs &&
-        !srs->isEquivalentTo( _srs.get() ) &&
-        !srs->transform(xy, _srs.get(), localxy) )
-    {    
-        return false;
-    }
-    else
+    // See if we need to xform the input:
+    if (srs && srs->isHorizEquivalentTo(_srs.get()) == false)
     {
-        // normalize a geographic longitude to -180:+180
-        if ( _srs->isGeographic() )
-            localxy.x() = normalizeLongitude( localxy.x() );            
-
-        //Account for small rounding errors along the edges of the extent
-        if (osg::equivalent(_west, localxy.x())) localxy.x() = _west;
-        if (osg::equivalent(_east, localxy.x())) localxy.x() = _east;
-        if (osg::equivalent(_south, localxy.y())) localxy.y() = _south;
-        if (osg::equivalent(_north, localxy.y())) localxy.y() = _north;
-
-        if ( crossesAntimeridian() )
-        {
-            if ( localxy.x() > 0.0 )
-            {
-                return localxy.x() >= _west && localxy.x() <= 180.0 && localxy.y() >= _south && localxy.y() <= _north;
-            }
-            else
-            {
-                return localxy.x() >= -180.0 && localxy.x() <= _east && localxy.y() >= _south && localxy.y() <= _north;
-            }
-        }
-        else
+        // If the transform fails, bail out with error
+        if (srs->transform(xy, _srs.get(), local) == false)
         {
-            return localxy.x() >= _west && localxy.x() <= _east && localxy.y() >= _south && localxy.y() <= _north;
+            return false;
         }
     }
+
+    // Quantize the Y coordinate to account for tiny rounding errors:
+    if (osg::equivalent(south(), local.y()))
+        local.y() = south();
+    if (osg::equivalent(north(), local.y()))
+        local.y() = north();
+
+    // Test the Y coordinate:
+    if (local.y() < south() || local.y() > north())
+        return false;
+
+    // Bring the X coordinate into normal range:
+    local.x() = normalizeX(local.x());
+    
+    // Quantize the X coordinate to account for tiny rounding errors:
+    if (osg::equivalent(west(), local.x()))
+        local.x() = west();
+    if (osg::equivalent(east(), local.x()))
+        local.x() = east();
+
+    // account for the antimeridian wrap-around:
+    double a0 = west(), a1 = west() + width();
+    double b0 = east() - width(), b1 = east();
+    return (a0 <= local.x() && local.x() <= a1) || (b0 <= local.x() && local.x() <= b1);
 }
 
 bool
-GeoExtent::contains( const GeoPoint& rhs ) const
+GeoExtent::contains(const GeoPoint& rhs) const
 {
     return contains( rhs.x(), rhs.y(), rhs.getSRS() );
 }
 
 bool
-GeoExtent::contains( const Bounds& rhs ) const
+GeoExtent::contains(const Bounds& rhs) const
 {
     return
         isValid() &&
@@ -890,59 +882,76 @@ GeoExtent::contains( const Bounds& rhs ) const
 }
 
 bool
-GeoExtent::contains( const GeoExtent& rhs ) const
+GeoExtent::contains(const GeoExtent& rhs) const
 {
     return
         isValid() &&
         rhs.isValid() &&
-        contains( rhs.west(), rhs.south() ) &&
-        contains( rhs.east(), rhs.north() ) &&
-        contains( rhs.getCentroid() );   // this accounts for the antimeridian
+        contains( rhs.west(), rhs.south(), rhs.getSRS() ) &&
+        contains( rhs.east(), rhs.north(), rhs.getSRS() ) &&
+        contains( rhs.getCentroid(), rhs.getSRS() );   // this accounts for the antimeridian
 }
 
+#undef  OVERLAPS
+#define OVERLAPS(A, B, C, D) (!(B <= C || A >= D))
+
 bool
-GeoExtent::intersects( const GeoExtent& rhs, bool checkSRS ) const
+GeoExtent::intersects(const GeoExtent& rhs, bool checkSRS) const
 {
     if ( !isValid() || !rhs.isValid() )
         return false;
 
+    // Transform the incoming extent if necessary:
     if ( checkSRS && !_srs->isHorizEquivalentTo(rhs.getSRS()) )
     {
-        GeoExtent rhsExt = rhs.transform(getSRS());
-        return this->intersects( rhsExt );
+        if (_srs->isContiguous())
+        {
+            GeoExtent rhsExt = rhs.transform(getSRS());
+            return this->intersects( rhsExt, false );
+        }
+        else
+        {
+            // non-contiguous projection? convert to a contiguous one:
+            GeoExtent thisGeo = transform(getSRS()->getGeographicSRS());
+            GeoExtent rhsGeo = rhs.transform(getSRS()->getGeographicSRS());
+            return thisGeo.intersects(rhsGeo, false);
+        }
     }
 
-    if ( rhs.crossesAntimeridian() )
-    {
-        GeoExtent rhsWest, rhsEast;
-        rhs.splitAcrossAntimeridian( rhsWest, rhsEast );
-        return rhsWest.intersects(*this) || rhsEast.intersects(*this);
-    }
-    else if ( crossesAntimeridian() )
+    // Trivial reject: y-dimension does not overlap:
+    bool y_excl = south() >= rhs.north() || north() <= rhs.south();
+    if (y_excl)
+        return false;
+
+    // Trivial reject: x-dimension does not overlap in projected SRS:
+    if (!_srs->isGeographic())
     {
-        GeoExtent west, east;
-        splitAcrossAntimeridian(west, east);
-        return rhs.intersects(west) || rhs.intersects(east);
+        bool x_excl = west() >= rhs.east() || east() <= rhs.west();
+        return x_excl == false;
     }
-    else
-    {
-        bool exclusive =
-            _west >= rhs.east() ||
-            _east <= rhs.west() ||
-            _south >= rhs.north() ||
-            _north <= rhs.south();
 
-        return !exclusive;
-    }
+    // By now we know that Y overlaps and we are in a geographic SRS
+    // and therefore must consider the antimeridian wrap-around in X.
+    // a and b are "this"; c and d are "rhs":
+    double a0 = east() - width(), a1 = east();
+    double b0 = west(), b1 = west() + width();
+    double c0 = rhs.east() - rhs.width(), c1 = rhs.east();
+    double d0 = rhs.west(), d1 = rhs.west() + rhs.width();
+    return
+        OVERLAPS(a0, a1, c0, c1) ||
+        OVERLAPS(a0, a1, d0, d1) ||
+        OVERLAPS(b0, b1, c0, c1) ||
+        OVERLAPS(b0, b1, d0, d1);
 }
 
-
-void
-GeoExtent::recomputeCircle()
+GeoCircle
+GeoExtent::computeBoundingGeoCircle() const
 {
+    GeoCircle circle;
+
     if ( !isValid() )
     {
-        _circle.setRadius( -1.0 );
+        circle.setRadius( -1.0 );
     }
     else 
     {
@@ -952,8 +961,7 @@ GeoExtent::recomputeCircle()
         if ( getSRS()->isProjected() )
         {
             double ext = std::max( width(), height() );
-            _circle.setRadius( 0.5*ext * 1.414121356237 ); /*sqrt(2)*/
-            //_circle.setRadius( (osg::Vec2d(x,y)-osg::Vec2d(_west,_south)).length() );
+            circle.setRadius( 0.5*ext * 1.414121356237 ); /*sqrt(2)*/
         }
         else // isGeographic
         {
@@ -970,113 +978,103 @@ GeoExtent::recomputeCircle()
             radius2 = std::max(radius2, (center-ne).length2());
             radius2 = std::max(radius2, (center-sw).length2());
 
-            _circle.setRadius( sqrt(radius2) );
-#if 0
-            double extDegrees;
-            double metersPerDegree = (getSRS()->getEllipsoid()->getRadiusEquator() * 2.0 * osg::PI) / 360.0;
-
-            if ( width() > height() )
-            {
-                extDegrees = width();
-                double widestLatitude = std::min( fabs(north()), fabs(south()) );
-                metersPerDegree *= cos(osg::DegreesToRadians(widestLatitude));
-            }
-            else
-            {
-                extDegrees = height();
-            }
-
-            double extMeters = extDegrees * metersPerDegree;
-            _circle.setRadius( 0.5*extMeters * 1.414121356237 ); /*sqrt(2)*/
-#endif
+            circle.setRadius( sqrt(radius2) );
         }
 
-        _circle.setCenter( GeoPoint(getSRS(), x, y, 0.0, ALTMODE_ABSOLUTE) );
+        circle.setCenter( GeoPoint(getSRS(), x, y, 0.0, ALTMODE_ABSOLUTE) );
     }
-}
 
+    return circle;
+}
 
 void
-GeoExtent::expandToInclude( double x, double y )
+GeoExtent::expandToInclude(double x, double y)
 {
-    if ( west() == DBL_MAX )
-    {
-        _west = x;
-        _east = x;
-        _south = y;
-        _north = y;
-    }
-    else if ( getSRS() && getSRS()->isGeographic() )
-    {
-        x = normalizeLongitude( x );
+    if (!is_valid(x) || !is_valid(y))
+        return;
 
-        // calculate possible expansion distances. The lesser of the two
-        // will be the direction in which we expand.
+    // First, bring the X coordinate into the local frame.
+    x = normalizeX(x);
 
-        // west:
-        double dw;
-        if ( x > west() )
-            dw = west() - (x-360.);
-        else
-            dw = west() - x;
+    // Invalid? Set to a point.
+    if (isInvalid())
+    {
+        set(x, y, x, y);
+        return;
+    }
 
-        // east:
-        double de;
-        if ( x < east() )
-            de = (x+360.) - east();
-        else
-            de = x - east();
+    // Check each coordinate separately:
+    double cx, cy;
+    getCentroid(cx, cy);
+    bool containsX = contains(x, cy);
+    bool containsY = contains(cx, y);
 
-        // this is the empty space available - growth beyond this 
-        // automatically yields full extent [-180..180]
-        double maxWidth = 360.0-width();
+    // Expand along the Y axis:
+    if (!containsY)
+    {
+        if (y < south())
+        {
+            _height += (_south-y);
+            _south = y;
+        }
+        else if (y > north())
+        {
+            _height = y - south();
+        }
+    }
 
-        // if both are > 180, then the point is already in our extent.
-        if ( dw <= 180. || de <= 180. )
+    if (!containsX)
+    {
+        if (isGeographic())
         {
-            if ( dw < de )
+            if (x > west())
             {
-                if ( dw < maxWidth )
+                double w0 = x - west(); // non-wrap-around width
+                double w1 = (180.0 - x) + (west() - (-180.0) + _width); // wrap-around width
+                if (w0 <= w1)
                 {
-                    // expand westward
-                    _west -= dw;                    
-                    _west = normalizeLongitude( _west );
+                    _width = w0;
                 }
                 else
                 {
-                    // reached full extent
-                    _west = -180.0;
-                    _east =  180.0;
+                    _west = x;
+                    _width = w1;
                 }
             }
-            else
+            else // (x < west())
             {
-                if ( de < maxWidth )
+                double w0 = _width + (west() - x); // non-wrap-around
+                double w1 = (x - (-180.0)) + (180.0 - west()); // wrap-around
+                if (w0 < w1)
                 {
-                    // expand eastward
-                    _east += de;
-                    _east = normalizeLongitude(_east);
+                    _west = x;
+                    _width = w0;
                 }
                 else
                 {
-                    // reached full extent.
-                    _west = -180.0;
-                    _east =  180.0;
+                    _width = w1;
                 }
             }
         }
-        //else already inside longitude extent
+        else
+        {
+            // projected mode is the same approach as Y
+            if (x < west())
+            {
+                _width += _west - x;
+                _west = x;
+            }
+            else if (x > east())
+            {
+                _width = x - west();
+            }
+        }
     }
-    else
+
+    if (!containsX || !containsY)
     {
-        _west = std::min(_west, x);
-        _east = std::max(_east, x);
+        clamp();
     }
-
-    _south = std::min(_south, y);
-    _north = std::max(_north, y);
-
-    recomputeCircle();
 }
 
 void
@@ -1088,27 +1086,42 @@ GeoExtent::expandToInclude(const Bounds& rhs)
 }
 
 bool
-GeoExtent::expandToInclude( const GeoExtent& rhs )
+GeoExtent::expandToInclude(const GeoExtent& rhs)
 {
-    if ( isInvalid() || rhs.isInvalid() ) return false;
+    if ( isInvalid() || rhs.isInvalid() )
+        return false;
 
-    if ( !rhs.getSRS()->isEquivalentTo( _srs.get() ) )
+    if ( !rhs.getSRS()->isHorizEquivalentTo( _srs.get() ) )
     {
-        return expandToInclude( transform(rhs.getSRS()) );
+        return expandToInclude( rhs.transform(_srs.get()) );
     }
+
     else
     {
-        // include the centroid first in order to honor an 
-        // antimeridian-crossing profile
+        // include the centroid first in order to get the optimal
+        // expansion direction.
         expandToInclude( rhs.getCentroid() );
         expandToInclude( rhs.west(), rhs.south() );
         expandToInclude( rhs.east(), rhs.north() );
-        return true;
+    }
+
+    return true;
+}
+
+namespace
+{
+    void sort4(double* n, bool* b)
+    {
+        if (n[0] > n[1]) std::swap(n[0], n[1]), std::swap(b[0], b[1]);
+        if (n[2] > n[3]) std::swap(n[2], n[3]), std::swap(b[2], b[3]);
+        if (n[0] > n[2]) std::swap(n[0], n[2]), std::swap(b[0], b[2]);
+        if (n[1] > n[3]) std::swap(n[1], n[3]), std::swap(b[1], b[3]);
+        if (n[1] > n[2]) std::swap(n[1], n[2]), std::swap(b[1], b[2]);
     }
 }
 
 GeoExtent
-GeoExtent::intersectionSameSRS( const GeoExtent& rhs ) const
+GeoExtent::intersectionSameSRS(const GeoExtent& rhs) const
 {
     if ( isInvalid() || rhs.isInvalid() || !_srs->isHorizEquivalentTo( rhs.getSRS() ) )
         return GeoExtent::INVALID;
@@ -1122,31 +1135,69 @@ GeoExtent::intersectionSameSRS( const GeoExtent& rhs ) const
 
     GeoExtent result( *this );
 
-    double westAngle, eastAngle;
-    
-    // see if the rhs western boundary intersects our extent:
-    westAngle = s_westToEastLongitudeDistance( west(), rhs.west() );
-    eastAngle = s_westToEastLongitudeDistance( rhs.west(), east() );
-    if ( westAngle < width() && eastAngle < width() ) // yes, so adjust the result eastward:
+    if (isGeographic())
     {
-        result._west += westAngle;
-    }
+        if (width() == 360.0)
+        {
+            result._west = rhs._west;
+            result._width = rhs._width;
+        }
+        else if (rhs.width() == 360.0)
+        {
+            result._west = _west;
+            result._width = _width;
+        }
+        else
+        {
+            // Sort the four X coordinates, remembering whether each one is west or east edge:
+            double x[4];
+            bool iswest[4];
+            x[0] = west(), x[1] = east(), x[2] = rhs.west(), x[3] = rhs.east();
+            iswest[0] = true, iswest[1] = false, iswest[2] = true, iswest[3] = false;
+            sort4(x, iswest);
+
+            // find the western-most west coord:
+            int iw = -1;
+            for (int i=0; i<4 && iw<0; ++i)
+            {
+                if (iswest[i])
+                    iw = i;
+            }
+
+            // iterate from there, finding the LAST west coord and stopping on the 
+            // FIRST east coord found.
+            int q = iw+4;
+            int ie = -1;
+            for (int i = iw; i < q && ie < 0; ++i)
+            {
+                int j = i;
+                if (j >= 4) j-=4;
+                if (iswest[j])
+                    iw = j; // found a better west coord; remember it.
+                else
+                    ie = j; // found the western-most east coord; done.
+            }
 
-    // now see if the rhs eastern boundary intersects out extent:
-    westAngle = s_westToEastLongitudeDistance( west(), rhs.east() );
-    eastAngle = s_westToEastLongitudeDistance( rhs.east(), east() );
-    if ( westAngle < width() && eastAngle < width() ) // yes, so adjust again:
+            result._west = x[iw];
+            if (ie >= iw)
+                result._width = x[ie] - x[iw];
+            else
+                result._width = (180.0 - x[iw]) + (x[ie] - (-180.0)); // crosses the antimeridian
+        }
+    }
+    else
     {
-        result._east -= eastAngle;
+        // projected mode is simple
+        result._west = std::max(west(), rhs.west());
+        double eastTemp = std::min(east(), rhs.east());
+        result._width = eastTemp - result._west;
     }
 
-    // normalize our new longitudes
-    result._west = normalizeLongitude( result._west );
-    result._east = normalizeLongitude( result._east );
+    result._south = std::max(south(), rhs.south());
+    double northTemp = std::min(north(), rhs.north());
+    result._height = northTemp - result._south;
 
-    // latitude is easy, just clamp it
-    result._south = std::max( south(), rhs.south() );
-    result._north = std::min( north(), rhs.north() );
+    result.clamp();
 
     OE_DEBUG << "Intersection of " << this->toString() << " and " << rhs.toString() << " is: " 
         << result.toString()
@@ -1158,38 +1209,71 @@ GeoExtent::intersectionSameSRS( const GeoExtent& rhs ) const
 void
 GeoExtent::scale(double x_scale, double y_scale)
 {
-    if ( isInvalid() )
+    if (isInvalid() || !is_valid(x_scale) || !is_valid(y_scale))
         return;
 
-    double orig_width = width();
-    double orig_height = height();
+    double cx = _west + 0.5*_width;
 
-    double new_width  = orig_width  * x_scale;
-    double new_height = orig_height * y_scale;
-
-    double halfXDiff = (new_width - orig_width) / 2.0;
-    double halfYDiff = (new_height - orig_height) /2.0;
+    double cy = _south + 0.5*_height;
+     
+    setOriginAndSize(
+        normalizeX(cx - 0.5*_width*x_scale),
+        cy - 0.5*_height*y_scale,
+        _width * x_scale,
+        _height * y_scale);
+}
 
-    _west  -= halfXDiff;
-    _east  += halfXDiff;
-    _south -= halfYDiff;
-    _north += halfYDiff;
+void
+GeoExtent::expand(double x, double y)
+{
+    if (isInvalid() || !is_valid(x) || !is_valid(y))
+        return;
 
-    recomputeCircle();
+    setOriginAndSize(
+        normalizeX(_west - 0.5*x),
+        _south - 0.5*y,
+        _width + x,
+        _height + y);
 }
 
 void
-GeoExtent::expand( double x, double y )
+GeoExtent::clamp()
 {
-    if ( isInvalid() )
-        return;
+    if (osg::equivalent(_west, floor(_west)))
+        _west = floor(_west);
+    else if (osg::equivalent(_west, ceil(_west)))
+        _west = ceil(_west);
 
-    _west  -= .5*x;
-    _east  += .5*x;
-    _south -= .5*y;
-    _north += .5*y;
+    if (osg::equivalent(_south, floor(_south)))
+        _south = floor(_south);
+    else if (osg::equivalent(_south, ceil(_south)))
+        _south = ceil(_south);
 
-    recomputeCircle();
+    if (osg::equivalent(_width, floor(_width)))
+        _width = floor(_width);
+    else if (osg::equivalent(_width, ceil(_width)))
+        _width = ceil(_width);
+
+    if (osg::equivalent(_height, floor(_height)))
+        _height = floor(_height);
+    else if (osg::equivalent(_height, ceil(_height)))
+        _height = ceil(_height);
+
+    if (isGeographic())
+    {
+        _width = osg::clampBetween(_width, 0.0, 360.0);
+        _height = osg::clampBetween(_height, 0.0, 180.0);
+
+        if (south() < -90.0)
+        {
+            _height -= (-90.0)-_south;
+            _south = -90.0;
+        }
+        else if (north() > 90.0)
+        {
+            _height -= (north()-90.0);            
+        }
+    }
 }
 
 double
@@ -1198,26 +1282,17 @@ GeoExtent::area() const
     return isValid() ? width() * height() : 0.0;
 }
 
-void
-GeoExtent::normalize()
-{
-    if (isValid() && _srs->isGeographic())
-    {
-        _west = s_normalizeLongitude( _west );
-        _east = s_normalizeLongitude( _east );
-    }
-}
-
 double
-GeoExtent::normalizeLongitude( double longitude ) const
+GeoExtent::normalizeX(double x) const
 {
-    if (isValid() && _srs->isGeographic())
+    if (isValid() && is_valid(x) && _srs->isGeographic())
     {
-        double minLon, maxLon;
-        s_getLongitudeFrame( _west, minLon, maxLon );        
-        return s_normalizeLongitude( longitude, minLon, maxLon );
+        while (x < -180.0)
+            x += 360.0;
+        while ( x > 180.0 )
+            x -= 360.0;
     }
-    return longitude;
+    return x;
 }
 
 std::string
@@ -1267,10 +1342,10 @@ GeoExtent::createPolytope(osg::Polytope& tope) const
         // convert 4 corners to world space (ECEF)
         osg::Vec3d center(0.0, 0.0, 0.0);
         osg::Vec3d sw, se, nw, ne;
-        GeoPoint(getSRS(), _west, _south, 0.0, ALTMODE_ABSOLUTE).toWorld( sw );
-        GeoPoint(getSRS(), _east, _south, 0.0, ALTMODE_ABSOLUTE).toWorld( se );
-        GeoPoint(getSRS(), _east, _north, 0.0, ALTMODE_ABSOLUTE).toWorld( ne );
-        GeoPoint(getSRS(), _west, _north, 0.0, ALTMODE_ABSOLUTE).toWorld( nw );
+        GeoPoint(getSRS(), west(), south(), 0.0, ALTMODE_ABSOLUTE).toWorld( sw );
+        GeoPoint(getSRS(), east(), south(), 0.0, ALTMODE_ABSOLUTE).toWorld( se );
+        GeoPoint(getSRS(), east(), north(), 0.0, ALTMODE_ABSOLUTE).toWorld( ne );
+        GeoPoint(getSRS(), west(), north(), 0.0, ALTMODE_ABSOLUTE).toWorld( nw );
 
         // bounding planes in ECEF space:
         tope.add( osg::Plane(center, nw, sw) ); // west
@@ -1282,23 +1357,67 @@ GeoExtent::createPolytope(osg::Polytope& tope) const
     return true;
 }
 
+osg::BoundingSphered
+GeoExtent::createWorldBoundingSphere(double minElev, double maxElev) const
+{
+    osg::BoundingSphered bs;
+
+    if (getSRS()->isProjected())
+    {
+        osg::Vec3d w;
+        GeoPoint(getSRS(), xMin(), yMin(), minElev, ALTMODE_ABSOLUTE).toWorld(w); bs.expandBy(w);
+        GeoPoint(getSRS(), xMax(), yMax(), maxElev, ALTMODE_ABSOLUTE).toWorld(w); bs.expandBy(w);
+    }
+
+    else // geocentric
+    {
+        osg::Vec3d w;
+        GeoPoint(getSRS(), xMin(), yMin(), minElev, ALTMODE_ABSOLUTE).toWorld(w); bs.expandBy(w);
+        GeoPoint(getSRS(), xMax(), yMin(), minElev, ALTMODE_ABSOLUTE).toWorld(w); bs.expandBy(w);
+        GeoPoint(getSRS(), xMax(), yMax(), minElev, ALTMODE_ABSOLUTE).toWorld(w); bs.expandBy(w);
+        GeoPoint(getSRS(), xMin(), yMax(), minElev, ALTMODE_ABSOLUTE).toWorld(w); bs.expandBy(w);
+        GeoPoint(getSRS(), xMin(), yMin(), maxElev, ALTMODE_ABSOLUTE).toWorld(w); bs.expandBy(w);
+        GeoPoint(getSRS(), xMax(), yMin(), maxElev, ALTMODE_ABSOLUTE).toWorld(w); bs.expandBy(w);
+        GeoPoint(getSRS(), xMax(), yMax(), maxElev, ALTMODE_ABSOLUTE).toWorld(w); bs.expandBy(w);
+        GeoPoint(getSRS(), xMin(), yMax(), maxElev, ALTMODE_ABSOLUTE).toWorld(w); bs.expandBy(w);
+    }
+
+    return bs;
+}
+
+bool
+GeoExtent::createScaleBias(const GeoExtent& rhs, osg::Matrix& output) const
+{    
+    double scalex = width() / rhs.width();
+    double scaley = height() / rhs.height();
+    double biasx  = (west()-rhs.west()) / rhs.width();
+    double biasy  = (south()-rhs.south()) / rhs.height();
+
+    output(0,0) = scalex;
+    output(1,1) = scaley;
+    output(3,0) = biasx;
+    output(3,1) = biasy;
+
+    return true;
+}
+
 /***************************************************************************/
 
-DataExtent::DataExtent(const osgEarth::GeoExtent& extent, unsigned minLevel,  unsigned maxLevel) :
+DataExtent::DataExtent(const GeoExtent& extent, unsigned minLevel,  unsigned maxLevel) :
 GeoExtent(extent)
 {
     _minLevel = minLevel;
     _maxLevel = maxLevel;
 }
 
-DataExtent::DataExtent(const osgEarth::GeoExtent& extent, unsigned minLevel) :
+DataExtent::DataExtent(const GeoExtent& extent, unsigned minLevel) :
 GeoExtent(extent),
 _maxLevel( 25 )
 {
     _minLevel = minLevel;
 }
 
-DataExtent::DataExtent(const osgEarth::GeoExtent& extent ) :
+DataExtent::DataExtent(const GeoExtent& extent ) :
 GeoExtent(extent),
 _minLevel( 0 ),
 _maxLevel( 25 )
@@ -1734,9 +1853,6 @@ namespace
             height = osg::minimum(image->s(), image->t());
         }
 
-        // need to know this in order to choose the right interpolation algorithm
-        const bool isSrcContiguous = src_extent.getSRS()->isContiguous();
-
         osg::Image *result = new osg::Image();
         //result->allocateImage(width, height, 1, GL_RGBA, GL_UNSIGNED_BYTE);
         result->allocateImage(width, height, 1, image->getPixelFormat(), image->getDataType()); //GL_UNSIGNED_BYTE);
@@ -1762,6 +1878,7 @@ namespace
         // the sample grid into the source coordinate system.
         double *srcPointsX = new double[numPixels * 2];
         double *srcPointsY = srcPointsX + numPixels;
+
         dest_extent.getSRS()->transformExtentPoints(
             src_extent.getSRS(),
             dest_extent.xMin() + .5 * dx, dest_extent.yMin() + .5 * dy,
@@ -1985,6 +2102,125 @@ GeoImage::takeImage()
     return _image.release();
 }
 
+/***************************************************************************/
+
+#define DEFAULT_NORMAL osg::Vec3(0,0,1)
+#define DEFAULT_CURVATURE 0.0f
+
+NormalMap::NormalMap(unsigned s, unsigned t) :
+osg::Image(),
+_write(0L),
+_read(0L)
+{
+    const osg::Vec3 defaultNormal(DEFAULT_NORMAL);
+    const float defaultCurvature(DEFAULT_CURVATURE);
+
+    if ( s > 0 && t > 0 )
+    {
+        allocateImage(s, t, 1, GL_RGBA, GL_UNSIGNED_BYTE, 1);
+
+        _write = new ImageUtils::PixelWriter(this);
+        _read = new ImageUtils::PixelReader(this);
+
+        for (unsigned y=0; y<t; ++y)
+            for (unsigned x=0; x<s; ++x)
+                set(x, y, defaultNormal, defaultCurvature);
+    }
+}
+
+NormalMap::~NormalMap()
+{
+    if (_read) delete _read;
+    if (_write) delete _write;
+}
+
+void
+NormalMap::set(unsigned s, unsigned t, const osg::Vec3& normal, float curvature)
+{
+    if (!_write) return;
+
+    osg::Vec4f encoding(
+        0.5f*(normal.x()+1.0f),
+        0.5f*(normal.y()+1.0f),
+        0.5f*(normal.z()+1.0f),
+        0.5f*(curvature+1.0f));
+
+    (*_write)(encoding, s, t);
+}
+
+osg::Vec3
+NormalMap::getNormal(unsigned s, unsigned t) const
+{
+    if (!_read) return osg::Vec3(0,0,1);
+
+    osg::Vec4 encoding = (*_read)(s, t);
+    return osg::Vec3(
+        encoding.x()*2.0 - 1.0,
+        encoding.y()*2.0 - 1.0,
+        encoding.z()*2.0 - 1.0);
+}
+
+osg::Vec3
+NormalMap::getNormalByUV(double u, double v) const
+{
+    if (!_read) return osg::Vec3(0,0,1);
+
+    double c = u * (double)(s()-1);
+    double r = v * (double)(t()-1);
+
+    unsigned rowMin = osg::maximum((int)floor(r), 0);
+    unsigned rowMax = osg::maximum(osg::minimum((int)ceil(r), (int)(t() - 1)), 0);
+    unsigned colMin = osg::maximum((int)floor(c), 0);
+    unsigned colMax = osg::maximum(osg::minimum((int)ceil(c), (int)(s() - 1)), 0);
+    
+    if (rowMin > rowMax) rowMin = rowMax;
+    if (colMin > colMax) colMin = colMax;
+
+    osg::Vec3 ur = getNormal(colMax, rowMax);
+    osg::Vec3 ll = getNormal(colMin, rowMin);
+    osg::Vec3 ul = getNormal(colMin, rowMax);
+    osg::Vec3 lr = getNormal(colMax, rowMin);
+
+    osg::Vec3 result;
+
+    // Bilinear:
+    //if (interpolation == INTERP_BILINEAR)
+    {
+        //Check for exact value
+        if ((colMax == colMin) && (rowMax == rowMin))
+        {
+            // exact
+            result = getNormal(colMin, rowMin);
+        }
+        else if (colMax == colMin)
+        {
+            //Linear interpolate vertically
+            result = ll*((double)rowMax - r) + ul*(r - (double)rowMin);
+        }
+        else if (rowMax == rowMin)
+        {
+            //Linear interpolate horizontally
+            result = ll*((double)colMax - c) + lr*(c - (double)colMin);
+        }
+        else
+        {
+            //Bilinear interpolate
+            osg::Vec3 n1 = ll*((double)colMax - c) + lr*(c - (double)colMin);
+            osg::Vec3 n2 = ul*((double)colMax - c) + ur*(c - (double)colMin);
+            result = n1*((double)rowMax - r) + n2*(r - (double)rowMin);
+        }
+    }
+
+    result.normalize();
+    return result;
+}
+
+float
+NormalMap::getCurvature(unsigned s, unsigned t) const
+{
+    if (!_read) return 0.0f;
+    return (*_read)(s, t).a() * 2.0f - 1.0f;
+}
 
 /***************************************************************************/
 
@@ -2010,7 +2246,25 @@ _extent     ( extent ),
 _minHeight( FLT_MAX ),
 _maxHeight( -FLT_MAX )
 {
-    if ( _heightField.valid() && extent.isInvalid() )
+    init();
+}
+
+GeoHeightField::GeoHeightField(osg::HeightField* heightField,
+                               NormalMap*        normalMap,
+                               const GeoExtent&  extent) :
+_heightField( heightField ),
+_normalMap  ( normalMap ),
+_extent     ( extent ),
+_minHeight  ( FLT_MAX ),
+_maxHeight  ( -FLT_MAX )
+{
+    init();
+}
+
+void
+GeoHeightField::init()
+{
+    if ( _heightField.valid() && _extent.isInvalid() )
     {
         OE_WARN << LC << "Created with a valid heightfield AND INVALID extent" << std::endl;
     }
@@ -2041,6 +2295,24 @@ GeoHeightField::valid() const
     return _heightField.valid() && _extent.isValid();
 }
 
+float
+GeoHeightField::getElevation(double x, double y) const
+{
+    return HeightFieldUtils::getHeightAtLocation(
+        _heightField.get(),
+        x, y,
+        _extent.xMin(), _extent.yMin(),
+        _heightField->getXInterval(), _heightField->getYInterval(),
+        INTERP_BILINEAR);
+}
+
+osg::Vec3
+GeoHeightField::getNormal(double x, double y) const
+{
+    return !_normalMap.valid() ? osg::Vec3(0,0,1) :
+        _normalMap->getNormalByUV((x - _extent.xMin()) / _extent.width(), (y - _extent.yMin()) / _extent.height());
+}
+
 bool
 GeoHeightField::getElevation(const SpatialReference* inputSRS, 
                              double                  x, 
@@ -2055,6 +2327,71 @@ GeoHeightField::getElevation(const SpatialReference* inputSRS,
 
 
     // first xform the input point into our local SRS:
+    if (inputSRS != extentSRS)
+    {
+        if (inputSRS && !inputSRS->transform(xy, extentSRS, local))
+            return false;
+    }
+
+    // check that the point falls within the heightfield bounds:
+    if ( _extent.contains(local.x(), local.y()) )
+    {
+        double xInterval = _extent.width()  / (double)(_heightField->getNumColumns()-1);
+        double yInterval = _extent.height() / (double)(_heightField->getNumRows()-1);
+
+        // sample the heightfield at the input coordinates:
+        // (note: since it's sampling the HF, it will return an MSL height if applicable)
+        out_elevation = HeightFieldUtils::getHeightAtLocation(
+            _heightField.get(), 
+            local.x(), local.y(),
+            _extent.xMin(), _extent.yMin(), 
+            xInterval, yInterval, 
+            interp);
+
+        // if the vertical datums don't match, do a conversion:
+        if (out_elevation != NO_DATA_VALUE && 
+            outputSRS && 
+            !extentSRS->isVertEquivalentTo(outputSRS) )
+        {
+            // if the caller provided a custom output SRS, perform the appropriate
+            // Z transformation. This requires a lat/long point:
+
+            osg::Vec3d geolocal(local);
+            if ( !extentSRS->isGeographic() )
+            {
+                extentSRS->transform(geolocal, extentSRS->getGeographicSRS(), geolocal);
+            }
+
+            VerticalDatum::transform(
+                extentSRS->getVerticalDatum(),
+                outputSRS->getVerticalDatum(),
+                geolocal.y(), geolocal.x(), out_elevation);
+        }
+
+        return true;
+    }
+    else
+    {
+        out_elevation = 0.0f;
+        return false;
+    }
+}
+
+bool
+GeoHeightField::getElevationAndNormal(const SpatialReference* inputSRS,
+                                      double                  x,
+                                      double                  y,
+                                      ElevationInterpolation  interp,
+                                      const SpatialReference* outputSRS,
+                                      float&                  out_elevation,
+                                      osg::Vec3f&             out_normal) const
+{
+    osg::Vec3d xy(x, y, 0);
+    osg::Vec3d local = xy;
+    const SpatialReference* extentSRS = _extent.getSRS();
+
+
+    // first xform the input point into our local SRS:
     if ( inputSRS && !inputSRS->transform(xy, extentSRS, local) )
         return false;
 
@@ -2093,11 +2430,34 @@ GeoHeightField::getElevation(const SpatialReference* inputSRS,
                 geolocal.y(), geolocal.x(), out_elevation);
         }
 
+        // If we have a normal map, use it; if not, attempt to generate a normal
+        // by sampling the heightfield.
+        if (_normalMap.valid())
+        {
+            //OE_INFO << "Normal Map Exists\n";
+            double nx = osg::clampBetween((local.x() - _extent.xMin()) / _extent.width(), 0.0, 1.0);
+            double ny = osg::clampBetween((local.y() - _extent.yMin()) / _extent.height(), 0.0, 1.0);
+            out_normal = _normalMap->getNormalByUV(nx, ny);
+        }
+        else
+        {
+            HeightFieldNeighborhood hood;
+            hood.setNeighbor(0, 0, _heightField.get());
+
+            // calculate the normal at the same location:
+            out_normal = HeightFieldUtils::getNormalAtLocation(
+                hood,
+                local.x(), local.y(),
+                _extent.xMin(), _extent.yMin(), 
+                xInterval, yInterval);
+        }
+
         return true;
     }
     else
     {
         out_elevation = 0.0f;
+        out_normal.set(0.0f, 0.0f, 1.0f);
         return false;
     }
 }
@@ -2126,9 +2486,9 @@ GeoHeightField::createSubSample( const GeoExtent& destEx, unsigned int width, un
     double xstep = div / (double)(width-1);
     double ystep = div / (double)(height-1);
     
-    for( x = x0, col = 0; col < width; x += xstep, col++ )
+    for( x = x0, col = 0; col < (int)width; x += xstep, col++ )
     {
-        for( y = y0, row = 0; row < height; y += ystep, row++ )
+        for( y = y0, row = 0; row < (int)height; y += ystep, row++ )
         {
             float height = HeightFieldUtils::getHeightAtNormalizedLocation(
                 _heightField.get(), x, y, interpolation );
@@ -2163,6 +2523,18 @@ GeoHeightField::takeHeightField()
     return _heightField.release();
 }
 
+NormalMap*
+GeoHeightField::getNormalMap()
+{
+    return _normalMap.get();
+}
+
+const NormalMap*
+GeoHeightField::getNormalMap() const
+{
+    return _normalMap.get();
+}
+
 double
 GeoHeightField::getXInterval() const
 {
diff --git a/src/osgEarth/GeoTransform b/src/osgEarth/GeoTransform
index 25b0639..4f91030 100644
--- a/src/osgEarth/GeoTransform
+++ b/src/osgEarth/GeoTransform
@@ -71,6 +71,7 @@ namespace osgEarth
          * a reference terrain (see setTerrain).
          */
         void setAutoRecomputeHeights(bool value);
+        bool getAutoRecomputeHeights() const { return _autoRecomputeHeights; }
 
     public:
         /** Callback that lets the user intercept the compute*Matrix functions during a traversal. */
@@ -99,6 +100,12 @@ namespace osgEarth
                 : osg::MatrixTransform::computeWorldToLocalMatrix(m, nv);
         }
 
+
+    public: // osg::Node
+
+        virtual void traverse(osg::NodeVisitor& nv);
+
+
     public: // TerrainCallback interface
 
         // called when new data pages in and autoRecompute is true
@@ -106,13 +113,16 @@ namespace osgEarth
                          osg::Node*              node,
                          TerrainCallbackContext& context);
 
+
     protected:
         virtual ~GeoTransform() { }
 
-        GeoPoint                   _position;
-        osg::observer_ptr<Terrain> _terrain;
-        bool                       _autoRecompute;
-        bool                       _autoRecomputeReady;
+        GeoPoint                   _position;                 // Current position
+        osg::observer_ptr<Terrain> _terrain;                  // Terrain for relative height resolution
+        bool                       _terrainCallbackInstalled; // Whether the Terrain callback is in
+        bool                       _findTerrain;              // True is we need _terrain but don't have it
+        bool                       _autoRecomputeHeights;     // Whether to resolve relative position Z's
+        bool                       _dirtyClamp;               // Whether a terrain clamp is required
 
         osg::ref_ptr<ComputeMatrixCallback> _computeMatrixCallback;
 
diff --git a/src/osgEarth/GeoTransform.cpp b/src/osgEarth/GeoTransform.cpp
index afb4437..a784495 100644
--- a/src/osgEarth/GeoTransform.cpp
+++ b/src/osgEarth/GeoTransform.cpp
@@ -18,6 +18,8 @@
  */
 #include <osgEarth/GeoTransform>
 #include <osgEarth/Terrain>
+#include <osgEarth/MapNode>
+#include <osgEarth/NodeUtils>
 
 #define LC "[GeoTransform] "
 
@@ -26,39 +28,39 @@
 using namespace osgEarth;
 
 GeoTransform::GeoTransform() :
-_autoRecompute     ( false ),
-_autoRecomputeReady( false )
+_findTerrain(false),
+_terrainCallbackInstalled(false),
+_autoRecomputeHeights(true),
+_dirtyClamp(false)
 {
    //nop
 }
 
-GeoTransform::GeoTransform(const GeoTransform& rhs,
-                           const osg::CopyOp&  op) :
+GeoTransform::GeoTransform(const GeoTransform& rhs, const osg::CopyOp& op) :
 osg::MatrixTransform(rhs, op)
 {
-    _position           = rhs._position;
-    _terrain            = rhs._terrain.get();
-    _autoRecompute      = rhs._autoRecompute;
-    _autoRecomputeReady = false;
+    _position = rhs._position;
+    _terrain = rhs._terrain.get();
+    _autoRecomputeHeights = rhs._autoRecomputeHeights;
+    _terrainCallbackInstalled = false;
+    _findTerrain = false;
+    _dirtyClamp = rhs._dirtyClamp;
 }
 
 void
 GeoTransform::setTerrain(Terrain* terrain)
 {
     _terrain = terrain;
-
-    // Change in the terrain means we need to recompute the position
-    // if one is set.
-    if ( _position.isValid() )
-        setPosition( _position );
+    setPosition(_position);
 }
 
 void
 GeoTransform::setAutoRecomputeHeights(bool value)
 {
-    if (value != _autoRecompute)
+    if (value != _autoRecomputeHeights)
     {
-        _autoRecompute = value;
+        _autoRecomputeHeights = value;
+        setPosition(_position);
     }
 }
 
@@ -74,26 +76,33 @@ GeoTransform::setPosition(const GeoPoint& position)
     if ( !position.isValid() )
         return false;
 
+    bool result = true;
+
     _position = position;
 
     // relative Z or reprojection require a terrain:
     osg::ref_ptr<Terrain> terrain;
     _terrain.lock(terrain);
 
-    // relative Z requires a terrain:
-    if (position.altitudeMode() == ALTMODE_RELATIVE && !terrain.valid())
+    // If we don't have a pointer to a terrain, schedule an attempt
+    // to find one on the next update traversal.
+    if (!terrain.valid() && !_findTerrain)
     {
-        OE_TEST << LC << "setPosition failed condition 1\n";
-        return false;
+        _findTerrain = true;
+        ADJUST_UPDATE_TRAV_COUNT(this, +1);
     }
 
     GeoPoint p;
 
     // transform into terrain SRS if neccesary:
     if (terrain.valid() && !terrain->getSRS()->isEquivalentTo(position.getSRS()))
+    {
         p = position.transform(terrain->getSRS());
+    }
     else
+    {
         p = position;
+    }
 
     // bail if the transformation failed:
     if ( !p.isValid() )
@@ -102,31 +111,30 @@ GeoTransform::setPosition(const GeoPoint& position)
         return false;
     }
 
-    // convert to absolute height:
-    if ( !p.makeAbsolute(_terrain.get()) )
+    // Convert the point to an absolute Z if necessry. If we don't have
+    // a terrain, skip and hope for the best.
+    if (terrain.valid())
     {
-        OE_TEST << LC << "setPosition failed condition 3\n";
-        return false;
+        result = p.makeAbsolute(terrain.get()) && result;
+    }
+
+    // Is this is a relative-Z position, we need to install a terrain callback
+    // so we can recompute the altitude when new terrain tiles become available.
+    if (_position.altitudeMode() == ALTMODE_RELATIVE &&
+        _autoRecomputeHeights &&
+        !_terrainCallbackInstalled &&
+        terrain.valid())
+    {
+        // The Adapter template auto-destructs, so we never need to remote it manually.
+        terrain->addTerrainCallback( new TerrainCallbackAdapter<GeoTransform>(this) );
+        _terrainCallbackInstalled = true;
     }
 
-    // assemble the matrix:
+    // Finally, assemble the matrix from our position point.
     osg::Matrixd local2world;
     p.createLocalToWorld( local2world );
     this->setMatrix( local2world );
 
-    // install auto-recompute?
-    if (_autoRecompute &&
-        _position.altitudeMode() == ALTMODE_RELATIVE &&
-        !_autoRecomputeReady)
-    {
-        // by using the adapter, there's no need to remove
-        // the callback then this object destructs.
-        terrain->addTerrainCallback(
-           new TerrainCallbackAdapter<GeoTransform>(this) );
-
-        _autoRecomputeReady = true;
-    }
-
     return true;
 }
 
@@ -135,19 +143,53 @@ GeoTransform::onTileAdded(const TileKey&          key,
                           osg::Node*              node,
                           TerrainCallbackContext& context)
 {
-   if (!_position.isValid() || _position.altitudeMode() != ALTMODE_RELATIVE)
-   {
-       OE_TEST << LC << "onTileAdded fail condition 1\n";
-       return;
-   }
-
-   if (!key.getExtent().contains(_position))
-   {
-       OE_DEBUG << LC << "onTileAdded fail condition 2\n";
-       return;
-   }
-
-   setPosition(_position);
+    if (!_dirtyClamp)
+    {
+       if (!_position.isValid() || _position.altitudeMode() != ALTMODE_RELATIVE || !_autoRecomputeHeights)
+       {
+           OE_TEST << LC << "onTileAdded fail condition 1\n";
+           return;
+       }
+
+       if (key.valid() && !key.getExtent().contains(_position))
+       {
+           OE_TEST << LC << "onTileAdded fail condition 2\n";
+           return;
+       }
+
+       _dirtyClamp = true;
+       ADJUST_UPDATE_TRAV_COUNT(this, +1);
+    }
+
+    //setPosition(_position);
+}
+
+void
+GeoTransform::traverse(osg::NodeVisitor& nv)
+{
+    if (nv.getVisitorType() == nv.UPDATE_VISITOR)
+    {
+        if (_findTerrain)
+        {
+            MapNode* mapNode = osgEarth::findInNodePath<MapNode>(nv);
+            if (mapNode)
+            {
+                _findTerrain = false;
+                ADJUST_UPDATE_TRAV_COUNT(this, -1);
+                setTerrain(mapNode->getTerrain());
+                OE_DEBUG << LC << "Discovered terrain.\n";
+            }
+        }
+
+        if (_dirtyClamp)
+        {
+            setPosition(_position);
+            _dirtyClamp = false;
+            ADJUST_UPDATE_TRAV_COUNT(this, -1);
+        }
+    }
+
+    osg::MatrixTransform::traverse(nv);
 }
 
 void
@@ -156,6 +198,7 @@ GeoTransform::setComputeMatrixCallback(GeoTransform::ComputeMatrixCallback* cb)
     _computeMatrixCallback = cb;
 }
 
+
 bool
 GeoTransform::ComputeMatrixCallback::computeLocalToWorldMatrix(const GeoTransform* xform, osg::Matrix& m, osg::NodeVisitor* nv) const
 {
diff --git a/src/osgEarth/GeometryClamper b/src/osgEarth/GeometryClamper
index c033fd5..ca7c2ff 100644
--- a/src/osgEarth/GeometryClamper
+++ b/src/osgEarth/GeometryClamper
@@ -23,7 +23,7 @@
 #include <osgEarth/Common>
 #include <osgEarth/SpatialReference>
 #include <osgEarth/Terrain>
-#include <osgEarth/DPLineSegmentIntersector>
+#include <osgUtil/LineSegmentIntersector>
 #include <osg/NodeVisitor>
 #include <osg/fast_back_stack>
 
@@ -32,6 +32,7 @@ namespace osgEarth
     /**
      * Utility that takes existing OSG geometry and modifies it so that
      * it "conforms" with a terrain patch.
+     * TODO: Consider relocating this into Annotation namespace
      */
     class OSGEARTH_EXPORT GeometryClamper : public osg::NodeVisitor
     {
@@ -57,7 +58,7 @@ namespace osgEarth
 
     public: // osg::NodeVisitor
 
-        void apply( osg::Geode& );
+        void apply( osg::Drawable& );
         void apply( osg::Transform& );
 
     protected:
@@ -68,7 +69,7 @@ namespace osgEarth
         float                                _scale;
         float                                _offset;
         osg::fast_back_stack<osg::Matrixd>   _matrixStack;
-        osg::ref_ptr<DPLineSegmentIntersector> _lsi;
+        osg::ref_ptr<osgUtil::LineSegmentIntersector> _lsi;
     };
 
 
diff --git a/src/osgEarth/GeometryClamper.cpp b/src/osgEarth/GeometryClamper.cpp
index 686a434..8a6dab1 100644
--- a/src/osgEarth/GeometryClamper.cpp
+++ b/src/osgEarth/GeometryClamper.cpp
@@ -39,7 +39,7 @@ _scale          ( 1.0f ),
 _offset         ( 0.0f )
 {
     this->setNodeMaskOverride( ~0 );
-    _lsi = new osgEarth::DPLineSegmentIntersector(osg::Vec3d(0,0,0), osg::Vec3d(0,0,0));
+    _lsi = new osgUtil::LineSegmentIntersector(osg::Vec3d(0,0,0), osg::Vec3d(0,0,0));
 }
 
 void
@@ -54,11 +54,15 @@ GeometryClamper::apply(osg::Transform& xform)
 }
 
 void
-GeometryClamper::apply(osg::Geode& geode)
+GeometryClamper::apply(osg::Drawable& drawable)
 {
     if ( !_terrainSRS.valid() )
         return;
 
+    osg::Geometry* geom = drawable.asGeometry();
+    if ( !geom )
+        return;
+
     const osg::Matrixd& local2world = _matrixStack.back();
     osg::Matrix world2local;
     world2local.invert( local2world );
@@ -74,113 +78,106 @@ GeometryClamper::apply(osg::Geode& geode)
 
     unsigned count = 0;
 
-    for( unsigned i=0; i<geode.getNumDrawables(); ++i )
+    bool geomDirty = false;
+    osg::Vec3Array*  verts = static_cast<osg::Vec3Array*>(geom->getVertexArray());
+    osg::FloatArray* zOffsets = 0L;
+
+    // if preserve-Z is on, check for our elevations array. Create it if is doesn't
+    // already exist.
+    bool buildZOffsets = false;
+    if ( _preserveZ )
     {
-        bool geomDirty = false;
-        osg::Geometry* geom = geode.getDrawable(i)->asGeometry();
-        if ( geom )
+        osg::UserDataContainer* udc = geom->getOrCreateUserDataContainer();
+        unsigned n = udc->getUserObjectIndex( ZOFFSETS_NAME );
+        if ( n < udc->getNumUserObjects() )
         {
-            osg::Vec3Array*  verts = static_cast<osg::Vec3Array*>(geom->getVertexArray());
-            osg::FloatArray* zOffsets = 0L;
+            zOffsets = dynamic_cast<osg::FloatArray*>(udc->getUserObject(n));
+        }
 
-            // if preserve-Z is on, check for our elevations array. Create it if is doesn't
-            // already exist.
-            bool buildZOffsets = false;
-            if ( _preserveZ )
-            {
-                osg::UserDataContainer* udc = geom->getOrCreateUserDataContainer();
-                unsigned n = udc->getUserObjectIndex( ZOFFSETS_NAME );
-                if ( n < udc->getNumUserObjects() )
-                {
-                    zOffsets = dynamic_cast<osg::FloatArray*>(udc->getUserObject(n));
-                }
+        else
+        {
+            zOffsets = new osg::FloatArray();
+            zOffsets->setName( ZOFFSETS_NAME );
+            zOffsets->reserve( verts->size() );
+            udc->addUserObject( zOffsets );
+            buildZOffsets = true;
+        }
+    }
 
-                else
-                {
-                    zOffsets = new osg::FloatArray();
-                    zOffsets->setName( ZOFFSETS_NAME );
-                    zOffsets->reserve( verts->size() );
-                    udc->addUserObject( zOffsets );
-                    buildZOffsets = true;
-                }
-            }
+    for( unsigned k=0; k<verts->size(); ++k )
+    {
+        osg::Vec3d vw = (*verts)[k];
+        vw = vw * local2world;
+
+        if ( isGeocentric )
+        {
+            // normal to the ellipsoid:
+            n_vector = em->computeLocalUpVector(vw.x(),vw.y(),vw.z());
 
-            for( unsigned k=0; k<verts->size(); ++k )
+            // if we need to build to z-offsets array, calculate the z offset now:
+            if ( buildZOffsets || _scale != 1.0 )
             {
-                osg::Vec3d vw = (*verts)[k];
-                vw = vw * local2world;
+                double lat,lon,hae;
+                em->convertXYZToLatLongHeight(vw.x(), vw.y(), vw.z(), lat, lon, hae);
 
-                if ( isGeocentric )
+                if ( buildZOffsets )
                 {
-                    // normal to the ellipsoid:
-                    n_vector = em->computeLocalUpVector(vw.x(),vw.y(),vw.z());
-
-                    // if we need to build to z-offsets array, calculate the z offset now:
-                    if ( buildZOffsets || _scale != 1.0 )
-                    {
-                        double lat,lon,hae;
-                        em->convertXYZToLatLongHeight(vw.x(), vw.y(), vw.z(), lat, lon, hae);
-
-                        if ( buildZOffsets )
-                        {
-                            zOffsets->push_back( (*verts)[k].z() );
-                        }
-
-                        if ( _scale != 1.0 )
-                        {
-                            msl = vw - n_vector*hae;
-                        }
-                    }
+                    zOffsets->push_back( hae ); //(*verts)[k].z() );
                 }
 
-                else if ( buildZOffsets ) // flat map
+                if ( _scale != 1.0 )
                 {
-                    zOffsets->push_back( float(vw.z()) );
+                    msl = vw - n_vector*hae;
                 }
+            }
+        }
 
-                _lsi->reset();
-                _lsi->setStart( vw + n_vector*r*_scale );
-                _lsi->setEnd( vw - n_vector*r );
-                _lsi->setIntersectionLimit( _lsi->LIMIT_NEAREST );
+        else if ( buildZOffsets ) // flat map
+        {
+            zOffsets->push_back( float(vw.z()) );
+        }
 
-                _terrainPatch->accept( iv );
+        _lsi->reset();
+        _lsi->setStart( vw + n_vector*r*_scale );
+        _lsi->setEnd( vw - n_vector*r );
+        _lsi->setIntersectionLimit( _lsi->LIMIT_NEAREST );
 
-                if ( _lsi->containsIntersections() )
-                {
-                    osg::Vec3d fw = _lsi->getFirstIntersection().getWorldIntersectPoint();
-                    if ( _scale != 1.0 )
-                    {
-                        osg::Vec3d delta = fw - msl;
-                        fw += delta*_scale;
-                    }
-                    if ( _offset != 0.0 )
-                    {
-                        fw += n_vector*_offset;
-                    }
-                    if ( _preserveZ && (zOffsets != 0L) )
-                    {
-                        fw += n_vector * (*zOffsets)[k];
-                    }
-
-                    (*verts)[k] = (fw * world2local);
-                    geomDirty = true;
-                    ++count;
-                }
-            }
+        _terrainPatch->accept( iv );
 
-            if ( geomDirty )
+        if ( _lsi->containsIntersections() )
+        {
+            osg::Vec3d fw = _lsi->getFirstIntersection().getWorldIntersectPoint();
+            if ( _scale != 1.0 )
             {
-                geom->dirtyBound();
-                if ( geom->getUseVertexBufferObjects() )
-                {
-                    verts->getVertexBufferObject()->setUsage( GL_DYNAMIC_DRAW_ARB );
-                    verts->dirty();
-                }
-                else
-                {
-                    geom->dirtyDisplayList();
-                }
+                osg::Vec3d delta = fw - msl;
+                fw += delta*_scale;
+            }
+            if ( _offset != 0.0 )
+            {
+                fw += n_vector*_offset;
+            }
+            if ( _preserveZ && (zOffsets != 0L) )
+            {
+                fw += n_vector * (*zOffsets)[k];
             }
+
+            (*verts)[k] = (fw * world2local);
+            geomDirty = true;
+            ++count;
+        }
+    }
+
+    if ( geomDirty )
+    {
+        geom->dirtyBound();
+        if ( geom->getUseVertexBufferObjects() )
+        {
+            verts->getVertexBufferObject()->setUsage( GL_DYNAMIC_DRAW_ARB );
+            verts->dirty();
+        }
+        else
+        {
+            geom->dirtyDisplayList();
         }
 
         OE_DEBUG << LC << "clamped " << count << " verts." << std::endl;
@@ -188,7 +185,6 @@ GeometryClamper::apply(osg::Geode& geode)
 }
 
 
-
 void
 GeometryClamperCallback::onTileAdded(const TileKey&          key, 
                                      osg::Node*              tile, 
diff --git a/src/osgEarth/HTTPClient b/src/osgEarth/HTTPClient
index bd64272..777ffdc 100644
--- a/src/osgEarth/HTTPClient
+++ b/src/osgEarth/HTTPClient
@@ -99,8 +99,10 @@ namespace osgEarth
         /** Ready-only access to the parameter list (as built with addParameter) */
         const Parameters& getParameters() const;        
 
+        //! Add a head name/value pair to an HTTP request
         void addHeader( const std::string& name, const std::string& value );
 
+        //! Collection of headers in this request
         const Headers& getHeaders() const;
 
         /**
@@ -173,7 +175,9 @@ namespace osgEarth
         const std::string& getMimeType() const;
 
         /** How long did it take to fetch this response (in seconds) */
-        double getDuration() const { return _duration_s; }        
+        double getDuration() const { return _duration_s; }     
+
+        const std::string& getMessage() const { return _message; }
 
     private:
         struct Part : public osg::Referenced
@@ -190,6 +194,7 @@ namespace osgEarth
         bool        _cancelled;
         double      _duration_s;
         TimeStamp   _lastModified;
+        std::string _message;
 
         Config getHeadersAsConfig() const;
 
diff --git a/src/osgEarth/HTTPClient.cpp b/src/osgEarth/HTTPClient.cpp
index eeececa..55bc4cb 100644
--- a/src/osgEarth/HTTPClient.cpp
+++ b/src/osgEarth/HTTPClient.cpp
@@ -1,1658 +1,1704 @@
-/* -*-c++-*- */
-/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2016 Pelican Mapping
- * http://osgearth.org
- *
- * osgEarth is free software; you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>
- */
-#include <osgEarth/HTTPClient>
-#include <osgEarth/Registry>
-#include <osgEarth/Version>
-#include <osgEarth/Progress>
-#include <osgEarth/StringUtils>
-#include <osgDB/ReadFile>
-#include <osgDB/Registry>
-#include <osgDB/FileNameUtils>
-#include <osg/Notify>
-#include <osg/Timer>
-#include <string.h>
-#include <sstream>
-#include <fstream>
-#include <iterator>
-#include <iostream>
-#include <algorithm>
-#include <curl/curl.h>
-
-// Whether to use WinInet instead of cURL - CMAKE option
-#ifdef OSGEARTH_USE_WININET_FOR_HTTP
-#include <WinInet.h>
-#pragma comment(lib, "wininet.lib")
-#endif
-
-#define LC "[HTTPClient] "
-
-//#define OE_TEST OE_NOTICE
-#define OE_TEST OE_NULL
-
-using namespace osgEarth;
-
-//----------------------------------------------------------------------------
-
-ProxySettings::ProxySettings( const Config& conf )
-{
-    mergeConfig( conf );
-}
-
-ProxySettings::ProxySettings( const std::string& host, int port ) :
-_hostName(host),
-_port(port)
-{
-    //nop
-}
-
-void
-ProxySettings::mergeConfig( const Config& conf )
-{
-    _hostName = conf.value<std::string>( "host", "" );
-    _port = conf.value<int>( "port", 8080 );
-    _userName = conf.value<std::string>( "username", "" );
-    _password = conf.value<std::string>( "password", "" );
-}
-
-Config
-ProxySettings::getConfig() const
-{
-    Config conf( "proxy" );
-    conf.add( "host", _hostName );
-    conf.add( "port", toString(_port) );
-    conf.add( "username", _userName);
-    conf.add( "password", _password);
-
-    return conf;
-}
-
-bool
-ProxySettings::fromOptions( const osgDB::Options* dbOptions, optional<ProxySettings>& out )
-{
-    if ( dbOptions )
-    {
-        std::string jsonString = dbOptions->getPluginStringData( "osgEarth::ProxySettings" );
-        if ( !jsonString.empty() )
-        {
-            Config conf;
-            conf.fromJSON( jsonString );
-            out = ProxySettings( conf );
-            return true;
-        }
-    }
-    return false;
-}
-
-void
-ProxySettings::apply( osgDB::Options* dbOptions ) const
-{
-    if ( dbOptions )
-    {
-        Config conf = getConfig();
-        dbOptions->setPluginStringData( "osgEarth::ProxySettings", conf.toJSON() );
-    }
-}
-
-/****************************************************************************/
-   
-namespace osgEarth
-{
-    struct StreamObject
-    {
-        StreamObject(std::ostream* stream) : _stream(stream) { }
-
-        void write(const char* ptr, size_t realsize)
-        {
-            if (_stream) _stream->write(ptr, realsize);
-        }
-
-        void writeHeader(const char* ptr, size_t realsize)
-        {            
-            std::string header(ptr);            
-            StringTokenizer tok(":");
-            StringVector tized;
-            tok.tokenize(header, tized);            
-            if ( tized.size() >= 2 )
-                _headers[tized[0]] = tized[1];                
-        }
-
-        std::ostream* _stream;
-        Headers _headers;
-        std::string     _resultMimeType;
-    };
-
-    static size_t
-    StreamObjectReadCallback(void* ptr, size_t size, size_t nmemb, void* data)
-    {
-        size_t realsize = size* nmemb;
-        StreamObject* sp = (StreamObject*)data;
-        sp->write((const char*)ptr, realsize);
-        return realsize;
-    }
-
-    static size_t
-    StreamObjectHeaderCallback(void* ptr, size_t size, size_t nmemb, void* data)
-    {
-        size_t realsize = size* nmemb;
-        StreamObject* sp = (StreamObject*)data;                
-        sp->writeHeader((const char*)ptr, realsize);        
-        return realsize;
-    }
-
-    TimeStamp
-    getCurlFileTime(void* curl)
-    {
-        long filetime;
-        if (CURLE_OK != curl_easy_getinfo(curl, CURLINFO_FILETIME, &filetime))
-            return TimeStamp(0);
-        else if (filetime < 0)
-            return TimeStamp(0);
-        else
-            return TimeStamp(filetime);
-    }
-}
-
-static int CurlProgressCallback(void *clientp,double dltotal,double dlnow,double ultotal,double ulnow)
-{
-    ProgressCallback* callback = (ProgressCallback*)clientp;
-    bool cancelled = false;
-    if (callback)
-    {
-        cancelled = callback->isCanceled() || callback->reportProgress(dlnow, dltotal);
-    }
-    return cancelled;
-}
-
-/****************************************************************************/
-
-HTTPRequest::HTTPRequest( const std::string& url )
-: _url( url )
-{
-    //NOP
-}
-
-HTTPRequest::HTTPRequest( const HTTPRequest& rhs ) :
-_parameters( rhs._parameters ),
-_headers(rhs._headers),
-_url( rhs._url )
-{
-    //nop
-}
-
-void
-HTTPRequest::addParameter( const std::string& name, const std::string& value )
-{
-    _parameters[name] = value;
-}
-
-void
-HTTPRequest::addParameter( const std::string& name, int value )
-{
-    std::stringstream buf;
-    buf << value;
-     std::string bufStr;
-    bufStr = buf.str();
-    _parameters[name] = bufStr;
-}
-
-void
-HTTPRequest::addParameter( const std::string& name, double value )
-{
-    std::stringstream buf;
-    buf << value;
-     std::string bufStr;
-    bufStr = buf.str();
-    _parameters[name] = bufStr;
-}
-
-const HTTPRequest::Parameters&
-HTTPRequest::getParameters() const
-{
-    return _parameters; 
-}
-
-void
-HTTPRequest::addHeader( const std::string& name, const std::string& value )
-{
-    _headers[name] = value;
-}
-
-const Headers&
-HTTPRequest::getHeaders() const
-{
-    return _headers; 
-}
-
-void HTTPRequest::setLastModified( const DateTime &lastModified)
-{    
-    addHeader("If-Modified-Since", lastModified.asRFC1123());
-}
-
-
-std::string
-HTTPRequest::getURL() const
-{
-    if ( _parameters.size() == 0 )
-    {
-        return _url;
-    }
-    else
-    {
-        std::stringstream buf;
-        buf << _url;
-        for( Parameters::const_iterator i = _parameters.begin(); i != _parameters.end(); i++ )
-        {
-            buf << ( i == _parameters.begin() && _url.find( "?" ) == std::string::npos? "?" : "&" );
-            buf << i->first << "=" << i->second;
-        }
-         std::string bufStr;
-         bufStr = buf.str();
-        return bufStr;
-    }
-}
-
-/****************************************************************************/
-
-HTTPResponse::HTTPResponse( long _code ) :
-_response_code( _code ),
-_cancelled(false),
-_duration_s(0.0),
-_lastModified(0u)
-{
-    _parts.reserve(1);
-}
-
-HTTPResponse::HTTPResponse( const HTTPResponse& rhs ) :
-_response_code( rhs._response_code ),
-_parts( rhs._parts ),
-_mimeType( rhs._mimeType ),
-_cancelled( rhs._cancelled ),
-_duration_s(0.0),
-_lastModified(0u)
-{
-    //nop
-}
-
-unsigned
-HTTPResponse::getCode() const {
-    return _response_code;
-}
-
-bool
-HTTPResponse::isOK() const {
-    return _response_code == 200L && !isCancelled();
-}
-
-bool
-HTTPResponse::isCancelled() const {
-    return _cancelled;
-}
-
-unsigned int
-HTTPResponse::getNumParts() const {
-    return _parts.size();
-}
-
-unsigned int
-HTTPResponse::getPartSize( unsigned int n ) const {
-    return _parts[n]->_size;
-}
-
-const std::string&
-HTTPResponse::getPartHeader( unsigned int n, const std::string& name ) const {
-    return _parts[n]->_headers[name];
-}
-
-std::istream&
-HTTPResponse::getPartStream( unsigned int n ) const {
-    return _parts[n]->_stream;
-}
-
-std::string
-HTTPResponse::getPartAsString( unsigned int n ) const {
-    std::string streamStr;
-    streamStr = _parts[n]->_stream.str();    
-    return streamStr;
-}
-
-const std::string&
-HTTPResponse::getMimeType() const {
-    return _mimeType;
-}
-
-Config
-HTTPResponse::getHeadersAsConfig() const
-{
-    Config conf;
-    if ( _parts.size() > 0 )
-    {
-        for( Headers::const_iterator i = _parts[0]->_headers.begin(); i != _parts[0]->_headers.end(); ++i )
-        {
-            conf.set(i->first, i->second);
-        }
-    }
-    return conf;
-}
-
-/****************************************************************************/
-
-#define QUOTE_(X) #X
-#define QUOTE(X) QUOTE_(X)
-#define USER_AGENT "osgearth" QUOTE(OSGEARTH_MAJOR_VERSION) "." QUOTE(OSGEARTH_MINOR_VERSION)
-
-
-namespace
-{
-    // TODO: consider moving this stuff into the osgEarth::Registry;
-    // don't like it here in the global scope
-    // per-thread client map (must be global scope)
-    static PerThread<HTTPClient>       s_clientPerThread;
-
-    static optional<ProxySettings>     s_proxySettings;
-
-    static std::string                 s_userAgent = USER_AGENT;
-
-    static long                        s_timeout = 0;
-    static long                        s_connectTimeout = 0;
-
-    // HTTP debugging.
-    static bool                        s_HTTP_DEBUG = false;
-    static Threading::Mutex            s_HTTP_DEBUG_mutex;
-    static int                         s_HTTP_DEBUG_request_count;
-    static double                      s_HTTP_DEBUG_total_duration;
-
-    static osg::ref_ptr< URLRewriter > s_rewriter;
-
-    static osg::ref_ptr< CurlConfigHandler > s_curlConfigHandler;
-}
-
-HTTPClient&
-HTTPClient::getClient()
-{
-    return s_clientPerThread.get();
-}
-
-HTTPClient::HTTPClient() :
-_initialized    ( false ),
-_curl_handle    ( 0L ),
-_simResponseCode( -1L ),
-_previousHttpAuthentication(0L)
-{
-    //nop
-    //do no CURL calls here.
-}
-
-void
-HTTPClient::initialize() const
-{
-    if ( !_initialized )
-    {
-        const_cast<HTTPClient*>(this)->initializeImpl();
-    }
-}
-
-void
-HTTPClient::initializeImpl()
-{
-    _previousHttpAuthentication = 0;
-    _curl_handle = curl_easy_init();
-
-    //Get the user agent
-    std::string userAgent = s_userAgent;
-    const char* userAgentEnv = getenv("OSGEARTH_USERAGENT");
-    if (userAgentEnv)
-    {
-        userAgent = std::string(userAgentEnv);
-    }
-
-    //Check for a response-code simulation (for testing)
-    const char* simCode = getenv("OSGEARTH_SIMULATE_HTTP_RESPONSE_CODE");
-    if ( simCode )
-    {
-        _simResponseCode = osgEarth::as<long>(std::string(simCode), 404L);
-        OE_WARN << LC << "Simulating a network error with Response Code = " << _simResponseCode << std::endl;
-    }
-
-    // Check to HTTP disabling (for testing)
-    const char* disable = getenv("OSGEARTH_HTTP_DISABLE");
-    if (disable)
-    {
-        _simResponseCode = 503L; // SERVICE UNAVAILABLE
-    }
-
-    // Dumps out HTTP request/response info
-    if ( ::getenv("OSGEARTH_HTTP_DEBUG") )
-    {
-        s_HTTP_DEBUG = true;
-        OE_WARN << LC << "HTTP debugging enabled" << std::endl;
-    }
-
-    OE_DEBUG << LC << "HTTPClient setting userAgent=" << userAgent << std::endl;
-
-    curl_easy_setopt( _curl_handle, CURLOPT_USERAGENT, userAgent.c_str() );
-    curl_easy_setopt( _curl_handle, CURLOPT_WRITEFUNCTION, osgEarth::StreamObjectReadCallback );
-    curl_easy_setopt( _curl_handle, CURLOPT_HEADERFUNCTION, osgEarth::StreamObjectHeaderCallback );
-    curl_easy_setopt( _curl_handle, CURLOPT_FOLLOWLOCATION, (void*)1 );
-    curl_easy_setopt( _curl_handle, CURLOPT_MAXREDIRS, (void*)5 );
-    curl_easy_setopt( _curl_handle, CURLOPT_PROGRESSFUNCTION, &CurlProgressCallback);
-    curl_easy_setopt( _curl_handle, CURLOPT_NOPROGRESS, (void*)0 ); //0=enable.
-    curl_easy_setopt( _curl_handle, CURLOPT_FILETIME, true );
-
-    // Enable automatic CURL decompression of known types. An empty string will automatically add all supported encoding types that are built into curl.
-    // Note that you must have curl built against zlib to support gzip or deflate encoding.
-    curl_easy_setopt( _curl_handle, CURLOPT_ENCODING, "");
-
-    osg::ref_ptr< CurlConfigHandler > curlConfigHandler = getCurlConfigHandler();
-    if (curlConfigHandler.valid()) {
-        curlConfigHandler->onInitialize(_curl_handle);
-    }
-
-    long timeout = s_timeout;
-    const char* timeoutEnv = getenv("OSGEARTH_HTTP_TIMEOUT");
-    if (timeoutEnv)
-    {
-        timeout = osgEarth::as<long>(std::string(timeoutEnv), 0);
-    }
-    OE_DEBUG << LC << "Setting timeout to " << timeout << std::endl;
-    curl_easy_setopt( _curl_handle, CURLOPT_TIMEOUT, timeout );
-    long connectTimeout = s_connectTimeout;
-    const char* connectTimeoutEnv = getenv("OSGEARTH_HTTP_CONNECTTIMEOUT");
-    if (connectTimeoutEnv)
-    {
-        connectTimeout = osgEarth::as<long>(std::string(connectTimeoutEnv), 0);
-    }
-    OE_DEBUG << LC << "Setting connect timeout to " << connectTimeout << std::endl;
-    curl_easy_setopt( _curl_handle, CURLOPT_CONNECTTIMEOUT, connectTimeout );
-
-    _initialized = true;
-}
-
-HTTPClient::~HTTPClient()
-{
-    if (_curl_handle) curl_easy_cleanup( _curl_handle );
-    _curl_handle = 0;
-}
-
-void
-HTTPClient::setProxySettings( const ProxySettings& proxySettings )
-{
-    s_proxySettings = proxySettings;
-}
-
-const std::string& HTTPClient::getUserAgent()
-{
-    return s_userAgent;
-}
-
-void  HTTPClient::setUserAgent(const std::string& userAgent)
-{
-    s_userAgent = userAgent;
-}
-
-long HTTPClient::getTimeout()
-{
-    return s_timeout;
-}
-
-void HTTPClient::setTimeout( long timeout )
-{
-    s_timeout = timeout;
-}
-
-long HTTPClient::getConnectTimeout()
-{
-    return s_connectTimeout;
-}
-
-void HTTPClient::setConnectTimeout( long timeout )
-{
-    s_connectTimeout = timeout;
-}
-URLRewriter* HTTPClient::getURLRewriter()
-{
-    return s_rewriter.get();
-}
-
-void HTTPClient::setURLRewriter( URLRewriter* rewriter )
-{
-    s_rewriter = rewriter;
-}
-
-CurlConfigHandler* HTTPClient::getCurlConfigHandler()
-{
-    return s_curlConfigHandler.get();
-}
-
-void HTTPClient::setCurlConfighandler(CurlConfigHandler* handler)
-{
-    s_curlConfigHandler = handler;
-}
-
-void
-HTTPClient::globalInit()
-{
-    curl_global_init(CURL_GLOBAL_ALL);
-}
-
-void
-HTTPClient::readOptions(const osgDB::Options* options, std::string& proxy_host, std::string& proxy_port) const
-{
-    // try to set proxy host/port by reading the CURL proxy options
-    if ( options )
-    {
-        std::istringstream iss( options->getOptionString() );
-        std::string opt;
-        while( iss >> opt )
-        {
-            int index = opt.find( "=" );
-            if( opt.substr( 0, index ) == "OSG_CURL_PROXY" )
-            {
-                proxy_host = opt.substr( index+1 );
-            }
-            else if ( opt.substr( 0, index ) == "OSG_CURL_PROXYPORT" )
-            {
-                proxy_port = opt.substr( index+1 );
-            }
-        }
-    }
-}
-
-bool
-HTTPClient::decodeMultipartStream(const std::string&   boundary,
-                                  HTTPResponse::Part*  input,
-                                  HTTPResponse::Parts& output) const
-{
-    std::string bstr = std::string("--") + boundary;
-    std::string line;
-    char tempbuf[256];
-
-    // first thing in the stream should be the boundary.
-    input->_stream.read( tempbuf, bstr.length() );
-    tempbuf[bstr.length()] = 0;
-    line = tempbuf;
-    if ( line != bstr )
-    {
-        OE_INFO << LC 
-            << "decodeMultipartStream: protocol violation; "
-            << "expecting boundary; instead got: \"" 
-            << line
-            << "\"" << std::endl;
-        return false;
-    }
-
-    for( bool done=false; !done; )
-    {
-        osg::ref_ptr<HTTPResponse::Part> next_part = new HTTPResponse::Part();
-
-        // first finish off the boundary.
-        std::getline( input->_stream, line );
-        if ( line == "--" )
-        {
-            done = true;
-        }
-        else
-        {
-            // read all headers. this ends with a blank line.
-            line = " ";
-            while( line.length() > 0 && !done )
-            {
-                std::getline( input->_stream, line );
-
-                // check for EOS:
-                if ( line == "--" )
-                {
-                    done = true;
-                }
-                else
-                {                    
-                    StringTokenizer tok(":");
-                    StringVector tized;
-                    tok.tokenize(line, tized);            
-                    if ( tized.size() >= 2 )
-                        next_part->_headers[tized[0]] = tized[1];                        
-                }
-            }
-        }
-
-        if ( !done )
-        {
-            // read data until we reach the boundary
-            unsigned int bstr_ptr = 0;
-            std::string temp;
-            //unsigned int c = 0;
-            while( bstr_ptr < bstr.length() )
-            {
-                char b;
-                input->_stream.read( &b, 1 );
-                if ( b == bstr[bstr_ptr] )
-                {
-                    bstr_ptr++;
-                }
-                else
-                {
-                    for( unsigned int i=0; i<bstr_ptr; i++ )
-                    {
-                        next_part->_stream << bstr[i];
-                    }
-                    next_part->_stream << b;
-                    next_part->_size += bstr_ptr + 1;
-                    bstr_ptr = 0;
-                }
-            }
-            output.push_back( next_part.get() );
-        }
-    }
-
-    return true;
-}
-
-HTTPResponse
-HTTPClient::get( const HTTPRequest&    request,
-                 const osgDB::Options* options,
-                 ProgressCallback*     progress)
-{
-    return getClient().doGet( request, options, progress );
-}
-
-HTTPResponse 
-HTTPClient::get( const std::string&    url,
-                 const osgDB::Options* options,
-                 ProgressCallback*     progress)
-{
-    return getClient().doGet( url, options, progress);
-}
-
-ReadResult
-HTTPClient::readImage(const HTTPRequest&    request,
-                      const osgDB::Options* options,
-                      ProgressCallback*     progress)
-{
-    return getClient().doReadImage( request, options, progress );
-}
-
-ReadResult
-HTTPClient::readNode(const HTTPRequest&    request,
-                     const osgDB::Options* options,
-                     ProgressCallback*     progress)
-{
-    return getClient().doReadNode( request, options, progress );
-}
-
-ReadResult
-HTTPClient::readObject(const HTTPRequest&    request,
-                       const osgDB::Options* options,
-                       ProgressCallback*     progress)
-{
-    return getClient().doReadObject( request, options, progress );
-}
-
-ReadResult
-HTTPClient::readString(const HTTPRequest&    request,
-                       const osgDB::Options* options,
-                       ProgressCallback*     progress)
-{
-    return getClient().doReadString( request, options, progress );
-}
-
-bool
-HTTPClient::download(const std::string& uri,
-                     const std::string& localPath)
-{
-    return getClient().doDownload( uri, localPath );
-}
-
-
-#ifdef OSGEARTH_USE_WININET_FOR_HTTP
-
-namespace
-{
-    std::string GetLastErrorAsString()
-    {
-        //Get the error message, if any.
-        DWORD errorMessageID = ::GetLastError();
-        if(errorMessageID == 0)
-            return std::string("Error Code 0"); //No error message has been recorded
-
-        LPSTR messageBuffer = nullptr;
-        size_t size = FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
-                                     NULL, errorMessageID, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&messageBuffer, 0, NULL);
-
-        std::string message(messageBuffer, size);
-
-        //Free the buffer.
-        LocalFree(messageBuffer);
-
-        message = Stringify() << "[Code " << errorMessageID << "] " << message;
-
-        return message;
-    }
-}
-
-HTTPResponse
-HTTPClient::doGet(const HTTPRequest&    request,
-                  const osgDB::Options* options, 
-                  ProgressCallback*     progress) const
-{
-    OE_START_TIMER(http_get);
-
-    std::string url = request.getURL();
-    // Rewrite the url if the url rewriter is available  
-    osg::ref_ptr< URLRewriter > rewriter = getURLRewriter();
-    if ( rewriter.valid() )
-    {
-        std::string oldURL = url;
-        url = rewriter->rewrite( oldURL );
-        OE_DEBUG << LC << "Rewrote URL " << oldURL << " to " << url << std::endl;
-    }
-
-    HINTERNET hInternet = InternetOpen(
-        getUserAgent().c_str(),
-        //"Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; AS; rv:11.0) like Gecko",
-        INTERNET_OPEN_TYPE_PRECONFIG, 
-        NULL,       // proxy
-        NULL,       // proxy bypass
-        0);         // flags
-
-    if( !hInternet )
-    {
-        OE_WARN << LC << "InternetOpen failed: " << GetLastErrorAsString() << std::endl;
-        return HTTPResponse(0);
-    }
-
-    // clears any session cookies..?
-    InternetSetOption( 0, INTERNET_OPTION_END_BROWSER_SESSION, NULL, 0 );
-    
-    // parse the URL:
-    URL_COMPONENTS urlcomp;
-    ZeroMemory(&urlcomp, sizeof(urlcomp));
-	urlcomp.dwStructSize = sizeof(urlcomp);
-	urlcomp.dwHostNameLength = 1;
-    urlcomp.dwUserNameLength = 1;
-    urlcomp.dwPasswordLength = 1;
-    urlcomp.dwUrlPathLength  = 1;
-
-    if ( !InternetCrackUrl(url.c_str(), 0, 0L, &urlcomp) )
-    {
-        OE_WARN << LC << "InternetCrackUrl failed for " << url << ": " << GetLastErrorAsString() << std::endl;
-        InternetCloseHandle( hInternet );
-        return HTTPResponse(0);
-    }
-    
-    WORD port =
-        urlcomp.nPort != 0 ? urlcomp.nPort :
-        urlcomp.nScheme == INTERNET_SCHEME_HTTPS ? INTERNET_DEFAULT_HTTPS_PORT :
-        INTERNET_DEFAULT_HTTP_PORT;
-
-    std::string hostName( urlcomp.lpszHostName );
-    int slash = hostName.find_first_of('/');
-    if ( slash != std::string::npos )
-        hostName = hostName.substr(0, slash);
-
-    OE_DEBUG
-        << "\n"
-        << "Host name = " << hostName << "\n"
-        << "Url path = " << urlcomp.lpszUrlPath << "\n"
-        << "Port = " << port << "\n";
-    
-    DWORD openFlags =
-        //INTERNET_FLAG_PRAGMA_NOCACHE |
-        INTERNET_FLAG_RELOAD |
-        INTERNET_FLAG_NO_CACHE_WRITE |
-        INTERNET_FLAG_KEEP_CONNECTION;
-
-    if ( urlcomp.nScheme == INTERNET_SCHEME_HTTPS)
-    {
-        openFlags |= INTERNET_FLAG_SECURE;
-    }
-
-    // InternetOpenUrl is a lot less code, but we have to use the InternetConnnect +
-    // HttpOpenRequest + HttpSendRequest approach in order to support system dialogs
-    // for PKI certificates and username/password queries.
-    
-    HINTERNET hConnection = InternetConnect(
-        hInternet,
-        hostName.c_str(),
-        port,
-        "", // username
-        "", // password
-        INTERNET_SERVICE_HTTP,
-        0,  // flags
-        INTERNET_NO_CALLBACK); // context
-
-    if ( !hConnection )
-    {
-        OE_WARN << LC << "InternetConnect failed for " << url << ": " << GetLastErrorAsString() << std::endl;
-        InternetCloseHandle( hInternet );
-        return HTTPResponse(0);
-    }
-
-    HINTERNET hRequest = HttpOpenRequest(
-        hConnection,            // handle from InternetConnect
-        "GET",                  // verb
-        urlcomp.lpszUrlPath,    // path
-        NULL,                   // HTTP version (NULL = default)
-        NULL,                   // Referrer
-        NULL,                   // Accept types
-        openFlags,              // flags
-        INTERNET_NO_CALLBACK);                  // context (user data)
-
-    if ( !hRequest )
-    {
-        OE_WARN << LC << "HttpOpenRequest failed for " << url << ": " << GetLastErrorAsString() << std::endl;
-        InternetCloseHandle( hConnection );
-        InternetCloseHandle( hInternet );
-        return HTTPResponse(0);
-    }
-
-    while( !HttpSendRequest(hRequest, NULL, 0, NULL, 0) )
-    {
-        DWORD errorNum = GetLastError();
-            
-        // Request for client cert; open system dialog.
-        if ( errorNum == ERROR_INTERNET_CLIENT_AUTH_CERT_NEEDED )
-        {
-            OE_WARN << LC << "Server reports ERROR_INTERNET_CLIENT_AUTH_CERT_NEEDED!\n";
-
-            // Return ERROR_SUCCESS regardless of clicking on OK or Cancel
-            DWORD dialogResult = InternetErrorDlg(
-                GetDesktopWindow(), 
-                hRequest,
-                ERROR_INTERNET_CLIENT_AUTH_CERT_NEEDED,
-                FLAGS_ERROR_UI_FILTER_FOR_ERRORS | FLAGS_ERROR_UI_FLAGS_GENERATE_DATA | FLAGS_ERROR_UI_FLAGS_CHANGE_OPTIONS, 
-                NULL );
-
-            if ( dialogResult != ERROR_SUCCESS )
-            {
-                OE_WARN << LC << "InternetErrorDlg failed to produce client cert " << url << ": " << GetLastErrorAsString() << std::endl;
-                InternetCloseHandle( hRequest );
-                InternetCloseHandle( hConnection );
-                InternetCloseHandle( hInternet );
-                return HTTPResponse(0);
-            }
-        }
-
-        else
-        {
-            OE_WARN << LC << "HttpSendRequest failed to open " << url << ": " << GetLastErrorAsString() << std::endl;
-            InternetCloseHandle( hRequest );
-            InternetCloseHandle( hConnection );
-            InternetCloseHandle( hInternet );
-            return HTTPResponse(0);
-        }
-    }
-
-    int statusCode = 0;
-    std::string contentType;
-
-    char  buffer[4096];
-    DWORD bufferLen = 4096;
-    DWORD index = 0;
-
-    if ( HttpQueryInfo(hRequest, HTTP_QUERY_STATUS_CODE, buffer, &bufferLen, &index) )
-    {             
-        statusCode = as<int>( std::string(buffer, bufferLen), 0 );
-    }
-    
-    HTTPResponse response(statusCode);
-
-    bufferLen = 4096, index = 0;
-    if ( HttpQueryInfo(hRequest, HTTP_QUERY_CONTENT_TYPE, buffer, &bufferLen, &index) )
-    {
-        response._mimeType = std::string(buffer, bufferLen);
-    }
-
-    bufferLen = 4096, index = 0;
-    if ( HttpQueryInfo(hRequest, HTTP_QUERY_LAST_MODIFIED, buffer, &bufferLen, &index) )
-    {
-        response._lastModified = as<long>(std::string(buffer, bufferLen), 0L);
-    }
-
-    response._mimeType = contentType;
-
-    if ( statusCode == 200 )
-    {    
-        osg::ref_ptr<HTTPResponse::Part> part = new HTTPResponse::Part();
-
-        DWORD numBytesRead = 0;
-        while( InternetReadFile(hRequest, buffer, 4096, &numBytesRead) && numBytesRead )
-        {
-            part->_stream << std::string(buffer, numBytesRead);
-        }
-
-        response._parts.push_back( part.get() );
-    }
-
-    InternetCloseHandle( hRequest );
-    InternetCloseHandle( hConnection );
-    InternetCloseHandle( hInternet );
-
-    response._duration_s = OE_STOP_TIMER(http_get);
-
-    if ( progress )
-    {
-        progress->stats("http_get_time") += OE_GET_TIMER(http_get);
-        progress->stats("http_get_count") += 1;
-        if ( response._cancelled )
-            progress->stats("http_cancel_count") += 1;
-    }
-    
-    return response;
-}
-
-#else // OSGEARTH_USE_WININET_FOR_HTTP
-
-HTTPResponse
-HTTPClient::doGet(const HTTPRequest&    request,
-                  const osgDB::Options* options, 
-                  ProgressCallback*     progress) const
-{    
-    initialize();
-
-    OE_START_TIMER(http_get);
-    
-    std::string url = request.getURL();
-
-    const osgDB::AuthenticationMap* authenticationMap = (options && options->getAuthenticationMap()) ? 
-            options->getAuthenticationMap() :
-            osgDB::Registry::instance()->getAuthenticationMap();
-
-    std::string proxy_host;
-    std::string proxy_port = "8080";
-
-    std::string proxy_auth;
-
-    //TODO: don't do all this proxy setup on every GET. Just do it once per client, or only when 
-    // the proxy information changes.
-
-    //Try to get the proxy settings from the global settings
-    if (s_proxySettings.isSet())
-    {
-        proxy_host = s_proxySettings.get().hostName();
-        std::stringstream buf;
-        buf << s_proxySettings.get().port();
-        proxy_port = buf.str();
-
-        std::string proxy_username = s_proxySettings.get().userName();
-        std::string proxy_password = s_proxySettings.get().password();
-        if (!proxy_username.empty() && !proxy_password.empty())
-        {
-            proxy_auth = proxy_username + std::string(":") + proxy_password;
-        }
-    }
-
-    //Try to get the proxy settings from the local options that are passed in.
-    readOptions( options, proxy_host, proxy_port );
-
-    optional< ProxySettings > proxySettings;
-    ProxySettings::fromOptions( options, proxySettings );
-    if (proxySettings.isSet())
-    {       
-        proxy_host = proxySettings.get().hostName();
-        proxy_port = toString<int>(proxySettings.get().port());
-        OE_DEBUG << LC << "Read proxy settings from options " << proxy_host << " " << proxy_port << std::endl;
-    }
-
-    //Try to get the proxy settings from the environment variable
-    const char* proxyEnvAddress = getenv("OSG_CURL_PROXY");
-    if (proxyEnvAddress) //Env Proxy Settings
-    {
-        proxy_host = std::string(proxyEnvAddress);
-
-        const char* proxyEnvPort = getenv("OSG_CURL_PROXYPORT"); //Searching Proxy Port on Env
-        if (proxyEnvPort)
-        {
-            proxy_port = std::string( proxyEnvPort );
-        }
-    }
-
-    const char* proxyEnvAuth = getenv("OSGEARTH_CURL_PROXYAUTH");
-    if (proxyEnvAuth)
-    {
-        proxy_auth = std::string(proxyEnvAuth);
-    }
-
-    // Set up proxy server:
-    std::string proxy_addr;
-    if ( !proxy_host.empty() )
-    {
-        std::stringstream buf;
-        buf << proxy_host << ":" << proxy_port;
-        std::string bufStr;
-        bufStr = buf.str();
-        proxy_addr = bufStr;
-    
-        if ( s_HTTP_DEBUG )
-        {
-            OE_NOTICE << LC << "Using proxy: " << proxy_addr << std::endl;
-        }
-
-        //curl_easy_setopt( _curl_handle, CURLOPT_HTTPPROXYTUNNEL, 1 ); 
-        curl_easy_setopt( _curl_handle, CURLOPT_PROXY, proxy_addr.c_str() );
-
-        //Setup the proxy authentication if setup
-        if (!proxy_auth.empty())
-        {
-            if ( s_HTTP_DEBUG )
-            {
-                OE_NOTICE << LC << "Using proxy authentication " << proxy_auth << std::endl;
-            }
-
-            curl_easy_setopt( _curl_handle, CURLOPT_PROXYUSERPWD, proxy_auth.c_str());
-        }
-    }
-    else
-    {
-        OE_DEBUG << LC << "Removing proxy settings" << std::endl;
-        curl_easy_setopt( _curl_handle, CURLOPT_PROXY, 0 );
-    }
-
-    // Rewrite the url if the url rewriter is available  
-    osg::ref_ptr< URLRewriter > rewriter = getURLRewriter();
-    if ( rewriter.valid() )
-    {
-        std::string oldURL = url;
-        url = rewriter->rewrite( oldURL );
-        OE_DEBUG << LC << "Rewrote URL " << oldURL << " to " << url << std::endl;
-    }
-
-    const osgDB::AuthenticationDetails* details = authenticationMap ?
-        authenticationMap->getAuthenticationDetails( url ) :
-        0;
-
-    if (details)
-    {
-        const std::string colon(":");
-        std::string password(details->username + colon + details->password);
-        curl_easy_setopt(_curl_handle, CURLOPT_USERPWD, password.c_str());
-        const_cast<HTTPClient*>(this)->_previousPassword = password;
-
-        // use for https.
-        // curl_easy_setopt(_curl, CURLOPT_KEYPASSWD, password.c_str());
-
-#if LIBCURL_VERSION_NUM >= 0x070a07
-        if (details->httpAuthentication != _previousHttpAuthentication)
-        { 
-            curl_easy_setopt(_curl_handle, CURLOPT_HTTPAUTH, details->httpAuthentication); 
-            const_cast<HTTPClient*>(this)->_previousHttpAuthentication = details->httpAuthentication;
-        }
-#endif
-    }
-    else
-    {
-        if (!_previousPassword.empty())
-        {
-            curl_easy_setopt(_curl_handle, CURLOPT_USERPWD, 0);
-            const_cast<HTTPClient*>(this)->_previousPassword.clear();
-        }
-
-#if LIBCURL_VERSION_NUM >= 0x070a07
-        // need to reset if previously set.
-        if (_previousHttpAuthentication!=0)
-        {
-            curl_easy_setopt(_curl_handle, CURLOPT_HTTPAUTH, 0); 
-            const_cast<HTTPClient*>(this)->_previousHttpAuthentication = 0;
-        }
-#endif
-    }  
-
-
-    // Set any headers
-    struct curl_slist *headers=NULL;
-    if (!request.getHeaders().empty())
-    {
-        for (HTTPRequest::Parameters::const_iterator itr = request.getHeaders().begin(); itr != request.getHeaders().end(); ++itr)
-        {
-            std::stringstream buf;
-            buf << itr->first << ": " << itr->second;
-            headers = curl_slist_append(headers, buf.str().c_str());
-        }
-    }
-
-    // Disable the default Pragma: no-cache that curl adds by default.
-    headers = curl_slist_append(headers, "Pragma: ");
-    curl_easy_setopt(_curl_handle, CURLOPT_HTTPHEADER, headers); 
-    
-    osg::ref_ptr<HTTPResponse::Part> part = new HTTPResponse::Part();
-    StreamObject sp( &part->_stream );
-
-    //Take a temporary ref to the callback (why? dangerous.)
-    //osg::ref_ptr<ProgressCallback> progressCallback = callback;
-    curl_easy_setopt( _curl_handle, CURLOPT_URL, url.c_str() );
-    if (progress)
-    {
-        curl_easy_setopt(_curl_handle, CURLOPT_PROGRESSDATA, progress);
-    }
-
-    CURLcode res;
-    long response_code = 0L;
-
-    OE_START_TIMER(get_duration);
-
-    if ( _simResponseCode < 0 )
-    {
-        char errorBuf[CURL_ERROR_SIZE];
-        errorBuf[0] = 0;
-        curl_easy_setopt( _curl_handle, CURLOPT_ERRORBUFFER, (void*)errorBuf );
-        curl_easy_setopt( _curl_handle, CURLOPT_WRITEDATA, (void*)&sp);
-        curl_easy_setopt( _curl_handle, CURLOPT_HEADERDATA, (void*)&sp);
-
-        //Disable peer certificate verification to allow us to access in https servers where the peer certificate cannot be verified.
-        curl_easy_setopt( _curl_handle, CURLOPT_SSL_VERIFYPEER, (void*)0 );
-        
-        osg::ref_ptr< CurlConfigHandler > curlConfigHandler = getCurlConfigHandler();
-        if (curlConfigHandler.valid()) {
-            curlConfigHandler->onGet(_curl_handle);
-        }
-
-        res = curl_easy_perform(_curl_handle);
-        curl_easy_setopt( _curl_handle, CURLOPT_WRITEDATA, (void*)0 );
-        curl_easy_setopt( _curl_handle, CURLOPT_PROGRESSDATA, (void*)0);
-
-        if (!proxy_addr.empty())
-        {
-            long connect_code = 0L;
-            CURLcode r = curl_easy_getinfo(_curl_handle, CURLINFO_HTTP_CONNECTCODE, &connect_code);
-            if ( r != CURLE_OK )
-            {
-                OE_WARN << LC << "Proxy connect error: " << curl_easy_strerror(r) << std::endl;
-                return HTTPResponse(0);
-            }
-        }
-
-        curl_easy_getinfo( _curl_handle, CURLINFO_RESPONSE_CODE, &response_code );        
-    }
-    else
-    {
-        // simulate failure with a custom response code
-        response_code = _simResponseCode;
-        res = response_code == 408 ? CURLE_OPERATION_TIMEDOUT : CURLE_COULDNT_CONNECT;
-    }
-
-    HTTPResponse response( response_code );    
-    
-    // read the response content type:
-    char* content_type_cp;
-
-    curl_easy_getinfo( _curl_handle, CURLINFO_CONTENT_TYPE, &content_type_cp );    
-
-    if ( content_type_cp != NULL )
-    {
-        response._mimeType = content_type_cp;    
-    } 
-
-    // read the file time:
-    response._lastModified = getCurlFileTime( _curl_handle );
-
-    // upon success, parse the data:
-    if ( res != CURLE_ABORTED_BY_CALLBACK && res != CURLE_OPERATION_TIMEDOUT )
-    {        
-        // check for multipart content
-        if (response._mimeType.length() > 9 && 
-            ::strstr( response._mimeType.c_str(), "multipart" ) == response._mimeType.c_str() )
-        {
-            OE_DEBUG << LC << "detected multipart data; decoding..." << std::endl;
-
-            //TODO: parse out the "wcs" -- this is WCS-specific
-            if ( !decodeMultipartStream( "wcs", part.get(), response._parts ) )
-            {
-                // error decoding an invalid multipart stream.
-                // should we do anything, or just leave the response empty?
-            }
-        }
-        else
-        {            
-            for (Headers::iterator itr = sp._headers.begin(); itr != sp._headers.end(); ++itr)
-            {                
-                part->_headers[itr->first] = itr->second;                
-            }
-
-            // Write the headers to the metadata
-            response._parts.push_back( part.get() );
-        }
-    }
-    else  /*if (res == CURLE_ABORTED_BY_CALLBACK || res == CURLE_OPERATION_TIMEDOUT) */
-    {        
-        //If we were aborted by a callback, then it was cancelled by a user
-        response._cancelled = true;
-    }
-
-    response._duration_s = OE_STOP_TIMER(get_duration);
-
-    if ( progress )
-    {
-        progress->stats()["http_get_time"] += OE_STOP_TIMER(http_get);
-        progress->stats()["http_get_count"] += 1;
-        if ( response._cancelled )
-            progress->stats()["http_cancel_count"] += 1;
-    }
-
-    if ( s_HTTP_DEBUG )
-    {
-        TimeStamp filetime = getCurlFileTime(_curl_handle);
-
-        OE_NOTICE << LC 
-            << "GET(" << response_code << ", " << response._mimeType << ") : \"" 
-            << url << "\" (" << DateTime(filetime).asRFC1123() << ") t="
-            << std::setprecision(4) << response.getDuration() << "s" << std::endl;
-
-        {
-            Threading::ScopedMutexLock lock(s_HTTP_DEBUG_mutex);
-            s_HTTP_DEBUG_request_count++;
-            s_HTTP_DEBUG_total_duration += response.getDuration();
-
-            if ( s_HTTP_DEBUG_request_count % 60 == 0 )
-            {
-                OE_NOTICE << LC << "Average duration = " << s_HTTP_DEBUG_total_duration/(double)s_HTTP_DEBUG_request_count
-                    << std::endl;
-            }
-        }
-
-#if 0
-        // time details - almost 100% of the time is spent in
-        // STARTTRANSFER, which is the time until the first byte is received.
-        double td[7];
-
-        curl_easy_getinfo(_curl_handle, CURLINFO_TOTAL_TIME,         &td[0]);
-        curl_easy_getinfo(_curl_handle, CURLINFO_NAMELOOKUP_TIME,    &td[1]);
-        curl_easy_getinfo(_curl_handle, CURLINFO_CONNECT_TIME,       &td[2]);
-        curl_easy_getinfo(_curl_handle, CURLINFO_APPCONNECT_TIME,    &td[3]);
-        curl_easy_getinfo(_curl_handle, CURLINFO_PRETRANSFER_TIME,   &td[4]);
-        curl_easy_getinfo(_curl_handle, CURLINFO_STARTTRANSFER_TIME, &td[5]);
-        curl_easy_getinfo(_curl_handle, CURLINFO_REDIRECT_TIME,      &td[6]);
-
-        for(int i=0; i<7; ++i)
-        {
-            OE_NOTICE << LC
-                << std::setprecision(4)
-                << "TIMES: total=" <<td[0]
-                << ", lookup=" <<td[1]<<" ("<<(int)((td[1]/td[0])*100)<<"%)"
-                << ", connect=" <<td[2]<<" ("<<(int)((td[2]/td[0])*100)<<"%)"
-                << ", appconn=" <<td[3]<<" ("<<(int)((td[3]/td[0])*100)<<"%)"
-                << ", prexfer=" <<td[4]<<" ("<<(int)((td[4]/td[0])*100)<<"%)"
-                << ", startxfer=" <<td[5]<<" ("<<(int)((td[5]/td[0])*100)<<"%)"
-                << ", redir=" <<td[6]<<" ("<<(int)((td[6]/td[0])*100)<<"%)"
-                << std::endl;
-        }
-#endif
-
-        // Free the headers
-        if (headers)
-        {
-            curl_slist_free_all(headers);
-        }
-    }
-
-    return response;
-}
-
-#endif // USE_WININET
-
-bool
-HTTPClient::doDownload(const std::string& url, const std::string& filename)
-{
-    initialize();
-
-    // download the data
-    HTTPResponse response = this->doGet( HTTPRequest(url) );
-
-    if ( response.isOK() )
-    {
-        unsigned int part_num = response.getNumParts() > 1? 1 : 0;
-        std::istream& input_stream = response.getPartStream( part_num );
-
-        std::ofstream fout;
-        fout.open(filename.c_str(), std::ios::out | std::ios::binary);
-
-        input_stream.seekg (0, std::ios::end);
-        int length = input_stream.tellg();
-        input_stream.seekg (0, std::ios::beg);
-
-        char *buffer = new char[length];
-        input_stream.read(buffer, length);
-        fout.write(buffer, length);
-        delete[] buffer;
-        fout.close();
-        return true;
-    }
-    else
-    {
-        OE_WARN << LC << "Error downloading file " << filename
-            << " (" << response.getCode() << ")" << std::endl;
-        return false;
-    } 
-}
-
-namespace
-{
-    osgDB::ReaderWriter*
-    getReader( const std::string& url, const HTTPResponse& response )
-    {        
-        osgDB::ReaderWriter* reader = 0L;
-
-        // try extension first:
-        std::string ext = osgDB::getFileExtension( url );
-        if ( !ext.empty() )
-        {
-            reader = osgDB::Registry::instance()->getReaderWriterForExtension( ext );
-        }
-
-        if ( !reader )
-        {
-            // try to look up a reader by mime-type first:
-            std::string mimeType = response.getMimeType();
-            if ( !mimeType.empty() )
-            {
-                reader = osgDB::Registry::instance()->getReaderWriterForMimeType(mimeType);
-            }
-        }
-
-        if ( !reader && s_HTTP_DEBUG )
-        {
-            OE_WARN << LC << "Cannot find an OSG plugin to read response data (ext="
-                << ext << "; mime-type=" << response.getMimeType()
-                << ")" << std::endl;
-
-            if ( endsWith(response.getMimeType(), "xml", false) )
-            {
-                OE_WARN << LC << "Content:\n" << response.getPartAsString(0) << "\n";
-            }
-        }
-
-        return reader;
-    }
-}
-
-ReadResult
-HTTPClient::doReadImage(const HTTPRequest&    request,
-                        const osgDB::Options* options,
-                        ProgressCallback*     callback)
-{
-    initialize();
-
-    ReadResult result;
-
-    HTTPResponse response = this->doGet(request, options, callback);
-
-    if (response.isOK())
-    {
-        osgDB::ReaderWriter* reader = getReader(request.getURL(), response);
-        if (!reader)
-        {            
-            result = ReadResult(ReadResult::RESULT_NO_READER);
-        }
-
-        else 
-        {
-            osgDB::ReaderWriter::ReadResult rr = reader->readImage(response.getPartStream(0), options);
-            if ( rr.validImage() )
-            {
-                result = ReadResult(rr.takeImage());
-            }
-            else 
-            {
-                if ( s_HTTP_DEBUG )
-                {
-                    OE_WARN << LC << reader->className() 
-                        << " failed to read image from " << request.getURL() 
-                        << "; message = " << rr.message()
-                        <<  std::endl;
-                }
-                result = ReadResult(ReadResult::RESULT_READER_ERROR);
-                result.setErrorDetail( rr.message() );
-            }
-        }
-        
-        // last-modified (file time)
-        result.setLastModifiedTime( response._lastModified ); //getCurlFileTime(_curl_handle) );
-        
-        // Time of query
-        result.setDuration( response.getDuration() );
-    }
-    else
-    {
-        result = ReadResult(
-            response.isCancelled()                           ? ReadResult::RESULT_CANCELED :
-            response.getCode() == HTTPResponse::NOT_FOUND    ? ReadResult::RESULT_NOT_FOUND :
-            response.getCode() == HTTPResponse::SERVER_ERROR ? ReadResult::RESULT_SERVER_ERROR :
-            response.getCode() == HTTPResponse::NOT_MODIFIED ? ReadResult::RESULT_NOT_MODIFIED :
-                                                               ReadResult::RESULT_UNKNOWN_ERROR );
-
-        //If we have an error but it's recoverable, like a server error or timeout then set the callback to retry.
-        if (HTTPClient::isRecoverable( result.code() ) )
-        {            
-            if (callback)
-            {
-                if ( s_HTTP_DEBUG )
-                {
-                    OE_NOTICE << LC << "Error in HTTPClient for " << request.getURL() << " but it's recoverable" << std::endl;
-                }
-                callback->setNeedsRetry( true );
-            }
-        }        
-    }
-
-    // encode headers
-    result.setMetadata( response.getHeadersAsConfig() );
-
-    // set the source name
-    if ( result.getImage() )
-        result.getImage()->setName( request.getURL() );
-
-    return result;
-}
-
-ReadResult
-HTTPClient::doReadNode(const HTTPRequest&    request,
-                       const osgDB::Options* options,
-                       ProgressCallback*     callback)
-{
-    initialize();
-
-    ReadResult result;
-
-    HTTPResponse response = this->doGet(request, options, callback);
-
-    if (response.isOK())
-    {
-        osgDB::ReaderWriter* reader = getReader(request.getURL(), response);
-        if (!reader)
-        {
-            result = ReadResult(ReadResult::RESULT_NO_READER);
-        }
-
-        else 
-        {
-            osgDB::ReaderWriter::ReadResult rr = reader->readNode(response.getPartStream(0), options);
-            if ( rr.validNode() )
-            {
-                result = ReadResult(rr.takeNode());
-            }
-            else 
-            {
-                if ( s_HTTP_DEBUG )
-                {
-                    OE_WARN << LC << reader->className() 
-                        << " failed to read node from " << request.getURL() 
-                        << "; message = " << rr.message()
-                        <<  std::endl;
-                }
-                result = ReadResult(ReadResult::RESULT_READER_ERROR);
-                result.setErrorDetail( rr.message() );
-            }
-        }
-        
-        // last-modified (file time)
-        result.setLastModifiedTime( response._lastModified ); //getCurlFileTime(_curl_handle) );
-    }
-    else
-    {
-        result = ReadResult(
-            response.isCancelled()                           ? ReadResult::RESULT_CANCELED :
-            response.getCode() == HTTPResponse::NOT_FOUND    ? ReadResult::RESULT_NOT_FOUND :
-            response.getCode() == HTTPResponse::SERVER_ERROR ? ReadResult::RESULT_SERVER_ERROR :
-            response.getCode() == HTTPResponse::NOT_MODIFIED ? ReadResult::RESULT_NOT_MODIFIED :
-                                                               ReadResult::RESULT_UNKNOWN_ERROR );
-
-        //If we have an error but it's recoverable, like a server error or timeout then set the callback to retry.
-        if (HTTPClient::isRecoverable( result.code() ) )
-        {
-            if (callback)
-            {
-                if ( s_HTTP_DEBUG )
-                {
-                    OE_NOTICE << LC << "Error in HTTPClient for " << request.getURL() << " but it's recoverable" << std::endl;
-                }
-                callback->setNeedsRetry( true );
-            }
-        }
-    }
-
-    // encode headers
-    result.setMetadata( response.getHeadersAsConfig() );
-
-    return result;
-}
-
-ReadResult
-HTTPClient::doReadObject(const HTTPRequest&    request,
-                         const osgDB::Options* options,
-                         ProgressCallback*     callback)
-{
-    initialize();
-
-    ReadResult result;
-
-    HTTPResponse response = this->doGet(request, options, callback);
-
-    if (response.isOK())
-    {
-        osgDB::ReaderWriter* reader = getReader(request.getURL(), response);
-        if (!reader)
-        {
-            result = ReadResult(ReadResult::RESULT_NO_READER);
-        }
-
-        else 
-        {
-            osgDB::ReaderWriter::ReadResult rr = reader->readObject(response.getPartStream(0), options);
-            if ( rr.validObject() )
-            {
-                result = ReadResult(rr.takeObject());
-            }
-            else 
-            {
-                if ( s_HTTP_DEBUG )
-                {
-                    OE_WARN << LC << reader->className() 
-                        << " failed to read object from " << request.getURL() 
-                        << "; message = " << rr.message()
-                        <<  std::endl;
-                }
-                result = ReadResult(ReadResult::RESULT_READER_ERROR);
-                result.setErrorDetail( rr.message() );
-            }
-        }
-        
-        // last-modified (file time)
-        result.setLastModifiedTime( response._lastModified ); //getCurlFileTime(_curl_handle) );
-    }
-    else
-    {
-        result = ReadResult(
-            response.isCancelled() ? ReadResult::RESULT_CANCELED :
-            response.getCode() == HTTPResponse::NOT_FOUND ? ReadResult::RESULT_NOT_FOUND :
-            response.getCode() == HTTPResponse::SERVER_ERROR ? ReadResult::RESULT_SERVER_ERROR :
-            response.getCode() == HTTPResponse::NOT_MODIFIED ? ReadResult::RESULT_NOT_MODIFIED :
-            ReadResult::RESULT_UNKNOWN_ERROR );
-
-        //If we have an error but it's recoverable, like a server error or timeout then set the callback to retry.
-        if (HTTPClient::isRecoverable( result.code() ) )
-        {
-            if (callback)
-            {
-                if ( s_HTTP_DEBUG )
-                {
-                    OE_NOTICE << LC << "Error in HTTPClient for " << request.getURL() << " but it's recoverable" << std::endl;
-                }
-                callback->setNeedsRetry( true );
-            }
-        }
-    }
-
-    result.setMetadata( response.getHeadersAsConfig() );
-
-    return result;
-}
-
-
-ReadResult
-HTTPClient::doReadString(const HTTPRequest&    request,
-                         const osgDB::Options* options,
-                         ProgressCallback*     callback )
-{
-    initialize();
-
-    ReadResult result;
-
-    HTTPResponse response = this->doGet( request, options, callback );
-    if ( response.isOK() )
-    {
-        result = ReadResult( new StringObject(response.getPartAsString(0)) );
-    }
-
-    else if ( response.getCode() >= 400 && response.getCode() < 500 && response.getCode() != 404 )
-    {
-        // for request errors, return an error result with the part data intact
-        // so the user can parse it as needed. We only do this for readString.
-        result = ReadResult( 
-            ReadResult::RESULT_SERVER_ERROR,
-            new StringObject(response.getPartAsString(0)) );
-    }
-
-    else
-    {
-        result = ReadResult(
-            response.isCancelled() ?                           ReadResult::RESULT_CANCELED :
-            response.getCode() == HTTPResponse::NOT_FOUND    ? ReadResult::RESULT_NOT_FOUND :
-            response.getCode() == HTTPResponse::SERVER_ERROR ? ReadResult::RESULT_SERVER_ERROR :
-            response.getCode() == HTTPResponse::NOT_MODIFIED ? ReadResult::RESULT_NOT_MODIFIED :
-                                                               ReadResult::RESULT_UNKNOWN_ERROR );
-
-        //If we have an error but it's recoverable, like a server error or timeout then set the callback to retry.
-        if (HTTPClient::isRecoverable( result.code() ) )
-        {            
-            if (callback)
-            {
-                if ( s_HTTP_DEBUG )
-                {
-                    OE_NOTICE << LC << "Error in HTTPClient for " << request.getURL() << " but it's recoverable" << std::endl;
-                }
-                callback->setNeedsRetry( true );
-            }
-        }
-    }
-
-    // encode headers
-    result.setMetadata( response.getHeadersAsConfig() );
-
-    // last-modified (file time)
-    result.setLastModifiedTime( response._lastModified ); //getCurlFileTime(_curl_handle) );
-
-    return result;
-}
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+ * Copyright 2016 Pelican Mapping
+ * http://osgearth.org
+ *
+ * osgEarth is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+#include <osgEarth/HTTPClient>
+#include <osgEarth/Registry>
+#include <osgEarth/Version>
+#include <osgEarth/Progress>
+#include <osgEarth/StringUtils>
+#include <osgEarth/Metrics>
+#include <osgDB/ReadFile>
+#include <osgDB/Registry>
+#include <osgDB/FileNameUtils>
+#include <osg/Notify>
+#include <osg/Timer>
+#include <string.h>
+#include <sstream>
+#include <fstream>
+#include <iterator>
+#include <iostream>
+#include <algorithm>
+#include <curl/curl.h>
+
+// Whether to use WinInet instead of cURL - CMAKE option
+#ifdef OSGEARTH_USE_WININET_FOR_HTTP
+#include <WinInet.h>
+#pragma comment(lib, "wininet.lib")
+#endif
+
+#define LC "[HTTPClient] "
+
+//#define OE_TEST OE_NOTICE
+#define OE_TEST OE_NULL
+
+using namespace osgEarth;
+
+//----------------------------------------------------------------------------
+
+ProxySettings::ProxySettings( const Config& conf )
+{
+    mergeConfig( conf );
+}
+
+ProxySettings::ProxySettings( const std::string& host, int port ) :
+_hostName(host),
+_port(port)
+{
+    //nop
+}
+
+void
+ProxySettings::mergeConfig( const Config& conf )
+{
+    _hostName = conf.value<std::string>( "host", "" );
+    _port = conf.value<int>( "port", 8080 );
+    _userName = conf.value<std::string>( "username", "" );
+    _password = conf.value<std::string>( "password", "" );
+}
+
+Config
+ProxySettings::getConfig() const
+{
+    Config conf( "proxy" );
+    conf.add( "host", _hostName );
+    conf.add( "port", toString(_port) );
+    conf.add( "username", _userName);
+    conf.add( "password", _password);
+
+    return conf;
+}
+
+bool
+ProxySettings::fromOptions( const osgDB::Options* dbOptions, optional<ProxySettings>& out )
+{
+    if ( dbOptions )
+    {
+        std::string jsonString = dbOptions->getPluginStringData( "osgEarth::ProxySettings" );
+        if ( !jsonString.empty() )
+        {
+            Config conf;
+            conf.fromJSON( jsonString );
+            out = ProxySettings( conf );
+            return true;
+        }
+    }
+    return false;
+}
+
+void
+ProxySettings::apply( osgDB::Options* dbOptions ) const
+{
+    if ( dbOptions )
+    {
+        Config conf = getConfig();
+        dbOptions->setPluginStringData( "osgEarth::ProxySettings", conf.toJSON() );
+    }
+}
+
+/****************************************************************************/
+
+namespace osgEarth
+{
+    struct StreamObject
+    {
+        StreamObject(std::ostream* stream) : _stream(stream) { }
+
+        void write(const char* ptr, size_t realsize)
+        {
+            if (_stream) _stream->write(ptr, realsize);
+        }
+
+        void writeHeader(const char* ptr, size_t realsize)
+        {
+            std::string header(ptr);
+            StringTokenizer tok(":");
+            StringVector tized;
+            tok.tokenize(header, tized);
+            if ( tized.size() >= 2 )
+                _headers[tized[0]] = tized[1];
+        }
+
+        std::ostream* _stream;
+        Headers _headers;
+        std::string     _resultMimeType;
+    };
+
+    static size_t
+    StreamObjectReadCallback(void* ptr, size_t size, size_t nmemb, void* data)
+    {
+        size_t realsize = size* nmemb;
+        StreamObject* sp = (StreamObject*)data;
+        sp->write((const char*)ptr, realsize);
+        return realsize;
+    }
+
+    static size_t
+    StreamObjectHeaderCallback(void* ptr, size_t size, size_t nmemb, void* data)
+    {
+        size_t realsize = size* nmemb;
+        StreamObject* sp = (StreamObject*)data;
+        sp->writeHeader((const char*)ptr, realsize);
+        return realsize;
+    }
+
+    TimeStamp
+    getCurlFileTime(void* curl)
+    {
+        long filetime;
+        if (CURLE_OK != curl_easy_getinfo(curl, CURLINFO_FILETIME, &filetime))
+            return TimeStamp(0);
+        else if (filetime < 0)
+            return TimeStamp(0);
+        else
+            return TimeStamp(filetime);
+    }
+}
+
+static int CurlProgressCallback(void *clientp,double dltotal,double dlnow,double ultotal,double ulnow)
+{
+    ProgressCallback* callback = (ProgressCallback*)clientp;
+    bool cancelled = false;
+    if (callback)
+    {
+        cancelled = callback->isCanceled() || callback->reportProgress(dlnow, dltotal);
+    }
+    return cancelled;
+}
+
+/****************************************************************************/
+
+HTTPRequest::HTTPRequest( const std::string& url )
+: _url( url )
+{
+    //NOP
+}
+
+HTTPRequest::HTTPRequest( const HTTPRequest& rhs ) :
+_parameters( rhs._parameters ),
+_headers(rhs._headers),
+_url( rhs._url )
+{
+    //nop
+}
+
+void
+HTTPRequest::addParameter( const std::string& name, const std::string& value )
+{
+    _parameters[name] = value;
+}
+
+void
+HTTPRequest::addParameter( const std::string& name, int value )
+{
+    std::stringstream buf;
+    buf << value;
+     std::string bufStr;
+    bufStr = buf.str();
+    _parameters[name] = bufStr;
+}
+
+void
+HTTPRequest::addParameter( const std::string& name, double value )
+{
+    std::stringstream buf;
+    buf << value;
+     std::string bufStr;
+    bufStr = buf.str();
+    _parameters[name] = bufStr;
+}
+
+const HTTPRequest::Parameters&
+HTTPRequest::getParameters() const
+{
+    return _parameters;
+}
+
+void
+HTTPRequest::addHeader( const std::string& name, const std::string& value )
+{
+    _headers[name] = value;
+}
+
+const Headers&
+HTTPRequest::getHeaders() const
+{
+    return _headers;
+}
+
+void HTTPRequest::setLastModified( const DateTime &lastModified)
+{
+    addHeader("If-Modified-Since", lastModified.asRFC1123());
+}
+
+
+std::string
+HTTPRequest::getURL() const
+{
+    if ( _parameters.size() == 0 )
+    {
+        return _url;
+    }
+    else
+    {
+        std::stringstream buf;
+        buf << _url;
+        for( Parameters::const_iterator i = _parameters.begin(); i != _parameters.end(); i++ )
+        {
+            buf << ( i == _parameters.begin() && _url.find( "?" ) == std::string::npos? "?" : "&" );
+            buf << i->first << "=" << i->second;
+        }
+         std::string bufStr;
+         bufStr = buf.str();
+        return bufStr;
+    }
+}
+
+/****************************************************************************/
+
+HTTPResponse::HTTPResponse( long _code ) :
+_response_code( _code ),
+_cancelled(false),
+_duration_s(0.0),
+_lastModified(0u)
+{
+    _parts.reserve(1);
+}
+
+HTTPResponse::HTTPResponse( const HTTPResponse& rhs ) :
+_response_code( rhs._response_code ),
+_parts( rhs._parts ),
+_mimeType( rhs._mimeType ),
+_cancelled( rhs._cancelled ),
+_duration_s(0.0),
+_lastModified(0u)
+{
+    //nop
+}
+
+unsigned
+HTTPResponse::getCode() const {
+    return _response_code;
+}
+
+bool
+HTTPResponse::isOK() const {
+    return _response_code == 200L && !isCancelled();
+}
+
+bool
+HTTPResponse::isCancelled() const {
+    return _cancelled;
+}
+
+unsigned int
+HTTPResponse::getNumParts() const {
+    return _parts.size();
+}
+
+unsigned int
+HTTPResponse::getPartSize( unsigned int n ) const {
+    return _parts[n]->_size;
+}
+
+const std::string&
+HTTPResponse::getPartHeader( unsigned int n, const std::string& name ) const {
+    return _parts[n]->_headers[name];
+}
+
+std::istream&
+HTTPResponse::getPartStream( unsigned int n ) const {
+    return _parts[n]->_stream;
+}
+
+std::string
+HTTPResponse::getPartAsString( unsigned int n ) const {
+    std::string streamStr;
+    streamStr = _parts[n]->_stream.str();
+    return streamStr;
+}
+
+const std::string&
+HTTPResponse::getMimeType() const {
+    return _mimeType;
+}
+
+Config
+HTTPResponse::getHeadersAsConfig() const
+{
+    Config conf;
+    if ( _parts.size() > 0 )
+    {
+        for( Headers::const_iterator i = _parts[0]->_headers.begin(); i != _parts[0]->_headers.end(); ++i )
+        {
+            conf.set(i->first, i->second);
+        }
+    }
+    return conf;
+}
+
+/****************************************************************************/
+
+#define QUOTE_(X) #X
+#define QUOTE(X) QUOTE_(X)
+#define USER_AGENT "osgearth" QUOTE(OSGEARTH_MAJOR_VERSION) "." QUOTE(OSGEARTH_MINOR_VERSION)
+
+
+namespace
+{
+    // TODO: consider moving this stuff into the osgEarth::Registry;
+    // don't like it here in the global scope
+    // per-thread client map (must be global scope)
+    static PerThread<HTTPClient>       s_clientPerThread;
+
+    static optional<ProxySettings>     s_proxySettings;
+
+    static std::string                 s_userAgent = USER_AGENT;
+
+    static long                        s_timeout = 0;
+    static long                        s_connectTimeout = 0;
+
+    // HTTP debugging.
+    static bool                        s_HTTP_DEBUG = false;
+    static Threading::Mutex            s_HTTP_DEBUG_mutex;
+    static int                         s_HTTP_DEBUG_request_count;
+    static double                      s_HTTP_DEBUG_total_duration;
+
+    static osg::ref_ptr< URLRewriter > s_rewriter;
+
+    static osg::ref_ptr< CurlConfigHandler > s_curlConfigHandler;
+}
+
+HTTPClient&
+HTTPClient::getClient()
+{
+    return s_clientPerThread.get();
+}
+
+HTTPClient::HTTPClient() :
+_initialized    ( false ),
+_curl_handle    ( 0L ),
+_simResponseCode( -1L ),
+_previousHttpAuthentication(0L)
+{
+    //nop
+    //do no CURL calls here.
+}
+
+void
+HTTPClient::initialize() const
+{
+    if ( !_initialized )
+    {
+        const_cast<HTTPClient*>(this)->initializeImpl();
+    }
+}
+
+void
+HTTPClient::initializeImpl()
+{
+    _previousHttpAuthentication = 0;
+    _curl_handle = curl_easy_init();
+
+    //Get the user agent
+    std::string userAgent = s_userAgent;
+    const char* userAgentEnv = getenv("OSGEARTH_USERAGENT");
+    if (userAgentEnv)
+    {
+        userAgent = std::string(userAgentEnv);
+    }
+
+    //Check for a response-code simulation (for testing)
+    const char* simCode = getenv("OSGEARTH_SIMULATE_HTTP_RESPONSE_CODE");
+    if ( simCode )
+    {
+        _simResponseCode = osgEarth::as<long>(std::string(simCode), 404L);
+        OE_WARN << LC << "Simulating a network error with Response Code = " << _simResponseCode << std::endl;
+    }
+
+    // Check to HTTP disabling (for testing)
+    const char* disable = getenv("OSGEARTH_HTTP_DISABLE");
+    if (disable)
+    {
+        _simResponseCode = 503L; // SERVICE UNAVAILABLE
+        OE_WARN << LC << "HTTP traffic disabled" << std::endl;
+    }
+
+    // Dumps out HTTP request/response info
+    if ( ::getenv("OSGEARTH_HTTP_DEBUG") )
+    {
+        s_HTTP_DEBUG = true;
+        OE_WARN << LC << "HTTP debugging enabled" << std::endl;
+    }
+
+    OE_DEBUG << LC << "HTTPClient setting userAgent=" << userAgent << std::endl;
+
+    curl_easy_setopt( _curl_handle, CURLOPT_USERAGENT, userAgent.c_str() );
+    curl_easy_setopt( _curl_handle, CURLOPT_WRITEFUNCTION, osgEarth::StreamObjectReadCallback );
+    curl_easy_setopt( _curl_handle, CURLOPT_HEADERFUNCTION, osgEarth::StreamObjectHeaderCallback );
+    curl_easy_setopt( _curl_handle, CURLOPT_FOLLOWLOCATION, (void*)1 );
+    curl_easy_setopt( _curl_handle, CURLOPT_MAXREDIRS, (void*)5 );
+    curl_easy_setopt( _curl_handle, CURLOPT_PROGRESSFUNCTION, &CurlProgressCallback);
+    curl_easy_setopt( _curl_handle, CURLOPT_NOPROGRESS, (void*)0 ); //0=enable.
+    curl_easy_setopt( _curl_handle, CURLOPT_FILETIME, true );
+
+    // Enable automatic CURL decompression of known types. An empty string will automatically add all supported encoding types that are built into curl.
+    // Note that you must have curl built against zlib to support gzip or deflate encoding.
+    curl_easy_setopt( _curl_handle, CURLOPT_ENCODING, "");
+
+    osg::ref_ptr< CurlConfigHandler > curlConfigHandler = getCurlConfigHandler();
+    if (curlConfigHandler.valid()) {
+        curlConfigHandler->onInitialize(_curl_handle);
+    }
+
+    long timeout = s_timeout;
+    const char* timeoutEnv = getenv("OSGEARTH_HTTP_TIMEOUT");
+    if (timeoutEnv)
+    {
+        timeout = osgEarth::as<long>(std::string(timeoutEnv), 0);
+    }
+    OE_DEBUG << LC << "Setting timeout to " << timeout << std::endl;
+    curl_easy_setopt( _curl_handle, CURLOPT_TIMEOUT, timeout );
+    long connectTimeout = s_connectTimeout;
+    const char* connectTimeoutEnv = getenv("OSGEARTH_HTTP_CONNECTTIMEOUT");
+    if (connectTimeoutEnv)
+    {
+        connectTimeout = osgEarth::as<long>(std::string(connectTimeoutEnv), 0);
+    }
+    OE_DEBUG << LC << "Setting connect timeout to " << connectTimeout << std::endl;
+    curl_easy_setopt( _curl_handle, CURLOPT_CONNECTTIMEOUT, connectTimeout );
+
+    _initialized = true;
+}
+
+HTTPClient::~HTTPClient()
+{
+    if (_curl_handle) curl_easy_cleanup( _curl_handle );
+    _curl_handle = 0;
+}
+
+void
+HTTPClient::setProxySettings( const ProxySettings& proxySettings )
+{
+    s_proxySettings = proxySettings;
+}
+
+const std::string& HTTPClient::getUserAgent()
+{
+    return s_userAgent;
+}
+
+void  HTTPClient::setUserAgent(const std::string& userAgent)
+{
+    s_userAgent = userAgent;
+}
+
+long HTTPClient::getTimeout()
+{
+    return s_timeout;
+}
+
+void HTTPClient::setTimeout( long timeout )
+{
+    s_timeout = timeout;
+}
+
+long HTTPClient::getConnectTimeout()
+{
+    return s_connectTimeout;
+}
+
+void HTTPClient::setConnectTimeout( long timeout )
+{
+    s_connectTimeout = timeout;
+}
+URLRewriter* HTTPClient::getURLRewriter()
+{
+    return s_rewriter.get();
+}
+
+void HTTPClient::setURLRewriter( URLRewriter* rewriter )
+{
+    s_rewriter = rewriter;
+}
+
+CurlConfigHandler* HTTPClient::getCurlConfigHandler()
+{
+    return s_curlConfigHandler.get();
+}
+
+void HTTPClient::setCurlConfighandler(CurlConfigHandler* handler)
+{
+    s_curlConfigHandler = handler;
+}
+
+void
+HTTPClient::globalInit()
+{
+    curl_global_init(CURL_GLOBAL_ALL);
+}
+
+void
+HTTPClient::readOptions(const osgDB::Options* options, std::string& proxy_host, std::string& proxy_port) const
+{
+    // try to set proxy host/port by reading the CURL proxy options
+    if ( options )
+    {
+        std::istringstream iss( options->getOptionString() );
+        std::string opt;
+        while( iss >> opt )
+        {
+            int index = opt.find( "=" );
+            if( opt.substr( 0, index ) == "OSG_CURL_PROXY" )
+            {
+                proxy_host = opt.substr( index+1 );
+            }
+            else if ( opt.substr( 0, index ) == "OSG_CURL_PROXYPORT" )
+            {
+                proxy_port = opt.substr( index+1 );
+            }
+        }
+    }
+}
+
+bool
+HTTPClient::decodeMultipartStream(const std::string&   boundary,
+                                  HTTPResponse::Part*  input,
+                                  HTTPResponse::Parts& output) const
+{
+    std::string bstr = std::string("--") + boundary;
+    std::string line;
+    char tempbuf[256];
+
+    // first thing in the stream should be the boundary.
+    input->_stream.read( tempbuf, bstr.length() );
+    tempbuf[bstr.length()] = 0;
+    line = tempbuf;
+    if ( line != bstr )
+    {
+        OE_INFO << LC
+            << "decodeMultipartStream: protocol violation; "
+            << "expecting boundary; instead got: \""
+            << line
+            << "\"" << std::endl;
+        return false;
+    }
+
+    for( bool done=false; !done; )
+    {
+        osg::ref_ptr<HTTPResponse::Part> next_part = new HTTPResponse::Part();
+
+        // first finish off the boundary.
+        std::getline( input->_stream, line );
+        if ( line == "--" )
+        {
+            done = true;
+        }
+        else
+        {
+            // read all headers. this ends with a blank line.
+            line = " ";
+            while( line.length() > 0 && !done )
+            {
+                std::getline( input->_stream, line );
+
+                // check for EOS:
+                if ( line == "--" )
+                {
+                    done = true;
+                }
+                else
+                {
+                    StringTokenizer tok(":");
+                    StringVector tized;
+                    tok.tokenize(line, tized);
+                    if ( tized.size() >= 2 )
+                        next_part->_headers[tized[0]] = tized[1];
+                }
+            }
+        }
+
+        if ( !done )
+        {
+            // read data until we reach the boundary
+            unsigned int bstr_ptr = 0;
+            std::string temp;
+            //unsigned int c = 0;
+            while( bstr_ptr < bstr.length() )
+            {
+                char b;
+                input->_stream.read( &b, 1 );
+                if ( b == bstr[bstr_ptr] )
+                {
+                    bstr_ptr++;
+                }
+                else
+                {
+                    for( unsigned int i=0; i<bstr_ptr; i++ )
+                    {
+                        next_part->_stream << bstr[i];
+                    }
+                    next_part->_stream << b;
+                    next_part->_size += bstr_ptr + 1;
+                    bstr_ptr = 0;
+                }
+            }
+            output.push_back( next_part.get() );
+        }
+    }
+
+    return true;
+}
+
+HTTPResponse
+HTTPClient::get( const HTTPRequest&    request,
+                 const osgDB::Options* options,
+                 ProgressCallback*     progress)
+{
+    return getClient().doGet( request, options, progress );
+}
+
+HTTPResponse
+HTTPClient::get( const std::string&    url,
+                 const osgDB::Options* options,
+                 ProgressCallback*     progress)
+{
+    return getClient().doGet( url, options, progress);
+}
+
+ReadResult
+HTTPClient::readImage(const HTTPRequest&    request,
+                      const osgDB::Options* options,
+                      ProgressCallback*     progress)
+{
+    return getClient().doReadImage( request, options, progress );
+}
+
+ReadResult
+HTTPClient::readNode(const HTTPRequest&    request,
+                     const osgDB::Options* options,
+                     ProgressCallback*     progress)
+{
+    return getClient().doReadNode( request, options, progress );
+}
+
+ReadResult
+HTTPClient::readObject(const HTTPRequest&    request,
+                       const osgDB::Options* options,
+                       ProgressCallback*     progress)
+{
+    return getClient().doReadObject( request, options, progress );
+}
+
+ReadResult
+HTTPClient::readString(const HTTPRequest&    request,
+                       const osgDB::Options* options,
+                       ProgressCallback*     progress)
+{
+    return getClient().doReadString( request, options, progress );
+}
+
+bool
+HTTPClient::download(const std::string& uri,
+                     const std::string& localPath)
+{
+    return getClient().doDownload( uri, localPath );
+}
+
+
+#ifdef OSGEARTH_USE_WININET_FOR_HTTP
+
+namespace
+{
+    std::string GetLastErrorAsString()
+    {
+        //Get the error message, if any.
+        DWORD errorMessageID = ::GetLastError();
+        if(errorMessageID == 0)
+            return std::string("Error Code 0"); //No error message has been recorded
+
+        LPSTR messageBuffer = nullptr;
+        size_t size = FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
+                                     NULL, errorMessageID, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&messageBuffer, 0, NULL);
+
+        std::string message(messageBuffer, size);
+
+        //Free the buffer.
+        LocalFree(messageBuffer);
+
+        message = Stringify() << "[Code " << errorMessageID << "] " << message;
+
+        return message;
+    }
+}
+
+HTTPResponse
+HTTPClient::doGet(const HTTPRequest&    request,
+                  const osgDB::Options* options,
+                  ProgressCallback*     progress) const
+{
+    METRIC_BEGIN("HTTPClient::doGet", 1,
+                   "url", request.getURL().c_str());
+
+    OE_START_TIMER(http_get);
+
+    std::string url = request.getURL();
+    // Rewrite the url if the url rewriter is available
+    osg::ref_ptr< URLRewriter > rewriter = getURLRewriter();
+    if ( rewriter.valid() )
+    {
+        std::string oldURL = url;
+        url = rewriter->rewrite( oldURL );
+        OE_DEBUG << LC << "Rewrote URL " << oldURL << " to " << url << std::endl;
+    }
+
+    HINTERNET hInternet = InternetOpen(
+        getUserAgent().c_str(),
+        //"Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; AS; rv:11.0) like Gecko",
+        INTERNET_OPEN_TYPE_PRECONFIG,
+        NULL,       // proxy
+        NULL,       // proxy bypass
+        0);         // flags
+
+    if( !hInternet )
+    {
+        OE_WARN << LC << "InternetOpen failed: " << GetLastErrorAsString() << std::endl;
+        return HTTPResponse(0);
+    }
+
+    // clears any session cookies..?
+    // Commented this out, because is invalidates authorization caching:
+    //InternetSetOption( 0, INTERNET_OPTION_END_BROWSER_SESSION, NULL, 0 );
+
+    // parse the URL:
+    URL_COMPONENTS urlcomp;
+    ZeroMemory(&urlcomp, sizeof(urlcomp));
+	urlcomp.dwStructSize = sizeof(urlcomp);
+	urlcomp.dwHostNameLength = 1;
+    urlcomp.dwUserNameLength = 1;
+    urlcomp.dwPasswordLength = 1;
+    urlcomp.dwUrlPathLength  = 1;
+
+    if ( !InternetCrackUrl(url.c_str(), 0, 0L, &urlcomp) )
+    {
+        OE_WARN << LC << "InternetCrackUrl failed for " << url << ": " << GetLastErrorAsString() << std::endl;
+        InternetCloseHandle( hInternet );
+        return HTTPResponse(0);
+    }
+
+    WORD port =
+        urlcomp.nPort != 0 ? urlcomp.nPort :
+        urlcomp.nScheme == INTERNET_SCHEME_HTTPS ? INTERNET_DEFAULT_HTTPS_PORT :
+        INTERNET_DEFAULT_HTTP_PORT;
+
+    std::string hostName( urlcomp.lpszHostName );
+    int slash = hostName.find_first_of('/');
+    if ( slash != std::string::npos )
+        hostName = hostName.substr(0, slash);
+
+    OE_DEBUG
+        << "\n"
+        << "Host name = " << hostName << "\n"
+        << "Url path = " << urlcomp.lpszUrlPath << "\n"
+        << "Port = " << port << "\n";
+
+    DWORD openFlags =
+        //INTERNET_FLAG_PRAGMA_NOCACHE |
+        INTERNET_FLAG_RELOAD |
+        INTERNET_FLAG_NO_CACHE_WRITE |
+        INTERNET_FLAG_KEEP_CONNECTION;
+
+    if ( urlcomp.nScheme == INTERNET_SCHEME_HTTPS)
+    {
+        openFlags |= INTERNET_FLAG_SECURE;
+    }
+
+    // InternetOpenUrl is a lot less code, but we have to use the InternetConnnect +
+    // HttpOpenRequest + HttpSendRequest approach in order to support system dialogs
+    // for PKI certificates and username/password queries.
+
+    HINTERNET hConnection = InternetConnect(
+        hInternet,
+        hostName.c_str(),
+        port,
+        "", // username
+        "", // password
+        INTERNET_SERVICE_HTTP,
+        0,  // flags
+        INTERNET_NO_CALLBACK); // context
+
+    if ( !hConnection )
+    {
+        OE_WARN << LC << "InternetConnect failed for " << url << ": " << GetLastErrorAsString() << std::endl;
+        InternetCloseHandle( hInternet );
+        return HTTPResponse(0);
+    }
+
+    HINTERNET hRequest = HttpOpenRequest(
+        hConnection,            // handle from InternetConnect
+        "GET",                  // verb
+        urlcomp.lpszUrlPath,    // path
+        NULL,                   // HTTP version (NULL = default)
+        NULL,                   // Referrer
+        NULL,                   // Accept types
+        openFlags,              // flags
+        INTERNET_NO_CALLBACK);                  // context (user data)
+
+    if ( !hRequest )
+    {
+        OE_WARN << LC << "HttpOpenRequest failed for " << url << ": " << GetLastErrorAsString() << std::endl;
+        InternetCloseHandle( hConnection );
+        InternetCloseHandle( hInternet );
+        return HTTPResponse(0);
+    }
+
+    while( !HttpSendRequest(hRequest, NULL, 0, NULL, 0) )
+    {
+        DWORD errorNum = GetLastError();
+
+        // Request for client cert; open system dialog.
+        if ( errorNum == ERROR_INTERNET_CLIENT_AUTH_CERT_NEEDED )
+        {
+            OE_WARN << LC << "Server reports ERROR_INTERNET_CLIENT_AUTH_CERT_NEEDED!\n";
+
+            // Return ERROR_SUCCESS regardless of clicking on OK or Cancel
+            DWORD dialogResult = InternetErrorDlg(
+                GetDesktopWindow(),
+                hRequest,
+                ERROR_INTERNET_CLIENT_AUTH_CERT_NEEDED,
+                FLAGS_ERROR_UI_FILTER_FOR_ERRORS | FLAGS_ERROR_UI_FLAGS_GENERATE_DATA | FLAGS_ERROR_UI_FLAGS_CHANGE_OPTIONS,
+                NULL );
+
+            if ( dialogResult != ERROR_SUCCESS )
+            {
+                OE_WARN << LC << "InternetErrorDlg failed to produce client cert " << url << ": " << GetLastErrorAsString() << std::endl;
+                InternetCloseHandle( hRequest );
+                InternetCloseHandle( hConnection );
+                InternetCloseHandle( hInternet );
+                return HTTPResponse(0);
+            }
+        }
+
+        else
+        {
+            OE_WARN << LC << "HttpSendRequest failed to open " << url << ": " << GetLastErrorAsString() << std::endl;
+            InternetCloseHandle( hRequest );
+            InternetCloseHandle( hConnection );
+            InternetCloseHandle( hInternet );
+            return HTTPResponse(0);
+        }
+    }
+
+    int statusCode = 0;
+    std::string contentType;
+
+    char  buffer[4096];
+    DWORD bufferLen = 4096;
+    DWORD index = 0;
+
+    if ( HttpQueryInfo(hRequest, HTTP_QUERY_STATUS_CODE, buffer, &bufferLen, &index) )
+    {
+        statusCode = as<int>( std::string(buffer, bufferLen), 0 );
+    }
+
+    HTTPResponse response(statusCode);
+
+    bufferLen = 4096, index = 0;
+    if ( HttpQueryInfo(hRequest, HTTP_QUERY_CONTENT_TYPE, buffer, &bufferLen, &index) )
+    {
+        response._mimeType = std::string(buffer, bufferLen);
+    }
+
+    bufferLen = 4096, index = 0;
+    if ( HttpQueryInfo(hRequest, HTTP_QUERY_LAST_MODIFIED, buffer, &bufferLen, &index) )
+    {
+        response._lastModified = as<long>(std::string(buffer, bufferLen), 0L);
+    }
+
+    response._mimeType = contentType;
+
+    if ( statusCode == 200 )
+    {
+        osg::ref_ptr<HTTPResponse::Part> part = new HTTPResponse::Part();
+
+        DWORD numBytesRead = 0;
+        while( InternetReadFile(hRequest, buffer, 4096, &numBytesRead) && numBytesRead )
+        {
+            part->_stream << std::string(buffer, numBytesRead);
+        }
+
+        response._parts.push_back( part.get() );
+    }
+
+    InternetCloseHandle( hRequest );
+    InternetCloseHandle( hConnection );
+    InternetCloseHandle( hInternet );
+
+    response._duration_s = OE_STOP_TIMER(http_get);
+
+    if ( progress )
+    {
+        progress->stats("http_get_time") += OE_GET_TIMER(http_get);
+        progress->stats("http_get_count") += 1;
+        if ( response._cancelled )
+            progress->stats("http_cancel_count") += 1;
+    }
+
+    METRIC_END("HTTPClient::doGet", 1,
+                 "response_code", toString<int>(response.getCode()).c_str());
+
+    return response;
+}
+
+#else // OSGEARTH_USE_WININET_FOR_HTTP
+
+HTTPResponse
+HTTPClient::doGet(const HTTPRequest&    request,
+                  const osgDB::Options* options,
+                  ProgressCallback*     progress) const
+{
+    METRIC_BEGIN("HTTPClient::doGet", 1,
+                   "url", request.getURL().c_str());
+
+    initialize();
+
+    OE_START_TIMER(http_get);
+
+    std::string url = request.getURL();
+
+    const osgDB::AuthenticationMap* authenticationMap = (options && options->getAuthenticationMap()) ?
+            options->getAuthenticationMap() :
+            osgDB::Registry::instance()->getAuthenticationMap();
+
+    std::string proxy_host;
+    std::string proxy_port = "8080";
+
+    std::string proxy_auth;
+
+    //TODO: don't do all this proxy setup on every GET. Just do it once per client, or only when
+    // the proxy information changes.
+
+    //Try to get the proxy settings from the global settings
+    if (s_proxySettings.isSet())
+    {
+        proxy_host = s_proxySettings.get().hostName();
+        std::stringstream buf;
+        buf << s_proxySettings.get().port();
+        proxy_port = buf.str();
+
+        std::string proxy_username = s_proxySettings.get().userName();
+        std::string proxy_password = s_proxySettings.get().password();
+        if (!proxy_username.empty() && !proxy_password.empty())
+        {
+            proxy_auth = proxy_username + std::string(":") + proxy_password;
+        }
+    }
+
+    //Try to get the proxy settings from the local options that are passed in.
+    readOptions( options, proxy_host, proxy_port );
+
+    optional< ProxySettings > proxySettings;
+    ProxySettings::fromOptions( options, proxySettings );
+    if (proxySettings.isSet())
+    {
+        proxy_host = proxySettings.get().hostName();
+        proxy_port = toString<int>(proxySettings.get().port());
+        OE_DEBUG << LC << "Read proxy settings from options " << proxy_host << " " << proxy_port << std::endl;
+    }
+
+    //Try to get the proxy settings from the environment variable
+    const char* proxyEnvAddress = getenv("OSG_CURL_PROXY");
+    if (proxyEnvAddress) //Env Proxy Settings
+    {
+        proxy_host = std::string(proxyEnvAddress);
+
+        const char* proxyEnvPort = getenv("OSG_CURL_PROXYPORT"); //Searching Proxy Port on Env
+        if (proxyEnvPort)
+        {
+            proxy_port = std::string( proxyEnvPort );
+        }
+    }
+
+    const char* proxyEnvAuth = getenv("OSGEARTH_CURL_PROXYAUTH");
+    if (proxyEnvAuth)
+    {
+        proxy_auth = std::string(proxyEnvAuth);
+    }
+
+    // Set up proxy server:
+    std::string proxy_addr;
+    if ( !proxy_host.empty() )
+    {
+        std::stringstream buf;
+        buf << proxy_host << ":" << proxy_port;
+        std::string bufStr;
+        bufStr = buf.str();
+        proxy_addr = bufStr;
+
+        if ( s_HTTP_DEBUG )
+        {
+            OE_NOTICE << LC << "Using proxy: " << proxy_addr << std::endl;
+        }
+
+        //curl_easy_setopt( _curl_handle, CURLOPT_HTTPPROXYTUNNEL, 1 );
+        curl_easy_setopt( _curl_handle, CURLOPT_PROXY, proxy_addr.c_str() );
+
+        //Setup the proxy authentication if setup
+        if (!proxy_auth.empty())
+        {
+            if ( s_HTTP_DEBUG )
+            {
+                OE_NOTICE << LC << "Using proxy authentication " << proxy_auth << std::endl;
+            }
+
+            curl_easy_setopt( _curl_handle, CURLOPT_PROXYUSERPWD, proxy_auth.c_str());
+        }
+    }
+    else
+    {
+        OE_DEBUG << LC << "Removing proxy settings" << std::endl;
+        curl_easy_setopt( _curl_handle, CURLOPT_PROXY, 0 );
+    }
+
+    // Rewrite the url if the url rewriter is available
+    osg::ref_ptr< URLRewriter > rewriter = getURLRewriter();
+    if ( rewriter.valid() )
+    {
+        std::string oldURL = url;
+        url = rewriter->rewrite( oldURL );
+        OE_DEBUG << LC << "Rewrote URL " << oldURL << " to " << url << std::endl;
+    }
+
+    const osgDB::AuthenticationDetails* details = authenticationMap ?
+        authenticationMap->getAuthenticationDetails( url ) :
+        0;
+
+    if (details)
+    {
+        const std::string colon(":");
+        std::string password(details->username + colon + details->password);
+        curl_easy_setopt(_curl_handle, CURLOPT_USERPWD, password.c_str());
+        const_cast<HTTPClient*>(this)->_previousPassword = password;
+
+        // use for https.
+        // curl_easy_setopt(_curl, CURLOPT_KEYPASSWD, password.c_str());
+
+#if LIBCURL_VERSION_NUM >= 0x070a07
+        if (details->httpAuthentication != _previousHttpAuthentication)
+        {
+            curl_easy_setopt(_curl_handle, CURLOPT_HTTPAUTH, details->httpAuthentication);
+            const_cast<HTTPClient*>(this)->_previousHttpAuthentication = details->httpAuthentication;
+        }
+#endif
+    }
+    else
+    {
+        if (!_previousPassword.empty())
+        {
+            curl_easy_setopt(_curl_handle, CURLOPT_USERPWD, 0);
+            const_cast<HTTPClient*>(this)->_previousPassword.clear();
+        }
+
+#if LIBCURL_VERSION_NUM >= 0x070a07
+        // need to reset if previously set.
+        if (_previousHttpAuthentication!=0)
+        {
+            curl_easy_setopt(_curl_handle, CURLOPT_HTTPAUTH, 0);
+            const_cast<HTTPClient*>(this)->_previousHttpAuthentication = 0;
+        }
+#endif
+    }
+
+
+    // Set any headers
+    struct curl_slist *headers=NULL;
+    if (!request.getHeaders().empty())
+    {
+        for (HTTPRequest::Parameters::const_iterator itr = request.getHeaders().begin(); itr != request.getHeaders().end(); ++itr)
+        {
+            std::stringstream buf;
+            buf << itr->first << ": " << itr->second;
+            headers = curl_slist_append(headers, buf.str().c_str());
+        }
+    }
+
+    // Disable the default Pragma: no-cache that curl adds by default.
+    headers = curl_slist_append(headers, "Pragma: ");
+    curl_easy_setopt(_curl_handle, CURLOPT_HTTPHEADER, headers);
+
+    osg::ref_ptr<HTTPResponse::Part> part = new HTTPResponse::Part();
+    StreamObject sp( &part->_stream );
+
+    //Take a temporary ref to the callback (why? dangerous.)
+    //osg::ref_ptr<ProgressCallback> progressCallback = callback;
+    curl_easy_setopt( _curl_handle, CURLOPT_URL, url.c_str() );
+    if (progress)
+    {
+        curl_easy_setopt(_curl_handle, CURLOPT_PROGRESSDATA, progress);
+    }
+
+    CURLcode res;
+    long response_code = 0L;
+
+    OE_START_TIMER(get_duration);
+
+    if ( _simResponseCode < 0 )
+    {
+        char errorBuf[CURL_ERROR_SIZE];
+        errorBuf[0] = 0;
+        curl_easy_setopt( _curl_handle, CURLOPT_ERRORBUFFER, (void*)errorBuf );
+        curl_easy_setopt( _curl_handle, CURLOPT_WRITEDATA, (void*)&sp);
+        curl_easy_setopt( _curl_handle, CURLOPT_HEADERDATA, (void*)&sp);
+
+        //Disable peer certificate verification to allow us to access in https servers where the peer certificate cannot be verified.
+        curl_easy_setopt( _curl_handle, CURLOPT_SSL_VERIFYPEER, (void*)0 );
+
+        osg::ref_ptr< CurlConfigHandler > curlConfigHandler = getCurlConfigHandler();
+        if (curlConfigHandler.valid()) {
+            curlConfigHandler->onGet(_curl_handle);
+        }
+
+        res = curl_easy_perform(_curl_handle);
+
+        curl_easy_setopt( _curl_handle, CURLOPT_WRITEDATA, (void*)0 );
+        curl_easy_setopt( _curl_handle, CURLOPT_PROGRESSDATA, (void*)0);
+
+        if (!proxy_addr.empty())
+        {
+            long connect_code = 0L;
+            CURLcode r = curl_easy_getinfo(_curl_handle, CURLINFO_HTTP_CONNECTCODE, &connect_code);
+            if ( r != CURLE_OK )
+            {
+                OE_WARN << LC << "Proxy connect error: " << curl_easy_strerror(r) << std::endl;
+
+                // Free the headers
+                if (headers)
+                {
+                    curl_slist_free_all(headers);
+                }
+
+                return HTTPResponse(0);
+            }
+        }
+
+        curl_easy_getinfo( _curl_handle, CURLINFO_RESPONSE_CODE, &response_code );
+    }
+    else
+    {
+        // simulate failure with a custom response code
+        response_code = _simResponseCode;
+        res = response_code == 408 ? CURLE_OPERATION_TIMEDOUT : CURLE_COULDNT_CONNECT;
+    }
+
+    HTTPResponse response( response_code );
+
+    // read the response content type:
+    char* content_type_cp;
+
+    curl_easy_getinfo( _curl_handle, CURLINFO_CONTENT_TYPE, &content_type_cp );
+
+    if ( content_type_cp != NULL )
+    {
+        response._mimeType = content_type_cp;
+    }
+
+    // read the file time:
+    response._lastModified = getCurlFileTime( _curl_handle );
+
+    if (res == CURLE_OK)
+    {
+        // check for multipart content
+        if (response._mimeType.length() > 9 &&
+            ::strstr( response._mimeType.c_str(), "multipart" ) == response._mimeType.c_str() )
+        {
+            OE_DEBUG << LC << "detected multipart data; decoding..." << std::endl;
+
+            //TODO: parse out the "wcs" -- this is WCS-specific
+            if ( !decodeMultipartStream( "wcs", part.get(), response._parts ) )
+            {
+                // error decoding an invalid multipart stream.
+                // should we do anything, or just leave the response empty?
+            }
+        }
+        else
+        {
+            for (Headers::iterator itr = sp._headers.begin(); itr != sp._headers.end(); ++itr)
+            {
+                part->_headers[itr->first] = itr->second;
+            }
+
+            // Write the headers to the metadata
+            response._parts.push_back( part.get() );
+        }
+    }
+
+    else if (res == CURLE_ABORTED_BY_CALLBACK || res == CURLE_OPERATION_TIMEDOUT)
+    {
+        //If we were aborted by a callback, then it was cancelled by a user
+        response._cancelled = true;
+    }
+
+    else
+    {
+        response._message = curl_easy_strerror(res);
+
+        if (res == CURLE_GOT_NOTHING)
+        {
+            OE_DEBUG << LC << "CURLE_GOT_NOTHING for " << url << std::endl;
+        }
+    }
+
+    response._duration_s = OE_STOP_TIMER(get_duration);
+
+    if ( progress )
+    {
+        progress->stats()["http_get_time"] += OE_STOP_TIMER(http_get);
+        progress->stats()["http_get_count"] += 1;
+        if ( response._cancelled )
+            progress->stats()["http_cancel_count"] += 1;
+    }
+
+    if ( s_HTTP_DEBUG )
+    {
+        TimeStamp filetime = getCurlFileTime(_curl_handle);
+
+        OE_NOTICE << LC
+            << "GET(" << response_code << ") " << response._mimeType << ": \""
+            << url << "\" (" << DateTime(filetime).asRFC1123() << ") t="
+            << std::setprecision(4) << response.getDuration() << "s" << std::endl;
+
+        {
+            Threading::ScopedMutexLock lock(s_HTTP_DEBUG_mutex);
+            s_HTTP_DEBUG_request_count++;
+            s_HTTP_DEBUG_total_duration += response.getDuration();
+
+            if ( s_HTTP_DEBUG_request_count % 60 == 0 )
+            {
+                OE_NOTICE << LC << "Average duration = " << s_HTTP_DEBUG_total_duration/(double)s_HTTP_DEBUG_request_count
+                    << std::endl;
+            }
+        }
+
+#if 0
+        // time details - almost 100% of the time is spent in
+        // STARTTRANSFER, which is the time until the first byte is received.
+        double td[7];
+
+        curl_easy_getinfo(_curl_handle, CURLINFO_TOTAL_TIME,         &td[0]);
+        curl_easy_getinfo(_curl_handle, CURLINFO_NAMELOOKUP_TIME,    &td[1]);
+        curl_easy_getinfo(_curl_handle, CURLINFO_CONNECT_TIME,       &td[2]);
+        curl_easy_getinfo(_curl_handle, CURLINFO_APPCONNECT_TIME,    &td[3]);
+        curl_easy_getinfo(_curl_handle, CURLINFO_PRETRANSFER_TIME,   &td[4]);
+        curl_easy_getinfo(_curl_handle, CURLINFO_STARTTRANSFER_TIME, &td[5]);
+        curl_easy_getinfo(_curl_handle, CURLINFO_REDIRECT_TIME,      &td[6]);
+
+        for(int i=0; i<7; ++i)
+        {
+            OE_NOTICE << LC
+                << std::setprecision(4)
+                << "TIMES: total=" <<td[0]
+                << ", lookup=" <<td[1]<<" ("<<(int)((td[1]/td[0])*100)<<"%)"
+                << ", connect=" <<td[2]<<" ("<<(int)((td[2]/td[0])*100)<<"%)"
+                << ", appconn=" <<td[3]<<" ("<<(int)((td[3]/td[0])*100)<<"%)"
+                << ", prexfer=" <<td[4]<<" ("<<(int)((td[4]/td[0])*100)<<"%)"
+                << ", startxfer=" <<td[5]<<" ("<<(int)((td[5]/td[0])*100)<<"%)"
+                << ", redir=" <<td[6]<<" ("<<(int)((td[6]/td[0])*100)<<"%)"
+                << std::endl;
+        }
+#endif
+    }
+
+    // Free the headers
+    if (headers)
+    {
+        curl_slist_free_all(headers);
+    }
+
+    METRIC_END("HTTPClient::doGet", 2,
+               "response_code", toString<int>(response.getCode()).c_str(),
+               "canceled", toString<bool>(response.isCancelled()).c_str());
+
+    return response;
+}
+
+#endif // USE_WININET
+
+bool
+HTTPClient::doDownload(const std::string& url, const std::string& filename)
+{
+    initialize();
+
+    // download the data
+    HTTPResponse response = this->doGet( HTTPRequest(url) );
+
+    if ( response.isOK() )
+    {
+        unsigned int part_num = response.getNumParts() > 1? 1 : 0;
+        std::istream& input_stream = response.getPartStream( part_num );
+
+        std::ofstream fout;
+        fout.open(filename.c_str(), std::ios::out | std::ios::binary);
+
+        input_stream.seekg (0, std::ios::end);
+        int length = input_stream.tellg();
+        input_stream.seekg (0, std::ios::beg);
+
+        char *buffer = new char[length];
+        input_stream.read(buffer, length);
+        fout.write(buffer, length);
+        delete[] buffer;
+        fout.close();
+        return true;
+    }
+    else
+    {
+        OE_WARN << LC << "Error downloading file " << filename
+            << " (" << response.getCode() << ")" << std::endl;
+        return false;
+    }
+}
+
+namespace
+{
+    osgDB::ReaderWriter*
+    getReader( const std::string& url, const HTTPResponse& response )
+    {
+        osgDB::ReaderWriter* reader = 0L;
+
+        // try extension first:
+        std::string ext = osgDB::getFileExtension( url );
+        if ( !ext.empty() )
+        {
+            reader = osgDB::Registry::instance()->getReaderWriterForExtension( ext );
+        }
+
+        if ( !reader )
+        {
+            // try to look up a reader by mime-type first:
+            std::string mimeType = response.getMimeType();
+            if ( !mimeType.empty() )
+            {
+                reader = osgDB::Registry::instance()->getReaderWriterForMimeType(mimeType);
+            }
+        }
+
+        if ( !reader && s_HTTP_DEBUG )
+        {
+            OE_WARN << LC << "Cannot find an OSG plugin to read response data (ext="
+                << ext << "; mime-type=" << response.getMimeType()
+                << ")" << std::endl;
+
+            if ( endsWith(response.getMimeType(), "xml", false) )
+            {
+                OE_WARN << LC << "Content:\n" << response.getPartAsString(0) << "\n";
+            }
+        }
+
+        return reader;
+    }
+}
+
+ReadResult
+HTTPClient::doReadImage(const HTTPRequest&    request,
+                        const osgDB::Options* options,
+                        ProgressCallback*     callback)
+{
+    initialize();
+
+    ReadResult result;
+
+    HTTPResponse response = this->doGet(request, options, callback);
+
+    if (response.isOK())
+    {
+        osgDB::ReaderWriter* reader = getReader(request.getURL(), response);
+        if (!reader)
+        {
+            result = ReadResult(ReadResult::RESULT_NO_READER);
+        }
+
+        else
+        {
+            osgDB::ReaderWriter::ReadResult rr = reader->readImage(response.getPartStream(0), options);
+            if ( rr.validImage() )
+            {
+                result = ReadResult(rr.takeImage());
+            }
+            else
+            {
+                if ( s_HTTP_DEBUG )
+                {
+                    OE_WARN << LC << reader->className()
+                        << " failed to read image from " << request.getURL()
+                        << "; message = " << rr.message()
+                        <<  std::endl;
+                }
+                result = ReadResult(ReadResult::RESULT_READER_ERROR);
+                result.setErrorDetail( rr.message() );
+            }
+        }
+
+        // last-modified (file time)
+        result.setLastModifiedTime( response._lastModified ); //getCurlFileTime(_curl_handle) );
+
+        // Time of query
+        result.setDuration( response.getDuration() );
+    }
+    else
+    {
+        result = ReadResult(
+            response.isCancelled()                           ? ReadResult::RESULT_CANCELED :
+            response.getCode() == HTTPResponse::NOT_FOUND    ? ReadResult::RESULT_NOT_FOUND :
+            response.getCode() == HTTPResponse::SERVER_ERROR ? ReadResult::RESULT_SERVER_ERROR :
+            response.getCode() == HTTPResponse::NOT_MODIFIED ? ReadResult::RESULT_NOT_MODIFIED :
+                                                               ReadResult::RESULT_UNKNOWN_ERROR );
+
+        //If we have an error but it's recoverable, like a server error or timeout then set the callback to retry.
+        if (HTTPClient::isRecoverable( result.code() ) )
+        {
+            if (callback)
+            {
+                if ( s_HTTP_DEBUG )
+                {
+                    if (response.isCancelled())
+                        OE_NOTICE << LC << "Request was cancelled" << std::endl;
+                    else
+                        OE_NOTICE << LC << "Error in HTTPClient for " << request.getURL() << " but it's recoverable" << std::endl;
+                }
+                callback->setNeedsRetry( true );
+            }
+        }
+    }
+
+    // encode headers
+    result.setMetadata( response.getHeadersAsConfig() );
+
+    // set the source name
+    if ( result.getImage() )
+        result.getImage()->setName( request.getURL() );
+
+    return result;
+}
+
+ReadResult
+HTTPClient::doReadNode(const HTTPRequest&    request,
+                       const osgDB::Options* options,
+                       ProgressCallback*     callback)
+{
+    initialize();
+
+    ReadResult result;
+
+    HTTPResponse response = this->doGet(request, options, callback);
+
+    if (response.isOK())
+    {
+        osgDB::ReaderWriter* reader = getReader(request.getURL(), response);
+        if (!reader)
+        {
+            result = ReadResult(ReadResult::RESULT_NO_READER);
+        }
+
+        else
+        {
+            osgDB::ReaderWriter::ReadResult rr = reader->readNode(response.getPartStream(0), options);
+            if ( rr.validNode() )
+            {
+                result = ReadResult(rr.takeNode());
+            }
+            else
+            {
+                if ( s_HTTP_DEBUG )
+                {
+                    OE_WARN << LC << reader->className()
+                        << " failed to read node from " << request.getURL()
+                        << "; message = " << rr.message()
+                        <<  std::endl;
+                }
+                result = ReadResult(ReadResult::RESULT_READER_ERROR);
+                result.setErrorDetail( rr.message() );
+            }
+        }
+
+        // last-modified (file time)
+        result.setLastModifiedTime( response._lastModified ); //getCurlFileTime(_curl_handle) );
+    }
+    else
+    {
+        result = ReadResult(
+            response.isCancelled()                           ? ReadResult::RESULT_CANCELED :
+            response.getCode() == HTTPResponse::NOT_FOUND    ? ReadResult::RESULT_NOT_FOUND :
+            response.getCode() == HTTPResponse::SERVER_ERROR ? ReadResult::RESULT_SERVER_ERROR :
+            response.getCode() == HTTPResponse::NOT_MODIFIED ? ReadResult::RESULT_NOT_MODIFIED :
+                                                               ReadResult::RESULT_UNKNOWN_ERROR );
+
+        //If we have an error but it's recoverable, like a server error or timeout then set the callback to retry.
+        if (HTTPClient::isRecoverable( result.code() ) )
+        {
+            if (callback)
+            {
+                if ( s_HTTP_DEBUG )
+                {
+                    if (response.isCancelled())
+                        OE_NOTICE << LC << "Request was cancelled" << std::endl;
+                    else
+                        OE_NOTICE << LC << "Error in HTTPClient for " << request.getURL() << " but it's recoverable" << std::endl;
+                }
+                callback->setNeedsRetry( true );
+            }
+        }
+    }
+
+    // encode headers
+    result.setMetadata( response.getHeadersAsConfig() );
+
+    return result;
+}
+
+ReadResult
+HTTPClient::doReadObject(const HTTPRequest&    request,
+                         const osgDB::Options* options,
+                         ProgressCallback*     callback)
+{
+    initialize();
+
+    ReadResult result;
+
+    HTTPResponse response = this->doGet(request, options, callback);
+
+    if (response.isOK())
+    {
+        osgDB::ReaderWriter* reader = getReader(request.getURL(), response);
+        if (!reader)
+        {
+            result = ReadResult(ReadResult::RESULT_NO_READER);
+        }
+
+        else
+        {
+            osgDB::ReaderWriter::ReadResult rr = reader->readObject(response.getPartStream(0), options);
+            if ( rr.validObject() )
+            {
+                result = ReadResult(rr.takeObject());
+            }
+            else
+            {
+                if ( s_HTTP_DEBUG )
+                {
+                    OE_WARN << LC << reader->className()
+                        << " failed to read object from " << request.getURL()
+                        << "; message = " << rr.message()
+                        <<  std::endl;
+                }
+                result = ReadResult(ReadResult::RESULT_READER_ERROR);
+                result.setErrorDetail( rr.message() );
+            }
+        }
+
+        // last-modified (file time)
+        result.setLastModifiedTime( response._lastModified ); //getCurlFileTime(_curl_handle) );
+    }
+    else
+    {
+        result = ReadResult(
+            response.isCancelled() ? ReadResult::RESULT_CANCELED :
+            response.getCode() == HTTPResponse::NOT_FOUND ? ReadResult::RESULT_NOT_FOUND :
+            response.getCode() == HTTPResponse::SERVER_ERROR ? ReadResult::RESULT_SERVER_ERROR :
+            response.getCode() == HTTPResponse::NOT_MODIFIED ? ReadResult::RESULT_NOT_MODIFIED :
+            ReadResult::RESULT_UNKNOWN_ERROR );
+
+        //If we have an error but it's recoverable, like a server error or timeout then set the callback to retry.
+        if (HTTPClient::isRecoverable( result.code() ) )
+        {
+            if (callback)
+            {
+                if ( s_HTTP_DEBUG )
+                {
+                    if (response.isCancelled())
+                        OE_NOTICE << LC << "Request was cancelled" << std::endl;
+                    else
+                        OE_NOTICE << LC << "Error in HTTPClient for " << request.getURL() << " but it's recoverable" << std::endl;
+                }
+                callback->setNeedsRetry( true );
+            }
+        }
+    }
+
+    result.setMetadata( response.getHeadersAsConfig() );
+
+    return result;
+}
+
+
+ReadResult
+HTTPClient::doReadString(const HTTPRequest&    request,
+                         const osgDB::Options* options,
+                         ProgressCallback*     callback )
+{
+    initialize();
+
+    ReadResult result;
+
+    HTTPResponse response = this->doGet( request, options, callback );
+    if ( response.isOK() )
+    {
+        result = ReadResult( new StringObject(response.getPartAsString(0)) );
+    }
+
+    else if ( response.getCode() >= 400 && response.getCode() < 500 && response.getCode() != 404 )
+    {
+        // for request errors, return an error result with the part data intact
+        // so the user can parse it as needed. We only do this for readString.
+        result = ReadResult(
+            ReadResult::RESULT_SERVER_ERROR,
+            new StringObject(response.getPartAsString(0)) );
+    }
+
+    else
+    {
+        result = ReadResult(
+            response.isCancelled() ?                           ReadResult::RESULT_CANCELED :
+            response.getCode() == HTTPResponse::NOT_FOUND    ? ReadResult::RESULT_NOT_FOUND :
+            response.getCode() == HTTPResponse::SERVER_ERROR ? ReadResult::RESULT_SERVER_ERROR :
+            response.getCode() == HTTPResponse::NOT_MODIFIED ? ReadResult::RESULT_NOT_MODIFIED :
+                                                               ReadResult::RESULT_UNKNOWN_ERROR );
+
+        //If we have an error but it's recoverable, like a server error or timeout then set the callback to retry.
+        if (HTTPClient::isRecoverable( result.code() ) )
+        {
+            if (callback)
+            {
+                if ( s_HTTP_DEBUG )
+                {
+                    if (response.isCancelled())
+                        OE_NOTICE << LC << "Request was cancelled" << std::endl;
+                    else
+                        OE_NOTICE << LC << "Error in HTTPClient for " << request.getURL() << " but it's recoverable" << std::endl;
+                }
+                callback->setNeedsRetry( true );
+            }
+        }
+    }
+
+    // encode headers
+    result.setMetadata( response.getHeadersAsConfig() );
+
+    // last-modified (file time)
+    result.setLastModifiedTime( response._lastModified ); //getCurlFileTime(_curl_handle) );
+
+    return result;
+}
diff --git a/src/osgEarth/HeightFieldUtils b/src/osgEarth/HeightFieldUtils
index 56ff28e..1ebb0df 100644
--- a/src/osgEarth/HeightFieldUtils
+++ b/src/osgEarth/HeightFieldUtils
@@ -76,8 +76,8 @@ namespace osgEarth
         static float getHeightAtPixel(
             const osg::HeightField* hf, 
             double c, double r, 
-            ElevationInterpolation interpoltion = INTERP_BILINEAR);
-        
+            ElevationInterpolation interpolation = INTERP_BILINEAR);
+
         /**
          * Gets the height value at the specified column and row, but instead of reading
          * the actual height, interpolates a height based on the neighbors.
@@ -99,6 +99,16 @@ namespace osgEarth
             ElevationInterpolation interpolation = INTERP_BILINEAR);
 
         /**
+         * Gets the normal vector at a geolocation
+         */
+        static osg::Vec3 getNormalAtLocation(
+            const HeightFieldNeighborhood& hood,
+            double x, double y, 
+            double llx, double lly,
+            double dx, double dy,
+            ElevationInterpolation interpolation = INTERP_BILINEAR);
+
+        /**
          * Gets the interpolated elevation at the specified "normalized unit location".
          * i.e., nx => [0.0, 1.0], ny => [0.0, 1.0] where 0.0 and 1.0 are the opposing
          * endposts of the heightfield.
@@ -120,17 +130,6 @@ namespace osgEarth
             ElevationInterpolation interp = INTERP_BILINEAR);
 
         /**
-         * Gets the normal vector at the specified "normalized unit location".
-         * i.e., nx => [0.0, 1.0], ny => [0.0, 1.0] where 0.0 and 1.0 are the opposing
-         * endposts of the heightfield.
-         */
-        static bool getNormalAtNormalizedLocation(
-            const osg::HeightField* hf,
-            double nx, double ny,
-            osg::Vec3& output,
-            ElevationInterpolation interp = INTERP_BILINEAR);
-
-        /**
          * Scales all the height values in a heightfield from scalar units to "linear degrees".
          * The only purpose of this is to show reasonable height values in a projected
          * Plate Carre map (in which vertical units are not well defined).
@@ -140,6 +139,10 @@ namespace osgEarth
         /**
          * Creates a heightfield containing MSL heights for the specified extent.
          *
+         * @param numCols Width of the heightfield, not including the border
+         * @param numRows Height of the heightfield, not including the border
+         * @param border Size (in samples) of the border (on each side). This will increase the
+         *               actual numRows and numCols.
          * @param expressHeightsAsHAE
          *      If the SRS (in GeoExtent) has a vertical datum, and expressHeightsAsHAE==true,
          *      the height values will be those of its reference geoid. If there is no vertical
@@ -150,6 +153,7 @@ namespace osgEarth
             const GeoExtent& ex, 
             unsigned         numCols,
             unsigned         numRows,
+            unsigned         border,
             bool             expressHeightsAsHAE =true);
 
         /**
@@ -195,9 +199,18 @@ namespace osgEarth
         /**
          * Convert a heightfield (and its neighbors) into a normal map* image.
          */
-        static osg::Image* convertToNormalMap(
+        static NormalMap* convertToNormalMap(
             const HeightFieldNeighborhood& hood,
             const SpatialReference*        hoodSRS);
+        
+        /**
+         * Reads elevation data from one image and writes a normal/curvature map
+         * for it to another image of the same size
+         */
+        static void createNormalMap(
+            const osg::Image* elevation,
+            NormalMap*        normalMap,
+            const GeoExtent&  extent);
 
 
         /**
@@ -207,67 +220,6 @@ namespace osgEarth
          **/
         static bool validateSamples(float &a, float &b, float &c, float &d);
     };
-
-#if 0
-    /**
-    * A collection of ValidDataOperators.  All operators must pass to be considered valid.
-    */
-    struct OSGEARTH_EXPORT CompositeValidValueOperator : public osgTerrain::ValidDataOperator
-    {
-        typedef std::vector<osg::ref_ptr<osgTerrain::ValidDataOperator> > ValidDataOperatorList;
-        ValidDataOperatorList& getOperators() { return _operators;}
-
-        virtual bool operator() (float value) const
-        {
-            for (ValidDataOperatorList::const_iterator itr = _operators.begin(); itr != _operators.end(); ++itr)
-            {
-                if (!(*itr->get())(value)) return false;
-            }
-            return true;
-        }
-
-        ValidDataOperatorList _operators;
-    };
-
-    /**
-     * Visitor that replaces "invalid" data values with a known value.
-     */
-    struct OSGEARTH_EXPORT ReplaceInvalidDataOperator : public osg::Referenced
-    {
-        ReplaceInvalidDataOperator();
-
-        virtual void operator()(osg::HeightField* heightField);
-
-        osgTerrain::ValidDataOperator* getValidDataOperator() { return _validDataOperator.get(); }
-        void setValidDataOperator(osgTerrain::ValidDataOperator* validDataOperator) { _validDataOperator = validDataOperator; }
-
-        float getReplaceWith() { return _replaceWith; }
-        void setReplaceWith( float replaceWith ) { _replaceWith = replaceWith; }
-
-        osg::ref_ptr<osgTerrain::ValidDataOperator> _validDataOperator;
-        float _replaceWith;
-    };
-
-    /**
-     * Visitor that replaces "invalid" data values with a default value.
-     */
-    struct OSGEARTH_EXPORT FillNoDataOperator : public osg::Referenced
-    {
-        FillNoDataOperator();
-
-        virtual void operator()(osg::HeightField* heightField);
-
-        osgTerrain::ValidDataOperator* getValidDataOperator() { return _validDataOperator.get(); }
-        void setValidDataOperator(osgTerrain::ValidDataOperator* validDataOperator) { _validDataOperator = validDataOperator; }
-
-        float getDefaultValue() { return _defaultValue; }
-        void setDefaultValue(float defaultValue) { _defaultValue = defaultValue; }
-
-        osg::ref_ptr<osgTerrain::ValidDataOperator> _validDataOperator;
-
-        float _defaultValue;
-    };
-#endif
 }
 
 #endif //OSGEARTH_HEIGHTFIELDUTILS_H
diff --git a/src/osgEarth/HeightFieldUtils.cpp b/src/osgEarth/HeightFieldUtils.cpp
index 351bd0c..6faa5e5 100644
--- a/src/osgEarth/HeightFieldUtils.cpp
+++ b/src/osgEarth/HeightFieldUtils.cpp
@@ -1,3 +1,4 @@
+
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
  * Copyright 2016 Pelican Mapping
@@ -254,6 +255,87 @@ HeightFieldUtils::getHeightAtLocation(const osg::HeightField* hf, double x, doub
     return getHeightAtPixel(hf, px, py, interpolation);
 }
 
+osg::Vec3
+HeightFieldUtils::getNormalAtLocation(const HeightFieldNeighborhood& hood, double x, double y, double llx, double lly, double dx, double dy, ElevationInterpolation interp)
+{
+    const osg::HeightField* hf = hood._center.get();
+    if (!hf)
+        return osg::Vec3(0,0,1);
+
+    double xcells = (double)(hf->getNumColumns()-1);
+    double ycells = (double)(hf->getNumRows()-1);
+
+    double xres = 1.0/xcells;
+    double yres = 1.0/ycells;
+
+    double mPerDegAtEquator = 111000.0;
+    double tIntervalMeters = hf->getYInterval() * mPerDegAtEquator; // TODO: account for Geo vs Proj (see convertToNormalMap)
+    
+    double s = osg::clampBetween( (x - llx) / dx, 0.0, xcells );
+    double t = osg::clampBetween( (y - lly) / dy, 0.0, ycells );
+
+    double lat = hf->getOrigin().y() + hf->getYInterval()*t;
+    
+    double sIntervalMeters = hf->getXInterval() * mPerDegAtEquator*cos(osg::DegreesToRadians(lat));
+
+    float centerHeight = getHeightAtLocation(hf, x, y, llx, lly, dx, dy, interp);
+
+    double nx = xres * s;
+    double ny = yres * t;
+
+    osg::Vec3 west(-sIntervalMeters, 0.0f, centerHeight);
+    osg::Vec3 east( sIntervalMeters, 0.0f, centerHeight);
+    osg::Vec3 south(0, -tIntervalMeters, centerHeight);
+    osg::Vec3 north(0,  tIntervalMeters, centerHeight);
+
+    bool clamped = false;
+
+    if (!getHeightAtNormalizedLocation(hood, nx - xres, ny, west.z())) {
+        west.x() = 0.0, 
+        west.z() = centerHeight;
+        clamped = true;
+    }
+
+    if (!getHeightAtNormalizedLocation(hood, nx + xres, ny, east.z())) {
+        east.x() = 0.0, east.z() = centerHeight;
+        clamped = true;
+    }
+
+    if (!getHeightAtNormalizedLocation(hood, nx, ny - yres, south.z())) {
+        south.y() = 0.0, south.z() = centerHeight;
+        clamped = true;
+    }
+
+    if (!getHeightAtNormalizedLocation(hood, nx, ny + yres, north.z())) {
+        north.y() = 0.0, north.z() = centerHeight;
+        clamped = true;
+    }    
+
+    // account for degenerate vectors
+    if (east.x() == 0.0 && west.x() == 0.0)
+        east.x() = sIntervalMeters;
+
+    if (north.y() == 0.0 && south.y() == 0.0)
+        north.y() = tIntervalMeters;
+
+    osg::Vec3 n = (east - west) ^ (north-south);
+    n.normalize();
+
+    //if (clamped)
+    //    n = osg::Vec3(1,0,0);
+
+    //if (clamped && (n*osg::Vec3(0,0,1) < 0.1)) {
+    //    OE_WARN << "H, normal = " << n.x() << ", " << n.y() << ", " << n.z() << "\n";
+    //}
+
+    // curvature:
+    float D = (0.5*(west.z() + east.z()) - centerHeight) / (sIntervalMeters*sIntervalMeters);
+    float E = (0.5*(south.z() + north.z()) - centerHeight) / (tIntervalMeters*tIntervalMeters);
+    float curvature = osg::clampBetween(-2.0f*(D + E)*100.0f, -1.0f, 1.0f);
+    
+    return n; // TODO, include curv
+}
+
 float
 HeightFieldUtils::getHeightAtNormalizedLocation(const osg::HeightField* input,
                                                 double nx, double ny,
@@ -271,18 +353,18 @@ HeightFieldUtils::getHeightAtNormalizedLocation(const HeightFieldNeighborhood& h
                                                 ElevationInterpolation interp)
 {
     osg::HeightField* hf = 0L;
-    //osg::ref_ptr<osg::HeightField> hf;
     double nx2, ny2;
     if ( hood.getNeighborForNormalizedLocation(nx, ny, hf, nx2, ny2) )
     {
         double px = osg::clampBetween(nx2, 0.0, 1.0) * (double)(hf->getNumColumns() - 1);
         double py = osg::clampBetween(ny2, 0.0, 1.0) * (double)(hf->getNumRows() - 1);
         output = getHeightAtPixel( hf, px, py, interp );
-        return true;
+        return output != NO_DATA_VALUE;
     }
     return false;
 }
 
+#if 0
 bool
 HeightFieldUtils::getNormalAtNormalizedLocation(const osg::HeightField* input,
                                                 double nx, double ny,
@@ -312,6 +394,7 @@ HeightFieldUtils::getNormalAtNormalizedLocation(const osg::HeightField* input,
     output.normalize();
     return true;
 }
+#endif
 
 void
 HeightFieldUtils::scaleHeightFieldToDegrees( osg::HeightField* hf )
@@ -425,13 +508,20 @@ osg::HeightField*
 HeightFieldUtils::createReferenceHeightField(const GeoExtent& ex,
                                              unsigned         numCols,
                                              unsigned         numRows,
+                                             unsigned         border,
                                              bool             expressAsHAE)
 {
     osg::HeightField* hf = new osg::HeightField();
-    hf->allocate( numCols, numRows );
-    hf->setOrigin( osg::Vec3d( ex.xMin(), ex.yMin(), 0.0 ) );
-    hf->setXInterval( (ex.xMax() - ex.xMin())/(double)(numCols-1) );
-    hf->setYInterval( (ex.yMax() - ex.yMin())/(double)(numRows-1) );
+
+    hf->allocate( numCols + 2*border, numRows + 2*border );
+
+    hf->setXInterval( ex.width() / (double)(numCols-1) );
+    hf->setYInterval( ex.height() / (double)(numRows-1) );
+
+    hf->setOrigin(osg::Vec3d(
+        ex.xMin() - hf->getXInterval()*(double)border,
+        ex.yMin() - hf->getYInterval()*(double)border,
+        0.0 ) );
 
     const VerticalDatum* vdatum = ex.isValid() ? ex.getSRS()->getVerticalDatum() : 0L;
 
@@ -444,12 +534,15 @@ HeightFieldUtils::createReferenceHeightField(const GeoExtent& ex,
         double lonInterval = geodeticExtent.width() / (double)(numCols-1);
         double latInterval = geodeticExtent.height() / (double)(numRows-1);
 
-        for( unsigned r=0; r<numRows; ++r )
+        double latStart = latMin - latInterval*(double)border;
+        double lonStart = lonMin - lonInterval*(double)border;
+
+        for( unsigned r=0; r<hf->getNumRows(); ++r )
         {            
-            double lat = latMin + latInterval*(double)r;
-            for( unsigned c=0; c<numCols; ++c )
+            double lat = latStart + latInterval*(double)r;
+            for( unsigned c=0; c<hf->getNumColumns(); ++c )
             {
-                double lon = lonMin + lonInterval*(double)c;
+                double lon = lonStart + lonInterval*(double)c;
                 double offset = vdatum->msl2hae(lat, lon, 0.0);
                 hf->setHeight( c, r, offset );
             }
@@ -457,11 +550,12 @@ HeightFieldUtils::createReferenceHeightField(const GeoExtent& ex,
     }
     else
     {
-        hf->getFloatArray()->assign(numCols*numRows, 0.0f);
+        hf->getFloatArray()->assign(hf->getNumColumns()*hf->getNumRows(), 0.0f);
     }
 
-    hf->setBorderWidth( 0 );
-    return hf;    
+    hf->setBorderWidth( border );
+
+    return hf;
 }
 
 void
@@ -594,7 +688,7 @@ HeightFieldUtils::createClusterCullingCallback(const osg::HeightField*    grid,
 }
 
 
-osg::Image*
+NormalMap*
 HeightFieldUtils::convertToNormalMap(const HeightFieldNeighborhood& hood,
                                      const SpatialReference*        hoodSRS)
 {
@@ -602,8 +696,7 @@ HeightFieldUtils::convertToNormalMap(const HeightFieldNeighborhood& hood,
     if ( !hf )
         return 0L;
     
-    osg::Image* image = new osg::Image();
-    image->allocateImage(hf->getNumColumns(), hf->getNumRows(), 1, GL_RGBA, GL_UNSIGNED_BYTE);
+    NormalMap* normalMap = new NormalMap(hf->getNumColumns(), hf->getNumRows());
 
     double xcells = (double)(hf->getNumColumns()-1);
     double ycells = (double)(hf->getNumRows()-1);
@@ -616,8 +709,6 @@ HeightFieldUtils::convertToNormalMap(const HeightFieldNeighborhood& hood,
         hoodSRS->isGeographic() ? hf->getYInterval() * mPerDegAtEquator :
         hf->getYInterval();
 
-    ImageUtils::PixelWriter write(image);
-    
     for(int t=0; t<(int)hf->getNumRows(); ++t)
     {
         // east-west interval in meters (changes for each row):
@@ -639,35 +730,105 @@ HeightFieldUtils::convertToNormalMap(const HeightFieldNeighborhood& hood,
             osg::Vec3f north( 0,  tIntervalMeters, centerHeight );
 
             if ( !HeightFieldUtils::getHeightAtNormalizedLocation(hood, nx-xres, ny, west.z()) )
-                west.x() = 0.0;
+                west.x() = 0.0, 
+                west.z() = centerHeight;
 
             if ( !HeightFieldUtils::getHeightAtNormalizedLocation(hood, nx+xres, ny, east.z()) )
-                east.x() = 0.0;
+                east.x() = 0.0, 
+                east.z() = centerHeight;
 
             if ( !HeightFieldUtils::getHeightAtNormalizedLocation(hood, nx, ny-yres, south.z()) )
-                south.y() = 0.0;
+                south.y() = 0.0, 
+                south.z() = centerHeight;
 
             if ( !HeightFieldUtils::getHeightAtNormalizedLocation(hood, nx, ny+yres, north.z()) )
-                north.y() = 0.0;
+                north.y() = 0.0, 
+                north.z() = centerHeight;
+
+            // account for degenerate vectors
+            if (east.x() == 0.0 && west.x() == 0.0)
+                east.x() = sIntervalMeters;
+
+            if (north.y() == 0.0 && south.y() == 0.0)
+                north.y() = tIntervalMeters;
+
+            osg::Vec3f n = (east - west) ^ (north - south);
+            n.normalize();
+
+            // calculate and encode curvature (2nd derivative of elevation)
+            //float L2inv = 1.0f/(sIntervalMeters*sIntervalMeters);
+            float D = (0.5*(west.z()+east.z()) - centerHeight) / (sIntervalMeters*sIntervalMeters); //* L2inv;
+            float E = (0.5*(south.z()+north.z()) - centerHeight) / (tIntervalMeters*tIntervalMeters); //* L2inv;
+            float curvature = osg::clampBetween(-2.0f*(D+E)*100.0f, -1.0f, 1.0f);
+
+            //// encode for RGBA [0..1]
+            //osg::Vec4f enc( n.x(), n.y(), n.z(), curvature );
+            //enc = (enc + osg::Vec4f(1.0,1.0,1.0,1.0))*0.5;
+
+            //write(enc, s, t);
+
+            normalMap->set(s, t, n, curvature);
+        }
+    }
+
+    return normalMap;
+}
+
+//TODO: get rid of this. -gw
+void
+HeightFieldUtils::createNormalMap(const osg::Image* elevation,
+                                  NormalMap* normalMap,
+                                  const GeoExtent& extent)
+{   
+    ImageUtils::PixelReader readElevation(elevation);
+    //ImageUtils::PixelWriter writeNormal(normalMap);
+
+    int sMax = (int)elevation->s()-1;
+    int tMax = (int)elevation->t()-1;
+        
+    // north-south interval in meters:
+    double xInterval = extent.width() / (double)(sMax);
+    double yInterval = extent.height() / (double)(tMax);
+
+    const SpatialReference* srs = extent.getSRS();
+    double mPerDegAtEquator = (srs->getEllipsoid()->getRadiusEquator() * 2.0 * osg::PI) / 360.0;
+    double dy = srs->isGeographic() ? yInterval * mPerDegAtEquator : yInterval;
+
+    for (int t = 0; t<(int)elevation->t(); ++t)
+    {
+        double lat = extent.yMin() + yInterval*(double)t;
+        double dx = srs->isGeographic() ? xInterval * mPerDegAtEquator * cos(osg::DegreesToRadians(lat)) : xInterval;
+
+        for(int s=0; s<(int)elevation->s(); ++s)
+        {
+            float h = readElevation(s, t).r();
+
+            osg::Vec3f west ( s > 0 ? -dx : 0, 0, h );
+            osg::Vec3f east ( s < sMax ?  dx : 0, 0, h );
+            osg::Vec3f south( 0, t > 0 ? -dy : 0, h );
+            osg::Vec3f north( 0, t < tMax ? dy : 0, h );
+
+            west.z() = readElevation(std::max(0, s - 1), t).r();
+            east.z() = readElevation(std::min(sMax, s + 1), t).r();
+            south.z() = readElevation(s, std::max(0, t - 1)).r();
+            north.z() = readElevation(s, std::min(tMax, t + 1)).r();
 
             osg::Vec3f n = (east-west) ^ (north-south);
             n.normalize();
 
             // calculate and encode curvature (2nd derivative of elevation)
-            float L2inv = 1.0f/(sIntervalMeters*sIntervalMeters);
-            float D = (0.5*(west.z()+east.z()) - centerHeight) * L2inv;
-            float E = (0.5*(south.z()+north.z()) - centerHeight) * L2inv;
+            float D = (0.5*(west.z()+east.z()) - h) / (dx*dx);
+            float E = (0.5*(south.z()+north.z()) - h) / (dy*dy);
             float curvature = osg::clampBetween(-2.0f*(D+E)*100.0f, -1.0f, 1.0f);
 
             // encode for RGBA [0..1]
-            osg::Vec4f enc( n.x(), n.y(), n.z(), curvature );
-            enc = (enc + osg::Vec4f(1.0,1.0,1.0,1.0))*0.5;
+            //osg::Vec4f NC( n.x(), n.y(), n.z(), curvature );
+            //NC = (NC + osg::Vec4f(1.0,1.0,1.0,1.0))*0.5;
 
-            write(enc, s, t);
+            normalMap->set(s, t, n, curvature);
+            //writeNormal(NC, s, t);
         }
     }
-
-    return image;
 }
 
 /******************************************************************************************/
diff --git a/src/osgEarth/Horizon.cpp b/src/osgEarth/Horizon.cpp
index 0c7dd45..97888bd 100644
--- a/src/osgEarth/Horizon.cpp
+++ b/src/osgEarth/Horizon.cpp
@@ -26,7 +26,10 @@
 
 #define LC "[Horizon] "
 
-#define OSGEARTH_HORIZON_UDC_NAME "osgEarth.Horizon"
+namespace
+{
+    const std::string OSGEARTH_HORIZON_UDC_NAME = "osgEarth.Horizon";
+}
 
 using namespace osgEarth;
 
@@ -395,10 +398,8 @@ HorizonCullCallback::operator()(osg::Node* node, osg::NodeVisitor* nv)
             if ( _proxy.lock(proxy) )
             {
                 if ( isVisible(proxy.get(), nv) )
-                {
                     traverse(node, nv);
-                    return;
-                }
+                return;
             }
         }
 
diff --git a/src/osgEarth/IOTypes b/src/osgEarth/IOTypes
index 82b94f5..83e98fd 100644
--- a/src/osgEarth/IOTypes
+++ b/src/osgEarth/IOTypes
@@ -102,10 +102,18 @@ namespace osgEarth
         ReadResult( osg::Object* result )
             : _code(RESULT_OK), _result(result), _fromCache(false), _lmt(0), _duration_s(0.0) { }
 
+        template<typename T>
+        ReadResult( const osg::ref_ptr<T>& result )
+            : _code(RESULT_OK), _result(result), _fromCache(false), _lmt(0), _duration_s(0.0) { }
+
         /** Construct a successful result with metadata */
         ReadResult( osg::Object* result, const Config& meta )
             : _code(RESULT_OK), _result(result), _meta(meta), _fromCache(false), _lmt(0), _duration_s(0.0) { }
 
+        template<typename T>
+        ReadResult( const osg::ref_ptr<T>& result, const Config& meta )
+            : _code(RESULT_OK), _result(result), _meta(meta), _fromCache(false), _lmt(0), _duration_s(0.0) { }
+
         /** Copy construct */
         ReadResult( const ReadResult& rhs )
             : _code(rhs._code), _result(rhs._result.get()), _meta(rhs._meta), _fromCache(rhs._fromCache), _lmt(rhs._lmt), _duration_s(rhs._duration_s) { }
diff --git a/src/osgEarth/ImageLayer b/src/osgEarth/ImageLayer
index 6262c8c..0839c41 100644
--- a/src/osgEarth/ImageLayer
+++ b/src/osgEarth/ImageLayer
@@ -37,27 +37,24 @@ namespace osgEarth
     class OSGEARTH_EXPORT ImageLayerOptions : public TerrainLayerOptions
     {
     public:
-        /** Constructs new image layer options. */
-        ImageLayerOptions( const ConfigOptions& options =ConfigOptions() );
+        // Construct empty options
+        ImageLayerOptions();
 
-        /**
-         * Constructs new image layer options, giving a name to the layer and specifying
-         * driver options to use.
-         */         
-        ImageLayerOptions( const std::string& name, const TileSourceOptions& driverOpt =TileSourceOptions() );
+        // Deserialize options from a config structure
+        ImageLayerOptions(const ConfigOptions& options);
+
+        // Construct options with a default name
+        ImageLayerOptions(const std::string& name);
+
+        // Consruct options with a name and a driver configuration
+        ImageLayerOptions(const std::string& name, const TileSourceOptions& tileSourceOptions);
 
         /** dtor */
         virtual ~ImageLayerOptions() { }
 
 
     public: // properties
-
-        /**
-         * The initial opacity of this layer
-         */
-        optional<float>& opacity() { return _opacity; }
-        const optional<float>& opacity() const { return _opacity; }
-
+        
         /**
          * The initial minimum camera range at which this layer is visible.
          */
@@ -141,15 +138,14 @@ namespace osgEarth
 
     public:
 
-        virtual Config getConfig() const { return getConfig(false); }
-        virtual Config getConfig( bool isolate ) const;
+        virtual Config getConfig() const;
         virtual void mergeConfig( const Config& conf );
         
     private:
         void fromConfig( const Config& conf );
         void setDefaults();
 
-        optional<float>       _opacity;
+        //optional<float>       _opacity;
         optional<float>       _minRange;
         optional<float>       _maxRange;
         optional<osg::Vec4ub> _transparentColor;
@@ -168,23 +164,6 @@ namespace osgEarth
     //--------------------------------------------------------------------
 
     /**
-     * Callback for receiving notification of property changes on an ImageLayer.
-     */
-    struct ImageLayerCallback : public TerrainLayerCallback
-    {
-        virtual void onOpacityChanged( class ImageLayer* layer ) { }
-        virtual void onVisibleRangeChanged( class ImageLayer* layer ) {}
-        virtual void onColorFiltersChanged( class ImageLayer* layer ) { }
-        virtual ~ImageLayerCallback() { }
-    };
-
-    typedef void (ImageLayerCallback::*ImageLayerCallbackMethodPtr)(ImageLayer* layer);
-
-    typedef std::list< osg::ref_ptr<ImageLayerCallback> > ImageLayerCallbackList;
-
-    //--------------------------------------------------------------------
-
-    /**
      * Internal utility class for post-processing image tiles that come from 
      * a TileSource
      */
@@ -207,7 +186,16 @@ namespace osgEarth
         bool                               _layerInTargetProfile;
     };
 
-    //--------------------------------------------------------------------
+
+    struct ImageLayerCallback : public TerrainLayerCallback
+    {
+        //virtual void onOpacityChanged(class ImageLayer* layer) { }
+        virtual void onVisibleRangeChanged(class ImageLayer* layer) { }
+        virtual void onColorFiltersChanged(class ImageLayer* layer) { }
+
+        typedef void (ImageLayerCallback::*MethodPtr)(class ImageLayer* layer);
+    };
+
 
     /**
      * A map terrain layer containing bitmap image data.
@@ -215,36 +203,34 @@ namespace osgEarth
     class OSGEARTH_EXPORT ImageLayer : public TerrainLayer
     {
     public:
+        META_Layer(osgEarth, ImageLayer, ImageLayerOptions);
+
+        /**
+         * Construct a new ImageLayer. Use the options() to configure it
+         * before adding it to a Map.
+         */
+        ImageLayer();
+
         /**
          * Constructs a new image layer.
          */
-        ImageLayer( const ImageLayerOptions& options );
+        ImageLayer(const ImageLayerOptions& options);
 
         /**
          * Constructs a new image layer with the given name and driver options.
          */
-        ImageLayer( const std::string& name, const TileSourceOptions& driverOptions );
+        ImageLayer(const std::string& name, const TileSourceOptions& tileSourceOptions);
 
         /**
          * Constructs a new image layer with a custom TileSource.
+         *
+         * Note: the ImageLayerOptions contains a driver() member for configuring a 
+         * TileSource. But in this constructor, you are passing in an existing TileSource,
+         * and thus the driver() member in ImageLayerOptions will not be used.
          */
-        ImageLayer( const ImageLayerOptions& options, TileSource* tileSource );
-
-        /** dtor */
-        virtual ~ImageLayer() { }
+        ImageLayer(const ImageLayerOptions& options, TileSource* tileSource);
 
     public:
-        /**
-         * Access to the initialization options used to create this image layer
-         */
-        const ImageLayerOptions& getImageLayerOptions() const { return _runtimeOptions; }
-        virtual const TerrainLayerOptions& getTerrainLayerRuntimeOptions() const { return _runtimeOptions; }
-
-        /** Adds a property notification callback to this layer */
-        void addCallback( ImageLayerCallback* cb );
-
-        /** Removes a property notification callback from this layer */
-        void removeCallback( ImageLayerCallback* cb );
 
         /** Override: see TerrainLayer */
         virtual void setTargetProfileHint( const Profile* profile );
@@ -264,33 +250,30 @@ namespace osgEarth
          */
         const ColorFilterChain& getColorFilters() const;
 
-
     public: // runtime properties
 
         /**
-         * Sets the opacity of this image layer.
-         * @param opacity Opacity [0..1] -> [transparent..opaque]
+         * Minimum camera range at which this image layer is visible 
          */
-        void setOpacity( float opacity );
-
-        float getOpacity() const { return *_runtimeOptions.opacity(); }
-
-        float getMinVisibleRange() const { return *_runtimeOptions.minVisibleRange();}
+        float getMinVisibleRange() const;
         void setMinVisibleRange( float minVisibleRange );
-
-        float getMaxVisibleRange() const { return *_runtimeOptions.maxVisibleRange();}
+        
+        /**
+         * Maximum camera range at which this image layer is visible 
+         */
+        float getMaxVisibleRange() const;
         void setMaxVisibleRange( float maxVisibleRange );
 
         /**
          * Whether this layer is marked for render sharing
          */
-        bool isShared() const { return *_runtimeOptions.shared() == true; }
+        bool isShared() const;
 
         /**
          * Whether this layer represents coverage data that should not be subject
          * to color-space filtering, interpolation, or compression.
          */
-        bool isCoverage() const { return *_runtimeOptions.coverage() == true; }
+        bool isCoverage() const;
 
         /**
          * When isShared() == true, the engine will call this function to bind the
@@ -312,16 +295,19 @@ namespace osgEarth
          */
         optional<std::string>& shareTexMatUniformName() { return _shareTexMatUniformName; }
         const optional<std::string>& shareTexMatUniformName() const { return _shareTexMatUniformName; }
-
+        
 
     public: // methods
 
+        virtual bool createTextureSupported() const { return false; }
+        virtual osg::Texture* createTexture(const TileKey& key, ProgressCallback* progress, osg::Matrixf& textureMatrix) { return 0L; }
+
         /**
          * Creates a GeoImage from this layer corresponding to the provided key. The
          * image is in the profile of the key and will be reprojected, mosaiced and
          * cropped automatically.
          */
-        virtual GeoImage createImage( const TileKey& key, ProgressCallback* progress = 0);
+        GeoImage createImage( const TileKey& key, ProgressCallback* progress = 0);
 
         /**
          * Creates an image that is in the image layer's native profile.
@@ -332,13 +318,31 @@ namespace osgEarth
          * Applies the texture compression options to a texture.
          */
         void applyTextureCompressionMode(osg::Texture* texture) const;
+       
+        typedef ImageLayerCallback Callback;
 
-    public: // TerrainLayer override
+    public: // Layer
+        
+        virtual const Status& open();
 
-        //CacheBin* getCacheBin( const Profile* profile );
+        //! Subclass can override this when not using a TileSource
+        //! by calling setTileSourceExpected(false).
+        virtual GeoImage createImageImplementation(const TileKey&, ProgressCallback* progress);
+
+    protected: // Layer
+            
+        virtual void init();
 
     protected:
 
+        /** dtor */
+        virtual ~ImageLayer() { }
+
+        // Constructor for a subclass that owns the layer options structure
+        ImageLayer(ImageLayerOptions* optionsPtr);
+
+    private:
+
         // Creates an image that's in the same profile as the provided key.
         GeoImage createImageInKeyProfile(const TileKey& key, ProgressCallback* progress);
 
@@ -349,23 +353,16 @@ namespace osgEarth
         // Fetches multiple images from the TileSource; mosaics/reprojects/crops as necessary, and
         // returns a single tile. This is called by createImageFromTileSource() if the key profile
         // doesn't match the layer profile.
-        GeoImage assembleImageFromTileSource(const TileKey& key, ProgressCallback* progress);
+        GeoImage assembleImage(const TileKey& key, ProgressCallback* progress);
 
-
-    protected:
-        ImageLayerOptions                        _runtimeOptions;
         osg::ref_ptr<TileSource::ImageOperation> _preCacheOp;
         Threading::Mutex                         _mutex;
         osg::ref_ptr<osg::Image>                 _emptyImage;
-        ImageLayerCallbackList                   _callbacks;
         optional<int>                            _shareImageUnit;
         optional<std::string>                    _shareTexUniformName;
         optional<std::string>                    _shareTexMatUniformName;
 
-        virtual void fireCallback( TerrainLayerCallbackMethodPtr method );
-        virtual void fireCallback( ImageLayerCallbackMethodPtr method );
-
-        void init();
+        virtual void fireCallback(ImageLayerCallback::MethodPtr method);
 
         TileSource::ImageOperation* getOrCreatePreCacheOp();
     };
diff --git a/src/osgEarth/ImageLayer.cpp b/src/osgEarth/ImageLayer.cpp
index 6c2a6a2..ee01bcd 100644
--- a/src/osgEarth/ImageLayer.cpp
+++ b/src/osgEarth/ImageLayer.cpp
@@ -28,6 +28,7 @@
 #include <osgEarth/MemCache>
 #include <osgEarth/Registry>
 #include <osgEarth/Capabilities>
+#include <osgEarth/Metrics>
 #include <osg/Version>
 #include <osgDB/WriteFile>
 #include <memory.h>
@@ -42,16 +43,34 @@ using namespace OpenThreads;
 //#undef  OE_DEBUG
 //#define OE_DEBUG OE_INFO
 
+namespace osgEarth {
+    REGISTER_OSGEARTH_LAYER(image, ImageLayer);
+}
+
 //------------------------------------------------------------------------
 
-ImageLayerOptions::ImageLayerOptions( const ConfigOptions& options ) :
+ImageLayerOptions::ImageLayerOptions() :
+TerrainLayerOptions()
+{
+    setDefaults();
+    fromConfig(_conf);
+}
+
+ImageLayerOptions::ImageLayerOptions(const ConfigOptions& options) :
 TerrainLayerOptions(options)
 {
     setDefaults();
     fromConfig( _conf );
 }
 
-ImageLayerOptions::ImageLayerOptions( const std::string& name, const TileSourceOptions& driverOpt ) :
+ImageLayerOptions::ImageLayerOptions(const std::string& name) :
+TerrainLayerOptions(name)
+{
+    setDefaults();
+    fromConfig( _conf );
+}
+
+ImageLayerOptions::ImageLayerOptions(const std::string& name, const TileSourceOptions& driverOpt) :
 TerrainLayerOptions(name, driverOpt)
 {
     setDefaults();
@@ -61,7 +80,6 @@ TerrainLayerOptions(name, driverOpt)
 void
 ImageLayerOptions::setDefaults()
 {
-    _opacity.init( 1.0f );
     _transparentColor.init( osg::Vec4ub(0,0,0,0) );
     _minRange.init( 0.0 );
     _maxRange.init( FLT_MAX );
@@ -81,10 +99,9 @@ ImageLayerOptions::mergeConfig( const Config& conf )
 }
 
 void
-ImageLayerOptions::fromConfig( const Config& conf )
+ImageLayerOptions::fromConfig(const Config& conf)
 {
     conf.getIfSet( "nodata_image",   _noDataImageFilename );
-    conf.getIfSet( "opacity",        _opacity );
     conf.getIfSet( "min_range",      _minRange );
     conf.getIfSet( "max_range",      _maxRange );
     conf.getIfSet( "shared",         _shared );
@@ -124,20 +141,20 @@ ImageLayerOptions::fromConfig( const Config& conf )
 }
 
 Config
-ImageLayerOptions::getConfig( bool isolate ) const
+ImageLayerOptions::getConfig() const
 {
-    Config conf = TerrainLayerOptions::getConfig( isolate );
+    Config conf = TerrainLayerOptions::getConfig();
+    conf.key() = "image";
 
-    conf.updateIfSet( "nodata_image",   _noDataImageFilename );
-    conf.updateIfSet( "opacity",        _opacity );
-    conf.updateIfSet( "min_range",      _minRange );
-    conf.updateIfSet( "max_range",      _maxRange );
-    conf.updateIfSet( "shared",         _shared );
-    conf.updateIfSet( "coverage",       _coverage );
-    conf.updateIfSet( "feather_pixels", _featherPixels );
+    conf.set( "nodata_image",   _noDataImageFilename );
+    conf.set( "min_range",      _minRange );
+    conf.set( "max_range",      _maxRange );
+    conf.set( "shared",         _shared );
+    conf.set( "coverage",       _coverage );
+    conf.set( "feather_pixels", _featherPixels );
 
     if (_transparentColor.isSet())
-        conf.update("transparent_color", colorToString( _transparentColor.value()));
+        conf.set("transparent_color", colorToString( _transparentColor.value()));
 
     if ( _colorFilters.size() > 0 )
     {
@@ -145,32 +162,34 @@ ImageLayerOptions::getConfig( bool isolate ) const
         if ( ColorFilterRegistry::instance()->writeChain( _colorFilters, filtersConf ) )
         {
             conf.update( filtersConf );
-            //conf.add( filtersConf );
         }
     }
 
-    conf.updateIfSet("mag_filter","LINEAR",                _magFilter,osg::Texture::LINEAR);
-    conf.updateIfSet("mag_filter","LINEAR_MIPMAP_LINEAR",  _magFilter,osg::Texture::LINEAR_MIPMAP_LINEAR);
-    conf.updateIfSet("mag_filter","LINEAR_MIPMAP_NEAREST", _magFilter,osg::Texture::LINEAR_MIPMAP_NEAREST);
-    conf.updateIfSet("mag_filter","NEAREST",               _magFilter,osg::Texture::NEAREST);
-    conf.updateIfSet("mag_filter","NEAREST_MIPMAP_LINEAR", _magFilter,osg::Texture::NEAREST_MIPMAP_LINEAR);
-    conf.updateIfSet("mag_filter","NEAREST_MIPMAP_NEAREST",_magFilter,osg::Texture::NEAREST_MIPMAP_NEAREST);
-    conf.updateIfSet("min_filter","LINEAR",                _minFilter,osg::Texture::LINEAR);
-    conf.updateIfSet("min_filter","LINEAR_MIPMAP_LINEAR",  _minFilter,osg::Texture::LINEAR_MIPMAP_LINEAR);
-    conf.updateIfSet("min_filter","LINEAR_MIPMAP_NEAREST", _minFilter,osg::Texture::LINEAR_MIPMAP_NEAREST);
-    conf.updateIfSet("min_filter","NEAREST",               _minFilter,osg::Texture::NEAREST);
-    conf.updateIfSet("min_filter","NEAREST_MIPMAP_LINEAR", _minFilter,osg::Texture::NEAREST_MIPMAP_LINEAR);
-    conf.updateIfSet("min_filter","NEAREST_MIPMAP_NEAREST",_minFilter,osg::Texture::NEAREST_MIPMAP_NEAREST);
-
-    conf.updateIfSet("texture_compression", "none", _texcomp, osg::Texture::USE_IMAGE_DATA_FORMAT);
-    conf.updateIfSet("texture_compression", "auto", _texcomp, (osg::Texture::InternalFormatMode)~0);
-    conf.updateIfSet("texture_compression", "on",   _texcomp, (osg::Texture::InternalFormatMode)~0);
-    conf.updateIfSet("texture_compression", "fastdxt", _texcomp, (osg::Texture::InternalFormatMode)(~0 - 1));
+    conf.set("mag_filter","LINEAR",                _magFilter,osg::Texture::LINEAR);
+    conf.set("mag_filter","LINEAR_MIPMAP_LINEAR",  _magFilter,osg::Texture::LINEAR_MIPMAP_LINEAR);
+    conf.set("mag_filter","LINEAR_MIPMAP_NEAREST", _magFilter,osg::Texture::LINEAR_MIPMAP_NEAREST);
+    conf.set("mag_filter","NEAREST",               _magFilter,osg::Texture::NEAREST);
+    conf.set("mag_filter","NEAREST_MIPMAP_LINEAR", _magFilter,osg::Texture::NEAREST_MIPMAP_LINEAR);
+    conf.set("mag_filter","NEAREST_MIPMAP_NEAREST",_magFilter,osg::Texture::NEAREST_MIPMAP_NEAREST);
+    conf.set("min_filter","LINEAR",                _minFilter,osg::Texture::LINEAR);
+    conf.set("min_filter","LINEAR_MIPMAP_LINEAR",  _minFilter,osg::Texture::LINEAR_MIPMAP_LINEAR);
+    conf.set("min_filter","LINEAR_MIPMAP_NEAREST", _minFilter,osg::Texture::LINEAR_MIPMAP_NEAREST);
+    conf.set("min_filter","NEAREST",               _minFilter,osg::Texture::NEAREST);
+    conf.set("min_filter","NEAREST_MIPMAP_LINEAR", _minFilter,osg::Texture::NEAREST_MIPMAP_LINEAR);
+    conf.set("min_filter","NEAREST_MIPMAP_NEAREST",_minFilter,osg::Texture::NEAREST_MIPMAP_NEAREST);
+
+    conf.set("texture_compression", "none", _texcomp, osg::Texture::USE_IMAGE_DATA_FORMAT);
+    conf.set("texture_compression", "auto", _texcomp, (osg::Texture::InternalFormatMode)~0);
+    conf.set("texture_compression", "on",   _texcomp, (osg::Texture::InternalFormatMode)~0);
+    conf.set("texture_compression", "fastdxt", _texcomp, (osg::Texture::InternalFormatMode)(~0 - 1));
     //TODO add all the enums
 
     // uniform names
-    conf.updateIfSet("shared_sampler", _shareTexUniformName);
-    conf.updateIfSet("shared_matrix",  _shareTexMatUniformName);
+    conf.set("shared_sampler", _shareTexUniformName);
+    conf.set("shared_matrix",  _shareTexMatUniformName);
+
+    //if (driver().isSet())
+    //    conf.set("driver", driver()->getDriver());
 
     return conf;
 }
@@ -276,117 +295,138 @@ ImageLayerTileProcessor::process( osg::ref_ptr<osg::Image>& image ) const
 
 //------------------------------------------------------------------------
 
-ImageLayer::ImageLayer( const ImageLayerOptions& options ) :
-TerrainLayer( options, &_runtimeOptions ),
-_runtimeOptions( options )
+ImageLayer::ImageLayer() :
+TerrainLayer(&_optionsConcrete),
+_options(&_optionsConcrete)
 {
     init();
 }
 
-ImageLayer::ImageLayer( const std::string& name, const TileSourceOptions& driverOptions ) :
-TerrainLayer   ( ImageLayerOptions(name, driverOptions), &_runtimeOptions ),
-_runtimeOptions( ImageLayerOptions(name, driverOptions) )
+ImageLayer::ImageLayer(const ImageLayerOptions& options) :
+TerrainLayer(&_optionsConcrete),
+_options(&_optionsConcrete),
+_optionsConcrete(options)
 {
     init();
 }
 
-ImageLayer::ImageLayer( const ImageLayerOptions& options, TileSource* tileSource ) :
-TerrainLayer   ( options, &_runtimeOptions, tileSource ),
-_runtimeOptions( options )
+ImageLayer::ImageLayer(const std::string& name, const TileSourceOptions& tileSourceOptions) :
+TerrainLayer(&_optionsConcrete),
+_options(&_optionsConcrete),
+_optionsConcrete(name, tileSourceOptions)
 {
     init();
 }
 
-void
-ImageLayer::init()
+ImageLayer::ImageLayer(const ImageLayerOptions& options, TileSource* tileSource) :
+TerrainLayer(&_optionsConcrete, tileSource),
+_options(&_optionsConcrete),
+_optionsConcrete(options)
 {
-    TerrainLayer::init();
+    init();
+}
 
-    // Set the tile size to 256 if it's not explicitly set.
-    if (!_runtimeOptions.driver()->tileSize().isSet())
-    {
-        _runtimeOptions.driver()->tileSize().init( 256 );
-    }
+ImageLayer::ImageLayer(ImageLayerOptions* optionsPtr) :
+TerrainLayer(optionsPtr? optionsPtr : &_optionsConcrete),
+_options(optionsPtr? optionsPtr : &_optionsConcrete)
+{
+    //init(); // will be called by subclass.
+}
 
-    _emptyImage = ImageUtils::createEmptyImage();
+const Status&
+ImageLayer::open()
+{
+    if (!_emptyImage.valid())
+        _emptyImage = ImageUtils::createEmptyImage();
 
-    if ( _runtimeOptions.shareTexUniformName().isSet() )
-        _shareTexUniformName = _runtimeOptions.shareTexUniformName().get();
+    if ( options().shareTexUniformName().isSet() )
+        _shareTexUniformName = options().shareTexUniformName().get();
     else
         _shareTexUniformName.init( Stringify() << "layer_" << getUID() << "_tex" );
 
-    if ( _runtimeOptions.shareTexMatUniformName().isSet() )
-        _shareTexMatUniformName = _runtimeOptions.shareTexMatUniformName().get();
+    if ( options().shareTexMatUniformName().isSet() )
+        _shareTexMatUniformName = options().shareTexMatUniformName().get();
     else
-        _shareTexMatUniformName.init( Stringify()  << "layer_" << getUID() << "_texMatrix" );
-}
+        _shareTexMatUniformName.init(Stringify() << _shareTexUniformName.get() << "_matrix");
 
-void
-ImageLayer::addCallback( ImageLayerCallback* cb )
-{
-    _callbacks.push_back( cb );
+    // If we are using createTexture to make image tiles,
+    // we don't need to load a tile source plugin.
+    if (createTextureSupported())
+    {
+        setTileSourceExpected(false);
+    }
+
+    return TerrainLayer::open();
 }
 
 void
-ImageLayer::removeCallback( ImageLayerCallback* cb )
+ImageLayer::init()
 {
-    ImageLayerCallbackList::iterator i = std::find( _callbacks.begin(), _callbacks.end(), cb );
-    if ( i != _callbacks.end() ) 
-        _callbacks.erase( i );
+    TerrainLayer::init();
+
+    // image layers render as a terrain texture.
+    setRenderType(RENDERTYPE_TILE);
 }
 
 void
-ImageLayer::fireCallback( TerrainLayerCallbackMethodPtr method )
+ImageLayer::fireCallback(ImageLayerCallback::MethodPtr method)
 {
-    for( ImageLayerCallbackList::const_iterator i = _callbacks.begin(); i != _callbacks.end(); ++i )
+    for(CallbackVector::const_iterator i = _callbacks.begin(); i != _callbacks.end(); ++i)
     {
-        ImageLayerCallback* cb = i->get();
-        (cb->*method)( this );
+        ImageLayerCallback* cb = dynamic_cast<ImageLayerCallback*>(i->get());
+        if (cb) (cb->*method)( this );
     }
 }
 
 void
-ImageLayer::fireCallback( ImageLayerCallbackMethodPtr method )
+ImageLayer::setMinVisibleRange( float minVisibleRange )
 {
-    for( ImageLayerCallbackList::const_iterator i = _callbacks.begin(); i != _callbacks.end(); ++i )
-    {
-        ImageLayerCallback* cb = i->get();
-        (cb->*method)( this );
-    }
+    options().minVisibleRange() = minVisibleRange;
+    fireCallback( &ImageLayerCallback::onVisibleRangeChanged );
 }
 
-void
-ImageLayer::setOpacity( float value ) 
+float
+ImageLayer::getMinVisibleRange() const
 {
-    _runtimeOptions.opacity() = osg::clampBetween( value, 0.0f, 1.0f );
-    fireCallback( &ImageLayerCallback::onOpacityChanged );
+    return options().minVisibleRange().get();
 }
 
 void
-ImageLayer::setMinVisibleRange( float minVisibleRange )
+ImageLayer::setMaxVisibleRange( float maxVisibleRange )
 {
-    _runtimeOptions.minVisibleRange() = minVisibleRange;
+    options().maxVisibleRange() = maxVisibleRange;
     fireCallback( &ImageLayerCallback::onVisibleRangeChanged );
 }
 
-void
-ImageLayer::setMaxVisibleRange( float maxVisibleRange )
+float
+ImageLayer::getMaxVisibleRange() const
 {
-    _runtimeOptions.maxVisibleRange() = maxVisibleRange;
-    fireCallback( &ImageLayerCallback::onVisibleRangeChanged );
+    return options().maxVisibleRange().get();
+}
+
+bool
+ImageLayer::isShared() const
+{
+    return options().shared().get();
+}
+
+bool
+ImageLayer::isCoverage() const
+{
+    return options().coverage().get();
 }
 
 void
 ImageLayer::addColorFilter( ColorFilter* filter )
 {
-    _runtimeOptions.colorFilters().push_back( filter );
+    options().colorFilters().push_back( filter );
     fireCallback( &ImageLayerCallback::onColorFiltersChanged );
 }
 
 void
 ImageLayer::removeColorFilter( ColorFilter* filter )
 {
-    ColorFilterChain& filters = _runtimeOptions.colorFilters();
+    ColorFilterChain& filters = options().colorFilters();
     ColorFilterChain::iterator i = std::find(filters.begin(), filters.end(), filter);
     if ( i != filters.end() )
     {
@@ -398,7 +438,7 @@ ImageLayer::removeColorFilter( ColorFilter* filter )
 const ColorFilterChain&
 ImageLayer::getColorFilters() const
 {
-    return _runtimeOptions.colorFilters();
+    return options().colorFilters();
 }
 
 void
@@ -424,7 +464,7 @@ ImageLayer::getOrCreatePreCacheOp()
                 _targetProfileHint->isEquivalentTo( getProfile() );
 
             ImageLayerPreCacheOperation* op = new ImageLayerPreCacheOperation();
-            op->_processor.init( _runtimeOptions, _readOptions.get(), layerInTargetProfile );
+            op->_processor.init( options(), _readOptions.get(), layerInTargetProfile );
 
             _preCacheOp = op;
         }
@@ -432,21 +472,14 @@ ImageLayer::getOrCreatePreCacheOp()
     return _preCacheOp.get();
 }
 
-
-//CacheBin*
-//ImageLayer::getCacheBin( const Profile* profile)
-//{
-//    // specialize ImageLayer to only consider the horizontal signature (ignore vertical
-//    // datum component for images)
-//    std::string binId = *_runtimeOptions.cacheId() + "_" + profile->getHorizSignature();
-//    return TerrainLayer::getCacheBin( profile, binId );
-//}
-
-
 GeoImage
 ImageLayer::createImage(const TileKey&    key,
                         ProgressCallback* progress)
 {
+    ScopedMetric m("ImageLayer::createImage", 2,
+                    "key", key.str().c_str(),
+                    "name", getName().c_str());
+
     if (getStatus().isError())
     {
         return GeoImage::INVALID;
@@ -455,6 +488,17 @@ ImageLayer::createImage(const TileKey&    key,
     return createImageInKeyProfile( key, progress );
 }
 
+GeoImage
+ImageLayer::createImageImplementation(const TileKey& key, ProgressCallback* progress)
+{
+    // Check here in case a subclass calls this method directly.
+    //if ( !isKeyInLegalRange(key) )
+    //{
+    //    return GeoImage::INVALID;
+    //}
+
+    return createImageFromTileSource(key, progress);
+}
 
 GeoImage
 ImageLayer::createImageInNativeProfile(const TileKey&    key,
@@ -492,24 +536,21 @@ ImageLayer::createImageInNativeProfile(const TileKey&    key,
         ImageMosaic mosaic;
         for( std::vector<TileKey>::iterator k = nativeKeys.begin(); k != nativeKeys.end(); ++k )
         {
-            bool isFallback = false;
             GeoImage image = createImageInKeyProfile( *k, progress );
             if ( image.valid() )
             {
+                foundAtLeastOneRealTile = true;
                 mosaic.getImages().push_back( TileImage(image.getImage(), *k) );
             }
             else
             {
-                // if we get EVEN ONE invalid tile, we have to abort because there will be
-                // empty spots in the mosaic. (By "invalid" we mean a tile that could not
-                // even be resolved through the fallback procedure.)
-                return GeoImage::INVALID;
-                //TODO: probably need to change this so the mosaic uses alpha.
+                // We didn't get an image so pad the mosaic with a transparent image.
+                mosaic.getImages().push_back( TileImage(ImageUtils::createEmptyImage(getTileSize(), getTileSize()), *k));
             }
         }
 
         // bail out if we got nothing.
-        if ( mosaic.getImages().size() > 0 )
+        if ( foundAtLeastOneRealTile )
         {
             // assemble new GeoImage from the mosaic.
             double rxmin, rymin, rxmax, rymax;
@@ -529,11 +570,6 @@ GeoImage
 ImageLayer::createImageInKeyProfile(const TileKey&    key, 
                                     ProgressCallback* progress)
 {
-    if (getStatus().isError())
-    {
-        return GeoImage::INVALID;
-    }
-
     // If the layer is disabled, bail out.
     if ( !getEnabled() )
     {
@@ -541,7 +577,8 @@ ImageLayer::createImageInKeyProfile(const TileKey&    key,
     }
 
     // Make sure the request is in range.
-    if ( !isKeyInRange(key) )
+    // TODO: perhaps this should be a call to mayHaveData(key) instead.
+    if ( !isKeyInLegalRange(key) )
     {
         return GeoImage::INVALID;
     }
@@ -567,13 +604,18 @@ ImageLayer::createImageInKeyProfile(const TileKey&    key,
 
     // locate the cache bin for the target profile for this layer:
     CacheBin* cacheBin = getCacheBin( key.getProfile() );
+    
 
-    // validate that we have either a valid tile source, or we're cache-only.
-    if (getTileSource() || (cacheBin && policy.isCacheOnly()))
-    {
-        //nop = OK.
-    }
-    else
+    // Can we continue? Only if either:
+    //  a) there is a valid tile source plugin;
+    //  b) a tile source is not expected, meaning the subclass overrides getHeightField; or
+    //  c) we are in cache-only mode and there is a valid cache bin.
+    bool canContinue =
+        getTileSource() ||
+        !isTileSourceExpected() ||
+        (policy.isCacheOnly() && cacheBin != 0L);
+
+    if (!canContinue)
     {
         disable("Error: layer does not have a valid TileSource, cannot create image");
         return GeoImage::INVALID;
@@ -624,9 +666,16 @@ ImageLayer::createImageInKeyProfile(const TileKey&    key,
             return GeoImage::INVALID;
         }
     }
-
-    // Get an image from the underlying TileSource.
-    result = createImageFromTileSource( key, progress );
+    
+    if (key.getProfile()->isHorizEquivalentTo(getProfile()))
+    {
+        result = createImageImplementation(key, progress);
+    }
+    else
+    {
+        // If the profiles are different, use a compositing method to assemble the tile.
+        result = assembleImage( key, progress );
+    }
 
     // Normalize the image if necessary
     if ( result.valid() )
@@ -686,7 +735,7 @@ ImageLayer::createImageFromTileSource(const TileKey&    key,
     // If the profiles are different, use a compositing method to assemble the tile.
     if ( !key.getProfile()->isHorizEquivalentTo( getProfile() ) )
     {
-        return assembleImageFromTileSource( key, progress );
+        return assembleImage( key, progress );
     }
 
     // Good to go, ask the tile source for an image:
@@ -698,18 +747,25 @@ ImageLayer::createImageFromTileSource(const TileKey&    key,
         OE_DEBUG << LC << "createImageFromTileSource: blacklisted(" << key.str() << ")" << std::endl;
         return GeoImage::INVALID;
     }
-    
-    if ( !source->hasData( key ) )
+
+    if (!mayHaveData(key))
     {
-        OE_DEBUG << LC << "createImageFromTileSource: hasData(" << key.str() << ") == false" << std::endl;
+        OE_DEBUG << LC << "createImageFromTileSource: mayHaveData(" << key.str() << ") == false" << std::endl;
         return GeoImage::INVALID;
     }
 
+    //if ( !source->hasData( key ) )
+    //{
+    //    OE_DEBUG << LC << "createImageFromTileSource: hasData(" << key.str() << ") == false" << std::endl;
+    //    return GeoImage::INVALID;
+    //}
+
     // create an image from the tile source.
     osg::ref_ptr<osg::Image> result = source->createImage( key, op.get(), progress );   
 
     // Process images with full alpha to properly support MP blending.    
-    if ( result.valid() && *_runtimeOptions.featherPixels())
+    if (result.valid() && 
+        options().featherPixels() == true)
     {
         ImageUtils::featherAlphaRegions( result.get() );
     }    
@@ -731,16 +787,22 @@ ImageLayer::createImageFromTileSource(const TileKey&    key,
 
 
 GeoImage
-ImageLayer::assembleImageFromTileSource(const TileKey&    key,
-                                        ProgressCallback* progress)
+ImageLayer::assembleImage(const TileKey& key, ProgressCallback* progress)
 {
+    // If we got here, asset that there's a non-null layer profile.
+    if (!getProfile())
+    {
+        setStatus(Status::Error(Status::AssertionFailure, "assembleImage with undefined profile"));
+        return GeoImage::INVALID;
+    }
+
     GeoImage mosaicedImage, result;
 
     // Scale the extent if necessary to apply an "edge buffer"
     GeoExtent ext = key.getExtent();
-    if ( _runtimeOptions.edgeBufferRatio().isSet() )
+    if ( options().edgeBufferRatio().isSet() )
     {
-        double ratio = _runtimeOptions.edgeBufferRatio().get();
+        double ratio = options().edgeBufferRatio().get();
         ext.scale(ratio, ratio);
     }
 
@@ -763,7 +825,7 @@ ImageLayer::assembleImageFromTileSource(const TileKey&    key,
 
         for( std::vector<TileKey>::iterator k = intersectingKeys.begin(); k != intersectingKeys.end(); ++k )
         {
-            GeoImage image = createImageFromTileSource( *k, progress );
+            GeoImage image = createImageImplementation( *k, progress );
 
             if ( image.valid() )
             {
@@ -784,7 +846,7 @@ ImageLayer::assembleImageFromTileSource(const TileKey&    key,
                         osg::ref_ptr<osg::Image> convertedImg = ImageUtils::convertToRGBA8(image.getImage());
                         if (convertedImg.valid())
                         {
-                            image = GeoImage(convertedImg, image.getExtent());
+                            image = GeoImage(convertedImg.get(), image.getExtent());
                         }
                     }
                 }
@@ -822,7 +884,7 @@ ImageLayer::assembleImageFromTileSource(const TileKey&    key,
                 parentKey.valid() && !image.valid();
                 parentKey = parentKey.createParentKey())
             {
-                image = createImageFromTileSource( parentKey, progress );
+                image = createImageImplementation( parentKey, progress );
                 if ( image.valid() )
                 {
                     GeoImage cropped;
@@ -836,7 +898,7 @@ ImageLayer::assembleImageFromTileSource(const TileKey&    key,
                             osg::ref_ptr<osg::Image> convertedImg = ImageUtils::convertToRGBA8(image.getImage());
                             if (convertedImg.valid())
                             {
-                                image = GeoImage(convertedImg, image.getExtent());
+                                image = GeoImage(convertedImg.get(), image.getExtent());
                             }
                         }
 
@@ -873,7 +935,7 @@ ImageLayer::assembleImageFromTileSource(const TileKey&    key,
     }
     else
     {
-        OE_DEBUG << LC << "assembleImageFromTileSource: no intersections (" << key.str() << ")" << std::endl;
+        OE_DEBUG << LC << "assembleImage: no intersections (" << key.str() << ")" << std::endl;
     }
 
     // Final step: transform the mosaic into the requesting key's extent.
@@ -887,13 +949,15 @@ ImageLayer::assembleImageFromTileSource(const TileKey&    key,
         result = mosaicedImage.reproject( 
             key.getProfile()->getSRS(),
             &key.getExtent(), 
-            *_runtimeOptions.reprojectedTileSize(),
-            *_runtimeOptions.reprojectedTileSize(),
-            *_runtimeOptions.driver()->bilinearReprojection() );
+            options().reprojectedTileSize().get(),
+            options().reprojectedTileSize().get(),
+            options().driver()->bilinearReprojection().get());
     }
 
     // Process images with full alpha to properly support MP blending.
-    if ( result.valid() && *_runtimeOptions.featherPixels() && !isCoverage() )
+    if (result.valid() && 
+        options().featherPixels() == true &&
+        isCoverage() == false)
     {
         ImageUtils::featherAlphaRegions( result.getImage() );
     }
@@ -915,7 +979,7 @@ ImageLayer::applyTextureCompressionMode(osg::Texture* tex) const
     }
 
 
-    else if ( _runtimeOptions.textureCompression() == (osg::Texture::InternalFormatMode)~0 )
+    else if ( options().textureCompression() == (osg::Texture::InternalFormatMode)~0 )
     {
         // auto mode:
         if ( Registry::capabilities().isGLES() )
@@ -936,7 +1000,7 @@ ImageLayer::applyTextureCompressionMode(osg::Texture* tex) const
             }
         }
     }
-    else if ( _runtimeOptions.textureCompression() == (osg::Texture::InternalFormatMode)(~0 - 1))
+    else if ( options().textureCompression() == (osg::Texture::InternalFormatMode)(~0 - 1))
     {
         osg::Timer_t start = osg::Timer::instance()->tick();
         osgDB::ImageProcessor* imageProcessor = osgDB::Registry::instance()->getImageProcessorForExtension("fastdxt");
@@ -955,7 +1019,7 @@ ImageLayer::applyTextureCompressionMode(osg::Texture* tex) const
             }
             else
             {
-                OE_INFO << "FastDXT only works on GL_RGBA or GL_RGB images" << std::endl;
+                OE_DEBUG << "FastDXT only works on GL_RGBA or GL_RGB images" << std::endl;
                 return;
             }
 
@@ -964,7 +1028,7 @@ ImageLayer::applyTextureCompressionMode(osg::Texture* tex) const
             osg::Timer_t end = osg::Timer::instance()->tick();
             image->dirty();
             tex->setImage(0, image);
-            OE_INFO << "Compress took " << osg::Timer::instance()->delta_m(start, end) << std::endl;        
+            OE_DEBUG << "Compress took " << osg::Timer::instance()->delta_m(start, end) << std::endl;        
         }
         else
         {
@@ -972,9 +1036,9 @@ ImageLayer::applyTextureCompressionMode(osg::Texture* tex) const
         }
 
     }
-    else if ( _runtimeOptions.textureCompression().isSet() )
+    else if ( options().textureCompression().isSet() )
     {
         // use specifically picked a mode.
-        tex->setInternalFormatMode( *_runtimeOptions.textureCompression() );
+        tex->setInternalFormatMode(options().textureCompression().get());
     }
 }
diff --git a/src/osgEarth/ImageMosaic.cpp b/src/osgEarth/ImageMosaic.cpp
index b8ea0b9..d23670a 100644
--- a/src/osgEarth/ImageMosaic.cpp
+++ b/src/osgEarth/ImageMosaic.cpp
@@ -108,9 +108,10 @@ ImageMosaic::createImage()
 
     unsigned int pixelsWide = tilesWide * tileWidth;
     unsigned int pixelsHigh = tilesHigh * tileHeight;
+	unsigned int tileDepth = tile->_image->r();
 
     osg::ref_ptr<osg::Image> image = new osg::Image;
-    image->allocateImage(pixelsWide, pixelsHigh, 1, tile->_image->getPixelFormat(), tile->_image->getDataType());
+    image->allocateImage(pixelsWide, pixelsHigh, tileDepth, tile->_image->getPixelFormat(), tile->_image->getDataType());
     image->setInternalTextureFormat(tile->_image->getInternalTextureFormat());
     ImageUtils::markAsNormalized(image.get(), ImageUtils::isNormalized(tile->getImage()));
 
diff --git a/src/osgEarth/ImageToHeightFieldConverter b/src/osgEarth/ImageToHeightFieldConverter
index 810cca3..d4a79f4 100644
--- a/src/osgEarth/ImageToHeightFieldConverter
+++ b/src/osgEarth/ImageToHeightFieldConverter
@@ -60,6 +60,8 @@ namespace osgEarth
         */
         osg::Image* convert(const osg::HeightField* hf, int pixelSize = 32);
 
+        osg::Image* convertToR32F(const osg::HeightField* hf) const;
+
     private:
         osg::HeightField* convert16(const osg::Image* image ) const; 
         osg::HeightField* convert32(const osg::Image* image ) const; 
diff --git a/src/osgEarth/ImageToHeightFieldConverter.cpp b/src/osgEarth/ImageToHeightFieldConverter.cpp
index 45a6f98..f11ea84 100644
--- a/src/osgEarth/ImageToHeightFieldConverter.cpp
+++ b/src/osgEarth/ImageToHeightFieldConverter.cpp
@@ -23,6 +23,7 @@
 #include <osgEarth/ImageToHeightFieldConverter>
 #include <osgEarth/GeoCommon>
 #include <osg/Notify>
+#include <osg/Texture>
 #include <limits.h>
 #include <string.h>
 
@@ -187,6 +188,32 @@ osg::Image* ImageToHeightFieldConverter::convert16(const osg::HeightField* hf )
   return image;
 }
 
+osg::Image* ImageToHeightFieldConverter::convertToR32F(const osg::HeightField* hf) const
+{
+    if ( !hf ) {
+    return NULL;
+  }
+
+  osg::Image* image = new osg::Image();
+  image->allocateImage(hf->getNumColumns(), hf->getNumRows(), 1, GL_RED, GL_FLOAT); //GL_LUMINANCE, GL_SHORT);
+  image->setInternalTextureFormat(GL_R32F);
+
+  const osg::FloatArray* floats = hf->getFloatArray();
+
+  for( unsigned int i = 0; i < floats->size(); ++i  ) {
+      float h = floats->at( i );
+      *(float*)image->data(i) = h;
+      // Set NO_DATA_VALUE to a valid short value.
+      //if (h == NO_DATA_VALUE)
+      //{        
+      //    h = -SHRT_MAX;
+      //}
+      //*(short*)image->data(i) = (short)h;
+  }
+
+  return image;
+}
+
 osg::Image* ImageToHeightFieldConverter::convert32(const osg::HeightField* hf) const {
   if ( !hf ) {
     return NULL;
diff --git a/src/osgEarth/ImageUtils b/src/osgEarth/ImageUtils
index 9042a4f..3db0708 100644
--- a/src/osgEarth/ImageUtils
+++ b/src/osgEarth/ImageUtils
@@ -324,11 +324,29 @@ namespace osgEarth
             const Bounds&     referenceBounds);
 
         /**
+         * Bicubic upsampling in a quadrant. Target image is already allocated.
+         */
+        static bool bicubicUpsample(
+            const osg::Image* source,
+            osg::Image* target,
+            unsigned quadrant,
+            unsigned stride);
+
+        /**
          * 
          */
         static osg::Image* upSampleNN(const osg::Image* src, int quadrant);
 
         /**
+         * Activates mipmapping for a texture image if the correct filters exist.
+         *
+         * If OSG has an ImageProcessor service installed, this method will use that
+         * to generate mipmaps. If not, the method will be a NOP and the GPU wil
+         * generate mipmaps (if necessary) upon GPU transfer.
+         */
+        static void activateMipMaps(osg::Texture* texture);
+
+        /**
          * Reads color data out of an image, regardles of its internal pixel format.
          */
         class OSGEARTH_EXPORT PixelReader
@@ -340,6 +358,9 @@ namespace osgEarth
              */
             PixelReader(const osg::Image* image);
 
+            /** Sets an image to read. */
+            void setImage(const osg::Image* image);
+
             /** Whether to use bilinear interpolation when reading with u,v coords (default=true) */
             void setBilinear(bool value) { _bilinear = value; }
 
@@ -363,6 +384,7 @@ namespace osgEarth
 
             /** Reads a color from the image by unit coords [0..1] */
             osg::Vec4 operator()(float u, float v, int r=0, int m=0) const;
+            osg::Vec4 operator()(double u, double v, int r=0, int m=0) const;
 
             // internals:
             const unsigned char* data(int s=0, int t=0, int r=0, int m=0) const {
diff --git a/src/osgEarth/ImageUtils.cpp b/src/osgEarth/ImageUtils.cpp
index f557b25..65226ed 100644
--- a/src/osgEarth/ImageUtils.cpp
+++ b/src/osgEarth/ImageUtils.cpp
@@ -35,7 +35,7 @@
 #define LC "[ImageUtils] "
 
 
-#if defined(OSG_GLES1_AVAILABLE) || defined(OSG_GLES2_AVAILABLE)
+#if defined(OSG_GLES1_AVAILABLE) || defined(OSG_GLES2_AVAILABLE) || defined(OSG_GLES3_AVAILABLE)
 #    define GL_RGB8_INTERNAL  GL_RGB8_OES
 #    define GL_RGB8A_INTERNAL GL_RGBA8_OES
 #else
@@ -234,7 +234,7 @@ ImageUtils::resizeImage(const osg::Image* input,
         {
             output->allocateImage( out_s, out_t, input->r(), input->getPixelFormat(), input->getDataType(), input->getPacking() );
             output->setInternalTextureFormat( input->getInternalTextureFormat() );
-            markAsNormalized(output, isNormalized(input));
+            markAsNormalized(output.get(), isNormalized(input));
         }
         else
         {
@@ -258,11 +258,6 @@ ImageUtils::resizeImage(const osg::Image* input,
         PixelReader read( input );
         PixelWriter write( output.get() );
 
-        unsigned int pixel_size_bytes = input->getRowSizeInBytes() / in_s;
-
-        unsigned char* dataOffset = output->getMipmapData(mipmapLevel);
-        unsigned int   dataRowSizeBytes = output->getRowSizeInBytes() >> mipmapLevel;
-
         for( unsigned int output_row=0; output_row < out_t; output_row++ )
         {
             // get an appropriate input row
@@ -367,9 +362,7 @@ ImageUtils::flattenImage(osg::Image*                             input,
         layer->setPixelAspectRatio(input->getPixelAspectRatio());
         markAsNormalized(layer, isNormalized(input));
 
-#if OSG_MIN_VERSION_REQUIRED(3,1,0)
         layer->setRowLength(input->getRowLength());
-#endif
         layer->setOrigin(input->getOrigin());
         layer->setFileName(input->getFileName());
         layer->setWriteHint(input->getWriteHint());
@@ -381,6 +374,211 @@ ImageUtils::flattenImage(osg::Image*                             input,
     return true;
 }
 
+bool
+ImageUtils::bicubicUpsample(const osg::Image* source,
+                            osg::Image* target,
+                            unsigned quadrant,
+                            unsigned stride)
+{
+    const int border = 1; // don't change this.
+
+    int width = ((source->s() - 2*border)/2)+1 + 2*border;
+    int height = ((source->t() - 2*border)/2)+1 + 2*border;
+
+    int s_off = quadrant == 0 || quadrant == 2 ? 0 : source->s()-width;
+    int t_off = quadrant == 2 || quadrant == 3 ? 0 : source->t()-height;
+
+    ImageUtils::PixelReader readSource(source);
+    ImageUtils::PixelWriter writeTarget(target);
+    ImageUtils::PixelReader readTarget(target);
+
+    // copy the main box, which is all odd-numbered cells when there is a border size = 1.
+    for (int t = 1; t<height-1; ++t)
+    {
+        for (int s = 1; s<width-1; ++s)
+        {
+            osg::Vec4 value = readSource(s_off+s, t_off+t);
+            writeTarget(value, (s-1)*2+1, (t-1)*2+1);
+        }
+    }
+
+    // copy the corner border cells.
+    writeTarget(readSource(s_off, t_off), 0, 0); // upper left.
+    writeTarget(readSource(s_off + width - 1, t_off), target->s()-1, 0);
+    writeTarget(readSource(s_off, t_off + height - 1), 0, target->t()-1);
+    writeTarget(readSource(s_off + width - 1, t_off + height - 1), target->s() - 1, target->t() - 1);
+
+    // copy the border intermediate cells.
+    for (int s=1; s<width-1; ++s) // top/bottom:
+    {
+        writeTarget(readSource(s_off+s, t_off), (s-1)*2+1, 0);
+        writeTarget(readSource(s_off+s, t_off + height - 1), (s-1)*2+1, target->t()-1);
+    }
+    for (int t = 1; t < height-1; ++t) // left/right:
+    {
+        writeTarget(readSource(s_off, t_off+t), 0, (t-1)*2+1);
+        writeTarget(readSource(s_off + width - 1, t_off + t), target->s()-1, (t-1)*2+1);
+    }
+
+    // now interpolate the missing columns, including the border cells.
+    for (int s = 2; s<target->s()-2; s += 2)
+    {
+        for (int t = 0; t < target->t(); )
+        {
+            int offset = (s-1) % stride; // the minus1 accounts for the border
+            int s0 = std::max(s - offset, 0);
+            int s1 = std::min(s0 + (int)stride, target->s()-1);
+            double mu = (double)offset / (double)(s1-s0);
+            osg::Vec4 p1 = readTarget(s0, t);
+            osg::Vec4 p2 = readTarget(s1, t);
+            double mu2 = (1.0 - cos(mu*osg::PI))*0.5;
+            osg::Vec4 v = (p1*(1.0-mu2)) + (p2*mu2);
+            writeTarget(v, s, t);
+
+            if (t == 0 || t == target->t()-2) t+=1; else t+=2;
+        }
+    }
+
+    // next interpolate the odd numbered rows
+    for (int s = 0; s < target->s();)
+    {
+        for (int t = 2; t<target->t()-2; t += 2)
+        {
+            int offset = (t-1) % stride; // the minus1 accounts for the border
+            int t0 = std::max(t - offset, 0);
+            int t1 = std::min(t0 + (int)stride, target->t()-1);
+            double mu = (double)offset / double(t1-t0);
+
+            osg::Vec4 p1 = readTarget(s, t0);
+            osg::Vec4 p2 = readTarget(s, t1);
+            double mu2 = (1.0 - cos(mu*osg::PI))*0.5;
+            osg::Vec4 v = (p1*(1.0-mu2)) + (p2*mu2);
+            writeTarget(v, s, t);
+        }
+
+        if (s == 0 || s == target->s()-2) s+=1; else s+=2;
+    }
+
+    // then interpolate the centers
+    for (int s = 2; s<target->s()-2; s += 2)
+    {
+        for (int t = 2; t<target->t()-2; t += 2)
+        {
+            int s_offset = (s-1) % stride;
+            int s0 = std::max(s - s_offset, 0);
+            int s1 = std::min(s0 + (int)stride, target->s()-1);
+
+            int t_offset = (t-1) % stride;
+            int t0 = std::max(t - t_offset, 0);
+            int t1 = std::min(t0 + (int)stride, target->t()-1);
+
+            double mu, mu2;
+
+            osg::Vec4 p1 = readTarget(s0, t);
+            osg::Vec4 p2 = readTarget(s1, t);
+            mu = (double)s_offset / (double)(s1-s0);
+            mu2 = (1.0 - cos(mu*osg::PI))*0.5;
+            osg::Vec4 v1 = (p1*(1.0-mu2)) + (p2*mu2);
+            
+            osg::Vec4 p3 = readTarget(s, t0);
+            osg::Vec4 p4 = readTarget(s, t1);
+            mu = (double)t_offset / (double)(t1-t0);
+            mu2 = (1.0 - cos(mu*osg::PI))*0.5;
+            osg::Vec4 v2 = (p3*(1.0-mu2)) + (p4*mu2);
+
+            osg::Vec4 v = (v1+v2)*0.5;
+
+            writeTarget(v, s, t);
+        }
+    }
+
+
+#if 0
+    // first copy and expand the source quadrant by copying every
+    // even-numbered pixel.
+    for (int s=0; s<=source->s()/2; ++s)
+    {
+        for (int t=0; t<=source->t()/2; ++t)
+        {
+            osg::Vec4 value = readSource(s_off+s, t_off+t);
+            unsigned out_s = s*2, out_t = t*2;
+            writeTarget(value, s*2, t*2);
+        }
+    }
+
+    // next interpolate the odd numbered columns, based on the stride.
+    for (int s = 1; s<target->s()-1; s += 2)
+    {
+        for (int t = 0; t < target->t(); t += 2)
+        {
+            int offset = s % stride;
+            int s0 = std::max(s - offset, 0);
+            int s1 = std::min(s0 + (int)stride, target->s()-1);
+            float mu = (float)offset / (float)(s1-s0);
+
+            osg::Vec4 p1 = readTarget(s0, t);
+            osg::Vec4 p2 = readTarget(s1, t);
+            float mu2 = (1.0 - cosf(mu*osg::PI))*0.5;
+            osg::Vec4 v = (p1*(1.0-mu2)) + (p2*mu2);
+            writeTarget(v, s, t);
+        }
+    }
+
+    // next interpolate the odd numbered rows
+    for (int s = 0; s < target->s(); s += 2)
+    {
+        for (int t = 1; t<target->t()-1; t += 2)
+        {
+            int offset = t % stride;
+            int t0 = std::max(t - offset, 0);
+            int t1 = std::min(t0 + (int)stride, target->t()-1);
+            float mu = (float)offset / float(t1-t0);
+
+            osg::Vec4 p1 = readTarget(s, t0);
+            osg::Vec4 p2 = readTarget(s, t1);
+            float mu2 = (1.0 - cosf(mu*osg::PI))*0.5;
+            osg::Vec4 v = (p1*(1.0-mu2)) + (p2*mu2);
+            writeTarget(v, s, t);
+        }
+    }
+
+    // then interpolate the centers
+    for (int s = 1; s<target->s()-1; s += 2)
+    {
+        for (int t = 1; t<target->t()-1; t += 2)
+        {
+            int s_offset = s % stride;
+            int s0 = std::max(s - s_offset, 0);
+            int s1 = std::min(s0 + (int)stride, target->s()-1);
+
+            int t_offset = t % stride;
+            int t0 = std::max(t - t_offset, 0);
+            int t1 = std::min(t0 + (int)stride, target->t()-1);
+
+            float mu, mu2;
+
+            osg::Vec4 p1 = readTarget(s0, t);
+            osg::Vec4 p2 = readTarget(s1, t);
+            mu = (float)s_offset / (float)(s1-s0);
+            mu2 = (1.0 - cosf(mu*osg::PI))*0.5;
+            osg::Vec4 v1 = (p1*(1.0-mu2)) + (p2*mu2);
+            
+            osg::Vec4 p3 = readTarget(s, t0);
+            osg::Vec4 p4 = readTarget(s, t1);
+            mu = (float)t_offset / (float)(t1-t0);
+            mu2 = (1.0 - cosf(mu*osg::PI))*0.5;
+            osg::Vec4 v2 = (p3*(1.0-mu2)) + (p4*mu2);
+
+            osg::Vec4 v = (v1+v2)*0.5;
+
+            writeTarget(v, s, t);
+        }
+    }
+#endif
+    
+    return true;
+}
+
 osg::Image*
 ImageUtils::buildNearestNeighborMipmaps(const osg::Image* input)
 {
@@ -424,8 +622,8 @@ ImageUtils::buildNearestNeighborMipmaps(const osg::Image* input)
     for( int level=0; level<numMipmapLevels; ++level )
     {
         osg::ref_ptr<osg::Image> temp;
-        ImageUtils::resizeImage(input2, level_s, level_t, result, level, false);
-        ImageUtils::resizeImage(input2, level_s, level_t, temp, 0, false);
+        ImageUtils::resizeImage(input2.get(), level_s, level_t, result, level, false);
+        ImageUtils::resizeImage(input2.get(), level_s, level_t, temp, 0, false);
         level_s >>= 1;
         level_t >>= 1;
         input2 = temp.get();
@@ -722,7 +920,6 @@ ImageUtils::upSampleNN(const osg::Image* src, int quadrant)
     int seed = *(int*)dst->data(0,0);
 
     Random rng(seed+quadrant);
-    int c = 0;
 
     for(int t=0; t<dst->t(); t+=2)
     {
@@ -853,7 +1050,7 @@ ImageUtils::computeTextureCompressionMode(const osg::Image*                 imag
 
     const Capabilities& caps = Registry::capabilities();
 
-#ifndef OSG_GLES2_AVAILABLE
+#if defined(OSG_GLES2_AVAILABLE) && defined(OSG_GLES3_AVAILABLE)
 
     if (image->getPixelFormat() == GL_RGBA && image->getPixelSizeInBits() == 32) 
     {
@@ -1091,7 +1288,7 @@ ImageUtils::hasAlphaChannel(const osg::Image* image)
 bool
 ImageUtils::hasTransparency(const osg::Image* image, float threshold)
 {
-    if ( !image || !PixelReader::supports(image) )
+    if ( !image || !hasAlphaChannel(image) || !PixelReader::supports(image) )
         return false;
 
     PixelReader read(image);
@@ -1105,6 +1302,38 @@ ImageUtils::hasTransparency(const osg::Image* image, float threshold)
 }
 
 
+void
+ImageUtils::activateMipMaps(osg::Texture* tex)
+{
+#ifdef OSGEARTH_ENABLE_NVTT_CPU_MIPMAPS
+    // Verify that this texture requests mipmaps:
+    osg::Texture::FilterMode minFilter = tex->getFilter(tex->MIN_FILTER);
+
+    bool needsMipmaps =
+        minFilter == tex->LINEAR_MIPMAP_LINEAR ||
+        minFilter == tex->LINEAR_MIPMAP_NEAREST ||
+        minFilter == tex->NEAREST_MIPMAP_LINEAR ||
+        minFilter == tex->NEAREST_MIPMAP_NEAREST;
+
+    if (needsMipmaps && tex->getNumImages() > 0)
+    {
+        // See if we have a CPU mipmap generator:
+        osgDB::ImageProcessor* ip = osgDB::Registry::instance()->getImageProcessor();
+        if (ip)
+        {
+            for (unsigned i = 0; i < tex->getNumImages(); ++i)
+            {
+                if (tex->getImage(i)->getNumMipmapLevels() <= 1)
+                {
+                    ip->generateMipMap(*tex->getImage(i), true, ip->USE_CPU);
+                }
+            }
+        }
+    }
+#endif
+}
+
+
 bool
 ImageUtils::featherAlphaRegions(osg::Image* image, float maxAlpha)
 {
@@ -1361,6 +1590,27 @@ namespace
     };
 
     template<typename T>
+    struct ColorReader<GL_RED, T>
+    {
+        static osg::Vec4 read(const ImageUtils::PixelReader* ia, int s, int t, int r, int m)
+        {
+            const T* ptr = (const T*)ia->data(s, t, r, m);
+            float l = float(*ptr) * GLTypeTraits<T>::scale(ia->_normalized);
+            return osg::Vec4(l, l, l, 1.0f);
+        }
+    };
+
+    template<typename T>
+    struct ColorWriter<GL_RED, T>
+    {
+        static void write(const ImageUtils::PixelWriter* iw, const osg::Vec4f& c, int s, int t, int r, int m)
+        {
+            T* ptr = (T*)iw->data(s, t, r, m);
+            (*ptr) = (T)(c.r() / GLTypeTraits<T>::scale(iw->_normalized));
+        }
+    };
+
+    template<typename T>
     struct ColorReader<GL_ALPHA, T>
     {
         static osg::Vec4 read(const ImageUtils::PixelReader* ia, int s, int t, int r, int m)
@@ -1669,7 +1919,10 @@ namespace
             break;
         case GL_LUMINANCE:
             return chooseReader<GL_LUMINANCE>(dataType);
-            break;        
+            break;   
+        case GL_RED:
+            return chooseReader<GL_RED>(dataType);
+            break;       
         case GL_ALPHA:
             return chooseReader<GL_ALPHA>(dataType);
             break;        
@@ -1699,41 +1952,56 @@ namespace
 }
     
 ImageUtils::PixelReader::PixelReader(const osg::Image* image) :
-_image     (image),
 _bilinear  (false)
 {
-    _normalized = ImageUtils::isNormalized(image);
-    _colMult = _image->getPixelSizeInBits() / 8;
-    _rowMult = _image->getRowSizeInBytes();
-    _imageSize = _image->getImageSizeInBytes();
-    GLenum dataType = _image->getDataType();
-    _reader = getReader( _image->getPixelFormat(), dataType );
-    if ( !_reader )
-    {
-        OE_WARN << "[PixelReader] No reader found for pixel format " << std::hex << _image->getPixelFormat() << std::endl; 
-        _reader = &ColorReader<0,GLbyte>::read;
+    setImage(image);
+}
+
+void
+ImageUtils::PixelReader::setImage(const osg::Image* image)
+{
+    _image = image;
+    if (image)
+    {
+        _normalized = ImageUtils::isNormalized(image);
+        _colMult = _image->getPixelSizeInBits() / 8;
+        _rowMult = _image->getRowSizeInBytes();
+        _imageSize = _image->getImageSizeInBytes();
+        GLenum dataType = _image->getDataType();
+        _reader = getReader( _image->getPixelFormat(), dataType );
+        if ( !_reader )
+        {
+            OE_WARN << "[PixelReader] No reader found for pixel format " << std::hex << _image->getPixelFormat() << std::endl; 
+            _reader = &ColorReader<0,GLbyte>::read;
+        }
     }
 }
 
 osg::Vec4
 ImageUtils::PixelReader::operator()(float u, float v, int r, int m) const
+{
+    return operator()((double)u, (double)v, r, m);
+}
+
+osg::Vec4
+ImageUtils::PixelReader::operator()(double u, double v, int r, int m) const
  {
      if ( _bilinear )
      {
-         float sizeS = (float)(_image->s()-1);
-         float sizeT = (float)(_image->t()-1);
+         double sizeS = (double)(_image->s()-1);
+         double sizeT = (double)(_image->t()-1);
 
          // u, v => [0..1]
-         float s = u * sizeS;
-         float t = v * sizeT;
+         double s = u * sizeS;
+         double t = v * sizeT;
 
-         float s0 = std::max(floorf(s), 0.0f);
-         float s1 = std::min(s0+1.0f, sizeS);
-         float smix = s0 < s1 ? (s-s0)/(s1-s0) : 0.0f;
+         double s0 = std::max(floorf(s), 0.0f);
+         double s1 = std::min(s0+1.0f, sizeS);
+         double smix = s0 < s1 ? (s-s0)/(s1-s0) : 0.0f;
 
-         float t0 = std::max(floorf(t), 0.0f);
-         float t1 = std::min(t0+1.0f, sizeT);
-         float tmix = t0 < t1 ? (t-t0)/(t1-t0) : 0.0f;
+         double t0 = std::max(floorf(t), 0.0f);
+         double t1 = std::min(t0+1.0f, sizeT);
+         double tmix = t0 < t1 ? (t-t0)/(t1-t0) : 0.0f;
 
          osg::Vec4 UL = (*_reader)(this, (int)s0, (int)t0, r, m); // upper left
          osg::Vec4 UR = (*_reader)(this, (int)s1, (int)t0, r, m); // upper right
@@ -1748,8 +2016,8 @@ ImageUtils::PixelReader::operator()(float u, float v, int r, int m) const
      else
      {
          return (*_reader)(this,
-             (int)(u * (float)(_image->s()-1)),
-             (int)(v * (float)(_image->t()-1)),
+             (int)(u * (double)(_image->s()-1)),
+             (int)(v * (double)(_image->t()-1)),
              r, m);
      }
 }
@@ -1801,7 +2069,10 @@ namespace
             break;
         case GL_LUMINANCE:
             return chooseWriter<GL_LUMINANCE>(dataType);
-            break;        
+            break;      
+        case GL_RED:
+            return chooseWriter<GL_RED>(dataType);
+            break;         
         case GL_ALPHA:
             return chooseWriter<GL_ALPHA>(dataType);
             break;        
@@ -1830,16 +2101,19 @@ namespace
 ImageUtils::PixelWriter::PixelWriter(osg::Image* image) :
 _image(image)
 {
-    _normalized = ImageUtils::isNormalized(image);
-    _colMult = _image->getPixelSizeInBits() / 8;
-    _rowMult = _image->getRowSizeInBytes();
-    _imageSize = _image->getImageSizeInBytes();
-    GLenum dataType = _image->getDataType();
-    _writer = getWriter( _image->getPixelFormat(), dataType );
-    if ( !_writer )
-    {
-        OE_WARN << "[PixelWriter] No writer found for pixel format " << std::hex << _image->getPixelFormat() << std::endl; 
-        _writer = &ColorWriter<0, GLbyte>::write;
+    if (image)
+    {
+        _normalized = ImageUtils::isNormalized(image);
+        _colMult = _image->getPixelSizeInBits() / 8;
+        _rowMult = _image->getRowSizeInBytes();
+        _imageSize = _image->getImageSizeInBytes();
+        GLenum dataType = _image->getDataType();
+        _writer = getWriter( _image->getPixelFormat(), dataType );
+        if ( !_writer )
+        {
+            OE_WARN << "[PixelWriter] No writer found for pixel format " << std::hex << _image->getPixelFormat() << std::endl; 
+            _writer = &ColorWriter<0, GLbyte>::write;
+        }
     }
 }
 
diff --git a/src/osgEarth/Instancing.vert.glsl b/src/osgEarth/Instancing.vert.glsl
index 08ae434..d998950 100644
--- a/src/osgEarth/Instancing.vert.glsl
+++ b/src/osgEarth/Instancing.vert.glsl
@@ -1,4 +1,6 @@
 #version $GLSL_VERSION_STR
+$GLSL_DEFAULT_PRECISION_FLOAT
+
 #extension GL_EXT_gpu_shader4 : enable
 #extension GL_ARB_draw_instanced: enable
 
diff --git a/src/osgEarth/IntersectionPicker b/src/osgEarth/IntersectionPicker
index 3d925b5..cde6317 100644
--- a/src/osgEarth/IntersectionPicker
+++ b/src/osgEarth/IntersectionPicker
@@ -29,6 +29,11 @@ namespace osgEarth
 {
     /**
      * Utility for picking objects from the scene.
+     *
+     * Please consider the RTTPicker instead unless you need to pick
+     * multiple overlapping objects in the scene. RTTPicker offers more
+     * complete support (for annotations, shader-based geometry, etc.)
+     * than IntersectionPicker.
      */
     class OSGEARTH_EXPORT IntersectionPicker
     {
diff --git a/src/osgEarth/LandCover b/src/osgEarth/LandCover
new file mode 100644
index 0000000..3abee01
--- /dev/null
+++ b/src/osgEarth/LandCover
@@ -0,0 +1,250 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+ * Copyright 2016 Pelican Mapping
+ * http://osgearth.org
+ *
+ * osgEarth is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+#ifndef OSGEARTH_LAND_COVER_H
+#define OSGEARTH_LAND_COVER_H 1
+
+#include <osgEarth/Config>
+#include <osgEarth/Layer>
+#include <osgEarth/ImageLayer>
+#include <vector>
+
+namespace osgEarth
+{
+    /**
+     * A single classification definition of land cover and the coded
+     * value that osgEarth uses to represent the class in a coverage raster.
+     *
+     * For example, "forest"=11 or "water"=230.
+     *
+     * A collection of these makes up a land cover dictionary.
+     * Note: use set/getName to set the classification text that maps to the value.
+     */
+    class OSGEARTH_EXPORT LandCoverClass : public osg::Object
+    {
+    public:
+        META_Object(osgEarth, LandCoverClass);
+
+        //! Construct an empty land cover class.
+        LandCoverClass();
+
+        //! Construct a land cover class with the given name and numerical value
+        LandCoverClass(const std::string& name, int value);
+
+        //! Construct a land cover class from a serialized Config.
+        LandCoverClass(const Config& conf);
+
+        //! Copy constructor
+        LandCoverClass(const LandCoverClass& rhs, const osg::CopyOp& op);
+
+        //! Code value that represents this class in a coverage raster
+        void setValue(int value) { _value = value; }
+        int getValue() const { return _value; }
+
+    public:
+        void fromConfig(const Config& conf);
+        Config getConfig() const;
+
+    private:
+        int _value;
+    };
+    typedef std::vector< osg::ref_ptr<LandCoverClass> > LandCoverClassVector;
+
+
+    /**
+     * Configures a LandCoverDictionary layer.
+     */
+    class OSGEARTH_EXPORT LandCoverDictionaryOptions : public LayerOptions
+    {
+    public:
+        //! Construct new dictionary options
+        LandCoverDictionaryOptions(const ConfigOptions& co = ConfigOptions()) :
+            LayerOptions(co) { fromConfig(_conf); }
+        
+        //! Collection of classes defined in this dictionary.
+        LandCoverClassVector& classes() { return _landCoverClasses; }
+        const LandCoverClassVector& classes() const { return _landCoverClasses; }
+
+        //! Load the options from an XML file
+        bool loadFromXML(const URI& uri);
+        
+    public:
+        virtual Config getConfig() const;
+        
+    protected:
+        virtual void mergeConfig(const Config& conf) {
+            LayerOptions::mergeConfig(conf);
+            fromConfig(conf);
+        }
+        void fromConfig(const Config& conf);
+
+        
+        LandCoverClassVector _landCoverClasses;
+    };
+
+
+    /**
+     * Complete set of available land cover classes.
+     * Add this to a Map so that land cover facilities can find and use it.
+     * Adding more than one LandCoverDictionary to a Map will have undefined results!
+     */
+    class OSGEARTH_EXPORT LandCoverDictionary : public Layer
+    {
+    public:
+        META_Layer(osgEarth, LandCoverDictionary, LandCoverDictionaryOptions);
+
+        //! Constructs an empty dictionary */
+        LandCoverDictionary();
+
+        //! Constructs a dictionary from serializable options. */
+        LandCoverDictionary(const LandCoverDictionaryOptions&);
+
+        //! Add a class.
+        void addClass(const std::string& name, int value =INT_MAX);
+
+        //! Load from an XML file.
+        bool loadFromXML(const URI& uri);
+        
+    public:
+        LandCoverClassVector& getClasses() { return options().classes(); }
+        const LandCoverClassVector& getClasses() const { return options().classes(); }
+
+        //! Gets a land cover class by its name.
+        const LandCoverClass* getClassByName(const std::string& name) const;
+
+        //! Gets the land cover class corresponding to the integer value.
+        const LandCoverClass* getClassByValue(int value) const;
+    };
+
+
+    /**
+     * Maps an integral value from a land cover coverage raster to one of the 
+     * land cover classes in the dictionary.
+     * For example, 42 -> "tundra".
+     */
+    class OSGEARTH_EXPORT LandCoverValueMapping : public osg::Object
+    {
+    public:
+        META_Object(osgEarth, LandCoverValueMapping);
+
+        //! Construct a blank mapping
+        LandCoverValueMapping();
+
+        //! Construct with values
+        LandCoverValueMapping(int value, const std::string& className);
+
+        //! Deserialize a mapping
+        LandCoverValueMapping(const Config& conf);
+
+        //! Copy a mapping
+        LandCoverValueMapping(const LandCoverValueMapping& rhs, const osg::CopyOp& op);
+
+    public:
+        //! Value in the coverage raster to map to a land over class
+        void setValue(int value) { _value = value; }
+        int getValue() const { return _value.get(); }
+
+        //! Name of the land cover class we are mapping to
+        void setLandCoverClassName(const std::string& name) { _lcClassName = name; }
+        const std::string& getLandCoverClassName() const { return _lcClassName.get(); }
+
+    public:
+        void fromConfig(const Config&);
+        Config getConfig() const;
+
+    private:
+        optional<int> _value;
+        optional<std::string> _lcClassName;
+    };
+    typedef std::vector< osg::ref_ptr<LandCoverValueMapping> > LandCoverValueMappingVector;
+    
+    /**
+     * Serializable configuration for a LandCoverCoverageLayer.
+     */
+    class OSGEARTH_EXPORT LandCoverCoverageLayerOptions : public ImageLayerOptions
+    {
+    public:
+        //! Construct the options.
+        LandCoverCoverageLayerOptions(const ConfigOptions& co = ConfigOptions());
+
+        //! Collection of value mappings for the coverage layer.
+        LandCoverValueMappingVector& mappings() { return _valueMappings; }
+        const LandCoverValueMappingVector& mappings() const { return _valueMappings; }
+
+        //! Warping factor for this layer (default is 0.0)
+        optional<float>& warp() { return _warp; }
+        const optional<float>& warp() const { return _warp; }
+
+        //! Load the data from an XML file
+        bool loadMappingsFromXML(const URI& uri);
+
+        //! Add a mapping from a value to a class (convenience)
+        void map(int value, const std::string& className);
+
+    public:
+        virtual Config getConfig() const;
+
+    protected:
+        void fromConfig(const Config& conf);
+
+        virtual void mergeConfig(const Config& conf) {
+            ImageLayerOptions::mergeConfig(conf);
+            fromConfig(conf);
+        }
+
+    private:
+        LandCoverValueMappingVector _valueMappings;
+        optional<float> _warp;
+    };
+
+    /**
+     * Component coverage layer of a LandCoverLayer.
+     * This layer only lives inside a LandCoverLayer; it makes no sense to add
+     * it to a map directly.
+     */
+    class OSGEARTH_EXPORT LandCoverCoverageLayer : public ImageLayer
+    {
+    public:
+        META_Layer(osgEarth, LandCoverCoverageLayer, LandCoverCoverageLayerOptions);
+
+        /** Construct an empty land cover coverage layer. */
+        LandCoverCoverageLayer();
+
+        /** Deserialize a land cover coverage layer. */
+        LandCoverCoverageLayer(const LandCoverCoverageLayerOptions& options);
+
+        //! Code mappings.
+        LandCoverValueMappingVector& getMappings() { return options().mappings(); }
+        const LandCoverValueMappingVector& getMappings() const { return options().mappings(); }
+
+        //! Land cover dictionary to use.
+        void setDictionary(LandCoverDictionary* value) { _lcDictionary = value; }
+        LandCoverDictionary* getDictionary() const { return _lcDictionary.get(); }
+
+        //! Load mappings from XML
+        bool loadMappingsFromXML(const URI& uri);
+
+    public: // Layer
+
+    private:
+        osg::ref_ptr<LandCoverDictionary> _lcDictionary;
+    };
+
+} // namespace osgEarth
+
+#endif // OSGEARTH_LAND_COVER_H
diff --git a/src/osgEarth/LandCover.cpp b/src/osgEarth/LandCover.cpp
new file mode 100644
index 0000000..8dbba93
--- /dev/null
+++ b/src/osgEarth/LandCover.cpp
@@ -0,0 +1,315 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+ * Copyright 2016 Pelican Mapping
+ * http://osgearth.org
+ *
+ * osgEarth is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+#include <osgEarth/LandCover>
+#include <osgEarth/XmlUtils>
+#include <osgEarth/Registry>
+
+#define LC "[LandCover] "
+
+using namespace osgEarth;
+
+LandCoverClass::LandCoverClass() :
+osg::Object(),
+_value(0)
+{
+    //nop
+}
+
+LandCoverClass::LandCoverClass(const std::string& name, int value) :
+osg::Object()
+{
+    setName(name);
+    setValue(value);
+}
+
+LandCoverClass::LandCoverClass(const Config& conf) :
+osg::Object(),
+_value(0)
+{
+    fromConfig(conf);
+}
+
+LandCoverClass::LandCoverClass(const LandCoverClass& rhs, const osg::CopyOp& op) :
+osg::Object(rhs, op),
+_value(rhs._value)
+{
+    //nop
+}
+
+void
+LandCoverClass::fromConfig(const Config& conf)
+{
+    setName(conf.value("name"));
+}
+
+Config
+LandCoverClass::getConfig() const
+{
+    Config conf("class");
+    conf.set("name", getName());
+    return conf;
+}
+
+//............................................................................
+
+#undef  LC
+#define LC "[LandCoverDictionary] "
+
+REGISTER_OSGEARTH_LAYER(land_cover_dictionary, LandCoverDictionary);
+
+void
+LandCoverDictionaryOptions::fromConfig(const Config& conf)
+{
+    const Config* classes = conf.child_ptr("classes");
+    if (classes)
+    {
+        int value = 0;
+        for(ConfigSet::const_iterator i = classes->children().begin();
+            i != classes->children().end();
+            ++i)
+        {
+            osg::ref_ptr<LandCoverClass> lcc = new LandCoverClass(*i);
+            if (!lcc->getName().empty())
+            {
+                lcc->setValue( value++ );
+                _landCoverClasses.push_back(lcc.get());
+            }
+        }
+    }
+    OE_INFO << LC << _landCoverClasses.size() << " classes defined.\n";
+}
+
+Config
+LandCoverDictionaryOptions::getConfig() const
+{
+    Config conf = LayerOptions::getConfig();
+    conf.key() = "land_cover_dictionary";
+    if (!classes().empty())
+    {
+        Config classes("classes");
+        conf.add(classes);
+        for (LandCoverClassVector::const_iterator i = _landCoverClasses.begin();
+            i != _landCoverClasses.end();
+            ++i)
+        {
+            const LandCoverClass* lcc = i->get();
+            if (lcc && !lcc->getName().empty())
+            {
+                classes.add(lcc->getConfig());
+            }
+        }
+    }
+    return conf;
+}
+
+bool
+LandCoverDictionaryOptions::loadFromXML(const URI& uri)
+{
+    osg::ref_ptr<XmlDocument> xml = XmlDocument::load(uri);
+    if (xml.valid())
+    {
+        _conf = xml->getConfig().child("land_cover_dictionary");
+        fromConfig(_conf);
+        return true;
+    }
+    else
+    {
+        return false;
+    }
+}
+
+LandCoverDictionary::LandCoverDictionary() :
+Layer(&_optionsConcrete),
+_options(&_optionsConcrete)
+{
+    init();
+}
+
+LandCoverDictionary::LandCoverDictionary(const LandCoverDictionaryOptions& options) :
+Layer(&_optionsConcrete),
+_options(&_optionsConcrete),
+_optionsConcrete(options)
+{
+    init();
+}
+
+void
+LandCoverDictionary::addClass(const std::string& name, int value)
+{
+    if (value == INT_MAX)
+        value = options().classes().size();
+
+    options().classes().push_back(new LandCoverClass(name, value));
+}
+
+const LandCoverClass*
+LandCoverDictionary::getClassByName(const std::string& name) const
+{
+    for (LandCoverClassVector::const_iterator i = options().classes().begin();
+        i != options().classes().end();
+        ++i)
+    {
+        if (i->get()->getName() == name)
+            return i->get();
+    }
+    return 0L;
+}
+
+
+const LandCoverClass*
+LandCoverDictionary::getClassByValue(int value) const
+{
+    for (LandCoverClassVector::const_iterator i = options().classes().begin();
+        i != options().classes().end();
+        ++i)
+    {
+        if (i->get()->getValue() == value)
+            return i->get();
+    }
+    return 0L;
+}
+
+//...........................................................................
+
+#undef  LC
+#define LC "[LandCoverValueMapping] "
+
+LandCoverValueMapping::LandCoverValueMapping() :
+osg::Object()
+{
+    //nop
+}
+
+LandCoverValueMapping::LandCoverValueMapping(const Config& conf) :
+osg::Object()
+{
+    fromConfig(conf);
+}
+
+LandCoverValueMapping::LandCoverValueMapping(const LandCoverValueMapping& rhs, const osg::CopyOp& op) :
+osg::Object(rhs, op)
+{
+    _value = rhs._value;
+    _lcClassName = rhs._lcClassName;
+}
+
+LandCoverValueMapping::LandCoverValueMapping(int value, const std::string& className) :
+osg::Object(),
+_value(value),
+_lcClassName(className)
+{
+    //nop
+}
+
+void
+LandCoverValueMapping::fromConfig(const Config& conf)
+{
+    conf.getIfSet("value", _value);
+    conf.getIfSet("class", _lcClassName);
+}
+
+Config
+LandCoverValueMapping::getConfig() const
+{
+    Config conf("mapping");
+    conf.addIfSet("value", _value);
+    conf.addIfSet("class", _lcClassName);
+    return conf;
+}
+
+//...........................................................................
+
+#undef  LC
+#define LC "[LandCoverCoverageLayer] "
+
+LandCoverCoverageLayerOptions::LandCoverCoverageLayerOptions(const ConfigOptions& co) :
+ImageLayerOptions(co),
+_warp(0.0f)
+{
+    fromConfig(_conf);
+}
+
+void
+LandCoverCoverageLayerOptions::fromConfig(const Config& conf)
+{
+    ConfigSet mappings = conf.child("land_cover_mappings").children("mapping");
+    for (ConfigSet::const_iterator i = mappings.begin(); i != mappings.end(); ++i)
+    {
+        osg::ref_ptr<LandCoverValueMapping> mapping = new LandCoverValueMapping(*i);
+        _valueMappings.push_back(mapping.get());
+    }
+
+    conf.getIfSet("warp", _warp);
+}
+
+Config
+LandCoverCoverageLayerOptions::getConfig() const
+{
+    Config conf = ImageLayerOptions::getConfig();
+    conf.key() = "coverage";
+    if (conf.hasChild("land_cover_mappings") == false)
+    {   
+        Config mappings("land_cover_mappings");
+        conf.add(mappings); //.update(mappings);
+        for(LandCoverValueMappingVector::const_iterator i = _valueMappings.begin();
+            i != _valueMappings.end();
+            ++i)
+        {
+            LandCoverValueMapping* mapping = i->get();
+            if (mapping)
+                mappings.add(mapping->getConfig());
+        }
+    }
+    conf.set("warp", _warp);
+    return conf;
+}
+
+bool
+LandCoverCoverageLayerOptions::loadMappingsFromXML(const URI& uri)
+{
+    osg::ref_ptr<XmlDocument> xml = XmlDocument::load(uri);
+    if (xml.valid())
+    {
+        fromConfig(xml->getConfig());
+        return true;
+    }
+    else return false;
+}
+
+void
+LandCoverCoverageLayerOptions::map(int value, const std::string& lcClass)
+{
+    mappings().push_back(new LandCoverValueMapping(value, lcClass));
+}
+
+LandCoverCoverageLayer::LandCoverCoverageLayer() :
+ImageLayer(&_optionsConcrete),
+_options(&_optionsConcrete)
+{
+    init();
+}
+
+LandCoverCoverageLayer::LandCoverCoverageLayer(const LandCoverCoverageLayerOptions& options) :
+ImageLayer(&_optionsConcrete),
+_options(&_optionsConcrete),
+_optionsConcrete(options)
+{
+    init();
+}
+
diff --git a/src/osgEarth/LandCoverLayer b/src/osgEarth/LandCoverLayer
new file mode 100644
index 0000000..7c20375
--- /dev/null
+++ b/src/osgEarth/LandCoverLayer
@@ -0,0 +1,108 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+ * Copyright 2016 Pelican Mapping
+ * http://osgearth.org
+ *
+ * osgEarth is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+#ifndef OSGEARTH_LAND_COVER_LAYER
+#define OSGEARTH_LAND_COVER_LAYER 1
+
+#include <osgEarth/ImageLayer>
+#include <osgEarth/LandCover>
+
+namespace osgEarth
+{
+    /**
+     * Serializable configuration for a LandCoverLayer.
+     */
+    class OSGEARTH_EXPORT LandCoverLayerOptions : public ImageLayerOptions
+    {
+    public:
+        LandCoverLayerOptions(const ConfigOptions& co = ConfigOptions());
+        
+        /**
+         * Images layer from which to read source coverage data.
+         */
+        std::vector<LandCoverCoverageLayerOptions>& coverages() { return _coverages; }
+        const std::vector<LandCoverCoverageLayerOptions>& coverages() const { return _coverages; }
+
+        /**
+         * Amount by which to warp texture coordinates of coverage data.
+         * Try 0.01 as a starting point.
+         */
+        optional<float>& warpFactor() { return _warp; }
+        const optional<float>& warpFactor() const { return _warp; }
+
+        /**
+         * LOD at which to calculate the noise function for warping.
+         */
+        optional<unsigned>& noiseLOD() { return _noiseLOD; }
+        const optional<unsigned>& noiseLOD() const { return _noiseLOD; }
+
+    public:
+        virtual Config getConfig() const;
+
+    protected:
+        void fromConfig(const Config& conf);
+        virtual void mergeConfig(const Config& conf) {
+            ImageLayerOptions::mergeConfig(conf);
+            fromConfig(conf);
+        }
+
+        optional<float> _warp;
+        optional<unsigned> _noiseLOD;
+        std::vector<LandCoverCoverageLayerOptions> _coverages;
+    };
+
+    
+    /**
+     * Layer that provides land cover raster data, in which each texel 
+     * contains a land cover code as defined in the LandCoverDictionary.
+     * This appears in a Map as a shared, non-visible Layer.
+     */
+    class OSGEARTH_EXPORT LandCoverLayer : public ImageLayer
+    {
+    public:
+        META_Layer(osgEarth, LandCoverLayer, LandCoverLayerOptions);
+
+        /** Construct an emptry land cover layer. Use options() to configure */
+        LandCoverLayer();
+
+        /** Construct a land cover layer from options. */
+        LandCoverLayer(const LandCoverLayerOptions& options);
+
+        //! Given a land cover tile, which you can generate by calling
+        //! createImage, get the land cover class at the given parametric
+        //! coordinates [0..1].
+        const LandCoverClass* getClassByUV(const GeoImage& tile, double u, double v) const;
+
+    protected: // Layer
+
+        virtual void init();
+
+        virtual void addedToMap(const class Map*);
+
+        virtual GeoImage createImageImplementation(const TileKey& key, ProgressCallback*);
+
+    protected: // TerrainLayer
+
+        virtual TileSource* createTileSource();
+
+        osg::ref_ptr<LandCoverDictionary> _lcDictionary;
+    };
+
+} // namespace osgEarth
+
+#endif // OSGEARTH_LAND_COVER_LAYER
diff --git a/src/osgEarth/LandCoverLayer.cpp b/src/osgEarth/LandCoverLayer.cpp
new file mode 100644
index 0000000..4cfe4ed
--- /dev/null
+++ b/src/osgEarth/LandCoverLayer.cpp
@@ -0,0 +1,635 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+ * Copyright 2016 Pelican Mapping
+ * http://osgearth.org
+ *
+ * osgEarth is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+#include <osgEarth/LandCoverLayer>
+#include <osgEarth/ImageUtils>
+#include <osgEarth/Registry>
+#include <osgEarth/Map>
+#include <osgEarth/MetaTile>
+#include <osgEarth/SimplexNoise>
+#include <osgEarth/ThreadingUtils>
+#include <osgEarth/Progress>
+
+using namespace osgEarth;
+
+#define LC "[LandCoverLayer] "
+
+REGISTER_OSGEARTH_LAYER(land_cover, LandCoverLayer);
+
+namespace
+{
+    osg::Vec2 getSplatCoords(const TileKey& key, float baseLOD, const osg::Vec2& covUV)
+    {
+        osg::Vec2 out;
+
+        float dL = (float)key.getLOD() - baseLOD;
+        float factor = pow(2.0f, dL);
+        float invFactor = 1.0/factor;
+        out.set( covUV.x()*invFactor, covUV.y()*invFactor ); 
+
+        // For upsampling we need to calculate an offset as well
+        if ( factor >= 1.0 )
+        {
+            unsigned wide, high;
+            key.getProfile()->getNumTiles(key.getLOD(), wide, high);
+
+            float tileX = (float)key.getTileX();
+            float tileY = (float)(wide-1-key.getTileY()); // swap Y. (not done in the shader version.)
+
+            osg::Vec2 a( floor(tileX*invFactor), floor(tileY*invFactor) );
+            osg::Vec2 b( a.x()*factor, a.y()*factor );
+            osg::Vec2 c( (a.x()+1.0f)*factor, (a.y()+1.0f)*factor );
+            osg::Vec2 offset( (tileX-b.x())/(c.x()-b.x()), (tileY-b.y())/(c.y()-b.y()) );
+
+            out += offset;
+        }
+
+        return out;
+    }
+
+    osg::Vec2 warpCoverageCoords(const osg::Vec2& covIn, float noise, float warp)
+    {
+        float n1 = 2.0 * noise - 1.0;
+        return osg::Vec2(
+            covIn.x() + sin(n1*osg::PI*2.0) * warp,
+            covIn.y() + sin(n1*osg::PI*2.0) * warp);
+    }
+
+    float getNoise(SimplexNoise& noiseGen, const osg::Vec2& uv)
+    {
+        // TODO: check that u and v are 0..s and not 0..s-1
+        double n = noiseGen.getTiledValue(uv.x(), uv.y());
+        n = osg::clampBetween(n, 0.0, 1.0);
+        return n;
+    }
+
+    
+    typedef std::vector<int> CodeMap;
+
+    struct ILayer 
+    {
+        GeoImage  image;
+        float     scale;
+        osg::Vec2 bias;
+        bool      valid;
+        float     warp;
+        ImageUtils::PixelReader* read;
+        unsigned* codeTable;
+        unsigned loads;
+
+        ILayer() : valid(true), read(0L), scale(1.0f), warp(0.0f), loads(0) { }
+
+        ~ILayer() { if (read) delete read; }
+
+        void load(const TileKey& key, LandCoverCoverageLayer* sourceLayer, ProgressCallback* progress)
+        {
+            if (sourceLayer->getEnabled() && 
+                sourceLayer->isKeyInLegalRange(key) &&
+                sourceLayer->mayHaveDataInExtent(key.getExtent()))
+            {
+                for(TileKey k = key; k.valid() && !image.valid(); k = k.createParentKey())
+                {
+                    image = sourceLayer->createImage(k, progress);
+
+                    // check for cancelation:
+                    if (progress && progress->isCanceled())
+                    {
+                        break;
+                    }
+                } 
+            }
+
+            valid = image.valid();
+
+            if ( valid )
+            {
+                scale = key.getExtent().width() / image.getExtent().width();
+                bias.x() = (key.getExtent().xMin() - image.getExtent().xMin()) / image.getExtent().width();
+                bias.y() = (key.getExtent().yMin() - image.getExtent().yMin()) / image.getExtent().height();
+
+                read = new ImageUtils::PixelReader(image.getImage());
+
+                // cannot interpolate coverage data:
+                read->setBilinear( false );
+
+                warp = sourceLayer->options().warp().get();
+            }
+        }
+    };
+
+    // Constructs a code map (int to int) for a coverage layer. We will use this
+    // code map to map coverage layer codes to dictionary codes.
+    void buildCodeMap(const LandCoverCoverageLayer* coverage, CodeMap& codemap)
+    {
+        if (!coverage) {
+            OE_WARN << LC << "ILLEGAL: coverage not passed to buildCodeMap\n";
+            return;
+        }
+        if (!coverage->getDictionary()) {
+            OE_WARN << LC << "ILLEGAL: coverage dictionary not set in buildCodeMap\n";
+            return;
+        }
+
+        int highestValue = 0;
+
+        for (LandCoverValueMappingVector::const_iterator k = coverage->getMappings().begin();
+            k != coverage->getMappings().end();
+            ++k)
+        {
+            const LandCoverValueMapping* mapping = k->get();
+            int value = mapping->getValue();
+            if (value > highestValue)
+                highestValue = value;
+        }
+
+        codemap.assign(highestValue+1, -1);
+
+        for (LandCoverValueMappingVector::const_iterator k = coverage->getMappings().begin();
+            k != coverage->getMappings().end();
+            ++k)
+        {
+            const LandCoverValueMapping* mapping = k->get();
+            int value = mapping->getValue();
+            const LandCoverClass* lcClass = coverage->getDictionary()->getClassByName(mapping->getLandCoverClassName());
+            if (lcClass)
+            {
+                codemap[value] = lcClass->getValue();
+            }
+        }
+    }
+
+    typedef std::vector<osg::ref_ptr<LandCoverCoverageLayer> > LandCoverCoverageLayerVector;
+
+    /*
+     * TileSource that provides GeoImage's to the LandCoverLayer.
+     */
+    class LandCoverTileSource : public TileSource
+    {
+    public:
+        LandCoverTileSource(const LandCoverLayerOptions& options);
+
+    public: // TileSource
+        Status initialize(const osgDB::Options* readOptions);
+
+        osg::Image* createImage(const TileKey& key, ProgressCallback* progress);
+        
+        CachePolicy getCachePolicyHint() const {
+            return CachePolicy::NO_CACHE;
+        }
+
+        const LandCoverLayerOptions* _options;
+        const LandCoverLayerOptions& options() const { return *_options; }
+
+        void setDictionary(LandCoverDictionary*);
+        
+        // image layers, one per data source
+        LandCoverCoverageLayerVector _coverages;
+
+        // code maps (vector index is the source code; value is the destination code)
+        std::vector<CodeMap> _codemaps;
+
+        // todo
+        std::vector<float> _warps;
+
+        osg::ref_ptr<osgDB::Options> _readOptions;  
+
+        osg::ref_ptr<LandCoverDictionary> _lcDictionary;
+
+        friend class LandCoverLayer;
+    };
+
+
+    LandCoverTileSource::LandCoverTileSource(const LandCoverLayerOptions& options) :
+        TileSource(options),
+        _options(&options)
+    {
+        // Increase the L2 cache size since the parent LandCoverLayer is going to be
+        // using meta-tiling to create mosaics for warping
+        setDefaultL2CacheSize(64);
+    }
+
+    Status
+    LandCoverTileSource::initialize(const osgDB::Options* readOptions)
+    {
+        const Profile* profile = getProfile();
+        if ( !profile )
+        {
+            profile = osgEarth::Registry::instance()->getGlobalGeodeticProfile();
+            setProfile( profile );
+        }
+
+        for(unsigned i=0; i<options().coverages().size(); ++i)
+        {
+            LandCoverCoverageLayerOptions coverageOptions = options().coverages()[i];
+            if (coverageOptions.enabled() == false)
+                continue;
+
+            coverageOptions.cachePolicy() = CachePolicy::NO_CACHE; // TODO: yes? no?
+
+            // Create the coverage layer:
+            LandCoverCoverageLayer* layer = new LandCoverCoverageLayer( coverageOptions );
+
+            // Set up and open it.
+            layer->setTargetProfileHint( profile );
+            layer->setReadOptions(readOptions);
+            const Status& s = layer->open();
+            if (s.isOK())
+            {
+                _coverages.push_back(layer);
+                _codemaps.resize(_codemaps.size()+1);
+                OE_INFO << LC << "Opened coverage \"" << layer->getName() << "\"\n";
+            }
+            else
+            {
+                OE_WARN << "Layer \"" << layer->getName() << "\": " << s.toString() << std::endl;
+            }
+
+            // Integrate data extents into this tile source.
+            const DataExtentList& de = layer->getDataExtents();
+            for(DataExtentList::const_iterator dei = de.begin(); dei != de.end(); ++dei)
+            {
+                getDataExtents().push_back(*dei);
+            }
+        }
+
+        return STATUS_OK;
+    }
+
+    void
+    LandCoverTileSource::setDictionary(LandCoverDictionary* lcd)
+    {
+        _lcDictionary = lcd;
+
+        for (unsigned i = 0; i<_coverages.size(); ++i)
+        {
+            _coverages[i]->setDictionary(lcd);
+            buildCodeMap(_coverages[i].get(), _codemaps[i]);
+        }
+    }
+    
+    //TODO: overriding createImage directly like this will bypass caching.
+    //      This is a temporary solution; need to refactor.
+    osg::Image*
+    LandCoverTileSource::createImage(const TileKey& key, ProgressCallback* progress)
+    {
+        if ( _coverages.empty() )
+            return 0L;
+
+        std::vector<ILayer> layers(_coverages.size());
+
+        // Allocate the new coverage image; it will contain unnormalized values.
+        osg::ref_ptr<osg::Image> out = new osg::Image();
+        ImageUtils::markAsUnNormalized(out.get(), true);
+
+        // Allocate a suitable format:
+        GLint internalFormat = GL_LUMINANCE32F_ARB;
+
+        int tilesize = getPixelsPerTile();
+
+        out->allocateImage(tilesize, tilesize, 1, GL_RGB, GL_FLOAT);
+        out->setInternalTextureFormat(internalFormat);
+
+        osg::Vec2 cov;    // coverage coordinates
+
+        ImageUtils::PixelWriter write( out.get() );
+
+        float du = 1.0f / (float)(out->s()-1);
+        float dv = 1.0f / (float)(out->t()-1);
+
+        osg::Vec4 nodata;
+        if (internalFormat == GL_LUMINANCE16F_ARB)
+            nodata.set(-32768, -32768, -32768, -32768);
+        else
+            nodata.set(NO_DATA_VALUE, NO_DATA_VALUE, NO_DATA_VALUE, NO_DATA_VALUE);
+
+        unsigned pixelsWritten = 0u;
+
+        for(float u=0.0f; u<=1.0f; u+=du)
+        {
+            for(float v=0.0f; v<=1.0f; v+=dv)
+            {
+                bool wrotePixel = false;
+                for(int L = layers.size()-1; L >= 0 && !wrotePixel; --L)
+                {
+                    if (progress && progress->isCanceled())
+                        return 0L;
+
+                    ILayer& layer = layers[L];
+                    if ( !layer.valid )
+                        continue;
+
+                    if ( !layer.image.valid() )
+                        layer.load(key, _coverages[L].get(), progress);
+
+                    if (!layer.valid)
+                        continue;
+
+                    osg::Vec2 cov(layer.scale*u + layer.bias.x(), layer.scale*v + layer.bias.y());
+
+                    if ( cov.x() >= 0.0f && cov.x() <= 1.0f && cov.y() >= 0.0f && cov.y() <= 1.0f )
+                    {
+                        osg::Vec4 texel = (*layer.read)(cov.x(), cov.y());
+
+                        if ( texel.r() != NO_DATA_VALUE )
+                        {
+                            // store the warp factor in the green channel
+                            texel.g() = layer.warp;
+
+                            // store the layer index in the blue channel
+                            texel.b() = (float)L;
+
+                            if (texel.r() < 1.0f)
+                            {
+                                // normalized code; convert
+                                int code = (int)(texel.r()*255.0f);
+                                if (code < _codemaps[L].size())
+                                {
+                                    int value = _codemaps[L][code];
+                                    if (value >= 0)
+                                    {
+                                        texel.r() = (float)value;
+                                        write.f(texel, u, v);
+                                        wrotePixel = true;
+                                        pixelsWritten++;
+                                    }
+                                }
+                            }
+                            else
+                            {
+                                // unnormalized
+                                int code = (int)texel.r();
+                                if (code < _codemaps[L].size() && _codemaps[L][code] >= 0)
+                                {
+                                    texel.r() = (float)_codemaps[L][code];
+                                    write.f(texel, u, v);
+                                    wrotePixel = true;
+                                    pixelsWritten++;
+                                }
+                            }
+                        }
+                    }
+                }
+
+                if ( !wrotePixel )
+                {
+                    write.f(nodata, u, v);
+                }
+            }
+        }
+
+        return pixelsWritten > 0u? out.release() : 0L;
+    }
+}
+
+//........................................................................
+
+#undef  LC
+#define LC "[LandCoverLayerOptions] "
+
+LandCoverLayerOptions::LandCoverLayerOptions(const ConfigOptions& options) :
+ImageLayerOptions(options),
+_noiseLOD(12u),
+_warp(0.0f)
+{
+    fromConfig(_conf);
+}
+
+void
+LandCoverLayerOptions::fromConfig(const Config& conf)
+{
+    conf.getIfSet("warp", _warp);
+    conf.getIfSet("noise_lod", _noiseLOD);
+
+    ConfigSet layerConfs = conf.child("coverages").children("coverage");
+    for (ConfigSet::const_iterator i = layerConfs.begin(); i != layerConfs.end(); ++i)
+    {
+        _coverages.push_back(LandCoverCoverageLayerOptions(*i));
+    }
+}
+
+Config
+LandCoverLayerOptions::getConfig() const
+{
+    Config conf = ImageLayerOptions::getConfig();
+    conf.key() = "land_cover";
+
+    conf.addIfSet("warp", _warp);
+    conf.addIfSet("noise_lod", _noiseLOD);
+
+    if (_coverages.size() > 0)
+    {
+        Config images("coverages");
+        for (std::vector<LandCoverCoverageLayerOptions>::const_iterator i = _coverages.begin();
+            i != _coverages.end();
+            ++i)
+        {
+            images.add("coverage", i->getConfig());
+        }
+        conf.update(images);
+    }
+
+    return conf;
+}
+
+//...........................................................................
+
+#undef  LC
+#define LC "[LandCoverLayer] "
+
+
+LandCoverLayer::LandCoverLayer() :
+ImageLayer(&_optionsConcrete),
+_options(&_optionsConcrete)
+{
+    init();
+}
+
+LandCoverLayer::LandCoverLayer(const LandCoverLayerOptions& options) :
+ImageLayer(&_optionsConcrete),
+_options(&_optionsConcrete),
+_optionsConcrete(options)
+{
+    init();
+}
+
+void
+LandCoverLayer::init()
+{
+    options().coverage() = true;
+    options().visible() = false;
+    options().shared() = true;
+    ImageLayer::init();
+}
+
+void
+LandCoverLayer::addedToMap(const Map* map)
+{
+    // Find a land cover dictionary if there is one.
+    // There had better be one, or we are not going to get very far!
+    // This is called after createTileSource, so the TileSource should exist at this point.
+    // Note. If the land cover dictionary isn't already in the Map...this will fail! (TODO)
+    // Consider a LayerListener. (TODO)
+    _lcDictionary = map->getLayer<LandCoverDictionary>();
+    if (_lcDictionary.valid() && getTileSource())
+    {
+        static_cast<LandCoverTileSource*>(getTileSource())->setDictionary(_lcDictionary.get());
+    }
+    else
+    {
+        OE_WARN << LC << "Did not find a LandCoverDictionary in the Map!\n";
+    }
+}
+
+TileSource*
+LandCoverLayer::createTileSource()
+{
+    return new LandCoverTileSource(options());
+}
+
+GeoImage
+LandCoverLayer::createImageImplementation(const TileKey& key, ProgressCallback* progress)
+{
+    if (true) // warping enabled
+    {
+        MetaImage metaImage;
+
+        for (int x = -1; x <= 1; ++x)
+        {
+            for (int y = -1; y <= 1; ++y)
+            {
+                if (progress && progress->isCanceled())
+                    return GeoImage::INVALID;
+
+                // compute the neighoring key:
+                TileKey subkey = key.createNeighborKey(x, y);
+                if (subkey.valid())
+                {
+                    // compute the closest ancestor key with actual data for the neighbor key:
+                    TileKey bestkey = getBestAvailableTileKey(subkey);
+                    if (bestkey.valid())
+                    {
+                        // load the image and store it to the metaimage.
+                        GeoImage tile = ImageLayer::createImageImplementation(bestkey, progress);
+                        if (tile.valid())
+                        {
+                            osg::Matrix scaleBias;
+                            subkey.getExtent().createScaleBias(bestkey.getExtent(), scaleBias);
+                            metaImage.setImage(x, y, tile.getImage(), scaleBias);
+                        }
+                    }
+                }
+            }
+        }
+
+        const osg::Image* mainImage = metaImage.getImage(0, 0);
+        if ( !mainImage)
+            return GeoImage::INVALID;
+
+        // new image for the warped data:
+        osg::ref_ptr<osg::Image> image = new osg::Image();
+        image->allocateImage(
+            mainImage->s(),
+            mainImage->t(),
+            mainImage->r(),
+            mainImage->getPixelFormat(),
+            mainImage->getDataType(),
+            mainImage->getPacking());
+        image->setInternalTextureFormat(mainImage->getInternalTextureFormat());
+        ImageUtils::markAsUnNormalized(image.get(), true);
+
+        ImageUtils::PixelWriter write(image.get());
+
+        // Configure the noise function:
+        SimplexNoise noiseGen;
+        noiseGen.setNormalize(true);
+        noiseGen.setRange(0.0, 1.0);
+        noiseGen.setFrequency(4.0);
+        noiseGen.setPersistence(0.8);
+        noiseGen.setLacunarity(2.2);
+        noiseGen.setOctaves(8);
+
+        osg::Vec2d cov;
+        osg::Vec2 noiseCoords;
+        osg::Vec4 pixel;
+        osg::Vec4 nodata(NO_DATA_VALUE, NO_DATA_VALUE, NO_DATA_VALUE, NO_DATA_VALUE);
+        
+        float pdL = pow(2, (float)key.getLOD() - options().noiseLOD().get());
+
+        for (int t = 0; t < image->t(); ++t)
+        {
+            double v = (double)t / (double)(image->t() - 1);
+            for (int s = 0; s < image->s(); ++s)
+            {
+                double u = (double)s / (double)(image->s() - 1);
+                
+                if (progress && progress->isCanceled())
+                    return GeoImage::INVALID;
+
+                cov.set(u, v);
+
+                // first read the unwarped pixel to get the warping value.
+                // (warp is stored in pixel.g)
+                metaImage.read(cov.x(), cov.y(), pixel);
+                float warp = pixel.g() * pdL;
+
+                noiseCoords = getSplatCoords(key, options().noiseLOD().get(), cov);
+                double noise = getNoise(noiseGen, noiseCoords);
+                cov = warpCoverageCoords(cov, noise, warp);
+
+                if (metaImage.read(cov.x(), cov.y(), pixel))
+                {
+                    // only apply the warping if the location of the warped pixel
+                    // came from the same source layer. Otherwise you will get some
+                    // unsavory speckling. (Layer index is stored in pixel.b)
+                    osg::Vec4 unwarpedPixel;
+                    if (metaImage.read(u, v, unwarpedPixel) && pixel.b() != unwarpedPixel.b())
+                        write(unwarpedPixel, s, t);
+                    else
+                        write(pixel, s, t);
+                }
+                else
+                {
+                    write(nodata, s, t);
+                }
+            }
+        }
+
+        return GeoImage(image.get(), key.getExtent());
+    }
+
+    else
+    {
+        return ImageLayer::createImageImplementation(key, progress);
+    }
+}
+
+const LandCoverClass*
+LandCoverLayer::getClassByUV(const GeoImage& tile, double u, double v) const
+{
+    if (!tile.valid())
+        return 0L;
+
+    if (!_lcDictionary.valid())
+        return 0L;
+
+    ImageUtils::PixelReader read(tile.getImage());
+    read.setBilinear(false); // nearest neighbor only!
+    float value = read(u, v).r();
+
+    return _lcDictionary->getClassByValue((int)value);
+}
diff --git a/src/osgEarth/Layer b/src/osgEarth/Layer
index 2366975..668e871 100644
--- a/src/osgEarth/Layer
+++ b/src/osgEarth/Layer
@@ -21,37 +21,309 @@
 #define OSGEARTH_LAYER_H 1
 
 #include <osgEarth/Common>
+#include <osgEarth/Config>
+#include <osgEarth/Status>
+#include <osgEarth/TileKey>
+#include <osgEarth/PluginLoader>
+#include <osgEarth/CachePolicy>
+#include <osgEarth/TerrainResources>
+#include <osgEarth/Cache>
+#include <osg/BoundingBox>
+#include <osg/StateSet>
+#include <osgDB/Options>
+#include <vector>
+
+namespace osgUtil {
+    class CullVisitor;
+}
 
 namespace osgEarth
 {
     class SequenceControl;
 
     /**
+     * Base serializable options class for configuring a Layer.
+     */
+    class OSGEARTH_EXPORT LayerOptions : public ConfigOptions
+    {
+    public:
+        /** Name of the layer */
+        optional<std::string>& name() { return _name; }
+        const optional<std::string>& name() const { return _name; }
+
+        /**
+         * Whether to open and use this layer in the map. When a layer
+         * is not enabled, osgEarth components will ignore it altogether.
+         * Layers are enabled by default.
+         */
+        optional<bool>& enabled() { return _enabled; }
+        const optional<bool>& enabled() const { return _enabled; }
+
+        /**
+         * Uniquely identifies the caching location to use for this
+         * layer if a cache is enabled and if this layer does any caching.
+         * By default, the cache system will automatically generate a
+         * cache ID for each Layer, but you can set one manually here.
+         */
+        optional<std::string>& cacheId() { return _cacheId; }
+        const optional<std::string>& cacheId() const { return _cacheId; }
+
+        /**
+         * The caching policy directs how to use caching for this layer
+         * if a cache is available.
+         */
+        optional<CachePolicy>& cachePolicy() { return _cachePolicy; }
+        const optional<CachePolicy>& cachePolicy() const { return _cachePolicy; }
+
+        /**
+         * If set, calls setDefine() on the layer's stateset to make the
+         * #define available in shader code.
+         */
+        optional<std::string>& shaderDefine() { return _shaderDefine; }
+        const optional<std::string>& shaderDefine() const { return _shaderDefine; }
+
+        /**
+         * Shader snippet(s) to inject into this layer's stateset.
+         */
+        optional<std::string>& shader() { return _shader; }
+        const optional<std::string>& shader() const { return _shader; }
+
+    public:
+        LayerOptions();
+        LayerOptions(const ConfigOptions& configOptions);
+
+        virtual Config getConfig() const;
+        void fromConfig(const Config& conf);
+        virtual void mergeConfig(const Config& conf);
+
+    protected:
+        void setDefaults();
+
+        optional<std::string> _name;
+        optional<bool> _enabled;
+        optional<std::string> _cacheId;
+        optional<CachePolicy> _cachePolicy;
+        optional<std::string> _shaderDefine;
+        optional<std::string> _shader;
+    };
+
+
+    /**
+     * Base callback for all Layer types
+     */
+    class LayerCallback : public osg::Referenced
+    {
+        //NOP
+    };
+
+
+#define META_Layer(LIBRARY, TYPE, OPTIONS) \
+    private: \
+        OPTIONS * _options; \
+        OPTIONS   _optionsConcrete; \
+        TYPE ( const TYPE& rhs, const osg::CopyOp& op ) { } \
+    public: \
+        META_Object(LIBRARY, TYPE); \
+        OPTIONS& options() { return *_options; } \
+        const OPTIONS& options() const { return *_options; }
+
+
+    /**
      * Base class for all Map layers. 
      */
-    class OSGEARTH_EXPORT Layer : public osg::Referenced
+    class OSGEARTH_EXPORT Layer : public osg::Object
     {
     public:
+        META_Layer(osgEarth, Layer, LayerOptions);
+
+        /**
+         * Constructs a map layer
+         */
         Layer();
 
-        /** dtor */
-        virtual ~Layer() { }
+        /**
+         * Constructs a map layer by deserializing options. 
+         */
+        Layer(LayerOptions* options);
 
         /**
-         * Gets this layer's unique ID.
+         * This layer's unique ID.
+         * This value is generated automatically at runtime and is not
+         * guaranteed to be the same from one run to the next.
          */
         UID getUID() const { return _uid; }
 
         /**
+         * Sets the osgDB read options for this Layer.
+         */
+        virtual void setReadOptions(const osgDB::Options* options);
+        const osgDB::Options* getReadOptions() const { return _readOptions.get(); }
+
+        /**
+         * Open a layer and return the status.
+         * The layer options are applied in open().
+         */
+        virtual const Status& open();
+
+        /**
+         * Gets the status of this layer.
+         */
+        const Status& getStatus() const { return _status; }
+
+        /**
+         * @deprecated
          * Sequence controller if the layer has one.
          */
         virtual SequenceControl* getSequenceControl() { return 0L; }
 
+        /**
+         * Returns a Config object serializing this layer, if applicable.
+         */
+        virtual Config getConfig() const;
+
+        /**
+         * Whether the layer is enabled (used in the map). This will return
+         * false if the layer is set to disabled OR if getStatus returns an error.
+         */
+        virtual bool getEnabled() const;
+
+        /**
+         * Gets a optional scene graph provided by the layer (default is NULL).
+         * When this layer is added to a Map, the MapNode will call this method and
+         * add the return value to its scene graph; and remove it when the Layer
+         * is removed from the Map.
+         */
+        virtual osg::Node* getOrCreateNode() { return 0L; }
+
+        /**
+         * Extent of this layer's data. 
+         * This method may return GeoExtent::INVALID which just means that the
+         * information is unavailable.
+         */
+        virtual const GeoExtent& getExtent() const;
+
+        /**
+         * Returns or generates a caching ID for this layer
+         */
+        virtual std::string getCacheID() const;
+
+    public:
+
+        /** Adds a property notification callback to this layer */
+        void addCallback(LayerCallback* cb);
+
+        /** Removes a property notification callback from this layer */
+        void removeCallback(LayerCallback* cb);
+
+
+    public:
+
+        /**
+         * Access the layer's stateset, creating it is necessary
+         */
+        osg::StateSet* getOrCreateStateSet();
+
+        /**
+         * Layer's stateset, or NULL if none exists
+         */
+        osg::StateSet* getStateSet() const {
+            return _stateSet.get();
+        }
+
+        /**
+         * How (and if) to use this layer when rendering terrain tiles.
+         */
+        enum RenderType
+        {
+            // Layer does not require its own terrain rendering pass (model layers, elevation layers)
+            RENDERTYPE_NONE, 
+
+            // Layer requires a terrain rendering pass that draws terrain tile geometry
+            RENDERTYPE_TILE,
+
+            // Layer requires a terrain rendering pass that emits terrian patches or
+            // invokes a custom drawing function
+            RENDERTYPE_PATCH
+        };
+
+        RenderType getRenderType() const { return _renderType; }
+
+        void setRenderType(RenderType value) { _renderType = value; }
+
+        virtual void modifyTileBoundingBox(const TileKey& key, osg::BoundingBox& box) const { }
+
+        /**
+         * Return the class type name without namespace. For example if the leaf class type
+         * is osgEarth::ImageLayer, this method returns "ImageLayer".
+         */
+        const char* getTypeName() const;
+
+        //! Map will call this function when this Layer is added to a Map.
+        virtual void addedToMap(const class Map*) { }
+
+        //! Map will call this function when this Layer is removed from a Map.
+        virtual void removedFromMap(const class Map*) { }
+
+        //! MapNode will call this function when terrain resources are available.
+        virtual void setTerrainResources(TerrainResources*) { }
+
+
+    public: // Experimental
+
+        //! Called before culling this layer - return false to reject.
+        virtual bool cull(const osgUtil::CullVisitor*, osg::State::StateSetStack& stateSetStack) const;
+
+
+    public: // osg::Object
+
+        virtual void setName(const std::string& name);
+
+
+    public: // Loading a layer from a plugin
+        
+        static Layer* create(const ConfigOptions& options);
+        static Layer* create(const std::string& driver, const ConfigOptions& options);
+
+        static const ConfigOptions& getConfigOptions(const osgDB::Options*);
+
+    protected:
+
+        /** dtor */
+        virtual ~Layer();
+
+        // post-ctor initialization, chain to subclasses.
+        virtual void init();
+
+        const Status& setStatus(const Status& status) { _status = status; return _status; }
+        const Status& setStatus(const Status::Code& statusCode, const std::string& message) { return setStatus(Status(statusCode, message)); }
 
     private:
         UID _uid;
+        osg::ref_ptr<osg::StateSet> _stateSet;
+        RenderType _renderType;
+        Status _status;
+
+    protected:
+        typedef std::vector<osg::ref_ptr<LayerCallback> > CallbackVector;
+        CallbackVector _callbacks;
+        osg::ref_ptr<osgDB::Options> _readOptions;
+        osg::ref_ptr<CacheSettings> _cacheSettings;
+
+        // allow the map access to the addedToMap/removedFromMap methods
+        friend class Map;
     };
 
+    typedef std::vector< osg::ref_ptr<Layer> > LayerVector;
+
+    
+#define REGISTER_OSGEARTH_LAYER(NAME,CLASS) \
+    extern "C" void osgdb_##NAME(void) {} \
+    static osgEarth::RegisterPluginLoader< osgEarth::PluginLoader<CLASS, osgEarth::Layer> > g_proxy_##CLASS_##NAME( #NAME );
+    
+#define USE_OSGEARTH_LAYER(NAME) \
+    extern "C" void osgdb_##NAME(void); \
+    static osgDB::PluginFunctionProxy proxy_osgearth_layer_##NAME(osgdb_##NAME);
+
 } // namespace osgEarth
 
 #endif // OSGEARTH_IMAGE_TERRAIN_LAYER_H
diff --git a/src/osgEarth/Layer.cpp b/src/osgEarth/Layer.cpp
index a8e6b61..fbb25eb 100644
--- a/src/osgEarth/Layer.cpp
+++ b/src/osgEarth/Layer.cpp
@@ -18,10 +18,313 @@
  */
 #include <osgEarth/Layer>
 #include <osgEarth/Registry>
+#include <osgEarth/ShaderLoader>
+#include <osgDB/Registry>
+#include <osgUtil/CullVisitor>
 
 using namespace osgEarth;
 
-Layer::Layer()
+#define LC "[Layer] Layer \"" << getName() << "\" "
+
+//.................................................................
+
+LayerOptions::LayerOptions() :
+ConfigOptions()
+{
+    fromConfig(_conf);
+}
+
+LayerOptions::LayerOptions(const ConfigOptions& co) :
+ConfigOptions(co)
+{
+    fromConfig(_conf);
+}
+
+Config LayerOptions::getConfig() const
+{
+    Config conf = ConfigOptions::getConfig();
+    conf.set("name", _name);
+    conf.set("enabled", _enabled);
+    conf.set("cacheid", _cacheId);
+    if (_cachePolicy.isSet() && !_cachePolicy->empty())
+        conf.setObj("cache_policy", _cachePolicy);
+    conf.set("shader_define", _shaderDefine);
+    conf.set("shader", _shader);
+    return conf;
+}
+
+void LayerOptions::fromConfig(const Config& conf)
+{
+    _enabled.init(true);
+
+    conf.getIfSet("name", _name);
+    conf.getIfSet("enabled", _enabled);
+    conf.getIfSet("cache_id", _cacheId); // compat
+    conf.getIfSet("cacheid", _cacheId);
+    conf.getObjIfSet("cache_policy", _cachePolicy);
+
+    // legacy support:
+    if (!_cachePolicy.isSet())
+    {
+        if ( conf.value<bool>( "cache_only", false ) == true )
+            _cachePolicy->usage() = CachePolicy::USAGE_CACHE_ONLY;
+        if ( conf.value<bool>( "cache_enabled", true ) == false )
+            _cachePolicy->usage() = CachePolicy::USAGE_NO_CACHE;
+    }
+    conf.getIfSet("shader_define", _shaderDefine);
+    conf.getIfSet("shader", _shader);
+}
+
+void LayerOptions::mergeConfig(const Config& conf)
+{
+    ConfigOptions::mergeConfig(conf);
+    fromConfig(_conf);
+}
+
+//.................................................................
+
+Layer::Layer() :
+_options(&_optionsConcrete)
+{
+    _uid = osgEarth::Registry::instance()->createUID();
+    _renderType = RENDERTYPE_NONE;
+    _status = Status::OK();
+
+    init();
+}
+
+Layer::Layer(LayerOptions* optionsPtr) :
+_options(optionsPtr? optionsPtr : &_optionsConcrete)
+{
+    _uid = osgEarth::Registry::instance()->createUID();
+    _renderType = RENDERTYPE_NONE;
+    _status = Status::OK();
+    // init() will be called by base class
+}
+
+Layer::~Layer()
+{
+    OE_DEBUG << LC << "~Layer\n";
+}
+
+//void
+//Layer::setReadOptions(const osgDB::Options* options)
+//{
+//    _readOptions = Registry::cloneOrCreateOptions(options);
+//}
+
+void
+Layer::setReadOptions(const osgDB::Options* readOptions)
+{
+    // We are storing _cacheSettings both in the Read Options AND
+    // as a class member. This is probably not strictly necessary
+    // but we will keep the ref in the Layer just to be on the safe
+    // side - gw
+
+    _readOptions = Registry::cloneOrCreateOptions(readOptions);
+
+    // Create some local cache settings for this layer:
+    CacheSettings* oldSettings = CacheSettings::get(readOptions);
+    _cacheSettings = oldSettings ? new CacheSettings(*oldSettings) : new CacheSettings();
+
+    // bring in the new policy for this layer if there is one:
+    _cacheSettings->integrateCachePolicy(options().cachePolicy());
+
+    // if caching is a go, install a bin.
+    if (_cacheSettings->isCacheEnabled())
+    {
+        std::string binID = getCacheID();
+
+        // make our cacheing bin!
+        CacheBin* bin = _cacheSettings->getCache()->addBin(binID);
+        if (bin)
+        {
+            OE_INFO << LC << "Cache bin is [" << binID << "]\n";
+            _cacheSettings->setCacheBin( bin );
+        }
+        else
+        {
+            // failed to create the bin, so fall back on no cache mode.
+            OE_WARN << LC << "Failed to open a cache bin [" << binID << "], disabling caching\n";
+            _cacheSettings->cachePolicy() = CachePolicy::NO_CACHE;
+        }
+    }
+
+    // Store it for further propagation!
+    _cacheSettings->store(_readOptions.get());
+}
+
+std::string
+Layer::getCacheID() const
+{
+    std::string binID;
+    if (options().cacheId().isSet() && !options().cacheId()->empty())
+    {
+        binID = options().cacheId().get();
+    }
+    else
+    {
+        Config conf = getConfig();
+        binID = hashToString(conf.toJSON(false));
+    }
+    return binID;
+}
+
+Config
+Layer::getConfig() const
+{
+    return options().getConfig();
+}
+
+bool
+Layer::getEnabled() const
+{
+    return (options().enabled() == true) && getStatus().isOK();
+}
+
+void
+Layer::init()
+{
+    // Copy the layer options name into the Object name.
+    // This happens here AND in open.
+    if (options().name().isSet())
+    {
+        osg::Object::setName(options().name().get());
+    }
+}
+
+const Status&
+Layer::open()
+{
+    // Copy the layer options name into the Object name.
+    if (options().name().isSet())
+    {
+        osg::Object::setName(options().name().get());
+    }
+    
+    // Install any shader #defines
+    if (options().shaderDefine().isSet() && !options().shaderDefine()->empty())
+    {
+        OE_INFO << LC << "Setting shader define " << options().shaderDefine().get() << "\n";
+        getOrCreateStateSet()->setDefine(options().shaderDefine().get());
+    }
+
+    // Load any user defined shaders
+    if (options().shader().isSet() && !options().shader()->empty())
+    {
+        OE_INFO << LC << "Installing inline shader code\n";
+        VirtualProgram* vp = VirtualProgram::getOrCreate(this->getOrCreateStateSet());
+        ShaderPackage package;
+        package.add("", options().shader().get());
+        package.loadAll(vp, getReadOptions());
+    }
+
+    return _status;
+}
+
+void
+Layer::setName(const std::string& name)
 {
-    _uid = Registry::instance()->createUID();
+    osg::Object::setName(name);
+    options().name() = name;
 }
+
+const char*
+Layer::getTypeName() const
+{
+    return typeid(*this).name();
+}
+
+#define LAYER_OPTIONS_TAG "osgEarth.LayerOptions"
+
+Layer*
+Layer::create(const ConfigOptions& options)
+{
+    return create(options.getConfig().key(), options);
+}
+
+Layer*
+Layer::create(const std::string& name, const ConfigOptions& options)
+{
+    if ( name.empty() )
+    {
+        OE_WARN << "[Layer] ILLEGAL- Layer::create requires a plugin name" << std::endl;
+        return 0L;
+    }
+
+    // convey the configuration options:
+    osg::ref_ptr<osgDB::Options> dbopt = Registry::instance()->cloneOrCreateOptions();
+    dbopt->setPluginData( LAYER_OPTIONS_TAG, (void*)&options );
+
+    //std::string pluginExtension = std::string( ".osgearth_" ) + name;
+    std::string pluginExtension = std::string( "." ) + name;
+
+    // use this instead of osgDB::readObjectFile b/c the latter prints a warning msg.
+    osgDB::ReaderWriter::ReadResult rr = osgDB::Registry::instance()->readObject( pluginExtension, dbopt.get() );
+    if ( !rr.validObject() || rr.error() )
+    {
+        // quietly fail so we don't get tons of msgs.
+        return 0L;
+    }
+
+    Layer* layer = dynamic_cast<Layer*>( rr.getObject() );
+    if ( layer == 0L )
+    {
+        // TODO: communicate an error somehow
+        return 0L;
+    }
+
+    if (layer->getName().empty())
+        layer->setName(name);
+
+    rr.takeObject();
+    return layer;
+}
+
+const ConfigOptions&
+Layer::getConfigOptions(const osgDB::Options* options)
+{
+    static ConfigOptions s_default;
+    const void* data = options->getPluginData(LAYER_OPTIONS_TAG);
+    return data ? *static_cast<const ConfigOptions*>(data) : s_default;
+}
+
+void
+Layer::addCallback(LayerCallback* cb)
+{
+    _callbacks.push_back( cb );
+}
+
+void
+Layer::removeCallback(LayerCallback* cb)
+{
+    CallbackVector::iterator i = std::find( _callbacks.begin(), _callbacks.end(), cb );
+    if ( i != _callbacks.end() ) 
+        _callbacks.erase( i );
+}
+
+bool
+Layer::cull(const osgUtil::CullVisitor* cv, osg::State::StateSetStack& stateSetStack) const
+{
+    //if (getStateSet())
+    //    cv->pushStateSet(getStateSet());
+    return true;
+}
+
+const GeoExtent&
+Layer::getExtent() const
+{
+    static GeoExtent s_invalid = GeoExtent::INVALID;
+    return s_invalid;
+}
+
+osg::StateSet*
+Layer::getOrCreateStateSet()
+{
+    if (!_stateSet.valid())
+    {
+        _stateSet = new osg::StateSet();
+        _stateSet->setName("Layer");
+    }
+    return _stateSet.get();
+}
\ No newline at end of file
diff --git a/src/osgEarth/LayerListener b/src/osgEarth/LayerListener
new file mode 100644
index 0000000..b8280e6
--- /dev/null
+++ b/src/osgEarth/LayerListener
@@ -0,0 +1,146 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+ * Copyright 2016 Pelican Mapping
+ * http://osgearth.org
+ *
+ * osgEarth is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+#ifndef OSGEARTH_LAYER_LISTENER_H
+#define OSGEARTH_LAYER_LISTENER_H 1
+
+#include <osgEarth/Map>
+#include <osgEarth/MapCallback>
+
+namespace osgEarth
+{
+    /**
+     * A helper class for when you need to know when a named layer is added
+     * or removed from the Map. Use it like this:
+     *
+     *   LayerListener<MyClass> L;
+     *   ...
+     *   L.listen(map, "layerName", targetObject, &MyClass::setLayer);
+     *
+     * In this example, targetObject is an instance of MyClass, and setLayer
+     * is the member function of MyClass to call whenever layer "layerName"
+     * arrives in or departs from the Map. The "setLayer" function signature
+     * is
+     * 
+     *   bool setLayer(Layer*);
+     *
+     * When "layerName" arrives in the map, the listener will call 
+     * 
+     *   targetObject->setLayer(layer);
+     *
+     * And it will call the same method with NULL when the layer departs the map.
+     */
+    template<typename LISTENER, typename LAYERTYPE>
+    class LayerListener
+    {
+    public:
+        typedef void(LISTENER::*Function)(LAYERTYPE*);
+
+        struct Entry {
+            Entry() { }
+            osg::observer_ptr<const Map> _map;
+            std::string _layerName;
+            LISTENER* _object;
+            Function _function;
+            osg::ref_ptr<MapCallback> _callback;
+            LayerListener<LISTENER,LAYERTYPE>* _listener;
+        };
+
+        struct Callback : public MapCallback {
+            Entry* _entry;
+            Callback(Entry* entry) : _entry(entry) { }
+            void onLayerAdded(Layer* layer, unsigned index) {
+                LAYERTYPE* layerCast = dynamic_cast<LAYERTYPE*>(layer);
+                if (layerCast)
+                    _entry->_listener->onLayerAdded(_entry, layerCast, index);
+            }
+            void onLayerRemoved(Layer* layer, unsigned index) {
+                _entry->_listener->onLayerRemoved(_entry, layer, index);
+            }
+        };
+
+        //! Listens for a layer of type LAYERTYPE and name "layerName" to arrive.
+        void listen(const Map* map, const std::string& layerName, LISTENER* object, Function function) {
+            if (map) {
+                _entries.push_back(Entry());
+                Entry& e = _entries.back();
+                e._map = map;
+                e._layerName = layerName;
+                e._object = object;
+                e._function = function;
+                e._callback = new Callback(&e);
+                e._listener = this;
+                map->addMapCallback(e._callback.get());
+
+                // See if it's already there
+                LAYERTYPE* layer = map->getLayerByName<LAYERTYPE>(layerName);
+                if (layer)
+                    onLayerAdded(&e, layer, map->getIndexOfLayer(layer));
+            }
+        }
+
+        //! Listens for the first layer of type LAYERTYPE to arrive (regardles of name)
+        void listen(const Map* map, LISTENER* object, Function function) {
+            if (map) {
+                _entries.push_back(Entry());
+                Entry& e = _entries.back();
+                e._map = map;
+                e._object = object;
+                e._function = function;
+                e._callback = new Callback(&e);
+                e._listener = this;
+                map->addMapCallback(e._callback.get());
+
+                // See if it's already there
+                LAYERTYPE* layer = map->getLayer<LAYERTYPE>();
+                if (layer)
+                    onLayerAdded(&e, layer, map->getIndexOfLayer(layer));
+            }
+        }
+
+        void clear() {
+            for (typename std::vector<Entry>::iterator i = _entries.begin(); i != _entries.end(); ++i) {
+                if (i->_callback.valid()) {
+                    osg::ref_ptr<const Map> map;
+                    if (i->_map.lock(map)) {
+                        map->removeMapCallback(i->_callback.get());
+                    }
+                }
+            }
+            _entries.clear();
+        }
+
+        typename std::vector<Entry> _entries;
+
+        ~LayerListener() {
+            clear();
+        }
+
+        void onLayerAdded(Entry* entry, LAYERTYPE* layer, unsigned index) {
+            if (entry->_layerName.empty() || entry->_layerName == layer->getName())
+                ((*entry->_object).*(entry->_function))(layer);
+        }
+
+        void onLayerRemoved(Entry* entry, Layer* layer, unsigned index) {
+            if (entry->_layerName.empty() || entry->_layerName == layer->getName())
+                ((*entry->_object).*(entry->_function))(0L);
+        }
+    };
+}
+
+#endif // OSGEARTH_LAYER_LISTENER_H
\ No newline at end of file
diff --git a/src/osgEarth/Lighting b/src/osgEarth/Lighting
new file mode 100644
index 0000000..c75df11
--- /dev/null
+++ b/src/osgEarth/Lighting
@@ -0,0 +1,137 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+* Copyright 2015 Pelican Mapping
+* http://osgearth.org
+*
+* osgEarth is free software; you can redistribute it and/or modify
+* it under the terms of the GNU Lesser General Public License as published by
+* the Free Software Foundation; either version 2 of the License, or
+* (at your option) any later version.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+* IN THE SOFTWARE.
+*
+* You should have received a copy of the GNU Lesser General Public License
+* along with this program.  If not, see <http://www.gnu.org/licenses/>
+*/
+
+#ifndef OSGEARTH_LIGHTING_H
+#define OSGEARTH_LIGHTING_H 1
+
+#include <osgEarth/Common>
+#include <osg/Node>
+#include <osg/NodeVisitor>
+#include <osg/LightSource>
+#include <osg/Light>
+#include <osg/Material>
+#include <set>
+
+// Use this with StateSet::setDefine to toggle lighting in osgEarth lighting shaders.
+#define OE_LIGHTING_DEFINE "OE_LIGHTING"
+
+namespace osgEarth
+{
+    /**
+     * Traverses a graph, looking for Lights and Materials, and generates either static
+     * uniforms or dynamic cull callbacks for them so they will work with core profile
+     * shaders. (This is necessary for GL3+ core, OSX, Mesa etc. that don't support
+     * compatibility mode.)
+     */
+    class OSGEARTH_EXPORT GenerateGL3LightingUniforms : public osg::NodeVisitor
+    {
+    public:
+        GenerateGL3LightingUniforms();
+
+    public: // osg::NodeVisitor
+        virtual void apply(osg::Node& node);
+        virtual void apply(osg::LightSource& node);
+
+    protected:
+        std::set<osg::StateSet*> _statesets;
+
+        template<typename T> bool alreadyInstalled(osg::Callback* cb) const {
+            return !cb ? false : dynamic_cast<T*>(cb)!=0L ? true : alreadyInstalled<T>(cb->getNestedCallback());
+        }
+    };
+
+    /**
+     * Material that will work in both FFP and non-FFP mode, by using the uniform
+     * osg_FrontMaterial in place of gl_FrontMaterial.
+     */
+    class OSGEARTH_EXPORT MaterialGL3 : public osg::Material
+    {
+    public:
+        MaterialGL3() : osg::Material() { }
+        MaterialGL3(const MaterialGL3& mat, const osg::CopyOp& copyop = osg::CopyOp::SHALLOW_COPY) : osg::Material(mat, copyop) {}
+        MaterialGL3(const osg::Material& mat) : osg::Material(mat) { }
+
+        META_StateAttribute(osgEarth, MaterialGL3, MATERIAL);
+
+        virtual void apply(osg::State&) const;
+    };
+
+    /**
+     * StateAttributeCallback that will update osg::Material properties as Uniforms
+     */
+    class OSGEARTH_EXPORT MaterialCallback : public osg::StateAttributeCallback
+    {
+    public:
+        virtual void operator() (osg::StateAttribute* attr, osg::NodeVisitor* nv);        
+    };
+
+    /**
+     * Light that will work in both FFP and non-FFP mode.
+     * To use this, find a LightSource and replace the Light with a LightGL3.
+     * Then install the LightSourceGL3UniformGenerator cull callback on the LightSource.
+     */
+    class OSGEARTH_EXPORT LightGL3 : public osg::Light
+    {
+    public:
+        LightGL3() : osg::Light(), _enabled(true) { }
+        LightGL3(int lightnum) : osg::Light(lightnum), _enabled(true) { }
+        LightGL3(const LightGL3& light, const osg::CopyOp& copyop =osg::CopyOp::SHALLOW_COPY)
+            : osg::Light(light, copyop), _enabled(light._enabled) {}
+        LightGL3(const osg::Light& light)
+            : osg::Light(light), _enabled(true) { }
+
+        void setEnabled(bool value) { _enabled = value; }
+        bool getEnabled() const { return _enabled; }
+
+        virtual osg::Object* cloneType() const { return new LightGL3(_lightnum); }
+        virtual osg::Object* clone(const osg::CopyOp& copyop) const { return new LightGL3(*this,copyop); }
+        virtual bool isSameKindAs(const osg::Object* obj) const { return dynamic_cast<const LightGL3 *>(obj)!=NULL; }
+        virtual const char* libraryName() const { return "osgEarth"; }
+        virtual const char* className() const { return "LightGL3"; }
+        virtual Type getType() const { return LIGHT; }
+
+        virtual void apply(osg::State&) const;
+
+    protected:
+        bool _enabled;
+    };
+
+    /**
+     * Cull callback that will install Light uniforms based on the Light in a LightSource.
+     * Install this directly on the LightSource node. 
+     */
+    class OSGEARTH_EXPORT LightSourceGL3UniformGenerator : public osg::NodeCallback
+    {
+    public:
+        /**
+         * Creates and installs Uniforms on the stateset for the Light components
+         * of the Light that are non-positional (everything but the position and direction)
+         */
+        void generateNonPositionalData(osg::StateSet* ss, osg::Light* light);
+
+    public: // osg::NodeCallback
+
+        bool run(osg::Object* obj, osg::Object* data);
+    };
+}
+
+#endif // OSGEARTH_LIGHTING_H
diff --git a/src/osgEarth/Lighting.cpp b/src/osgEarth/Lighting.cpp
new file mode 100644
index 0000000..7f6b8ce
--- /dev/null
+++ b/src/osgEarth/Lighting.cpp
@@ -0,0 +1,264 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+* Copyright 2015 Pelican Mapping
+* http://osgearth.org
+*
+* osgEarth is free software; you can redistribute it and/or modify
+* it under the terms of the GNU Lesser General Public License as published by
+* the Free Software Foundation; either version 2 of the License, or
+* (at your option) any later version.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+* IN THE SOFTWARE.
+*
+* You should have received a copy of the GNU Lesser General Public License
+* along with this program.  If not, see <http://www.gnu.org/licenses/>
+*/
+#include <osgEarth/Lighting>
+#include <osg/Program>
+#include <osgUtil/CullVisitor>
+#include <osgDB/ObjectWrapper>
+#include <osgDB/InputStream>
+#include <osgDB/OutputStream>
+#include <osgEarth/StringUtils>
+
+using namespace osgEarth;
+
+#define LC "[Lighting] "
+
+// prefix to use for uniforms.
+#define UPREFIX "osg_"
+
+//............................................................................
+
+GenerateGL3LightingUniforms::GenerateGL3LightingUniforms() :
+osg::NodeVisitor(TRAVERSE_ALL_CHILDREN)
+{
+    setNodeMaskOverride(~0);
+}
+
+void
+GenerateGL3LightingUniforms::apply(osg::Node& node)
+{
+    osg::StateSet* stateset = node.getStateSet();
+    if (stateset)
+    {
+        if (_statesets.find(stateset) == _statesets.end())
+        {
+            const osg::StateSet::RefAttributePair* rap = stateset->getAttributePair(osg::StateAttribute::MATERIAL);
+            if (rap)
+            {
+                osg::Material* material = dynamic_cast<osg::Material*>(rap->first.get());
+                if (material)
+                {
+                    osg::Material* mat = material;
+
+    #if !defined(OSG_GL_FIXED_FUNCTION_AVAILABLE)
+                    // If there's no FFP, we need to replace the Material with a GL3 Material to prevent
+                    // error messages on the console.
+                    if (dynamic_cast<MaterialGL3*>(material) == 0L)
+                    {
+                        mat = new MaterialGL3(*material);
+                        stateset->setAttributeAndModes(mat, rap->second);
+                    }
+    #endif
+
+                    // Install the MaterialCallback so uniforms are updated.
+                    if (!mat->getUpdateCallback())
+                    {
+                        if (stateset->getDataVariance() == osg::Object::DYNAMIC)
+                            mat->setUpdateCallback(new MaterialCallback());
+                        else
+                        {
+                            MaterialCallback mc;
+                            mc.operator()(mat, NULL);
+                        }
+                    }
+                }
+
+                // mark this stateset as visited.
+                _statesets.insert(stateset);
+            }
+        }
+    }
+    traverse(node);
+}
+
+void
+GenerateGL3LightingUniforms::apply(osg::LightSource& lightSource)
+{
+    if (lightSource.getLight())
+    {
+        if (!alreadyInstalled<LightSourceGL3UniformGenerator>(lightSource.getCullCallback()))
+        {
+            lightSource.addCullCallback(new LightSourceGL3UniformGenerator());
+        }
+
+#if !defined(OSG_GL_FIXED_FUNCTION_AVAILABLE)
+        // If there's no FFP, we need to replace the Light with a LightGL3 to prevent
+        // error messages on the console.
+        if (dynamic_cast<LightGL3*>(lightSource.getLight()) == 0L)
+        {
+            lightSource.setLight(new LightGL3(*lightSource.getLight()));
+        }
+#endif
+    }
+
+    apply(static_cast<osg::Node&>(lightSource));
+}
+
+//............................................................................
+
+bool
+LightSourceGL3UniformGenerator::run(osg::Object* obj, osg::Object* data)
+{
+    osg::LightSource* lightSource = dynamic_cast<osg::LightSource*>(obj);
+    osgUtil::CullVisitor* cv = dynamic_cast<osgUtil::CullVisitor*>(data);
+
+    if (cv && lightSource && lightSource->getLight())
+    {
+        osg::Light* light = lightSource->getLight();
+
+        // replace the index with the light number:
+        //std::string prefix = Stringify() << UPREFIX << "LightSource[" << light->getLightNum() << "].";
+        std::string prefix;
+        if (light->getLightNum() < 10)
+        {
+            prefix = UPREFIX "LightSource[#].";
+            prefix[prefix.length() - 3] = (char)('0' + light->getLightNum());
+        }
+        else
+        {
+            prefix = UPREFIX "LightSource[##].";
+            int lightNumTens = light->getLightNum()/10;
+            prefix[prefix.length() - 4] = (char)('0'+lightNumTens);
+            prefix[prefix.length() - 3] = (char)('0' +(light->getLightNum()-(10*lightNumTens)));
+        }
+
+        // Lights are positional state so their location in the scene graph is only important
+        // in terms of model transformation, and not in terms of what gets lit.
+        // Place these uniforms at the root stateset so they affect the entire graph:
+        osg::StateSet* ss = cv->getCurrentRenderStage()->getStateSet();
+        if (ss == 0L)
+            cv->getCurrentRenderStage()->setStateSet(ss = new osg::StateSet());
+
+        ss->getOrCreateUniform(prefix + "ambient", osg::Uniform::FLOAT_VEC4)->set(light->getAmbient());
+        ss->getOrCreateUniform(prefix + "diffuse", osg::Uniform::FLOAT_VEC4)->set(light->getDiffuse());
+        ss->getOrCreateUniform(prefix + "specular", osg::Uniform::FLOAT_VEC4)->set(light->getSpecular());
+
+        // add the positional elements:
+        const osg::Matrix& mvm = *cv->getModelViewMatrix();
+        ss->getOrCreateUniform(prefix + "position", osg::Uniform::FLOAT_VEC4)->set(light->getPosition() * mvm);
+        osg::Vec3 directionLocal = osg::Matrix::transform3x3(light->getDirection(), mvm);
+        directionLocal.normalize();
+        ss->getOrCreateUniform(prefix + "spotDirection", osg::Uniform::FLOAT_VEC3)->set(directionLocal);
+
+        ss->getOrCreateUniform(prefix + "spotExponent", osg::Uniform::FLOAT)->set(light->getSpotExponent());
+        ss->getOrCreateUniform(prefix + "spotCutoff", osg::Uniform::FLOAT)->set(light->getSpotCutoff());
+        ss->getOrCreateUniform(prefix + "spotCosCutoff", osg::Uniform::FLOAT)->set(cosf(light->getSpotCutoff()));
+        ss->getOrCreateUniform(prefix + "constantAttenuation", osg::Uniform::FLOAT)->set(light->getConstantAttenuation());
+        ss->getOrCreateUniform(prefix + "linearAttenuation", osg::Uniform::FLOAT)->set(light->getLinearAttenuation());
+        ss->getOrCreateUniform(prefix + "quadraticAttenuation", osg::Uniform::FLOAT)->set(light->getQuadraticAttenuation());
+
+        LightGL3* lightGL3 = dynamic_cast<LightGL3*>(light);
+        bool enabled = lightGL3 ? lightGL3->getEnabled() : true;
+        ss->getOrCreateUniform(prefix + "enabled", osg::Uniform::BOOL)->set(enabled);
+
+        osg::Uniform* fsu = ss->getOrCreateUniform("oe_lighting_framestamp", osg::Uniform::UNSIGNED_INT);
+        unsigned fs;
+        fsu->get(fs);
+
+        osg::StateSet::DefinePair* numLights = ss->getDefinePair("OE_NUM_LIGHTS");
+
+        if (fs != cv->getFrameStamp()->getFrameNumber())
+        {
+            ss->setDefine("OE_NUM_LIGHTS", "1", osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE);
+            fsu->set(cv->getFrameStamp()->getFrameNumber());
+        }
+        else
+        {
+            int value = 1;
+            if (numLights) {
+                value = ::atoi(numLights->first.c_str()) + 1;
+            }
+            ss->setDefine("OE_NUM_LIGHTS", Stringify() << value, osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE);
+        }
+    }
+    return traverse(obj, data);
+}
+
+//............................................................................
+void MaterialCallback::operator() (osg::StateAttribute* attr, osg::NodeVisitor* nv)
+{
+    static const std::string AMBIENT = UPREFIX "FrontMaterial.ambient";
+    static const std::string DIFFUSE = UPREFIX "FrontMaterial.diffuse";
+    static const std::string SPECULAR = UPREFIX "FrontMaterial.specular";
+    static const std::string EMISSION = UPREFIX "FrontMaterial.emission";
+    static const std::string SHININESS = UPREFIX "FrontMaterial.shininess";
+
+    osg::Material* material = static_cast<osg::Material*>(attr);
+    for (unsigned int i = 0; i < attr->getNumParents(); i++)
+    {
+        osg::StateSet* stateSet = attr->getParent(i);
+
+        stateSet->getOrCreateUniform(AMBIENT, osg::Uniform::FLOAT_VEC4)->set(material->getAmbient(osg::Material::FRONT));
+        stateSet->getOrCreateUniform(DIFFUSE, osg::Uniform::FLOAT_VEC4)->set(material->getDiffuse(osg::Material::FRONT));
+        stateSet->getOrCreateUniform(SPECULAR, osg::Uniform::FLOAT_VEC4)->set(material->getSpecular(osg::Material::FRONT));
+        stateSet->getOrCreateUniform(EMISSION, osg::Uniform::FLOAT_VEC4)->set(material->getEmission(osg::Material::FRONT));
+        stateSet->getOrCreateUniform(SHININESS, osg::Uniform::FLOAT)->set(material->getShininess(osg::Material::FRONT));
+
+        //TODO: back-face materials
+    }
+}
+
+//............................................................................
+
+void
+LightGL3::apply(osg::State& state) const
+{
+#ifdef OSG_GL_FIXED_FUNCTION_AVAILABLE
+    osg::Light::apply(state);
+#endif
+}
+
+// Serializer for LightGL3.
+// The odd namespace name is here because this macro cannot be used more than once
+// in the same namespace in the same cpp file.
+namespace osgEarth_TEMP1
+{
+    REGISTER_OBJECT_WRAPPER(
+        LightGL3,
+        new osgEarth::LightGL3,
+        osgEarth::LightGL3,
+        "osg::Object osg::StateAttribute osg::Light osgEarth::LightGL3")
+    {
+        ADD_BOOL_SERIALIZER(Enabled, true);
+    }
+}
+
+//............................................................................
+
+void
+MaterialGL3::apply(osg::State& state) const
+{
+#ifdef OSG_GL_FIXED_FUNCTION_AVAILABLE
+    osg::Material::apply(state);
+#endif
+}
+
+// Serializer for MaterialGL3.
+// The odd namespace name is here because this macro cannot be used more than once
+// in the same namespace in the same cpp file.
+namespace osgEarth_TEMP2
+{
+    REGISTER_OBJECT_WRAPPER(
+        MaterialGL3,
+        new osgEarth::MaterialGL3,
+        osgEarth::MaterialGL3,
+        "osg::Object osg::StateAttribute osg::Material osgEarth::MaterialGL3") { }
+}
\ No newline at end of file
diff --git a/src/osgEarth/LineFunctor b/src/osgEarth/LineFunctor
index d2204bf..dc56f19 100644
--- a/src/osgEarth/LineFunctor
+++ b/src/osgEarth/LineFunctor
@@ -791,7 +791,6 @@ namespace osgEarth
                 case(GL_LINES):
                 {
                     IndexPointer iptr = indices;
-                    Index first = *iptr;
                     for(GLsizei i=1;i<count;i+=2,iptr+=2)
                     {
                         this->operator()(*iptr,*(iptr+1));
@@ -801,7 +800,6 @@ namespace osgEarth
                 case(GL_LINE_STRIP):
                 {
                     IndexPointer iptr = indices;
-                    Index first = *iptr;
                     for(GLsizei i=1;i<count;i+=1,++iptr)
                     {
                         this->operator()(*iptr,*(iptr+1));
@@ -889,7 +887,6 @@ namespace osgEarth
                 case(GL_LINES):
                 {
                     IndexPointer iptr = indices;
-                    Index first = *iptr;
                     for(GLsizei i=1;i<count;i+=2,iptr+=2)
                     {
                         this->operator()(*iptr,*(iptr+1));
@@ -899,7 +896,6 @@ namespace osgEarth
                 case(GL_LINE_STRIP):
                 {
                     IndexPointer iptr = indices;
-                    Index first = *iptr;
                     for(GLsizei i=1;i<count;i+=1,++iptr)
                     {
                         this->operator()(*iptr,*(iptr+1));
@@ -987,7 +983,6 @@ namespace osgEarth
                 case(GL_LINES):
                 {
                     IndexPointer iptr = indices;
-                    Index first = *iptr;
                     for(GLsizei i=1;i<count;i+=2,iptr+=2)
                     {
                         this->operator()(*iptr,*(iptr+1));
@@ -998,7 +993,6 @@ namespace osgEarth
                 case(GL_POINTS):
                 {
                     IndexPointer iptr = indices;
-                    Index first = *iptr;
                     for(GLsizei i=1;i<count;i+=1,++iptr)
                     {
                         this->operator()(*iptr,*(iptr+1));
@@ -1254,7 +1248,6 @@ namespace osgEarth
                 case(GL_LINES):
                 {
                     IndexPointer iptr = indices;
-                    Index first = *iptr;
                     for(GLsizei i=1;i<count;i+=2,iptr+=2)
                     {
                         T::line(*iptr,*(iptr+1));
@@ -1264,7 +1257,6 @@ namespace osgEarth
                 case(GL_LINE_STRIP):
                 {
                     IndexPointer iptr = indices;
-                    Index first = *iptr;
                     for(GLsizei i=1;i<count;i+=1,++iptr)
                     {
                         T::line(*iptr,*(iptr+1));
@@ -1352,7 +1344,6 @@ namespace osgEarth
                 case(GL_LINES):
                 {
                     IndexPointer iptr = indices;
-                    Index first = *iptr;
                     for(GLsizei i=1;i<count;i+=2,iptr+=2)
                     {
                         T::line(*iptr,*(iptr+1));
@@ -1362,7 +1353,6 @@ namespace osgEarth
                 case(GL_LINE_STRIP):
                 {
                     IndexPointer iptr = indices;
-                    Index first = *iptr;
                     for(GLsizei i=1;i<count;i+=1,++iptr)
                     {
                         T::line(*iptr,*(iptr+1));
@@ -1450,7 +1440,6 @@ namespace osgEarth
                 case(GL_LINES):
                 {
                     IndexPointer iptr = indices;
-                    Index first = *iptr;
                     for(GLsizei i=1;i<count;i+=2,iptr+=2)
                     {
                         T::line(*iptr,*(iptr+1));
@@ -1461,7 +1450,6 @@ namespace osgEarth
                 case(GL_POINTS):
                 {
                     IndexPointer iptr = indices;
-                    Index first = *iptr;
                     for(GLsizei i=1;i<count;i+=1,++iptr)
                     {
                         T::line(*iptr,*(iptr+1));
diff --git a/src/osgEarth/Locators.cpp b/src/osgEarth/Locators.cpp
index fb3e825..fcb780c 100644
--- a/src/osgEarth/Locators.cpp
+++ b/src/osgEarth/Locators.cpp
@@ -29,7 +29,7 @@ _inverseCalculated(false),
 _x0(0.0), _x1(1.0),
 _y0(0.0), _y1(1.0)
 {
-    this->setThreadSafeRefUnref(true);
+    //nop
 }
 
 GeoLocator::GeoLocator( const GeoExtent& dataExtent ) :
@@ -38,7 +38,7 @@ _dataExtent( dataExtent ),
 _x0(0.0), _x1(1.0),
 _y0(0.0), _y1(1.0)
 {
-    this->setThreadSafeRefUnref(true);
+    //nop
 }
 
 GeoLocator::GeoLocator( const osgTerrain::Locator& prototype, const GeoExtent& dataExtent ) :
diff --git a/src/osgEarth/Map b/src/osgEarth/Map
index ee63fb7..63d3a8e 100644
--- a/src/osgEarth/Map
+++ b/src/osgEarth/Map
@@ -25,12 +25,9 @@
 #include <osgEarth/Profile>
 #include <osgEarth/MapOptions>
 #include <osgEarth/MapCallback>
-#include <osgEarth/ImageLayer>
-#include <osgEarth/ElevationLayer>
-#include <osgEarth/ModelLayer>
-#include <osgEarth/MaskLayer>
 #include <osgEarth/Revisioning>
 #include <osgEarth/ThreadingUtils>
+#include <osgEarth/ElevationPool>
 #include <osgDB/Options>
 
 namespace osgEarth
@@ -42,13 +39,16 @@ namespace osgEarth
      * container for all Layer objects (that contain the actual data) and
      * the rendering options.
      */
-    class OSGEARTH_EXPORT Map : public osg::Referenced
+    class OSGEARTH_EXPORT Map : public osg::Object
     {
     public:
-        /**
-         * Constructs a new, empty map.
-         */
-        Map( const MapOptions& options =MapOptions() );
+        META_Object(osgEarth, Map);
+
+        /** Construct a new, empty map. */
+        Map();
+
+        /** Construct a new empty map with options */
+        Map(const MapOptions& options);
 
     public:
         /**
@@ -67,8 +67,7 @@ namespace osgEarth
         const MapOptions& getInitialMapOptions() const { return _initMapOptions; }
 
         /**
-         * Gets the map's master profile. This value may not be available until 
-         * after autoCalculateProfile has been called.
+         * Gets the map's master profile, which defines its SRS and tiling structure.
          */
         const Profile* getProfile() const;
 
@@ -83,103 +82,89 @@ namespace osgEarth
         const SpatialReference* getWorldSRS() const;
 
         /**
-         * Copies references of the map image layers into the output list.
-         * This method is thread safe. It returns the map revision that was
-         * in effect when the data was copied.
+         * Adds a Layer to the map. 
          */
-        Revision getImageLayers( ImageLayerVector& out_layers ) const;
+        void addLayer(Layer* layer);
 
         /**
-         * Gets the number of image layers in the map.
+         * Inserts a Layer at a specific index in the Map.
          */
-        int getNumImageLayers() const;
+        void insertLayer(Layer* layer, unsigned index);
 
         /**
-         * Gets an image layer by name.
+         * Removes a layer from the map.
          */
-        ImageLayer* getImageLayerByName( const std::string& name ) const;
+        void removeLayer(Layer* layer);
 
         /**
-         * Gets an image layer by its unique ID.
+         * Moves a layer to another position in the Map.
          */
-        ImageLayer* getImageLayerByUID( UID layerUID ) const;
+        void moveLayer(Layer* layer, unsigned index);
 
         /**
-         * Gets an image layer at the specified index.
+         * Gets the index of the specified layer, or returns
+         * getNumLayers() if the layer is not found.
          */
-        ImageLayer* getImageLayerAt( int index ) const;
+        unsigned getIndexOfLayer(const Layer* layer) const;
 
         /**
-         * Copies references of the elevation layers into the output list.
+         * Copies references of the map layers into the output list.
          * This method is thread safe. It returns the map revision that was
          * in effect when the data was copied.
          */
-        Revision getElevationLayers( ElevationLayerVector& out_layers ) const;
+        Revision getLayers(LayerVector& out_layers) const;
 
         /**
-         * Gets the number of elevation layers in the map.
+         * Gets the number of layers in the map.
          */
-        int getNumElevationLayers() const;
+        unsigned getNumLayers() const;
 
         /**
-         * Gets an elevation layer by name.
+         * Gets a layer by name.
          */
-        ElevationLayer* getElevationLayerByName( const std::string& name ) const;
+        Layer* getLayerByName(const std::string& name) const;
 
-        /**
-         * Gets an elevation layer by its unique ID.
-         */
-        ElevationLayer* getElevationLayerByUID( UID layerUID ) const;
+        template<typename T> T* getLayerByName(const std::string& name) const;
 
         /**
-         * Gets an elevation layer at the specified index.
+         * Gets an image layer by its unique ID.
          */
-        ElevationLayer* getElevationLayerAt( int index ) const;
+        Layer* getLayerByUID(UID layerUID) const;
 
-        /** 
-         * Copies references of the model layers into the output list.
-         * This method is thread safe. It returns the map revision that was
-         * in effect when the data was copied.
-         */
-        Revision getModelLayers( ModelLayerVector& out_layers ) const;
+        template<typename T> T* getLayerByUID(UID layerUID) const;
 
         /**
-         * Gets the number of model layers in the map.
+         * Gets the layer at the specified index.
          */
-        int getNumModelLayers() const;
+        Layer* getLayerAt(unsigned index) const;
 
-        /**
-         * Gets a model layer by name.
-         */
-        ModelLayer* getModelLayerByName( const std::string& name ) const;
+        template<typename T> T* getLayerAt(unsigned index) const;
 
         /**
-         * Gets a model layer by its unique ID.
+         * Fills the vector with references to all layers of the specified type.
          */
-        ModelLayer* getModelLayerByUID( UID layerUID ) const;
+        template<typename T>
+        Revision getLayers(std::vector< osg::ref_ptr<T> >& output) const;
 
-        /**
-         * Gets the model layer at the specified index.
-         */
-        ModelLayer* getModelLayerAt( int index ) const;
+        template<typename T>
+        Revision getLayers(osg::MixinVector< osg::ref_ptr<T> >& output) const;
 
         /**
-         * Copies references of the mask layers into the output list.
-         * This method is thread safe. It returns the map revision that was
-         * in effect when the data was copied.
+         * Gets the first layer of the specified type. This is useful when you 
+         * know there in only one layer of the type you are looking for.
          */
-        int getTerrainMaskLayers( MaskLayerVector& out_list ) const;
+        template<typename T> T* getLayer() const;
 
         /**
          * Adds a map layer callback to this map. This will be notified whenever layers are
          * added, removed, or re-ordered.
          */
-        void addMapCallback( MapCallback* callback ) const;
+        MapCallback* addMapCallback(MapCallback* callback) const;
 
         /**
          * Removes a callback previously added with addMapCallback.
          */
-        void removeMapCallback( MapCallback* callback );
+        void removeMapCallback(MapCallback* callback) const;
 
         /**
          * Begin a batch-update operation. Call this if you intend to add multiple
@@ -197,71 +182,6 @@ namespace osgEarth
         void endUpdate();
 
         /**
-         * Adds an image layer to the map.
-         */
-        void addImageLayer( ImageLayer* layer );
-
-        /**
-        * Adds an image layer to the map at the specified index.
-        */
-        void insertImageLayer( ImageLayer* layer, unsigned int index );
-
-        /**
-         * Removes an image layer from the map.
-         */
-        void removeImageLayer( ImageLayer* layer );
-
-        /**
-         * Moves (re-orders) an image layer to another index position in its list.
-         */
-        void moveImageLayer( ImageLayer* layer, unsigned int newIndex );
-
-        /**
-         * Adds an elevation layer to the map.
-         */
-        void addElevationLayer( ElevationLayer* layer );
-
-        /**
-         * Removes an elevation layer from the map.
-         */
-        void removeElevationLayer( ElevationLayer* layer );
-
-        /**
-         * Moves (re-orders) an elevation layer to another index position in its list.
-         */
-        void moveElevationLayer( ElevationLayer* layer, unsigned int newIndex );
-
-        /**
-         * Adds a new model layer to the map.
-         */
-        void addModelLayer( ModelLayer* layer );
-
-        /**
-         * Adds a new model layer to the map at the specified index.
-         */
-        void insertModelLayer( ModelLayer* layer, unsigned int index );
-
-        /**
-         * Removes a model layer from the map.
-         */
-        void removeModelLayer( ModelLayer* layer );
-
-        /**
-         * Moves (re-orders) an image layer to another index position in its list.
-         */
-        void moveModelLayer( ModelLayer* layer, unsigned int newIndex );
-
-        /**
-         * Adds a new layer to use as a terrain mask.
-         */
-        void addTerrainMaskLayer( MaskLayer* layer );
-
-        /**
-         * Removed a terrain mask layer that was set with setTerrainMaskLayer().
-         */
-        void removeTerrainMaskLayer( MaskLayer* layer );
-
-        /**
          * Clear all layers from this map.
          */
         void clear();
@@ -272,7 +192,6 @@ namespace osgEarth
          */
         void setLayersFromMap( const Map* map );
 
-    public:
         /**
          * Gets the user-provided options structure stored in this map.
          */
@@ -282,12 +201,12 @@ namespace osgEarth
         /**
          * Sets the readable name of this map.
          */
-        void setName( const std::string& name );
+        void setMapName( const std::string& name );
 
         /** 
          * Gets the readable name of this map.
          */
-        const std::string& getName() const { return _name; }
+        const std::string& getMapName() const { return _name; }
               
         /**
          * Sets the Cache for this Map. Set to NULL for no cache.
@@ -319,60 +238,120 @@ namespace osgEarth
          */    
         bool sync( class MapFrame& frame ) const;
 
-        enum ModelParts {
-            IMAGE_LAYERS     = 1 << 0,
-            ELEVATION_LAYERS = 1 << 1,
-            TERRAIN_LAYERS   = IMAGE_LAYERS | ELEVATION_LAYERS,
-            MODEL_LAYERS     = 1 << 2,
-            MASK_LAYERS      = 1 << 3,
-            MASKED_TERRAIN_LAYERS = TERRAIN_LAYERS | MASK_LAYERS,
-            ENTIRE_MODEL     = 0xff
-        };
-
         /**
          * Gets the database options associated with this map.
          */
         const osgDB::Options* getReadOptions() const { return _readOptions.get(); }
 
+        /**
+         * Gets a version of the map profile without any vertical datum
+         */
         const Profile* getProfileNoVDatum() const { return _profileNoVDatum.get(); }
 
+        /**
+         * Access to an elevation sampling pool tied to this map
+         */
+        ElevationPool* getElevationPool() const;
+
     protected:
 
         virtual ~Map();
 
+        Map(const Map& rhs, const osg::CopyOp& copy) { }
+
     private:
 
         UID _uid;
         MapOptions _mapOptions;
         const MapOptions _initMapOptions;
         std::string _name;
-        ImageLayerVector _imageLayers;
-        ElevationLayerVector _elevationLayers;
-        ModelLayerVector _modelLayers;
-        MaskLayerVector _terrainMaskLayers;
-        MapCallbackList _mapCallbacks;
+        LayerVector _layers;
+        mutable MapCallbackList _mapCallbacks;
         osg::ref_ptr<const osgDB::Options> _globalOptions;
         mutable Threading::ReadWriteMutex _mapDataMutex;
         osg::ref_ptr<const Profile> _profile;
         osg::ref_ptr<const Profile> _profileNoVDatum;
         Revision _dataModelRevision;
         osg::ref_ptr<osgDB::Options> _readOptions;
+        osg::ref_ptr<ElevationPool> _elevationPool;
 
-        struct ElevationLayerCB : public ElevationLayerCallback {
+        struct ElevationLayerCB : public VisibleLayerCallback {
             osg::observer_ptr<Map> _map;
             ElevationLayerCB(Map*);
-            void onVisibleChanged(TerrainLayer* layer);
+            void onVisibleChanged(VisibleLayer* layer);
         };
         osg::ref_ptr<ElevationLayerCB> _elevationLayerCB;
         friend struct ElevationLayerCB;
 
-        void notifyElevationLayerVisibleChanged(TerrainLayer*);
+        void notifyElevationLayerVisibleChanged(VisibleLayer*);
+
 
     private:
+        void ctor();
         void calculateProfile();
 
         friend class MapInfo;
+
+
+    public: // deprecated functions (for backwards compatibility)
+        void addImageLayer(class ImageLayer* layer);
+        void insertImageLayer(class ImageLayer* layer, unsigned index);
+        void removeImageLayer(class ImageLayer* layer);
+        void moveImageLayer(class ImageLayer* layer, unsigned newIndex);
+        void addElevationLayer(class ElevationLayer* layer);
+        void removeElevationLayer(class ElevationLayer* layer);
+        void moveElevationLayer(class ElevationLayer* layer, unsigned newIndex);
+        void addModelLayer(class ModelLayer* layer);
+        void insertModelLayer(class ModelLayer* layer, unsigned index);
+        void removeModelLayer(class ModelLayer* layer);
+        void moveModelLayer(class ModelLayer* layer, unsigned newIndex);
+        void addTerrainMaskLayer(class MaskLayer* layer);
+        void removeTerrainMaskLayer(class MaskLayer* layer);
+
     };
+    
+
+
+
+    // Templated inline methods
+
+    template<typename T> T* Map::getLayerByName(const std::string& name) const {
+        return dynamic_cast<T*>(getLayerByName(name));
+    }
+
+    template<typename T> T* Map::getLayerByUID(UID layerUID) const {
+        return dynamic_cast<T*>(getLayerByUID(layerUID));
+    }
+
+    template<typename T> T* Map::getLayerAt(unsigned index) const {
+        return dynamic_cast<T*>(getLayerAt(index));
+    }
+
+    template<typename T>
+    Revision Map::getLayers(std::vector< osg::ref_ptr<T> >& output) const {
+        for (LayerVector::const_iterator i = _layers.begin(); i != _layers.end(); ++i) {
+            T* obj = dynamic_cast<T*>(i->get());
+            if (obj) output.push_back(obj);
+        }
+        return _dataModelRevision;
+    }
+
+    template<typename T>
+    Revision Map::getLayers(osg::MixinVector< osg::ref_ptr<T> >& output) const {
+        for (LayerVector::const_iterator i = _layers.begin(); i != _layers.end(); ++i) {
+            T* obj = dynamic_cast<T*>(i->get());
+            if (obj) output.push_back(obj);
+        }
+        return _dataModelRevision;
+    }
+
+    template<typename T> T* Map::getLayer() const {
+        for (LayerVector::const_iterator i = _layers.begin(); i != _layers.end(); ++i) {
+            T* obj = dynamic_cast<T*>(i->get());
+            if (obj) return obj;
+        }
+        return 0L;
+    }
 }
 
 #endif // OSGEARTH_MAP_H
diff --git a/src/osgEarth/Map.cpp b/src/osgEarth/Map.cpp
index eef6afa..03a55bc 100644
--- a/src/osgEarth/Map.cpp
+++ b/src/osgEarth/Map.cpp
@@ -23,6 +23,8 @@
 #include <osgEarth/TileSource>
 #include <osgEarth/HeightFieldUtils>
 #include <osgEarth/URI>
+#include <osgEarth/ElevationPool>
+#include <osgEarth/Utils>
 #include <iterator>
 
 using namespace osgEarth;
@@ -38,7 +40,7 @@ _map(map)
 }
 
 void
-Map::ElevationLayerCB::onVisibleChanged(TerrainLayer* layer)
+Map::ElevationLayerCB::onVisibleChanged(VisibleLayer* layer)
 {
     osg::ref_ptr<Map> map;
     if ( _map.lock(map) )
@@ -49,12 +51,28 @@ Map::ElevationLayerCB::onVisibleChanged(TerrainLayer* layer)
 
 //------------------------------------------------------------------------
 
+Map::Map() :
+osg::Object(),
+_dataModelRevision(0)
+{
+    ctor();
+}
+
 Map::Map( const MapOptions& options ) :
-osg::Referenced      ( true ),
+osg::Object(),
 _mapOptions          ( options ),
 _initMapOptions      ( options ),
 _dataModelRevision   ( 0 )
 {
+    ctor();
+}
+
+void
+Map::ctor()
+{
+    // Set the object name.
+    osg::Object::setName("osgEarth.Map");
+
     // Generate a UID.
     _uid = Registry::instance()->createUID();
 
@@ -64,7 +82,7 @@ _dataModelRevision   ( 0 )
         !Registry::instance()->defaultCachePolicy().isSet())
     {
         Registry::instance()->setDefaultCachePolicy( _mapOptions.cachePolicy().get() );
-        OE_INFO << LC 
+        OE_INFO << LC
             << "Setting default cache policy from map ("
             << _mapOptions.cachePolicy()->usageString() << ")" << std::endl;
     }
@@ -73,7 +91,8 @@ _dataModelRevision   ( 0 )
     _readOptions = osg::clone( Registry::instance()->getDefaultOptions() );
 
     // put the CacheSettings object in there. We will propogate this throughout
-    // the data model and the renderer.
+    // the data model and the renderer. These will be stored in the readOptions
+    // (and ONLY there)
     CacheSettings* cacheSettings = new CacheSettings();
 
     // Set up a cache if there's one in the options:
@@ -99,18 +118,32 @@ _dataModelRevision   ( 0 )
     // we do our own caching
     _readOptions->setObjectCacheHint( osgDB::Options::CACHE_NONE );
 
+    // encode this map in the read options.
+    OptionsData<const Map>::set(_readOptions.get(), "osgEarth.Map", this);
+
     // set up a callback that the Map will use to detect Elevation Layer
     // visibility changes
     _elevationLayerCB = new ElevationLayerCB(this);
+
+    // elevation sampling
+    _elevationPool = new ElevationPool();
+    _elevationPool->setMap( this );
 }
 
 Map::~Map()
 {
-    OE_DEBUG << "~Map" << std::endl;
+    OE_DEBUG << LC << "~Map" << std::endl;
+    clear();
+}
+
+ElevationPool*
+Map::getElevationPool() const
+{
+    return _elevationPool.get();
 }
 
 void
-Map::notifyElevationLayerVisibleChanged(TerrainLayer* layer)
+Map::notifyElevationLayerVisibleChanged(VisibleLayer* layer)
 {
     // bump the revision safely:
     Revision newRevision;
@@ -119,7 +152,10 @@ Map::notifyElevationLayerVisibleChanged(TerrainLayer* layer)
         newRevision = ++_dataModelRevision;
     }
 
-    // a separate block b/c we don't need the mutex   
+    // reinitialize the elevation pool:
+    _elevationPool->clear();
+
+    // a separate block b/c we don't need the mutex
     for( MapCallbackList::iterator i = _mapCallbacks.begin(); i != _mapCallbacks.end(); i++ )
     {
         i->get()->onMapModelChanged( MapModelChange(
@@ -130,9 +166,10 @@ Map::notifyElevationLayerVisibleChanged(TerrainLayer* layer)
 bool
 Map::isGeocentric() const
 {
-    return 
-        _mapOptions.coordSysType() == MapOptions::CSTYPE_GEOCENTRIC ||
-        _mapOptions.coordSysType() == MapOptions::CSTYPE_GEOCENTRIC_CUBE;
+    return
+        _mapOptions.coordSysType().isSet() ? _mapOptions.coordSysType() == MapOptions::CSTYPE_GEOCENTRIC :
+        getSRS() ? getSRS()->isGeographic() :
+        true;
 }
 
 const osgDB::Options*
@@ -147,168 +184,8 @@ Map::setGlobalOptions( const osgDB::Options* options )
     _globalOptions = options;
 }
 
-Revision
-Map::getImageLayers( ImageLayerVector& out_list ) const
-{
-    out_list.reserve( _imageLayers.size() );
-
-    Threading::ScopedReadLock lock( const_cast<Map*>(this)->_mapDataMutex );
-    for( ImageLayerVector::const_iterator i = _imageLayers.begin(); i != _imageLayers.end(); ++i )
-        out_list.push_back( i->get() );
-
-    return _dataModelRevision;
-}
-
-int
-Map::getNumImageLayers() const
-{
-    Threading::ScopedReadLock lock( const_cast<Map*>(this)->_mapDataMutex );
-    return _imageLayers.size();
-}
-
-ImageLayer*
-Map::getImageLayerByName( const std::string& name ) const
-{
-    Threading::ScopedReadLock( const_cast<Map*>(this)->_mapDataMutex );
-    for( ImageLayerVector::const_iterator i = _imageLayers.begin(); i != _imageLayers.end(); ++i )
-        if ( i->get()->getName() == name )
-            return i->get();
-    return 0L;
-}
-
-ImageLayer*
-Map::getImageLayerByUID( UID layerUID ) const
-{
-    Threading::ScopedReadLock( const_cast<Map*>(this)->_mapDataMutex );
-    for( ImageLayerVector::const_iterator i = _imageLayers.begin(); i != _imageLayers.end(); ++i )
-        if ( i->get()->getUID() == layerUID )
-            return i->get();
-    return 0L;
-}
-
-ImageLayer*
-Map::getImageLayerAt( int index ) const
-{
-    Threading::ScopedReadLock( const_cast<Map*>(this)->_mapDataMutex );
-    if ( index >= 0 && index < (int)_imageLayers.size() )
-        return _imageLayers[index].get();
-    else
-        return 0L;
-}
-
-Revision
-Map::getElevationLayers( ElevationLayerVector& out_list ) const
-{
-    out_list.reserve( _elevationLayers.size() );
-
-    Threading::ScopedReadLock lock( const_cast<Map*>(this)->_mapDataMutex );
-    for( ElevationLayerVector::const_iterator i = _elevationLayers.begin(); i != _elevationLayers.end(); ++i )
-        out_list.push_back( i->get() );
-
-    return _dataModelRevision;
-}
-
-int
-Map::getNumElevationLayers() const
-{
-    Threading::ScopedReadLock lock( const_cast<Map*>(this)->_mapDataMutex );
-    return _elevationLayers.size();
-}
-
-ElevationLayer*
-Map::getElevationLayerByName( const std::string& name ) const
-{
-    Threading::ScopedReadLock( const_cast<Map*>(this)->_mapDataMutex );
-    for( ElevationLayerVector::const_iterator i = _elevationLayers.begin(); i != _elevationLayers.end(); ++i )
-        if ( i->get()->getName() == name )
-            return i->get();
-    return 0L;
-}
-
-ElevationLayer*
-Map::getElevationLayerByUID( UID layerUID ) const
-{
-    Threading::ScopedReadLock( const_cast<Map*>(this)->_mapDataMutex );
-    for( ElevationLayerVector::const_iterator i = _elevationLayers.begin(); i != _elevationLayers.end(); ++i )
-        if ( i->get()->getUID() == layerUID )
-            return i->get();
-    return 0L;
-}
-
-ElevationLayer*
-Map::getElevationLayerAt( int index ) const
-{
-    Threading::ScopedReadLock( const_cast<Map*>(this)->_mapDataMutex );
-    if ( index >= 0 && index < (int)_elevationLayers.size() )
-        return _elevationLayers[index].get();
-    else
-        return 0L;
-}
-
-Revision
-Map::getModelLayers( ModelLayerVector& out_list ) const
-{
-    out_list.reserve( _modelLayers.size() );
-
-    Threading::ScopedReadLock lock( const_cast<Map*>(this)->_mapDataMutex );
-    for( ModelLayerVector::const_iterator i = _modelLayers.begin(); i != _modelLayers.end(); ++i )
-        out_list.push_back( i->get() );
-
-    return _dataModelRevision;
-}
-
-ModelLayer*
-Map::getModelLayerByName( const std::string& name ) const
-{
-    Threading::ScopedReadLock( const_cast<Map*>(this)->_mapDataMutex );
-    for( ModelLayerVector::const_iterator i = _modelLayers.begin(); i != _modelLayers.end(); ++i )
-        if ( i->get()->getName() == name )
-            return i->get();
-    return 0L;
-}
-
-ModelLayer*
-Map::getModelLayerByUID( UID layerUID ) const
-{
-    Threading::ScopedReadLock( const_cast<Map*>(this)->_mapDataMutex );
-    for( ModelLayerVector::const_iterator i = _modelLayers.begin(); i != _modelLayers.end(); ++i )
-        if ( i->get()->getUID() == layerUID )
-            return i->get();
-    return 0L;
-}
-
-
-ModelLayer*
-Map::getModelLayerAt( int index ) const
-{
-    Threading::ScopedReadLock( const_cast<Map*>(this)->_mapDataMutex );
-    if ( index >= 0 && index < (int)_modelLayers.size() )
-        return _modelLayers[index].get();
-    else
-        return 0L;
-}
-
-int
-Map::getNumModelLayers() const
-{
-    Threading::ScopedReadLock lock( const_cast<Map*>(this)->_mapDataMutex );
-    return _modelLayers.size();
-}
-
-int
-Map::getTerrainMaskLayers( MaskLayerVector& out_list ) const
-{
-    out_list.reserve( _terrainMaskLayers.size() );
-
-    Threading::ScopedReadLock lock( const_cast<Map*>(this)->_mapDataMutex );
-    for( MaskLayerVector::const_iterator i = _terrainMaskLayers.begin(); i != _terrainMaskLayers.end(); ++i )
-        out_list.push_back( i->get() );
-
-    return _dataModelRevision;
-}
-
 void
-Map::setName( const std::string& name ) {
+Map::setMapName( const std::string& name ) {
     _name = name;
 }
 
@@ -342,15 +219,16 @@ Map::setCache(Cache* cache)
         cacheSettings->setCache(cache);
 }
 
-void 
-Map::addMapCallback( MapCallback* cb ) const
+MapCallback*
+Map::addMapCallback(MapCallback* cb) const
 {
     if ( cb )
-        const_cast<Map*>(this)->_mapCallbacks.push_back( cb );
+        _mapCallbacks.push_back( cb );
+    return cb;
 }
 
-void 
-Map::removeMapCallback( MapCallback* cb )
+void
+Map::removeMapCallback(MapCallback* cb) const
 {
     MapCallbackList::iterator i = std::find( _mapCallbacks.begin(), _mapCallbacks.end(), cb);
     if (i != _mapCallbacks.end())
@@ -374,7 +252,7 @@ void
 Map::endUpdate()
 {
     MapModelChange msg( MapModelChange::END_BATCH_UPDATE, _dataModelRevision );
- 
+
     for( MapCallbackList::iterator i = _mapCallbacks.begin(); i != _mapCallbacks.end(); i++ )
     {
         i->get()->onMapModelChanged( msg );
@@ -382,200 +260,173 @@ Map::endUpdate()
 }
 
 void
-Map::addImageLayer( ImageLayer* layer )
+Map::addLayer(Layer* layer)
 {
     osgEarth::Registry::instance()->clearBlacklist();
-    unsigned int index = -1;
     if ( layer )
     {
-        // Set the DB options for the map from the layer, including the cache policy.
-        layer->setReadOptions( _readOptions.get() );
-
-        // Tell the layer the map profile, if possible:
-        if ( _profile.valid() )
+        if (layer->getEnabled())
         {
-            layer->setTargetProfileHint( _profile.get() );
-        }
+            // Pass along the Read Options (including the cache settings, etc.) to the layer:
+            layer->setReadOptions(_readOptions.get());
+            
+            // If this is a terrain layer, tell it about the Map profile.
+            TerrainLayer* terrainLayer = dynamic_cast<TerrainLayer*>(layer);
+            if (terrainLayer && _profile.valid())
+            {
+                terrainLayer->setTargetProfileHint( _profile.get() );
+            }            
+
+            // Attempt to open the layer. Don't check the status here.
+            layer->open();
+
+            // If this is an elevation layer, install a callback so we know when
+            // it's visibility changes:
+            ElevationLayer* elevationLayer = dynamic_cast<ElevationLayer*>(layer);
+            if (elevationLayer)
+            {
+                elevationLayer->addCallback(_elevationLayerCB.get());
 
-        // open the layer:
-        layer->open();
+                // invalidate the elevation pool
+                getElevationPool()->clear();
+            }
+        }
 
         int newRevision;
+        unsigned index = -1;
 
         // Add the layer to our stack.
         {
             Threading::ScopedWriteLock lock( _mapDataMutex );
 
-            _imageLayers.push_back( layer );
-            index = _imageLayers.size() - 1;
+            _layers.push_back( layer );
+            index = _layers.size() - 1;
             newRevision = ++_dataModelRevision;
         }
 
-        // a separate block b/c we don't need the mutex   
+        // tell the layer it was just added.
+        layer->addedToMap(this);
+
+        // a separate block b/c we don't need the mutex
         for( MapCallbackList::iterator i = _mapCallbacks.begin(); i != _mapCallbacks.end(); i++ )
         {
-            i->get()->onMapModelChanged( MapModelChange(
-                MapModelChange::ADD_IMAGE_LAYER, newRevision, layer, index) );
+            i->get()->onMapModelChanged(MapModelChange(
+                MapModelChange::ADD_LAYER, newRevision, layer, index));
         }
     }
 }
 
-
 void
-Map::insertImageLayer( ImageLayer* layer, unsigned int index )
+Map::insertLayer(Layer* layer, unsigned index)
 {
     osgEarth::Registry::instance()->clearBlacklist();
     if ( layer )
     {
-        //Set options for the map from the layer
-        layer->setReadOptions( _readOptions.get() );
-
-        // Tell the layer the map profile, if possible:
-        if ( _profile.valid() )
+        if (layer->getEnabled())
         {
-            layer->setTargetProfileHint( _profile.get() );
-        }
-
-        // open the layer:
-        layer->open();
-
-        int newRevision;
+            // Pass along the Read Options (including the cache settings, etc.) to the layer:
+            layer->setReadOptions(_readOptions.get());
+            
+            // If this is a terrain layer, tell it about the Map profile.
+            TerrainLayer* terrainLayer = dynamic_cast<TerrainLayer*>(layer);
+            if (terrainLayer && _profile.valid())
+            {
+                terrainLayer->setTargetProfileHint( _profile.get() );
+            }            
 
-        // Add the layer to our stack.
-        {
-            Threading::ScopedWriteLock lock( _mapDataMutex );
+            // Attempt to open the layer. Don't check the status here.
+            layer->open();
 
-            if (index >= _imageLayers.size())
-                _imageLayers.push_back(layer);
-            else
-                _imageLayers.insert( _imageLayers.begin() + index, layer );
+            // If this is an elevation layer, install a callback so we know when
+            // it's visibility changes:
+            ElevationLayer* elevationLayer = dynamic_cast<ElevationLayer*>(layer);
+            if (elevationLayer)
+            {
+                elevationLayer->addCallback(_elevationLayerCB.get());
 
-            newRevision = ++_dataModelRevision;
+                // invalidate the elevation pool
+                getElevationPool()->clear();
+            }
         }
 
-        // a separate block b/c we don't need the mutex   
-        for( MapCallbackList::iterator i = _mapCallbacks.begin(); i != _mapCallbacks.end(); i++ )
-        {
-            i->get()->onMapModelChanged( MapModelChange(
-                MapModelChange::ADD_IMAGE_LAYER, newRevision, layer, index) );
-        }   
-    } 
-}
-
-void
-Map::addElevationLayer( ElevationLayer* layer )
-{
-    osgEarth::Registry::instance()->clearBlacklist();
-    unsigned int index = -1;
-    if ( layer )
-    {
-        //Set options for the map from the layer
-        layer->setReadOptions( _readOptions.get() );
-
-        // Tell the layer the map profile, if possible:
-        if ( _profile.valid() )
-            layer->setTargetProfileHint( _profile.get() );
-
-        layer->open();
-
         int newRevision;
 
         // Add the layer to our stack.
         {
             Threading::ScopedWriteLock lock( _mapDataMutex );
 
-            _elevationLayers.push_back( layer );
-            index = _elevationLayers.size() - 1;
+            if (index >= _layers.size())
+                _layers.push_back(layer);
+            else
+                _layers.insert( _layers.begin() + index, layer );
+
             newRevision = ++_dataModelRevision;
         }
 
-        // listen for changes in the layer.
-        layer->addCallback( _elevationLayerCB.get() );
+        // tell the layer it was just added.
+        layer->addedToMap(this);
 
-        // a separate block b/c we don't need the mutex   
+        // a separate block b/c we don't need the mutex
         for( MapCallbackList::iterator i = _mapCallbacks.begin(); i != _mapCallbacks.end(); i++ )
         {
-            i->get()->onMapModelChanged( MapModelChange(
-                MapModelChange::ADD_ELEVATION_LAYER, newRevision, layer, index) );
+            //i->get()->onMapModelChanged( MapModelChange(
+            //    MapModelChange::ADD_IMAGE_LAYER, newRevision, layer, index) );
+
+            i->get()->onMapModelChanged(MapModelChange(
+                MapModelChange::ADD_LAYER, newRevision, layer, index));
         }
     }
 }
 
-void 
-Map::removeImageLayer( ImageLayer* layer )
+void
+Map::removeLayer(Layer* layer)
 {
     osgEarth::Registry::instance()->clearBlacklist();
     unsigned int index = -1;
 
-    osg::ref_ptr<ImageLayer> layerToRemove = layer;
+    osg::ref_ptr<Layer> layerToRemove = layer;
     Revision newRevision;
 
     if ( layerToRemove.get() )
     {
         Threading::ScopedWriteLock lock( _mapDataMutex );
         index = 0;
-        for( ImageLayerVector::iterator i = _imageLayers.begin(); i != _imageLayers.end(); i++, index++ )
+        for( LayerVector::iterator i = _layers.begin(); i != _layers.end(); i++, index++ )
         {
             if ( i->get() == layerToRemove.get() )
             {
-                _imageLayers.erase( i );
+                _layers.erase( i );
                 newRevision = ++_dataModelRevision;
                 break;
             }
         }
-    }
 
-    // a separate block b/c we don't need the mutex
-    if ( newRevision >= 0 ) // layerToRemove.get() )
-    {
-        for( MapCallbackList::iterator i = _mapCallbacks.begin(); i != _mapCallbacks.end(); i++ )
-        {
-            i->get()->onMapModelChanged( MapModelChange(
-                MapModelChange::REMOVE_IMAGE_LAYER, newRevision, layerToRemove.get(), index) );
-            //i->get()->onImageLayerRemoved( layerToRemove.get(), index, newRevision );
-        }
+        // tell the layer it was just removed.
+        layerToRemove->removedFromMap(this);
     }
-}
 
-void 
-Map::removeElevationLayer( ElevationLayer* layer )
-{
-    osgEarth::Registry::instance()->clearBlacklist();
-    unsigned int index = -1;
-
-    osg::ref_ptr<ElevationLayer> layerToRemove = layer;
-    Revision newRevision;
-
-    if ( layerToRemove.get() )
+    ElevationLayer* elevationLayer = dynamic_cast<ElevationLayer*>(layerToRemove.get());
+    if (elevationLayer)
     {
-        Threading::ScopedWriteLock lock( _mapDataMutex );
-        index = 0;
-        for( ElevationLayerVector::iterator i = _elevationLayers.begin(); i != _elevationLayers.end(); i++, index++ )
-        {
-            if ( i->get() == layerToRemove.get() )
-            {
-                _elevationLayers.erase( i );
-                newRevision = ++_dataModelRevision;
-                break;
-            }
-        }
+        elevationLayer->removeCallback(_elevationLayerCB.get());
 
-        layerToRemove->removeCallback( _elevationLayerCB.get() );
+        // invalidate the pool
+        getElevationPool()->clear();
     }
 
     // a separate block b/c we don't need the mutex
-    if ( newRevision >= 0 ) //layerToRemove.get() )
+    if ( newRevision >= 0 ) // layerToRemove.get() )
     {
         for( MapCallbackList::iterator i = _mapCallbacks.begin(); i != _mapCallbacks.end(); i++ )
         {
             i->get()->onMapModelChanged( MapModelChange(
-                MapModelChange::REMOVE_ELEVATION_LAYER, newRevision, layerToRemove.get(), index) );
+                MapModelChange::REMOVE_LAYER, newRevision, layerToRemove.get(), index) );
         }
     }
 }
 
 void
-Map::moveImageLayer( ImageLayer* layer, unsigned int newIndex )
+Map::moveLayer(Layer* layer, unsigned newIndex)
 {
     unsigned int oldIndex = 0;
     unsigned int actualIndex = 0;
@@ -586,11 +437,11 @@ Map::moveImageLayer( ImageLayer* layer, unsigned int newIndex )
         Threading::ScopedWriteLock lock( _mapDataMutex );
 
         // preserve the layer with a ref:
-        osg::ref_ptr<ImageLayer> layerToMove = layer;
+        osg::ref_ptr<Layer> layerToMove = layer;
 
         // find it:
-        ImageLayerVector::iterator i_oldIndex = _imageLayers.end();
-        for( ImageLayerVector::iterator i = _imageLayers.begin(); i != _imageLayers.end(); i++, actualIndex++ )
+        LayerVector::iterator i_oldIndex = _layers.end();
+        for( LayerVector::iterator i = _layers.begin(); i != _layers.end(); i++, actualIndex++ )
         {
             if ( i->get() == layer )
             {
@@ -600,61 +451,20 @@ Map::moveImageLayer( ImageLayer* layer, unsigned int newIndex )
             }
         }
 
-        if ( i_oldIndex == _imageLayers.end() )
+        if ( i_oldIndex == _layers.end() )
             return; // layer not found in list
 
         // erase the old one and insert the new one.
-        _imageLayers.erase( i_oldIndex );
-        _imageLayers.insert( _imageLayers.begin() + newIndex, layerToMove.get() );
+        _layers.erase( i_oldIndex );
+        _layers.insert( _layers.begin() + newIndex, layerToMove.get() );
 
         newRevision = ++_dataModelRevision;
     }
 
-    // a separate block b/c we don't need the mutex
-    if ( layer )
+    // if this is an elevation layer, invalidate the elevation pool
+    if (dynamic_cast<ElevationLayer*>(layer))
     {
-        for( MapCallbackList::iterator i = _mapCallbacks.begin(); i != _mapCallbacks.end(); i++ )
-        {
-            i->get()->onMapModelChanged( MapModelChange(
-                MapModelChange::MOVE_IMAGE_LAYER, newRevision, layer, oldIndex, newIndex) );
-        }
-    }
-}
-
-void
-Map::moveElevationLayer( ElevationLayer* layer, unsigned int newIndex )
-{
-    unsigned int oldIndex = 0;
-    unsigned int actualIndex = 0;
-    Revision newRevision;
-
-    if ( layer )
-    {
-        Threading::ScopedWriteLock lock( _mapDataMutex );
-
-        // preserve the layer with a ref:
-        osg::ref_ptr<ElevationLayer> layerToMove = layer;
-
-        // find it:
-        ElevationLayerVector::iterator i_oldIndex = _elevationLayers.end();
-        for( ElevationLayerVector::iterator i = _elevationLayers.begin(); i != _elevationLayers.end(); i++, actualIndex++ )
-        {
-            if ( i->get() == layer )
-            {
-                i_oldIndex = i;
-                oldIndex = actualIndex;
-                break;
-            }
-        }
-
-        if ( i_oldIndex == _elevationLayers.end() )
-            return; // layer not found in list
-
-        // erase the old one and insert the new one.
-        _elevationLayers.erase( i_oldIndex );
-        _elevationLayers.insert( _elevationLayers.begin() + newIndex, layerToMove.get() );
-
-        newRevision = ++_dataModelRevision;
+        getElevationPool()->clear();
     }
 
     // a separate block b/c we don't need the mutex
@@ -663,255 +473,115 @@ Map::moveElevationLayer( ElevationLayer* layer, unsigned int newIndex )
         for( MapCallbackList::iterator i = _mapCallbacks.begin(); i != _mapCallbacks.end(); i++ )
         {
             i->get()->onMapModelChanged( MapModelChange(
-                MapModelChange::MOVE_ELEVATION_LAYER, newRevision, layer, oldIndex, newIndex) );
+                MapModelChange::MOVE_LAYER, newRevision, layer, oldIndex, newIndex) );
         }
     }
 }
 
-void
-Map::addModelLayer( ModelLayer* layer )
+Revision
+Map::getLayers(LayerVector& out_list) const
 {
-    if ( layer )
-    {
-        unsigned int index = -1;
+    out_list.reserve( _layers.size() );
 
-        Revision newRevision;
-        {
-            Threading::ScopedWriteLock lock( _mapDataMutex );
-            _modelLayers.push_back( layer );
-            index = _modelLayers.size() - 1;
-            newRevision = ++_dataModelRevision;
-        }
-
-        // initialize the model layer
-        layer->setReadOptions(_readOptions.get());
-
-        // open it and check the status
-        layer->open();
+    Threading::ScopedReadLock lock( const_cast<Map*>(this)->_mapDataMutex );
+    for( LayerVector::const_iterator i = _layers.begin(); i != _layers.end(); ++i )
+        out_list.push_back( i->get() );
 
-        // a seprate block b/c we don't need the mutex
-        for( MapCallbackList::iterator i = _mapCallbacks.begin(); i != _mapCallbacks.end(); i++ )
-        {
-            i->get()->onMapModelChanged( MapModelChange(
-                MapModelChange::ADD_MODEL_LAYER, newRevision, layer, index ) );
-        }
-    }
+    return _dataModelRevision;
 }
 
-void
-Map::insertModelLayer( ModelLayer* layer, unsigned int index )
+unsigned
+Map::getNumLayers() const
 {
-    if ( layer )
-    {
-        Revision newRevision;
-        {
-            Threading::ScopedWriteLock lock( _mapDataMutex );
-            _modelLayers.insert( _modelLayers.begin() + index, layer );
-            newRevision = ++_dataModelRevision;
-        }
-
-        // initialize the model layer
-        layer->setReadOptions(_readOptions.get());
-
-        // open and get the status
-        layer->open();
-
-        // a seprate block b/c we don't need the mutex
-        for( MapCallbackList::iterator i = _mapCallbacks.begin(); i != _mapCallbacks.end(); i++ )
-        {
-            i->get()->onMapModelChanged( MapModelChange(
-                MapModelChange::ADD_MODEL_LAYER, newRevision, layer, index) );
-        }
-    }
+    Threading::ScopedReadLock lock( const_cast<Map*>(this)->_mapDataMutex );
+    return _layers.size();
 }
 
-void
-Map::removeModelLayer( ModelLayer* layer )
+Layer*
+Map::getLayerByName(const std::string& name) const
 {
-    if ( layer )
-    {
-        //Take a reference to the layer since we will be deleting it
-        osg::ref_ptr< ModelLayer > layerRef = layer;
-
-        Revision newRevision;
-        {
-            Threading::ScopedWriteLock lock( _mapDataMutex );
-            for( ModelLayerVector::iterator i = _modelLayers.begin(); i != _modelLayers.end(); ++i )
-            {
-                if ( i->get() == layer )
-                {
-                    _modelLayers.erase( i );
-                    newRevision = ++_dataModelRevision;
-                    break;
-                }
-            }
-        }
-
-        for( MapCallbackList::iterator i = _mapCallbacks.begin(); i != _mapCallbacks.end(); ++i )
-        {
-            i->get()->onMapModelChanged( MapModelChange(
-                MapModelChange::REMOVE_MODEL_LAYER, newRevision, layerRef.get()) );
-        }
-    }
+    Threading::ScopedReadLock( const_cast<Map*>(this)->_mapDataMutex );
+    for(LayerVector::const_iterator i = _layers.begin(); i != _layers.end(); ++i)
+        if ( i->get()->getName() == name )
+            return i->get();
+    return 0L;
 }
 
-void
-Map::moveModelLayer( ModelLayer* layer, unsigned int newIndex )
+Layer*
+Map::getLayerByUID(UID layerUID) const
 {
-    unsigned int oldIndex = 0;
-    unsigned int actualIndex = 0;
-    Revision newRevision;
-
-    if ( layer )
-    {
-        Threading::ScopedWriteLock lock( _mapDataMutex );
-
-        // preserve the layer with a ref:
-        osg::ref_ptr<ModelLayer> layerToMove = layer;
-
-        // find it:
-        ModelLayerVector::iterator i_oldIndex = _modelLayers.end();
-        for( ModelLayerVector::iterator i = _modelLayers.begin(); i != _modelLayers.end(); i++, actualIndex++ )
-        {
-            if ( i->get() == layer )
-            {
-                i_oldIndex = i;
-                oldIndex = actualIndex;
-                break;
-            }
-        }
-
-        if ( i_oldIndex == _modelLayers.end() )
-            return; // layer not found in list
-
-        // erase the old one and insert the new one.
-        _modelLayers.erase( i_oldIndex );
-        _modelLayers.insert( _modelLayers.begin() + newIndex, layerToMove.get() );
-
-        newRevision = ++_dataModelRevision;
-    }
-
-    // a separate block b/c we don't need the mutex
-    if ( layer )
-    {
-        for( MapCallbackList::iterator i = _mapCallbacks.begin(); i != _mapCallbacks.end(); i++ )
-        {
-            i->get()->onMapModelChanged( MapModelChange(
-                MapModelChange::MOVE_MODEL_LAYER, newRevision, layer, oldIndex, newIndex) );
-        }
-    }
+    Threading::ScopedReadLock( const_cast<Map*>(this)->_mapDataMutex );
+    for( LayerVector::const_iterator i = _layers.begin(); i != _layers.end(); ++i )
+        if ( i->get()->getUID() == layerUID )
+            return i->get();
+    return 0L;
 }
 
-void
-Map::addTerrainMaskLayer( MaskLayer* layer )
+Layer*
+Map::getLayerAt(unsigned index) const
 {
-    if ( layer )
-    {
-        Revision newRevision;
-        {
-            Threading::ScopedWriteLock lock( _mapDataMutex );
-            _terrainMaskLayers.push_back(layer);
-            newRevision = ++_dataModelRevision;
-        }
-
-        layer->setReadOptions(_readOptions.get());
-
-        layer->open();
-
-        // a separate block b/c we don't need the mutex   
-        for( MapCallbackList::iterator i = _mapCallbacks.begin(); i != _mapCallbacks.end(); i++ )
-        {
-            i->get()->onMapModelChanged( MapModelChange(
-                MapModelChange::ADD_MASK_LAYER, newRevision, layer) );
-        }
-    }
+    Threading::ScopedReadLock( const_cast<Map*>(this)->_mapDataMutex );
+    if ( index >= 0 && index < (int)_layers.size() )
+        return _layers[index].get();
+    else
+        return 0L;
 }
 
-void
-Map::removeTerrainMaskLayer( MaskLayer* layer )
+unsigned
+Map::getIndexOfLayer(const Layer* layer) const
 {
-    if ( layer )
+    Threading::ScopedReadLock( const_cast<Map*>(this)->_mapDataMutex );
+    unsigned index = 0;
+    for (; index < _layers.size(); ++index)
     {
-        //Take a reference to the layer since we will be deleting it
-        osg::ref_ptr< MaskLayer > layerRef = layer;
-        Revision newRevision;
-        {
-            Threading::ScopedWriteLock lock( _mapDataMutex );
-            for( MaskLayerVector::iterator i = _terrainMaskLayers.begin(); i != _terrainMaskLayers.end(); ++i )
-            {
-                if ( i->get() == layer )
-                {
-                    _terrainMaskLayers.erase( i );
-                    newRevision = ++_dataModelRevision;
-                    break;
-                }
-            }
-        }
-        
-        // a separate block b/c we don't need the mutex   
-        for( MapCallbackList::iterator i = _mapCallbacks.begin(); i != _mapCallbacks.end(); i++ )
-        {
-            i->get()->onMapModelChanged( MapModelChange(
-                MapModelChange::REMOVE_MASK_LAYER, newRevision, layerRef.get()) );
-        }   
+        if (_layers[index] == layer)
+            break;
     }
+    return index;
 }
 
 
 void
 Map::clear()
 {
-    ImageLayerVector     imageLayersRemoved;
-    ElevationLayerVector elevLayersRemoved;
-    ModelLayerVector     modelLayersRemoved;
-    MaskLayerVector      maskLayersRemoved;
-
+    LayerVector layersRemoved;
     Revision newRevision;
     {
         Threading::ScopedWriteLock lock( _mapDataMutex );
 
-        imageLayersRemoved.swap( _imageLayers );
-        elevLayersRemoved.swap ( _elevationLayers );
-        modelLayersRemoved.swap( _modelLayers );
+        layersRemoved.swap( _layers );
 
         // calculate a new revision.
         newRevision = ++_dataModelRevision;
     }
-    
-    // a separate block b/c we don't need the mutex   
+
+    // a separate block b/c we don't need the mutex
     for( MapCallbackList::iterator i = _mapCallbacks.begin(); i != _mapCallbacks.end(); i++ )
     {
-        for( ImageLayerVector::iterator k = imageLayersRemoved.begin(); k != imageLayersRemoved.end(); ++k )
-            i->get()->onMapModelChanged( MapModelChange(MapModelChange::REMOVE_IMAGE_LAYER, newRevision, k->get()) );
-        for( ElevationLayerVector::iterator k = elevLayersRemoved.begin(); k != elevLayersRemoved.end(); ++k )
-            i->get()->onMapModelChanged( MapModelChange(MapModelChange::REMOVE_ELEVATION_LAYER, newRevision, k->get()) );
-        for( ModelLayerVector::iterator k = modelLayersRemoved.begin(); k != modelLayersRemoved.end(); ++k )
-            i->get()->onMapModelChanged( MapModelChange(MapModelChange::REMOVE_MODEL_LAYER, newRevision, k->get()) );
+        for(LayerVector::iterator layer = layersRemoved.begin();
+            layer != layersRemoved.end();
+            ++layer)
+        {
+            i->get()->onMapModelChanged(MapModelChange(MapModelChange::REMOVE_LAYER, newRevision, layer->get()));
+        }
     }
+
+    // Invalidate the elevation pool.
+    getElevationPool()->clear();
 }
 
 
 void
-Map::setLayersFromMap( const Map* map )
+Map::setLayersFromMap(const Map* map)
 {
     this->clear();
 
     if ( map )
     {
-        ImageLayerVector newImages;
-        map->getImageLayers( newImages );
-        for( ImageLayerVector::iterator i = newImages.begin(); i != newImages.end(); ++i )
-            addImageLayer( i->get() );
-
-        ElevationLayerVector newElev;
-        map->getElevationLayers( newElev );
-        for( ElevationLayerVector::iterator i = newElev.begin(); i != newElev.end(); ++i )
-            addElevationLayer( i->get() );
-
-        ModelLayerVector newModels;
-        map->getModelLayers( newModels );
-        for( ModelLayerVector::iterator i = newModels.begin(); i != newModels.end(); ++i )
-            addModelLayer( i->get() );
+        LayerVector layers;
+        map->getLayers(layers);
+        for (LayerVector::iterator i = layers.begin(); i != layers.end(); ++i)
+            addLayer(i->get());
     }
 }
 
@@ -919,161 +589,71 @@ Map::setLayersFromMap( const Map* map )
 void
 Map::calculateProfile()
 {
+    // collect the terrain layers; we will need them later
+    TerrainLayerVector layers;
+    getLayers(layers);
+
+    // Figure out the map profile:
     if ( !_profile.valid() )
     {
-        osg::ref_ptr<const Profile> userProfile;
+        osg::ref_ptr<const Profile> profile;
+
+        // Do the map options contain a profile? If so, try to use it:
         if ( _mapOptions.profile().isSet() )
         {
-            userProfile = Profile::create( _mapOptions.profile().value() );
+            profile = Profile::create( _mapOptions.profile().value() );
         }
 
-        if ( _mapOptions.coordSysType() == MapOptions::CSTYPE_GEOCENTRIC )
-        {
-            if ( userProfile.valid() )
-            {
-                if ( userProfile->isOK() && userProfile->getSRS()->isGeographic() )
-                {
-                    _profile = userProfile.get();
-                }
-                else
-                {
-                    OE_WARN << LC 
-                        << "Map is geocentric, but the configured profile SRS ("
-                        << userProfile->getSRS()->getName() << ") is not geographic; "
-                        << "it will be ignored."
-                        << std::endl;
-                }
-            }
-        }
-        else if ( _mapOptions.coordSysType() == MapOptions::CSTYPE_GEOCENTRIC_CUBE )
+        // Do the map options contain an override coordinate system type?
+        // If so, attempt to apply that next:
+        if (_mapOptions.coordSysType().isSetTo(MapOptions::CSTYPE_GEOCENTRIC))
         {
-            if ( userProfile.valid() )
+            if (profile.valid() && profile->getSRS()->isProjected())
             {
-                if ( userProfile->isOK() && userProfile->getSRS()->isCube() )
-                {
-                    _profile = userProfile.get();
-                }
-                else
-                {
-                    OE_WARN << LC 
-                        << "Map is geocentric cube, but the configured profile SRS ("
-                        << userProfile->getSRS()->getName() << ") is not geocentric cube; "
-                        << "it will be ignored."
-                        << std::endl;
-                }
-            }
-        }
-        else // CSTYPE_PROJECTED
-        {
-            if ( userProfile.valid() )
-            {
-                if ( userProfile->isOK() && userProfile->getSRS()->isProjected() )
-                {
-                    _profile = userProfile.get();
-                }
-                else
-                {
-                    OE_WARN << LC 
-                        << "Map is projected, but the configured profile SRS ("
-                        << userProfile->getSRS()->getName() << ") is not projected; "
-                        << "it will be ignored."
-                        << std::endl;
-                }
+                OE_WARN << LC << "Geocentric map type conflicts with the projected SRS profile; ignoring your profile\n";
+                profile = Registry::instance()->getGlobalGeodeticProfile();
             }
         }
 
-        // At this point, if we don't have a profile we need to search tile sources until we find one.
-        if ( !_profile.valid() )
+        // Do the map options ask for a projected map?
+        else if (_mapOptions.coordSysType().isSetTo(MapOptions::CSTYPE_PROJECTED))
         {
-            Threading::ScopedReadLock lock( _mapDataMutex );
-
-            for( ImageLayerVector::iterator i = _imageLayers.begin(); i != _imageLayers.end() && !_profile.valid(); i++ )
+            // Is there a conflict in the MapOptions?
+            if (profile.valid() && profile->getSRS()->isGeographic())
             {
-                ImageLayer* layer = i->get();
-                if ( layer->getTileSource() )
-                {
-                    _profile = layer->getTileSource()->getProfile();
-                }
+                OE_WARN << LC << "Projected map type conflicts with the geographic SRS profile; converting to Equirectangular projection\n";
+                unsigned u, v;
+                profile->getNumTiles(0, u, v);
+                const osgEarth::SpatialReference* eqc = profile->getSRS()->createEquirectangularSRS();
+                osgEarth::GeoExtent e = profile->getExtent().transform( eqc );
+                profile = osgEarth::Profile::create( eqc, e.xMin(), e.yMin(), e.xMax(), e.yMax(), u, v);
             }
 
-            for( ElevationLayerVector::iterator i = _elevationLayers.begin(); i != _elevationLayers.end() && !_profile.valid(); i++ )
+            // Is there no profile set? Try to derive it from the Map layers:
+            if (!profile.valid())
             {
-                ElevationLayer* layer = i->get();
-                if ( layer->getTileSource() )
+                for (TerrainLayerVector::iterator i = layers.begin(); !profile.valid() && i != layers.end(); ++i)
                 {
-                    _profile = layer->getTileSource()->getProfile();
+                    profile = i->get()->getProfile();
                 }
             }
-        }
 
-        // ensure that the profile we found is the correct kind
-        // convert a geographic profile to Plate Carre if necessary
-        if ( _mapOptions.coordSysType() == MapOptions::CSTYPE_GEOCENTRIC && !( _profile.valid() && _profile->getSRS()->isGeographic() ) )
-        {
-            // by default, set a geocentric map to use global-geodetic WGS84.
-            _profile = osgEarth::Registry::instance()->getGlobalGeodeticProfile();
-        }
-        else if ( _mapOptions.coordSysType() == MapOptions::CSTYPE_GEOCENTRIC_CUBE && !( _profile.valid() && _profile->getSRS()->isCube() ) )
-        {
-            //If the map type is a Geocentric Cube, set the profile to the cube profile.
-            _profile = osgEarth::Registry::instance()->getCubeProfile();
-        }
-        else if ( _mapOptions.coordSysType() == MapOptions::CSTYPE_PROJECTED && _profile.valid() && _profile->getSRS()->isGeographic() )
-        {
-            OE_INFO << LC << "Projected map with geographic SRS; activating EQC profile" << std::endl;            
-            unsigned u, v;
-            _profile->getNumTiles(0, u, v);
-            const osgEarth::SpatialReference* eqc = _profile->getSRS()->createEquirectangularSRS();
-            osgEarth::GeoExtent e = _profile->getExtent().transform( eqc );
-            _profile = osgEarth::Profile::create( eqc, e.xMin(), e.yMin(), e.xMax(), e.yMax(), u, v);
-        }
-        else if ( _mapOptions.coordSysType() == MapOptions::CSTYPE_PROJECTED && !( _profile.valid() && _profile->getSRS()->isProjected() ) )
-        {
-            // TODO: should there be a default projected profile?
-            _profile = 0;
-        }
-
-        // finally, fire an event if the profile has been set.
-        if ( _profile.valid() )
-        {
-            OE_INFO << LC << "Map profile is: " << _profile->toString() << std::endl;
-
-            for( MapCallbackList::iterator i = _mapCallbacks.begin(); i != _mapCallbacks.end(); i++ )
+            if (!profile.valid())
             {
-                i->get()->onMapInfoEstablished( MapInfo(this) );
+                OE_WARN << LC << "No profile information available; defaulting to Mercator projection\n";
+                profile = Registry::instance()->getGlobalMercatorProfile();
             }
         }
 
-        else
-        {
-            OE_WARN << LC << "Warning, not yet able to establish a map profile!" << std::endl;
+        // Finally, if there is still no profile, default to global geodetic.
+        if (!profile.valid())
+        {            
+            profile = Registry::instance()->getGlobalGeodeticProfile();
         }
-    }
-
-    if ( _profile.valid() )
-    {
-        // tell all the loaded layers what the profile is, as a hint
-        {
-            Threading::ScopedWriteLock lock( _mapDataMutex );
 
-            for( ImageLayerVector::iterator i = _imageLayers.begin(); i != _imageLayers.end(); i++ )
-            {
-                ImageLayer* layer = i->get();
-                if ( layer->getEnabled() == true )
-                {
-                    layer->setTargetProfileHint( _profile.get() );
-                }
-            }
 
-            for( ElevationLayerVector::iterator i = _elevationLayers.begin(); i != _elevationLayers.end(); i++ )
-            {
-                ElevationLayer* layer = i->get();
-                if ( layer->getEnabled() )
-                {
-                    layer->setTargetProfileHint( _profile.get() );
-                }
-            }
-        }
+        // Set the map's profile!
+        _profile = profile.release();        
 
         // create a "proxy" profile to use when querying elevation layers with a vertical datum
         if ( _profile->getSRS()->getVerticalDatum() != 0L )
@@ -1086,17 +666,34 @@ Map::calculateProfile()
         {
             _profileNoVDatum = _profile;
         }
+
+        // finally, fire an event if the profile has been set.
+        OE_INFO << LC << "Map profile is: " << _profile->toString() << std::endl;
+
+        for( MapCallbackList::iterator i = _mapCallbacks.begin(); i != _mapCallbacks.end(); i++ )
+        {
+            i->get()->onMapInfoEstablished( MapInfo(this) );
+        }
+    }
+
+    // Tell all the layers about the profile
+    for (TerrainLayerVector::iterator i = layers.begin(); i != layers.end(); ++i)
+    {
+        if (i->get()->getEnabled())
+        {
+            i->get()->setTargetProfileHint(_profile.get());
+        }
     }
 }
 
 const SpatialReference*
 Map::getWorldSRS() const
 {
-    return isGeocentric() ? getSRS()->getECEF() : getSRS();
+    return getSRS() && getSRS()->isGeographic() ? getSRS()->getECEF() : getSRS();
 }
 
 bool
-Map::sync( MapFrame& frame ) const
+Map::sync(MapFrame& frame) const
 {
     bool result = false;
 
@@ -1105,40 +702,105 @@ Map::sync( MapFrame& frame ) const
         // hold the read lock while copying the layer lists.
         Threading::ScopedReadLock lock( const_cast<Map*>(this)->_mapDataMutex );
 
-        if ( frame._parts & IMAGE_LAYERS )
-        {
-            if ( !frame._initialized )
-                frame._imageLayers.reserve( _imageLayers.size() );
-            frame._imageLayers.clear();
-            std::copy( _imageLayers.begin(), _imageLayers.end(), std::back_inserter(frame._imageLayers) );
-        }
+        if (!frame._initialized)
+            frame._layers.reserve(_layers.size());
 
-        if ( frame._parts & ELEVATION_LAYERS )
-        {
-            frame._elevationLayers = _elevationLayers;
-        }
+        frame._layers.clear();
 
-        if ( frame._parts & MODEL_LAYERS )
-        {
-            if ( !frame._initialized )
-                frame._modelLayers.reserve( _modelLayers.size() );
-            frame._modelLayers.clear();
-            std::copy( _modelLayers.begin(), _modelLayers.end(), std::back_inserter(frame._modelLayers) );
-        }
-
-        if ( frame._parts & MASK_LAYERS )
-        {
-          if ( !frame._initialized )
-              frame._maskLayers.reserve( _terrainMaskLayers.size() );
-          frame._maskLayers.clear();
-          std::copy( _terrainMaskLayers.begin(), _terrainMaskLayers.end(), std::back_inserter(frame._maskLayers) );
-        }
+        std::copy(_layers.begin(), _layers.end(), std::back_inserter(frame._layers));
 
         // sync the revision numbers.
         frame._initialized = true;
         frame._mapDataModelRevision = _dataModelRevision;
-            
+
         result = true;
-    }    
+    }
     return result;
 }
+
+
+//......................................................................
+// @deprecated functions
+
+#include <osgEarth/ImageLayer>
+#include <osgEarth/ElevationLayer>
+#include <osgEarth/ModelLayer>
+#include <osgEarth/MaskLayer>
+
+void 
+Map::addImageLayer(class ImageLayer* layer) {
+    OE_DEPRECATED(Map::addImageLayer, Map::addLayer);
+    addLayer(layer);
+}
+
+void 
+Map::insertImageLayer(class ImageLayer* layer, unsigned index) {
+    OE_DEPRECATED(Map::insertImageLayer, Map::insertLayer);
+    insertLayer(layer, index);
+}
+
+void 
+Map::removeImageLayer(class ImageLayer* layer) {
+    OE_DEPRECATED(Map::removeImageLayer, Map::removeLayer);
+    removeLayer(layer);
+}
+
+void 
+Map::moveImageLayer(class ImageLayer* layer, unsigned newIndex) {
+    OE_DEPRECATED(Map::moveImageLayer, Map::moveLayer);
+    moveLayer(layer, newIndex);
+}
+
+void 
+Map::addElevationLayer(class ElevationLayer* layer) {
+    OE_DEPRECATED(Map::addElevationLayer, Map::addLayer);
+    addLayer(layer);
+}
+
+void 
+Map::removeElevationLayer(class ElevationLayer* layer) {
+    OE_DEPRECATED(Map::removeElevationLayer, Map::removeLayer);
+    removeLayer(layer);
+}
+
+void 
+Map::moveElevationLayer(class ElevationLayer* layer, unsigned newIndex) {
+    OE_DEPRECATED(Map::moveElevationLayer, Map::moveLayer);
+    moveLayer(layer, newIndex);
+}
+
+void 
+Map::addModelLayer(class ModelLayer* layer) {
+    OE_DEPRECATED(Map::addModelLayer, Map::addLayer);
+    addLayer(layer);
+}
+
+void 
+Map::insertModelLayer(class ModelLayer* layer, unsigned index) {
+    OE_DEPRECATED(Map::insertModelLayer, Map::insertLayer);
+    insertLayer(layer, index);
+}
+
+void 
+Map::removeModelLayer(class ModelLayer* layer) {
+    OE_DEPRECATED(Map::removeModelLayer, Map::removeLayer);
+    removeLayer(layer);
+}
+
+void 
+Map::moveModelLayer(class ModelLayer* layer, unsigned newIndex) {
+    OE_DEPRECATED(Map::moveModelLayer, Map::moveLayer);
+    moveLayer(layer, newIndex);
+}
+
+void 
+Map::addTerrainMaskLayer(class MaskLayer* layer) {
+    OE_DEPRECATED(Map::addTerrainMaskLayer, Map::addLayer);
+    addLayer(layer);
+}
+
+void 
+Map::removeTerrainMaskLayer(class MaskLayer* layer) {
+    OE_DEPRECATED(Map::removeTerrainMaskLayer, Map::removeLayer);
+    removeLayer(layer);
+}
diff --git a/src/osgEarth/MapCallback b/src/osgEarth/MapCallback
index 36bd9a5..8f052d9 100644
--- a/src/osgEarth/MapCallback
+++ b/src/osgEarth/MapCallback
@@ -27,6 +27,7 @@
 namespace osgEarth
 {
     class MapInfo;
+    class Layer;
     class ImageLayer;
     class ElevationLayer;
     class ModelLayer;
@@ -38,31 +39,68 @@ namespace osgEarth
      */
     struct OSGEARTH_EXPORT MapCallback : public osg::Referenced
     {
+        //! Invoked when a Map first establishes a valid profile.
         virtual void onMapInfoEstablished( const MapInfo& mapInfo ) { } 
 
-        // callbacks for begin/end batch update operations.
+        //! Invoked when a batch operation begins.
         virtual void onBeginUpdate() { }
+
+        //! Invoked when a batch operation ends.
         virtual void onEndUpdate() { }
 
-        virtual void onMapModelChanged( const MapModelChange& change );
+        //! Invoked whenever a layer is added, removed, or moved from a Map.
+        //! The default implementation in turn calls onLayerAdded, onLayerRemoved,
+        //! or onLayerMoved, respectively.
+        virtual void onMapModelChanged(const MapModelChange& change);
+
+        //! Invoked when a layer is added to a Map.
+        virtual void onLayerAdded(Layer* layer, unsigned index);
+
+        //! Invoked when a layer is removed from a Map.
+        virtual void onLayerRemoved(Layer* layer, unsigned index);
 
+        //! Invoked when a layer is re-ordered within a Map.
+        virtual void onLayerMoved(Layer* layer, unsigned oldIndex, unsigned newIndex);
+
+    public: // Deprecated functions
+
+        // @deprecated Use onLayerAdded
         virtual void onImageLayerAdded( ImageLayer* layer, unsigned int index ) { }
+        // @deprecated Use onLayerRemoved
         virtual void onImageLayerRemoved( ImageLayer* layer, unsigned int index ) { }
+        // @deprecated Use onLayerMoved
         virtual void onImageLayerMoved( ImageLayer* layer, unsigned int oldIndex, unsigned int newIndex ) { }
 
+        // @deprecated Use onLayerAdded
         virtual void onElevationLayerAdded( ElevationLayer* layer, unsigned int index ) { }
+        // @deprecated Use onLayerRemoved
         virtual void onElevationLayerRemoved( ElevationLayer* layer, unsigned int index ) { }
+        // @deprecated Use onLayerMoved
         virtual void onElevationLayerMoved( ElevationLayer* layer, unsigned int oldIndex, unsigned int newIndex ) { }
 
+        // @deprecated Use onLayerAdded
         virtual void onModelLayerAdded( ModelLayer* layer, unsigned int index ) { }
-        virtual void onModelLayerRemoved( ModelLayer* layer ) { }
+        // @deprecated Use onLayerRemoved
+        virtual void onModelLayerRemoved( ModelLayer* layer, unsigned int index ) { }
+        // @deprecated Use onLayerMoved
         virtual void onModelLayerMoved( ModelLayer* layer, unsigned int oldIndex, unsigned int newIndex ) { }
 
+        // @deprecated Use onLayerAdded
         virtual void onMaskLayerAdded( MaskLayer* mask ) { }
+        // @deprecated Use onLayerRemoved
         virtual void onMaskLayerRemoved( MaskLayer* mask ) { }
 
+    public:
         /** dtor */
         virtual ~MapCallback() { }
+
+        /**
+         * Invokes a callback method for all layers in the specified Map.
+         * This is useful if you create a callback for a Map that already has
+         * existing Layers in it and you want to "simulate" adding/removing them all.
+         */
+        void invokeOnLayerAdded(const class Map*);
+        void invokeOnLayerRemoved(const class Map*);
     };
 
     typedef std::list< osg::ref_ptr<MapCallback> > MapCallbackList;
diff --git a/src/osgEarth/MapCallback.cpp b/src/osgEarth/MapCallback.cpp
index 82d9a37..4011a2a 100644
--- a/src/osgEarth/MapCallback.cpp
+++ b/src/osgEarth/MapCallback.cpp
@@ -22,6 +22,7 @@
 #include <osgEarth/ElevationLayer>
 #include <osgEarth/ModelLayer>
 #include <osgEarth/MaskLayer>
+#include <osgEarth/Map>
 
 #define LC "[MapCallback] "
 
@@ -32,42 +33,86 @@ MapCallback::onMapModelChanged( const MapModelChange& change )
 {
     switch( change.getAction() )
     {
-    case MapModelChange::ADD_ELEVATION_LAYER: 
-        onElevationLayerAdded( change.getElevationLayer(), change.getFirstIndex() );
+    case MapModelChange::ADD_LAYER: 
+        onLayerAdded(change.getLayer(), change.getFirstIndex());
         break;
-    case MapModelChange::ADD_IMAGE_LAYER:
-        onImageLayerAdded( change.getImageLayer(), change.getFirstIndex() ); 
-        break;
-    case MapModelChange::ADD_MASK_LAYER:
-        onMaskLayerAdded( change.getMaskLayer() );
-        break;
-    case MapModelChange::ADD_MODEL_LAYER:
-        onModelLayerAdded( change.getModelLayer(), change.getFirstIndex() ); 
-        break;
-    case MapModelChange::REMOVE_ELEVATION_LAYER:
-        onElevationLayerRemoved( change.getElevationLayer(), change.getFirstIndex() ); 
-        break;
-    case MapModelChange::REMOVE_IMAGE_LAYER:
-        onImageLayerRemoved( change.getImageLayer(), change.getFirstIndex() );
-        break;
-    case MapModelChange::REMOVE_MASK_LAYER:
-        onMaskLayerRemoved( change.getMaskLayer() ); 
-        break;
-    case MapModelChange::REMOVE_MODEL_LAYER:
-        onModelLayerRemoved( change.getModelLayer() ); 
-        break;
-    case MapModelChange::MOVE_ELEVATION_LAYER:
-        onElevationLayerMoved( change.getElevationLayer(), change.getFirstIndex(), change.getSecondIndex() ); 
-        break;
-    case MapModelChange::MOVE_IMAGE_LAYER:
-        onImageLayerMoved( change.getImageLayer(), change.getFirstIndex(), change.getSecondIndex() ); 
-        break;
-    case MapModelChange::MOVE_MODEL_LAYER:
-        onModelLayerMoved( change.getModelLayer(), change.getFirstIndex(), change.getSecondIndex() ); 
+
+    case MapModelChange::REMOVE_LAYER:
+        onLayerRemoved(change.getLayer(), change.getFirstIndex());
         break;
-    case MapModelChange::UNSPECIFIED: 
+
+    case MapModelChange::MOVE_LAYER:
+        onLayerMoved(change.getLayer(), change.getFirstIndex(), change.getSecondIndex());
         break;
+
     default: 
         break;
     }
 }
+
+void
+MapCallback::invokeOnLayerAdded(const Map* map)
+{
+    LayerVector layers;
+    map->getLayers(layers);
+    unsigned index = 0;
+    if (layers.size() > 0)
+    {
+        onBeginUpdate();
+        for (LayerVector::iterator i = layers.begin(); i != layers.end(); ++i)
+            onLayerAdded(i->get(), index++);
+        onEndUpdate();
+    }
+}
+
+void
+MapCallback::invokeOnLayerRemoved(const Map* map)
+{
+    LayerVector layers;
+    map->getLayers(layers);
+    unsigned index = 0;
+    if (layers.size() > 0)
+    {
+        onBeginUpdate();
+        for (LayerVector::iterator i = layers.begin(); i != layers.end(); ++i)
+            onLayerRemoved(i->get(), index++);
+        onEndUpdate();
+    }
+}
+
+void
+MapCallback::onLayerAdded(Layer* layer, unsigned index)
+{
+    if (dynamic_cast<ImageLayer*>(layer))
+        onImageLayerAdded(static_cast<ImageLayer*>(layer), index);
+    else if (dynamic_cast<ElevationLayer*>(layer))
+        onElevationLayerAdded(static_cast<ElevationLayer*>(layer), index);
+    else if (dynamic_cast<ModelLayer*>(layer))
+        onModelLayerAdded(static_cast<ModelLayer*>(layer), index);
+    else if (dynamic_cast<MaskLayer*>(layer))
+        onMaskLayerAdded(static_cast<MaskLayer*>(layer));
+}
+
+void 
+MapCallback::onLayerRemoved(Layer* layer, unsigned index)
+{
+    if (dynamic_cast<ImageLayer*>(layer))
+        onImageLayerRemoved(static_cast<ImageLayer*>(layer), index);
+    else if (dynamic_cast<ElevationLayer*>(layer))
+        onElevationLayerRemoved(static_cast<ElevationLayer*>(layer), index);
+    else if (dynamic_cast<ModelLayer*>(layer))
+        onModelLayerRemoved(static_cast<ModelLayer*>(layer), index);
+    else if (dynamic_cast<MaskLayer*>(layer))
+        onMaskLayerRemoved(static_cast<MaskLayer*>(layer));
+}
+
+void
+MapCallback::onLayerMoved(Layer* layer, unsigned oldIndex, unsigned newIndex)
+{
+    if (dynamic_cast<ImageLayer*>(layer))
+        onImageLayerMoved(static_cast<ImageLayer*>(layer), oldIndex, newIndex);
+    else if (dynamic_cast<ElevationLayer*>(layer))
+        onElevationLayerMoved(static_cast<ElevationLayer*>(layer), oldIndex, newIndex);
+    else if (dynamic_cast<ModelLayer*>(layer))
+        onModelLayerMoved(static_cast<ModelLayer*>(layer), oldIndex, newIndex);
+}
diff --git a/src/osgEarth/MapFrame b/src/osgEarth/MapFrame
index f6ea581..6a3c30a 100644
--- a/src/osgEarth/MapFrame
+++ b/src/osgEarth/MapFrame
@@ -23,11 +23,18 @@
 #include <osgEarth/Common>
 #include <osgEarth/Containers>
 #include <osgEarth/MapInfo>
-#include <osgEarth/Map>
+#include <osgEarth/Revisioning>
+#include <osgEarth/Layer>
+#include <osgEarth/ElevationLayer>
 
 namespace osgEarth
 {
     class Profile;
+    class Map;
+    class MapOptions;
+    class TileKey;
+    class ElevationPool;
+    class ProgressCallback;
 
     /**
      * A "snapshot in time" of a Map model revision. Use this class to get a safe "copy" of
@@ -44,12 +51,8 @@ namespace osgEarth
 
         MapFrame(const MapFrame& rhs);
 
-        MapFrame(Map::ModelParts parts);
-
         MapFrame(const Map* map);
 
-        MapFrame(const Map* map, Map::ModelParts parts);
-
         bool isValid() const;
 
         /** dtor */
@@ -70,6 +73,9 @@ namespace osgEarth
          */
         bool needsSync() const;
 
+        /** Releases any references held by this frame. Call sync() to restablish */
+        void release();
+
         /** The source map's UID */
         UID getUID() const;
 
@@ -78,31 +84,41 @@ namespace osgEarth
 
         /** Convenience method to access the map's profile */
         const Profile* getProfile() const { return _mapInfo.getProfile(); }
-        
 
-        /** The image layer stack snapshot */
-        const ImageLayerVector& imageLayers() const { return _imageLayers; }
-        ImageLayer* getImageLayerAt( int index ) const { return _imageLayers[index].get(); }
-        ImageLayer* getImageLayerByUID( UID uid ) const;
-        ImageLayer* getImageLayerByName( const std::string& name ) const;
+        const LayerVector& layers() const { return _layers; }
+        
+        template<typename T>
+        unsigned getLayers(std::vector<osg::ref_ptr<T> >& output) const {
+            for (LayerVector::const_iterator i = _layers.begin(); i != _layers.end(); ++i) {
+                T* t = dynamic_cast<T*>(i->get());
+                if (t) output.push_back(t);
+            }
+            return output.size();
+        }
+
+        template<typename T>
+        unsigned getLayers(osg::MixinVector< osg::ref_ptr<T> >& output) const {
+            for (LayerVector::const_iterator i = _layers.begin(); i != _layers.end(); ++i) {
+                T* obj = dynamic_cast<T*>(i->get());
+                if (obj) output.push_back(obj);
+            }
+            return output.size();
+        }
+
+        /** Whether the frame contains a layer with the matching UID */
+        bool containsLayer(UID uid) const;
+
+        template<typename T>
+        T* getLayerByName(const std::string& name) {
+            for (LayerVector::const_iterator i = _layers.begin(); i != _layers.end(); ++i) {
+                if (i->get()->getName() == name)
+                    return dynamic_cast<T*>(i->get());
+            }
+            return 0L;
+        }
 
         /** The elevation layer stack snapshot */
         const ElevationLayerVector& elevationLayers() const { return _elevationLayers; }
-        ElevationLayer* getElevationLayerAt( int index ) const { return _elevationLayers[index].get(); }
-        ElevationLayer* getElevationLayerByUID( UID uid ) const;
-        ElevationLayer* getElevationLayerByName( const std::string& name ) const;
-
-        /** The model layer set snapshot */
-        const ModelLayerVector& modelLayers() const { return _modelLayers; }
-        ModelLayer* getModelLayerAt(int index) const { return _modelLayers[index].get(); }
-
-        /** The mask layer set snapshot */
-        const MaskLayerVector& terrainMaskLayers() const { return _maskLayers; }
-
-        /** Gets the index of the layer in the layer stack snapshot. */
-        int indexOf( ImageLayer* layer ) const;
-        int indexOf( ElevationLayer* layer ) const;
-        int indexOf( ModelLayer* layer ) const;
 
         /** Gets the map data model revision with which this frame is currently sync'd */
         Revision getRevision() const { return _mapDataModelRevision; }
@@ -126,18 +142,29 @@ namespace osgEarth
             bool                            expressHeightsAsHAE,
             ProgressCallback*               progress) const;
 
+        bool populateHeightFieldAndNormalMap(
+            osg::ref_ptr<osg::HeightField>& hf,
+            osg::ref_ptr<NormalMap>&        normalMap,
+            const TileKey&                  key,
+            bool                            expressHeightsAsHAE,
+            ProgressCallback*               progress) const;
+
+        /**
+         * Gets the map's elevation pool.
+         * This returns a ref_ptr because the Map itself is held with an observer_ptr.
+         */
+        ElevationPool* getElevationPool() const;
+
     private:
         bool _initialized;
         osg::observer_ptr<const Map> _map;
         std::string _name;
         MapInfo _mapInfo;
-        Map::ModelParts _parts;
         Revision _mapDataModelRevision;
-        ImageLayerVector _imageLayers;
+        LayerVector _layers;
         ElevationLayerVector _elevationLayers;
-        ModelLayerVector _modelLayers;
-        MaskLayerVector _maskLayers;
         unsigned _highestMinLevel;
+        osg::ref_ptr<osg::Referenced> _pool;
 
         friend class Map;
 
diff --git a/src/osgEarth/MapFrame.cpp b/src/osgEarth/MapFrame.cpp
index 55d9bf1..cc7108a 100644
--- a/src/osgEarth/MapFrame.cpp
+++ b/src/osgEarth/MapFrame.cpp
@@ -18,6 +18,8 @@
  */
 #include <osgEarth/MapFrame>
 #include <osgEarth/Cache>
+#include <osgEarth/Map>
+#include <osgEarth/ElevationPool>
 
 using namespace osgEarth;
 
@@ -26,8 +28,7 @@ using namespace osgEarth;
 MapFrame::MapFrame() :
 _initialized    ( false ),
 _highestMinLevel( 0 ),
-_mapInfo       ( 0L ),
-_parts(Map::ENTIRE_MODEL)
+_mapInfo        ( 0L )
 {
     //nop
 }
@@ -36,13 +37,10 @@ MapFrame::MapFrame(const MapFrame& rhs) :
 _initialized         ( rhs._initialized ),
 _map                 ( rhs._map.get() ),
 _mapInfo             ( rhs._mapInfo ),
-_parts               ( rhs._parts ),
 _highestMinLevel     ( rhs._highestMinLevel ),
 _mapDataModelRevision( rhs._mapDataModelRevision ),
-_imageLayers         ( rhs._imageLayers ),
-_elevationLayers     ( rhs._elevationLayers ),
-_modelLayers         ( rhs._modelLayers ),
-_maskLayers          ( rhs._maskLayers )
+_layers              ( rhs._layers ),
+_pool                ( rhs._pool.get() )
 {
     //no sync required here; we copied the arrays etc
 }
@@ -51,17 +49,6 @@ MapFrame::MapFrame(const Map* map) :
 _initialized    ( false ),
 _map            ( map ),
 _mapInfo        ( map ),
-_parts          ( Map::ENTIRE_MODEL ),
-_highestMinLevel( 0 )
-{
-    sync();
-}
-
-MapFrame::MapFrame(const Map* map, Map::ModelParts parts) :
-_initialized    ( false ),
-_map            ( map ),
-_mapInfo        ( map ),
-_parts          ( parts ),
 _highestMinLevel( 0 )
 {
     sync();
@@ -76,19 +63,28 @@ MapFrame::isValid() const
 void
 MapFrame::setMap(const Map* map)
 {
-    _imageLayers.clear();
-    _elevationLayers.clear();
-    _modelLayers.clear();
-    _maskLayers.clear();
+    _layers.clear();
+    _pool = 0L;
 
     _map = map;
     if ( map )
-        _mapInfo = MapInfo(map);
+    {
+        _mapInfo.setMap(map);
+    }
 
     _initialized = false;
     _highestMinLevel = 0;
 
-    sync();
+    if (map)
+    {
+        sync();
+    }
+}
+
+ElevationPool*
+MapFrame::getElevationPool() const
+{
+    return static_cast<ElevationPool*>(_pool.get());
 }
 
 bool
@@ -99,19 +95,19 @@ MapFrame::sync()
     osg::ref_ptr<const Map> map;
     if ( _map.lock(map) )
     {
-        changed = _map->sync( *this );
+        changed = map->sync( *this );
         if ( changed )
         {
             refreshComputedValues();
         }
+        _pool = map->getElevationPool();
     }
     else
     {
-        _imageLayers.clear();
+        _layers.clear();
         _elevationLayers.clear();
-        _modelLayers.clear();
-        _maskLayers.clear();
-    }
+        changed = true;
+    }    
 
     return changed;
 }
@@ -129,6 +125,15 @@ MapFrame::needsSync() const
         (map->getDataModelRevision() != _mapDataModelRevision || !_initialized);
 }
 
+void
+MapFrame::release()
+{
+    _layers.clear();
+    _pool = 0L;
+    _initialized = false;
+    _highestMinLevel = 0;
+}
+
 UID
 MapFrame::getUID() const
 {
@@ -139,28 +144,39 @@ MapFrame::getUID() const
         return (UID)0;
 }
 
+bool
+MapFrame::containsLayer(UID uid) const
+{
+    for (LayerVector::const_iterator i = _layers.begin(); i != _layers.end(); ++i)
+        if (i->get()->getUID() == uid)
+            return true;
+    return false;
+}
+
 void
 MapFrame::refreshComputedValues()
 {
-    // cache the min LOD based on all image/elev layers
     _highestMinLevel = 0;
 
-    for(ImageLayerVector::const_iterator i = _imageLayers.begin(); 
-        i != _imageLayers.end();
-        ++i)
-    {
-        const optional<unsigned>& minLevel = i->get()->getTerrainLayerRuntimeOptions().minLevel();
-        if ( minLevel.isSet() && minLevel.value() > _highestMinLevel )
-            _highestMinLevel = minLevel.value();
-    }
+    _elevationLayers.clear();
 
-    for(ElevationLayerVector::const_iterator i = _elevationLayers.begin(); 
-        i != _elevationLayers.end();
-        ++i)
+    for (LayerVector::const_iterator i = _layers.begin(); i != _layers.end(); ++i)
     {
-        const optional<unsigned>& minLevel = i->get()->getTerrainLayerRuntimeOptions().minLevel();
-        if ( minLevel.isSet() && minLevel.value() > _highestMinLevel )
-            _highestMinLevel = minLevel.value();
+        TerrainLayer* terrainLayer = dynamic_cast<TerrainLayer*>(i->get());
+        if (terrainLayer)
+        {
+            const optional<unsigned>& minLevel = terrainLayer->options().minLevel();
+            if (minLevel.isSet() && minLevel.value() > _highestMinLevel)
+            {
+                _highestMinLevel = minLevel.value();
+            }
+            
+            ElevationLayer* elevation = dynamic_cast<ElevationLayer*>(terrainLayer);
+            if (elevation)
+            {
+                _elevationLayers.push_back(elevation);
+            }
+        }
     }
 }
 
@@ -173,9 +189,11 @@ MapFrame::populateHeightField(osg::ref_ptr<osg::HeightField>& hf,
     osg::ref_ptr<const Map> map;
     if ( _map.lock(map) )
     {        
-        ElevationInterpolation interp = map->getMapOptions().elevationInterpolation().get();    
-        return _elevationLayers.populateHeightField(
+        ElevationInterpolation interp = map->getMapOptions().elevationInterpolation().get();
+
+        return _elevationLayers.populateHeightFieldAndNormalMap(
             hf.get(),
+            0L,         // no normal map to populate
             key,
             convertToHAE ? map->getProfileNoVDatum() : 0L,
             interp,
@@ -187,127 +205,83 @@ MapFrame::populateHeightField(osg::ref_ptr<osg::HeightField>& hf,
     }
 }
 
-
-int
-MapFrame::indexOf( ImageLayer* layer ) const
-{
-    ImageLayerVector::const_iterator i = std::find( _imageLayers.begin(), _imageLayers.end(), layer );
-    return i != _imageLayers.end() ? i - _imageLayers.begin() : -1;
-}
-
-
-int
-MapFrame::indexOf( ElevationLayer* layer ) const
-{
-    ElevationLayerVector::const_iterator i = std::find( _elevationLayers.begin(), _elevationLayers.end(), layer );
-    return i != _elevationLayers.end() ? i - _elevationLayers.begin() : -1;
-}
-
-
-int
-MapFrame::indexOf( ModelLayer* layer ) const
-{
-    ModelLayerVector::const_iterator i = std::find( _modelLayers.begin(), _modelLayers.end(), layer );
-    return i != _modelLayers.end() ? i - _modelLayers.begin() : -1;
-}
-
-
-ImageLayer*
-MapFrame::getImageLayerByUID( UID uid ) const
+bool
+MapFrame::populateHeightFieldAndNormalMap(osg::ref_ptr<osg::HeightField>& hf,
+                                          osg::ref_ptr<NormalMap>&        normalMap,
+                                          const TileKey&                  key,
+                                          bool                            convertToHAE,
+                                          ProgressCallback*               progress) const
 {
-    for(ImageLayerVector::const_iterator i = _imageLayers.begin(); i != _imageLayers.end(); ++i )
-        if ( i->get()->getUID() == uid )
-            return i->get();
-    return 0L;
-}
-
+    osg::ref_ptr<const Map> map;
+    if ( _map.lock(map) )
+    {        
+        ElevationInterpolation interp = map->getMapOptions().elevationInterpolation().get();
 
-ImageLayer*
-MapFrame::getImageLayerByName( const std::string& name ) const
-{
-    for(ImageLayerVector::const_iterator i = _imageLayers.begin(); i != _imageLayers.end(); ++i )
-        if ( i->get()->getName() == name )
-            return i->get();
-    return 0L;
+        return _elevationLayers.populateHeightFieldAndNormalMap(
+            hf.get(),
+            normalMap.get(),
+            key,
+            convertToHAE ? map->getProfileNoVDatum() : 0L,
+            interp,
+            progress );
+    }
+    else
+    {
+        return false;
+    }
 }
 
-
 bool
 MapFrame::isCached( const TileKey& key ) const
 {
     // is there a map cache at all?
-    if ( _map.valid() && _map->getCache() == 0L )
+    osg::ref_ptr<const Map> map;
+    if (_map.lock(map) && map->getCache() == 0L)
         return false;
 
-    //Check to see if the tile will load fast
-    // Check the imagery layers
-    for( ImageLayerVector::const_iterator i = imageLayers().begin(); i != imageLayers().end(); i++ )
-    {   
-        const ImageLayer* layer = i->get();
-
-        if (!layer->getEnabled())
-            continue;
-
-        // If we're cache only we should be fast
-        if (layer->getCacheSettings()->cachePolicy()->isCacheOnly())
-            continue;
-
-        // no-cache? always slow
-        if (layer->getCacheSettings()->cachePolicy()->isCacheDisabled())
-            return false;
-
-        // No tile source? skip it
-        osg::ref_ptr< TileSource > source = layer->getTileSource();
-        if (!source.valid())
-            continue;
-
-        //If the tile is blacklisted, it should also be fast.
-        if ( source->getBlacklist()->contains( key ) )
-            continue;
-
-        //If no data is available on this tile, we'll be fast
-        if ( !source->hasData( key ) )
-            continue;
-
-        if ( !layer->isCached(key) )
-            return false;
-    }
-
-    for( ElevationLayerVector::const_iterator i = elevationLayers().begin(); i != elevationLayers().end(); ++i )
+    for (LayerVector::const_iterator i = _layers.begin(); i != _layers.end(); ++i)
     {
-        const ElevationLayer* layer = i->get();
-
-        if (!layer->getEnabled())
-            continue;
+        TerrainLayer* layer = dynamic_cast<TerrainLayer*>(i->get());
+        if (layer)
+        {
+            if (!layer->getEnabled())
+                continue;
 
-        //If we're cache only we should be fast
-        if (layer->getCacheSettings()->cachePolicy()->isCacheOnly())
-            continue;
+            // If we're cache only we should be fast
+            if (layer->getCacheSettings()->cachePolicy()->isCacheOnly())
+                continue;
 
-        // no-cache? always high-latency.
-        if (layer->getCacheSettings()->cachePolicy()->isCacheDisabled())
-            return false;
+            // no-cache? always slow
+            if (layer->getCacheSettings()->cachePolicy()->isCacheDisabled())
+                return false;
 
-        osg::ref_ptr< TileSource > source = layer->getTileSource();
-        if (!source.valid())
-            continue;
+            //If no data is available on this tile, we'll be fast
+            if (!layer->mayHaveData(key))
+                continue;
 
-        //If the tile is blacklisted, it should also be fast.
-        if ( source->getBlacklist()->contains( key ) )
-            continue;
+            // No tile source? skip it
+            osg::ref_ptr< TileSource > source = layer->getTileSource();
+            if (!source.valid())
+                continue;
 
-        if ( !source->hasData( key ) )
-            continue;
+            //If the tile is blacklisted, it should also be fast.
+            if (source->getBlacklist()->contains(key))
+                continue;
 
-        if ( !i->get()->isCached( key ) )
-            return false;
+            if (!layer->isCached(key))
+                return false;
+        }
     }
-
     return true;
 }
 
 const MapOptions&
 MapFrame::getMapOptions() const
 {
-    return _map->getMapOptions();
+    static MapOptions defaultMapOptions;
+    osg::ref_ptr<const Map> map;
+    if (_map.lock(map))
+        return map->getMapOptions();
+    else
+        return defaultMapOptions;
 }
diff --git a/src/osgEarth/MapInfo b/src/osgEarth/MapInfo
index c19790c..e84677a 100644
--- a/src/osgEarth/MapInfo
+++ b/src/osgEarth/MapInfo
@@ -35,23 +35,40 @@ namespace osgEarth
     class OSGEARTH_EXPORT MapInfo
     {
     public:
-        MapInfo( const Map* map );
+        //! Constructs a new MapInfo object from the data in a Map
+        MapInfo(const Map* map);
 
-        MapInfo( const MapInfo& rhs );
+        //! Copy constructor
+        MapInfo(const MapInfo& rhs);
 
-        /** dtor */
+        //! Dtor
         virtual ~MapInfo() { }
+
+        //! Sets the map info to reflect a Map object
+        void setMap(const Map* map);
         
+        //! Map's profile
         const Profile* getProfile() const { return _profile.get(); }
+
+        //! Map's profile SRS
         const SpatialReference* getSRS() const { return _profile->getSRS(); }
 
+        //! True if the map's visualization is geocentric
         bool isGeocentric() const { return _isGeocentric; }
+
+        //! @deprecated (always false)
         bool isCube() const { return _isCube; }
+
+        //! True if the map's SRS is a plate carre projected profile
         bool isPlateCarre() const { return _profile->getSRS()->isPlateCarre(); }
 
+        //! True if the map's SRS is projected
         bool isProjectedSRS() const { return !isGeographicSRS(); }
+
+        //! True if the map's SRS Is geographic (lat/long)
         bool isGeographicSRS() const { return _profile->getSRS()->isGeographic(); }
 
+        //! Elevation interpolation method used by the map
         ElevationInterpolation getElevationInterpolation() const { return _elevationInterpolation;}
 
     private:
diff --git a/src/osgEarth/MapInfo.cpp b/src/osgEarth/MapInfo.cpp
index d55ae1f..c07256d 100644
--- a/src/osgEarth/MapInfo.cpp
+++ b/src/osgEarth/MapInfo.cpp
@@ -23,20 +23,13 @@ using namespace osgEarth;
 
 #define LC "[MapInfo] "
 
-
 MapInfo::MapInfo( const Map* map ) :
 _profile               ( 0L ),
 _isGeocentric          ( true ),
 _isCube                ( false ),
 _elevationInterpolation( INTERP_BILINEAR )
-{ 
-    if ( map )
-    {
-        _profile = map->getProfile();
-        _isGeocentric = map->isGeocentric();
-        _isCube = map->getMapOptions().coordSysType() == MapOptions::CSTYPE_GEOCENTRIC_CUBE;
-        _elevationInterpolation = *map->getMapOptions().elevationInterpolation();
-    }
+{
+    setMap(map);
 }
 
 MapInfo::MapInfo( const MapInfo& rhs ) :
@@ -47,3 +40,19 @@ _elevationInterpolation( rhs._elevationInterpolation )
 {
     //nop
 }
+
+void
+MapInfo::setMap(const Map* map)
+{
+    if (map)
+    {
+        _profile = map->getProfile();
+        _isGeocentric = map->isGeocentric();
+        _isCube = map->getMapOptions().coordSysType() == MapOptions::CSTYPE_GEOCENTRIC_CUBE;
+        _elevationInterpolation = *map->getMapOptions().elevationInterpolation();
+    }
+    else
+    {
+        _profile = 0L;
+    }
+}
diff --git a/src/osgEarth/MapModelChange b/src/osgEarth/MapModelChange
index 3a169c0..09c8a50 100644
--- a/src/osgEarth/MapModelChange
+++ b/src/osgEarth/MapModelChange
@@ -36,23 +36,17 @@ namespace osgEarth
     {
         enum ActionType
         {
-            BEGIN_BATCH_UPDATE,               // signals the start of multiple changes
-            END_BATCH_UPDATE,                 // signals the end of multiple changes
-            ADD_IMAGE_LAYER,
-            REMOVE_IMAGE_LAYER,
-            MOVE_IMAGE_LAYER,
-            ADD_ELEVATION_LAYER,
-            REMOVE_ELEVATION_LAYER,
-            MOVE_ELEVATION_LAYER,
+            BEGIN_BATCH_UPDATE,         // signals the start of multiple changes
+            END_BATCH_UPDATE,           // signals the end of multiple changes
+            ADD_LAYER,
+            REMOVE_LAYER,
+            MOVE_LAYER,
             TOGGLE_ELEVATION_LAYER,     // visibilty toggle on elevation is a model change.
-            ADD_MODEL_LAYER,
-            REMOVE_MODEL_LAYER,
-            MOVE_MODEL_LAYER,
-            ADD_MASK_LAYER,
-            REMOVE_MASK_LAYER,
             UNSPECIFIED
         };
 
+        MapModelChange() : _action(UNSPECIFIED) { }
+
         MapModelChange( ActionType action, Revision mapModeRev, Layer* layer =0L, int firstIndex =-1, int secondIndex =-1 ) 
             : _action(action), _layer(layer), _modelRevision(mapModeRev), _firstIndex(firstIndex), _secondIndex(secondIndex) { }
 
diff --git a/src/osgEarth/MapNode b/src/osgEarth/MapNode
index c9dc4b6..84193ae 100644
--- a/src/osgEarth/MapNode
+++ b/src/osgEarth/MapNode
@@ -35,6 +35,9 @@ namespace osgEarth
     class TerrainEngineNode;
     class MapNodeCullData;
     class SpatialReference;
+    class ResourceReleaser;
+    class DrapingManager;
+    class ClampingManager;
 
     /**
      * OSG Node that forms the root of an osgEarth map. This node is a "view" component
@@ -131,25 +134,18 @@ namespace osgEarth
         bool isGeocentric() const;
 
         /**
-         * Accesses the group node that contains all the ModelLayers.
+         * Accesses the group node that contains all the nodes added by Layers.
          */
-        osg::Group* getModelLayerGroup() const;
+        osg::Group* getLayerNodeGroup() const;
 
-        /**
-         * Accesses the root node for a specific ModelLayer.
-         */
-        osg::Node* getModelLayerNode( ModelLayer* layer ) const;
+        // @deprecated - use getLayerNodeGroup
+        osg::Group* getModelLayerGroup() const { return getLayerNodeGroup(); }
 
         /**
-         * Adds a node that decorates the terrain groups
-         */
-        void addTerrainDecorator( osg::Group* decorator );
-
-        /**
-         * Removes a node previously added via addTerrainDecorator.
+         * Accesses the root node for a specific ModelLayer.
          */
-        void removeTerrainDecorator( osg::Group* decorator );
-
+        osg::Node* getLayerNode(Layer* layer) const;
+        
         /**
          * Gets the overlay decorator in this mapnode. Usually you do not need to
          * access this directly. Instead install a DrapeableNode in the scene graph.
@@ -168,6 +164,11 @@ namespace osgEarth
         TerrainEngineNode* getTerrainEngine() const;
 
         /**
+         * Gets a service that you can use to release GL objects.
+         */
+        ResourceReleaser* getResourceReleaser() const;
+
+        /**
          * Gets the Config object serializing external data. External data is information
          * that osgEarth itself does not control, but that an app can include in the
          * MapNode for the purposes of including it in a .earth file.
@@ -212,6 +213,9 @@ namespace osgEarth
          */
         void openMapLayers();
 
+        //! Serializes the MapNode into a Config object
+        Config getConfig() const;
+
 
     public: // special purpose
 
@@ -235,22 +239,19 @@ namespace osgEarth
 
     private:
 
-        unsigned int               _id;
-        osg::ref_ptr<Map>          _map;
-        osg::ref_ptr< osg::Group > _models;
-        osg::ref_ptr< osg::Group > _overlayModels;
-        OverlayDecorator*          _overlayDecorator;
-        MapNodeOptions             _mapNodeOptions;
-        Config                     _externalConf;
-
-        // keep track of nodes created by model layers
-        typedef std::map<ModelLayer*, osg::ref_ptr< osg::Node > > ModelLayerNodeMap;
-        ModelLayerNodeMap        _modelLayerNodes;
-        osg::Group*              _maskLayerNode;
-        unsigned                 _lastNumBlacklistedFilenames;
-        osg::ref_ptr<TerrainEngineNode> _terrainEngine;
-        bool                     _terrainEngineInitialized;
-        osg::Group*              _terrainEngineContainer;
+        osg::ref_ptr<Map>  _map;
+        osg::Group*        _layerNodes;
+        OverlayDecorator*  _overlayDecorator;
+        MapNodeOptions     _mapNodeOptions;
+        Config             _externalConf;
+        osg::Group*        _maskLayerNode;
+        unsigned           _lastNumBlacklistedFilenames;
+        TerrainEngineNode* _terrainEngine;
+        bool               _terrainEngineInitialized;
+        osg::Group*        _terrainEngineContainer;
+        ResourceReleaser*  _resourceReleaser;
+        DrapingManager*    _drapingManager;
+        ClampingManager*   _clampingManager;
 
         std::vector< osg::ref_ptr<Extension> > _extensions;
 
@@ -258,29 +259,23 @@ namespace osgEarth
 
     public: // MapCallback proxy
 
-        void onModelLayerAdded( ModelLayer*, unsigned int );
-        void onModelLayerRemoved( ModelLayer* );
-        void onModelLayerMoved( ModelLayer* layer, unsigned int oldIndex, unsigned int newIndex );
-
-    public:
-        struct TileRangeData : public osg::Referenced {
-            TileRangeData(double minRange, double maxRange) : _minRange( minRange ), _maxRange( maxRange ) { }
-            double _minRange;
-            double _maxRange;
-        };
+        void onLayerAdded(Layer* layer, unsigned index);
+        void onLayerRemoved(Layer* layer, unsigned index);
+        void onLayerMoved(Layer* layer, unsigned oldIndex, unsigned newIndex);
 
     private:
 
-        osg::ref_ptr< MapCallback >        _mapCallback;
+        osg::ref_ptr< MapCallback > _mapCallback;
     
         void init();
 
-#if 0
-        MapNodeCullData* getCullData(osg::Camera* camera) const;
-        typedef std::map<osg::Camera*, osg::ref_ptr<MapNodeCullData> > CullDataMap;
-        mutable CullDataMap _cullData;
-        mutable Threading::ReadWriteMutex _cullDataMutex;
-#endif
+        DrapingManager* getDrapingManager();
+        friend class DrapingTechnique;
+        friend class DrapeableNode;
+        
+        ClampingManager* getClampingManager();
+        friend class ClampingTechnique;
+        friend class ClampableNode;
     };
 
 } // namespace osgEarth
diff --git a/src/osgEarth/MapNode.cpp b/src/osgEarth/MapNode.cpp
index dc22be0..7781c83 100644
--- a/src/osgEarth/MapNode.cpp
+++ b/src/osgEarth/MapNode.cpp
@@ -35,13 +35,17 @@
 #include <osgEarth/VirtualProgram>
 #include <osgEarth/OverlayDecorator>
 #include <osgEarth/TerrainEngineNode>
-#include <osgEarth/TextureCompositor>
+#include <osgEarth/TerrainResources>
 #include <osgEarth/ShaderGenerator>
 #include <osgEarth/SpatialReference>
 #include <osgEarth/MapModelChange>
+#include <osgEarth/Lighting>
+#include <osgEarth/ResourceReleaser>
 #include <osgEarth/URI>
 #include <osg/ArgumentParser>
 #include <osg/PagedLOD>
+#include <osgUtil/Optimizer>
+#include <typeinfo>
 
 using namespace osgEarth;
 
@@ -51,19 +55,29 @@ using namespace osgEarth;
 
 namespace
 {
+    /**
+     * A group that the osgUtil::Optimizer won't remove even when it's empty.
+     */
+    struct StickyGroup : public osg::Group { };
+
     // adapter that lets MapNode listen to Map events
     struct MapNodeMapCallbackProxy : public MapCallback
     {
         MapNodeMapCallbackProxy(MapNode* node) : _node(node) { }
 
-        void onModelLayerAdded( ModelLayer* layer, unsigned int index ) {
-            _node->onModelLayerAdded( layer, index );
+        void onLayerAdded(Layer* layer, unsigned index) {
+            _node->onLayerAdded(layer, index);
+            // for backwards compat until we refactor ModelLayer to use Layer::getNode
+            MapCallback::onLayerAdded(layer, index);
         }
-        void onModelLayerRemoved( ModelLayer* layer ) {
-            _node->onModelLayerRemoved( layer );
+        void onLayerRemoved(Layer* layer, unsigned index) {
+            _node->onLayerRemoved(layer, index);
+            // for backwards compat until we refactor ModelLayer to use Layer::getNode
+            MapCallback::onLayerRemoved(layer, index);
         }
-        void onModelLayerMoved( ModelLayer* layer, unsigned int oldIndex, unsigned int newIndex ) {
-            _node->onModelLayerMoved( layer, oldIndex, newIndex);
+        void onLayerMoved(Layer* layer, unsigned oldIndex, unsigned newIndex) {
+            _node->onLayerMoved(layer, oldIndex, newIndex);
+            MapCallback::onLayerMoved(layer, oldIndex, newIndex);
         }
 
         osg::observer_ptr<MapNode> _node;
@@ -71,11 +85,11 @@ namespace
 
     // callback that will run the MapNode installer on model layers so that
     // MapNodeObservers can have MapNode access
-    struct MapNodeObserverInstaller : public NodeOperation
+    struct MapNodeObserverInstaller : public SceneGraphCallback
     {
         MapNodeObserverInstaller( MapNode* mapNode ) : _mapNode( mapNode ) { }
 
-        void operator()( osg::Node* node )
+        virtual void onPostMergeNode(osg::Node* node)
         {
             if ( _mapNode.valid() && node )
             {
@@ -116,27 +130,6 @@ public:
                   node.setRange(0, 0, FLT_MAX);
                   node.setRange(1, FLT_MAX, FLT_MAX);
               }
-              else
-              {
-                  //If the child is not blacklisted, it is possible that it could have been blacklisted previously so reset the
-                  //ranges of both the first and second children.  This gives the second child another
-                  //chance to be traversed in case a layer was added that might have data.
-                  osg::ref_ptr< MapNode::TileRangeData > ranges = static_cast< MapNode::TileRangeData* >(node.getUserData());
-                  if (ranges)
-                  {
-                      if (node.getRangeMode() == osg::LOD::PIXEL_SIZE_ON_SCREEN)
-                      {
-                          node.setRange( 0, ranges->_minRange, ranges->_maxRange );
-                          node.setRange( 1, ranges->_maxRange, FLT_MAX );
-                      }
-                      else
-                      {
-                          node.setRange(0, ranges->_minRange, ranges->_maxRange);
-                          node.setRange(1, 0, ranges->_minRange);
-                      }
-                  }
-              }
-
           }
           traverse(node);
       }
@@ -186,34 +179,44 @@ MapNode::load(osg::ArgumentParser& args, const MapNodeOptions& defaults)
 //---------------------------------------------------------------------------
 
 MapNode::MapNode() :
-_map( new Map() )
+_map( new Map() ),
+_layerNodes(0L),
+_terrainEngine(0L)
 {
     init();
 }
 
 MapNode::MapNode( Map* map ) :
-_map( map )
+_map( map ),
+_layerNodes(0L),
+_terrainEngine(0L)
 {
     init();
 }
 
 MapNode::MapNode( const MapNodeOptions& options ) :
 _map( new Map() ),
-_mapNodeOptions( options )
+_mapNodeOptions( options ),
+_layerNodes(0L),
+_terrainEngine(0L)
 {
     init();
 }
 
 MapNode::MapNode( Map* map, const MapNodeOptions& options ) :
 _map( map? map : new Map() ),
-_mapNodeOptions( options )
+_mapNodeOptions( options ),
+_layerNodes(0L),
+_terrainEngine(0L)
 {
     init();
 }
 
 MapNode::MapNode( Map* map, const MapNodeOptions& options, bool autoInit ) :
 _map( map? map : new Map() ),
-_mapNodeOptions( options )
+_mapNodeOptions( options ),
+_layerNodes(0L),
+_terrainEngine(0L)
 {
     if ( autoInit )
     {
@@ -272,109 +275,98 @@ MapNode::init()
             << std::endl;
     }
 
+    // Create and install a GL resource releaser that this node and extensions can use.
+    _resourceReleaser = new ResourceReleaser();
+    this->addChild(_resourceReleaser);
+
     // TODO: not sure why we call this here
     _map->setGlobalOptions( local_options.get() );
 
     // load and attach the terrain engine, but don't initialize it until we need it
     const TerrainOptions& terrainOptions = _mapNodeOptions.getTerrainOptions();
 
-    _terrainEngine = TerrainEngineNodeFactory::create( _map.get(), terrainOptions );
+    _terrainEngine = TerrainEngineNodeFactory::create( terrainOptions );
     _terrainEngineInitialized = false;
 
+    if ( _terrainEngine )
+    {
+        _terrainEngine->setMap( _map.get(), terrainOptions );
+    }
+    else
+    {
+        OE_WARN << "FAILED to create a terrain engine for this map" << std::endl;
+    }
+
     // the engine needs a container so we can set lighting state on the container and
-    // not on the terrain engine itself. Setting the dynamic variance will prevent
-    // an optimizer from collapsing the empty group node.
-    _terrainEngineContainer = new osg::Group();
+    // not on the terrain engine itself. Makeing it a StickyGroup prevents the osgUtil
+    // Optimizer from collapsing it if it's empty
+    _terrainEngineContainer = new StickyGroup();
     _terrainEngineContainer->setDataVariance( osg::Object::DYNAMIC );
     this->addChild( _terrainEngineContainer );
 
     // initialize terrain-level lighting:
     if ( terrainOptions.enableLighting().isSet() )
     {
-        _terrainEngineContainer->getOrCreateStateSet()->addUniform(
-            Registry::shaderFactory()->createUniformForGLMode(GL_LIGHTING, *terrainOptions.enableLighting()) );
+        _terrainEngineContainer->getOrCreateStateSet()->setDefine(OE_LIGHTING_DEFINE, terrainOptions.enableLighting().get());
 
         _terrainEngineContainer->getOrCreateStateSet()->setMode(
             GL_LIGHTING,
             terrainOptions.enableLighting().value() ? 1 : 0 );
     }
 
-    if ( _terrainEngine )
-    {
-        // inform the terrain engine of the map information now so that it can properly
-        // initialize it's CoordinateSystemNode. This is necessary in order to support
-        // manipulators and to set up the texture compositor prior to frame-loop
-        // initialization.
-        _terrainEngine->preInitialize( _map.get(), terrainOptions );
-        _terrainEngineContainer->addChild( _terrainEngine );
-    }
-    else
-    {
-        OE_WARN << "FAILED to create a terrain engine for this map" << std::endl;
-    }
-
-    // make a group for the model layers.
-    // NOTE: for now, we are going to nullify any shader programs that occur above the model
-    // group, since it does not YET support shader composition. Programs defined INSIDE a
-    // model layer will still work OK though.
-    _models = new osg::Group();
-    _models->setName( "osgEarth::MapNode.modelsGroup" );
-    //_models->getOrCreateStateSet()->setRenderBinDetails(1, "RenderBin");
-    addChild( _models.get() );
-
     // a decorator for overlay models:
     _overlayDecorator = new OverlayDecorator();
+    _terrainEngineContainer->addChild(_overlayDecorator);
 
     // install the Draping technique for overlays:
-    {
-        DrapingTechnique* draping = new DrapingTechnique();
-
-        const char* envOverlayTextureSize = ::getenv("OSGEARTH_OVERLAY_TEXTURE_SIZE");
-
-        if ( _mapNodeOptions.overlayBlending().isSet() )
-            draping->setOverlayBlending( *_mapNodeOptions.overlayBlending() );
-        if ( envOverlayTextureSize )
-            draping->setTextureSize( as<int>(envOverlayTextureSize, 1024) );
-        else if ( _mapNodeOptions.overlayTextureSize().isSet() )
-            draping->setTextureSize( *_mapNodeOptions.overlayTextureSize() );
-        if ( _mapNodeOptions.overlayMipMapping().isSet() )
-            draping->setMipMapping( *_mapNodeOptions.overlayMipMapping() );
-        if ( _mapNodeOptions.overlayAttachStencil().isSet() )
-            draping->setAttachStencil( *_mapNodeOptions.overlayAttachStencil() );
-        if ( _mapNodeOptions.overlayResolutionRatio().isSet() )
-            draping->setResolutionRatio( *_mapNodeOptions.overlayResolutionRatio() );
-
-        draping->reestablish( getTerrainEngine() );
-        _overlayDecorator->addTechnique( draping );
-    }
+    DrapingTechnique* draping = new DrapingTechnique();
+
+    const char* envOverlayTextureSize = ::getenv("OSGEARTH_OVERLAY_TEXTURE_SIZE");
+
+    if ( _mapNodeOptions.overlayBlending().isSet() )
+        draping->setOverlayBlending( *_mapNodeOptions.overlayBlending() );
+    if ( envOverlayTextureSize )
+        draping->setTextureSize( as<int>(envOverlayTextureSize, 1024) );
+    else if ( _mapNodeOptions.overlayTextureSize().isSet() )
+        draping->setTextureSize( *_mapNodeOptions.overlayTextureSize() );
+    if ( _mapNodeOptions.overlayMipMapping().isSet() )
+        draping->setMipMapping( *_mapNodeOptions.overlayMipMapping() );
+    if ( _mapNodeOptions.overlayAttachStencil().isSet() )
+        draping->setAttachStencil( *_mapNodeOptions.overlayAttachStencil() );
+    if ( _mapNodeOptions.overlayResolutionRatio().isSet() )
+        draping->setResolutionRatio( *_mapNodeOptions.overlayResolutionRatio() );
+
+    draping->reestablish( _terrainEngine );
+    _overlayDecorator->addTechnique( draping );
+    _drapingManager = &draping->getDrapingManager();
 
     // install the Clamping technique for overlays:
-    {
-        _overlayDecorator->addTechnique( new ClampingTechnique() );
-    }
+    ClampingTechnique* clamping = new ClampingTechnique();
+    _overlayDecorator->addTechnique(clamping);
+    _clampingManager = &clamping->getClampingManager();
 
-    addTerrainDecorator( _overlayDecorator );
+    _overlayDecorator->setTerrainEngine(_terrainEngine);
+    _overlayDecorator->addChild(_terrainEngine);
 
-    // install any pre-existing model layers:
-    ModelLayerVector modelLayers;
-    _map->getModelLayers( modelLayers );
-    int modelLayerIndex = 0;
-    for( ModelLayerVector::const_iterator k = modelLayers.begin(); k != modelLayers.end(); k++, modelLayerIndex++ )
-    {
-        onModelLayerAdded( k->get(), modelLayerIndex );
-    }
+    // make a group for the model layers. (Sticky otherwise the osg optimizer will remove it)
+    _layerNodes = new StickyGroup();
+    _layerNodes->setName( "osgEarth::MapNode.layerNodes" );
+    this->addChild( _layerNodes );
 
+    // Callback listens for changes in the Map:
     _mapCallback = new MapNodeMapCallbackProxy(this);
-    // install a layer callback for processing further map actions:
-    _map->addMapCallback( _mapCallback.get()  );
+    _map->addMapCallback( _mapCallback.get() );
+
+    // Simulate adding all existing layers:
+    _mapCallback->invokeOnLayerAdded(_map.get());
+
 
     osg::StateSet* stateset = getOrCreateStateSet();
+    stateset->setName("MapNode");
 
     if ( _mapNodeOptions.enableLighting().isSet() )
     {
-        stateset->addUniform(Registry::shaderFactory()->createUniformForGLMode(
-            GL_LIGHTING,
-            _mapNodeOptions.enableLighting().value() ? 1 : 0));
+        stateset->setDefine(OE_LIGHTING_DEFINE, terrainOptions.enableLighting().get());
 
         stateset->setMode(
             GL_LIGHTING,
@@ -406,12 +398,18 @@ MapNode::init()
         }
     }
 
-    // install the default rendermode uniform:
-    stateset->addUniform( new osg::Uniform("oe_isPickCamera", false) );
-    stateset->addUniform( new osg::Uniform("oe_isShadowCamera", false) );
+    // install a default material for everything in the map
+    osg::Material* defaultMaterial = new MaterialGL3();
+    defaultMaterial->setDiffuse(defaultMaterial->FRONT, osg::Vec4(1,1,1,1));
+    defaultMaterial->setAmbient(defaultMaterial->FRONT, osg::Vec4(1,1,1,1));
+    stateset->setAttributeAndModes(defaultMaterial, 1);
+    MaterialCallback().operator()(defaultMaterial, 0L);
 
     dirtyBound();
 
+    // install a callback that sets the viewport size uniform:
+    this->addCullCallback(new InstallViewportSizeUniform());
+
     // register for event traversals so we can deal with blacklisted filenames
     ADJUST_EVENT_TRAV_COUNT( this, 1 );
 
@@ -423,15 +421,73 @@ MapNode::~MapNode()
 {
     _map->removeMapCallback( _mapCallback.get() );
 
-    ModelLayerVector modelLayers;
-    _map->getModelLayers( modelLayers );
-    //Remove our model callback from any of the model layers in the map
-    for (osgEarth::ModelLayerVector::iterator itr = modelLayers.begin(); itr != modelLayers.end(); ++itr)
+    _mapCallback->invokeOnLayerRemoved(_map.get());
+    //ModelLayerVector modelLayers;
+    //_map->getLayers( modelLayers );
+    ////Remove our model callback from any of the model layers in the map
+    //for (osgEarth::ModelLayerVector::iterator itr = modelLayers.begin(); itr != modelLayers.end(); ++itr)
+    //{
+    //    this->onModelLayerRemoved( itr->get() );
+    //}
+
+    _map->clear();
+
+    this->clearExtensions();
+
+    osg::observer_ptr<TerrainEngineNode> te = _terrainEngine;
+    removeChildren(0, getNumChildren());
+    
+    OE_DEBUG << LC << "~MapNode (TerrainEngine="
+        << (te.valid()? te.get()->referenceCount() : 0) << ", Map=" << _map->referenceCount() << ")\n";
+}
+
+Config
+MapNode::getConfig() const
+{
+    Config mapConf("map");
+    mapConf.set("version", "2");
+
+    MapFrame mapf( _map.get() );
+
+    // the map and node options:
+    Config optionsConf = _map->getInitialMapOptions().getConfig();
+    optionsConf.merge( getMapNodeOptions().getConfig() );
+    mapConf.add( "options", optionsConf );
+
+    // the layers
+    LayerVector layers;
+    mapf.getLayers(layers);
+
+    for (LayerVector::const_iterator i = layers.begin(); i != layers.end(); ++i)
+    {
+        const Layer* layer = i->get();
+
+        Config layerConf = layer->getConfig();
+        if (!layerConf.empty() && !layerConf.key().empty())
+        {
+            mapConf.add(layerConf);
+        }
+    }
+
+    typedef std::vector< osg::ref_ptr<Extension> > Extensions;
+    for(Extensions::const_iterator i = getExtensions().begin(); i != getExtensions().end(); ++i)
     {
-        this->onModelLayerRemoved( itr->get() );
+        Extension* e = i->get();
+        Config conf = e->getConfigOptions().getConfig();
+        if ( !conf.key().empty() )
+        {
+            mapConf.add( conf );
+        }
     }
 
-    this->clearExtensions();
+    Config ext = externalConfig();
+    if ( !ext.empty() )
+    {
+        ext.key() = "external";
+        mapConf.add( ext );
+    }
+
+    return mapConf;
 }
 
 osg::BoundingSphere
@@ -440,10 +496,14 @@ MapNode::computeBound() const
     osg::BoundingSphere bs;
     if ( getTerrainEngine() )
     {
-        bs.expandBy(  getTerrainEngine()->getBound() );
+        bs.expandBy( getTerrainEngine()->getBound() );
+    }
+
+    if (_layerNodes)
+    {
+        bs.expandBy( _layerNodes->getBound() );
     }
-    if ( _models.valid() )
-        bs.expandBy( _models->getBound() );
+
     return bs;
 }
 
@@ -482,16 +542,15 @@ MapNode::getTerrain() const
 TerrainEngineNode*
 MapNode::getTerrainEngine() const
 {
-    if ( !_terrainEngineInitialized && _terrainEngine )
-    {
-        _terrainEngine->postInitialize( _map.get(), getMapNodeOptions().getTerrainOptions() );
-        MapNode* me = const_cast< MapNode* >(this);
-        me->_terrainEngineInitialized = true;
-        me->dirtyBound();
-    }
     return _terrainEngine;
 }
 
+ResourceReleaser*
+MapNode::getResourceReleaser() const
+{
+    return _resourceReleaser;
+}
+
 void
 MapNode::addExtension(Extension* extension, const osgDB::Options* options)
 {
@@ -513,7 +572,7 @@ MapNode::addExtension(Extension* extension, const osgDB::Options* options)
             extensionIF->connect( this );
         }
 
-        OE_INFO << LC << "Added extension [" << extension->getName() << "]\n";
+        OE_INFO << LC << "Added extension \"" << extension->getName() << "\"\n";
     }
 }
 
@@ -548,18 +607,18 @@ MapNode::clearExtensions()
 }
 
 osg::Group*
-MapNode::getModelLayerGroup() const
+MapNode::getLayerNodeGroup() const
 {
-    return _models.get();
+    return _layerNodes;
 }
 
 osg::Node*
-MapNode::getModelLayerNode( ModelLayer* layer ) const
+MapNode::getLayerNode(Layer* layer) const
 {
-    ModelLayerNodeMap::const_iterator i = _modelLayerNodes.find( layer );
-    return i != _modelLayerNodes.end() ? i->second : 0L;
+    return layer ? layer->getOrCreateNode() : 0L;
 }
 
+
 const MapNodeOptions&
 MapNode::getMapNodeOptions() const
 {
@@ -578,117 +637,102 @@ MapNode::isGeocentric() const
     return _map->isGeocentric();
 }
 
+namespace
+{
+    void rebuildLayerNodes(const Map* map, osg::Group* layerNodes)
+    {
+        layerNodes->removeChildren(0, layerNodes->getNumChildren());
+
+        LayerVector layers;
+        map->getLayers(layers);
+        for (LayerVector::iterator i = layers.begin(); i != layers.end(); ++i)
+        {
+            Layer* layer = i->get();
+            osg::Node* node = layer->getOrCreateNode();
+            if (node)
+            {
+                osg::Group* container = new osg::Group();
+                container->setName(layer->getName());
+                container->addChild(node);
+                container->setStateSet(layer->getStateSet());
+                layerNodes->addChild(container);
+            }
+        }
+    }
+}
+
 void
-MapNode::onModelLayerAdded( ModelLayer* layer, unsigned int index )
+MapNode::onLayerAdded(Layer* layer, unsigned index)
 {
-    if ( !layer->getEnabled() )
+    if (!layer || !layer->getEnabled())
         return;
+    
+    // Communicate terrain resources to the layer:
+    layer->setTerrainResources(getTerrainEngine()->getResources());
 
-    // install a noe operation that will associate this mapnode with
-    // any MapNodeObservers loaded by the model layer:
-    ModelSource* modelSource = layer->getModelSource();
-    if ( modelSource )
+    // Compatibility, until we refactor things.
+    ModelLayer* modelLayer = dynamic_cast<ModelLayer*>(layer);
+    if (modelLayer)
     {
-        // install a post-processing callback on the ModelLayer's source
-        // so we can update the MapNode on new data that comes in:
-        modelSource->addPostMergeOperation( new MapNodeObserverInstaller(this) );
+        // TODO:  Why go through all the MapNodeObserver stuff when we can just pass in the MapNode here?
+        modelLayer->getOrCreateSceneGraph(_map.get(), _map->getReadOptions(), 0L);
+        // Install the MapNodeObserverInstaller so that MapNodeObservers will be notified of the MapNode.
+        modelLayer->getSceneGraphCallbacks()->add(new MapNodeObserverInstaller(this));
     }
 
-    // create the scene graph:
-    osg::Node* node = layer->getOrCreateSceneGraph( _map.get(), _map->getReadOptions(), 0L );
-
-    if ( node )
+    // Create the layer's node, if it has one:
+    osg::Node* node = layer->getOrCreateNode();
+    if (node)
     {
-        if ( _modelLayerNodes.find( layer ) != _modelLayerNodes.end() )
-        {
-            OE_WARN
-                << "Illegal: tried to add the name model layer more than once: "
-                << layer->getName()
-                << std::endl;
-        }
-        else
-        {
-            if ( dynamic_cast<TerrainDecorator*>(node) )
-            {
-                OE_INFO << LC << "Installing overlay node" << std::endl;
-                addTerrainDecorator( node->asGroup() );
-            }
-            else
-            {
-                _models->insertChild( index, node );
-            }
+        // Call setMapNode on any MapNodeObservers on this initial creation.
+        MapNodeReplacer replacer( this );
+        //node->accept( replacer );
+
+        rebuildLayerNodes(_map.get(), _layerNodes);
 
-            ModelSource* ms = layer->getModelSource();
+        OE_DEBUG << LC << "Adding node from layer \"" << layer->getName() << "\" to the scene graph\n";
 
-            if ( ms )
+        // TODO: move this logic into ModelLayer.
+        if (modelLayer)
+        {
+            ModelSource* ms = modelLayer->getModelSource();
+            if (ms)
             {
                 // enfore a rendering bin if necessary:
-                if ( ms->getOptions().renderOrder().isSet() )
+                if (ms->getOptions().renderOrder().isSet())
                 {
                     osg::StateSet* mss = node->getOrCreateStateSet();
                     mss->setRenderBinDetails(
                         ms->getOptions().renderOrder().value(),
                         mss->getBinName().empty() ? "DepthSortedBin" : mss->getBinName());
                 }
-                if ( ms->getOptions().renderBin().isSet() )
+                if (ms->getOptions().renderBin().isSet())
                 {
                     osg::StateSet* mss = node->getOrCreateStateSet();
                     mss->setRenderBinDetails(
                         mss->getBinNumber(),
-                        ms->getOptions().renderBin().get() );
+                        ms->getOptions().renderBin().get());
                 }
             }
-
-            _modelLayerNodes[ layer ] = node;
         }
-
-        dirtyBound();
     }
 }
 
 void
-MapNode::onModelLayerRemoved( ModelLayer* layer )
+MapNode::onLayerRemoved(Layer* layer, unsigned index)
 {
-    if ( layer )
+    if (layer && layer->getOrCreateNode())
     {
-        // look up the node associated with this model layer.
-        ModelLayerNodeMap::iterator i = _modelLayerNodes.find( layer );
-        if ( i != _modelLayerNodes.end() )
-        {
-            osg::Node* node = i->second;
-
-            if ( dynamic_cast<TerrainDecorator*>( node ) )
-            {
-                removeTerrainDecorator( node->asGroup() );
-            }
-            else
-            {
-                _models->removeChild( node );
-            }
-
-            _modelLayerNodes.erase( i );
-        }
-
-        dirtyBound();
+        rebuildLayerNodes(_map.get(), _layerNodes);
     }
 }
 
 void
-MapNode::onModelLayerMoved( ModelLayer* layer, unsigned int oldIndex, unsigned int newIndex )
+MapNode::onLayerMoved(Layer* layer, unsigned oldIndex, unsigned newIndex)
 {
-    if ( layer )
+    if (layer && layer->getOrCreateNode())
     {
-        // look up the node associated with this model layer.
-        ModelLayerNodeMap::iterator i = _modelLayerNodes.find( layer );
-        if ( i != _modelLayerNodes.end() )
-        {
-            osg::ref_ptr<osg::Node> node = i->second;
-
-            _models->removeChild( node.get() );
-            _models->insertChild( newIndex, node.get() );
-        }
-
-        dirtyBound();
+        rebuildLayerNodes(_map.get(), _layerNodes);
     }
 }
 
@@ -706,46 +750,6 @@ namespace
     };
 }
 
-void
-MapNode::addTerrainDecorator(osg::Group* decorator)
-{
-    if ( _terrainEngine )
-    {
-        decorator->addChild( _terrainEngine );
-        _terrainEngine->getParent(0)->replaceChild( _terrainEngine, decorator );
-        dirtyBound();
-
-        TerrainDecorator* td = dynamic_cast<TerrainDecorator*>( decorator );
-        if ( td )
-            td->onInstall( _terrainEngine );
-    }
-}
-
-void
-MapNode::removeTerrainDecorator(osg::Group* decorator)
-{
-    if ( _terrainEngine )
-    {
-        TerrainDecorator* td = dynamic_cast<TerrainDecorator*>( decorator );
-        if ( td )
-            td->onUninstall( _terrainEngine );
-
-        osg::ref_ptr<osg::Node> child = _terrainEngine;
-        for( osg::Group* g = child->getParent(0); g != _terrainEngineContainer; )
-        {
-            if ( g == decorator )
-            {
-                g->getParent(0)->replaceChild( g, child );
-                g->removeChild( child );
-                break;
-            }
-            child = g;
-            g = g->getParent(0);
-        }
-        dirtyBound();
-    }
-}
-
 namespace
 {
     template<typename T> void tryOpenLayer(T* layer)
@@ -766,24 +770,11 @@ MapNode::openMapLayers()
 {
     MapFrame frame(_map.get());
 
-    for (unsigned i = 0; i < frame.imageLayers().size(); ++i)
-    {
-        tryOpenLayer(frame.getImageLayerAt(i));
-    }
-
-    for (unsigned i = 0; i < frame.elevationLayers().size(); ++i)
-    {
-        tryOpenLayer(frame.getElevationLayerAt(i));
-    }
-
-    for (unsigned i = 0; i < frame.modelLayers().size(); ++i)
-    {
-        tryOpenLayer(frame.getModelLayerAt(i));
-    }
-
-    for (unsigned i = 0; i < frame.terrainMaskLayers().size(); ++i)
+    for (LayerVector::const_iterator i = frame.layers().begin();
+        i != frame.layers().end();
+        ++i)
     {
-        tryOpenLayer(frame.terrainMaskLayers().at(i).get());
+        tryOpenLayer(i->get());
     }
 }
 
@@ -801,30 +792,25 @@ MapNode::traverse( osg::NodeVisitor& nv )
             _terrainEngine->accept( v );
         }
 
-        // This is a placeholder for later, if we decide to delay the automatic opening of
-        // layers until the first traversal.
-#if 0
-        // if the model has changed, we need to queue up an update so we can open new layers.
-        if (_mapRevisionMonitor.outOfSyncWith(_map->getDataModelRevision()))
-        {
-            openMapLayers();
-            _mapRevisionMonitor.sync(_map->getDataModelRevision());
-        }
-#endif
-
         // traverse:
         std::for_each( _children.begin(), _children.end(), osg::NodeAcceptOp(nv) );
     }
 
-    else if (nv.getVisitorType() == nv.UPDATE_VISITOR || nv.getVisitorType() == nv.CULL_VISITOR)
-    {
-        // put the MapNode in the visitor data and traverse.
-        VisitorData::store(nv, "osgEarth::MapNode", this);
-        std::for_each( _children.begin(), _children.end(), osg::NodeAcceptOp(nv) );
-    }
-
     else
     {
-        osg::Group::traverse( nv );
+        if (dynamic_cast<osgUtil::BaseOptimizerVisitor*>(&nv) == 0L)
+            osg::Group::traverse( nv );
     }
 }
+
+DrapingManager*
+MapNode::getDrapingManager()
+{
+    return _drapingManager;
+}
+
+ClampingManager*
+MapNode::getClampingManager()
+{
+    return _clampingManager;
+}
diff --git a/src/osgEarth/MapNodeOptions b/src/osgEarth/MapNodeOptions
index 9c633df..9277c55 100644
--- a/src/osgEarth/MapNodeOptions
+++ b/src/osgEarth/MapNodeOptions
@@ -36,9 +36,16 @@ namespace osgEarth
     class OSGEARTH_EXPORT MapNodeOptions : public ConfigOptions
     {
     public:
+        //! Construct from a Config serialization
         MapNodeOptions( const Config& conf =Config() );
+
+        //! Construct from a set of terrain options
         MapNodeOptions( const TerrainOptions& terrainOpts );
+
+        //! Copy construct
         MapNodeOptions( const MapNodeOptions& rhs );
+
+        //! Destruct
         virtual ~MapNodeOptions();
 
         /** 
diff --git a/src/osgEarth/MapNodeOptions.cpp b/src/osgEarth/MapNodeOptions.cpp
index 2447ec3..a33be96 100644
--- a/src/osgEarth/MapNodeOptions.cpp
+++ b/src/osgEarth/MapNodeOptions.cpp
@@ -89,7 +89,7 @@ MapNodeOptions::getConfig() const
     Config conf; // start with a fresh one since this is a FINAL object  // = ConfigOptions::getConfig();
     conf.key() = "options";
 
-    conf.updateObjIfSet( "proxy",                    _proxySettings );
+    conf.setObj( "proxy",                    _proxySettings );
     conf.updateIfSet   ( "cache_only",               _cacheOnly );
     conf.updateIfSet   ( "lighting",                 _enableLighting );
     conf.updateIfSet   ( "terrain",                  _terrainOptionsConf );
diff --git a/src/osgEarth/MapOptions b/src/osgEarth/MapOptions
index 5a74e39..a76ee16 100644
--- a/src/osgEarth/MapOptions
+++ b/src/osgEarth/MapOptions
@@ -46,11 +46,11 @@ namespace osgEarth
         };		
 
     public:
+        //! Construct from serialized config options
         MapOptions( const ConfigOptions& options =ConfigOptions() )
             : ConfigOptions          ( options ),
               _cachePolicy           ( ),
               _cstype                ( CSTYPE_GEOCENTRIC ),
-              _referenceURI          ( "" ),
               _elevationInterpolation( INTERP_BILINEAR )
         {
             fromConfig(_conf);
@@ -66,7 +66,10 @@ namespace osgEarth
         const optional<std::string>& name() const { return _name; }
 
         /**
-         * The coordinate system type of the map (default is CSTYPE_GEOCENTRIC)
+         * The coordinate system type of the map.
+         * By default this is "geocentric" (round earth), unless you specify a profile
+         * below, in which case it will be "projected" if the profile contains a 
+         * projected SRS.
          */
         optional<CoordinateSystemType>& coordSysType() { return _cstype; }
         const optional<CoordinateSystemType>& coordSysType() const { return _cstype; }
@@ -96,15 +99,6 @@ namespace osgEarth
          */
         optional<ElevationInterpolation>& elevationInterpolation(void) { return _elevationInterpolation; }
         const optional<ElevationInterpolation>& elevationInterpolation(void) const { return _elevationInterpolation;}
-
-        
-    public:
-        /**
-         * A reference location that drivers can use to load data from relative locations.
-         * NOTE: this is a runtime-only property and is NOT serialized in the ConfigOptions.
-         */
-        optional<std::string>& referenceURI() { return _referenceURI; }
-        const optional<std::string>& referenceURI() const { return _referenceURI; }
     
     public:
         Config getConfig() const;
@@ -122,7 +116,6 @@ namespace osgEarth
         optional<CacheOptions>           _cacheOptions;
         optional<CachePolicy>            _cachePolicy;
         optional<CoordinateSystemType>   _cstype;
-        optional<std::string>            _referenceURI;
         optional<ElevationInterpolation> _elevationInterpolation;
     };
 }
diff --git a/src/osgEarth/MapOptions.cpp b/src/osgEarth/MapOptions.cpp
index dc95195..6922298 100644
--- a/src/osgEarth/MapOptions.cpp
+++ b/src/osgEarth/MapOptions.cpp
@@ -40,7 +40,6 @@ MapOptions::fromConfig( const Config& conf )
     conf.getIfSet( "type", "round",      _cstype, CSTYPE_GEOCENTRIC );
     conf.getIfSet( "type", "projected",  _cstype, CSTYPE_PROJECTED );
     conf.getIfSet( "type", "flat",       _cstype, CSTYPE_PROJECTED );
-    conf.getIfSet( "type", "cube",       _cstype, CSTYPE_GEOCENTRIC_CUBE );
 
     conf.getIfSet( "elevation_interpolation", "nearest",     _elevationInterpolation, INTERP_NEAREST);
     conf.getIfSet( "elevation_interpolation", "average",     _elevationInterpolation, INTERP_AVERAGE);
@@ -51,22 +50,21 @@ MapOptions::fromConfig( const Config& conf )
 Config
 MapOptions::getConfig() const
 {
-    Config conf = ConfigOptions::newConfig();
+    Config conf = ConfigOptions::getConfig();
 
     conf.updateIfSet   ( "name",         _name );
-    conf.updateObjIfSet( "profile",      _profileOptions );
-    conf.updateObjIfSet( "cache",        _cacheOptions );
-    conf.updateObjIfSet( "cache_policy", _cachePolicy );
+    conf.setObj( "profile",      _profileOptions );
+    conf.setObj( "cache",        _cacheOptions );
+    conf.setObj( "cache_policy", _cachePolicy );
 
     // all variations:
-    conf.updateIfSet( "type", "geocentric", _cstype, CSTYPE_GEOCENTRIC );
-    conf.updateIfSet( "type", "projected",  _cstype, CSTYPE_PROJECTED );
-    conf.updateIfSet( "type", "cube",       _cstype, CSTYPE_GEOCENTRIC_CUBE );
+    conf.set( "type", "geocentric", _cstype, CSTYPE_GEOCENTRIC );
+    conf.set( "type", "projected",  _cstype, CSTYPE_PROJECTED );
 
-    conf.updateIfSet( "elevation_interpolation", "nearest",     _elevationInterpolation, INTERP_NEAREST);
-    conf.updateIfSet( "elevation_interpolation", "average",     _elevationInterpolation, INTERP_AVERAGE);
-    conf.updateIfSet( "elevation_interpolation", "bilinear",    _elevationInterpolation, INTERP_BILINEAR);
-    conf.updateIfSet( "elevation_interpolation", "triangulate", _elevationInterpolation, INTERP_TRIANGULATE);
+    conf.set( "elevation_interpolation", "nearest",     _elevationInterpolation, INTERP_NEAREST);
+    conf.set( "elevation_interpolation", "average",     _elevationInterpolation, INTERP_AVERAGE);
+    conf.set( "elevation_interpolation", "bilinear",    _elevationInterpolation, INTERP_BILINEAR);
+    conf.set( "elevation_interpolation", "triangulate", _elevationInterpolation, INTERP_TRIANGULATE);
 
     return conf;
 }
diff --git a/src/osgEarth/MaskLayer b/src/osgEarth/MaskLayer
index e856a4a..1d134c7 100644
--- a/src/osgEarth/MaskLayer
+++ b/src/osgEarth/MaskLayer
@@ -33,31 +33,15 @@ namespace osgEarth
     /**
      * Configuration options for a MaskLayer.
      */
-    class OSGEARTH_EXPORT MaskLayerOptions : public ConfigOptions
+    class OSGEARTH_EXPORT MaskLayerOptions : public LayerOptions
     {
-    public:        
-        MaskLayerOptions(const ConfigOptions& options =ConfigOptions() );
-
-        MaskLayerOptions(const std::string& name, const MaskSourceOptions& driverOptions =MaskSourceOptions() );
+    public:
+        MaskLayerOptions(const ConfigOptions& options =ConfigOptions());
 
         /** dtor */
         virtual ~MaskLayerOptions() { }
 
-        /**
-         * The readable name of the layer.
-         */
-        optional<std::string>& name() { return _name; }
-        const optional<std::string>& name() const { return _name; }
-
-        /**
-         * Options for the underlying model source driver.
-         */
-        optional<MaskSourceOptions>& driver() { return _driver; }
-        const optional<MaskSourceOptions>& driver() const { return _driver; }
-
-        /**
-         * Gets or sets the minimum of detail for which this layer should generate data.
-         */
+        /** Minimum of detail for which this layer should apply. */
         optional<unsigned>& minLevel() { return _minLevel; }
         const optional<unsigned>& minLevel() const { return _minLevel; }
 
@@ -69,7 +53,6 @@ namespace osgEarth
         void fromConfig( const Config& conf );
         void setDefaults();
 
-        optional<std::string>       _name;
         optional<MaskSourceOptions> _driver;
         optional<unsigned>          _minLevel;
     };
@@ -83,73 +66,29 @@ namespace osgEarth
     class OSGEARTH_EXPORT MaskLayer : public Layer
     {
     public:
-        /**
-         * Constructs a new mask layer based on a configuration setup.
-         */
-        MaskLayer( const MaskLayerOptions& options =MaskLayerOptions() );
-
-        /**
-         * Constructs a new mask layer with a user-provided driver options.
-         */
-        MaskLayer( const std::string& name, const MaskSourceOptions& options );
-        
-        /**
-         * Constructs a new mask layer with a user-provided mask source.
-         */
-        MaskLayer(const MaskLayerOptions& options, MaskSource* source );
-
-        /** dtor */
-        virtual ~MaskLayer() { }
-
-        /**
-         * Access the underlying mask source.
-         */
-        MaskSource* getMaskSource() const { return _maskSource.get(); }
-
-        /** 
-         * Gets the name of this mask layer
-         */
-        const std::string& getName() const { return *_runtimeOptions.name(); }
+        META_Layer(osgEarth, MaskLayer, MaskLayerOptions);
 
         /**
          * Minimum terrain LOD at which masking should occur
          */
-        unsigned getMinLevel() const { return *_runtimeOptions.minLevel(); }
+        unsigned getMinLevel() const { return options().minLevel().get(); }
 
         /** 
          * Gets the geometric boundary polygon representing the area of the
          * terrain to mask out.
          */
-        osg::Vec3dArray* getOrCreateMaskBoundary(
+        virtual osg::Vec3dArray* getOrCreateMaskBoundary(
             float                   heightScale,
             const SpatialReference* srs,
-            ProgressCallback*       progress );
-
-    public:
-
-        void setReadOptions(const osgDB::Options* readOptions);
-
-        const Status& open();
-
-        const Status& getStatus() const { return _status; }
+            ProgressCallback*       progress) { return 0L; }
 
     protected:
 
-        Status initialize();
+        /** Create from subclass. */
+        MaskLayer(MaskLayerOptions* =0L);
 
-        void setStatus(const Status& value) { _status = value; }
-
-    private:
-        MaskLayerOptions              _initOptions;
-        MaskLayerOptions              _runtimeOptions;
-        osg::ref_ptr<MaskSource>      _maskSource;
-        Revision                      _maskSourceRev;
-        osg::ref_ptr<osg::Vec3dArray> _boundary;
-        osg::ref_ptr<osgDB::Options>  _readOptions;
-        OpenThreads::Mutex            _mutex;
-        Status                        _status;
-
-        void copyOptions();
+        /** dtor */
+        virtual ~MaskLayer() { }
     };
 
     typedef std::vector< osg::ref_ptr<MaskLayer> > MaskLayerVector;
diff --git a/src/osgEarth/MaskLayer.cpp b/src/osgEarth/MaskLayer.cpp
index fab35c4..75aa2a2 100644
--- a/src/osgEarth/MaskLayer.cpp
+++ b/src/osgEarth/MaskLayer.cpp
@@ -23,25 +23,17 @@
 #define LC "[MaskLayer] "
 
 using namespace osgEarth;
+
 //------------------------------------------------------------------------
 
-MaskLayerOptions::MaskLayerOptions( const ConfigOptions& options ) :
-ConfigOptions( options ),
+MaskLayerOptions::MaskLayerOptions(const ConfigOptions& options) :
+LayerOptions(options),
 _minLevel( 0 )
 {
     setDefaults();
     fromConfig( _conf ); 
 }
 
-MaskLayerOptions::MaskLayerOptions( const std::string& name, const MaskSourceOptions& driverOptions ) :
-ConfigOptions()
-{
-    setDefaults();
-    fromConfig( _conf );
-    _name = name;
-    _driver = driverOptions;
-}
-
 void
 MaskLayerOptions::setDefaults()
 {
@@ -51,18 +43,15 @@ MaskLayerOptions::setDefaults()
 Config
 MaskLayerOptions::getConfig() const
 {
-    Config conf = ConfigOptions::getConfig();
-
-    conf.updateIfSet( "name", _name );
-    conf.updateIfSet( "min_level", _minLevel );
-
+    Config conf = LayerOptions::getConfig();
+    conf.key() = "mask";
+    conf.addIfSet( "min_level", _minLevel );
     return conf;
 }
 
 void
 MaskLayerOptions::fromConfig( const Config& conf )
 {
-    conf.getIfSet( "name", _name );
     conf.getIfSet( "min_level", _minLevel );
 }
 
@@ -75,103 +64,86 @@ MaskLayerOptions::mergeConfig( const Config& conf )
 
 //------------------------------------------------------------------------
 
-MaskLayer::MaskLayer( const MaskLayerOptions& options ) :
-_initOptions( options )
-{
-    copyOptions();
-}
-
-MaskLayer::MaskLayer( const std::string& name, const MaskSourceOptions& options ) :
-_initOptions( MaskLayerOptions( name, options ) )
-{
-    copyOptions();
-}
-
-MaskLayer::MaskLayer( const MaskLayerOptions& options, MaskSource* source ) :
-_maskSource( source ),
-_initOptions( options )
-{
-    copyOptions();
-}
-
-void
-MaskLayer::copyOptions()
-{
-    _runtimeOptions = _initOptions;
-}
-
-void
-MaskLayer::setReadOptions(const osgDB::Options* readOptions)
-{
-    _readOptions = Registry::cloneOrCreateOptions(readOptions);
-}
-
-const Status&
-MaskLayer::open()
-{
-    _status = initialize();
-    return _status;
-}
-
-Status
-MaskLayer::initialize()
+//MaskLayer::MaskLayer(const MaskLayerOptions& options) :
+//Layer(&_optionsConcrete),
+//_options(&_optionsConcrete),
+//_optionsConcrete(options)
+//{
+//    init();
+//}
+
+//MaskLayer::MaskLayer(const MaskLayerOptions& options, MaskSource* source) :
+//Layer(&_optionsConcrete),
+//_options(&_optionsConcrete),
+//_optionsConcrete(options),
+//_maskSource(source)
+//{
+//    init();
+//}
+
+MaskLayer::MaskLayer(MaskLayerOptions* optionsPtr) :
+Layer(optionsPtr),
+_options(optionsPtr)
 {
-    if ( !_maskSource.valid() && _initOptions.driver().isSet() )
-    {
-        _maskSource = MaskSourceFactory::create( *_initOptions.driver() );
-        if (!_maskSource.valid())
-        {
-            return Status::Error(Status::ServiceUnavailable, Stringify()<<"Failed to create mask driver (" << _initOptions.driver()->getDriver() << ")");
-        }
-    }
-
-    if ( _maskSource.valid() )
-    {
-        const Status& sourceStatus = _maskSource->open(_readOptions.get());  
-        if (sourceStatus.isError())
-        {
-            return sourceStatus;
-        }
-    }
-
-    return Status::OK();
+    //nop - init will be called by base class
 }
 
-osg::Vec3dArray*
-MaskLayer::getOrCreateMaskBoundary( float heightScale, const SpatialReference *srs, ProgressCallback* progress )
-{
-    if (getStatus().isError())
-    {
-        return 0L;
-    }
-
-    OpenThreads::ScopedLock< OpenThreads::Mutex > lock( _mutex );
-    if ( _maskSource.valid() )
-    {
-        // if the model source has changed, regenerate the node.
-        if ( _boundary.valid() && !_maskSource->inSyncWith(_maskSourceRev) )
-        {
-            _boundary = 0L;
-        }
-
-        if ( !_boundary.valid() )
-        {
-			_boundary = _maskSource->createBoundary( srs, progress );
-            
-            if (_boundary.valid())
-            {
-			    for (osg::Vec3dArray::iterator vIt = _boundary->begin(); vIt != _boundary->end(); ++vIt)
-    				vIt->z() = vIt->z() * heightScale;
-
-                _maskSource->sync( _maskSourceRev );
-            }
-        }
-    }
-
-    if (!_boundary.valid())
-    {
-        setStatus(Status::Error("Failed to create masking boundary"));
-    }
-
-    return _boundary.get();
-}
+//const Status&
+//MaskLayer::open()
+//{
+//    if (!_maskSource.valid() && !options().driver().isSet())
+//    {
+//        return setStatus(Status::Error(Status::ConfigurationError, "Missing data source for mask geometry"));
+//    }
+//
+//    if (!_maskSource.valid() && options().driver().isSet())
+//    {
+//        _maskSource = MaskSourceFactory::create(options());
+//        if (!_maskSource.valid())
+//        {
+//            return setStatus(Status::Error(Status::ServiceUnavailable, 
+//                Stringify() << "Failed to load mask driver" << options().name()));
+//        }
+//    }
+//
+//    return setStatus(_maskSource->open(getReadOptions()));
+//}
+
+//osg::Vec3dArray*
+//MaskLayer::getOrCreateMaskBoundary( float heightScale, const SpatialReference *srs, ProgressCallback* progress )
+//{
+//    if (getStatus().isError())
+//    {
+//        return 0L;
+//    }
+//
+//    OpenThreads::ScopedLock< OpenThreads::Mutex > lock( _mutex );
+//    if ( _maskSource.valid() )
+//    {
+//        // if the model source has changed, regenerate the node.
+//        if ( _boundary.valid() && !_maskSource->inSyncWith(_maskSourceRev) )
+//        {
+//            _boundary = 0L;
+//        }
+//
+//        if ( !_boundary.valid() )
+//        {
+//			_boundary = _maskSource->createBoundary( srs, progress );
+//            
+//            if (_boundary.valid())
+//            {
+//			    for (osg::Vec3dArray::iterator vIt = _boundary->begin(); vIt != _boundary->end(); ++vIt)
+//    				vIt->z() = vIt->z() * heightScale;
+//
+//                _maskSource->sync( _maskSourceRev );
+//            }
+//        }
+//    }
+//
+//    if (!_boundary.valid())
+//    {
+//        setStatus(Status::Error("Failed to create masking boundary"));
+//    }
+//
+//    return _boundary.get();
+//}
diff --git a/src/osgEarth/MaskNode b/src/osgEarth/MaskNode
index 755c1fd..c8a5361 100644
--- a/src/osgEarth/MaskNode
+++ b/src/osgEarth/MaskNode
@@ -24,6 +24,7 @@
 
 namespace osgEarth
 {
+    //! @deprecated
     class OSGEARTH_EXPORT MaskNode : public osg::Group
     {
     public:
diff --git a/src/osgEarth/MaskSource b/src/osgEarth/MaskSource
index 0fa54d4..d025411 100644
--- a/src/osgEarth/MaskSource
+++ b/src/osgEarth/MaskSource
@@ -58,6 +58,9 @@ namespace osgEarth
 
     /**
      * MaskSource is a plugin object that generates a masking goemetry
+     *
+     * TODO: there is only one kind of mask source (FeatureMaskSource) so
+     * consider removing this and just having a single MaskLayer type. -gw
      */
     class OSGEARTH_EXPORT MaskSource : public osg::Object, public Revisioned
     {
diff --git a/src/osgEarth/MaskSource.cpp b/src/osgEarth/MaskSource.cpp
index dfbfaf7..fd532ca 100644
--- a/src/osgEarth/MaskSource.cpp
+++ b/src/osgEarth/MaskSource.cpp
@@ -92,7 +92,7 @@ MaskSourceFactory::~MaskSourceFactory()
 MaskSource*
 MaskSourceFactory::create( const MaskSourceOptions& options )
 {
-    MaskSource* source = 0L;
+    osg::ref_ptr<MaskSource> source;
 
     if ( !options.getDriver().empty() )
     {
@@ -101,7 +101,8 @@ MaskSourceFactory::create( const MaskSourceOptions& options )
         osg::ref_ptr<osgDB::Options> rwopts = Registry::instance()->cloneOrCreateOptions();
         rwopts->setPluginData( MASK_SOURCE_OPTIONS_TAG, (void*)&options );
 
-        source = dynamic_cast<MaskSource*>( osgDB::readObjectFile( driverExt, rwopts.get() ) );
+        osg::ref_ptr<osg::Object> object = osgDB::readRefObjectFile( driverExt, rwopts.get() );
+        source = dynamic_cast<MaskSource*>( object.release() );
         if ( source )
         {
             OE_INFO << "Loaded MaskSource driver \"" << options.getDriver() << "\" OK" << std::endl;
@@ -116,7 +117,7 @@ MaskSourceFactory::create( const MaskSourceOptions& options )
         OE_WARN << LC << "FAIL, illegal null driver specification" << std::endl;
     }
 
-    return source;
+    return source.release();
 }
 
 //------------------------------------------------------------------------
diff --git a/src/osgEarth/MemCache b/src/osgEarth/MemCache
index 5582fc7..b8f96cb 100644
--- a/src/osgEarth/MemCache
+++ b/src/osgEarth/MemCache
@@ -51,9 +51,6 @@ namespace osgEarth
         MemCache( const MemCache& rhs, const osg::CopyOp& op =osg::CopyOp::DEEP_COPY_ALL ) : Cache( rhs, op ) { }
 
         unsigned _maxBinSize;
-        float _writes;
-        float _reads;
-        float _hits;
     };
 
 } // namespace osgEarth
diff --git a/src/osgEarth/MemCache.cpp b/src/osgEarth/MemCache.cpp
index ff73a0e..95fc22f 100644
--- a/src/osgEarth/MemCache.cpp
+++ b/src/osgEarth/MemCache.cpp
@@ -77,7 +77,8 @@ namespace
         {
             if ( object ) 
             {
-                _lru.insert( key, std::make_pair(object, meta) );
+                osg::ref_ptr<const osg::Object> cloned = osg::clone(object, osg::CopyOp::DEEP_COPY_ALL);
+                _lru.insert( key, std::make_pair(cloned.get(), meta) );
                 return true;
             }
             else
@@ -124,10 +125,7 @@ namespace
 //------------------------------------------------------------------------
 
 MemCache::MemCache( unsigned maxBinSize ) :
-_maxBinSize( std::max(maxBinSize, 1u) ),
-_reads(0),
-_writes(0),
-_hits(0)
+_maxBinSize( std::max(maxBinSize, 1u) )
 {
     //nop
 }
diff --git a/src/osgEarth/Mercator b/src/osgEarth/Mercator
deleted file mode 100644
index 0431231..0000000
--- a/src/osgEarth/Mercator
+++ /dev/null
@@ -1,46 +0,0 @@
-///* -*-c++-*- */
-///* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-// * Copyright 2008-2009 Pelican Ventures, Inc.
-// * http://osgearth.org
-// *
-// * osgEarth is free software; you can redistribute it and/or modify
-// * it under the terms of the GNU Lesser General Public License as published by
-// * the Free Software Foundation; either version 2 of the License, or
-// * (at your option) any later version.
-// *
-// * This program is distributed in the hope that it will be useful,
-// * but WITHOUT ANY WARRANTY; without even the implied warranty of
-// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// * GNU Lesser General Public License for more details.
-// *
-// * You should have received a copy of the GNU Lesser General Public License
-// * along with this program.  If not, see <http://www.gnu.org/licenses/>
-// */
-//
-//#ifndef OSGEARTH_MERCATOR_H
-//#define OSGEARTH_MERCATOR_H 1
-//
-//#include <osgEarth/Common>
-//#include <osgEarth/TileKey>
-//#include <osgEarth/TileSource>
-//#include <osgTerrain/Locator>
-//#include <osgTerrain/TerrainTechnique>
-//#include <osgDB/ReaderWriter>
-//#include <string>
-//
-//namespace osgEarth
-//{
-//    class MercatorLocator : public osgTerrain::Locator
-//    {
-//    public:
-//        MercatorLocator( const osgTerrain::Locator& prototype, const GeoExtent& image_extent );
-//
-//        bool convertLocalToModel(const osg::Vec3d& local, osg::Vec3d& model) const;
-//        bool convertModelToLocal(const osg::Vec3d& world, osg::Vec3d& local) const;
-//
-//    private:
-//        GeoExtent _image_ext;
-//    };
-//}
-//
-//#endif // OSGEARTH_MERCATOR_H
\ No newline at end of file
diff --git a/src/osgEarth/Mercator.cpp b/src/osgEarth/Mercator.cpp
deleted file mode 100644
index 3c98b5c..0000000
--- a/src/osgEarth/Mercator.cpp
+++ /dev/null
@@ -1,149 +0,0 @@
-///* -*-c++-*- */
-///* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-// * Copyright 2008-2009 Pelican Ventures, Inc.
-// * http://osgearth.org
-// *
-// * osgEarth is free software; you can redistribute it and/or modify
-// * it under the terms of the GNU Lesser General Public License as published by
-// * the Free Software Foundation; either version 2 of the License, or
-// * (at your option) any later version.
-// *
-// * This program is distributed in the hope that it will be useful,
-// * but WITHOUT ANY WARRANTY; without even the implied warranty of
-// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// * GNU Lesser General Public License for more details.
-// *
-// * You should have received a copy of the GNU Lesser General Public License
-// * along with this program.  If not, see <http://www.gnu.org/licenses/>
-// */
-//
-//#include <osgEarth/Mercator>
-//#include <osg/Math>
-//#include <osg/Notify>
-//#include <sstream>
-//#include <algorithm>
-//#include <string.h>
-//
-//using namespace osgEarth;
-//
-//#define MERC_MAX_LAT  85.084059050110383
-//#define MERC_MIN_LAT -85.084059050110383
-//
-//static double
-//lonToU(double lon) {
-//    return (lon + 180.0) / 360.0;
-//}
-//
-//static double
-//latToV(double lat) {
-//    double sin_lat = sin( osg::DegreesToRadians( lat ) );
-//    return 0.5 - log( (1+sin_lat) / (1-sin_lat) ) / (4*osg::PI);
-//}
-//
-//static void
-//getUV(const GeoExtent& ext,
-//      double lon, double lat,
-//      double& out_u, double& out_v)
-//{
-//    out_u = (lon-ext.xMin())/ext.width();
-//
-//    double vmin = latToV( osg::clampBetween( ext.yMax(), MERC_MIN_LAT, MERC_MAX_LAT ) );
-//    double vmax = latToV( osg::clampBetween( ext.yMin(), MERC_MIN_LAT, MERC_MAX_LAT ) );
-//    double vlat = latToV( osg::clampBetween( lat, MERC_MIN_LAT, MERC_MAX_LAT ) );
-//
-//    out_v = (vlat-vmin)/(vmax-vmin);
-//}
-//
-//
-//MercatorLocator::MercatorLocator( const osgTerrain::Locator& prototype, const GeoExtent& image_ext ) :
-//osgTerrain::Locator( prototype )
-//{
-//    // assumption: incoming extent is Mercator SRS
-//    _image_ext = image_ext.transform( image_ext.getSRS()->getGeographicSRS() );
-//}
-//
-//
-//bool
-//MercatorLocator::convertLocalToModel(const osg::Vec3d& local, osg::Vec3d& world) const
-//{
-//    switch(_coordinateSystemType)
-//    {
-//        case(GEOCENTRIC):
-//        {
-//            return Locator::convertLocalToModel( local, world );
-//        }
-//        case(GEOGRAPHIC):
-//        {        
-//            return Locator::convertLocalToModel( local, world );
-//        }
-//        case(PROJECTED):
-//        {        
-//            return Locator::convertLocalToModel( local, world );
-//        }
-//    }    
-//
-//    return false;
-//}
-//
-//bool
-//MercatorLocator::convertModelToLocal(const osg::Vec3d& world, osg::Vec3d& local) const
-//{
-//    // OSG 2.7 bug workaround: bug fix in Locator submitted by GW on 10/3/2008:
-//    const_cast<MercatorLocator*>(this)->_inverse.invert( _transform );
-//
-//    switch(_coordinateSystemType)
-//    {
-//    case(GEOCENTRIC):
-//        {
-//            double longitude, latitude, height;
-//
-//            _ellipsoidModel->convertXYZToLatLongHeight(world.x(), world.y(), world.z(),
-//                latitude, longitude, height );
-//
-//            local = osg::Vec3d(longitude, latitude, height) * _inverse;
-//
-//            double lon_deg = osg::RadiansToDegrees(longitude);
-//            double lat_deg = osg::RadiansToDegrees(latitude);
-//            double xr, yr;
-//
-//            //GeoExtent tile_extent(
-//            //    NULL,
-//            //    osg::RadiansToDegrees(_transform(3,0)),
-//            //    osg::RadiansToDegrees(_transform(3,1)),
-//            //    osg::RadiansToDegrees(_transform(3,0)+_transform(0,0)),
-//            //    osg::RadiansToDegrees(_transform(3,1)+_transform(1,1) ) );
-//
-//            getUV( _image_ext, lon_deg, lat_deg, xr, yr );
-//
-//            local.x() = xr;
-//            local.y() = 1.0-yr;
-//            return true;
-//        }
-//
-//
-//    case(GEOGRAPHIC):
-//        {        
-//            local = world * _inverse;
-//
-//            osg::Vec3d w = world;
-//            double lon_deg = w.x();
-//            double lat_deg = w.y();
-//
-//            double xr, yr;
-//            getUV( _image_ext, lon_deg, lat_deg, xr, yr );
-//
-//            local.x() = xr;
-//            local.y() = 1.0-yr;
-//            return true;
-//        }
-//
-//    case(PROJECTED):
-//        {        
-//            local = world * _inverse;
-//            return true;      
-//        }
-//    }    
-//
-//    return false;
-//}
-//
diff --git a/src/osgEarth/MetaTile b/src/osgEarth/MetaTile
new file mode 100644
index 0000000..542dfe6
--- /dev/null
+++ b/src/osgEarth/MetaTile
@@ -0,0 +1,77 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+* Copyright 2008-2012 Pelican Mapping
+* http://osgearth.org
+*
+* osgEarth is free software; you can redistribute it and/or modify
+* it under the terms of the GNU Lesser General Public License as published by
+* the Free Software Foundation; either version 2 of the License, or
+* (at your option) any later version.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+* IN THE SOFTWARE.
+*
+* You should have received a copy of the GNU Lesser General Public License
+* along with this program.  If not, see <http://www.gnu.org/licenses/>
+*/
+#ifndef OSGEARTH_METATILE_H
+#define OSGEARTH_METATILE_H
+
+#include <osgEarth/Common>
+#include <osgEarth/ImageUtils>
+#include <osgEarth/GeoData>
+#include <osgEarth/TileKey>
+#include <osg/Image>
+
+namespace osgEarth
+{
+    class ImageLayer;
+
+    /**
+     * Metadata groups a collection of adjacent data tiles
+     * together to facilitate operations that overlap multiple tiles.
+     */
+    class OSGEARTH_EXPORT MetaImage
+    {
+    public:
+        //! Construct a new Metatiled image
+        MetaImage() { }
+
+        //! Sets the data elevation at location (x,y), where (0,0) is the center.
+        bool setImage(int x, int y, osg::Image* image, const osg::Matrix& scaleBias);
+
+        //! Gets the image at the neighbor location (x,y).
+        osg::Image* getImage(int x, int y) const;
+
+        //! Gets the positioning matrix for neightbor location (x,y).
+        const osg::Matrix& getScaleBias(int x, int y) const;
+
+        //! Reads the data from parametric location (u,v), where [u,v] in [-1, +2].
+        //! Returns true upon success with the value in [output];
+        //! false if there is no tile at the read location.
+        bool read(double u, double v, osg::Vec4& output) const;
+
+        void dump() const;
+
+    private:
+        //virtual ~MetaImage() { }
+
+        struct Tile {
+            Tile();
+            bool _valid;
+            osg::ref_ptr<osg::Image> _imageRef;
+            ImageUtils::PixelReader _read;
+            osg::Matrix _scaleBias;
+        };
+
+        Tile _tiles[3][3]; // col, row
+    };
+
+} // namespace osgEarth
+
+#endif // OSGEARTH_METATILE_H
diff --git a/src/osgEarth/MetaTile.cpp b/src/osgEarth/MetaTile.cpp
new file mode 100644
index 0000000..eb91ca9
--- /dev/null
+++ b/src/osgEarth/MetaTile.cpp
@@ -0,0 +1,122 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+* Copyright 2016 Pelican Mapping
+* http://osgearth.org
+*
+* osgEarth is free software; you can redistribute it and/or modify
+* it under the terms of the GNU Lesser General Public License as published by
+* the Free Software Foundation; either version 2 of the License, or
+* (at your option) any later version.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+* IN THE SOFTWARE.
+*
+* You should have received a copy of the GNU Lesser General Public License
+* along with this program.  If not, see <http://www.gnu.org/licenses/>
+*/
+#include <osgEarth/MetaTile>
+#include <osgEarth/ImageLayer>
+
+using namespace osgEarth;
+
+#define LC "[MetaImage] "
+
+
+MetaImage::Tile::Tile() :
+_valid(false),
+_read(0L)
+{
+    //nop
+}
+
+bool
+MetaImage::setImage(int x, int y, osg::Image* image, const osg::Matrix& scaleBias)
+{
+    if (image!=0L && x >= -1 && x <= 1 && y >= -1 && y <= 1)
+    {
+        x += 1, y += 1;
+
+        // We store this reference just in case the caller does not hold on to it,
+        // in which case the PixelReader would have a dangling pointer and crash.
+        _tiles[x][y]._imageRef = image;
+
+        _tiles[x][y]._read.setImage(image);
+        _tiles[x][y]._scaleBias = scaleBias;
+        _tiles[x][y]._valid = true;
+
+        return true;
+    }
+    else
+    {
+        OE_WARN << LC << "ILLEGAL call to MetaImage.setImage\n";
+        return false;
+    }
+}
+
+osg::Image*
+MetaImage::getImage(int x, int y) const
+{
+    x = osg::clampBetween(x+1, 0, 2);
+    y = osg::clampBetween(y+1, 0, 2);
+    return _tiles[x][y]._imageRef.get();
+}
+
+const osg::Matrix&
+MetaImage::getScaleBias(int x, int y) const
+{
+    x = osg::clampBetween(x+1, 0, 2);
+    y = osg::clampBetween(y+1, 0, 2);
+    return _tiles[x][y]._scaleBias;
+}
+
+bool
+MetaImage::read(double u, double v, osg::Vec4& output) const
+{
+    // clamp the input coordinates to the legal range:
+    u = osg::clampBetween(u, -1.0, 2.0);
+    v = osg::clampBetween(v, -1.0, 2.0);
+
+    // resolve the tile to sample:
+    int x = u < 0.0 ? 0 : u <= 1.0 ? 1 : 2;
+    int y = v < 0.0 ? 2 : v <= 1.0 ? 1 : 0;
+
+    const Tile& tile = _tiles[x][y];
+
+    if (!tile._valid)
+        return false;
+
+    // transform the coordinates to the tile:
+    u += u < 0.0 ? 1.0 : u > 1.0 ? -1.0 : 0.0;
+    v += v < 0.0 ? 1.0 : v > 1.0 ? -1.0 : 0.0;
+
+    // scale/bias to this tile's extent:
+    u = u * tile._scaleBias(0, 0) + tile._scaleBias(3, 0);
+    v = v * tile._scaleBias(1, 1) + tile._scaleBias(3, 1);
+
+    output = tile._read(u, v);
+    return true;
+}
+
+void
+MetaImage::dump() const
+{
+    for (int x = 0; x <= 2; ++x) {
+        for (int y = 0; y <= 2; ++y) {
+            const Tile& tile = _tiles[x][y];
+            if (!tile._valid) {
+                OE_INFO << "    [" << x << "][" << y << "]: invalid\n";
+            }
+            else {
+                OE_INFO << "    [" << x << "][" << y << "]: "
+                    << "s=" << tile._scaleBias(0,0) 
+                    << ", b=" << tile._scaleBias(3, 0) << " "
+                    << tile._scaleBias(3, 1) << "\n";
+            }                
+        }
+    }
+}
diff --git a/src/osgEarth/Metrics b/src/osgEarth/Metrics
new file mode 100644
index 0000000..37def68
--- /dev/null
+++ b/src/osgEarth/Metrics
@@ -0,0 +1,269 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+ * Copyright 2008-2012 Pelican Mapping
+ * http://osgearth.org
+ *
+ * osgEarth is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+
+#ifndef OSGEARTH_METRICS_H
+#define OSGEARTH_METRICS_H 1
+
+#include <osgEarth/Common>
+#include <osgEarth/Config>
+#include <iostream>
+#include <osgDB/fstream>
+
+// forward
+namespace osgViewer {
+    class Viewer;
+}
+
+namespace osgEarth
+{
+    /**
+     * Interface for a class that handles collecting metrics.
+     */
+    class OSGEARTH_EXPORT MetricsBackend : public osg::Referenced
+    {
+    public:
+        /**
+         * Begins an event.
+         * @param name
+         *        The name of the event
+         * @param args
+         *        The arguments to the event.
+         */
+        virtual void begin(const std::string& name, const Config& args =Config()) = 0;
+
+        /**
+         * Ends an event
+         * @param name
+         *        The name of the event.
+         * @param args
+         *        The arguments to the event.
+         */
+        virtual void end(const std::string& name, const Config& args =Config()) = 0;
+
+        /**
+         * A counter event
+         * @param graph
+         *        The graph to display the counters on.
+         * @param name0
+         *        The name of the first counter.
+         * @param value0
+         *        The value of the first counter.
+         * @param name1
+         *        The name of the second counter.
+         * @param value1
+         *        The value of the second counter.
+         * @param name2
+         *        The name of the third counter.
+         * @param value2
+         *        The value of the third counter.
+         */
+        virtual void counter(const std::string& graph,
+                             const std::string& name0, double value0,
+                             const std::string& name1, double value1,
+                             const std::string& name2, double value2) = 0;
+    };
+
+    /**
+     * A MetricsProvider that uses the chrome://tracing format as described here: http://www.gamasutra.com/view/news/176420/Indepth_Using_Chrometracing_to_view_your_inline_profiling_data.php
+     * https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU/edit?pli=1#
+     */
+    class OSGEARTH_EXPORT ChromeMetricsBackend : public MetricsBackend
+    {
+    public:
+        ChromeMetricsBackend(const std::string& filename);
+        ~ChromeMetricsBackend();
+
+        virtual void begin(const std::string& name, const Config& args =Config());
+        virtual void end(const std::string& name, const Config& args =Config());
+        
+
+        /**
+         * A counter event
+         * @param graph
+         *        The graph to display the counters on.
+         * @param name0
+         *        The name of the first counter.
+         * @param value0
+         *        The value of the first counter.
+         * @param name1
+         *        The name of the second counter.
+         * @param value1
+         *        The value of the second counter.
+         * @param name2
+         *        The name of the third counter.
+         * @param value2
+         *        The value of the third counter.
+         */
+        virtual void counter(const std::string& graph,
+                             const std::string& name0, double value0,
+                             const std::string& name1, double value1,
+                             const std::string& name2, double value2);
+
+    protected:
+        std::ofstream _metricsFile;
+        OpenThreads::Mutex _mutex;    
+        bool _firstEvent;
+        osg::Timer_t _startTime;
+    };
+
+    class OSGEARTH_EXPORT Metrics
+    {
+    public:
+        /**
+         * Begins an event.
+         * @param name
+         *        The name of the event
+         * @param args
+         *        The arguments to the event.
+         */
+        static void begin(const std::string& name, const Config& args =Config());
+
+
+        /**
+         * Begins an event with a variable number of arguments
+         * @param name
+         *        The name of the event
+         * @param argCount
+         *        The number of argument pairs (key, value) that will 
+         */
+        static void begin(const std::string& name, unsigned int argCount, ...);
+
+        /**
+         * Ends an event with a variable number of arguments
+         * @param name
+         *        The name of the event
+         * @param argCount
+         *        The number of argument pairs (key, value) that will 
+
+         */
+        static void end(const std::string& name, unsigned int argCount, ...);
+
+        /**
+         * Ends an event
+         * @param name
+         *        The name of the event.
+         * @param args
+         *        The arguments to the event.
+         */
+        static void end(const std::string& name,  const Config& args = Config());
+
+        /**
+         * A counter event
+         * @param graph
+         *        The graph to display the counters on.
+         * @param name0
+         *        The name of the first counter.
+         * @param value0
+         *        The value of the first counter.         
+         */
+        static void counter(const std::string& graph,const std::string& name0, double value0);
+           
+        /**
+         * A counter event
+         * @param graph
+         *        The graph to display the counters on.
+         * @param name0
+         *        The name of the first counter.
+         * @param value0
+         *        The value of the first counter.
+         * @param name1
+         *        The name of the second counter.
+         * @param value1
+         *        The value of the second counter.
+         */
+        static void counter(const std::string& graph,const std::string& name0, double value0,
+                                                     const std::string& name1, double value1);
+
+        /**
+         * A counter event
+         * @param graph
+         *        The graph to display the counters on.
+         * @param name0
+         *        The name of the first counter.
+         * @param value0
+         *        The value of the first counter.
+         * @param name1
+         *        The name of the second counter.
+         * @param value1
+         *        The value of the second counter.
+         * @param name2
+         *        The name of the third counter.
+         * @param value2
+         *        The value of the third counter.
+         */
+        static void counter(const std::string& graph,const std::string& name0, double value0,
+                                                     const std::string& name1, double value1,
+                                                     const std::string& name2, double value2);
+
+        /**
+         * Gets the metrics backend.
+         */
+        static MetricsBackend* getMetricsBackend();
+
+        /*
+         * Whether metrics collection is enabled.  Equivalent to checking getMetricsBackend != NULL;
+         */
+        static bool enabled();
+
+        /**
+         * Sets the MetricsBackend
+         */
+        static void setMetricsBackend( MetricsBackend* backend );
+
+        /**
+         * Encodes varargs into a Config object for serialization by the metrics backend
+         */
+        static Config encodeArgs(unsigned count, ...);
+
+        /**
+         * Convenience function to run the OSG frame loop with metrics.
+         */
+        static int run(osgViewer::Viewer& viewer);
+    };
+
+    /**
+     * Utility that lets you start and stop metrics with a single line of code
+     * @example
+     * void myFunction() {
+     *     ScopedMetric("myFunction");
+     *     ...do some work
+     * }
+     */
+    class OSGEARTH_EXPORT ScopedMetric
+    {
+    public:
+        ScopedMetric(const std::string& name);
+        ScopedMetric(const std::string& name, const Config& args);
+        ScopedMetric(const std::string& name, int argCount, ...);
+        ~ScopedMetric();
+        std::string _name;
+    };
+
+#define METRIC_BEGIN(...) if (osgEarth::Metrics::enabled()) osgEarth::Metrics::begin(__VA_ARGS__)
+
+#define METRIC_END(...)   if (osgEarth::Metrics::enabled()) osgEarth::Metrics::end(__VA_ARGS__)
+    
+#define METRIC_SCOPED(NAME) \
+    osgEarth::ScopedMetric scoped_metric__(NAME) 
+
+#define METRIC_SCOPED_EX(NAME, COUNT, ...) \
+    osgEarth::ScopedMetric scoped_metric__(NAME, osgEarth::Metrics::enabled() ? osgEarth::Metrics::encodeArgs(COUNT, __VA_ARGS__) : osgEarth::Config())
+};
+
+#endif
diff --git a/src/osgEarth/Metrics.cpp b/src/osgEarth/Metrics.cpp
new file mode 100644
index 0000000..4c7a537
--- /dev/null
+++ b/src/osgEarth/Metrics.cpp
@@ -0,0 +1,496 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+ * Copyright 2008-2012 Pelican Mapping
+ * http://osgearth.org
+ *
+ * osgEarth is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+#include <osgEarth/Metrics>
+#include <osgEarth/ThreadingUtils>
+#include <osgEarth/Memory>
+#include <osgViewer/Viewer>
+#include <cstdarg>
+
+using namespace osgEarth;
+
+#define LC "[Metrics] "
+
+namespace
+{
+    static osg::ref_ptr< MetricsBackend > s_metrics_backend;
+    static bool s_metrics_debug = false;
+
+    class MetricsStartup
+    {
+    public:
+        MetricsStartup()
+        {
+            const char* metricsFile = ::getenv("OSGEARTH_METRICS_FILE");
+            if (metricsFile)
+            {
+                Metrics::setMetricsBackend(new ChromeMetricsBackend(std::string(metricsFile)));
+            }
+            const char* metricsVerbose = ::getenv("OSGEARTH_METRICS_DEBUG");
+            if (metricsVerbose)
+            {
+                s_metrics_debug = true;
+            }
+        }
+
+        ~MetricsStartup()
+        {
+            Metrics::setMetricsBackend(0);
+        }
+    };
+
+    static MetricsStartup s_metricsStartup;
+}
+
+void Metrics::begin(const std::string& name, const Config& args)
+{
+    if (s_metrics_backend.valid())
+    {
+        if (s_metrics_debug)
+            OE_INFO << LC << "begin: " << name << "  " << (args.empty() ? "" : args.toJSON(false)) << std::endl;
+
+        s_metrics_backend->begin(name, args);
+    }
+}
+
+Config Metrics::encodeArgs(unsigned count, ...)
+{
+    Config conf;
+
+    va_list args;
+    va_start( args, count );
+
+    for (unsigned int i = 0; i < count; i++)
+    {
+        char* key = va_arg(args, char*);
+        char* value = va_arg(args, char*);
+        conf.add( key, value );
+    }
+
+    va_end(args);
+
+    return conf;
+}
+
+void Metrics::begin(const std::string& name, unsigned int argCount, ...)
+{
+    if (!s_metrics_backend.valid())
+        return;
+
+    Config conf;
+
+    va_list args;
+    va_start( args, argCount );
+
+    for (unsigned int i = 0; i < argCount; i++)
+    {
+        char* key = va_arg(args, char*);
+        char* value = va_arg(args, char*);
+        conf.add( key, value );
+    }
+
+    va_end(args);
+
+    begin(name, conf);
+}
+
+void Metrics::end(const std::string& name, unsigned int argCount, ...)
+{
+    if (!s_metrics_backend.valid())
+        return;
+
+    Config conf;
+
+    va_list args;
+    va_start( args, argCount );
+
+    for (unsigned int i = 0; i < argCount; i++)
+    {
+        char* key = va_arg(args, char*);
+        char* value = va_arg(args, char*);
+        conf.add( key, value );
+    }
+
+    va_end(args);
+
+    end(name, conf);
+}
+
+void Metrics::end(const std::string& name, const Config& args)
+{
+    if (s_metrics_backend.valid())
+    {
+        s_metrics_backend->end(name, args);
+
+        if (s_metrics_debug)
+            OE_INFO << LC << "end: " << name << "  " << (args.empty() ? "" : args.toJSON(false)) << std::endl;
+    }
+}
+
+void Metrics::counter(const std::string& graph,const std::string& name0, double value0)
+{
+    Metrics::counter(graph, name0, value0,
+                            "", 0.0,
+                            "", 0.0);
+}
+
+void Metrics::counter(const std::string& graph,const std::string& name0, double value0,
+                                        const std::string& name1, double value1)
+{
+    Metrics::counter(graph, name0, value0,
+                            name1, value1,
+                            "", 0.0);
+}
+
+
+void Metrics::counter(const std::string& graph,const std::string& name0, double value0,
+                                        const std::string& name1, double value1,
+                                        const std::string& name2, double value2)
+{
+    if (s_metrics_backend.valid())
+    {
+        s_metrics_backend->counter(graph, name0, value0,
+                                          name1, value1,
+                                          name2, value2);
+    }
+}
+
+MetricsBackend* Metrics::getMetricsBackend()
+{
+    return s_metrics_backend.get();
+}
+
+void Metrics::setMetricsBackend(MetricsBackend* backend)
+{
+    s_metrics_backend = backend;
+}
+
+bool Metrics::enabled()
+{
+    return getMetricsBackend() != NULL;
+}
+
+int Metrics::run(osgViewer::Viewer& viewer)
+{
+    if (Metrics::enabled())
+    {
+        if (!viewer.isRealized())
+        {
+            viewer.realize();
+        }
+
+        // If Metrics are enabled, enable stats on the Viewer so that it we can report them for the Metrics
+        if (Metrics::enabled())
+        {
+            osgViewer::ViewerBase::Scenes scenes;
+            viewer.getScenes(scenes);
+            for (osgViewer::ViewerBase::Scenes::iterator itr = scenes.begin();
+                itr != scenes.end();
+                ++itr)
+            {
+                osgViewer::Scene* scene = *itr;
+                osgDB::DatabasePager* dp = scene->getDatabasePager();
+                if (dp && dp->isRunning())
+                {
+                    dp->resetStats();
+                }
+            }
+
+            viewer.getViewerStats()->collectStats("frame_rate", true);
+            viewer.getViewerStats()->collectStats("event", true);
+            viewer.getViewerStats()->collectStats("update", true);
+
+            viewer.getCamera()->getStats()->collectStats("rendering", true);
+            viewer.getCamera()->getStats()->collectStats("gpu", true);
+        }
+
+        // Report memory and fps every 10 frames.
+        unsigned int reportEvery = 10;
+
+        while (!viewer.done())
+        {
+            {
+                METRIC_SCOPED_EX("frame", 1, "number", toString<int>(viewer.getFrameStamp()->getFrameNumber()).c_str());
+                {
+                    METRIC_SCOPED("advance");
+                    viewer.advance();
+                }
+
+                {
+                    METRIC_SCOPED("event");
+                    viewer.eventTraversal();
+                }
+
+                {
+                    METRIC_SCOPED("update");
+                    viewer.updateTraversal();
+                }
+
+                {
+                    METRIC_SCOPED("render");
+                    viewer.renderingTraversals();
+                }
+
+            }
+
+            // Report memory and fps periodically. periodically.
+            if (viewer.getFrameStamp()->getFrameNumber() % reportEvery == 0)
+            {
+                // Only report the metrics if they are enabled to avoid computing the memory.
+                if (Metrics::enabled())
+                {
+                    Metrics::counter("Memory::WorkingSet", "WorkingSet", Memory::getProcessPhysicalUsage() / 1048576);
+                    Metrics::counter("Memory::PrivateBytes", "PrivateBytes", Memory::getProcessPrivateUsage() / 1048576);
+                    Metrics::counter("Memory::PeakPrivateBytes", "PeakPrivateBytes", Memory::getProcessPeakPrivateUsage() / 1048576);
+                }
+            }
+
+            double eventTime = 0.0;
+            if (viewer.getViewerStats()->getAttribute(viewer.getViewerStats()->getLatestFrameNumber(), "Event traversal time taken", eventTime))
+            {
+                Metrics::counter("Viewer::Event", "Event", eventTime * 1000.0);
+            }
+
+            double updateTime = 0.0;
+            if (viewer.getViewerStats()->getAttribute(viewer.getViewerStats()->getLatestFrameNumber(), "Update traversal time taken", updateTime))
+            {
+                Metrics::counter("Viewer::Update", "Update", updateTime * 1000.0);
+            }
+
+            double cullTime = 0.0;
+            if (viewer.getCamera()->getStats()->getAttribute(viewer.getCamera()->getStats()->getLatestFrameNumber(), "Cull traversal time taken", cullTime))
+            {
+                Metrics::counter("Viewer::Cull", "Cull", cullTime * 1000.0);
+            }
+
+            double drawTime = 0.0;
+            if (viewer.getCamera()->getStats()->getAttribute(viewer.getCamera()->getStats()->getLatestFrameNumber(), "Draw traversal time taken", drawTime))
+            {
+                Metrics::counter("Viewer::Draw", "Draw", drawTime * 1000.0);
+            }
+
+            double gpuTime = 0.0;
+            if (viewer.getCamera()->getStats()->getAttribute(viewer.getCamera()->getStats()->getLatestFrameNumber()-1, "GPU draw time taken", gpuTime))
+            {
+                Metrics::counter("Viewer::GPU", "GPU", gpuTime * 1000.0);
+            }
+
+            double frameRate = 0.0;
+            if (viewer.getViewerStats()->getAttribute(viewer.getViewerStats()->getLatestFrameNumber() - 1, "Frame rate", frameRate))
+            {
+                Metrics::counter("Viewer::FPS", "FPS", frameRate);
+            }
+
+        }
+
+        return 0;
+    }
+
+    else
+    {
+        return viewer.run();
+    }
+}
+
+
+
+ChromeMetricsBackend::ChromeMetricsBackend(const std::string& filename):
+_firstEvent(true)
+{
+    _startTime = osg::Timer::instance()->tick();
+    _metricsFile.open(filename.c_str(), std::ios::out);
+    _metricsFile << "[";
+}
+
+ChromeMetricsBackend::~ChromeMetricsBackend()
+{
+     OpenThreads::ScopedLock< OpenThreads::Mutex > lk(_mutex);
+    _metricsFile << "]";
+    _metricsFile.close();
+}
+
+void ChromeMetricsBackend::begin(const std::string& name, const Config& args)
+{
+    osg::Timer_t now = osg::Timer::instance()->tick();
+
+    OpenThreads::ScopedLock< OpenThreads::Mutex > lk(_mutex);
+    if (_firstEvent)
+    {
+        _firstEvent = false;
+    }
+    else
+    {
+        _metricsFile << "," << std::endl;
+    }
+    _metricsFile << "{"
+        << "\"cat\": \"" << "" << "\","
+        << "\"pid\": \"" << 0 << "\","
+        << "\"tid\": \"" << osgEarth::Threading::getCurrentThreadId() << "\","
+        << "\"ts\": \""  << std::setprecision(9) << osg::Timer::instance()->delta_u(_startTime, now) << "\","
+        << "\"ph\": \"B\","
+        << "\"name\": \""  << name << "\"";
+
+    if (!args.empty())
+    {
+        _metricsFile << "," << std::endl << " \"args\": {";
+        bool first = true;
+        for( ConfigSet::const_iterator i = args.children().begin(); i != args.children().end(); ++i ) {
+            if (first)
+            {
+                first = !first;
+            }
+            else
+            {
+                _metricsFile << "," << std::endl;
+            }
+            _metricsFile << "\"" << i->key() << "\" : \"" << i->value() << "\"";
+        }
+        _metricsFile << "}";
+    }
+
+    _metricsFile << "}";
+}
+
+void ChromeMetricsBackend::end(const std::string& name, const Config& args)
+{
+    osg::Timer_t now = osg::Timer::instance()->tick();
+
+    OpenThreads::ScopedLock< OpenThreads::Mutex > lk(_mutex);
+    if (_firstEvent)
+    {
+        _firstEvent = false;
+    }
+    else
+    {
+        _metricsFile << "," << std::endl;
+    }
+    _metricsFile << "{"
+        << "\"cat\": \"" << "" << "\","
+        << "\"pid\": \"" << 0 << "\","
+        << "\"tid\": \"" << osgEarth::Threading::getCurrentThreadId() << "\","
+        << "\"ts\": \""  << std::setprecision(9) << osg::Timer::instance()->delta_u(_startTime, now) << "\","
+        << "\"ph\": \"E\","
+        << "\"name\": \""  << name << "\"";
+
+    if (!args.empty())
+    {
+        _metricsFile << "," << std::endl << " \"args\": {";
+        bool first = true;
+        for( ConfigSet::const_iterator i = args.children().begin(); i != args.children().end(); ++i ) {
+            if (first)
+            {
+                first = !first;
+            }
+            else
+            {
+                _metricsFile << "," << std::endl;
+            }
+            _metricsFile << "\"" << i->key() << "\" : \"" << i->value() << "\"";
+        }
+        _metricsFile << "}";
+    }
+
+    _metricsFile << "}";
+}
+
+void ChromeMetricsBackend::counter(const std::string& graph,
+                             const std::string& name0, double value0,
+                             const std::string& name1, double value1,
+                             const std::string& name2, double value2)
+{
+    osg::Timer_t now = osg::Timer::instance()->tick();
+
+    OpenThreads::ScopedLock< OpenThreads::Mutex > lk(_mutex);
+    if (_firstEvent)
+    {
+        _firstEvent = false;
+    }
+    else
+    {
+        _metricsFile << "," << std::endl;
+    }
+
+    _metricsFile << "{"
+        << "\"cat\": \"" << "" << "\","
+        << "\"pid\": \"" << 0 << "\","
+        << "\"tid\": \"" << osgEarth::Threading::getCurrentThreadId() << "\","
+        << "\"ts\": \""  << std::setprecision(9) << osg::Timer::instance()->delta_u(_startTime, now) << "\","
+        << "\"ph\": \"C\","
+        << "\"name\": \""  << graph << "\","
+        << "\"args\" : {";
+
+    if (!name0.empty())
+    {
+        _metricsFile << "    \"" << name0 << "\": " << std::setprecision(9) << value0;
+    }
+
+    if (!name1.empty())
+    {
+        _metricsFile << ",    \"" << name1 << "\": " << std::setprecision(9) <<value1;
+    }
+
+    if (!name2.empty())
+    {
+        _metricsFile << ",    \"" << name2 << "\": " << std::setprecision(9) <<value2;
+    }
+
+    _metricsFile << "}}";
+}
+
+
+
+ScopedMetric::ScopedMetric(const std::string& name) :
+_name(name)
+{
+    static Config s_emptyConfig;
+    Metrics::begin(_name, s_emptyConfig);
+}
+
+ScopedMetric::ScopedMetric(const std::string& name, const Config& args) :
+_name(name)
+{
+    Metrics::begin(_name, args);
+}
+
+ScopedMetric::ScopedMetric(const std::string& name, int argCount, ...) :
+_name(name)
+{
+    if (!s_metrics_backend) return;
+
+    Config conf;
+
+    va_list args;
+    va_start( args, argCount );
+
+    for (unsigned int i = 0; i < argCount; i++)
+    {
+        char* key = va_arg(args, char*);
+        char* value = va_arg(args, char*);
+        conf.add( key, value );
+    }
+
+    va_end(args);
+
+    Metrics::begin(_name, conf);
+}
+
+ScopedMetric::~ScopedMetric()
+{
+    Metrics::end(_name);
+}
+
diff --git a/src/osgEarth/ModelLayer b/src/osgEarth/ModelLayer
index f9369dc..a9f4c0b 100644
--- a/src/osgEarth/ModelLayer
+++ b/src/osgEarth/ModelLayer
@@ -21,14 +21,14 @@
 #define OSGEARTH_MODEL_LAYER_H 1
 
 #include <osgEarth/Common>
-#include <osgEarth/AlphaEffect>
-#include <osgEarth/Layer>
+#include <osgEarth/VisibleLayer>
 #include <osgEarth/Cache>
 #include <osgEarth/Config>
 #include <osgEarth/ModelSource>
 #include <osgEarth/MaskSource>
 #include <osgEarth/ShaderUtils>
 #include <osgEarth/Containers>
+#include <osgEarth/SceneGraphCallback>
 #include <osg/Node>
 #include <osg/Array>
 #include <vector>
@@ -40,7 +40,7 @@ namespace osgEarth
     /**
      * Configuration options for a ModelLayer.
      */
-    class OSGEARTH_EXPORT ModelLayerOptions : public ConfigOptions
+    class OSGEARTH_EXPORT ModelLayerOptions : public VisibleLayerOptions
     {
     public:        
         /** Construct or deserialize new model layer options. */
@@ -62,12 +62,6 @@ namespace osgEarth
         ModelLayerOptions& operator = (const ModelLayerOptions&);
 
         /**
-         * The readable name of the layer.
-         */
-        optional<std::string>& name() { return _name; }
-        const optional<std::string>& name() const { return _name; }
-
-        /**
          * Options for the underlying model source driver.
          */
         optional<ModelSourceOptions>& driver() { return _driver; }
@@ -80,30 +74,12 @@ namespace osgEarth
         const optional<bool>& lightingEnabled() const { return _lighting; }
 
         /**
-         * Whether this layer is active
-         */
-        optional<bool>& enabled() { return _enabled; }
-        const optional<bool>& enabled() const { return _enabled; }
-
-        /**
-         * Whether this layer is visible
-         */
-        optional<bool>& visible() { return _visible; }
-        const optional<bool>& visible() const { return _visible; }
-
-        /**
-         * The opacity of this layer
-         */
-        optional<float>& opacity() { return _opacity; }
-        const optional<float>& opacity() const { return _opacity; }
-
-        /**
          * Masking options for cutting a hole in the terrain to accommodate this model.
          * Note; the mask will NOT honor any visibility or opacity settings on the
          * model layer.
          */
-        optional<MaskSourceOptions>& maskOptions() { return _maskOptions; }
-        const optional<MaskSourceOptions>& maskOptions() const { return _maskOptions; }
+        optional<MaskSourceOptions>& mask() { return _maskOptions; }
+        const optional<MaskSourceOptions>& mask() const { return _maskOptions; }
 
         /**
          * Minimum terrain LOD at which to apply the mask (if there if one)
@@ -118,18 +94,6 @@ namespace osgEarth
         optional<bool>& terrainPatch() { return _terrainPatch; }
         const optional<bool>& terrainPatch() const { return _terrainPatch; }
 
-        /**
-         * Caching policy for the layer
-         */
-        optional<CachePolicy>& cachePolicy() { return _cachePolicy; }
-        const optional<CachePolicy>& cachePolicy() const { return _cachePolicy; }
-
-        /**
-         * Express cacheid (overrides an automatically generated cache id)
-         */
-        optional<std::string>& cacheId() { return _cacheId; }
-        const optional<std::string>& cacheId() const { return _cacheId; }
-
 
     public:
         virtual Config getConfig() const;
@@ -139,11 +103,7 @@ namespace osgEarth
         void fromConfig( const Config& conf );
         void setDefaults();
 
-        optional<std::string>        _name;
         optional<ModelSourceOptions> _driver;
-        optional<bool>               _enabled;
-        optional<bool>               _visible;
-        optional<float>              _opacity;
         optional<bool>               _lighting;
         optional<MaskSourceOptions>  _maskOptions;
         optional<unsigned>           _maskMinLevel;
@@ -152,36 +112,42 @@ namespace osgEarth
         optional<std::string>        _cacheId;
     };
 
-    /**
-    * Callback for receiving notification of property changes on a ModelLayer.
-    */
-    struct ModelLayerCallback : public osg::Referenced
+    struct ModelLayerCallback : public VisibleLayerCallback
     {
-        virtual void onVisibleChanged( class ModelLayer* layer ) { }
-        virtual void onOpacityChanged( class ModelLayer* layer ) { }
-        virtual ~ModelLayerCallback() { }
+        // Nothing - placeholder
+        typedef void (ModelLayerCallback::*MethodPtr)(class ModelLayer* layer);
     };
 
-    typedef void (ModelLayerCallback::*ModelLayerCallbackMethodPtr)(ModelLayer* layer);
 
-    typedef std::list< osg::ref_ptr<ModelLayerCallback> > ModelLayerCallbackList;
-
-
-    class OSGEARTH_EXPORT ModelLayer : public Layer
+    /**
+     * Layer that contains an OSG scene graph
+     */
+    class OSGEARTH_EXPORT ModelLayer : public VisibleLayer
     {
     public:
+        META_Layer(osgEarth, ModelLayer, ModelLayerOptions);
+
+        /**
+         * Blank ModelLayer. Use options() to setup before calling open.
+         */
+        ModelLayer();
+
         /**
          * Constructs a new model layer.
          */
-        ModelLayer( const ModelLayerOptions& options );
+        ModelLayer(const ModelLayerOptions& options);
 
         /**
          * Constructs a new model layer with a user-provided driver options.
          */
-        ModelLayer( const std::string& name, const ModelSourceOptions& options );
+        ModelLayer(const std::string& name, const ModelSourceOptions& options);
         
         /**
          * Constructs a new model layer with a user-provided model source.
+         *
+         * Note: the ModelLayerOptions contains a driver() member for configuring a 
+         * TileSource. But in this constructor, you are passing in an existing TileSource,
+         * and thus the driver() member in ModelLayerOptions will not be used.
          */
         ModelLayer(const ModelLayerOptions& options, ModelSource* source );
 
@@ -190,19 +156,8 @@ namespace osgEarth
          */
         ModelLayer(const std::string& name, osg::Node* node);
 
-        /** dtor */
-        virtual ~ModelLayer();
 
     public:
-        /** 
-         * Gets the name of this model layer
-         */
-        const std::string& getName() const { return _runtimeOptions.name().get(); }
-
-        /**
-         * Gets the initialization options for this layer.
-         */
-        const ModelLayerOptions& getModelLayerOptions() const { return _initOptions; }
 
         /**
          * Access the underlying model source.
@@ -217,7 +172,7 @@ namespace osgEarth
         /**
          * The minimum terrain LOD at which to apply the mask.
          */
-        unsigned getMaskMinLevel() const { return _initOptions.maskMinLevel().get(); }
+        unsigned getMaskMinLevel() const { return options().maskMinLevel().get(); }
         
         /**
          * The boundary geometry for the mask.
@@ -231,28 +186,14 @@ namespace osgEarth
          * Whether this model layer is a terrain patch for the purposes of 
          * intersection testing. (convenience function)
          */
-        bool isTerrainPatch() const { return _initOptions.terrainPatch().get(); }
+        bool isTerrainPatch() const { return options().terrainPatch().get(); }
 
+        /** Access scene graph callbacks */
+        SceneGraphCallbacks* getSceneGraphCallbacks() { return _sgCallbacks.get(); }
 
     public:
 
         /**
-         * Sets the read options for this layer to user; this includes
-         * Cache and CachePolicy information potentially.
-         */
-        void setReadOptions(const osgDB::Options* readOptions);
-
-        /**
-         * Perform one-time initialization of the model layer.
-         */
-        const Status& open();
-
-        /**
-         * Status of this layer.
-         */
-        const Status& getStatus() const { return _status; }
-
-        /**
          * Creates the scene graph representing this model layer for the given Map,
          * or returns one that already exists.
          */
@@ -273,60 +214,47 @@ namespace osgEarth
 
     public: // properties
 
-        /** Whether this layer is rendered. */
-        bool getVisible() const;
-        void setVisible(bool value);
-
-        /** Whether this layer is used at all. */
-        bool getEnabled() const;
-
         /** whether to apply lighting to the model layer's root node */
         void setLightingEnabled( bool value );
         bool isLightingEnabled() const;
 
-        /**
-         * Sets the opacity of this image layer.
-         * @param opacity Opacity [0..1] -> [transparent..opaque]
-         */
-        void setOpacity( float opacity );
-        float getOpacity() const;
+    public: // Layer
 
-    public:
+        /** Open the layer and return its status */
+        virtual const Status& open();
 
-        /** Adds a property notification callback to this layer */
-        void addCallback( ModelLayerCallback* cb );
+        /** Serialize this layer */
+        virtual Config getConfig() const;
 
-        /** Removes a property notification callback from this layer */
-        void removeCallback( ModelLayerCallback* cb );
+        /** Node created by this model layer */
+        virtual osg::Node* getOrCreateNode();
+
+        //! Generate a cache ID for this layer
+        virtual std::string getCacheID() const;
 
     protected:
 
-        /**
-         * Set the status in case something fails
-         */
-        void setStatus(const Status& value) { _status = value; }
+        /** post-ctor initialization */
+        virtual void init();
+
+
+    protected:
+
+        virtual ~ModelLayer();
 
-    private:
         osg::ref_ptr<ModelSource>     _modelSource;
         osg::ref_ptr<MaskSource>      _maskSource;
-        const ModelLayerOptions       _initOptions;
-        ModelLayerOptions             _runtimeOptions;
         Revision                      _modelSourceRev;
-        ModelLayerCallbackList        _callbacks;
-        osg::ref_ptr<AlphaEffect>     _alphaEffect;
         osg::ref_ptr<osg::Vec3dArray> _maskBoundary;
-        osg::ref_ptr<osgDB::Options>  _readOptions;
         osg::ref_ptr<CacheSettings>   _cacheSettings;
-        Status                        _status;
+        osg::ref_ptr<SceneGraphCallbacks> _sgCallbacks;
 
-        typedef fast_map<UID, osg::observer_ptr<osg::Node> > Graphs;
+        typedef fast_map<UID, osg::ref_ptr<osg::Node> > Graphs;
         Graphs _graphs;
 
         mutable Threading::Mutex _mutex; // general-purpose mutex.
 
-        virtual void fireCallback( ModelLayerCallbackMethodPtr method );
-
-        void copyOptions();
+        void fireCallback(ModelLayerCallback::MethodPtr method);
 
         void setLightingEnabledNoLock(bool value);
     };
diff --git a/src/osgEarth/ModelLayer.cpp b/src/osgEarth/ModelLayer.cpp
index 2984a81..59c7f50 100644
--- a/src/osgEarth/ModelLayer.cpp
+++ b/src/osgEarth/ModelLayer.cpp
@@ -21,12 +21,17 @@
 #include <osgEarth/Registry>
 #include <osgEarth/Capabilities>
 #include <osgEarth/ShaderFactory>
+#include <osgEarth/Lighting>
 #include <osg/Depth>
 
-#define LC "[ModelLayer] "
+#define LC "[ModelLayer] Layer \"" << getName() << "\" "
 
 using namespace osgEarth;
 
+namespace osgEarth {
+    REGISTER_OSGEARTH_LAYER(model, ModelLayer);
+}
+
 //------------------------------------------------------------------------
 
 namespace
@@ -52,53 +57,40 @@ namespace
 
 //------------------------------------------------------------------------
 
-ModelLayerOptions::ModelLayerOptions( const ConfigOptions& options ) :
-ConfigOptions( options )
+ModelLayerOptions::ModelLayerOptions(const ConfigOptions& options) :
+VisibleLayerOptions( options )
 {
     setDefaults();
     fromConfig( _conf ); 
 }
 
-ModelLayerOptions::ModelLayerOptions( const std::string& name, const ModelSourceOptions& driverOptions ) :
-ConfigOptions()
+ModelLayerOptions::ModelLayerOptions(const std::string& in_name, const ModelSourceOptions& driverOptions) :
+VisibleLayerOptions()
 {
     setDefaults();
     fromConfig( _conf );
-    _name = name;
     _driver = driverOptions;
+    name() = in_name;
 }
 
 ModelLayerOptions::ModelLayerOptions(const ModelLayerOptions& rhs) :
-ConfigOptions(rhs.getConfig())
+VisibleLayerOptions(rhs)
 {
-    _name = optional<std::string>(rhs._name);
     _driver = optional<ModelSourceOptions>(rhs._driver);
-    _enabled = optional<bool>(rhs._enabled);
-    _visible = optional<bool>(rhs._visible);
-    _opacity = optional<float>(rhs._opacity);
     _lighting = optional<bool>(rhs._lighting);
     _maskOptions = optional<MaskSourceOptions>(rhs._maskOptions);
     _maskMinLevel = optional<unsigned>(rhs._maskMinLevel);
     _terrainPatch = optional<bool>(rhs._terrainPatch);
-    _cachePolicy = optional<CachePolicy>(rhs._cachePolicy);
-    _cacheId = optional<std::string>(rhs._cacheId);
 }
 
 ModelLayerOptions& ModelLayerOptions::operator =(const ModelLayerOptions& rhs)
 {
-    ConfigOptions::operator =(rhs);
-
-    _name = optional<std::string>(rhs._name);
+    VisibleLayerOptions::operator =(rhs);
     _driver = optional<ModelSourceOptions>(rhs._driver);
-    _enabled = optional<bool>(rhs._enabled);
-    _visible = optional<bool>(rhs._visible);
-    _opacity = optional<float>(rhs._opacity);
     _lighting = optional<bool>(rhs._lighting);
     _maskOptions = optional<MaskSourceOptions>(rhs._maskOptions);
     _maskMinLevel = optional<unsigned>(rhs._maskMinLevel);
     _terrainPatch = optional<bool>(rhs._terrainPatch);
-    _cachePolicy = optional<CachePolicy>(rhs._cachePolicy);
-    _cacheId = optional<std::string>(rhs._cacheId);
 
     return *this;
 }
@@ -106,40 +98,25 @@ ModelLayerOptions& ModelLayerOptions::operator =(const ModelLayerOptions& rhs)
 void
 ModelLayerOptions::setDefaults()
 {
-    _enabled.init     ( true );
-    _visible.init     ( true );
     _lighting.init    ( true );
-    _opacity.init     ( 1.0f );
     _maskMinLevel.init( 0 );
     _terrainPatch.init( false );
-
-    // Expressly set it here since we want no caching by default on a model layer.
-    _cachePolicy = CachePolicy::NO_CACHE;
 }
 
 Config
 ModelLayerOptions::getConfig() const
 {
-    Config conf = ConfigOptions::newConfig();
-
-    conf.updateIfSet( "name",           _name );
-    conf.updateIfSet( "enabled",        _enabled );
-    conf.updateIfSet( "visible",        _visible );
-    conf.updateIfSet( "lighting",       _lighting );
-    conf.updateIfSet( "opacity",        _opacity );
-    conf.updateIfSet( "mask_min_level", _maskMinLevel );
-    conf.updateIfSet( "patch",          _terrainPatch );  
+    Config conf = VisibleLayerOptions::getConfig();
+    conf.key() = "model";
 
-    conf.updateObjIfSet( "cache_policy", _cachePolicy );  
-    conf.updateIfSet("cacheid", _cacheId);
-
-    // Merge the ModelSource options
-    if ( driver().isSet() )
-        conf.merge( driver()->getConfig() );
+    conf.set( "name",           _name );
+    conf.set( "lighting",       _lighting );
+    conf.set( "mask_min_level", _maskMinLevel );
+    conf.set( "patch",          _terrainPatch );  
 
     // Merge the MaskSource options
-    if ( maskOptions().isSet() )
-        conf.add( "mask", maskOptions()->getConfig() );
+    if ( mask().isSet() )
+        conf.set( "mask", mask()->getConfig() );
 
     return conf;
 }
@@ -147,22 +124,15 @@ ModelLayerOptions::getConfig() const
 void
 ModelLayerOptions::fromConfig( const Config& conf )
 {
-    conf.getIfSet( "name",           _name );
-    conf.getIfSet( "enabled",        _enabled );
-    conf.getIfSet( "visible",        _visible );
     conf.getIfSet( "lighting",       _lighting );
-    conf.getIfSet( "opacity",        _opacity );
     conf.getIfSet( "mask_min_level", _maskMinLevel );
     conf.getIfSet( "patch",          _terrainPatch );
 
-    conf.getObjIfSet( "cache_policy", _cachePolicy );  
-    conf.getIfSet("cacheid", _cacheId);
-
     if ( conf.hasValue("driver") )
         driver() = ModelSourceOptions(conf);
 
     if ( conf.hasChild("mask") )
-        maskOptions() = MaskSourceOptions(conf.child("mask"));
+        mask() = MaskSourceOptions(conf.child("mask"));
 }
 
 void
@@ -174,30 +144,45 @@ ModelLayerOptions::mergeConfig( const Config& conf )
 
 //------------------------------------------------------------------------
 
-ModelLayer::ModelLayer( const ModelLayerOptions& options ) :
-_initOptions( options )
+ModelLayer::ModelLayer() :
+VisibleLayer(&_optionsConcrete),
+_options(&_optionsConcrete)
 {
-    copyOptions();
+    init();
 }
 
-ModelLayer::ModelLayer( const std::string& name, const ModelSourceOptions& options ) :
-_initOptions( ModelLayerOptions( name, options ) )
+ModelLayer::ModelLayer(const ModelLayerOptions& options) :
+VisibleLayer(&_optionsConcrete),
+_options(&_optionsConcrete),
+_optionsConcrete(options)
 {
-    copyOptions();
+    init();
 }
 
-ModelLayer::ModelLayer( const ModelLayerOptions& options, ModelSource* source ) :
-_modelSource( source ),
-_initOptions( options )
+ModelLayer::ModelLayer(const std::string& name, const ModelSourceOptions& options) :
+VisibleLayer(&_optionsConcrete),
+_options(&_optionsConcrete),
+_optionsConcrete(ModelLayerOptions(name, options))
 {
-    copyOptions();
+    init();
 }
 
-ModelLayer::ModelLayer(const std::string& name, osg::Node* node):
-_initOptions( ModelLayerOptions(name) ),
+ModelLayer::ModelLayer(const ModelLayerOptions& options, ModelSource* source) :
+VisibleLayer(&_optionsConcrete),
+_options(&_optionsConcrete),
+_optionsConcrete(options),
+_modelSource( source )
+{
+    init();
+}
+
+ModelLayer::ModelLayer(const std::string& name, osg::Node* node) :
+VisibleLayer(&_optionsConcrete),
+_options(&_optionsConcrete),
+_optionsConcrete(ModelLayerOptions(name)),
 _modelSource( new NodeModelSource(node) )
 {
-    copyOptions();
+    init();
 }
 
 ModelLayer::~ModelLayer()
@@ -205,26 +190,34 @@ ModelLayer::~ModelLayer()
     //nop
 }
 
-void
-ModelLayer::copyOptions()
+Config
+ModelLayer::getConfig() const
 {
-    _runtimeOptions = _initOptions;
+    Config layerConf = Layer::getConfig(); //getModelLayerOptions().getConfig();
+    layerConf.set("name", getName()); // redundant?
+    layerConf.set("driver", options().driver()->getDriver());
+    layerConf.key() = "model";
+    return layerConf;
+}
 
-    _alphaEffect = new AlphaEffect();
-    _alphaEffect->setAlpha( *_initOptions.opacity() );
+void
+ModelLayer::init()
+{
+    VisibleLayer::init();
+    _sgCallbacks = new SceneGraphCallbacks();
 }
 
 const Status&
 ModelLayer::open()
 {
-    if ( !_modelSource.valid() && _initOptions.driver().isSet() )
+    if ( !_modelSource.valid() && options().driver().isSet() )
     {
-        std::string driverName = _initOptions.driver()->getDriver();
+        std::string driverName = options().driver()->getDriver();
 
-        OE_INFO << LC << "Opening model layer \"" << getName() << "\", driver=\"" << driverName << "\"" << std::endl;
+        OE_INFO << LC << "Opening; driver=\"" << driverName << "\"" << std::endl;
         
         // Try to create the model source:
-        _modelSource = ModelSourceFactory::create( *_initOptions.driver() );
+        _modelSource = ModelSourceFactory::create( options().driver().get() );
         if ( _modelSource.valid() )
         {
             _modelSource->setName( this->getName() );
@@ -233,63 +226,64 @@ ModelLayer::open()
             if (modelStatus.isOK())
             {
                 // the mask, if there is one:
-                if ( !_maskSource.valid() && _initOptions.maskOptions().isSet() )
+                if ( !_maskSource.valid() && options().mask().isSet() )
                 {
                     OE_INFO << LC << "...initializing mask, driver=" << driverName << std::endl;
 
-                    _maskSource = MaskSourceFactory::create( *_initOptions.maskOptions() );
+                    _maskSource = MaskSourceFactory::create( options().mask().get() );
                     if ( _maskSource.valid() )
                     {
                         const Status& maskStatus = _maskSource->open(_readOptions.get());
                         if (maskStatus.isError())
                         {
-                            _status = maskStatus;
+                            setStatus(maskStatus);
                         }
                     }
                     else
                     {
-                        _status = Status::Error(Status::ServiceUnavailable, Stringify() << "Cannot find mask driver \"" << _initOptions.maskOptions()->getDriver() << "\"");
+                        setStatus(Status::Error(Status::ServiceUnavailable, Stringify() << "Cannot find mask driver \"" << options().mask()->getDriver() << "\""));
                     }
                 }
             }
             else
             {
                 // propagate the model source's error status
-                _status = modelStatus;
+                setStatus(modelStatus);
             }
         }
         else
         {
-            _status = Status::Error(Status::ServiceUnavailable, Stringify() << "Failed to create driver \"" << driverName << "\"");
+            setStatus(Status::Error(Status::ServiceUnavailable, Stringify() << "Failed to create driver \"" << driverName << "\""));
         }
     }
 
-    return _status;
+    return getStatus();
 }
 
+#if 0
 void
 ModelLayer::setReadOptions(const osgDB::Options* readOptions)
 {
-    _readOptions = Registry::cloneOrCreateOptions(readOptions);
+    Layer::setReadOptions(readOptions);
 
     // Create some local cache settings for this layer:
     CacheSettings* oldSettings = CacheSettings::get(readOptions);
     _cacheSettings = oldSettings ? new CacheSettings(*oldSettings) : new CacheSettings();
 
     // bring in the new policy for this layer if there is one:
-    _cacheSettings->integrateCachePolicy(_initOptions.cachePolicy());
+    _cacheSettings->integrateCachePolicy(options().cachePolicy());
 
     // if caching is a go, install a bin.
     if (_cacheSettings->isCacheEnabled())
     {
         std::string binID;
-        if (_initOptions.cacheId().isSet() && !_initOptions.cacheId()->empty())
+        if (options().cacheId().isSet() && !options().cacheId()->empty())
         {
-            binID = _initOptions.cacheId().get();
+            binID = options().cacheId().get();
         }
         else
         {
-            Config conf = _initOptions.driver()->getConfig();
+            Config conf = options().driver()->getConfig();
             binID = hashToString(conf.toJSON(false));
         }
 
@@ -297,13 +291,13 @@ ModelLayer::setReadOptions(const osgDB::Options* readOptions)
         CacheBin* bin = _cacheSettings->getCache()->addBin(binID);
         if (bin)
         {
-            OE_INFO << LC << "Layer \"" << getName() << "\" cache bin is [" << binID << "]\n";
+            OE_INFO << LC << "Cache bin is [" << binID << "]\n";
             _cacheSettings->setCacheBin( bin );
         }
         else
         {
             // failed to create the bin, so fall back on no cache mode.
-            OE_WARN << LC << "Layer " << getName() << " failed to open a cache bin [" << binID << "], disabling caching\n";
+            OE_WARN << LC << "Failed to open a cache bin [" << binID << "], disabling caching\n";
             _cacheSettings->cachePolicy() = CachePolicy::NO_CACHE;
         }
     }
@@ -311,6 +305,24 @@ ModelLayer::setReadOptions(const osgDB::Options* readOptions)
     // Store it for further propagation!
     _cacheSettings->store(_readOptions.get());
 }
+#endif
+
+std::string
+ModelLayer::getCacheID() const
+{
+    // Customized from the base class to use the driver() config instead of full config:
+    std::string binID;
+    if (options().cacheId().isSet() && !options().cacheId()->empty())
+    {
+        binID = options().cacheId().get();
+    }
+    else
+    {
+        Config conf = options().driver()->getConfig();
+        binID = hashToString(conf.toJSON(false));
+    }
+    return binID;
+}
 
 osg::Node*
 ModelLayer::getSceneGraph(const UID& mapUID) const
@@ -341,13 +353,15 @@ ModelLayer::getOrCreateSceneGraph(const Map*        map,
 
     if ( _modelSource.valid() )
     {
+        _modelSource->setSceneGraphCallbacks(_sgCallbacks.get());
+
         node = _modelSource->createNode( map, progress );
 
         if ( node )
         {
-            if ( _runtimeOptions.lightingEnabled().isSet() )
+            if ( options().lightingEnabled().isSet() )
             {
-                setLightingEnabledNoLock( *_runtimeOptions.lightingEnabled() );
+                setLightingEnabledNoLock( options().lightingEnabled().get() );
             }
 
             _modelSource->sync( _modelSourceRev );
@@ -356,13 +370,17 @@ ModelLayer::getOrCreateSceneGraph(const Map*        map,
             // add a parent group for shaders/effects to attach to without overwriting any model programs directly
             osg::Group* group = new osg::Group();
             group->addChild(node);
-            _alphaEffect->attach( group->getOrCreateStateSet() );
+
+            // assign the layer's stateset to the group.
+            osg::StateSet* groupSS = getOrCreateStateSet();
+            group->setStateSet(groupSS);
+
             node = group;
 
             // Toggle visibility if necessary
-            if ( _runtimeOptions.visible().isSet() )
+            if ( options().visible().isSet() )
             {
-                node->setNodeMask( *_runtimeOptions.visible() ? ~0 : 0 );
+                node->setNodeMask( options().visible().get() ? ~0 : 0 );
             }
 
             // Handle disabling depth testing
@@ -370,8 +388,13 @@ ModelLayer::getOrCreateSceneGraph(const Map*        map,
             {
                 osg::StateSet* ss = node->getOrCreateStateSet();
                 ss->setAttributeAndModes( new osg::Depth( osg::Depth::ALWAYS ) );
+              
                 ss->setRenderBinDetails( 99999, "RenderBin" ); //TODO: configure this bin ...
             }
+            else
+            {
+                groupSS->setRenderingHint(osg::StateSet::TRANSPARENT_BIN);
+            }
 
             // save it.
             _graphs[map->getUID()] = node;
@@ -381,54 +404,13 @@ ModelLayer::getOrCreateSceneGraph(const Map*        map,
     return node;
 }
 
-bool
-ModelLayer::getEnabled() const
-{
-    return *_runtimeOptions.enabled();
-}
-
-bool
-ModelLayer::getVisible() const
-{
-    return getEnabled() && *_runtimeOptions.visible();
-}
-
-void
-ModelLayer::setVisible(bool value)
-{
-    if ( _runtimeOptions.visible() != value )
-    {
-        _runtimeOptions.visible() = value;
-
-        _mutex.lock();
-        for(Graphs::iterator i = _graphs.begin(); i != _graphs.end(); ++i)
-        {
-            if ( i->second.valid() )
-                i->second->setNodeMask( value ? ~0 : 0 );
-        }
-        _mutex.unlock();
-
-        fireCallback( &ModelLayerCallback::onVisibleChanged );
-    }
-}
-
-float
-ModelLayer::getOpacity() const
-{
-    return *_runtimeOptions.opacity();
-}
-
-void
-ModelLayer::setOpacity(float opacity)
+osg::Node*
+ModelLayer::getOrCreateNode()
 {
-    if ( _runtimeOptions.opacity() != opacity )
-    {
-        _runtimeOptions.opacity() = opacity;
-
-        _alphaEffect->setAlpha(opacity);
-
-        fireCallback( &ModelLayerCallback::onOpacityChanged );
-    }
+    if (!_graphs.empty())
+        return _graphs.begin()->second.get();
+    else
+        return 0L;
 }
 
 void
@@ -441,7 +423,7 @@ ModelLayer::setLightingEnabled( bool value )
 void
 ModelLayer::setLightingEnabledNoLock(bool value)
 {
-    _runtimeOptions.lightingEnabled() = value;
+    options().lightingEnabled() = value;
 
     for(Graphs::iterator i = _graphs.begin(); i != _graphs.end(); ++i)
     {
@@ -455,8 +437,7 @@ ModelLayer::setLightingEnabledNoLock(bool value)
 
             if ( Registry::capabilities().supportsGLSL() )
             {
-                stateset->addUniform( Registry::shaderFactory()->createUniformForGLMode(
-                    GL_LIGHTING, value ) );
+                stateset->setDefine(OE_LIGHTING_DEFINE, value);
             }
         }
     }
@@ -465,31 +446,16 @@ ModelLayer::setLightingEnabledNoLock(bool value)
 bool
 ModelLayer::isLightingEnabled() const
 {
-    return *_runtimeOptions.lightingEnabled();
-}
-
-void
-ModelLayer::addCallback( ModelLayerCallback* cb )
-{
-    _callbacks.push_back( cb );
-}
-
-void
-ModelLayer::removeCallback( ModelLayerCallback* cb )
-{
-    ModelLayerCallbackList::iterator i = std::find( _callbacks.begin(), _callbacks.end(), cb );
-    if ( i != _callbacks.end() ) 
-        _callbacks.erase( i );
+    return options().lightingEnabled().get();
 }
 
-
 void
-ModelLayer::fireCallback( ModelLayerCallbackMethodPtr method )
+ModelLayer::fireCallback(ModelLayerCallback::MethodPtr method)
 {
-    for( ModelLayerCallbackList::const_iterator i = _callbacks.begin(); i != _callbacks.end(); ++i )
+    for(CallbackVector::const_iterator i = _callbacks.begin(); i != _callbacks.end(); ++i )
     {
-        ModelLayerCallback* cb = i->get();
-        (cb->*method)( this );
+        ModelLayerCallback* cb = dynamic_cast<ModelLayerCallback*>(i->get());
+        if (cb) (cb->*method)( this );
     }
 }
 
diff --git a/src/osgEarth/ModelSource b/src/osgEarth/ModelSource
index 2ef4f3e..b08e4ae 100644
--- a/src/osgEarth/ModelSource
+++ b/src/osgEarth/ModelSource
@@ -27,6 +27,7 @@
 #include <osgEarth/Revisioning>
 #include <osgEarth/ThreadingUtils>
 #include <osgEarth/Status>
+#include <osgEarth/SceneGraphCallback>
 #include <osg/Referenced>
 
 namespace osgEarth
@@ -112,32 +113,10 @@ namespace osgEarth
             ProgressCallback* progress );
 
         /**
-         * Add a post processing operation that will run (possibly in a pager
-         * thread) after a new node has been generated.
+         * Access to the scene graph callbacks
          */
-        void addPreMergeOperation( NodeOperation* cb );
-
-        /**
-         * Add a post processing operation that will run (in the update thread)
-         * after a node merges into the scene graph.
-         */
-        void addPostMergeOperation( NodeOperation* cb );
-        
-        /**
-         * Remove a pre-merge processing operation
-         */
-        void removePreMergeOperation( NodeOperation* cb );
-        
-        /**
-         * Remove a post-merge processing operation
-         */
-        void removePostMergeOperation( NodeOperation* cb );
-
-        /**
-         * The vector of post processor callback operations
-         */
-        const NodeOperationVector& preMergeOperations() const { return *_preMergeOps; }
-        const NodeOperationVector& postMergeOperations() const { return *_postMergeOps; }
+        void setSceneGraphCallbacks(SceneGraphCallbacks* value) { _sgCallbacks = value; }
+        SceneGraphCallbacks* getSceneGraphCallbacks() const { return _sgCallbacks.get(); }
 
 
     public: // META_Object specialization
@@ -175,16 +154,7 @@ namespace osgEarth
             const Map*        map,
             ProgressCallback* progress ) =0;
 
-        /**
-         * Fire the callbacks. The implementation class should call this whenever it adds
-         * a new node.
-         */
-        void firePostProcessors( osg::Node* node );
-
     protected:
-
-        osg::ref_ptr<RefNodeOperationVector> _postMergeOps;
-        osg::ref_ptr<RefNodeOperationVector> _preMergeOps;
         
         /** Subclass can set the status with this */
         void setStatus(const Status& value) { _status = value; }
@@ -197,9 +167,12 @@ namespace osgEarth
         DataExtentList   _dataExtents;
         Status _status;
 
+        osg::ref_ptr<SceneGraphCallbacks> _sgCallbacks;        
+
         friend class Map;
         friend class MapEngine;
         friend class ModelSourceFactory;
+
     };
 
     //--------------------------------------------------------------------
diff --git a/src/osgEarth/ModelSource.cpp b/src/osgEarth/ModelSource.cpp
index 3aa4e2c..0870b38 100644
--- a/src/osgEarth/ModelSource.cpp
+++ b/src/osgEarth/ModelSource.cpp
@@ -65,11 +65,11 @@ Config
 ModelSourceOptions::getConfig() const
 {
     Config conf = DriverConfigOptions::getConfig();
-    conf.updateIfSet( "min_range", _minRange );
-    conf.updateIfSet( "max_range", _maxRange );
-    conf.updateIfSet( "render_order", _renderOrder );
-    conf.updateIfSet( "render_bin", _renderBin );
-    conf.updateIfSet( "depth_test_enabled", _depthTestEnabled );
+    conf.set( "min_range", _minRange );
+    conf.set( "max_range", _maxRange );
+    conf.set( "render_order", _renderOrder );
+    conf.set( "render_bin", _renderBin );
+    conf.set( "depth_test_enabled", _depthTestEnabled );
     return conf;
 }
 
@@ -78,8 +78,7 @@ ModelSourceOptions::getConfig() const
 ModelSource::ModelSource( const ModelSourceOptions& options ) :
 _options( options )
 {
-   _preMergeOps  = new RefNodeOperationVector();
-   _postMergeOps = new RefNodeOperationVector();
+    _sgCallbacks = new SceneGraphCallbacks();
 }
 
 ModelSource::~ModelSource()
@@ -106,87 +105,12 @@ ModelSource::createNode(const Map*        map,
     osg::Node* node = createNodeImplementation(map, progress);
     if ( node )
     {
-        firePostProcessors( node );
+        getSceneGraphCallbacks()->firePreMergeNode(node);
+        getSceneGraphCallbacks()->firePostMergeNode(node);
     }
     return node;
 }
 
-
-void 
-ModelSource::addPreMergeOperation( NodeOperation* op )
-{
-    if ( op )
-    {
-        _preMergeOps->mutex().writeLock();
-        _preMergeOps->push_back( op );
-        _preMergeOps->mutex().writeUnlock();
-    }
-}
-
-
-void
-ModelSource::removePreMergeOperation( NodeOperation* op )
-{
-    if ( op )
-    {
-        _preMergeOps->mutex().writeLock();
-        NodeOperationVector::iterator i = std::find( _preMergeOps->begin(), _preMergeOps->end(), op );
-        if ( i != _postMergeOps->end() )
-            _preMergeOps->erase( i );
-        _preMergeOps->mutex().writeUnlock();
-    }
-}
-
-
-void 
-ModelSource::addPostMergeOperation( NodeOperation* op )
-{
-    if ( op )
-    {
-        _postMergeOps->mutex().writeLock();
-        _postMergeOps->push_back( op );
-        _postMergeOps->mutex().writeUnlock();
-    }
-}
-
-
-void
-ModelSource::removePostMergeOperation( NodeOperation* op )
-{
-    if ( op )
-    {
-        _postMergeOps->mutex().writeLock();
-        NodeOperationVector::iterator i = std::find( _postMergeOps->begin(), _postMergeOps->end(), op );
-        if ( i != _postMergeOps->end() )
-            _postMergeOps->erase( i );
-        _postMergeOps->mutex().writeUnlock();
-    }
-}
-
-
-void
-ModelSource::firePostProcessors( osg::Node* node )
-{
-    if ( node )
-    {
-        // pres:
-        _preMergeOps->mutex().readLock();
-        for( NodeOperationVector::iterator i = _preMergeOps->begin(); i != _preMergeOps->end(); ++i )
-        {
-            i->get()->operator()( node );
-        }
-        _preMergeOps->mutex().readUnlock();
-
-        // posts:
-        _postMergeOps->mutex().readLock();
-        for( NodeOperationVector::iterator i = _postMergeOps->begin(); i != _postMergeOps->end(); ++i )
-        {
-            i->get()->operator()( node );
-        }
-        _postMergeOps->mutex().readUnlock();
-    }
-}
-
 //------------------------------------------------------------------------
 
 #undef  LC
@@ -201,7 +125,7 @@ ModelSourceFactory::~ModelSourceFactory()
 ModelSource*
 ModelSourceFactory::create( const ModelSourceOptions& options )
 {
-    ModelSource* modelSource = 0L;
+    osg::ref_ptr<ModelSource> source;
 
     if ( !options.getDriver().empty() )
     {
@@ -210,18 +134,15 @@ ModelSourceFactory::create( const ModelSourceOptions& options )
         osg::ref_ptr<osgDB::Options> rwopts = Registry::instance()->cloneOrCreateOptions();
         rwopts->setPluginData( MODEL_SOURCE_OPTIONS_TAG, (void*)&options );
 
-        modelSource = dynamic_cast<ModelSource*>( osgDB::readObjectFile( driverExt, rwopts.get() ) );
-        //if ( !modelSource )
-        //{
-        //    OE_WARN << "FAILED to load model source driver \"" << options.getDriver() << "\"" << std::endl;
-        //}
+        osg::ref_ptr<osg::Object> object = osgDB::readRefObjectFile( driverExt, rwopts.get() );
+        source = dynamic_cast<ModelSource*>( object.release() );
     }
     else
     {
         OE_WARN << LC << "FAIL, illegal null driver specification" << std::endl;
     }
 
-    return modelSource;
+    return source.release();
 }
 
 //------------------------------------------------------------------------
diff --git a/src/osgEarth/NodeUtils b/src/osgEarth/NodeUtils
index ca6de4e..8c7e8ed 100644
--- a/src/osgEarth/NodeUtils
+++ b/src/osgEarth/NodeUtils
@@ -242,6 +242,16 @@ namespace osgEarth
         return node && node->getNumParents() > 0 ? findTopOfGraph(node->getParent(0)) : node;
     }
 
+    /** Finds a typed node in a node visitor's node path */
+    template<typename T>
+    T* findInNodePath(osg::NodeVisitor& nv) {
+        for (osg::NodePath::iterator i = nv.getNodePath().begin(); i != nv.getNodePath().end(); ++i) {
+            T* node = dynamic_cast<T*>(*i);
+            if (node) return node;
+        }
+        return 0L;
+    }
+
     /**
      * Replace one group with another
      */
@@ -295,6 +305,24 @@ namespace osgEarth
         }
     }
 
+    /** Remove a group from the middle of a scene graph */
+    inline void removeGroup(osg::Group* group)
+    {
+        if (group)
+        {
+            osg::ref_ptr<osg::Group> g = group;
+            while (g->getNumParents() > 0)
+            {
+                osg::Group* parent = group->getParent(group->getNumParents()-1);
+                for (unsigned c = 0; c < group->getNumChildren(); ++c)
+                {
+                    parent->addChild(group->getChild(c));
+                }
+                parent->removeChild(group);
+            }
+        }
+    }
+
     /**
      * Remove all a group's children.
      */
@@ -361,8 +389,9 @@ namespace osgEarth
 #define ADJUST_UPDATE_TRAV_COUNT( NODE, DELTA ) \
     { \
         int oldCount = NODE ->getNumChildrenRequiringUpdateTraversal(); \
-        if ( oldCount + DELTA >= 0 ) \
+        if ( oldCount + DELTA >= 0 ) { \
             NODE ->setNumChildrenRequiringUpdateTraversal( (unsigned int)(oldCount + DELTA ) ); \
+        } \
     }
 
     /**
diff --git a/src/osgEarth/Notify b/src/osgEarth/Notify
index 9b2492c..f1d9332 100644
--- a/src/osgEarth/Notify
+++ b/src/osgEarth/Notify
@@ -59,4 +59,6 @@ namespace osgEarth
 #define OE_STOP_TIMER(VAR) osg::Timer::instance()->delta_s( VAR##_oe_timer, osg::Timer::instance()->tick() )
 #define OE_GET_TIMER(VAR) osg::Timer::instance()->delta_s( VAR##_oe_timer, osg::Timer::instance()->tick() )
 
+#define OE_DEPRECATED(A, B) OE_WARN << #A << " is deprecated; please use " << #B << std::endl
+
 #endif // OSGEARTH_NOTIFY_H
diff --git a/src/osgEarth/ObjectIndex b/src/osgEarth/ObjectIndex
index a75056d..2c5c541 100644
--- a/src/osgEarth/ObjectIndex
+++ b/src/osgEarth/ObjectIndex
@@ -36,13 +36,8 @@
 
 namespace osgEarth
 {
-//#if OSG_VERSION_GREATER_OR_EQUAL(3,1,8)
     typedef unsigned       ObjectID;
     typedef osg::UIntArray ObjectIDArray;
-//#else
-//    typedef int            ObjectID;
-//    typedef osg::IntArray  ObjectIDArray;
-//#endif
 
     /** 
      * Virutal interface class for building an object index.
@@ -214,7 +209,7 @@ namespace osgEarth
     protected:
         virtual ~ObjectIndex() { }
         
-        typedef std::map<ObjectID, osg::ref_ptr<osg::Referenced> > IndexMap;
+        typedef std::map<ObjectID, osg::observer_ptr<osg::Referenced> > IndexMap;
 
         IndexMap                 _index;
         int                      _attribLocation;
diff --git a/src/osgEarth/ObjectIndex.cpp b/src/osgEarth/ObjectIndex.cpp
index b8c8b76..0d9df9b 100644
--- a/src/osgEarth/ObjectIndex.cpp
+++ b/src/osgEarth/ObjectIndex.cpp
@@ -22,6 +22,7 @@
 
 #include <osgEarth/ObjectIndex>
 #include <osgEarth/Registry>
+#include <osgEarth/VirtualProgram>
 #include <osg/NodeVisitor>
 #include <osg/Uniform>
 #include <osg/Geode>
@@ -41,6 +42,7 @@ namespace
 {
     const char* indexVertexInit =
         "#version " GLSL_VERSION_STR "\n"
+        GLSL_DEFAULT_PRECISION_FLOAT "\n"
 
         "#pragma vp_entryPoint oe_index_readObjectID \n"
         "#pragma vp_location   vertex_model \n"
diff --git a/src/osgEarth/OverlayDecorator b/src/osgEarth/OverlayDecorator
index 9fccfa4..5e6ea53 100644
--- a/src/osgEarth/OverlayDecorator
+++ b/src/osgEarth/OverlayDecorator
@@ -41,7 +41,7 @@ namespace osgEarth
     /**
      * Overlays geometry onto the terrain using various techniques.
      */
-    class OSGEARTH_EXPORT OverlayDecorator : public TerrainDecorator
+    class OSGEARTH_EXPORT OverlayDecorator : public osg::Group
     {
     public:
         OverlayDecorator();
@@ -89,9 +89,10 @@ namespace osgEarth
         void setMaximumHeight(double value) { _maxHeight = value; }
         double getMaximumHeight() const { return _maxHeight; }
 
-    public: // TerrainDecorator
-        virtual void onInstall( TerrainEngineNode* engine );
-        virtual void onUninstall( TerrainEngineNode* engine );
+        /**
+         * Initializes the decorator with a terrain engine
+         */
+        void setTerrainEngine(TerrainEngineNode*);
 
     public: // osg::Node
         void traverse( osg::NodeVisitor& nv );
@@ -116,7 +117,7 @@ namespace osgEarth
             osg::StateSet*                _terrainStateSet;  // same object as in PerViewData (shared across techniques)
             osg::ref_ptr<osg::Referenced> _techniqueData;    // technique sets this if needed
             const double*                 _horizonDistance;  // points to the PVD horizon distance
-            TextureCompositor*            _terrainResources; // for accessing image units
+            TerrainResources*             _terrainResources; // for accessing image units
             osg::Vec3d                    _eyeWorld;         // eyepoint in world coords
             osgShadow::ConvexPolyhedron   _visibleFrustumPH; // polyhedron representing the frustum
         };
@@ -136,7 +137,6 @@ namespace osgEarth
         optional<int>                 _explicitTextureUnit;
         optional<int>                 _textureUnit;
         optional<int>                 _textureSize;
-        bool                          _useShaders;
         bool                          _isGeocentric;
         unsigned                      _rttTraversalMask;
         double                        _maxHeight;
@@ -200,6 +200,8 @@ namespace osgEarth
         virtual const osg::BoundingSphere& getBound(
             OverlayDecorator::TechRTTParams& params) const { return params._group->getBound(); }
 
+        virtual bool optimizeToVisibleBound() const { return false; }
+
         virtual void onInstall( TerrainEngineNode* engine ) { }
 
         virtual void onUninstall( TerrainEngineNode* engine ) { }
diff --git a/src/osgEarth/OverlayDecorator.cpp b/src/osgEarth/OverlayDecorator.cpp
index ccd4317..392fc59 100755
--- a/src/osgEarth/OverlayDecorator.cpp
+++ b/src/osgEarth/OverlayDecorator.cpp
@@ -281,7 +281,6 @@ namespace
 //---------------------------------------------------------------------------
 
 OverlayDecorator::OverlayDecorator() :
-_useShaders          ( true ),
 _dumpRequested       ( false ),
 _rttTraversalMask    ( ~0 ),
 _maxHorizonDistance  ( DBL_MAX ),
@@ -358,7 +357,8 @@ OverlayDecorator::initializePerViewData( PerViewData& pvd, osg::Camera* cam )
         params._group = _overlayGroups[i].get();
         params._terrainStateSet = pvd._sharedTerrainStateSet.get(); // share it.
         params._horizonDistance = &pvd._sharedHorizonDistance;      // share it.
-        params._terrainResources = _engine->getResources();
+        if (_engine.valid())
+            params._terrainResources = _engine->getResources();
         params._mainCamera = cam;
     }
 }
@@ -370,43 +370,38 @@ OverlayDecorator::setOverlayGraphTraversalMask( unsigned mask )
     _rttTraversalMask = mask;
 }
 
-
 void
-OverlayDecorator::onInstall( TerrainEngineNode* engine )
+OverlayDecorator::setTerrainEngine(TerrainEngineNode* engine)
 {
-    _engine = engine;
+    if (engine)
+    {
+        _engine = engine;
 
-    // establish the earth's major axis:
-    MapInfo info(engine->getMap());
-    _isGeocentric = info.isGeocentric();
-    _srs = info.getProfile()->getSRS();
-    _ellipsoid = info.getProfile()->getSRS()->getEllipsoid();
+        // establish the earth's major axis:
+        MapInfo info(engine->getMap());
+        _isGeocentric = info.isGeocentric();
+        _srs = info.getProfile()->getSRS();
+        _ellipsoid = info.getProfile()->getSRS()->getEllipsoid();
 
-    for(Techniques::iterator t = _techniques.begin(); t != _techniques.end(); ++t )
-    {
-        t->get()->onInstall( engine );
+        for(Techniques::iterator t = _techniques.begin(); t != _techniques.end(); ++t )
+        {
+            t->get()->onInstall( engine );
+        }
     }
-}
-
 
-void
-OverlayDecorator::onUninstall( TerrainEngineNode* engine )
-{
-    for(Techniques::iterator t = _techniques.begin(); t != _techniques.end(); ++t )
+    else
     {
-        t->get()->onUninstall( engine );
+        for (Techniques::iterator t = _techniques.begin(); t != _techniques.end(); ++t)
+        {
+            t->get()->onUninstall(engine);
+        }
     }
-
-    _engine = 0L;
 }
 
-
 void
 OverlayDecorator::cullTerrainAndCalculateRTTParams(osgUtil::CullVisitor* cv,
                                                    PerViewData&          pvd)
 {
-    static int s_frame = 1;
-
     osg::Vec3d eye = cv->getViewPoint();
 
     double eyeLen;
@@ -423,7 +418,7 @@ OverlayDecorator::cullTerrainAndCalculateRTTParams(osgUtil::CullVisitor* cv,
 
     OE_TEST << LC << "------- OD CULL ------------------------" << std::endl;
 
-    if ( _isGeocentric )
+    if ( _isGeocentric && _engine.valid() )
     {
         eyeLen = eye.length();
 
@@ -596,55 +591,62 @@ OverlayDecorator::cullTerrainAndCalculateRTTParams(osgUtil::CullVisitor* cv,
     // now copy the RTT matrixes over to the techniques.
     for( unsigned t=0; t<pvd._techParams.size(); ++t )
     {
+        OverlayTechnique* tech = _techniques[t].get();
         TechRTTParams& params = pvd._techParams[t];
 
         // skip empty techniques
-        if ( !_techniques[t]->hasData(params) )
+        if ( !tech->hasData(params) )
             continue;
 
         // slice it to fit the overlay geometry. (this says 'visible' but it's just everything..
         // perhaps we can truly make it visible)
         osgShadow::ConvexPolyhedron visiblePH( frustumPH );
 
+        // if the technique wishes it, compute the bounds of the geometry and
+        // crop the frustum to fit those bounds. This will result in a tighter
+        // fit if the geometry bounds are smaller than the visible region of terrain.
+        if (tech->optimizeToVisibleBound())
+        {
 #if 0
-        osg::Polytope frustumPT;
-        frustumPH.getPolytope(frustumPT);
-        ComputeVisibleBounds cvb(frustumPT, MVP);
-        params._group->accept(cvb);
-        const osg::BoundingSphere& visibleOverlayBS = cvb._bs;
-        OE_WARN << "VBS radius = " << visibleOverlayBS.radius() << std::endl;
+            osg::Polytope frustumPT;
+            frustumPH.getPolytope(frustumPT);
+            ComputeVisibleBounds cvb(frustumPT, MVP);
+            params._group->accept(cvb);
+            const osg::BoundingSphere& visibleOverlayBS = cvb._bs;
+            OE_WARN << "VBS radius = " << visibleOverlayBS.radius() << std::endl;
 #else
-        const osg::BoundingSphere& visibleOverlayBS = _techniques[t]->getBound(params);
+            const osg::BoundingSphere& visibleOverlayBS = tech->getBound(params);
 #endif
-        if ( visibleOverlayBS.valid() )
-        {
-            // form an axis-aligned polytope around the bounding sphere of the
-            // overlay geometry. Use that to cut the camera frustum polytope.
-            // This will minimize the coverage area and also ensure inclusion
-            // of geometry that falls outside the camera frustum but inside
-            // the overlay area.
-            osg::Polytope visibleOverlayPT;
-
-            osg::Vec3d tangent(0,0,1);
-            if (fabs(worldUp*tangent) > 0.9999)
-                tangent.set(0,1,0);
-
-            osg::Vec3d westVec  = worldUp^tangent; westVec.normalize();
-            osg::Vec3d southVec = worldUp^westVec; southVec.normalize();
-            osg::Vec3d eastVec  = -westVec;
-            osg::Vec3d northVec = -southVec;
-
-            osg::Vec3d westPt  = visibleOverlayBS.center() + westVec*visibleOverlayBS.radius();
-            osg::Vec3d eastPt  = visibleOverlayBS.center() + eastVec*visibleOverlayBS.radius();
-            osg::Vec3d northPt = visibleOverlayBS.center() + northVec*visibleOverlayBS.radius();
-            osg::Vec3d southPt = visibleOverlayBS.center() + southVec*visibleOverlayBS.radius();
-
-            visibleOverlayPT.add(osg::Plane(-westVec,  westPt));
-            visibleOverlayPT.add(osg::Plane(-eastVec,  eastPt));
-            visibleOverlayPT.add(osg::Plane(-southVec, southPt));
-            visibleOverlayPT.add(osg::Plane(-northVec, northPt));
-
-            visiblePH.cut( visibleOverlayPT );
+            if ( visibleOverlayBS.valid() )
+            {
+                // form an axis-aligned polytope around the bounding sphere of the
+                // overlay geometry. Use that to cut the camera frustum polytope.
+                // This will minimize the coverage area and also ensure inclusion
+                // of geometry that falls outside the camera frustum but inside
+                // the overlay area.
+                osg::Polytope visibleOverlayPT;
+
+                osg::Vec3d tangent(0,0,1);
+                if (fabs(worldUp*tangent) > 0.9999)
+                    tangent.set(0,1,0);
+
+                osg::Vec3d westVec  = worldUp^tangent; westVec.normalize();
+                osg::Vec3d southVec = worldUp^westVec; southVec.normalize();
+                osg::Vec3d eastVec  = -westVec;
+                osg::Vec3d northVec = -southVec;
+
+                osg::Vec3d westPt  = visibleOverlayBS.center() + westVec*visibleOverlayBS.radius();
+                osg::Vec3d eastPt  = visibleOverlayBS.center() + eastVec*visibleOverlayBS.radius();
+                osg::Vec3d northPt = visibleOverlayBS.center() + northVec*visibleOverlayBS.radius();
+                osg::Vec3d southPt = visibleOverlayBS.center() + southVec*visibleOverlayBS.radius();
+
+                visibleOverlayPT.add(osg::Plane(-westVec,  westPt));
+                visibleOverlayPT.add(osg::Plane(-eastVec,  eastPt));
+                visibleOverlayPT.add(osg::Plane(-southVec, southPt));
+                visibleOverlayPT.add(osg::Plane(-northVec, northPt));
+
+                visiblePH.cut( visibleOverlayPT );
+            }
         }
 
         // for dumping, we want the previous fram's projection matrix
@@ -714,7 +716,7 @@ OverlayDecorator::cullTerrainAndCalculateRTTParams(osgUtil::CullVisitor* cv,
             params._rttViewMatrix.set( rttViewMatrix );
             params._rttProjMatrix.set( rttProjMatrix );
             params._eyeWorld = eye;
-            params._visibleFrustumPH = clampedFrustumPH; //frustumPH;
+            params._visibleFrustumPH = clampedFrustumPH;
         }
 
         // service a "dump" of the polyhedrons for dubugging purposes
@@ -727,17 +729,17 @@ OverlayDecorator::cullTerrainAndCalculateRTTParams(osgUtil::CullVisitor* cv,
             {
                 frustumPH.dumpGeometry(0,0,0,fn);
             }
-            osg::Node* camNode = osgDB::readNodeFile(fn);
+            osg::ref_ptr<osg::Node> camNode = osgDB::readRefNodeFile(fn);
             camNode->setName("camera");
 
             // visible overlay BEFORE cutting:
             //uncutVisiblePH.dumpGeometry(0,0,0,fn,osg::Vec4(0,1,1,1),osg::Vec4(0,1,1,.25));
-            //osg::Node* overlay = osgDB::readNodeFile(fn);
+            //osg::ref_ptr<osg::Node> overlay = osgDB::readRefNodeFile(fn);
             //overlay->setName("overlay");
 
             // visible overlay Polyherdron AFTER cuting:
             visiblePH.dumpGeometry(0,0,0,fn,osg::Vec4(1,.5,1,1),osg::Vec4(1,.5,0,.25));
-            osg::Node* intersection = osgDB::readNodeFile(fn);
+            osg::ref_ptr<osg::Node> intersection = osgDB::readRefNodeFile(fn);
             intersection->setName("intersection");
 
             // RTT frustum:
@@ -750,7 +752,7 @@ OverlayDecorator::cullTerrainAndCalculateRTTParams(osgUtil::CullVisitor* cv,
                 rttPH.transform( inverseMVP, MVP );
                 rttPH.dumpGeometry(0,0,0,fn,osg::Vec4(1,1,0,1),osg::Vec4(1,1,0,0.25));
             }
-            osg::Node* rttNode = osgDB::readNodeFile(fn);
+            osg::ref_ptr<osg::Node> rttNode = osgDB::readRefNodeFile(fn);
             rttNode->setName("rtt");
 
             // EyePoint
diff --git a/src/osgEarth/OverlayNode b/src/osgEarth/OverlayNode
deleted file mode 100644
index 3037a86..0000000
--- a/src/osgEarth/OverlayNode
+++ /dev/null
@@ -1,94 +0,0 @@
-/* -*-c++-*- */
-/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2011 Pelican Mapping
- * http://osgearth.org
- *
- * osgEarth is free software; you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>
- */
-
-#ifndef OSGEARTH_OVERLAY_NODE_H
-#define OSGEARTH_OVERLAY_NODE_H 1
-
-#include <osgEarth/Common>
-#include <osgEarth/MapNodeObserver>
-#include <osg/Group>
-
-namespace osgEarth
-{
-    class MapNode;
-
-    /**
-     * Abstract base class for a node that injects geometry into the MapNode's
-     * OverlayDecorator.
-     */
-    class OSGEARTH_EXPORT OverlayNode : public osg::Group, 
-                                        public MapNodeObserver
-    {
-    public:
-        // signature for the function that provides the technique group.
-        typedef osg::Group* (*TechniqueProvider)(MapNode*);      
-        
-    public:
-        /**
-         * Constructs a new overlay node.
-         */
-        OverlayNode( MapNode* mapNode, bool active, TechniqueProvider f =0L );
-
-    public:
-        /**
-         * Whether overlay is active. If true, the subgraph renders as a
-         * terrain overlay; If false it renders normally.
-         */
-        void setActive( bool value );
-        bool getActive() const { return _active; }
-
-    public: // MapNodeObserver
-
-        void setMapNode( MapNode* mapNode );
-
-        MapNode* getMapNode() { return _mapNode.get(); }
-
-        /** Sets the method that returns the technique group or overlay nodes */
-        void setTechniqueProvider(TechniqueProvider provider);
-
-    public: // osg::Node
-
-        virtual void traverse( osg::NodeVisitor& nv );
-
-    public: // osg::Group
-
-        // override these in order to manage the proxy container.
-        virtual bool addChild( osg::Node* child );
-        virtual bool insertChild( unsigned index, osg::Node* child );
-        virtual bool removeChild( osg::Node* child );
-        virtual bool replaceChild( osg::Node* origChild, osg::Node* newChild );
-
-    protected:
-        /** dtor */
-        virtual ~OverlayNode();
-        osg::ref_ptr<osg::Group> _overlayProxyContainer;
-
-    private:
-        bool                          _active;
-        bool                          _dirty;
-        bool                          _newActive;
-        osg::observer_ptr<MapNode>    _mapNode;
-        TechniqueProvider             _getGroup;
-
-        void applyChanges();
-    };
-
-} // namespace osgEarth
-
-#endif // OSGEARTH_OVERLAY_NODE_H
diff --git a/src/osgEarth/OverlayNode.cpp b/src/osgEarth/OverlayNode.cpp
deleted file mode 100644
index 57712d3..0000000
--- a/src/osgEarth/OverlayNode.cpp
+++ /dev/null
@@ -1,398 +0,0 @@
-/* -*-c++-*- */
-/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2016 Pelican Mapping
- * http://osgearth.org
- *
- * osgEarth is free software; you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>
- */
-
-#include <osgEarth/OverlayNode>
-#include <osgEarth/CullingUtils>
-#include <osgEarth/OverlayDecorator>
-#include <osgEarth/MapNode>
-#include <osgEarth/NodeUtils>
-#include <osgEarth/PrimitiveIntersector>
-#include <osgEarth/DPLineSegmentIntersector>
-#include <osgUtil/IntersectionVisitor>
-
-#define LC "[OverlayNode] "
-
-using namespace osgEarth;
-
-namespace
-{
-    /**
-     * When draping is enabled, the actual active graph goes under an OverlayProxy
-     * group. It tracks the accumulated stateset and nodemask of the Drapeable
-     * itself and applies it to the active geometry (which is installed under the
-     * MapNode's OverlayDecorator).
-     */
-    struct OverlayProxy : public osg::Group
-    {
-        OverlayProxy( osg::Node* owner ) 
-            : _owner(owner) { }
-
-        void traverse(osg::NodeVisitor& nv)
-        {
-            // only allow CULL and OD-internal traversal:
-            if ( dynamic_cast<OverlayDecorator::InternalNodeVisitor*>(&nv) )
-            {
-                osg::Group::traverse( nv );
-            }
-            else if ( nv.getVisitorType() == nv.CULL_VISITOR && _owner.valid() )
-            {
-                // first find the highest ancestor in the owner's node parental node path that does
-                // not occur in the visitor's node path. That is where we want to begin collecting
-                // state.
-                const osg::NodePath& visitorPath = nv.getNodePath();
-
-                // get the owner's node path (just use the first one)
-                osg::NodePathList ownerPaths;
-                ownerPaths = _owner->getParentalNodePaths();
-
-                // note: I descovered that getParentalNodePaths will stop when it finds an "invalid"
-                // node mask (e.g., == zero).. so indeed it's possible for there to be zero node paths.
-                if ( ownerPaths.size() > 0 )
-                {
-                    const osg::NodePath& ownerPath = ownerPaths[0];
-
-                    // first check the owner's traversal mask.
-                    bool visible = true;
-                    for( int k = 0; visible && k < (int)ownerPath.size(); ++k )
-                    {
-                        visible = nv.validNodeMask(*ownerPath[k]);
-                    }
-
-                    if ( visible )
-                    {
-                        // find the intersection point:
-                        int i = findIndexOfNodePathConvergence( visitorPath, ownerPath );
-
-                        if ( i >= 0 && i < (int)ownerPath.size()-1 )
-                        {
-                            osgUtil::CullVisitor* cv = Culling::asCullVisitor(nv);
-
-                            int pushes = 0;
-                            for( int k = i+1; k < (int)ownerPath.size(); ++k )
-                            {
-                                osg::Node* node = ownerPath[k];
-                                osg::StateSet* ss = ownerPath[k]->getStateSet();
-                                if ( ss )
-                                {
-                                    cv->pushStateSet( ss );
-                                    ++pushes;
-                                }
-                            }
-                        
-                            osg::Group::traverse( nv );
-
-                            for( int k = 0; k < pushes; ++k )
-                            {
-                                cv->popStateSet();
-                            }
-                        }
-                    }
-                }
-            }
-        }
-
-
-        // returns the deepest index into the ownerPath at which the two paths converge
-        // (i.e. share a node pointer)
-        int findIndexOfNodePathConvergence(const osg::NodePath& visitorPath, const osg::NodePath& ownerPath)
-        {
-            // use the knowledge that a NodePath is a vector.
-
-            for( int vi = visitorPath.size()-1; vi >= 0; --vi )
-            {
-                osg::Node* visitorNode = visitorPath[vi];
-                for( int oi = ownerPath.size()-1; oi >= 0; --oi )
-                {
-                    if ( ownerPath[oi] == visitorNode )
-                    {
-                        // found the deepest intersection, so set the start index to one higher.
-                        return oi;
-                    }
-                }
-            }   
-
-            // no convergence. 
-            return -1;
-        }
-
-        osg::observer_ptr<osg::Node> _owner;
-    };
-}
-
-//------------------------------------------------------------------------
-
-OverlayNode::OverlayNode( MapNode* mapNode, bool active, OverlayNode::TechniqueProvider provider ) :
-_newActive( active ),
-_active   ( false ),
-_dirty    ( false ),
-_getGroup ( provider )
-{
-    // create a container group that will house the culler. This culler
-    // allows a active node, which sits under the MapNode's OverlayDecorator,
-    // to "track" the traversal state of the OverlayNode itself.
-    _overlayProxyContainer = new OverlayProxy( this );
-
-    setMapNode( mapNode );
-
-    if ( mapNode && _getGroup )
-    {
-        if (_getGroup(mapNode) != 0L)
-        {
-            // If draping is requested, set up to apply it on the first update traversal.
-            // Can't apply it until then since we need safe access to the MapNode.
-            setActive( active );
-        }
-        else
-        {
-            OE_WARN << LC << "Overlay technique not available; disabled." << std::endl;
-        }
-    }
-}
-
-OverlayNode::~OverlayNode()
-{
-    // cleans up the overlayProxyContainer if necessary.
-    setMapNode(0L);
-}
-
-void
-OverlayNode::setMapNode( MapNode* mapNode )
-{
-    osg::ref_ptr<MapNode> oldMapNode;
-    _mapNode.lock(oldMapNode);
-
-    if ( oldMapNode.get() != mapNode )
-    {
-        if ( oldMapNode.valid() && _getGroup && _active && _overlayProxyContainer->getNumParents() > 0 )
-        {
-            osg::Group* group = _getGroup( oldMapNode.get() );
-            if ( group )
-                group->removeChild( _overlayProxyContainer.get() );
-        }
-
-        _mapNode = mapNode;
-
-        applyChanges();
-    }
-}
-
-void
-OverlayNode::setTechniqueProvider( OverlayNode::TechniqueProvider p )
-{
-    osg::ref_ptr<MapNode> save = getMapNode();
-    if ( save.valid() )
-        setMapNode( 0L );
-
-    _getGroup = p;
-
-    if ( save.valid() )
-        setMapNode( save.get() );
-}
-
-void
-OverlayNode::applyChanges()
-{
-    _active = _newActive;
-
-    osg::ref_ptr<MapNode> mapNode;
-    if ( _mapNode.lock(mapNode) && _getGroup )
-    {
-        if ( _active && _overlayProxyContainer->getNumParents() == 0 )
-        {
-            osg::Group* group = _getGroup( mapNode.get() );
-            if ( group )
-                group->addChild( _overlayProxyContainer.get() );
-        }
-        else if ( !_active && _overlayProxyContainer->getNumParents() > 0 )
-        {
-            osg::Group* group = _getGroup( mapNode.get() );
-            if ( group )
-                group->removeChild( _overlayProxyContainer.get() );
-        }
-
-        dirtyBound();
-    }
-}
-
-void
-OverlayNode::setActive( bool active )
-{    
-    if ( active != _active )
-    {        
-        _newActive = active;
-        if ( !_dirty )
-        {
-            _dirty = true;
-            ADJUST_UPDATE_TRAV_COUNT( this, 1 );
-        }        
-    }
-}
-
-bool
-OverlayNode::addChild( osg::Node* child )
-{
-    bool ok = osg::Group::addChild( child );
-    if ( _overlayProxyContainer.valid() )
-        _overlayProxyContainer->addChild( child );
-    return ok;
-}
-
-bool
-OverlayNode::insertChild( unsigned i, osg::Node* child )
-{
-    bool ok = osg::Group::insertChild( i, child );
-    if ( _overlayProxyContainer.valid() )
-        _overlayProxyContainer->insertChild( i, child );
-    return ok;
-}
-
-bool
-OverlayNode::removeChild( osg::Node* child )
-{
-    bool ok = osg::Group::removeChild( child );
-    if ( _overlayProxyContainer.valid() )
-        _overlayProxyContainer->removeChild( child );
-    return ok;
-}
-
-bool
-OverlayNode::replaceChild( osg::Node* oldChild, osg::Node* newChild )
-{
-    bool ok = osg::Group::replaceChild( oldChild, newChild );
-    if ( _overlayProxyContainer.valid() )
-        _overlayProxyContainer->replaceChild( oldChild, newChild );
-    return ok;
-}
-
-void
-OverlayNode::traverse( osg::NodeVisitor& nv )
-{
-    if ( !_overlayProxyContainer.valid() )
-    {
-        osg::Group::traverse( nv );
-    }
-    else
-    {
-        if ( nv.getVisitorType() == osg::NodeVisitor::CULL_VISITOR )
-        {
-            if ( _active )
-            {
-                // do nothing -- culling will happen via the OverlayProxy instead.
-            }
-            else
-            {
-                // for a non-active node, just traverse children as usual.
-                osg::Group::traverse( nv );
-            }
-        }
-
-        else if ( nv.getVisitorType() == osg::NodeVisitor::UPDATE_VISITOR )
-        {
-            if ( _dirty )
-            {
-                applyChanges();
-                _dirty = false;
-                ADJUST_UPDATE_TRAV_COUNT( this, -1 );
-            }
-            
-            // traverse children directly, regardles of active status
-            osg::Group::traverse( nv );
-        }
-
-        else if (dynamic_cast<osgUtil::IntersectionVisitor*>(&nv))
-        {
-            /*
-               In order to properly intersect with overlay geometries, attempt to find the point on the terrain where the pick occurred
-               cast a second intersector vertically at that point.
-
-               Currently this is only imlpemented for our custom PrimitiveIntersector.
-            */
-            osgUtil::IntersectionVisitor* iv = dynamic_cast<osgUtil::IntersectionVisitor*>(&nv);
-            osgEarth::PrimitiveIntersector* pi = iv ? dynamic_cast<osgEarth::PrimitiveIntersector *>(iv->getIntersector()) : 0L;
-
-            osg::ref_ptr<MapNode> mapNode;
-            if (pi && !pi->getOverlayIgnore() && _mapNode.lock(mapNode))
-            { 
-                osg::NodePath path = iv->getNodePath();
-                osg::NodePath prunedNodePath( path.begin(), path.end()-1 );
-                osg::Matrix modelToWorld = osg::computeLocalToWorld(prunedNodePath);
-                osg::Vec3d worldStart = pi->getStart() * modelToWorld;
-                osg::Vec3d worldEnd = pi->getEnd() * modelToWorld;
-
-                osg::ref_ptr<DPLineSegmentIntersector> lsi = new DPLineSegmentIntersector(worldStart, worldEnd);
-                osgUtil::IntersectionVisitor ivTerrain(lsi.get());
-                mapNode->getTerrainEngine()->accept(ivTerrain);
-
-                if (lsi->containsIntersections())
-                {
-                  osg::Vec3d worldIntersect = lsi->getFirstIntersection().getWorldIntersectPoint();
-                  
-                  GeoPoint mapIntersect;
-                  mapIntersect.fromWorld(mapNode->getMapSRS(), worldIntersect);
-
-                  osg::Vec3d newMapStart(mapIntersect.x(), mapIntersect.y(), 25000.0);
-                  osg::Vec3d newMapEnd(mapIntersect.x(), mapIntersect.y(), -25000.0);
-
-                  osg::Vec3d newWorldStart;
-                  mapNode->getMapSRS()->transformToWorld(newMapStart, newWorldStart);
-
-                  osg::Vec3d newWorldEnd;
-                  mapNode->getMapSRS()->transformToWorld(newMapEnd, newWorldEnd);
-
-                  osg::Matrix worldToModel;
-                  worldToModel.invert(modelToWorld);
-
-                  osg::Vec3d newModelStart = newWorldStart * worldToModel;
-                  osg::Vec3d newModelEnd = newWorldEnd * worldToModel;
-
-                  osg::ref_ptr<osgEarth::PrimitiveIntersector> pi2 = new osgEarth::PrimitiveIntersector(osgUtil::Intersector::MODEL, newModelStart, newModelEnd, pi->getThickness(), true);
-                  osgUtil::IntersectionVisitor iv2(pi2);
-                  iv2.setTraversalMask(iv->getTraversalMask());
-                  path[0]->accept(iv2);
-
-                  if (pi2->containsIntersections())
-                  {
-                    // Insert newlly found intersections into the original intersector.
-                    for (PrimitiveIntersector::Intersections::iterator it = pi2->getIntersections().begin(); it != pi2->getIntersections().end(); ++it)
-                    {
-                      PrimitiveIntersector::Intersection intersection(*it);
-                      intersection.ratio = 1.0;
-                      pi->insertIntersection(intersection);
-                    }
-                  }
-                }
-                else
-                {
-                  //OE_WARN << LC << "No hits on terrain!" << std::endl;
-                }
-            }
-            else
-            {
-              osg::Group::traverse( nv );
-            }
-        }
-
-        // handle other visitor types (like intersections, etc) by simply
-        // traversing the child graph.
-        else // if ( nv.getNodeVisitor() == osg::NodeVisitor::NODE_VISITOR )
-        {
-            osg::Group::traverse( nv );
-        }
-    }
-}
diff --git a/src/osgEarth/PagedNode b/src/osgEarth/PagedNode
new file mode 100644
index 0000000..5b3c7c6
--- /dev/null
+++ b/src/osgEarth/PagedNode
@@ -0,0 +1,95 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+* Copyright 2016 Pelican Mapping
+* http://osgearth.org
+*
+* osgEarth is free software; you can redistribute it and/or modify
+* it under the terms of the GNU Lesser General Public License as published by
+* the Free Software Foundation; either version 2 of the License, or
+* (at your option) any later version.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+* IN THE SOFTWARE.
+*
+* You should have received a copy of the GNU Lesser General Public License
+* along with this program.  If not, see <http://www.gnu.org/licenses/>
+*/
+#ifndef OSGEARTHUTIL_PAGEDNODE_H
+#define OSGEARTHUTIL_PAGEDNODE_H
+
+#include <osgEarth/Common>
+#include <osgEarth/optional>
+#include <osg/PagedLOD>
+
+namespace osgEarth
+{
+    /**
+     * PagedNode is a group with a self-contained paged child.
+     * It wraps the details of a PagedNode node into a container
+     * that is easier to manage.
+     *
+     * To use, override the class. Call setNode() with the default
+     * node to display, and implement loadChild() to load the 
+     * paged child node when it comes within range. Set the page-in
+     * range for the paged child node with setRange() and setRangeMode().
+     * Finally, call setupPaging() to complete setup.
+     */
+    class OSGEARTH_EXPORT PagedNode : public osg::Group
+    {
+    public:
+        PagedNode();
+
+        //! Sets the node to display by default when children
+        //! are not loaded.
+        void setNode(osg::Node* node);
+
+        //! Override to load paged child node
+        virtual osg::Node* loadChild() { return 0L; }
+
+        //! Bounding sphere of paged data
+        virtual osg::BoundingSphere getChildBound() const;
+
+        //! Returns true by default; override for custom behavior
+        virtual bool hasChild() const;
+
+        //! Call this after setting up your data on the attach point
+        void setupPaging();
+
+        /**
+         * Gets the range factor
+         */
+        float getRangeFactor() const { return _rangeFactor; }
+
+        /**
+         * Sets the range factor
+         */
+        void setRangeFactor(float rangeFactor) { _rangeFactor = rangeFactor; }
+
+        //! Sets the page-in rage mode (distance v. pixel-size; default is distance)
+        void setRangeMode(const osg::LOD::RangeMode);
+
+        //! Sets the range (or pixel size) at which to page in the child data.
+        void setRange(float range) { _range = range; }
+
+        //! Gets whether this node is additive.
+        bool getAdditive() const { return _additive; }
+
+        //! Sets whether the child replaces the default node (non-additive)
+        //! or renders alongside the default node (additive)
+        void setAdditive(bool value) { _additive = value; }
+
+    protected:
+        osg::Group* _attachPoint;
+        osg::PagedLOD* _plod;
+        bool _additive;
+        optional<float> _range;
+        float _rangeFactor;
+    };    
+}
+
+#endif // OSGEARTHUTIL_PAGEDNODE_H
diff --git a/src/osgEarth/PagedNode.cpp b/src/osgEarth/PagedNode.cpp
new file mode 100644
index 0000000..34f57a6
--- /dev/null
+++ b/src/osgEarth/PagedNode.cpp
@@ -0,0 +1,165 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+ * Copyright 2016 Pelican Mapping
+ * http://osgearth.org
+ *
+ * osgEarth is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+#include <osgEarth/PagedNode>
+#include <osgEarth/Utils>
+
+#include <osgDB/Registry>
+#include <osgDB/FileNameUtils>
+
+#define LC "[PagedNode] "
+
+using namespace osgEarth;
+
+namespace
+{    
+    struct PagedNodePseudoLoader : public osgDB::ReaderWriter
+    {
+        PagedNodePseudoLoader()
+        {
+            supportsExtension( "osgearth_pseudo_pagednode", "" );
+        }
+
+        const char* className() const
+        { // override
+            return "PagedNodePseudoLoader";
+        }
+
+        ReadResult readNode(const std::string& uri, const Options* options) const
+        {
+            if ( !acceptsExtension( osgDB::getLowerCaseFileExtension(uri) ) )
+                return ReadResult::FILE_NOT_HANDLED;
+
+            osg::ref_ptr<PagedNode> node;
+            if (!OptionsData<PagedNode>::lock(options, "osgEarth.PagedNode", node))
+            {
+                OE_WARN << "Internal error - no PagedNode object in OptionsData\n";
+                return ReadResult::ERROR_IN_READING_FILE;
+            }
+
+            return node->loadChild();
+        }
+    };
+
+    REGISTER_OSGPLUGIN(osgearth_pseudo_pagednode, PagedNodePseudoLoader);
+}
+
+PagedNode::PagedNode() :
+    _rangeFactor(6.0f),
+    _additive(false)
+{
+    _plod = new osg::PagedLOD;
+    addChild(_plod);
+
+    _attachPoint = new osg::Group;
+
+    _plod->addChild( _attachPoint );     
+}
+
+void PagedNode::setRangeMode(osg::LOD::RangeMode mode)
+{
+    _plod->setRangeMode(mode);
+}
+
+void PagedNode::setNode(osg::Node* node)
+{
+    if (node)
+        _attachPoint->addChild(node);
+}
+
+void PagedNode::setupPaging()
+{
+    osg::BoundingSphere bs = getChildBound();
+
+    _plod->setCenter( bs.center() ); 
+    _plod->setRadius( bs.radius() );
+
+    if ( hasChild() )
+    {    
+        // Now setup a filename on the PagedLOD that will load all of the children of this node.
+        _plod->setFileName(1, ".osgearth_pseudo_pagednode");
+      
+        // assemble data to pass to the pseudoloader
+        osgDB::Options* options = new osgDB::Options();
+        OptionsData<PagedNode>::set(options, "osgEarth.PagedNode", this);
+        _plod->setDatabaseOptions( options );
+
+        // Setup the min and max ranges.
+        float minRange;
+        if ( _range.isSet() )
+        {
+            minRange = _range.get();
+        }
+        else
+        {
+            if (_plod->getRangeMode() == _plod->DISTANCE_FROM_EYE_POINT)
+            {                
+                minRange = (float)(bs.radius() * _rangeFactor);
+            }
+            else
+            {
+                minRange = 256;
+            }
+        }
+
+        if (!_additive)
+        {
+            // Replace mode, the parent is replaced by its children.
+            if (_plod->getRangeMode() == _plod->DISTANCE_FROM_EYE_POINT)
+            {
+                _plod->setRange( 0, minRange, FLT_MAX );
+                _plod->setRange( 1, 0, minRange );
+            }
+            else
+            {
+                _plod->setRange(0, 0, minRange);
+                _plod->setRange(1, minRange, FLT_MAX);
+            }
+        }
+        else
+        {
+            // Additive, the parent remains and new data is added
+            if (_plod->getRangeMode() == _plod->DISTANCE_FROM_EYE_POINT)
+            {
+                _plod->setRange( 0, 0, FLT_MAX );
+                _plod->setRange( 1, 0, minRange );
+            }
+            else
+            {
+                _plod->setRange(0, 0, FLT_MAX);
+                _plod->setRange(1, minRange, FLT_MAX);
+            }
+        }
+    }
+    else
+    {
+        // no children, so max out the visibility range.
+        _plod->setRange( 0, 0, FLT_MAX );
+    }   
+}
+
+osg::BoundingSphere PagedNode::getChildBound() const
+{
+    return osg::BoundingSphere();
+}
+
+bool PagedNode::hasChild() const
+{
+    return true;
+}
+
diff --git a/src/osgEarth/PatchLayer b/src/osgEarth/PatchLayer
new file mode 100644
index 0000000..27570e2
--- /dev/null
+++ b/src/osgEarth/PatchLayer
@@ -0,0 +1,121 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+ * Copyright 2016 Pelican Mapping
+ * http://osgearth.org
+ *
+ * osgEarth is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+
+#ifndef OSGEARTH_PATCH_LAYER_H
+#define OSGEARTH_PATCH_LAYER_H 1
+
+#include <osgEarth/VisibleLayer>
+#include <osg/RenderInfo>
+#include <osg/Texture>
+
+namespace osgEarth
+{
+    //! Options that govern a PatchLayer
+    class /*header only*/ PatchLayerOptions : public VisibleLayerOptions
+    {
+    public:
+        PatchLayerOptions(const ConfigOptions& co = ConfigOptions()) : VisibleLayerOptions(co) { }
+    };
+
+    /**
+     * PatchLayer is a layer that can render terrain tiles using either
+     * a geometry shader patch (GL_PATCHES) or a custom draw callback.
+     */
+    class OSGEARTH_EXPORT PatchLayer : public VisibleLayer
+    {
+    public:
+        META_Layer(osgEarth, PatchLayer, PatchLayerOptions);
+
+        struct TileData : public osg::Referenced
+        {
+            osg::ref_ptr<osg::Texture> _texture;
+        };
+
+        struct DrawContext
+        {
+            const TileKey* key;
+            float          range;
+            osg::Texture*  colorTexture;
+            osg::Texture*  elevationTexture;
+            osg::Texture*  normalTexture;
+            osg::Texture*  coverageTexture;
+            DrawContext() : range(0.0f), colorTexture(0), elevationTexture(0), normalTexture(0), coverageTexture(0) { }
+        };
+
+        /**
+         * Callback that the terrain engine will call for custom tile rendering.
+         */
+        struct DrawCallback : public osg::Referenced
+        {
+            virtual void draw(osg::RenderInfo& ri, const DrawContext& di, osg::Referenced* data) =0;
+        };
+
+        /**
+         * Callback that the terrain engine will call to decide whether to 
+         * render a tile in this layer.
+         */
+        struct AcceptCallback : public osg::Referenced
+        {
+            /** Whether to accept the entire layer. Innvoked during Cull. */
+            virtual bool acceptLayer(osg::NodeVisitor& nv, const osg::Camera* camera) const { return true; }
+
+            /** Whether to accept a specific tile. Invoked during Cull. */
+            virtual bool acceptKey(const TileKey& key) const { return true; }
+        };
+
+
+    public:
+        PatchLayer();
+
+        /**
+         * Draw callback to use to render tiles in this layer
+         */
+        void setDrawCallback(DrawCallback* value) { _drawCallback = value; }
+        DrawCallback* getDrawCallback() const { return _drawCallback.get(); }
+
+        /**
+         * Terrain LOD at which to render tiles in this patch layer
+         */
+        void setAcceptCallback(AcceptCallback* value) { _acceptCallback = value; }
+        AcceptCallback* getAcceptCallback() const { return _acceptCallback.get(); }
+
+
+        TileData* createTileData(const TileKey& key) { return 0L; }
+
+    protected:
+
+        //! Subclass constructor
+        PatchLayer(PatchLayerOptions* options);
+
+        /** dtor */
+        virtual ~PatchLayer() { }
+
+        // post-ctor initialization, chain to subclasses.
+        virtual void init();
+
+    private:
+        osg::ref_ptr<DrawCallback> _drawCallback;
+        osg::ref_ptr<AcceptCallback> _acceptCallback;
+    };
+
+    typedef std::vector< osg::ref_ptr<PatchLayer> > PatchLayerVector;
+
+} // namespace osgEarth
+
+#endif // OSGEARTH_PATCH_LAYER_H
diff --git a/src/osgEarth/TilePatchCallback.cpp b/src/osgEarth/PatchLayer.cpp
similarity index 68%
rename from src/osgEarth/TilePatchCallback.cpp
rename to src/osgEarth/PatchLayer.cpp
index a0eac72..08265dd 100644
--- a/src/osgEarth/TilePatchCallback.cpp
+++ b/src/osgEarth/PatchLayer.cpp
@@ -16,11 +16,27 @@
  * You should have received a copy of the GNU Lesser General Public License
  * along with this program.  If not, see <http://www.gnu.org/licenses/>
  */
-#include <osgEarth/TilePatchCallback>
-
-#include <osg/Node>
-#include <osg/StateSet>
-#include <osgUtil/CullVisitor>
+#include <osgEarth/PatchLayer>
 
 using namespace osgEarth;
 
+PatchLayer::PatchLayer() :
+VisibleLayer()
+{
+    init();
+}
+
+PatchLayer::PatchLayer(PatchLayerOptions* optionsPtr) :
+VisibleLayer(optionsPtr ? optionsPtr : &_optionsConcrete),
+_options(optionsPtr ? optionsPtr : &_optionsConcrete)
+{
+    //nop - subclass will call init()
+}
+
+void
+PatchLayer::init()
+{
+    Layer::init();
+    
+    setRenderType(RENDERTYPE_PATCH);
+}
diff --git a/src/osgEarth/PhongLighting.frag.glsl b/src/osgEarth/PhongLighting.frag.glsl
new file mode 100644
index 0000000..ef673e2
--- /dev/null
+++ b/src/osgEarth/PhongLighting.frag.glsl
@@ -0,0 +1,145 @@
+#version $GLSL_VERSION_STR
+$GLSL_DEFAULT_PRECISION_FLOAT
+
+#pragma vp_name       Phong Lighting Vertex Stage
+#pragma vp_entryPoint oe_phong_fragment
+#pragma vp_location   fragment_lighting
+
+#pragma import_defines(OE_LIGHTING, OE_NUM_LIGHTS)
+
+#ifdef OE_LIGHTING
+
+in vec3 oe_phong_vertexView3; 
+
+// stage global
+vec3 vp_Normal;
+
+// Parameters of each light:
+struct osg_LightSourceParameters 
+{   
+   vec4 ambient;
+   vec4 diffuse;
+   vec4 specular;
+   vec4 position;
+   vec3 spotDirection;
+   float spotExponent;
+   float spotCutoff;
+   float spotCosCutoff;
+   float constantAttenuation;
+   float linearAttenuation;
+   float quadraticAttenuation;
+
+   bool enabled;
+};  
+uniform osg_LightSourceParameters osg_LightSource[OE_NUM_LIGHTS];
+
+// Surface material:
+struct osg_MaterialParameters  
+{   
+   vec4 emission;    // Ecm   
+   vec4 ambient;     // Acm   
+   vec4 diffuse;     // Dcm   
+   vec4 specular;    // Scm   
+   float shininess;  // Srm  
+};  
+uniform osg_MaterialParameters osg_FrontMaterial; 
+
+
+void oe_phong_fragment(inout vec4 color) 
+{
+    // See:
+    // https://en.wikipedia.org/wiki/Phong_reflection_model
+    // https://www.opengl.org/sdk/docs/tutorials/ClockworkCoders/lighting.php
+    // https://en.wikibooks.org/wiki/GLSL_Programming/GLUT/Multiple_Lights
+
+    vec3 N = normalize(vp_Normal);
+
+    float shine = clamp(osg_FrontMaterial.shininess, 1.0, 128.0); 
+
+    // Accumulate the lighting, starting with material emission. We are currently
+    // omitting the ambient term for now since we are not using LightModel ambience.
+    vec3 totalLighting =
+        osg_FrontMaterial.emission.rgb;
+        // + osg_FrontMaterial.ambient.rgb * osg_LightModel.ambient.rgb;
+    
+    int numLights = OE_NUM_LIGHTS; //min(osg_NumLights, MAX_LIGHTS);
+
+    for (int i=0; i<numLights; ++i)
+    {
+        const float attenuation = 1.0;
+
+        if (osg_LightSource[i].enabled)
+        {
+            float attenuation = 1.0;
+            vec3 L; // vertex-to-light-source vector.
+
+            // directional light:
+            if (osg_LightSource[i].position.w == 0.0)
+            {
+                L = normalize(osg_LightSource[i].position.xyz);
+            }
+
+            // point or spot light:
+            else
+            {
+                // calculate VL, the vertex-to-light vector:
+                vec4 V = vec4(oe_phong_vertexView3, 1.0) * osg_LightSource[i].position.w;
+                vec4 VL4 = osg_LightSource[i].position - V;
+                L = normalize(VL4.xyz);
+
+                // calculate attenuation:
+                float distance = length(VL4);
+                attenuation = 1.0 / (
+                    osg_LightSource[i].constantAttenuation +
+                    osg_LightSource[i].linearAttenuation * distance +
+                    osg_LightSource[i].quadraticAttenuation * distance * distance);
+
+                // for a spot light, the attenuation help form the cone:
+                if (osg_LightSource[i].spotCutoff <= 90.0)
+                {
+                    vec3 D = normalize(osg_LightSource[i].spotDirection);
+                    float clampedCos = max(0.0, dot(-L,D));
+                    attenuation = clampedCos < osg_LightSource[i].spotCosCutoff ?
+                        0.0 :
+                        attenuation * pow(clampedCos, osg_LightSource[i].spotExponent);
+                }
+            }
+
+            vec3 ambientReflection =
+                attenuation
+                * osg_FrontMaterial.ambient.rgb
+                * osg_LightSource[i].ambient.rgb;
+
+            float NdotL = max(dot(N,L), 0.0); 
+
+            vec3 diffuseReflection =
+                attenuation
+                * osg_LightSource[i].diffuse.rgb * osg_FrontMaterial.diffuse.rgb
+                * NdotL;
+                
+            vec3 specularReflection = vec3(0.0);
+            if (NdotL > 0.0)
+            {
+                vec3 H = reflect(-L,N); 
+                float HdotN = max(dot(H,N), 0.0); 
+
+                specularReflection =
+                    attenuation
+                    * osg_LightSource[i].specular.rgb
+                    * osg_FrontMaterial.specular.rgb
+                    * pow(HdotN, shine);
+            }
+
+            totalLighting += ambientReflection + diffuseReflection + specularReflection;
+        }
+    }
+    
+    color.rgb *= totalLighting;
+}
+
+#else
+
+// nop
+void oe_phong_fragment(inout vec4 color) { }
+
+#endif
\ No newline at end of file
diff --git a/src/osgEarth/PhongLighting.vert.glsl b/src/osgEarth/PhongLighting.vert.glsl
new file mode 100644
index 0000000..cf70b12
--- /dev/null
+++ b/src/osgEarth/PhongLighting.vert.glsl
@@ -0,0 +1,20 @@
+#version $GLSL_VERSION_STR
+$GLSL_DEFAULT_PRECISION_FLOAT
+
+#pragma vp_name       Phong Lighting Vertex Stage
+#pragma vp_entryPoint oe_phong_vertex
+#pragma vp_location   vertex_view
+
+#pragma import_defines(OE_LIGHTING)
+
+
+out vec3 oe_phong_vertexView3;
+
+void oe_phong_vertex(inout vec4 VertexVIEW)
+{
+#ifndef OE_LIGHTING
+    return;
+#endif
+
+    oe_phong_vertexView3 = VertexVIEW.xyz / VertexVIEW.w;
+}
diff --git a/src/osgEarth/PhongLightingEffect b/src/osgEarth/PhongLightingEffect
index b6121c7..3912e17 100644
--- a/src/osgEarth/PhongLightingEffect
+++ b/src/osgEarth/PhongLightingEffect
@@ -47,7 +47,7 @@ namespace osgEarth
         /** whether to create its own lighting mode uniform (default = true).
           * Set this to false if you are creating the lighting uniform elsewhere.
           * The lighting uniform is returned by ShaderFactory::createUniformForGLMode(GL_LIGHTING). */
-        void setCreateLightingUniform(bool value);
+        //void setCreateLightingUniform(bool value);
 
     public:
         /** attach this effect to a stateset. */
@@ -55,6 +55,7 @@ namespace osgEarth
 
         /** detach this effect from any attached statesets. */
         void detach();
+
         /** detach this effect from a stateset. */
         void detach(osg::StateSet* stateset);
 
@@ -65,7 +66,7 @@ namespace osgEarth
 
         bool _supported;
         StateSetList _statesets;
-        osg::ref_ptr<osg::Uniform> _lightingUniform;
+        //osg::ref_ptr<osg::Uniform> _lightingUniform;
 
         void init();
     };
diff --git a/src/osgEarth/PhongLightingEffect.cpp b/src/osgEarth/PhongLightingEffect.cpp
index abb6096..36a2e5a 100644
--- a/src/osgEarth/PhongLightingEffect.cpp
+++ b/src/osgEarth/PhongLightingEffect.cpp
@@ -19,13 +19,14 @@
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
 */
-
 #include <osgEarth/PhongLightingEffect>
 #include <osgEarth/Registry>
 #include <osgEarth/Capabilities>
 #include <osgEarth/ShaderFactory>
 #include <osgEarth/StringUtils>
 #include <osgEarth/VirtualProgram>
+#include <osgEarth/Shaders>
+#include <osgEarth/Lighting>
 
 // GL_LIGHTING is not always defined on GLES so define it.
 #ifndef GL_LIGHTING
@@ -34,111 +35,6 @@
 
 using namespace osgEarth;
 
-namespace
-{
-#ifdef OSG_GLES2_AVAILABLE
-    static const char* Phong_Vertex =
-        "#version " GLSL_VERSION_STR "\n"
-        GLSL_DEFAULT_PRECISION_FLOAT "\n"
-
-        "uniform bool oe_mode_GL_LIGHTING; \n"
-        "varying vec4 oe_lighting_adjustment; \n"
-        "varying vec4 oe_lighting_zero_vec; \n"
-        "varying vec3 vp_Normal; \n"
-
-        "void oe_phong_vertex(inout vec4 VertexVIEW) \n"
-        "{ \n"
-        "    if ( oe_mode_GL_LIGHTING == false ) return; \n"
-        "    oe_lighting_adjustment = vec4(1.0); \n"
-        "    vec3 N = vp_Normal; \n"
-        "    float NdotL = dot( N, normalize(gl_LightSource[0].position.xyz) ); \n"
-        "    NdotL = max( 0.0, NdotL ); \n"
-
-        // NOTE: See comment in the fragment shader below for an explanation of
-        //       this oe_zero_vec value.
-        "    oe_lighting_zero_vec = vec4(0.0); \n"
-
-        "    vec4 adj = \n"
-        "        gl_FrontLightProduct[0].ambient + \n"
-        "        gl_FrontLightProduct[0].diffuse * NdotL; \n"
-        "    oe_lighting_adjustment = clamp( adj, 0.0, 1.0 ); \n"
-        "} \n";
-
-    static const char* Phong_Fragment =
-        "#version " GLSL_VERSION_STR "\n"
-        GLSL_DEFAULT_PRECISION_FLOAT "\n"
-        
-        "uniform bool oe_mode_GL_LIGHTING; \n"
-        "varying vec4 oe_lighting_adjustment; \n"
-        "varying vec4 oe_lighting_zero_vec; \n"
-
-        "void oe_phong_fragment(inout vec4 color) \n"
-        "{ \n"
-        "    if ( oe_mode_GL_LIGHTING == false ) return; \n"
-        //NOTE: The follow was changed from the single line
-        //      "color *= oe_lighting_adjustment" to the current code to fix
-        //      an issue on iOS devices.  Adding a varying vec4 value set to
-        //      (0.0,0.0,0.0,0.0) to the color should not make a difference,
-        //      but it is part of the solution to the issue we were seeing.
-        //      Without it and the additional lines of code, the globe was
-        //      rendering textureless (just a white surface with lighting).
-        "    float alpha = color.a; \n"
-        "    color = color * oe_lighting_adjustment + oe_lighting_zero_vec; \n"
-        "    color.a = alpha; \n"
-        "} \n";
-
-#else
-
-    static const char* Phong_Vertex =
-        "#version " GLSL_VERSION_STR "\n"
-        GLSL_DEFAULT_PRECISION_FLOAT "\n"
-        
-        "uniform bool oe_mode_GL_LIGHTING; \n"
-
-        "out vec3 oe_phong_vertexView3; \n"
-
-        "void oe_phong_vertex(inout vec4 VertexVIEW) \n"
-        "{ \n"
-        "    if ( oe_mode_GL_LIGHTING == false ) return; \n"
-        "    oe_phong_vertexView3 = VertexVIEW.xyz / VertexVIEW.w; \n"
-        "} \n";
-
-    static const char* Phong_Fragment =
-        "#version " GLSL_VERSION_STR "\n"
-        GLSL_DEFAULT_PRECISION_FLOAT "\n"
-
-        "uniform bool oe_mode_GL_LIGHTING; \n"
-
-        "in vec3 oe_phong_vertexView3; \n"
-        "in vec3 vp_Normal; \n"
-
-        "void oe_phong_fragment(inout vec4 color) \n"
-        "{ \n"        
-        "    if ( oe_mode_GL_LIGHTING == false ) return; \n"
-
-        "    vec3 L = normalize(gl_LightSource[0].position.xyz); \n"
-        "    vec3 N = vp_Normal; \n"//normalize(vp_Normal); \n"
-        
-        "    vec4 ambient = gl_FrontLightProduct[0].ambient; \n"
-
-        "    float NdotL = max(dot(N,L), 0.0); \n"
-
-        "    vec4 diffuse = gl_FrontLightProduct[0].diffuse * NdotL; \n"
-        
-        "    vec4 specular= vec4(0); \n"
-        "    if (NdotL > 0.0) \n"
-        "    { \n"
-        "        vec3 V = normalize(oe_phong_vertexView3); \n"
-        "        vec3 H = reflect(-L,N); \n"
-        "        float HdotN = max(dot(H,N), 0.0); \n"
-        "        float shine = clamp(gl_FrontMaterial.shininess, 1.0, 128.0); \n"
-        "        specular = gl_FrontLightProduct[0].specular * pow(HdotN, shine); \n"
-        "    } \n"
-
-        "    color.rgb *= ambient.rgb + diffuse.rgb + specular.rgb; \n"
-        "} \n";
-#endif
-}
 
 PhongLightingEffect::PhongLightingEffect()
 {
@@ -155,20 +51,21 @@ void
 PhongLightingEffect::init()
 {
     _supported = Registry::capabilities().supportsGLSL();
-    if ( _supported )
-    {
-        _lightingUniform = Registry::shaderFactory()->createUniformForGLMode( GL_LIGHTING, 1 );
-    }
+    // Replaced with setDefine
+    //if ( _supported )
+    //{
+    //    _lightingUniform = Registry::shaderFactory()->createUniformForGLMode( GL_LIGHTING, 1 );
+    //}
 }
 
-void
-PhongLightingEffect::setCreateLightingUniform(bool value)
-{
-    if ( !value )
-    {        
-        _lightingUniform = 0L;
-    }
-}
+//void
+//PhongLightingEffect::setCreateLightingUniform(bool value)
+//{
+//    if ( !value )
+//    {        
+//        _lightingUniform = 0L;
+//    }
+//}
 
 PhongLightingEffect::~PhongLightingEffect()
 {
@@ -183,10 +80,16 @@ PhongLightingEffect::attach(osg::StateSet* stateset)
         _statesets.push_back(stateset);
         VirtualProgram* vp = VirtualProgram::getOrCreate(stateset);
         vp->setName( "osgEarth.PhongLightingEffect" );
-        vp->setFunction( "oe_phong_vertex", Phong_Vertex, ShaderComp::LOCATION_VERTEX_VIEW, 0.5f );
-        vp->setFunction( "oe_phong_fragment", Phong_Fragment, ShaderComp::LOCATION_FRAGMENT_LIGHTING, 0.5f);
-        if ( _lightingUniform.valid() )
-            stateset->addUniform( _lightingUniform.get() );
+        
+        Shaders shaders;
+        shaders.load(vp, shaders.PhongLightingVertex);
+        shaders.load(vp, shaders.PhongLightingFragment);
+
+        stateset->setDefine(OE_LIGHTING_DEFINE, osg::StateAttribute::ON);
+        stateset->setDefine("OE_NUM_LIGHTS", "1");
+
+        //if ( _lightingUniform.valid() )
+        //    stateset->addUniform( _lightingUniform.get() );
     }
 }
 
@@ -200,7 +103,7 @@ PhongLightingEffect::detach()
             osg::ref_ptr<osg::StateSet> stateset;
             if ( (*it).lock(stateset) )
             {
-                detach( stateset );
+                detach(stateset.get());
                 (*it) = 0L;
             }
         }
@@ -214,14 +117,17 @@ PhongLightingEffect::detach(osg::StateSet* stateset)
 {
     if ( stateset && _supported )
     {
-        if ( _lightingUniform.valid() )
-            stateset->removeUniform( _lightingUniform.get() );
+        //if ( _lightingUniform.valid() )
+        //    stateset->removeUniform( _lightingUniform.get() );
+
+        stateset->removeDefine(OE_LIGHTING_DEFINE);
 
         VirtualProgram* vp = VirtualProgram::get( stateset );
         if ( vp )
         {
-            vp->removeShader( "oe_phong_vertex" );
-            vp->removeShader( "oe_phong_fragment" );
+            Shaders shaders;
+            shaders.unload(vp, shaders.PhongLightingVertex);
+            shaders.unload(vp, shaders.PhongLightingFragment);
         }
     }
 }
diff --git a/src/osgEarth/PluginLoader b/src/osgEarth/PluginLoader
new file mode 100644
index 0000000..99bf9ae
--- /dev/null
+++ b/src/osgEarth/PluginLoader
@@ -0,0 +1,83 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+ * Copyright 2016 Pelican Mapping
+ * http://osgearth.org
+ *
+ * osgEarth is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+
+#ifndef OSGEARTH_PLUGIN_LOADER_H
+#define OSGEARTH_PLUGIN_LOADER_H 1
+
+#include <osgEarth/Common>
+#include <osgDB/Registry>
+#include <osgDB/FileNameUtils>
+
+namespace osgEarth
+{
+    /**
+     * Template that helps with the registration of plugins.
+     */
+    template<class T>
+    class RegisterPluginLoader
+    {
+        public:
+            RegisterPluginLoader(const std::string& name)
+            {
+                if (osgDB::Registry::instance())
+                {
+                    _rw = new T(name);
+                    osgDB::Registry::instance()->addReaderWriter(_rw.get());
+                }
+            }
+
+            ~RegisterPluginLoader()
+            {
+                if (osgDB::Registry::instance())
+                {
+                    osgDB::Registry::instance()->removeReaderWriter(_rw.get());
+                }
+            }
+
+            T* get() { return _rw.get(); }
+
+        protected:
+            osg::ref_ptr<T> _rw;
+    };
+
+    /**
+     * Template that create a plugin loader.
+     */
+    template<typename T, typename U>
+    class PluginLoader : public osgDB::ReaderWriter
+    {
+    public: // Plugin stuff
+        PluginLoader(const std::string& name) {
+            supportsExtension( name, name );
+        }
+
+        virtual ~PluginLoader() { }
+
+        ReadResult readObject(const std::string& filename, const osgDB::Options* dbOptions) const
+        {
+          if ( !acceptsExtension(osgDB::getLowerCaseFileExtension(filename)) )
+                return ReadResult::FILE_NOT_HANDLED;
+
+          return ReadResult( new T(U::getConfigOptions(dbOptions)) );
+        }
+    };
+
+} // namespace osgEarth
+
+#endif // OSGEARTH_PLUGIN_LOADER_H
diff --git a/src/osgEarth/Profile b/src/osgEarth/Profile
index 37155ba..7bc9ce5 100644
--- a/src/osgEarth/Profile
+++ b/src/osgEarth/Profile
@@ -75,10 +75,7 @@ namespace osgEarth
         Config getConfig() const;
 
     protected:
-        void mergeConfig( const Config& conf ) {
-            ConfigOptions::mergeConfig( conf );
-            fromConfig( conf );
-        }
+        virtual void mergeConfig( const Config& conf );
 
     private:
         void fromConfig( const Config& conf );
diff --git a/src/osgEarth/Profile.cpp b/src/osgEarth/Profile.cpp
index 7a28990..2e133e1 100644
--- a/src/osgEarth/Profile.cpp
+++ b/src/osgEarth/Profile.cpp
@@ -23,6 +23,7 @@
 #include <osgEarth/Cube>
 #include <osgEarth/SpatialReference>
 #include <osgEarth/StringUtils>
+#include <osgEarth/Bounds>
 #include <osgDB/FileNameUtils>
 #include <algorithm>
 #include <sstream>
@@ -55,6 +56,12 @@ _numTilesHighAtLod0( 1 )
     _namedProfile = namedProfile; // don't set above
 }
 
+ void
+ ProfileOptions::mergeConfig( const Config& conf ) {
+    ConfigOptions::mergeConfig( conf );
+    fromConfig( conf );
+}
+
 void
 ProfileOptions::fromConfig( const Config& conf )
 {
@@ -89,8 +96,8 @@ ProfileOptions::getConfig() const
     }
     else
     {
-        conf.updateIfSet( "srs", _srsInitString );
-        conf.updateIfSet( "vdatum", _vsrsInitString );
+        conf.set( "srs", _srsInitString );
+        conf.set( "vdatum", _vsrsInitString );
 
         if ( _bounds.isSet() )
         {
@@ -100,8 +107,8 @@ ProfileOptions::getConfig() const
             conf.update( "ymax", toString(_bounds->yMax()) );
         }
 
-        conf.updateIfSet( "num_tiles_wide_at_lod_0", _numTilesWideAtLod0 );
-        conf.updateIfSet( "num_tiles_high_at_lod_0", _numTilesHighAtLod0 );
+        conf.set( "num_tiles_wide_at_lod_0", _numTilesWideAtLod0 );
+        conf.set( "num_tiles_high_at_lod_0", _numTilesHighAtLod0 );
     }
     return conf;
 }
@@ -210,7 +217,30 @@ Profile::create(const std::string& srsInitString,
     }
     else if ( srs.valid() )
     {
-        OE_WARN << LC << "Failed to create profile; you must provide extents with a projected SRS." << std::endl;
+        OE_INFO << LC << "No extents given, making some up.\n";
+        Bounds bounds;
+        if (srs->guessBounds(bounds))
+        {
+            if (numTilesWideAtLod0 == 0 || numTilesHighAtLod0 == 0)
+            {
+                double ar = (bounds.width() / bounds.height());
+                if (ar >= 1.0) {
+                    int ari = (int)ar;
+                    numTilesHighAtLod0 = 1;
+                    numTilesWideAtLod0 = ari;
+                }
+                else {
+                    int ari = (int)(1.0/ar);
+                    numTilesWideAtLod0 = 1;
+                    numTilesHighAtLod0 = ari;
+                }
+            }            
+            return Profile::create(srs.get(), bounds.xMin(), bounds.yMin(), bounds.xMax(), bounds.yMax(), numTilesWideAtLod0, numTilesHighAtLod0);
+        }
+        else
+        {
+            OE_WARN << LC << "Failed to create profile; you must provide extents with a projected SRS." << std::endl;
+        }
     }
     else
     {
@@ -514,7 +544,7 @@ Profile::getNumTiles(unsigned int lod, unsigned int& out_tiles_wide, unsigned in
 unsigned int
 Profile::getLevelOfDetailForHorizResolution( double resolution, int tileSize ) const
 {
-    if ( tileSize <= 0 || resolution <= 0.0 ) return 0;
+    if ( tileSize <= 0 || resolution <= 0.0 ) return 23;
 
     double tileRes = (_extent.width() / (double)_numTilesWideAtLod0) / (double)tileSize;
     unsigned int level = 0;
diff --git a/src/osgEarth/Profiler b/src/osgEarth/Profiler
index 8daff41..9eaded4 100644
--- a/src/osgEarth/Profiler
+++ b/src/osgEarth/Profiler
@@ -29,6 +29,7 @@ namespace osgEarth
 {
     /**
     * Poor man's profiler.
+    * @deprecated
     */
     class OSGEARTH_EXPORT Profiler
     {
@@ -47,6 +48,11 @@ namespace osgEarth
         * Dumps the stats to the console.
         */
         static void dump();
+
+		/**
+		* Clears the current stats
+		*/
+		static void clear();
     };
 
     class /*OSGEARTH_EXPORT*/ ScopedProfiler
diff --git a/src/osgEarth/Profiler.cpp b/src/osgEarth/Profiler.cpp
index a21847f..56c586e 100644
--- a/src/osgEarth/Profiler.cpp
+++ b/src/osgEarth/Profiler.cpp
@@ -72,4 +72,11 @@ void Profiler::dump()
     {
         OE_NOTICE << itr->first << ": calls=" << S_CALL_COUNT[itr->first] << "  time=" << itr->second << "s" << std::endl;
     }
+}
+
+void Profiler::clear()
+{
+	S_ELAPSED_TIMES.clear();
+	S_CALL_COUNT.clear();
+	S_START_TIMES.clear();
 }
\ No newline at end of file
diff --git a/src/osgEarth/QuadTree b/src/osgEarth/QuadTree
deleted file mode 100644
index c7b5662..0000000
--- a/src/osgEarth/QuadTree
+++ /dev/null
@@ -1,199 +0,0 @@
-/* -*-c++-*- */
-/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
-* http://osgearth.org
-*
-* osgEarth is free software; you can redistribute it and/or modify
-* it under the terms of the GNU Lesser General Public License as published by
-* the Free Software Foundation; either version 2 of the License, or
-* (at your option) any later version.
-*
-* This program is distributed in the hope that it will be useful,
-* but WITHOUT ANY WARRANTY; without even the implied warranty of
-* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-* GNU Lesser General Public License for more details.
-*
-* You should have received a copy of the GNU Lesser General Public License
-* along with this program.  If not, see <http://www.gnu.org/licenses/>
-*/
-#ifndef OSGEARTH_QUAD_TREE
-#define OSGEARTH_QUAD_TREE 1
-
-#include <osgEarth/Common>
-
-#include <osg/Shape>
-#include <osg/Geometry>
-
-#include <map>
-
-namespace osgEarth
-{
-    class OSGEARTH_EXPORT QuadTree : public osg::Shape
-    {
-        public:
-
-            QuadTree();
-
-            QuadTree(const QuadTree& rhs, const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY);
-
-            META_Shape(osg, QuadTree)
-
-            struct OSGEARTH_EXPORT BuildOptions
-            {
-                BuildOptions();
-
-                unsigned int _numVerticesProcessed;
-                unsigned int _targetNumTrianglesPerLeaf;
-                unsigned int _maxNumLevels;
-                unsigned int _numTriangles;
-            };
-
-
-            /** Build the quadtree from the specified source geometry object.
-              * retun true on success. */
-            virtual bool build(BuildOptions& buildOptions, osg::Geometry* geometry);
-
-            struct LineSegmentIntersection
-            {
-                LineSegmentIntersection():
-                    ratio(-1.0),
-                    p0(0),
-                    p1(0),
-                    p2(0),
-                    r0(0.0f),
-                    r1(0.0f),
-                    r2(0.0f),
-                    primitiveIndex(0) {}
-
-                bool operator < (const LineSegmentIntersection& rhs) const { return ratio < rhs.ratio; }
-
-                typedef std::vector<unsigned int>   IndexList;
-                typedef std::vector<double>         RatioList;
-
-                double                          ratio;
-                osg::Vec3d                      intersectionPoint;
-                osg::Vec3                       intersectionNormal;
-
-                unsigned int                    p0;
-                unsigned int                    p1;
-                unsigned int                    p2;
-                float                           r0;
-                float                           r1;
-                float                           r2;
-
-                unsigned int                    primitiveIndex;
-            };
-
-
-            typedef std::vector<LineSegmentIntersection> LineSegmentIntersections;
-
-            /** compute the intersection of a line segment and the quadtree, return true if an intersection has been found.*/
-            virtual bool intersect(const osg::Vec3d& start, const osg::Vec3d& end, LineSegmentIntersections& intersections) const;
-
-
-            typedef int value_type;
-
-            struct QuadNode
-            {
-                QuadNode():
-                    first(0),
-                    second(0) {}
-
-                QuadNode(value_type f, value_type s):
-                    first(f),
-                    second(s) {}
-
-                osg::BoundingBox bb;
-
-                value_type first;
-                value_type second;
-            };
-
-            struct Triangle
-            {
-                Triangle():
-                    p0(0),p1(0),p2(0) {}
-
-                Triangle(unsigned int ip0, unsigned int ip1, unsigned int ip2):
-                    p0(ip0), p1(ip1), p2(ip2) {}
-
-                bool operator < (const Triangle& rhs) const
-                {
-                    if (p0<rhs.p0) return true;
-                    if (p0>rhs.p0) return false;
-                    if (p1<rhs.p1) return true;
-                    if (p1>rhs.p1) return false;
-                    return p2<rhs.p2;
-                }
-
-                unsigned int p0;
-                unsigned int p1;
-                unsigned int p2;
-            };
-
-            typedef std::vector< QuadNode >     QuadNodeList;
-            typedef std::vector< Triangle >     TriangleList;
-
-            int addNode(const QuadNode& node)
-            {
-                int num = static_cast<int>(_quadNodes.size());
-                _quadNodes.push_back(node);
-                return num;
-            }
-
-            QuadNode& getNode(int nodeNum) { return _quadNodes[nodeNum]; }
-            const QuadNode& getNode(int nodeNum) const { return _quadNodes[nodeNum]; }
-
-            QuadNodeList& getNodes() { return _quadNodes; }
-            const QuadNodeList& getNodes() const { return _quadNodes; }
-
-            void setVertices(osg::Vec3Array* vertices) { _vertices = vertices; }
-            const osg::Vec3Array* getVertices() const { return _vertices.get(); }
-
-            unsigned int addTriangle(const Triangle& tri)
-            {
-                unsigned int num = static_cast<unsigned int>(_triangles.size());
-                _triangles.push_back(tri);
-                return num;
-            }
-
-            Triangle& getTriangle(unsigned int i) { return _triangles[i]; }
-            const Triangle& getTriangle(unsigned int i) const { return _triangles[i]; }
-
-            TriangleList& getTriangles() { return _triangles; }
-            const TriangleList& getTriangles() const { return _triangles; }
-
-
-        protected:
-
-            osg::ref_ptr<osg::Vec3Array>        _vertices;
-            QuadNodeList                        _quadNodes;
-            TriangleList                        _triangles;
-
-    };
-
-    class QuadTreeBuilder : public osg::NodeVisitor
-    {
-        public:
-
-            QuadTreeBuilder();
-
-            QuadTreeBuilder(const QuadTreeBuilder& rhs);
-
-            META_NodeVisitor(osg, QuadTreeBuilder)
-
-            virtual QuadTreeBuilder* clone() { return new QuadTreeBuilder(*this); }
-
-            void apply(osg::Geode& geode);
-
-            QuadTree::BuildOptions _buildOptions;
-
-            osg::ref_ptr<QuadTree> _quadTreePrototype;
-        protected:
-
-            virtual ~QuadTreeBuilder() {}
-    };
-
-} // namespace osgEarth
-
-#endif // OSGEARTH_QUAD_TREE
diff --git a/src/osgEarth/QuadTree.cpp b/src/osgEarth/QuadTree.cpp
deleted file mode 100644
index 6b3addf..0000000
--- a/src/osgEarth/QuadTree.cpp
+++ /dev/null
@@ -1,825 +0,0 @@
-/* -*-c++-*- */
-/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
-* http://osgearth.org
-*
-* osgEarth is free software; you can redistribute it and/or modify
-* it under the terms of the GNU Lesser General Public License as published by
-* the Free Software Foundation; either version 2 of the License, or
-* (at your option) any later version.
-*
-* This program is distributed in the hope that it will be useful,
-* but WITHOUT ANY WARRANTY; without even the implied warranty of
-* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-* GNU Lesser General Public License for more details.
-*
-* You should have received a copy of the GNU Lesser General Public License
-* along with this program.  If not, see <http://www.gnu.org/licenses/>
-*/
-#undef NDEBUG
-#include <cassert>
-#include "QuadTree"
-#include <osg/Geode>
-#include <osg/TriangleIndexFunctor>
-#include <osg/Timer>
-
-#include <osg/io_utils>
-
-using namespace osgEarth;
-
-//#define VERBOSE_OUTPUT
-
-
-////////////////////////////////////////////////////////////////////////////////
-//
-// BuildQuadTree Declarartion - class used for building an single QuadTree
-
-struct BuildQuadTree
-{
-    BuildQuadTree(QuadTree& quadTree):
-        _quadTree(quadTree) {}
-
-    typedef std::vector< osg::Vec3 >            CenterList;
-    typedef std::vector< unsigned int >         Indices;
-    typedef std::vector< unsigned int >         AxisStack;
-
-    bool build(QuadTree::BuildOptions& options, osg::Geometry* geometry);
-
-    void computeDivisions(QuadTree::BuildOptions& options);
-
-    int divide(QuadTree::BuildOptions& options, osg::BoundingBox& bb, int nodeIndex, unsigned int level);
-
-    QuadTree&           _quadTree;
-
-    osg::BoundingBox    _bb;
-    AxisStack           _axisStack;
-    Indices             _primitiveIndices;
-    CenterList          _centers;
-
-protected:
-
-    BuildQuadTree& operator = (const BuildQuadTree&) { return *this; }
-};
-
-////////////////////////////////////////////////////////////////////////////////
-//
-// Functor for collecting triangle indices from Geometry
-struct TriangleIndicesCollector
-{
-    TriangleIndicesCollector():
-        _buildQuadTree(0)
-    {
-    }
-
-    inline void operator () (unsigned int p0, unsigned int p1, unsigned int p2)
-    {
-        const osg::Vec3& v0 = (*(_buildQuadTree->_quadTree.getVertices()))[p0];
-        const osg::Vec3& v1 = (*(_buildQuadTree->_quadTree.getVertices()))[p1];
-        const osg::Vec3& v2 = (*(_buildQuadTree->_quadTree.getVertices()))[p2];
-
-        unsigned int i = _buildQuadTree->_quadTree.addTriangle(QuadTree::Triangle(p0,p1,p2));
-
-        osg::BoundingBox bb(v0,v0);
-        bb.expandBy(v1);
-        bb.expandBy(v2);
-
-        _buildQuadTree->_centers.push_back(bb.center());
-        _buildQuadTree->_primitiveIndices.push_back(i);
-
-    }
-
-    BuildQuadTree* _buildQuadTree;
-};
-
-
-////////////////////////////////////////////////////////////////////////////////
-//
-// BuildQuadTree Implementation
-
-bool BuildQuadTree::build(QuadTree::BuildOptions& options, osg::Geometry* geometry)
-{
-
-#ifdef VERBOSE_OUTPUT
-    OSG_NOTICE<<"QuadTreeBuilder::createQuadTree()"<<std::endl;
-#endif
-
-    osg::Vec3Array* vertices = dynamic_cast<osg::Vec3Array*>(geometry->getVertexArray());
-    if (!vertices) return false;
-
-    if (vertices->size() <= options._targetNumTrianglesPerLeaf) return false;
-    
-#if OSG_VERSION_GREATER_OR_EQUAL(3,3,2)
-    _bb = geometry->getBoundingBox();
-#else
-    _bb = geometry->getBound();
-#endif
-
-    _quadTree.setVertices(vertices);
-
-    unsigned int estimatedSize = (unsigned int)(2.0*float(vertices->size())/float(options._targetNumTrianglesPerLeaf));
-
-#ifdef VERBOSE_OUTPUT
-    OSG_NOTICE<<"quadTree->_quadNodes.reserve()="<<estimatedSize<<std::endl<<std::endl;
-#endif
-
-    _quadTree.getNodes().reserve(estimatedSize*5);
-
-    computeDivisions(options);
-
-    options._numVerticesProcessed += vertices->size();
-
-    _primitiveIndices.reserve(options._numTriangles);
-    _centers.reserve(options._numTriangles);
-
-    _quadTree.getTriangles().reserve(options._numTriangles);
-
-
-    osg::TriangleIndexFunctor<TriangleIndicesCollector> collectTriangleIndices;
-    collectTriangleIndices._buildQuadTree = this;
-    geometry->accept(collectTriangleIndices);
-
-    _primitiveIndices.reserve(vertices->size());
-
-    QuadTree::QuadNode node(-1, _primitiveIndices.size());
-    node.bb = _bb;
-
-    int nodeNum = _quadTree.addNode(node);
-
-    osg::BoundingBox bb = _bb;
-    nodeNum = divide(options, bb, nodeNum, 0);
-
-    // now reorder the triangle list so that it's in order as per the primitiveIndex list.
-    QuadTree::TriangleList triangleList(_quadTree.getTriangles().size());
-    for(unsigned int i=0; i<_primitiveIndices.size(); ++i)
-    {
-        triangleList[i] = _quadTree.getTriangle(_primitiveIndices[i]);
-    }
-
-    _quadTree.getTriangles().swap(triangleList);
-
-
-#ifdef VERBOSE_OUTPUT
-    OSG_NOTICE<<"Root nodeNum="<<nodeNum<<std::endl;
-#endif
-
-
-//    OSG_NOTICE<<"_quadNodes.size()="<<_quadNodes.size()<<"  estimated size = "<<estimatedSize<<std::endl;
-//    OSG_NOTICE<<"_quadLeaves.size()="<<_quadLeaves.size()<<"  estimated size = "<<estimatedSize<<std::endl<<std::endl;
-
-
-    return !_quadTree.getNodes().empty();
-}
-
-void BuildQuadTree::computeDivisions(QuadTree::BuildOptions& options)
-{
-    osg::Vec3 dimensions(_bb.xMax()-_bb.xMin(),
-                         _bb.yMax()-_bb.yMin(),
-                         _bb.zMax()-_bb.zMin());
-
-#ifdef VERBOSE_OUTPUT
-    OSG_NOTICE<<"computeDivisions("<<options._maxNumLevels<<") "<<dimensions<< " { "<<std::endl;
-#endif
-
-    _axisStack.reserve(options._maxNumLevels);
-
-    for(unsigned int level=0; level<options._maxNumLevels; ++level)
-    {
-        int axis = (dimensions[0]>=dimensions[1])? 0 : 1;
-
-        _axisStack.push_back(axis);
-        dimensions[axis] *= 0.5f;
-
-#ifdef VERBOSE_OUTPUT
-        OSG_NOTICE<<"  "<<level<<", "<<dimensions<<", "<<axis<<std::endl;
-#endif
-    }
-
-#ifdef VERBOSE_OUTPUT
-    OSG_NOTICE<<"}"<<std::endl;
-#endif
-}
-
-int BuildQuadTree::divide(QuadTree::BuildOptions& options, osg::BoundingBox& bb, int nodeIndex, unsigned int level)
-{
-    QuadTree::QuadNode& node = _quadTree.getNode(nodeIndex);
-
-    bool needToDivide = level < _axisStack.size() &&
-                        (node.first<0 && static_cast<unsigned int>(node.second)>options._targetNumTrianglesPerLeaf);
-
-    if (!needToDivide)
-    {
-        if (node.first<0)
-        {
-            int istart = -node.first-1;
-            int iend = istart+node.second-1;
-
-            // leaf is done, now compute bound on it.
-            node.bb.init();
-            for(int i=istart; i<=iend; ++i)
-            {
-                const QuadTree::Triangle& tri = _quadTree.getTriangle(_primitiveIndices[i]);
-                const osg::Vec3& v0 = (*_quadTree.getVertices())[tri.p0];
-                const osg::Vec3& v1 = (*_quadTree.getVertices())[tri.p1];
-                const osg::Vec3& v2 = (*_quadTree.getVertices())[tri.p2];
-                node.bb.expandBy(v0);
-                node.bb.expandBy(v1);
-                node.bb.expandBy(v2);
-
-            }
-
-            if (node.bb.valid())
-            {
-                float epsilon = 1e-6f;
-                node.bb._min.x() -= epsilon;
-                node.bb._min.y() -= epsilon;
-                node.bb._min.z() -= epsilon;
-                node.bb._max.x() += epsilon;
-                node.bb._max.y() += epsilon;
-                node.bb._max.z() += epsilon;
-            }
-
-#ifdef VERBOSE_OUTPUT
-            if (!node.bb.valid())
-            {
-                OSG_NOTICE<<"After reset "<<node.first<<","<<node.second<<std::endl;
-                OSG_NOTICE<<"  bb._min ("<<node.bb._min<<")"<<std::endl;
-                OSG_NOTICE<<"  bb._max ("<<node.bb._max<<")"<<std::endl;
-            }
-            else
-            {
-                OSG_NOTICE<<"Set bb for nodeIndex = "<<nodeIndex<<std::endl;
-            }
-#endif
-        }
-
-        return nodeIndex;
-
-    }
-
-    int axis = _axisStack[level];
-
-#ifdef VERBOSE_OUTPUT
-    OSG_NOTICE<<"divide("<<nodeIndex<<", "<<level<< "), axis="<<axis<<std::endl;
-#endif
-
-    if (node.first<0)
-    {
-        // leaf node as first <= 0, so look at dividing it.
-
-        int istart = -node.first-1;
-        int iend = istart+node.second-1;
-
-        //OSG_NOTICE<<"  divide leaf"<<std::endl;
-
-        float original_min = bb._min[axis];
-        float original_max = bb._max[axis];
-
-        float mid = (original_min+original_max)*0.5f;
-
-        int originalLeftChildIndex = 0;
-        int originalRightChildIndex = 0;
-        bool insitueDivision = false;
-
-        {
-            //osg::Vec3Array* vertices = quadTree._vertices.get();
-            int left = istart;
-            int right = iend;
-
-            while(left<right)
-            {
-                while(left<right && (_centers[_primitiveIndices[left]][axis]<=mid)) { ++left; }
-
-                while(left<right && (_centers[_primitiveIndices[right]][axis]>mid)) { --right; }
-
-                while(left<right && (_centers[_primitiveIndices[right]][axis]>mid)) { --right; }
-
-                if (left<right)
-                {
-                    std::swap(_primitiveIndices[left], _primitiveIndices[right]);
-                    ++left;
-                    --right;
-                }
-            }
-
-            if (left==right)
-            {
-                if (_centers[_primitiveIndices[left]][axis]<=mid) ++left;
-                else --right;
-            }
-
-            QuadTree::QuadNode leftLeaf(-istart-1, (right-istart)+1);
-            QuadTree::QuadNode rightLeaf(-left-1, (iend-left)+1);
-
-#if 0
-            OSG_NOTICE<<"In  node.first     ="<<node.first     <<" node.second     ="<<node.second<<std::endl;
-            OSG_NOTICE<<"    leftLeaf.first ="<<leftLeaf.first <<" leftLeaf.second ="<<leftLeaf.second<<std::endl;
-            OSG_NOTICE<<"    rightLeaf.first="<<rightLeaf.first<<" rightLeaf.second="<<rightLeaf.second<<std::endl;
-            OSG_NOTICE<<"    left="<<left<<" right="<<right<<std::endl;
-
-            if (node.second != (leftLeaf.second +rightLeaf.second))
-            {
-                OSG_NOTICE<<"*** Error in size, leaf.second="<<node.second
-                                        <<", leftLeaf.second="<<leftLeaf.second
-                                        <<", rightLeaf.second="<<rightLeaf.second<<std::endl;
-            }
-            else
-            {
-                OSG_NOTICE<<"Size OK, leaf.second="<<node.second
-                                        <<", leftLeaf.second="<<leftLeaf.second
-                                        <<", rightLeaf.second="<<rightLeaf.second<<std::endl;
-            }
-#endif
-
-            if (leftLeaf.second<=0)
-            {
-                //OSG_NOTICE<<"LeftLeaf empty"<<std::endl;
-                originalLeftChildIndex = 0;
-                //originalRightChildIndex = addNode(rightLeaf);
-                originalRightChildIndex = nodeIndex;
-                insitueDivision = true;
-            }
-            else if (rightLeaf.second<=0)
-            {
-                //OSG_NOTICE<<"RightLeaf empty"<<std::endl;
-                // originalLeftChildIndex = addNode(leftLeaf);
-                originalLeftChildIndex = nodeIndex;
-                originalRightChildIndex = 0;
-                insitueDivision = true;
-            }
-            else
-            {
-                originalLeftChildIndex = _quadTree.addNode(leftLeaf);
-                originalRightChildIndex = _quadTree.addNode(rightLeaf);
-            }
-        }
-
-
-        float restore = bb._max[axis];
-        bb._max[axis] = mid;
-
-        //OSG_NOTICE<<"  divide leftLeaf "<<quadTree.getNode(nodeNum).first<<std::endl;
-        int leftChildIndex = originalLeftChildIndex!=0 ? divide(options, bb, originalLeftChildIndex, level+1) : 0;
-
-        bb._max[axis] = restore;
-
-        restore = bb._min[axis];
-        bb._min[axis] = mid;
-
-        //OSG_NOTICE<<"  divide rightLeaf "<<quadTree.getNode(nodeNum).second<<std::endl;
-        int rightChildIndex = originalRightChildIndex!=0 ? divide(options, bb, originalRightChildIndex, level+1) : 0;
-
-        bb._min[axis] = restore;
-
-
-        if (!insitueDivision)
-        {
-            // take a second reference to node we are working on as the std::vector<> resize could
-            // have invalidate the previous node ref.
-            QuadTree::QuadNode& newNodeRef = _quadTree.getNode(nodeIndex);
-
-            newNodeRef.first = leftChildIndex;
-            newNodeRef.second = rightChildIndex;
-
-            insitueDivision = true;
-
-            newNodeRef.bb.init();
-            if (leftChildIndex!=0) newNodeRef.bb.expandBy(_quadTree.getNode(leftChildIndex).bb);
-            if (rightChildIndex!=0) newNodeRef.bb.expandBy(_quadTree.getNode(rightChildIndex).bb);
-
-            if (!newNodeRef.bb.valid())
-            {
-                OSG_NOTICE<<"leftChildIndex="<<leftChildIndex<<" && originalLeftChildIndex="<<originalLeftChildIndex<<std::endl;
-                OSG_NOTICE<<"rightChildIndex="<<rightChildIndex<<" && originalRightChildIndex="<<originalRightChildIndex<<std::endl;
-
-                OSG_NOTICE<<"Invalid BB leftChildIndex="<<leftChildIndex<<", "<<rightChildIndex<<std::endl;
-                OSG_NOTICE<<"  bb._min ("<<newNodeRef.bb._min<<")"<<std::endl;
-                OSG_NOTICE<<"  bb._max ("<<newNodeRef.bb._max<<")"<<std::endl;
-
-                if (leftChildIndex!=0)
-                {
-                    OSG_NOTICE<<"  getNode(leftChildIndex).bb min = "<<_quadTree.getNode(leftChildIndex).bb._min<<std::endl;
-                    OSG_NOTICE<<"                                 max = "<<_quadTree.getNode(leftChildIndex).bb._max<<std::endl;
-                }
-                if (rightChildIndex!=0)
-                {
-                    OSG_NOTICE<<"  getNode(rightChildIndex).bb min = "<<_quadTree.getNode(rightChildIndex).bb._min<<std::endl;
-                    OSG_NOTICE<<"                              max = "<<_quadTree.getNode(rightChildIndex).bb._max<<std::endl;
-                }
-            }
-        }
-    }
-    else
-    {
-        OSG_NOTICE<<"NOT expecting to get here"<<std::endl;
-    }
-
-    return nodeIndex;
-
-}
-
-////////////////////////////////////////////////////////////////////////////////
-//
-// IntersectQuadTree
-//
-struct IntersectQuadTree
-{
-    IntersectQuadTree(const osg::Vec3Array& vertices,
-                    const QuadTree::QuadNodeList& nodes,
-                    const QuadTree::TriangleList& triangles,
-                    QuadTree::LineSegmentIntersections& intersections,
-                    const osg::Vec3d& s, const osg::Vec3d& e):
-                        _vertices(vertices),
-                        _quadNodes(nodes),
-                        _triangles(triangles),
-                        _intersections(intersections),
-                        _s(s),
-                        _e(e)
-    {
-        _d = e - s;
-        _length = _d.length();
-        _inverse_length = _length!=0.0f ? 1.0f/_length : 0.0;
-        _d *= _inverse_length;
-
-        _d_invX = _d.x()!=0.0f ? _d/_d.x() : osg::Vec3(0.0f,0.0f,0.0f);
-        _d_invY = _d.y()!=0.0f ? _d/_d.y() : osg::Vec3(0.0f,0.0f,0.0f);
-        _d_invZ = _d.z()!=0.0f ? _d/_d.z() : osg::Vec3(0.0f,0.0f,0.0f);
-    }
-
-    void intersect(const QuadTree::QuadNode& node, const osg::Vec3& s, const osg::Vec3& e) const;
-    bool intersectAndClip(osg::Vec3& s, osg::Vec3& e, const osg::BoundingBox& bb) const;
-
-    const osg::Vec3Array&               _vertices;
-    const QuadTree::QuadNodeList&           _quadNodes;
-    const QuadTree::TriangleList&         _triangles;
-    QuadTree::LineSegmentIntersections&   _intersections;
-
-    osg::Vec3 _s;
-    osg::Vec3 _e;
-
-    osg::Vec3 _d;
-    float     _length;
-    float     _inverse_length;
-
-    osg::Vec3 _d_invX;
-    osg::Vec3 _d_invY;
-    osg::Vec3 _d_invZ;
-
-
-protected:
-
-    IntersectQuadTree& operator = (const IntersectQuadTree&) { return *this; }
-};
-
-
-void IntersectQuadTree::intersect(const QuadTree::QuadNode& node, const osg::Vec3& ls, const osg::Vec3& le) const
-{
-    static const float esplison = 1e-10f;
-    if (node.first<0)
-    {
-        // treat as a leaf
-
-        //OSG_NOTICE<<"QuadTree::intersect("<<&leaf<<")"<<std::endl;
-        int istart = -node.first-1;
-        int iend = istart + node.second;
-
-        for(int i=istart; i<iend; ++i)
-        {
-            //const Triangle& tri = _triangles[_primitiveIndices[i]];
-            const QuadTree::Triangle& tri = _triangles[i];
-            // OSG_NOTICE<<"   tri("<<tri.p1<<","<<tri.p2<<","<<tri.p3<<")"<<std::endl;
-
-            const osg::Vec3& v0 = _vertices[tri.p0];
-            const osg::Vec3& v1 = _vertices[tri.p1];
-            const osg::Vec3& v2 = _vertices[tri.p2];
-
-            osg::Vec3 T = _s - v0;
-            osg::Vec3 E2 = v2 - v0;
-            osg::Vec3 E1 = v1 - v0;
-
-            osg::Vec3 P =  _d ^ E2;
-
-            float det = P * E1;
-
-            float r,r0,r1,r2;
-
-            if (det>esplison)
-            {
-                float u = (P*T);
-                if (u<0.0 || u>det) continue;
-
-                osg::Vec3 Q = T ^ E1;
-                float v = (Q*_d);
-                if (v<0.0 || v>det) continue;
-
-                if ((u+v)> det) continue;
-
-                float inv_det = 1.0f/det;
-                float t = (Q*E2)*inv_det;
-                if (t<0.0 || t>_length) continue;
-
-                u *= inv_det;
-                v *= inv_det;
-
-                r0 = 1.0f-u-v;
-                r1 = u;
-                r2 = v;
-                r = t * _inverse_length;
-            }
-            else if (det<-esplison)
-            {
-
-                float u = (P*T);
-                if (u>0.0 || u<det) continue;
-
-                osg::Vec3 Q = T ^ E1;
-                float v = (Q*_d);
-                if (v>0.0 || v<det) continue;
-
-                if ((u+v) < det) continue;
-
-                float inv_det = 1.0f/det;
-                float t = (Q*E2)*inv_det;
-                if (t<0.0 || t>_length) continue;
-
-                u *= inv_det;
-                v *= inv_det;
-
-                r0 = 1.0f-u-v;
-                r1 = u;
-                r2 = v;
-                r = t * _inverse_length;
-            }
-            else
-            {
-                continue;
-            }
-
-            osg::Vec3 in = v0*r0 + v1*r1 + v2*r2;
-            osg::Vec3 normal = E1^E2;
-            normal.normalize();
-
-#if 1
-            _intersections.push_back(QuadTree::LineSegmentIntersection());
-            QuadTree::LineSegmentIntersection& intersection = _intersections.back();
-
-            intersection.ratio = r;
-            intersection.primitiveIndex = i;
-            intersection.intersectionPoint = in;
-            intersection.intersectionNormal = normal;
-
-            intersection.p0 = tri.p0;
-            intersection.p1 = tri.p1;
-            intersection.p2 = tri.p2;
-            intersection.r0 = r0;
-            intersection.r1 = r1;
-            intersection.r2 = r2;
-
-#endif
-            // OSG_NOTICE<<"  got intersection ("<<in<<") ratio="<<r<<std::endl;
-        }
-    }
-    else
-    {
-        if (node.first>0)
-        {
-            osg::Vec3 l(ls), e(le);
-            if (intersectAndClip(l,e, _quadNodes[node.first].bb))
-            {
-                intersect(_quadNodes[node.first], l, e);
-            }
-        }
-        if (node.second>0)
-        {
-            osg::Vec3 l(ls), e(le);
-            if (intersectAndClip(l,e, _quadNodes[node.second].bb))
-            {
-                intersect(_quadNodes[node.second], l, e);
-            }
-        }
-    }
-}
-
-bool IntersectQuadTree::intersectAndClip(osg::Vec3& s, osg::Vec3& e, const osg::BoundingBox& bb) const
-{
-    //return true;
-
-    //if (!bb.valid()) return true;
-
-    // compate s and e against the xMin to xMax range of bb.
-    if (s.x()<=e.x())
-    {
-
-        // trivial reject of segment wholely outside.
-        if (e.x()<bb.xMin()) return false;
-        if (s.x()>bb.xMax()) return false;
-
-        if (s.x()<bb.xMin())
-        {
-            // clip s to xMin.
-            s = s+_d_invX*(bb.xMin()-s.x());
-        }
-
-        if (e.x()>bb.xMax())
-        {
-            // clip e to xMax.
-            e = s+_d_invX*(bb.xMax()-s.x());
-        }
-    }
-    else
-    {
-        if (s.x()<bb.xMin()) return false;
-        if (e.x()>bb.xMax()) return false;
-
-        if (e.x()<bb.xMin())
-        {
-            // clip s to xMin.
-            e = s+_d_invX*(bb.xMin()-s.x());
-        }
-
-        if (s.x()>bb.xMax())
-        {
-            // clip e to xMax.
-            s = s+_d_invX*(bb.xMax()-s.x());
-        }
-    }
-
-    // compate s and e against the yMin to yMax range of bb.
-    if (s.y()<=e.y())
-    {
-
-        // trivial reject of segment wholely outside.
-        if (e.y()<bb.yMin()) return false;
-        if (s.y()>bb.yMax()) return false;
-
-        if (s.y()<bb.yMin())
-        {
-            // clip s to yMin.
-            s = s+_d_invY*(bb.yMin()-s.y());
-        }
-
-        if (e.y()>bb.yMax())
-        {
-            // clip e to yMax.
-            e = s+_d_invY*(bb.yMax()-s.y());
-        }
-    }
-    else
-    {
-        if (s.y()<bb.yMin()) return false;
-        if (e.y()>bb.yMax()) return false;
-
-        if (e.y()<bb.yMin())
-        {
-            // clip s to yMin.
-            e = s+_d_invY*(bb.yMin()-s.y());
-        }
-
-        if (s.y()>bb.yMax())
-        {
-            // clip e to yMax.
-            s = s+_d_invY*(bb.yMax()-s.y());
-        }
-    }
-
-    // compate s and e against the zMin to zMax range of bb.
-    if (s.z()<=e.z())
-    {
-
-        // trivial reject of segment wholely outside.
-        if (e.z()<bb.zMin()) return false;
-        if (s.z()>bb.zMax()) return false;
-
-        if (s.z()<bb.zMin())
-        {
-            // clip s to zMin.
-            s = s+_d_invZ*(bb.zMin()-s.z());
-        }
-
-        if (e.z()>bb.zMax())
-        {
-            // clip e to zMax.
-            e = s+_d_invZ*(bb.zMax()-s.z());
-        }
-    }
-    else
-    {
-        if (s.z()<bb.zMin()) return false;
-        if (e.z()>bb.zMax()) return false;
-
-        if (e.z()<bb.zMin())
-        {
-            // clip s to zMin.
-            e = s+_d_invZ*(bb.zMin()-s.z());
-        }
-
-        if (s.z()>bb.zMax())
-        {
-            // clip e to zMax.
-            s = s+_d_invZ*(bb.zMax()-s.z());
-        }
-    }
-
-    // OSG_NOTICE<<"clampped segment "<<s<<" "<<e<<std::endl;
-
-    // if (s==e) return false;
-
-    return true;
-}
-
-
-////////////////////////////////////////////////////////////////////////////////
-//
-// QuadTree::BuildOptions
-
-QuadTree::BuildOptions::BuildOptions():
-_numVerticesProcessed(0),
-_targetNumTrianglesPerLeaf(4),
-_maxNumLevels(32),
-_numTriangles(0)
-{
-    //nop
-}
-
-////////////////////////////////////////////////////////////////////////////////
-//
-// QuadTree
-
-QuadTree::QuadTree()
-{
-}
-
-QuadTree::QuadTree(const QuadTree& rhs, const osg::CopyOp& copyop):
-    Shape(rhs, copyop),
-    _vertices(rhs._vertices),
-    _quadNodes(rhs._quadNodes),
-    _triangles(rhs._triangles)
-{
-}
-
-bool QuadTree::build(BuildOptions& options, osg::Geometry* geometry)
-{
-    BuildQuadTree build(*this);
-    return build.build(options, geometry);
-}
-
-bool QuadTree::intersect(const osg::Vec3d& start, const osg::Vec3d& end, LineSegmentIntersections& intersections) const
-{
-    if (_quadNodes.empty())
-    {
-        OSG_NOTICE<<"Warning: _quadTree is empty"<<std::endl;
-        return false;
-    }
-
-    unsigned int numIntersectionsBefore = intersections.size();
-
-    IntersectQuadTree intersector(*_vertices,
-                                _quadNodes,
-                                _triangles,
-                                intersections,
-                                start, end);
-
-    intersector.intersect(getNode(0), start, end);
-
-    return numIntersectionsBefore != intersections.size();
-}
-
-////////////////////////////////////////////////////////////////////////////////
-//
-// QuadTreeBuilder
-QuadTreeBuilder::QuadTreeBuilder():
-    osg::NodeVisitor()
-{
-    setTraversalMode( TRAVERSE_ALL_CHILDREN );
-    this->setNodeMaskOverride( ~0 );
-
-    _quadTreePrototype = new QuadTree;
-}
-
-QuadTreeBuilder::QuadTreeBuilder(const QuadTreeBuilder& rhs) :
-    osg::NodeVisitor( rhs ),
-    _buildOptions(rhs._buildOptions),
-    _quadTreePrototype(rhs._quadTreePrototype)
-{
-}
-
-void QuadTreeBuilder::apply(osg::Geode& geode)
-{
-    for(unsigned int i=0; i<geode.getNumDrawables(); ++i)
-    {
-
-        osg::Geometry* geom = geode.getDrawable(i)->asGeometry();
-        if (geom)
-        {
-            QuadTree* previous = dynamic_cast<QuadTree*>(geom->getShape());
-            if (previous) continue;
-
-            osg::ref_ptr<osg::Object> obj = _quadTreePrototype->cloneType();
-            osg::ref_ptr<QuadTree> quadTree = dynamic_cast<QuadTree*>(obj.get());
-
-            if (quadTree.valid() && quadTree->build(_buildOptions, geom))
-            {
-                geom->setShape(quadTree.get());
-            }
-        }
-    }
-}
diff --git a/src/osgEarth/Registry b/src/osgEarth/Registry
index a271ece..1dd7ec0 100644
--- a/src/osgEarth/Registry
+++ b/src/osgEarth/Registry
@@ -30,17 +30,18 @@
 #include <OpenThreads/ScopedLock>
 #include <osgEarth/ThreadingUtils>
 #include <osg/Referenced>
+#include <osg/OperationThread>
 #include <set>
 
 #define GDAL_SCOPED_LOCK \
-    OpenThreads::ScopedLock<OpenThreads::ReentrantMutex> _slock( osgEarth::Registry::instance()->getGDALMutex() )\
+    OpenThreads::ScopedLock<OpenThreads::ReentrantMutex> _slock( osgEarth::getGDALMutex() )\
 
 namespace osgText {
     class Font;
 }
 
 namespace osgEarth
-{    
+{
     class Cache;
     class Capabilities;
     class Profile;
@@ -51,9 +52,12 @@ namespace osgEarth
     class StateSetCache;
     class ObjectIndex;
     class Units;
-    
+
     typedef SharedSARepo<osg::Program> ProgramSharedRepo;
 
+
+    extern OSGEARTH_EXPORT OpenThreads::ReentrantMutex& getGDALMutex();
+
     /**
      * Application-wide global repository.
      */
@@ -63,6 +67,8 @@ namespace osgEarth
         /** Access the global Registry singleton. */
         static Registry* instance(bool erase = false);
 
+
+
         /** Gets a well-known named profile instance. */
         const Profile* getNamedProfile( const std::string& name ) const;
 
@@ -147,9 +153,9 @@ namespace osgEarth
          * shader factory if you want to alter any of osgEarth's baseline shaders
          * (advanced usage).
          */
-        const ShaderFactory* getShaderFactory() const;
+        ShaderFactory* getShaderFactory() const;
         void setShaderFactory( ShaderFactory* lib );
-        static const ShaderFactory* shaderFactory() { return instance()->getShaderFactory(); }
+        static ShaderFactory* shaderFactory() { return instance()->getShaderFactory(); }
 
         /**
          * The default shader generator.
@@ -175,12 +181,12 @@ namespace osgEarth
         static StateSetCache* stateSetCache() { return instance()->getStateSetCache(); }
 
         /**
-         * A shared cache for osg::Program objects created by the shader 
+         * A shared cache for osg::Program objects created by the shader
          * composition subsystem (VirtualProgram).
          */
         ProgramSharedRepo* getProgramSharedRepo();
         static ProgramSharedRepo* programSharedRepo() { return instance()->getProgramSharedRepo(); }
-        
+
         /**
          * Gets a reference to the global task service manager.
          */
@@ -223,10 +229,17 @@ namespace osgEarth
         /**
          * The name of the default terrain engine driver
          */
-        void setDefaultTerrainEngineDriverName( const std::string& name );
+        //void setDefaultTerrainEngineDriverName( const std::string& name );
         const std::string& getDefaultTerrainEngineDriverName() const { return _terrainEngineDriver; }
 
         /**
+         * If set, all MapNodes will use the terrain driver specified here regardless
+         * of the driver in the TerrainOptions/earth file.
+         */
+        optional<std::string>& overrideTerrainEngineDriverName() { return _overrideTerrainEngineDriverName; }
+        const optional<std::string>& overrideTerrainEngineDriverName() const { return _overrideTerrainEngineDriverName; }
+
+        /**
          * For debugging - tracks activities in progress.
          */
         void startActivity(const std::string& name);
@@ -261,13 +274,28 @@ namespace osgEarth
         TransientUserDataStore& dataStore() { return _dataStore; }
         const TransientUserDataStore& dataStore() const { return _dataStore; }
 
+        //! Access to the general-purpose async operations queue
+        osg::OperationQueue* getAsyncOperationQueue() const { return _opQueue.get(); }
+
+
+        /**
+         * Gets the device pixel ratio.
+         */
+        float getDevicePixelRatio() const;
+
+        /**
+        * Sets the device pixel ratio.  This value will be used to scale the size of objects specified in pixels.
+        * This value is useful when running in high dpi environments on high resolution displays.
+        */
+        void setDevicePixelRatio(float devicePixelRatio);
+
+
     protected:
         virtual ~Registry();
         Registry();
 
         void destruct();
 
-        OpenThreads::ReentrantMutex _gdal_mutex;
         bool _gdal_registered;
 
         osg::ref_ptr<const Profile> _global_geodetic_profile;
@@ -275,7 +303,7 @@ namespace osgEarth
         osg::ref_ptr<const Profile> _spherical_mercator_profile;
         osg::ref_ptr<const Profile> _cube_profile;
 
-        mutable Threading::Mutex _regMutex;  
+        mutable Threading::Mutex _regMutex;
         int _numGdalMutexGets;
 
         mutable osg::ref_ptr<Cache>   _defaultCache;
@@ -314,6 +342,7 @@ namespace osgEarth
         osg::ref_ptr<StateSetCache> _stateSetCache;
 
         std::string _terrainEngineDriver;
+        optional<std::string> _overrideTerrainEngineDriverName;
 
         mutable optional<std::string> _cacheDriver;
 
@@ -325,7 +354,7 @@ namespace osgEarth
         };
         std::set<Activity,ActivityLess> _activities;
         mutable Threading::Mutex _activityMutex;
-        
+
         ProgramSharedRepo _programRepo;
 
         optional<bool> _unRefImageDataAfterApply;
@@ -335,6 +364,14 @@ namespace osgEarth
         std::set<int> _offLimitsTextureImageUnits;
 
         TransientUserDataStore _dataStore;
+
+        osg::ref_ptr<osg::OperationQueue> _opQueue;
+
+        typedef std::vector< osg::ref_ptr<osg::OperationThread> > ThreadPool;
+        ThreadPool _opThreadPool;
+        unsigned _threadPoolSize;
+
+        float _devicePixelRatio;
     };
 }
 
@@ -346,8 +383,14 @@ struct osgEarthRegisterUnits {
     }
 };
 #define OSGEARTH_REGISTER_UNITS(NAME,INSTANCE) \
+    extern "C" void osgearth_units_##NAME(void) {} \
     static osgEarthRegisterUnits s_osgEarthRegistryUnitsProxy##NAME (INSTANCE)
 
+    
+#define USE_OSGEARTH_UNITS(NAME ) \
+    extern "C" void osgearth_units_##NAME(void); \
+    static osgDB::PluginFunctionProxy proxy_osgearth_units_##NAME(osgearth_units_##NAME);
+
 
 
 #endif //OSGEARTH_REGISTRY
diff --git a/src/osgEarth/Registry.cpp b/src/osgEarth/Registry.cpp
index 32a9439..97e8c8d 100644
--- a/src/osgEarth/Registry.cpp
+++ b/src/osgEarth/Registry.cpp
@@ -40,6 +40,7 @@
 
 #include <gdal_priv.h>
 #include <ogr_api.h>
+#include <cpl_error.h>
 #include <stdlib.h>
 #include <locale>
 
@@ -54,6 +55,13 @@ using namespace OpenThreads;
 
 #define LC "[Registry] "
 
+namespace
+{
+    void CPL_STDCALL myCPLErrorHandler(CPLErr errClass, int errNum, const char* msg)
+    {
+        OE_DEBUG << "[GDAL] " << msg << " (error " << errNum << ")" << std::endl;
+    }
+}
 
 Registry::Registry() :
 osg::Referenced     ( true ),
@@ -64,16 +72,21 @@ _caps               ( 0L ),
 _defaultFont        ( 0L ),
 _terrainEngineDriver( "mp" ),
 _cacheDriver        ( "filesystem" ),
-_overrideCachePolicyInitialized( false )
+_overrideCachePolicyInitialized( false ),
+_threadPoolSize(2u),
+_devicePixelRatio(1.0f)
 {
     // set up GDAL and OGR.
     OGRRegisterAll();
     GDALAllRegister();
-    
+
     // support Chinese character in the file name and attributes in ESRI's shapefile
     CPLSetConfigOption("GDAL_FILENAME_IS_UTF8","NO");
     CPLSetConfigOption("SHAPE_ENCODING","");
 
+    // Redirect GDAL/OGR console errors to our own handler
+    CPLPushErrorHandler(myCPLErrorHandler);
+
     // global initialization for CURL (not thread safe)
     HTTPClient::globalInit();
 
@@ -109,11 +122,13 @@ _overrideCachePolicyInitialized( false )
     osgDB::Registry::instance()->addMimeTypeExtensionMapping( "text/x-json",                          "osgb" );
     osgDB::Registry::instance()->addMimeTypeExtensionMapping( "image/jpg",                            "jpg" );
     osgDB::Registry::instance()->addMimeTypeExtensionMapping( "image/dds",                            "dds" );
-    
+    // This is not correct, but some versions of readymap can return tif with one f instead of two.
+    osgDB::Registry::instance()->addMimeTypeExtensionMapping( "image/tif",                            "tif" );
+
     // pre-load OSG's ZIP plugin so that we can use it in URIs
     std::string zipLib = osgDB::Registry::instance()->createLibraryNameForExtension( "zip" );
     if ( !zipLib.empty() )
-        osgDB::Registry::instance()->loadLibrary( zipLib );    
+        osgDB::Registry::instance()->loadLibrary( zipLib );
 
     // set up our default r/w options to NOT cache archives!
     _defaultOptions = new osgDB::Options();
@@ -123,26 +138,34 @@ _overrideCachePolicyInitialized( false )
     if ( teStr )
     {
         _terrainEngineDriver = std::string(teStr);
+        _overrideTerrainEngineDriverName = std::string(teStr);
+        OE_INFO << LC << "Terrain engine set from environment: " << _terrainEngineDriver << std::endl;
     }
 
     // load a default font
     const char* envFont = ::getenv("OSGEARTH_DEFAULT_FONT");
     if ( envFont )
     {
-        _defaultFont = osgText::readFontFile( std::string(envFont) );
+        _defaultFont = osgText::readRefFontFile( std::string(envFont) );
+        OE_INFO << LC << "Default font set from environment: " << envFont << std::endl;
     }
     if ( !_defaultFont.valid() )
     {
 #ifdef WIN32
-        _defaultFont = osgText::readFontFile("arial.ttf");
+        _defaultFont = osgText::readRefFontFile("arial.ttf");
+#else
+        _defaultFont = osgText::Font::getDefaultFont();
 #endif
     }
+
+#if OSG_VERSION_LESS_THAN(3,5,8)
     if ( _defaultFont.valid() )
     {
         // mitigates mipmapping issues that cause rendering artifacts
         // for some fonts/placement
         _defaultFont->setGlyphImageMargin( 2 );
     }
+#endif
 
     // register the system stock Units.
     Units::registerAll( this );
@@ -150,16 +173,17 @@ _overrideCachePolicyInitialized( false )
 
 Registry::~Registry()
 {
-    //nop
+    // pop the custom error handler
+    CPLPopErrorHandler();
 }
 
-Registry* 
+Registry*
 Registry::instance(bool erase)
 {
     static osg::ref_ptr<Registry> s_registry = new Registry;
 
-    if (erase) 
-    {   
+    if (erase)
+    {
         s_registry->destruct();
         s_registry = 0;
     }
@@ -167,22 +191,18 @@ Registry::instance(bool erase)
     return s_registry.get(); // will return NULL on erase
 }
 
-void 
+void
 Registry::destruct()
 {
     //nop
 }
 
-
-OpenThreads::ReentrantMutex&
-Registry::getGDALMutex()
+OpenThreads::ReentrantMutex& osgEarth::getGDALMutex()
 {
-    //_numGdalMutexGets++;
-    //OE_NOTICE << "GDAL = " << _numGdalMutexGets << std::endl;
+    static OpenThreads::ReentrantMutex _gdal_mutex;
     return _gdal_mutex;
 }
 
-
 const Profile*
 Registry::getGlobalGeodeticProfile() const
 {
@@ -314,7 +334,7 @@ Registry::getDefaultCacheDriverName() const
             {
                 _cacheDriver = value;
                 OE_DEBUG << LC << "Cache driver set from environment: " << value << std::endl;
-            }        
+            }
         }
     }
     return _cacheDriver.get();
@@ -356,6 +376,7 @@ Registry::overrideCachePolicy() const
                 {
                     TimeSpan maxAge = osgEarth::as<long>( std::string(cacheMaxAge), INT_MAX );
                     _overrideCachePolicy->maxAge() = maxAge;
+                    OE_INFO << LC << "Cache max age set from environment: " << cacheMaxAge << std::endl;
                 }
             }
 
@@ -460,7 +481,7 @@ Registry::initCapabilities()
         _caps = new Capabilities();
 }
 
-const ShaderFactory*
+ShaderFactory*
 Registry::getShaderFactory() const
 {
     return _shaderLib.get();
@@ -485,17 +506,17 @@ Registry::setShaderGenerator(ShaderGenerator* shaderGen)
     if ( shaderGen != 0L && shaderGen != _shaderGen.get() )
         _shaderGen = shaderGen;
 }
-        
+
 void
-Registry::setURIReadCallback( URIReadCallback* callback ) 
-{ 
+Registry::setURIReadCallback( URIReadCallback* callback )
+{
     _uriReadCallback = callback;
 }
 
 URIReadCallback*
 Registry::getURIReadCallback() const
 {
-    return _uriReadCallback.get(); 
+    return _uriReadCallback.get();
 }
 
 void
@@ -521,7 +542,7 @@ Registry::createUID()
 }
 
 const osgDB::Options*
-Registry::getDefaultOptions() const 
+Registry::getDefaultOptions() const
 {
     return _defaultOptions.get();
 }
@@ -529,8 +550,8 @@ Registry::getDefaultOptions() const
 osgDB::Options*
 Registry::cloneOrCreateOptions(const osgDB::Options* input)
 {
-    osgDB::Options* newOptions = 
-        input ? static_cast<osgDB::Options*>(input->clone(osg::CopyOp::DEEP_COPY_USERDATA)) : 
+    osgDB::Options* newOptions =
+        input ? static_cast<osgDB::Options*>(input->clone(osg::CopyOp::DEEP_COPY_USERDATA)) :
         new osgDB::Options();
 
     // clear the CACHE_ARCHIVES flag because it is evil
@@ -567,11 +588,11 @@ Registry::getUnits(const std::string& name) const
     return 0L;
 }
 
-void
-Registry::setDefaultTerrainEngineDriverName(const std::string& name)
-{
-    _terrainEngineDriver = name;
-}
+//void
+//Registry::setDefaultTerrainEngineDriverName(const std::string& name)
+//{
+//    _terrainEngineDriver = name;
+//}
 
 void
 Registry::setDefaultCacheDriverName(const std::string& name)
@@ -641,9 +662,9 @@ Registry::getActivities(std::set<std::string>& output)
     }
 }
 
-std::string 
+std::string
 Registry::getExtensionForMimeType(const std::string& mt)
-{            
+{
     std::string mt_lower = osgEarth::toLower(mt);
 
     const osgDB::Registry::MimeTypeExtensionMap& exmap = osgDB::Registry::instance()->getMimeTypeExtensionMap();
@@ -657,9 +678,9 @@ Registry::getExtensionForMimeType(const std::string& mt)
     return std::string();
 }
 
-std::string 
+std::string
 Registry::getMimeTypeForExtension(const std::string& ext)
-{            
+{
     std::string ext_lower = osgEarth::toLower(ext);
 
     const osgDB::Registry::MimeTypeExtensionMap& exmap = osgDB::Registry::instance()->getMimeTypeExtensionMap();
@@ -687,6 +708,18 @@ Registry::getOffLimitsTextureImageUnits() const
     return _offLimitsTextureImageUnits;
 }
 
+float
+Registry::getDevicePixelRatio() const
+{
+    return _devicePixelRatio;
+}
+
+void
+Registry::setDevicePixelRatio(float devicePixelRatio)
+{
+    _devicePixelRatio = devicePixelRatio;
+}
+
 
 //Simple class used to add a file extension alias for the earth_tile to the earth plugin
 class RegisterEarthTileExtension
@@ -694,7 +727,10 @@ class RegisterEarthTileExtension
 public:
     RegisterEarthTileExtension()
     {
+#if OSG_VERSION_LESS_THAN(3,5,4)
+        // Method deprecated beyone 3.5.4 since all ref counting is thread-safe by default
         osg::Referenced::setThreadSafeReferenceCounting( true );
+#endif
         osgDB::Registry::instance()->addFileExtensionAlias("earth_tile", "earth");
     }
 };
diff --git a/src/osgEarth/ResourceReleaser.cpp b/src/osgEarth/ResourceReleaser.cpp
index d7b0cf5..7b7b7bc 100644
--- a/src/osgEarth/ResourceReleaser.cpp
+++ b/src/osgEarth/ResourceReleaser.cpp
@@ -17,6 +17,7 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>
  */
 #include <osgEarth/ResourceReleaser>
+#include <osgEarth/Metrics>
 #include <osg/Version>
 
 using namespace osgEarth;
@@ -26,10 +27,8 @@ using namespace osgEarth;
 
 ResourceReleaser::ResourceReleaser()
 {
-#if OSG_VERSION_GREATER_OR_EQUAL(3,4,0)
     // ensure this node always gets traversed:
     this->setCullingActive(false);
-#endif
 
     // ensure the draw runs synchronously:
     this->setDataVariance(DYNAMIC);
@@ -62,10 +61,11 @@ ResourceReleaser::drawImplementation(osg::RenderInfo& ri) const
         Threading::ScopedMutexLock lock(_mutex);
         if (!_toRelease.empty())
         {
+            METRIC_SCOPED("ResourceReleaser");
             for (ObjectList::const_iterator i = _toRelease.begin(); i != _toRelease.end(); ++i)
             {
-                osg::Object* node = i->get();
-                node->releaseGLObjects(ri.getState());
+                osg::Object* object = i->get();
+                object->releaseGLObjects(ri.getState());
             }
             OE_DEBUG << LC << "Released " << _toRelease.size() << " objects\n";
             _toRelease.clear();
diff --git a/src/osgEarth/SceneGraphCallback b/src/osgEarth/SceneGraphCallback
new file mode 100644
index 0000000..51fe3ee
--- /dev/null
+++ b/src/osgEarth/SceneGraphCallback
@@ -0,0 +1,115 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+* Copyright 2016 Pelican Mapping
+* http://osgearth.org
+*
+* osgEarth is free software; you can redistribute it and/or modify
+* it under the terms of the GNU Lesser General Public License as published by
+* the Free Software Foundation; either version 2 of the License, or
+* (at your option) any later version.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+* IN THE SOFTWARE.
+*
+* You should have received a copy of the GNU Lesser General Public License
+* along with this program.  If not, see <http://www.gnu.org/licenses/>
+*/
+#ifndef OSGEARTH_SCENE_GRAPH_CALLBACK_H
+#define OSGEARTH_SCENE_GRAPH_CALLBACK_H 1
+
+#include <osgEarth/Common>
+#include <osgEarth/ThreadingUtils>
+#include <osg/Node>
+#include <osg/PagedLOD>
+#include <osg/observer_ptr>
+
+namespace osgEarth
+{
+    /**
+     * Callback for getting notifications of scene graph events.
+     * Not all methods may be invoked in all situations. It's up to
+     * the implementation to call then when it wants.
+     */
+    class SceneGraphCallback : public osg::Referenced
+    {
+    public:
+        // Called before a node is added to the live scene graph,
+        // possibly from a background pager thread
+        virtual void onPreMergeNode(osg::Node*) { }
+
+        // Called after a node is added to the live scene graph
+        // in the main/update thread
+        virtual void onPostMergeNode(osg::Node*) { }
+
+        // Called after a node is remoevd from the live scene graph
+        // in the main/update thread
+        virtual void onRemoveNode(osg::Node*) { }
+    };
+    typedef std::vector<osg::ref_ptr<SceneGraphCallback> > SceneGraphCallbackVector;
+
+    /**
+     * Cotnainer for scene graph callbacks. Typically an object that 
+     * supports scene graph callbacks will host an instance of this
+     * object and use it register and fire callbacks at the appropriate times.
+     */
+    class OSGEARTH_EXPORT SceneGraphCallbacks : public osg::Referenced
+    {
+    public:
+        //! Add a new callback
+        virtual void add(SceneGraphCallback* cb);
+
+        //! Remove an existing callback
+        virtual void remove(SceneGraphCallback* cb);
+
+        //! Invoke all pre-merge callbacks on the provided node
+        virtual void firePreMergeNode(osg::Node* node);
+
+        //! Invoke all post-merge callbacks on the provided node
+        virtual void firePostMergeNode(osg::Node* node);
+
+        //! Invoke all remove callbacks on the provided node
+        virtual void fireRemoveNode(osg::Node* node);
+
+    private:
+        SceneGraphCallbackVector _callbacks;
+        Threading::Mutex _mutex;
+    };
+
+    /**
+     * Extends the osg::PagedLOD class to invoke SceneGraphCallbacks
+     * when nodes are added or removed from the live scene graph.
+     * (Note, this object does not invoke the preMergeNode method.)
+     */
+    class OSGEARTH_EXPORT PagedLODWithSceneGraphCallbacks : public osg::PagedLOD
+    {
+    public:
+        PagedLODWithSceneGraphCallbacks(SceneGraphCallbacks* host);
+
+        /**
+         * Gets the SceneGraphCallbacks.
+         */
+        SceneGraphCallbacks* getSceneGraphCallbacks() const;
+
+        /**
+         * Sets the SceneGraphCallbacks
+         */
+        void setSceneGraphCallbacks(SceneGraphCallbacks* host);
+
+    public: // osg::Group
+        
+        virtual bool addChild(osg::Node* child);
+        virtual bool insertChild(unsigned index, osg::Node* child);
+        virtual bool replaceChild(osg::Node* origChild, osg::Node* newChild);
+        virtual bool removeChild(osg::Node* child);
+
+    private:
+        osg::observer_ptr<SceneGraphCallbacks> _host;
+    };
+}
+
+#endif // OSGEARTH_SCENE_GRAPH_CALLBACK_H
diff --git a/src/osgEarth/SceneGraphCallback.cpp b/src/osgEarth/SceneGraphCallback.cpp
new file mode 100644
index 0000000..3884f65
--- /dev/null
+++ b/src/osgEarth/SceneGraphCallback.cpp
@@ -0,0 +1,149 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+* Copyright 2016 Pelican Mapping
+* http://osgearth.org
+*
+* osgEarth is free software; you can redistribute it and/or modify
+* it under the terms of the GNU Lesser General Public License as published by
+* the Free Software Foundation; either version 2 of the License, or
+* (at your option) any later version.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+* IN THE SOFTWARE.
+*
+* You should have received a copy of the GNU Lesser General Public License
+* along with this program.  If not, see <http://www.gnu.org/licenses/>
+*/
+#include <osgEarth/SceneGraphCallback>
+
+using namespace osgEarth;
+
+void
+SceneGraphCallbacks::add(SceneGraphCallback* cb)
+{
+    if (cb)
+    {
+        Threading::ScopedMutexLock lock(_mutex);
+        _callbacks.push_back(cb);
+    }
+}
+
+void
+SceneGraphCallbacks::remove(SceneGraphCallback* cb)
+{
+    if (cb)
+    {
+        Threading::ScopedMutexLock lock(_mutex);
+        for (SceneGraphCallbackVector::iterator i = _callbacks.begin(); i != _callbacks.end(); ++i)
+        {
+            if (i->get() == cb)
+            {
+                _callbacks.erase(i);
+                break;
+            }
+        }
+    }
+}
+
+void
+SceneGraphCallbacks::firePreMergeNode(osg::Node* node)
+{
+    Threading::ScopedMutexLock lock(_mutex);
+    for (SceneGraphCallbackVector::iterator i = _callbacks.begin(); i != _callbacks.end(); ++i)
+        i->get()->onPreMergeNode(node);
+}
+
+void
+SceneGraphCallbacks::firePostMergeNode(osg::Node* node)
+{
+    for (SceneGraphCallbackVector::iterator i = _callbacks.begin(); i != _callbacks.end(); ++i)
+        i->get()->onPostMergeNode(node);
+}
+
+void
+SceneGraphCallbacks::fireRemoveNode(osg::Node* node)
+{
+    for (SceneGraphCallbackVector::iterator i = _callbacks.begin(); i != _callbacks.end(); ++i)
+        i->get()->onPostMergeNode(node);
+}
+
+
+PagedLODWithSceneGraphCallbacks::PagedLODWithSceneGraphCallbacks(SceneGraphCallbacks* host) :
+_host(host)
+{
+    //nop
+}
+
+SceneGraphCallbacks*
+PagedLODWithSceneGraphCallbacks::getSceneGraphCallbacks() const
+{
+    return _host.get();
+}
+
+void
+PagedLODWithSceneGraphCallbacks::setSceneGraphCallbacks(SceneGraphCallbacks* host)
+{
+    _host = host;
+}
+
+bool
+PagedLODWithSceneGraphCallbacks::addChild(osg::Node* child)
+{
+    bool ok = false;
+    if (child)
+    {
+        ok = osg::PagedLOD::addChild(child);
+        osg::ref_ptr<SceneGraphCallbacks> host;
+        if (_host.lock(host))
+            host->firePostMergeNode(child);
+    }
+    return ok;
+}
+
+bool
+PagedLODWithSceneGraphCallbacks::insertChild(unsigned index, osg::Node* child)
+{
+    bool ok = false;
+    if (child)
+    {
+        ok = osg::PagedLOD::insertChild(index, child);
+        osg::ref_ptr<SceneGraphCallbacks> host;
+        if (_host.lock(host))
+            host->firePostMergeNode(child);
+    }
+    return ok;
+}
+
+bool
+PagedLODWithSceneGraphCallbacks::replaceChild(osg::Node* oldChild, osg::Node* newChild)
+{
+    bool ok = false;
+    if (oldChild && newChild)
+    {
+        ok = osg::PagedLOD::replaceChild(oldChild, newChild);
+        osg::ref_ptr<SceneGraphCallbacks> host;
+        if (_host.lock(host))
+            host->firePostMergeNode(newChild);
+    }
+    return ok;
+}
+
+bool
+PagedLODWithSceneGraphCallbacks::removeChild(osg::Node* child)
+{
+    bool ok = false;
+    if (child)
+    {
+        osg::ref_ptr<osg::Node> node = child;
+        ok = osg::PagedLOD::removeChild(child);
+        osg::ref_ptr<SceneGraphCallbacks> host;
+        if (_host.lock(host))
+            host->fireRemoveNode(node.get());
+    }
+    return ok;
+}
diff --git a/src/osgEarth/ScreenSpaceLayout b/src/osgEarth/ScreenSpaceLayout
index 73dda9a..85d1f15 100644
--- a/src/osgEarth/ScreenSpaceLayout
+++ b/src/osgEarth/ScreenSpaceLayout
@@ -103,8 +103,10 @@ namespace osgEarth
               _inAnimTime           ( 0.40f ),
               _outAnimTime          ( 0.00f ),
               _sortByPriority       ( false ),
+              _sortByDistance       ( true ),
               _snapToPixel          ( true ),
-              _maxObjects           ( INT_MAX )
+              _maxObjects           ( INT_MAX ),
+              _renderBinNumber      ( 13 )
         {
             fromConfig(_conf);
         }
@@ -131,6 +133,10 @@ namespace osgEarth
         optional<bool>& sortByPriority() { return _sortByPriority; }
         const optional<bool>& sortByPriority() const { return _sortByPriority; }
 
+        /** If set, activate the AnnotationData distance-based sorting */
+        optional<bool>& sortByDistance() { return _sortByDistance; }
+        const optional<bool>& sortByDistance() const { return _sortByDistance; }
+
         /** Whether to always start rendering text on a pixel boundary, thereby 
           * minimizing filtering artifacts. */
         optional<bool>& snapToPixel() { return _snapToPixel; }
@@ -140,6 +146,10 @@ namespace osgEarth
         optional<unsigned>& maxObjects() { return _maxObjects; }
         const optional<unsigned>& maxObjects() const { return _maxObjects; }
 
+        /** Render bin number to use for the screen layout */
+        optional<int>& renderOrder() { return _renderBinNumber; }
+        const optional<int>& renderOrder() const { return _renderBinNumber; }
+
     public:
 
         Config getConfig() const;
@@ -150,8 +160,10 @@ namespace osgEarth
         optional<float>    _inAnimTime;
         optional<float>    _outAnimTime;
         optional<bool>     _sortByPriority;
+        optional<bool>     _sortByDistance;
         optional<bool>     _snapToPixel;
         optional<unsigned> _maxObjects;
+        optional<int>      _renderBinNumber;
 
         void fromConfig( const Config& conf );
     };
@@ -163,7 +175,7 @@ namespace osgEarth
          * Drawables rendered while this stateset is active will be projected from
          * scene space to 2D screen space with optional decluttering.
          */
-        static void activate(osg::StateSet* stateSet, int binNum =13);
+        static void activate(osg::StateSet* stateSet); //, int binNum =13);
 
         /**
          * Deactivates the use of the screen-space layout engine for a stateset.
diff --git a/src/osgEarth/ScreenSpaceLayout.cpp b/src/osgEarth/ScreenSpaceLayout.cpp
index 0d11511..346d7bc 100644
--- a/src/osgEarth/ScreenSpaceLayout.cpp
+++ b/src/osgEarth/ScreenSpaceLayout.cpp
@@ -122,9 +122,9 @@ namespace
     // TODO: a way to clear out this list when drawables go away
     struct DrawableInfo
     {
-        DrawableInfo() : _lastAlpha(1.0), _lastScale(1.0), _lastXY(-1.0, -1.0) { }
+        DrawableInfo() : _lastAlpha(1.0f), _lastScale(1.0f), _frame(0u) { }
         float _lastAlpha, _lastScale;
-        osg::Vec2d _lastXY;
+        unsigned _frame;
     };
 
     typedef std::map<const osg::Drawable*, DrawableInfo> DrawableMemory;
@@ -147,6 +147,7 @@ namespace
         // time stamp of the previous pass, for calculating animation speed
         osg::Timer_t _lastTimeStamp;
         bool _firstFrame;
+        osg::Matrix _lastCamVPW;
     };
 
     static bool s_declutteringEnabledGlobally = true;
@@ -170,8 +171,10 @@ ScreenSpaceLayoutOptions::fromConfig( const Config& conf )
     conf.getIfSet( "in_animation_time",   _inAnimTime );
     conf.getIfSet( "out_animation_time",  _outAnimTime );
     conf.getIfSet( "sort_by_priority",    _sortByPriority );
+    conf.getIfSet( "sort_by_distance",    _sortByDistance);
     conf.getIfSet( "snap_to_pixel",       _snapToPixel );
     conf.getIfSet( "max_objects",         _maxObjects );
+    conf.getIfSet( "render_order",        _renderBinNumber );
 }
 
 Config
@@ -183,13 +186,38 @@ ScreenSpaceLayoutOptions::getConfig() const
     conf.addIfSet( "in_animation_time",   _inAnimTime );
     conf.addIfSet( "out_animation_time",  _outAnimTime );
     conf.addIfSet( "sort_by_priority",    _sortByPriority );
+    conf.addIfSet( "sort_by_distance",    _sortByDistance);
     conf.addIfSet( "snap_to_pixel",       _snapToPixel );
     conf.addIfSet( "max_objects",         _maxObjects );
+    conf.addIfSet( "render_order",        _renderBinNumber );
     return conf;
 }
 
 //----------------------------------------------------------------------------
 
+template<typename T>
+struct LCGIterator
+{
+    T& _vec;
+    unsigned _seed;
+    unsigned _n;
+    unsigned _index;
+    unsigned _a, _c;
+    LCGIterator(T& vec) : _vec(vec), _seed(0u), _index(0u) {
+        _n = vec.size();
+        _a = _n+1;
+        _c = 15487457u; // a very large prime
+    }
+    bool hasMore() const { 
+        return _index < _n;
+    }
+    const typename T::value_type& next() {
+        _seed = (_a*_seed + _c) % _n;
+        _index++;
+        return _vec[_seed];
+    }
+};
+
 /**
  * A custom RenderLeaf sorting algorithm for decluttering objects.
  *
@@ -229,19 +257,21 @@ struct /*internal*/ DeclutterSort : public osgUtil::RenderBin::SortCallback
     // Sorts the bin. This runs in the CULL thread after the CULL traversal has completed.
     void sortImplementation(osgUtil::RenderBin* bin)
     {
+        const ScreenSpaceLayoutOptions& options = _context->_options;
+
         osgUtil::RenderBin::RenderLeafList& leaves = bin->getRenderLeafList();
+        
+        bin->copyLeavesFromStateGraphListToRenderLeafList();
 
         // first, sort the leaves:
         if ( _customSortFunctor && s_declutteringEnabledGlobally )
         {
             // if there's a custom sorting function installed
-            bin->copyLeavesFromStateGraphListToRenderLeafList();
             std::sort( leaves.begin(), leaves.end(), SortContainer( *_customSortFunctor ) );
         }
-        else
+        else if (options.sortByDistance() == true)
         {
             // default behavior:
-            bin->copyLeavesFromStateGraphListToRenderLeafList();
             std::sort( leaves.begin(), leaves.end(), SortFrontToBackPreservingGeodeTraversalOrder() );
         }
 
@@ -250,7 +280,13 @@ struct /*internal*/ DeclutterSort : public osgUtil::RenderBin::SortCallback
             return;
 
         // access the view-specific persistent data:
-        osg::Camera* cam   = bin->getStage()->getCamera();                
+        osg::Camera* cam = bin->getStage()->getCamera();
+
+        // bail out if this camera is a master camera with no GC
+        // (e.g., in a multi-screen layout)
+        if (cam == NULL || cam->getGraphicsContext() == NULL)
+            return;
+
         PerCamInfo& local = _perCam.get( cam );
 
         osg::Timer_t now = osg::Timer::instance()->tick();
@@ -281,16 +317,19 @@ struct /*internal*/ DeclutterSort : public osgUtil::RenderBin::SortCallback
         osg::Matrix refCamScaleMat;
         osg::Matrix refWindowMatrix = windowMatrix;
 
-        if ( cam->isRenderToTextureCamera() )
+        // If the camera is actually an RTT slave camera, it's our picker, and we need to
+        // adjust the scale to match it.
+        if (cam->isRenderToTextureCamera() &&
+            cam->getView() &&
+            cam->getView()->getCamera() &&
+            cam->getView()->getCamera() != cam)
+            //cam->getView()->findSlaveIndexForCamera(cam) < cam->getView()->getNumSlaves())
         {
-            osg::Camera* refCam = dynamic_cast<osg::Camera*>(cam->getUserData());
-            if ( refCam )
-            {
-                const osg::Viewport* refVP = refCam->getViewport();
-                refCamScale.set( vp->width() / refVP->width(), vp->height() / refVP->height(), 1.0 );
-                refCamScaleMat.makeScale( refCamScale );
-                refWindowMatrix = refVP->computeWindowMatrix();
-            }
+            osg::Camera* parentCam = cam->getView()->getCamera();
+            const osg::Viewport* refVP = parentCam->getViewport();
+            refCamScale.set( vp->width() / refVP->width(), vp->height() / refVP->height(), 1.0 );
+            refCamScaleMat.makeScale( refCamScale );
+            refWindowMatrix = refVP->computeWindowMatrix();
         }
 
         // Track the parent nodes of drawables that are obscured (and culled). Drawables
@@ -298,7 +337,6 @@ struct /*internal*/ DeclutterSort : public osgUtil::RenderBin::SortCallback
         // will be culled as a group.
         std::set<const osg::Node*> culledParents;
 
-        const ScreenSpaceLayoutOptions& options = _context->_options;
         unsigned limit = *options.maxObjects();
 
         bool snapToPixel = options.snapToPixel() == true;
@@ -307,14 +345,18 @@ struct /*internal*/ DeclutterSort : public osgUtil::RenderBin::SortCallback
         camVPW.postMult(cam->getViewMatrix());
         camVPW.postMult(cam->getProjectionMatrix());
         camVPW.postMult(refWindowMatrix);
-        //if (cam->getViewport())
-        //    camVPW.postMult(cam->getViewport()->computeWindowMatrix());
+
+        // has the camera moved?
+        bool camChanged = camVPW != local._lastCamVPW;
+        local._lastCamVPW = camVPW;
 
         // Go through each leaf and test for visibility.
         // Enforce the "max objects" limit along the way.
         for(osgUtil::RenderBin::RenderLeafList::iterator i = leaves.begin(); 
             i != leaves.end() && local._passed.size() < limit; 
             ++i )
+        //LCGIterator<osgUtil::RenderBin::RenderLeafList> i(leaves);
+        //while (i.hasMore() && local._passed.size() < limit)
         {
             bool visible = true;
 
@@ -404,23 +446,14 @@ struct /*internal*/ DeclutterSort : public osgUtil::RenderBin::SortCallback
 
             // if snapping is enabled, only snap when the camera stops moving.
             bool quantize = snapToPixel;
-            if ( quantize )
+            if ( quantize && !camChanged )
             {
-                DrawableInfo& info = local._memory[drawable];
-                
-                if ( info._lastXY.x() == winPos.x() && info._lastXY.y() == winPos.y() )
-                {
-                    // Quanitize the window draw coordinates to mitigate text rendering filtering anomalies.
-                    // Drawing text glyphs on pixel boundaries mitigates aliasing.
-                    // Adding 0.5 will cause the GPU to sample the glyph texels exactly on center.
-                    winPos.x() = floor(winPos.x()) + 0.5;
-                    winPos.y() = floor(winPos.y()) + 0.5;
-                }
-                else
-                {
-                    info._lastXY.set( winPos.x(), winPos.y() );
-                }
-            }            
+                // Quanitize the window draw coordinates to mitigate text rendering filtering anomalies.
+                // Drawing text glyphs on pixel boundaries mitigates aliasing.
+                // Adding 0.5 will cause the GPU to sample the glyph texels exactly on center.
+                winPos.x() = floor(winPos.x()) + 0.5;
+                winPos.y() = floor(winPos.y()) + 0.5;
+            }
 
             if ( s_declutteringEnabledGlobally )
             {
@@ -503,9 +536,8 @@ struct /*internal*/ DeclutterSort : public osgUtil::RenderBin::SortCallback
 
         // copy the final draw list back into the bin, rejecting any leaves whose parents
         // are in the cull list.
-
         if ( s_declutteringEnabledGlobally )
-        {
+        { 
             leaves.clear();
             for( osgUtil::RenderBin::RenderLeafList::const_iterator i=local._passed.begin(); i != local._passed.end(); ++i )
             {
@@ -540,7 +572,9 @@ struct /*internal*/ DeclutterSort : public osgUtil::RenderBin::SortCallback
                     }
 
                     leaf->_depth = info._lastAlpha;
-                    leaves.push_back( leaf );                
+                    leaves.push_back( leaf );
+
+                    info._frame++;
                 }
                 else
                 {
@@ -561,20 +595,29 @@ struct /*internal*/ DeclutterSort : public osgUtil::RenderBin::SortCallback
                 bool isBbox = dynamic_cast<const osgEarth::Annotation::BboxDrawable*>(drawable) != 0L;
                 bool fullyOut = true;
 
-                if ( info._lastScale != *options.minAnimationScale() )
+                if (info._frame > 0u)
                 {
-                    fullyOut = false;
-                    info._lastScale -= elapsedSeconds / std::max(*options.outAnimationTime(), 0.001f);
-                    if ( info._lastScale < *options.minAnimationScale() )
-                        info._lastScale = *options.minAnimationScale();
-                }
+                    if ( info._lastScale != *options.minAnimationScale() )
+                    {
+                        fullyOut = false;
+                        info._lastScale -= elapsedSeconds / std::max(*options.outAnimationTime(), 0.001f);
+                        if ( info._lastScale < *options.minAnimationScale() )
+                            info._lastScale = *options.minAnimationScale();
+                    }
 
-                if ( info._lastAlpha != *options.minAnimationAlpha() )
+                    if ( info._lastAlpha != *options.minAnimationAlpha() )
+                    {
+                        fullyOut = false;
+                        info._lastAlpha -= elapsedSeconds / std::max(*options.outAnimationTime(), 0.001f);
+                        if ( info._lastAlpha < *options.minAnimationAlpha() )
+                            info._lastAlpha = *options.minAnimationAlpha();
+                    }
+                }
+                else
                 {
-                    fullyOut = false;
-                    info._lastAlpha -= elapsedSeconds / std::max(*options.outAnimationTime(), 0.001f);
-                    if ( info._lastAlpha < *options.minAnimationAlpha() )
-                        info._lastAlpha = *options.minAnimationAlpha();
+                    // prevent first-frame "pop out"
+                    info._lastScale = options.minAnimationScale().get();
+                    info._lastAlpha = options.minAnimationAlpha().get();
                 }
 
                 leaf->_depth = info._lastAlpha;
@@ -590,6 +633,8 @@ struct /*internal*/ DeclutterSort : public osgUtil::RenderBin::SortCallback
                             leaf->_modelview->preMult( osg::Matrix::scale(info._lastScale,info._lastScale,1) );
                     }
                 }
+
+                info._frame++;
             }
         }
     }
@@ -654,6 +699,7 @@ namespace
 
             // render the list
             osgUtil::RenderBin::RenderLeafList& leaves = bin->getRenderLeafList();
+
             for(osgUtil::RenderBin::RenderLeafList::reverse_iterator rlitr = leaves.rbegin();
                 rlitr!= leaves.rend();
                 ++rlitr)
@@ -817,15 +863,17 @@ bool osgEarthScreenSpaceLayoutRenderBin::_vpInstalled = false;
 //----------------------------------------------------------------------------
 
 void
-ScreenSpaceLayout::activate(osg::StateSet* stateSet, int binNum)
+ScreenSpaceLayout::activate(osg::StateSet* stateSet) //, int binNum)
 {
     if ( stateSet )
     {
+        int binNum = getOptions().renderOrder().get();
+
         // the OVERRIDE prevents subsequent statesets from disabling the layout bin
         stateSet->setRenderBinDetails(
             binNum,
             OSGEARTH_SCREEN_SPACE_LAYOUT_BIN,
-            osg::StateSet::OVERRIDE_RENDERBIN_DETAILS);
+            osg::StateSet::OVERRIDE_PROTECTED_RENDERBIN_DETAILS);
 
         // Force a single shared layout bin per render stage
         stateSet->setNestRenderBins( false );
@@ -942,4 +990,4 @@ namespace osgEarth
     REGISTER_OSGEARTH_EXTENSION(osgearth_screen_space_layout, ScreenSpaceLayoutExtension);
     REGISTER_OSGEARTH_EXTENSION(osgearth_decluttering,        ScreenSpaceLayoutExtension);
 }
-                                       
\ No newline at end of file
+                                       
diff --git a/src/osgEarth/ShaderFactory b/src/osgEarth/ShaderFactory
index 166d149..75f2e73 100644
--- a/src/osgEarth/ShaderFactory
+++ b/src/osgEarth/ShaderFactory
@@ -24,6 +24,8 @@
 
 #include <osgEarth/Common>
 #include <osgEarth/VirtualProgram>
+#include <osgEarth/ColorFilter>
+#include <vector>
 
 namespace osgEarth
 {
@@ -67,6 +69,7 @@ namespace osgEarth
         virtual ShaderComp::StageMask createMains(
             const ShaderComp::FunctionLocationMap&    functions,
             const VirtualProgram::ShaderMap&          in_shaders,
+            const VirtualProgram::ExtensionsSet&      in_extensions,
             std::vector< osg::ref_ptr<osg::Shader> >& out_mains) const;
 
         /**
@@ -79,14 +82,6 @@ namespace osgEarth
             const ColorFilterChain& chain ) const;
 
         /**
-         * Gets a uniform corresponding to the given mode and value. These uniforms are 
-         * named in the form "oe_mode_MODE" (e.g., "oe_mode_GL_LIGHTING").
-         */
-        virtual osg::Uniform* createUniformForGLMode(
-            osg::StateAttribute::GLMode      mode,
-            osg::StateAttribute::GLModeValue value ) const;
-
-        /**
          * The name of the range uniform created by createRangeUniform().
          */
         virtual std::string getRangeUniformName() const;
diff --git a/src/osgEarth/ShaderFactory.cpp b/src/osgEarth/ShaderFactory.cpp
index 66823a0..7c455d0 100755
--- a/src/osgEarth/ShaderFactory.cpp
+++ b/src/osgEarth/ShaderFactory.cpp
@@ -22,6 +22,7 @@
 #include <osgEarth/ShaderLoader>
 #include <osgEarth/Registry>
 #include <osgEarth/Capabilities>
+#include <osgEarth/VirtualProgram>
 #include <osg/Shader>
 #include <osg/Program>
 #include <osg/State>
@@ -30,7 +31,7 @@
 
 #define LC "[ShaderFactory] "
 
-#ifdef OSG_GLES2_AVAILABLE
+#if defined(OSG_GLES2_AVAILABLE) || defined(OSG_GLES3_AVAILABLE)
     static bool s_GLES_SHADERS = true;
 #else
     static bool s_GLES_SHADERS = false;
@@ -80,17 +81,28 @@ namespace
         std::string interp;      // interpolation qualifer (flat, etc.)
         std::string type;        // float, vec4, etc.
         std::string name;        // name without any array specifiers, etc.
+        std::string prec;        // precision qualifier if any
         std::string declaration; // name including array specifiers (for decl)
         int         arraySize;   // 0 if not an array; else array size.
     };
 
     typedef std::vector<Variable> Variables;
+
+	void addExtensionsToBuffer(std::ostream& buf, const VirtualProgram::ExtensionsSet& in_extensions)
+	{
+	   for (VirtualProgram::ExtensionsSet::const_iterator it = in_extensions.begin(); it != in_extensions.end(); ++it)
+	   {
+	      const std::string& extension = *it;
+	      buf << "#extension "<<extension<< " : enable \n";
+	   }
+	}
 }
 
 
 ShaderComp::StageMask
 ShaderFactory::createMains(const ShaderComp::FunctionLocationMap&    functions,
                            const VirtualProgram::ShaderMap&          in_shaders,
+                           const VirtualProgram::ExtensionsSet&      in_extensions,
                            std::vector< osg::ref_ptr<osg::Shader> >& out_shaders) const
 {
     StageMask stages =
@@ -186,6 +198,11 @@ ShaderFactory::createMains(const ShaderComp::FunctionLocationMap&    functions,
             {
                 v.interp = tokens[p++];
             }
+            
+            if ( tokens[p] == "lowp" || tokens[p] == "mediump" || tokens[p] == "highp" )
+            {
+                v.prec = tokens[p++];
+            }
 
             if ( p+1 < tokens.size() )
             {
@@ -252,7 +269,7 @@ ShaderFactory::createMains(const ShaderComp::FunctionLocationMap&    functions,
         std::stringstream buf;
         buf << "VP_PerVertex { \n";
         for(Variables::const_iterator i = vars.begin(); i != vars.end(); ++i)
-            buf << INDENT << i->interp << (i->interp.empty()?"":" ") << i->declaration << "; \n";
+            buf << INDENT << i->interp << (i->interp.empty()?"":" ") << i->prec << (i->prec.empty()?"":" ") << i->declaration << "; \n";
         buf << "}";
         vertdata = buf.str();
     }
@@ -267,14 +284,16 @@ ShaderFactory::createMains(const ShaderComp::FunctionLocationMap&    functions,
 
         std::stringstream buf;
 
-        buf <<
-            "#version " << vs_glsl_version << "\n"
+        buf << "#version " << vs_glsl_version << "\n"
+            GLSL_DEFAULT_PRECISION_FLOAT << "\n"
             "#pragma vp_name VP Vertex Shader Main\n"
-            "#extension GL_ARB_gpu_shader5 : enable \n";
+            << (!s_GLES_SHADERS ? "#extension GL_ARB_gpu_shader5 : enable \n" : "");
+
+        addExtensionsToBuffer(buf, in_extensions);
 
         buf << "\n// Vertex stage globals:\n";
         for(Variables::const_iterator i = vars.begin(); i != vars.end(); ++i)
-            buf << i->declaration << "; \n";
+            buf << i->prec << (i->prec.empty()?"":" ") << i->declaration << "; \n";
         
         buf << "\n// Vertex stage outputs:\n";
         if ( hasGS || hasTCS )
@@ -430,9 +449,12 @@ ShaderFactory::createMains(const ShaderComp::FunctionLocationMap&    functions,
         std::stringstream buf;
 
         buf << "#version " << tcs_glsl_version << "\n"
+            << GLSL_DEFAULT_PRECISION_FLOAT << "\n"
             << "#pragma vp_name VP Tessellation Control Shader (TCS) Main\n"
             // For gl_MaxPatchVertices
-            << "#extension GL_NV_gpu_shader5 : enable\n";
+            << (!s_GLES_SHADERS ? "#extension GL_NV_gpu_shader5 : enable\n" : "");
+
+        addExtensionsToBuffer(buf, in_extensions);
 
         buf << glMatrixUniforms << "\n";
 
@@ -449,7 +471,7 @@ ShaderFactory::createMains(const ShaderComp::FunctionLocationMap&    functions,
         // Stage globals.
         buf << "\n// TCS stage globals \n";
         for(Variables::const_iterator i = vars.begin(); i != vars.end(); ++i)
-            buf << i->declaration << "; \n";
+            buf << i->prec << (i->prec.empty()?"":" ") << i->declaration << "; \n";
 
         // Helper functions:
         // TODO: move this into its own osg::Shader so it can be shared.
@@ -509,8 +531,11 @@ ShaderFactory::createMains(const ShaderComp::FunctionLocationMap&    functions,
         std::stringstream buf;
 
         buf << "#version " << tes_glsl_version << "\n"
+            << GLSL_DEFAULT_PRECISION_FLOAT << "\n"
             << "#pragma vp_name VP Tessellation Evaluation (TES) Shader MAIN\n";
 
+        addExtensionsToBuffer(buf, in_extensions);
+
         buf << glMatrixUniforms << "\n";
 
         buf << "\n// TES stage inputs (required):\n"
@@ -712,8 +737,11 @@ ShaderFactory::createMains(const ShaderComp::FunctionLocationMap&    functions,
         std::stringstream buf;
 
         buf << "#version " << gs_glsl_version << "\n"
+            << GLSL_DEFAULT_PRECISION_FLOAT << "\n"
             << "#pragma vp_name VP Geometry Shader Main\n";
 
+        addExtensionsToBuffer(buf, in_extensions);
+
         buf << glMatrixUniforms << "\n";
 
         if ( hasVS || hasTCS || hasTES )
@@ -902,14 +930,17 @@ ShaderFactory::createMains(const ShaderComp::FunctionLocationMap&    functions,
         std::stringstream buf;
 
         buf << "#version " << fs_glsl_version << "\n"
+            << GLSL_DEFAULT_PRECISION_FLOAT << "\n"
             << "#pragma vp_name VP Fragment Shader Main\n"
-            << "#extension GL_ARB_gpu_shader5 : enable \n";
+            << (!s_GLES_SHADERS ? "#extension GL_ARB_gpu_shader5 : enable \n" : "");
+
+        addExtensionsToBuffer(buf, in_extensions);
 
         // no output stage? Use default output
         if (!outputStage)
         {
             buf << "\n// Fragment output\n"
-                << "out vec4 oe_FragColor;\n";
+                << "out vec4 vp_FragColor;\n";
         }
 
         buf << "\n// Fragment stage inputs:\n";
@@ -920,7 +951,7 @@ ShaderFactory::createMains(const ShaderComp::FunctionLocationMap&    functions,
 
         // Declare stage globals.
         for(Variables::const_iterator i = vars.begin(); i != vars.end(); ++i)
-            buf << i->declaration << ";\n";
+            buf << i->prec << (i->prec.empty()?"":" ") << i->declaration << ";\n";
 
         if ( coloringStage || lightingStage || outputStage )
         {
@@ -994,7 +1025,7 @@ ShaderFactory::createMains(const ShaderComp::FunctionLocationMap&    functions,
         {
             // in the absense of any output functions, generate a default output statement
             // that simply writes to gl_FragColor.
-            buf << INDENT << "oe_FragColor = vp_Color;\n";
+            buf << INDENT << "vp_FragColor = vp_Color;\n";
         }
         buf << "}\n";
 
@@ -1015,8 +1046,7 @@ ShaderFactory::createColorFilterChainFragmentShader(const std::string&      func
 {
     std::stringstream buf;
     buf << 
-        "#version " GLSL_VERSION_STR "\n"
-        GLSL_DEFAULT_PRECISION_FLOAT "\n";
+        "#version " GLSL_VERSION_STR "\n" << GLSL_DEFAULT_PRECISION_FLOAT "\n";
 
     // write out the shader function prototypes:
     for( ColorFilterChain::const_iterator i = chain.begin(); i != chain.end(); ++i )
@@ -1044,20 +1074,20 @@ ShaderFactory::createColorFilterChainFragmentShader(const std::string&      func
 }
 
 
-osg::Uniform*
-ShaderFactory::createUniformForGLMode(osg::StateAttribute::GLMode      mode,
-                                      osg::StateAttribute::GLModeValue value) const
-{
-    osg::Uniform* u = 0L;
-
-    if ( mode == GL_LIGHTING )
-    {
-        u = new osg::Uniform(osg::Uniform::BOOL, "oe_mode_GL_LIGHTING");
-        u->set( (value & osg::StateAttribute::ON) != 0 );
-    }
-
-    return u;
-}
+//osg::Uniform*
+//ShaderFactory::createUniformForGLMode(osg::StateAttribute::GLMode      mode,
+//                                      osg::StateAttribute::GLModeValue value) const
+//{
+//    osg::Uniform* u = 0L;
+//
+//    if ( mode == GL_LIGHTING )
+//    {
+//        u = new osg::Uniform(osg::Uniform::BOOL, "oe_mode_GL_LIGHTING");
+//        u->set( (value & osg::StateAttribute::ON) != 0 );
+//    }
+//
+//    return u;
+//}
 
 std::string
 ShaderFactory::getRangeUniformName() const
diff --git a/src/osgEarth/ShaderGenerator b/src/osgEarth/ShaderGenerator
index d2aba7d..d3ad0ca 100644
--- a/src/osgEarth/ShaderGenerator
+++ b/src/osgEarth/ShaderGenerator
@@ -44,6 +44,7 @@ namespace osg
     class Texture2DMultisample;
     class TextureCubeMap;
     class PointSprite;
+    class LightSource;
 }
 
 namespace osgSim
@@ -57,7 +58,7 @@ namespace osgEarth
      * Traverses a scene graph and generates VirtualProgram attributes to
      * render the geometry using GLSL shaders.
      *
-     * You can use this class directly, but they osgEarth Registry holds
+     * You can use this class directly, but the osgEarth Registry holds
      * a system-wide implementation that the user can replace. So the best
      * way to use this class is:
      *
@@ -177,6 +178,7 @@ namespace osgEarth
         
         virtual void apply( osgSim::LightPointNode& );
 
+
     protected: // high-level entry points:
 
         virtual void optimizeStateSharing(osg::Node* graph, StateSetCache* cache);
@@ -193,7 +195,8 @@ namespace osgEarth
 
         struct OSGEARTH_EXPORT GenBuffers
         {
-            std::stringstream _vertHead, _vertBody;
+            std::stringstream _modelHead, _modelBody;
+            std::stringstream _viewHead, _viewBody;
             std::stringstream _fragHead, _fragBody;
             osg::StateSet*    _stateSet;
             unsigned          _version;
@@ -233,6 +236,9 @@ namespace osgEarth
         // each parent has a complete separate copy of the child.
         virtual void duplicateSharedNode(osg::Node& child);
 
+        // disables (or removes) attributes that won't work in teh current configuration
+        virtual void disableUnsupportedAttributes(osg::StateSet* stateset);
+
     protected:
 
         osg::ref_ptr<osg::State> _state;
diff --git a/src/osgEarth/ShaderGenerator.cpp b/src/osgEarth/ShaderGenerator.cpp
index 25575de..680ff1a 100644
--- a/src/osgEarth/ShaderGenerator.cpp
+++ b/src/osgEarth/ShaderGenerator.cpp
@@ -25,6 +25,8 @@
 #include <osgEarth/ShaderFactory>
 #include <osgEarth/StringUtils>
 #include <osgEarth/URI>
+#include <osgEarth/Lighting>
+#include <osgEarth/VirtualProgram>
 
 #include <osg/Drawable>
 #include <osg/Geode>
@@ -66,7 +68,7 @@ using namespace osgEarth;
 
 // compatibility string for GLES:
 
-#ifdef OSG_GLES2_AVAILABLE
+#if defined(OSG_GLES2_AVAILABLE) || defined(OSG_GLES3_AVAILABLE)
 #   define GLSL_PRECISION "precision mediump float;"
 #   define MEDIUMP        "mediump "
 #   define LOWP           "lowp "
@@ -87,67 +89,71 @@ using namespace osgEarth;
 #define TEXENV_COLOR   "oe_sg_texenvcolor"
 #define TEX_MATRIX     "oe_sg_texmat"
 
-#define VERTEX_FUNCTION   "oe_sg_vert"
-#define FRAGMENT_FUNCTION "oe_sg_frag"
+#define VERTEX_MODEL_FUNCTION "oe_sg_vert_model"
+#define VERTEX_VIEW_FUNCTION  "oe_sg_vert_view"
+#define FRAGMENT_FUNCTION     "oe_sg_frag"
 
 // other stuff
 #define INDENT "    "
 
 //------------------------------------------------------------------------
 
-struct OSGEarthShaderGenPseudoLoader : public osgDB::ReaderWriter
+namespace
 {
-    OSGEarthShaderGenPseudoLoader()
+    struct OSGEarthShaderGenPseudoLoader : public osgDB::ReaderWriter
     {
-        this->supportsExtension( SHADERGEN_PL_EXTENSION, "ShaderGen pseudoloader" );
-    }
+        OSGEarthShaderGenPseudoLoader()
+        {
+            this->supportsExtension( SHADERGEN_PL_EXTENSION, "ShaderGen pseudoloader" );
+        }
 
-    const char* className() const
-    {
-        return "OSGEarth ShaderGen pseudoloader";
-    }
+        const char* className() const
+        {
+            return "OSGEarth ShaderGen pseudoloader";
+        }
 
-    bool acceptsExtension(const std::string& extension) const
-    {
-        return osgDB::equalCaseInsensitive( extension, SHADERGEN_PL_EXTENSION );
-    }
+        bool acceptsExtension(const std::string& extension) const
+        {
+            return osgDB::equalCaseInsensitive( extension, SHADERGEN_PL_EXTENSION );
+        }
 
-    ReadResult readObject(const std::string& filename, const osgDB::Options* options) const
-    {
-        return readNode( filename, options );
-    }
+        ReadResult readObject(const std::string& filename, const osgDB::Options* options) const
+        {
+            return readNode( filename, options );
+        }
 
-    ReadResult readNode(const std::string& filename, const osgDB::Options* options) const
-    {
-        if ( !acceptsExtension(osgDB::getFileExtension(filename)) )
-            return ReadResult::FILE_NOT_HANDLED;
+        ReadResult readNode(const std::string& filename, const osgDB::Options* options) const
+        {
+            if ( !acceptsExtension(osgDB::getFileExtension(filename)) )
+                return ReadResult::FILE_NOT_HANDLED;
 
-        std::string stripped = osgDB::getNameLessExtension(filename);
+            std::string stripped = osgDB::getNameLessExtension(filename);
 
-        OE_INFO << LC << "Loading " << stripped << " from PLOD/Proxy and generating shaders." << std::endl;
+            OE_INFO << LC << "Loading " << stripped << " from PLOD/Proxy and generating shaders." << std::endl;
         
-        osgEarth::ReadResult result = URI(stripped).readNode(options);
-        if ( result.succeeded() && result.getNode() != 0L )
-        {
-            osg::ref_ptr<osg::Node> node = result.releaseNode();
+            osgEarth::ReadResult result = URI(stripped).readNode(options);
+            if ( result.succeeded() && result.getNode() != 0L )
+            {
+                osg::ref_ptr<osg::Node> node = result.releaseNode();
 
-            osgEarth::Registry::shaderGenerator().run(
-                node.get(),
-                osgDB::getSimpleFileName(stripped),
-                Registry::stateSetCache() );
+                osgEarth::Registry::shaderGenerator().run(
+                    node.get(),
+                    osgDB::getSimpleFileName(stripped),
+                    Registry::stateSetCache() );
 
-            return ReadResult( node.release() );
-        }
+                return ReadResult( node.release() );
+            }
 
-        else
-        {
-            OE_WARN << LC << "Error loading \"" << stripped << "\": " << result.errorDetail() << "\n";
-            return ReadResult::ERROR_IN_READING_FILE;
+            else
+            {
+                OE_WARN << LC << "Error loading \"" << stripped << "\": " << result.errorDetail() << "\n";
+                return ReadResult::ERROR_IN_READING_FILE;
+            }
         }
-    }
-};
+    };
 
-REGISTER_OSGPLUGIN(SHADERGEN_PL_EXTENSION, OSGEarthShaderGenPseudoLoader)
+    REGISTER_OSGPLUGIN(SHADERGEN_PL_EXTENSION, OSGEarthShaderGenPseudoLoader)
+}
 
 //------------------------------------------------------------------------
 
@@ -188,8 +194,12 @@ namespace
         osg::StateAttribute* _sa;
         unsigned             _unit;
     };
+}
+    
+//------------------------------------------------------------------------
 
-
+namespace
+{
     /**
      * The OSG State extended with mode/attribute accessors.
      */
@@ -280,7 +290,13 @@ namespace
         // ref: https://github.com/openscenegraph/osg/commit/22af59482ac4f727eeed5b97476a3a47d7fe8a69
         bool isModeless(osg::StateAttribute* sa) const
         {
-#if OSG_VERSION_LESS_THAN(3,3,1)            
+
+#if !defined(OSG_GL_FIXED_FUNCTION_AVAILABLE)
+            // No modes in non-ffp
+            return true;
+#endif
+
+#if OSG_VERSION_LESS_THAN(3,3,1)
             return
                 dynamic_cast<osg::Texture2DArray*>(sa) ||
                 dynamic_cast<osg::Texture2DMultisample*>(sa) ||
@@ -343,20 +359,6 @@ ShaderGenerator::ShaderGenerator()
     _duplicateSharedSubgraphs = false;
 }
 
-// pre-3.3.0, NodeVisitor didn't have a copy constructor.
-#if OSG_VERSION_LESS_THAN(3,3,0)
-ShaderGenerator::ShaderGenerator(const ShaderGenerator& rhs, const osg::CopyOp& copy) :
-osg::NodeVisitor         (),
-_active                  (rhs._active),
-_duplicateSharedSubgraphs(rhs._duplicateSharedSubgraphs)
-{
-    _visitorType              = rhs._visitorType;
-    _traversalMode            = rhs._traversalMode;
-    _traversalMask            = rhs._traversalMask;
-    _nodeMaskOverride         = rhs._nodeMaskOverride;
-    _state = new StateEx();
-}
-#else
 ShaderGenerator::ShaderGenerator(const ShaderGenerator& rhs, const osg::CopyOp& copy) :
 osg::NodeVisitor         (rhs, copy),
 _active                  (rhs._active),
@@ -364,7 +366,6 @@ _duplicateSharedSubgraphs(rhs._duplicateSharedSubgraphs)
 {
     _state = new StateEx();
 }
-#endif
 
 void
 ShaderGenerator::setIgnoreHint(osg::Object* object, bool ignore)
@@ -424,6 +425,10 @@ ShaderGenerator::run(osg::Node*         graph,
         // perform GL state sharing
         optimizeStateSharing( graph, cache );
 
+        // generate uniforms and uniform callbacks for lighting and material elements.
+        GenerateGL3LightingUniforms generateUniforms;
+        graph->accept(generateUniforms);
+
         osg::StateSet* stateset = cloneOrCreateStateSet(graph);
 
         // install a blank VP at the top as the default.
@@ -484,6 +489,7 @@ ShaderGenerator::apply(osg::Node& node)
 
     if ( stateset.valid() )
     {
+        disableUnsupportedAttributes(stateset.get());
         _state->popStateSet();
     }
 }
@@ -568,26 +574,25 @@ ShaderGenerator::apply( osg::Geode& node )
 
     if ( stateset.valid() )
     {
+        disableUnsupportedAttributes(stateset.get());
         _state->popStateSet();
     }
 }
 
-#if OSG_VERSION_GREATER_OR_EQUAL(3,3,3)
 void
-ShaderGenerator::apply( osg::Drawable& drawable )
+ShaderGenerator::apply( osg::Drawable& node )
 {
     if ( !_active )
         return;
 
-    if ( ignore(&drawable) )
+    if ( ignore(&node) )
         return;
 
     if ( _duplicateSharedSubgraphs )
-        duplicateSharedNode(drawable);
+        duplicateSharedNode(node);
 
-    apply( &drawable );
+    apply( &node );
 }
-#endif
 
 void 
 ShaderGenerator::apply( osg::Drawable* drawable )
@@ -630,6 +635,7 @@ ShaderGenerator::apply( osg::Drawable* drawable )
 
         if ( ss.valid() )
         {
+            disableUnsupportedAttributes(ss.get());
             _state->popStateSet();
         }
     }
@@ -695,6 +701,7 @@ ShaderGenerator::apply(osg::ClipNode& node)
 {
     static const char* s_clip_source =
         "#version " GLSL_VERSION_STR "\n"
+        GLSL_PRECISION "\n"
         "void oe_sg_set_clipvertex(inout vec4 vertexVIEW)\n"
         "{\n"
         "    gl_ClipVertex = vertexVIEW; \n"
@@ -748,7 +755,8 @@ ShaderGenerator::apply(osgSim::LightPointNode& node)
             replacement->removeTextureAttribute(0, sprite.get());
             node.setStateSet(replacement.get() );
         }
-
+        
+        disableUnsupportedAttributes(stateset.get());
         _state->popStateSet();
     }
 }
@@ -783,8 +791,8 @@ ShaderGenerator::processText(const osg::StateSet* ss, osg::ref_ptr<osg::StateSet
 
     std::string vertSrc =
         "#version " GLSL_VERSION_STR "\n" GLSL_PRECISION "\n"
-        "varying " MEDIUMP "vec4 " TEX_COORD_TEXT ";\n"
-        "void " VERTEX_FUNCTION "(inout vec4 vertexVIEW)\n"
+        "out " MEDIUMP "vec4 " TEX_COORD_TEXT ";\n"
+        "void " VERTEX_MODEL_FUNCTION "(inout vec4 unused)\n"
         "{ \n"
         INDENT TEX_COORD_TEXT " = gl_MultiTexCoord0;\n"
         "} \n";
@@ -792,15 +800,14 @@ ShaderGenerator::processText(const osg::StateSet* ss, osg::ref_ptr<osg::StateSet
     std::string fragSrc =
         "#version " GLSL_VERSION_STR "\n" GLSL_PRECISION "\n"
         "uniform sampler2D " SAMPLER_TEXT ";\n"
-        "varying " MEDIUMP "vec4 " TEX_COORD_TEXT ";\n"
+        "in " MEDIUMP "vec4 " TEX_COORD_TEXT ";\n"
         "void " FRAGMENT_FUNCTION "(inout vec4 color)\n"
         "{ \n"
-        INDENT MEDIUMP "vec4 texel = texture2D(" SAMPLER_TEXT ", " TEX_COORD_TEXT ".xy);\n"
-        //INDENT MEDIUMP "vec4 texel = texture2DLod(" SAMPLER_TEXT ", " TEX_COORD_TEXT ".xy, 0.0);\n"
+        INDENT MEDIUMP "vec4 texel = texture(" SAMPLER_TEXT ", " TEX_COORD_TEXT ".xy);\n"
         INDENT "color.a *= texel.a; \n"
         "}\n";
 
-    vp->setFunction( VERTEX_FUNCTION,   vertSrc, ShaderComp::LOCATION_VERTEX_MODEL, 0.5f );
+    vp->setFunction( VERTEX_MODEL_FUNCTION, vertSrc, ShaderComp::LOCATION_VERTEX_MODEL, 0.5f );
     vp->setFunction( FRAGMENT_FUNCTION, fragSrc, ShaderComp::LOCATION_FRAGMENT_COLORING, 0.5f );
     replacement->getOrCreateUniform( SAMPLER_TEXT, osg::Uniform::SAMPLER_2D )->set( 0 );
 
@@ -833,7 +840,7 @@ ShaderGenerator::processGeometry(const osg::StateSet*         original,
         new osg::StateSet();
 
     // likewise, create a VP that we might populate.
-    osg::ref_ptr<VirtualProgram> vp = VirtualProgram::cloneOrCreate(original, newStateSet);
+    osg::ref_ptr<VirtualProgram> vp = VirtualProgram::cloneOrCreate(original, newStateSet.get());
 
     // we'll set this to true if the new stateset goes into effect and
     // needs to be returned.
@@ -853,7 +860,7 @@ ShaderGenerator::processGeometry(const osg::StateSet*         original,
     {
         needNewStateSet = true;
         osg::StateAttribute::GLModeValue value = current->getMode(GL_LIGHTING);
-        newStateSet->addUniform( Registry::shaderFactory()->createUniformForGLMode(GL_LIGHTING, value) );
+        newStateSet->setDefine(OE_LIGHTING_DEFINE, value);
     }
     
     // start generating the shader source.
@@ -904,23 +911,37 @@ ShaderGenerator::processGeometry(const osg::StateSet*         original,
     {
         std::string version = GLSL_VERSION_STR;
 
-        std::string vertHeadSource;
-        vertHeadSource = buf._vertHead.str();
+        std::string modelHeadSource = buf._modelHead.str();
+        std::string modelBodySource = buf._modelBody.str();
+
+        if (!modelHeadSource.empty() && !modelBodySource.empty())
+        {
+            std::string modelSource = Stringify()
+                << "#version " << version << "\n" GLSL_PRECISION "\n"
+                << modelHeadSource
+                << "void " VERTEX_MODEL_FUNCTION "(inout vec4 vertex_model)\n{\n"
+                << modelBodySource
+                << "}\n";
+
+            vp->setFunction(VERTEX_MODEL_FUNCTION, modelSource, ShaderComp::LOCATION_VERTEX_MODEL, 0.5f);
+        }
 
-        std::string vertBodySource;
-        vertBodySource = buf._vertBody.str();
+        std::string viewHeadSource;
+        viewHeadSource = buf._viewHead.str();
 
+        std::string viewBodySource;
+        viewBodySource = buf._viewBody.str();
 
-        if ( !vertHeadSource.empty() || !vertBodySource.empty() )
+        if ( !viewHeadSource.empty() && !viewBodySource.empty() )
         {
-            std::string vertSource = Stringify()
+            std::string viewSource = Stringify()
                 << "#version " << version << "\n" GLSL_PRECISION "\n"
-                << vertHeadSource
-                << "void " VERTEX_FUNCTION "(inout vec4 vertex_view)\n{\n"
-                << vertBodySource
+                << viewHeadSource
+                << "void " VERTEX_VIEW_FUNCTION "(inout vec4 vertex_view)\n{\n"
+                << viewBodySource
                 << "}\n";
 
-            vp->setFunction(VERTEX_FUNCTION, vertSource, ShaderComp::LOCATION_VERTEX_VIEW, 0.5f);
+            vp->setFunction(VERTEX_VIEW_FUNCTION, viewSource, ShaderComp::LOCATION_VERTEX_VIEW, 0.5f);
         }
 
 
@@ -930,7 +951,7 @@ ShaderGenerator::processGeometry(const osg::StateSet*         original,
         std::string fragBodySource;
         fragBodySource = buf._fragBody.str();
 
-        if ( !fragHeadSource.empty() || !fragBodySource.empty() )
+        if ( !fragHeadSource.empty() && !fragBodySource.empty() )
         {
             std::string fragSource = Stringify()
                 << "#version " << version << "\n" GLSL_PRECISION "\n"
@@ -962,8 +983,9 @@ ShaderGenerator::apply(osg::Texture*     tex,
 {
    bool ok = true;
 
-   buf._vertHead << "varying " MEDIUMP "vec4 " TEX_COORD << unit << ";\n";
-   buf._fragHead << "varying " MEDIUMP "vec4 " TEX_COORD << unit << ";\n";
+   buf._modelHead << "out " MEDIUMP "vec4 " TEX_COORD << unit << ";\n";
+   buf._viewHead << "out " MEDIUMP "vec4 " TEX_COORD << unit << ";\n";
+   buf._fragHead << "in " MEDIUMP "vec4 " TEX_COORD << unit << ";\n";
 
    apply( texgen, unit, buf );
    apply( texmat, unit, buf );
@@ -1080,7 +1102,7 @@ ShaderGenerator::apply(osg::TexGen* texgen, int unit, GenBuffers& buf)
         switch( texgen->getMode() )
         {
         case osg::TexGen::OBJECT_LINEAR:
-            buf._vertBody
+            buf._modelBody
                 << INDENT "{\n"
                 << INDENT TEX_COORD << unit << " = "
                 <<      "gl_Vertex.x*gl_ObjectPlaneS[" <<unit<< "] + "
@@ -1091,7 +1113,7 @@ ShaderGenerator::apply(osg::TexGen* texgen, int unit, GenBuffers& buf)
             break;
 
         case osg::TexGen::EYE_LINEAR:
-            buf._vertBody
+            buf._viewBody
                 << INDENT "{\n"
                 << INDENT TEX_COORD << unit << " = "
                 <<      "vertex_view.x*gl_EyePlaneS[" <<unit<< "] + "
@@ -1102,9 +1124,9 @@ ShaderGenerator::apply(osg::TexGen* texgen, int unit, GenBuffers& buf)
             break;
 
         case osg::TexGen::SPHERE_MAP:
-            buf._vertHead
-                << "varying vec3 vp_Normal;\n";
-            buf._vertBody 
+            buf._viewHead
+                << "vec3 vp_Normal; // stage global\n";
+            buf._viewBody 
                 << INDENT "{\n" // scope it in case there are > 1
                 << INDENT "vec3 view_vec = normalize(vertex_view.xyz/vertex_view.w); \n"
                 << INDENT "vec3 r = reflect(view_vec, vp_Normal);\n"
@@ -1115,9 +1137,9 @@ ShaderGenerator::apply(osg::TexGen* texgen, int unit, GenBuffers& buf)
             break;
 
         case osg::TexGen::REFLECTION_MAP:
-            buf._vertHead
-                << "varying vec3 vp_Normal;\n";
-            buf._vertBody
+            buf._viewHead
+                << "vec3 vp_Normal; // stage global\n";
+            buf._viewBody
                 << INDENT "{\n"
                 << INDENT "vec3 view_vec = normalize(vertex_view.xyz/vertex_view.w);\n"
                 << INDENT TEX_COORD << unit << " = vec4(reflect(view_vec, vp_Normal), 1.0); \n"
@@ -1125,9 +1147,9 @@ ShaderGenerator::apply(osg::TexGen* texgen, int unit, GenBuffers& buf)
             break;
 
         case osg::TexGen::NORMAL_MAP:
-            buf._vertHead
-                << "varying vec3 vp_Normal;\n";
-            buf._vertBody
+            buf._viewHead
+                << "vec3 vp_Normal; //stage global\n";
+            buf._viewBody
                 << INDENT "{\n"
                 << INDENT TEX_COORD << unit << " = vec4(vp_Normal, 1.0); \n"
                 << INDENT "}\n";
@@ -1144,7 +1166,7 @@ ShaderGenerator::apply(osg::TexGen* texgen, int unit, GenBuffers& buf)
         // GLSL only supports built-in "gl_MultiTexCoord{0..7}"
         if ( unit <= 7 )
         {
-            buf._vertBody
+            buf._modelBody
                 << INDENT << TEX_COORD << unit << " = gl_MultiTexCoord" << unit << ";\n";
         }
         else
@@ -1154,7 +1176,7 @@ ShaderGenerator::apply(osg::TexGen* texgen, int unit, GenBuffers& buf)
                 << "requires a custom vertex attribute (osg_MultiTexCoord" << unit << ")."
                 << std::endl;
 
-            buf._vertBody 
+            buf._modelBody 
                 << INDENT << TEX_COORD << unit << " = osg_MultiTexCoord" << unit << ";\n";
         }
     }
@@ -1169,8 +1191,8 @@ ShaderGenerator::apply(osg::TexMat* texmat, int unit, GenBuffers& buf)
     {
         std::string texMatUniform = Stringify() << TEX_MATRIX << unit;
 
-        buf._vertHead << "uniform mat4 " << texMatUniform << ";\n";
-        buf._vertBody << INDENT << TEX_COORD << unit << " = " << texMatUniform << " * " << TEX_COORD<<unit << ";\n";
+        buf._viewHead << "uniform mat4 " << texMatUniform << ";\n";
+        buf._viewBody << INDENT << TEX_COORD << unit << " = " << texMatUniform << " * " << TEX_COORD<<unit << ";\n";
 
         buf._stateSet
             ->getOrCreateUniform(texMatUniform, osg::Uniform::FLOAT_MAT4)
@@ -1184,7 +1206,7 @@ bool
 ShaderGenerator::apply(osg::Texture1D* tex, int unit, GenBuffers& buf)
 {
     buf._fragHead << "uniform sampler1D " SAMPLER << unit << ";\n";
-    buf._fragBody << INDENT "texel = texture1D(" SAMPLER << unit << ", " TEX_COORD << unit << ".x);\n";
+    buf._fragBody << INDENT "texel = texture(" SAMPLER << unit << ", " TEX_COORD << unit << ".x);\n";
     buf._stateSet->getOrCreateUniform( Stringify() << SAMPLER << unit, osg::Uniform::SAMPLER_1D )->set( unit );
 
     return true;
@@ -1194,7 +1216,7 @@ bool
 ShaderGenerator::apply(osg::Texture2D* tex, int unit, GenBuffers& buf)
 {
     buf._fragHead << "uniform sampler2D " SAMPLER << unit << ";\n";
-    buf._fragBody << INDENT "texel = texture2D(" SAMPLER << unit << ", " TEX_COORD << unit << ".xy);\n";
+    buf._fragBody << INDENT "texel = texture(" SAMPLER << unit << ", " TEX_COORD << unit << ".xy);\n";
     buf._stateSet->getOrCreateUniform( Stringify() << SAMPLER << unit, osg::Uniform::SAMPLER_2D )->set( unit );
 
     return true;
@@ -1204,7 +1226,7 @@ bool
 ShaderGenerator::apply(osg::Texture3D* tex, int unit, GenBuffers& buf)
 {
     buf._fragHead << "uniform sampler3D " SAMPLER << unit << ";\n";
-    buf._fragBody << INDENT "texel = texture3D(" SAMPLER << unit << ", " TEX_COORD << unit << ".xyz);\n";
+    buf._fragBody << INDENT "texel = texture(" SAMPLER << unit << ", " TEX_COORD << unit << ".xyz);\n";
     buf._stateSet->getOrCreateUniform( Stringify() << SAMPLER << unit, osg::Uniform::SAMPLER_3D )->set( unit );
 
     return true;
@@ -1213,10 +1235,10 @@ ShaderGenerator::apply(osg::Texture3D* tex, int unit, GenBuffers& buf)
 bool
 ShaderGenerator::apply(osg::TextureRectangle* tex, int unit, GenBuffers& buf)
 {
-    buf._vertHead << "#extension GL_ARB_texture_rectangle : enable\n";
+    buf._viewHead << "#extension GL_ARB_texture_rectangle : enable\n";
 
     buf._fragHead << "uniform sampler2DRect " SAMPLER << unit << ";\n";
-    buf._fragBody << INDENT "texel = texture2DRect(" SAMPLER << unit << ", " TEX_COORD << unit << ".xy);\n";
+    buf._fragBody << INDENT "texel = texture(" SAMPLER << unit << ", " TEX_COORD << unit << ".xy);\n";
     buf._stateSet->getOrCreateUniform( Stringify() << SAMPLER << unit, osg::Uniform::SAMPLER_2D )->set( unit );
 
     return true;
@@ -1228,7 +1250,7 @@ ShaderGenerator::apply(osg::Texture2DArray* tex, int unit, GenBuffers& buf)
     buf._fragHead <<  "#extension GL_EXT_texture_array : enable \n";    
 
     buf._fragHead << "uniform sampler2DArray " SAMPLER << unit << ";\n";
-    buf._fragBody << INDENT "texel = texture2DArray(" SAMPLER << unit << ", " TEX_COORD << unit << ".xyz);\n";
+    buf._fragBody << INDENT "texel = texture(" SAMPLER << unit << ", " TEX_COORD << unit << ".xyz);\n";
     buf._stateSet->getOrCreateUniform( Stringify() << SAMPLER << unit, osg::Uniform::SAMPLER_2D_ARRAY )->set( unit );         
 
     return true;
@@ -1239,7 +1261,7 @@ ShaderGenerator::apply(osg::TextureCubeMap* tex, int unit, GenBuffers& buf)
 {
     std::string sampler = Stringify() << SAMPLER << unit;
     buf._fragHead << "uniform samplerCube " << sampler << ";\n";
-    buf._fragBody << INDENT "texel = textureCube(" << sampler << ", " TEX_COORD << unit << ".xyz);\n";
+    buf._fragBody << INDENT "texel = texture(" << sampler << ", " TEX_COORD << unit << ".xyz);\n";
     buf._stateSet->getOrCreateUniform( sampler, osg::Uniform::SAMPLER_CUBE )->set( unit );         
 
     return true;
@@ -1252,7 +1274,7 @@ ShaderGenerator::apply(osg::PointSprite* tex, int unit, GenBuffers& buf)
 
     std::string sampler = Stringify() << SAMPLER << unit;
     buf._fragHead << "uniform sampler2D " << sampler << ";\n";
-    buf._fragBody << INDENT << "texel = texture2D(" << sampler << ", gl_PointCoord);\n";
+    buf._fragBody << INDENT << "texel = texture(" << sampler << ", gl_PointCoord);\n";
     buf._stateSet->getOrCreateUniform( sampler, osg::Uniform::SAMPLER_2D )->set( unit );
 
     return true;
@@ -1281,3 +1303,19 @@ ShaderGenerator::apply(osg::StateAttribute* attr, GenBuffers& buf)
     // NOP for now.
     return false;
 }
+
+void
+ShaderGenerator::disableUnsupportedAttributes(osg::StateSet* stateset)
+{
+    if (!stateset)
+        return;
+
+#if 0
+#if !defined(OSG_GL_FIXED_FUNCTION_AVAILABLE)
+    stateset->removeAttribute(osg::StateAttribute::TEXENV);
+    stateset->removeAttribute(osg::StateAttribute::TEXENVFILTER);
+    stateset->removeAttribute(osg::StateAttribute::TEXGEN);
+    stateset->removeAttribute(osg::StateAttribute::LIGHTMODEL);
+#endif
+#endif
+}
diff --git a/src/osgEarth/ShaderLoader b/src/osgEarth/ShaderLoader
index 731e6b6..e51b85c 100644
--- a/src/osgEarth/ShaderLoader
+++ b/src/osgEarth/ShaderLoader
@@ -162,6 +162,10 @@ namespace osgEarth
             const std::string&     source,
             const std::string&     key,
             std::set<std::string>& output);
+
+        static void split(
+            const std::string& multisource,
+            std::vector<std::string>& out_sources);
     };
 
 } // namespace osgEarth
diff --git a/src/osgEarth/ShaderLoader.cpp b/src/osgEarth/ShaderLoader.cpp
index 95d419e..9cccb9c 100644
--- a/src/osgEarth/ShaderLoader.cpp
+++ b/src/osgEarth/ShaderLoader.cpp
@@ -173,62 +173,65 @@ ShaderLoader::load(const std::string&    filename,
         osgEarth::replaceIn(output, statement, fileSource);
     }
 
-    // Process any "#pragma define" statements
-    while(true)
-    {
-        const std::string token("#pragma vp_define");
-        std::string::size_type statementPos = output.find(token);
-        if ( statementPos == std::string::npos )
-            break;
+// Process any "#pragma define" statements
+while (true)
+{
+    const std::string token("#pragma vp_define");
+    std::string::size_type statementPos = output.find(token);
+    if (statementPos == std::string::npos)
+        break;
 
-        std::string::size_type startPos = output.find_first_not_of(" \t", statementPos+token.length());
-        if ( startPos == std::string::npos )
-            break;
+    std::string::size_type startPos = output.find_first_not_of(" \t", statementPos + token.length());
+    if (startPos == std::string::npos)
+        break;
 
-        std::string::size_type endPos = output.find('\n', startPos);
-        if ( endPos == std::string::npos )
-            break;
+    std::string::size_type endPos = output.find('\n', startPos);
+    if (endPos == std::string::npos)
+        break;
 
-        std::string statement( output.substr(statementPos, endPos-statementPos) );
-        std::string varName( trim(output.substr(startPos, endPos-startPos)) );
+    std::string statement(output.substr(statementPos, endPos - statementPos));
+    std::string varName(trim(output.substr(startPos, endPos - startPos)));
 
-        ShaderPackage::DefineMap::const_iterator d = package._defines.find( varName );
+    ShaderPackage::DefineMap::const_iterator d = package._defines.find(varName);
 
-        bool defineIt =
-            d != package._defines.end() &&
-            d->second == true;
+    bool defineIt =
+        d != package._defines.end() &&
+        d->second == true;
 
-        std::string newStatement = Stringify()
-            << (defineIt? "#define " : "#undef ")
-            << varName;
+    std::string newStatement = Stringify()
+        << (defineIt ? "#define " : "#undef ")
+        << varName;
 
-        osgEarth::replaceIn( output, statement, newStatement );
-    }
+    osgEarth::replaceIn(output, statement, newStatement);
+}
 
-    // Finally, process any replacements.
-    for(ShaderPackage::ReplaceMap::const_iterator i = package._replaces.begin();
-        i != package._replaces.end();
-        ++i)
-    {
-        osgEarth::replaceIn( output, i->first, i->second );
-    }
+// Process any replacements.
+for (ShaderPackage::ReplaceMap::const_iterator i = package._replaces.begin();
+    i != package._replaces.end();
+    ++i)
+{
+    osgEarth::replaceIn(output, i->first, i->second);
+}
 
-    return output;
+// Lastly, remove any CRs
+osgEarth::replaceIn(output, "\r", "");
+
+return output;
 }
 
 std::string
 ShaderLoader::load(const std::string&    filename,
                    const std::string&    inlineSource,
-                   const osgDB::Options* dbOptions )
+                   const osgDB::Options* dbOptions)
 {
     std::string output;
     bool useInlineSource = false;
 
-    URIContext context( dbOptions );
-    URI uri(filename, context );
+    URIContext context(dbOptions);
+    URI uri(filename, context);
 
     std::string path = osgDB::findDataFile(filename, dbOptions);
-    if ( path.empty() )
+    if (path.empty())
     {
         output = inlineSource;
         useInlineSource = true;
@@ -253,16 +256,35 @@ ShaderLoader::load(const std::string&    filename,
     osgEarth::replaceIn(output, "$GLSL_DEFAULT_PRECISION_FLOAT", GLSL_DEFAULT_PRECISION_FLOAT);
 
     // If we're using inline source, we have to post-process the string.
-    if ( useInlineSource )
+    if (useInlineSource)
     {
         // Replace tokens inserted in the CMakeModules/ConfigureShaders.cmake.in script.
-        osgEarth::replaceIn(output, "%EOL%",   "\n");
+        osgEarth::replaceIn(output, "%EOL%", "\n");
         osgEarth::replaceIn(output, "%QUOTE%", "\"");
     }
 
+    // Lastly, remove any CRs
+    osgEarth::replaceIn(output, "\r", "");
+
     return output;
 }
 
+void
+ShaderLoader::split(const std::string& multisource,
+                    std::vector<std::string>& output)
+{
+#define SPLIT_DELIM "[break]"
+#define SPLIT_DELIM_LEN 7
+    std::string::size_type offset = 0, pos = 0;
+    while ((pos = multisource.find(SPLIT_DELIM, offset)) != std::string::npos)
+    {
+        std::string source = multisource.substr(offset, pos-offset);
+        output.push_back(source);
+        offset = pos + SPLIT_DELIM_LEN;
+    }
+    output.push_back(multisource.substr(offset));
+}
+
 bool
 ShaderLoader::load(VirtualProgram*       vp,
                    const std::string&    filename,
@@ -275,96 +297,106 @@ ShaderLoader::load(VirtualProgram*       vp,
         return false;
     }
 
-    std::string source = load(filename, package, dbOptions);
-    if ( source.empty() )
+    // load the source string:
+    std::string multisource = load(filename, package, dbOptions);
+    if ( multisource.empty() )
     {
         OE_WARN << LC << "Failed to load shader source from \"" << filename << "\"\n";
         return false;
     }
 
-    // Remove the quotation marks from the source since they are illegal in GLSL
-    replaceIn( source, "\"", " ");
-
-    std::string loc = getPragmaValue(source, "vp_location");
-    ShaderComp::FunctionLocation location;
-    bool locationSet = true;
-
-    if      ( ciEquals(loc, "vertex_model") )
-        location = ShaderComp::LOCATION_VERTEX_MODEL;
-    else if ( ciEquals(loc, "vertex_view") )
-        location = ShaderComp::LOCATION_VERTEX_VIEW;
-    else if ( ciEquals(loc, "vertex_clip") )
-        location = ShaderComp::LOCATION_VERTEX_CLIP;
-    else if ( ciEquals(loc, "tess_control") || ciEquals(loc, "tessellation_control") )
-        location = ShaderComp::LOCATION_TESS_CONTROL;
-    else if ( ciEquals(loc, "tess_eval") || ciEquals(loc, "tessellation_eval") || ciEquals(loc, "tessellation_evaluation") || ciEquals(loc, "tess_evaluation") )
-        location = ShaderComp::LOCATION_TESS_EVALUATION;
-    else if ( ciEquals(loc, "vertex_geometry") || ciEquals(loc, "geometry") )
-        location = ShaderComp::LOCATION_GEOMETRY;
-    else if ( ciEquals(loc, "fragment" ) )
-        location = ShaderComp::LOCATION_FRAGMENT_COLORING;
-    else if ( ciEquals(loc, "fragment_coloring") )
-        location = ShaderComp::LOCATION_FRAGMENT_COLORING;
-    else if ( ciEquals(loc, "fragment_lighting") )
-        location = ShaderComp::LOCATION_FRAGMENT_LIGHTING;
-    else if ( ciEquals(loc, "fragment_output") )
-        location = ShaderComp::LOCATION_FRAGMENT_OUTPUT;
-    else
-    {
-        locationSet = false;
-    }
-
-    // If entry point is set, this is a function; otherwise a simple library.
-    std::string entryPoint = getPragmaValue(source, "vp_entryPoint");
+    // split the multisource string into one or more shader sources:
+    std::vector<std::string> sources;
+    split(multisource, sources);
 
-    // order is optional.
-    std::string orderStr = getPragmaValue(source, "vp_order");
-
-    if ( !entryPoint.empty() )
+    for (unsigned i = 0; i < sources.size(); ++i)
     {
-        if ( !locationSet )
+        std::string source = sources[i];
+
+        // Remove the quotation marks from the source since they are illegal in GLSL
+        replaceIn( source, "\"", " ");
+
+        std::string loc = getPragmaValue(source, "vp_location");
+        ShaderComp::FunctionLocation location;
+        bool locationSet = true;
+
+        if      ( ciEquals(loc, "vertex_model") )
+            location = ShaderComp::LOCATION_VERTEX_MODEL;
+        else if ( ciEquals(loc, "vertex_view") )
+            location = ShaderComp::LOCATION_VERTEX_VIEW;
+        else if ( ciEquals(loc, "vertex_clip") )
+            location = ShaderComp::LOCATION_VERTEX_CLIP;
+        else if ( ciEquals(loc, "tess_control") || ciEquals(loc, "tessellation_control") )
+            location = ShaderComp::LOCATION_TESS_CONTROL;
+        else if ( ciEquals(loc, "tess_eval") || ciEquals(loc, "tessellation_eval") || ciEquals(loc, "tessellation_evaluation") || ciEquals(loc, "tess_evaluation") )
+            location = ShaderComp::LOCATION_TESS_EVALUATION;
+        else if ( ciEquals(loc, "vertex_geometry") || ciEquals(loc, "geometry") )
+            location = ShaderComp::LOCATION_GEOMETRY;
+        else if ( ciEquals(loc, "fragment" ) )
+            location = ShaderComp::LOCATION_FRAGMENT_COLORING;
+        else if ( ciEquals(loc, "fragment_coloring") )
+            location = ShaderComp::LOCATION_FRAGMENT_COLORING;
+        else if ( ciEquals(loc, "fragment_lighting") )
+            location = ShaderComp::LOCATION_FRAGMENT_LIGHTING;
+        else if ( ciEquals(loc, "fragment_output") )
+            location = ShaderComp::LOCATION_FRAGMENT_OUTPUT;
+        else
         {
-            OE_WARN << LC << "Illegal: shader \"" << filename << "\" has invalid #pragma vp_location when vp_entryPoint is set\n";
-            return false;
+            locationSet = false;
         }
 
-        float order;
-        if ( ciEquals(orderStr, "FLT_MAX") || ciEquals(orderStr, "last") )
-            order = FLT_MAX;
-        else if ( ciEquals(orderStr, "-FLT_MAX") || ciEquals(orderStr, "first") )
-            order = -FLT_MAX;
-        else
-            order = as<float>(orderStr, 1.0f);
+        // If entry point is set, this is a function; otherwise a simple library.
+        std::string entryPoint = getPragmaValue(source, "vp_entryPoint");
 
-        // set the function!
-        vp->setFunction( entryPoint, source, location, 0L, order );
-    }
+        // order is optional.
+        std::string orderStr = getPragmaValue(source, "vp_order");
 
-    else
-    {
-        // install as a simple shader.
-        if ( locationSet )
+        if ( !entryPoint.empty() )
         {
-            // If a location is set, install in that location only
-            osg::Shader::Type type =
-                location == ShaderComp::LOCATION_VERTEX_MODEL || location == ShaderComp::LOCATION_VERTEX_VIEW || location == ShaderComp::LOCATION_VERTEX_CLIP ? osg::Shader::VERTEX :
-                osg::Shader::FRAGMENT;
-
-            osg::Shader* shader = new osg::Shader(type, source);
-            shader->setName( filename );
-            vp->setShader( filename, shader );
+            if ( !locationSet )
+            {
+                OE_WARN << LC << "Illegal: shader \"" << filename << "\" has invalid #pragma vp_location when vp_entryPoint is set\n";
+                return false;
+            }
+
+            float order;
+            if ( ciEquals(orderStr, "FLT_MAX") || ciEquals(orderStr, "last") )
+                order = FLT_MAX;
+            else if ( ciEquals(orderStr, "-FLT_MAX") || ciEquals(orderStr, "first") )
+                order = -FLT_MAX;
+            else
+                order = as<float>(orderStr, 1.0f);
+
+            // set the function!
+            vp->setFunction( entryPoint, source, location, 0L, order );
         }
 
         else
         {
-            // If no location was set, install in all stages.
-            osg::Shader::Type types[5] = { osg::Shader::VERTEX, osg::Shader::FRAGMENT, osg::Shader::GEOMETRY, osg::Shader::TESSCONTROL, osg::Shader::TESSEVALUATION };
-            for(int i=0; i<5; ++i)
+            // install as a simple shader.
+            if ( locationSet )
             {
-                osg::Shader* shader = new osg::Shader(types[i], source);
-                std::string name = Stringify() << filename + "_" + shader->getTypename();
-                shader->setName( name );
-                vp->setShader( name, shader );
+                // If a location is set, install in that location only
+                osg::Shader::Type type =
+                    location == ShaderComp::LOCATION_VERTEX_MODEL || location == ShaderComp::LOCATION_VERTEX_VIEW || location == ShaderComp::LOCATION_VERTEX_CLIP ? osg::Shader::VERTEX :
+                    osg::Shader::FRAGMENT;
+
+                osg::Shader* shader = new osg::Shader(type, source);
+                shader->setName( filename );
+                vp->setShader( filename, shader );
+            }
+
+            else
+            {
+                // If no location was set, install in all stages.
+                osg::Shader::Type types[5] = { osg::Shader::VERTEX, osg::Shader::FRAGMENT, osg::Shader::GEOMETRY, osg::Shader::TESSCONTROL, osg::Shader::TESSEVALUATION };
+                for(int i=0; i<5; ++i)
+                {
+                    osg::Shader* shader = new osg::Shader(types[i], source);
+                    std::string name = Stringify() << filename + "_" + shader->getTypename();
+                    shader->setName( name );
+                    vp->setShader( name, shader );
+                }
             }
         }
     }
@@ -383,23 +415,41 @@ ShaderLoader::unload(VirtualProgram*       vp,
         // fail quietly
         return false;
     }
-
-    std::string source = load(filename, package, dbOptions);
-    if ( source.empty() )
+    
+    // load the source string:
+    std::string multisource = load(filename, package, dbOptions);
+    if ( multisource.empty() )
     {
-        OE_WARN << LC << "Failed to unload shader source from \"" << filename << "\"\n";
+        OE_WARN << LC << "Failed to load shader source from \"" << filename << "\"\n";
         return false;
     }
 
-    std::string entryPoint = getPragmaValue(source, "vp_entryPoint");
-    if ( !entryPoint.empty() )
-    {
-        vp->removeShader( entryPoint );
-    }
-    else
+    // split the multisource string into one or more shader sources:
+    std::vector<std::string> sources;
+    split(multisource, sources);
+
+    for (unsigned i = 0; i < sources.size(); ++i)
     {
-        vp->removeShader( filename );
+        const std::string& source = sources[i];
+
+        //std::string source = load(filename, package, dbOptions);
+        //if ( source.empty() )
+        //{
+        //    OE_WARN << LC << "Failed to unload shader source from \"" << filename << "\"\n";
+        //    return false;
+        //}
+
+        std::string entryPoint = getPragmaValue(source, "vp_entryPoint");
+        if ( !entryPoint.empty() )
+        {
+            vp->removeShader( entryPoint );
+        }
+        else
+        {
+            vp->removeShader( filename );
+        }
     }
+
     return true;
 }
 
@@ -457,4 +507,4 @@ ShaderPackage::unloadAll(VirtualProgram*       vp,
         oks += unload( vp, i->first ) ? 1 : 0;
     }
     return oks == _sources.size();
-}
\ No newline at end of file
+}
diff --git a/src/osgEarth/ShaderUtils b/src/osgEarth/ShaderUtils
index 4a948d3..930d791 100644
--- a/src/osgEarth/ShaderUtils
+++ b/src/osgEarth/ShaderUtils
@@ -43,50 +43,6 @@ namespace osgEarth
     };
 
     /**
-    * Container for light uniforms
-    */
-    //light product
-    struct osg_LightProducts 
-    { 
-        osg_LightProducts(int id);
-        
-        osg::ref_ptr<osg::Uniform> ambient; // vec4 
-        osg::ref_ptr<osg::Uniform> diffuse; // vec4
-        osg::ref_ptr<osg::Uniform> specular; //vec4
-
-        // GLSL strings
-        static std::string glslDefinition();
-    };
-
-
-    struct osg_LightSourceParameters 
-    { 
-        osg_LightSourceParameters(int id);
-        
-        void setUniformsFromOsgLight(const osg::Light* light, osg::Matrix viewMatrix, const osg::Material* frontMat);
-        void applyState(osg::StateSet* stateset);
-        
-        osg::ref_ptr<osg::Uniform>  ambient; // vec4
-        osg::ref_ptr<osg::Uniform>  diffuse; // vec4 
-        osg::ref_ptr<osg::Uniform>  specular; // vec4
-        osg::ref_ptr<osg::Uniform>  position; // vec4
-        osg::ref_ptr<osg::Uniform>  halfVector; // vec4 
-        osg::ref_ptr<osg::Uniform>  spotDirection; // vec3 
-        osg::ref_ptr<osg::Uniform>  spotExponent; // float
-        osg::ref_ptr<osg::Uniform>  spotCutoff; // float
-        osg::ref_ptr<osg::Uniform>  spotCosCutoff; // float
-        osg::ref_ptr<osg::Uniform>  constantAttenuation; // float 
-        osg::ref_ptr<osg::Uniform>  linearAttenuation; // float
-        osg::ref_ptr<osg::Uniform>  quadraticAttenuation; // float
-
-        //just store the light product in here
-        osg_LightProducts _frontLightProduct;
-
-        // GLSL strings
-        static std::string glslDefinition();
-    };
-
-    /**
      * Preprocesses GLES shader source to include our osg_LightProducts and osg_LightSourceParameters
      * definitions and uniforms.
      */
@@ -99,38 +55,6 @@ namespace osgEarth
     };
 
     /**
-     * A callback that will update the osgEarth lighting uniforms (based on the
-     * FFP lighting state) if necessary.
-     */
-    class OSGEARTH_EXPORT UpdateLightingUniformsHelper : public osg::NodeCallback
-    {
-    public:
-        UpdateLightingUniformsHelper( bool useUpdateTraversal =false );
-        virtual ~UpdateLightingUniformsHelper();
-
-        void cullTraverse( osg::Node* node, osg::NodeVisitor* nv );
-        void updateTraverse( osg::Node* node );
-
-    public: // NodeCallback
-        // for use as a cull callback.
-        virtual void operator()(osg::Node*, osg::NodeVisitor* nv);
-
-    protected:
-        int   _maxLights;
-        //bool* _lightEnabled;
-        //bool  _lightingEnabled;
-        bool  _dirty;
-        bool  _applied;
-        bool  _useUpdateTrav;
-        OpenThreads::Mutex _stateSetMutex;
-
-        //osg::ref_ptr<osg::Uniform> _lightingEnabledUniform;
-        osg::ref_ptr<osg::Uniform> _lightEnabledUniform;
-         
-        std::vector<osg_LightSourceParameters>   _osgLightSourceParameters; 
-    };
-
-    /**
      * Helper class for dealing with array uniforms. Array uniform naming works
      * differently on different drivers (ATI vs NVIDIA), so this class helps mitigate
      * those differences.
diff --git a/src/osgEarth/ShaderUtils.cpp b/src/osgEarth/ShaderUtils.cpp
index fe37d34..8cdf256 100644
--- a/src/osgEarth/ShaderUtils.cpp
+++ b/src/osgEarth/ShaderUtils.cpp
@@ -25,6 +25,7 @@
 #include <osgEarth/URI>
 #include <osgEarth/GLSLChunker>
 #include <osg/ComputeBoundsVisitor>
+#include <osg/LightSource>
 #include <osgDB/FileUtils>
 #include <list>
 
@@ -203,7 +204,7 @@ namespace
             if ( !tokens[i].empty() )
             {
                 int len = tokens[i].length();
-                if ( tokens[i].at(len-1) == ';' )
+                if ( tokens[i][len-1] == ';' )
                     buf << " " << tokens[i].substr(0, len-1); // strip semicolon
                 else
                     buf << " " << tokens[i];
@@ -260,7 +261,7 @@ namespace
 
     void applySupportForNoFFPImpl(GLSLChunker::Chunks& chunks)
     {
-#if !defined(OSG_GL_FIXED_FUNCTION_AVAILABLE)
+#if !defined(OSG_GL_FIXED_FUNCTION_AVAILABLE) && !defined(OSG_GLES3_AVAILABLE) //osg state convertVertexShaderSourceToOsgBuiltIns inserts these and the double declaration is causing an error in gles
 
         // for geometry and tessellation shaders, replace the built-ins with 
         // osg uniform aliases.
@@ -275,7 +276,8 @@ namespace
 
         for (GLSLChunker::Chunks::iterator chunk = chunks.begin(); chunk != chunks.end(); ++chunk)
         {
-            if (chunk->type != GLSLChunker::Chunk::TYPE_DIRECTIVE)
+            if (chunk->type != GLSLChunker::Chunk::TYPE_DIRECTIVE ||
+                (chunk->tokens.size()>0 && chunk->tokens[0].compare(0, 3, "#if")==0))
             {
                 for (unsigned line = 0; line < 4; ++line) {
                     chunk = chunks.insert(chunk, chunker.chunkLine(lines[line]));
@@ -322,7 +324,6 @@ ShaderPreProcessor::run(osg::Shader* shader)
     {
         bool dirty = false;
 
-        // only runs for non-FFP (GLES, GL3+, etc.)
         std::string source = shader->getShaderSource();
 
         // First replace any quotes with spaces. Quotes are illegal.
@@ -332,50 +333,6 @@ ShaderPreProcessor::run(osg::Shader* shader)
             dirty = true;
         }
 
-        // find the first legal insertion point for replacement declarations. GLSL requires that nothing
-        // precede a "#version" compiler directive, so we must insert new declarations after it.
-        std::string::size_type declPos = source.rfind( "#version " );
-        if ( declPos != std::string::npos )
-        {
-            // found the string, now find the next linefeed and set the insertion point after it.
-            declPos = source.find( '\n', declPos );
-            declPos = declPos != std::string::npos ? declPos+1 : source.length();
-        }
-        else
-        {
-            declPos = 0;
-        }
-
-#if !defined(OSG_GL_FIXED_FUNCTION_AVAILABLE)
-
-        int maxLights = Registry::capabilities().getMaxLights();
-
-        for( int i=0; i<maxLights; ++i )
-        {
-            if ( replaceAndInsertDeclaration(
-                source, declPos,
-                Stringify() << "gl_LightSource[" << i << "]",
-                Stringify() << "osg_LightSource" << i,
-                Stringify() 
-                    << osg_LightSourceParameters::glslDefinition() << "\n"
-                    << "uniform osg_LightSourceParameters " ) )
-            {
-                dirty = true;
-            }
-
-            if ( replaceAndInsertDeclaration(
-                source, declPos,
-                Stringify() << "gl_FrontLightProduct[" << i << "]", 
-                Stringify() << "osg_FrontLightProduct" << i,
-                Stringify()
-                    << osg_LightProducts::glslDefinition() << "\n"
-                    << "uniform osg_LightProducts " ) )
-            {
-                dirty = true;
-            }
-        }
-#endif
-
         // Chunk the shader.
         GLSLChunker chunker;
         GLSLChunker::Chunks chunks;
@@ -388,323 +345,11 @@ ShaderPreProcessor::run(osg::Shader* shader)
         replaceVaryings( shader->getType(), chunks );
         chunker.write( chunks, source );
         shader->setShaderSource( source );
-    }
-}
-
-//------------------------------------------------------------------------
-
-osg_LightProducts::osg_LightProducts(int id)
-{
-    std::stringstream uniNameStream;
-    uniNameStream << "osg_FrontLightProduct" << id; //[" << id << "]";
-    std::string uniName = uniNameStream.str();
-
-    ambient = new osg::Uniform(osg::Uniform::FLOAT_VEC4, uniName+".ambient"); // vec4
-    diffuse = new osg::Uniform(osg::Uniform::FLOAT_VEC4, uniName+".diffuse"); // vec4
-    specular = new osg::Uniform(osg::Uniform::FLOAT_VEC4, uniName+".specular"); // vec4
-}
-
-std::string 
-osg_LightProducts::glslDefinition()
-{
-    //Note: it's important that there be NO linefeeds in here, since that would
-    // break the shader merging code.
-    return
-        "struct osg_LightProducts {"
-        " vec4 ambient;"
-        " vec4 diffuse;"
-        " vec4 specular;"
-        " };";
-}
-
-//------------------------------------------------------------------------
-
-osg_LightSourceParameters::osg_LightSourceParameters(int id)
-    : _frontLightProduct(id)
-{
-    std::stringstream uniNameStream;
-    uniNameStream << "osg_LightSource" << id; // [" << id << "]";
-    std::string uniName = uniNameStream.str();
-    
-    ambient = new osg::Uniform(osg::Uniform::FLOAT_VEC4, uniName+".ambient"); // vec4
-    diffuse = new osg::Uniform(osg::Uniform::FLOAT_VEC4, uniName+".diffuse"); // vec4
-    specular = new osg::Uniform(osg::Uniform::FLOAT_VEC4, uniName+".specular"); // vec4
-    position = new osg::Uniform(osg::Uniform::FLOAT_VEC4, uniName+".position"); // vec4
-    halfVector = new osg::Uniform(osg::Uniform::FLOAT_VEC4, uniName+".halfVector"); // vec4
-    spotDirection = new osg::Uniform(osg::Uniform::FLOAT_VEC3, uniName+".spotDirection"); // vec3
-    spotExponent = new osg::Uniform(osg::Uniform::FLOAT, uniName+".spotExponent"); // float
-    spotCutoff = new osg::Uniform(osg::Uniform::FLOAT, uniName+".spotCutoff"); // float
-    spotCosCutoff = new osg::Uniform(osg::Uniform::FLOAT, uniName+".spotCosCutoff"); // float
-    constantAttenuation = new osg::Uniform(osg::Uniform::FLOAT, uniName+".constantAttenuation"); // float
-    linearAttenuation = new osg::Uniform(osg::Uniform::FLOAT, uniName+".linearAttenuation"); // float
-    quadraticAttenuation = new osg::Uniform(osg::Uniform::FLOAT, uniName+".quadraticAttenuation"); // float
-}
-
-void osg_LightSourceParameters::setUniformsFromOsgLight(const osg::Light* light, osg::Matrix viewMatrix, const osg::Material* frontMat)
-{
-    if(light){
-        ambient->set(light->getAmbient());
-        diffuse->set(light->getDiffuse());
-        specular->set(light->getSpecular());
-        
-        osg::Vec4 eyeLightPos = light->getPosition()*viewMatrix;
-        position->set(eyeLightPos);
-       
-        // compute half vec
-        osg::Vec4 normPos = eyeLightPos;
-        normPos.normalize();
-        osg::Vec4 halfVec4 = normPos + osg::Vec4(0,0,1,0);
-        halfVec4.normalize();
-        halfVector->set(halfVec4);
-        
-        spotDirection->set(light->getDirection()*viewMatrix);
-        spotExponent->set(light->getSpotExponent());
-        spotCutoff->set(light->getSpotCutoff());
-        //need to compute cosCutOff
-        //spotCosCutoff->set(light->get)
-        constantAttenuation->set(light->getConstantAttenuation());
-        linearAttenuation->set(light->getLinearAttenuation());
-        quadraticAttenuation->set(light->getQuadraticAttenuation());
-        
-        //front product
-        if(frontMat){
-             osg::Vec4 frontAmbient = frontMat->getAmbient(osg::Material::FRONT);
-             osg::Vec4 frontDiffuse = frontMat->getDiffuse(osg::Material::FRONT);
-             osg::Vec4 frontSpecular = frontMat->getSpecular(osg::Material::FRONT);
-            _frontLightProduct.ambient->set(osg::Vec4(light->getAmbient().x() * frontAmbient.x(),
-                                                      light->getAmbient().y() * frontAmbient.y(),
-                                                      light->getAmbient().z() * frontAmbient.z(),
-                                                      light->getAmbient().w() * frontAmbient.w()));
-            
-            _frontLightProduct.diffuse->set(osg::Vec4(light->getDiffuse().x() * frontDiffuse.x(),
-                                                      light->getDiffuse().y() * frontDiffuse.y(),
-                                                      light->getDiffuse().z() * frontDiffuse.z(),
-                                                      light->getDiffuse().w() * frontDiffuse.w()));
-            
-            _frontLightProduct.specular->set(osg::Vec4(light->getSpecular().x() * frontSpecular.x(),
-                                                      light->getSpecular().y() * frontSpecular.y(),
-                                                      light->getSpecular().z() * frontSpecular.z(),
-                                                      light->getSpecular().w() * frontSpecular.w()));
-        }
-        else {
-            _frontLightProduct.ambient->set(osg::Vec4(light->getAmbient().x(),
-                                                      light->getAmbient().y(),
-                                                      light->getAmbient().z(),
-                                                      light->getAmbient().w()));
-            
-            _frontLightProduct.diffuse->set(osg::Vec4(light->getDiffuse().x(),
-                                                      light->getDiffuse().y(),
-                                                      light->getDiffuse().z(),
-                                                      light->getDiffuse().w()));
-            
-            _frontLightProduct.specular->set(osg::Vec4(light->getSpecular().x(),
-                                                      light->getSpecular().y(),
-                                                      light->getSpecular().z(),
-                                                      light->getSpecular().w()));
-        }
-    }
-}
-
-void osg_LightSourceParameters::applyState(osg::StateSet* stateset)
-{
-    stateset->addUniform(ambient.get());
-    stateset->addUniform(diffuse.get());
-    stateset->addUniform(specular.get());
-    stateset->addUniform(position.get());
-    stateset->addUniform(halfVector.get());
-    stateset->addUniform(spotDirection.get());
-    stateset->addUniform(spotExponent.get());
-    stateset->addUniform(spotCutoff.get());
-    stateset->addUniform(spotCosCutoff.get());
-    stateset->addUniform(constantAttenuation.get());
-    stateset->addUniform(linearAttenuation.get());
-    stateset->addUniform(quadraticAttenuation.get());
-    
-    //apply front light product
-    stateset->addUniform(_frontLightProduct.ambient.get());
-    stateset->addUniform(_frontLightProduct.diffuse.get());
-    stateset->addUniform(_frontLightProduct.specular.get());
-}
-
-std::string
-osg_LightSourceParameters::glslDefinition()
-{
-    //Note: it's important that there be NO linefeeds in here, since that would
-    // break the shader merging code.
-    return
-        "struct osg_LightSourceParameters {"
-        " vec4 ambient;"
-        " vec4 diffuse;"
-        " vec4 specular;"
-        " vec4 position;"
-        " vec4 halfVector;"
-        " vec3 spotDirection;"
-        " float spotExponent;"
-        " float spotCutoff;"
-        " float spotCosCutoff;"
-        " float constantAttenuation;"
-        " float linearAttenuation;"
-        " float quadraticAttenuation;"
-        " };";
-}
-
-//------------------------------------------------------------------------
-
-
-#undef LC
-#define LC "[UpdateLightingUniformHelper] "
-
-UpdateLightingUniformsHelper::UpdateLightingUniformsHelper( bool useUpdateTrav ) :
-_dirty          ( true ),
-_applied        ( false ),
-_useUpdateTrav  ( useUpdateTrav )
-{
-    _maxLights = Registry::instance()->getCapabilities().getMaxLights();
-    for(int i=0; i<_maxLights; ++i )
-    {
-        _osgLightSourceParameters.push_back(osg_LightSourceParameters(i));
-    }
-}
-
-UpdateLightingUniformsHelper::~UpdateLightingUniformsHelper()
-{
-    //nop
-}
-
-void
-UpdateLightingUniformsHelper::cullTraverse( osg::Node* node, osg::NodeVisitor* nv )
-{
-    osgUtil::CullVisitor* cv = static_cast<osgUtil::CullVisitor*>(nv);
-    if ( cv )
-    {
-        StateSetStack stateSetStack;
-
-        if ( node->getStateSet() )
-            stateSetStack.push_front( node->getStateSet() );
-
-        osgUtil::StateGraph* sg = cv->getCurrentStateGraph();
-        while( sg )
-        {
-            const osg::StateSet* stateset = sg->getStateSet();
-            if (stateset)
-            {
-                stateSetStack.push_front(stateset);
-            }                
-            sg = sg->_parent;
-        }
-
-#if 0
-        // Update the overall lighting-enabled value:
-        bool lightingEnabled =
-            ( getModeValue(stateSetStack, GL_LIGHTING) & osg::StateAttribute::ON ) != 0;
-
-        if ( lightingEnabled != _lightingEnabled || !_applied )
-        {
-            _lightingEnabled = lightingEnabled;
-            if ( _useUpdateTrav )
-                _dirty = true;
-            else
-                _lightingEnabledUniform->set( _lightingEnabled );
-        }
-#endif
-
-        osg::View* view = cv->getCurrentCamera()->getView();
-        if ( view )
-        {
-            osg::Light* light = view->getLight();
-            if ( light )
-            {
-                const osg::Material* material = getFrontMaterial(stateSetStack);
-                _osgLightSourceParameters[0].setUniformsFromOsgLight(light, cv->getCurrentCamera()->getViewMatrix(), material);
-            }
-        }
-
-#if 0
-        else
-        {
-            // Update the list of enabled lights:
-            for( int i=0; i < _maxLights; ++i )
-            {
-                bool enabled =
-                    ( getModeValue( stateSetStack, GL_LIGHT0 + i ) & osg::StateAttribute::ON ) != 0;
-                
-                const osg::Light* light = getLightByID(stateSetStack, i);
-                const osg::Material* material = getFrontMaterial(stateSetStack);
-
-                if ( light )
-                {
-                    OE_NOTICE << "Found Light " << i << std::endl;
-                }
-
-                if ( _lightEnabled[i] != enabled || !_applied )
-                {
-                    _lightEnabled[i] = enabled;
-                    if ( _useUpdateTrav ){
-                        _dirty = true;
-                    }else{
-                        _lightEnabledUniform->setElement( i, _lightEnabled[i] );
-                    }
-                }
-                
-                //update light position info regardsless of if applied for now
-                if(light){
-                    OE_NOTICE << "Setting light source params." << std::endl;
-                    _osgLightSourceParameters[i].setUniformsFromOsgLight(light, cv->getCurrentCamera()->getViewMatrix(), material);
-                }
-            }	
-        }
-#endif
-
-        // apply if necessary:
-        if ( !_applied && !_useUpdateTrav )
-        {
-            OpenThreads::ScopedLock<OpenThreads::Mutex> lock( _stateSetMutex );
-            if (!_applied)
-            {
-                //node->getOrCreateStateSet()->addUniform( _lightingEnabledUniform.get() );
-                //node->getStateSet()->addUniform( _lightEnabledUniform.get() );
-                for( int i=0; i < _maxLights; ++i )
-                {
-                    _osgLightSourceParameters[i].applyState(node->getStateSet());
-                }
-                _applied = true;
-            }
-        }		
-    }        
-}
 
-void
-UpdateLightingUniformsHelper::updateTraverse( osg::Node* node )
-{
-    if ( _dirty )
-    {
-        //_lightingEnabledUniform->set( _lightingEnabled );
-
-        //for( int i=0; i < _maxLights; ++i )
-            //_lightEnabledUniform->setElement( i, _lightEnabled[i] );
-
-        _dirty = false;
-
-        if ( !_applied )
-        {
-            osg::StateSet* stateSet = node->getOrCreateStateSet();
-            //stateSet->addUniform( _lightingEnabledUniform.get() );
-            //stateSet->addUniform( _lightEnabledUniform.get() );
-            for( int i=0; i < _maxLights; ++i )
-            {
-                _osgLightSourceParameters[i].applyState(stateSet);
-            }
-        }
+        //OE_WARN << source << std::endl << std::endl;
     }
 }
 
-void
-UpdateLightingUniformsHelper::operator()(osg::Node* node, osg::NodeVisitor* nv)
-{
-    cullTraverse( node, nv );
-    traverse(node, nv);
-}
-
 //------------------------------------------------------------------------
 
 ArrayUniform::ArrayUniform( const std::string& name, osg::Uniform::Type type, osg::StateSet* stateSet, unsigned size )
@@ -960,7 +605,8 @@ DiscardAlphaFragments::install(osg::StateSet* ss, float minAlpha) const
         if ( vp )
         {
             std::string code = Stringify()
-                << "#version " GLSL_VERSION_STR "\n"
+                << "#version " << GLSL_VERSION_STR << "\n"
+                << GLSL_DEFAULT_PRECISION_FLOAT << "\n"
                 << "void oe_discardalpha_frag(inout vec4 color) { \n"
                 << "    if ( color.a < " << std::setprecision(1) << minAlpha << ") discard;\n"
                 << "} \n";
diff --git a/src/osgEarth/Shaders b/src/osgEarth/Shaders
index 01a4f13..89cd6ae 100644
--- a/src/osgEarth/Shaders
+++ b/src/osgEarth/Shaders
@@ -31,11 +31,11 @@ namespace osgEarth
     public:
         Shaders();
 
-        std::string AlphaEffectFragment;
         std::string DepthOffsetVertex;
         std::string DrapingVertex, DrapingFragment;
         std::string GPUClampingVertex, GPUClampingFragment, GPUClampingVertexLib;
         std::string InstancingVertex;
+        std::string PhongLightingVertex, PhongLightingFragment;
 	};	
 
 } // namespace osgEarth
diff --git a/src/osgEarth/Shaders.cpp.in b/src/osgEarth/Shaders.cpp.in
index 5c8d7b9..31d009c 100644
--- a/src/osgEarth/Shaders.cpp.in
+++ b/src/osgEarth/Shaders.cpp.in
@@ -6,11 +6,6 @@ namespace osgEarth
 {
     Shaders::Shaders()
     {
-        // AlphaEffect
-        AlphaEffectFragment = "AlphaEffect.frag.glsl";
-        _sources[AlphaEffectFragment] = "@AlphaEffect.frag.glsl@";
-
-
         // Depth Offset
         DepthOffsetVertex = "DepthOffset.vert.glsl";
        _sources[DepthOffsetVertex] = "@DepthOffset.vert.glsl@";
@@ -38,5 +33,13 @@ namespace osgEarth
         // DrawInstanced
         InstancingVertex = "Instancing.vert.glsl";
         _sources[InstancingVertex] = "@Instancing.vert.glsl@";
+
+
+        // PhongLightingEffect
+        PhongLightingVertex = "PhongLighting.vert.glsl";
+        _sources[PhongLightingVertex] = "@PhongLighting.vert.glsl@";
+        
+        PhongLightingFragment = "PhongLighting.frag.glsl";
+        _sources[PhongLightingFragment] = "@PhongLighting.frag.glsl@";
     }
 };
\ No newline at end of file
diff --git a/src/osgEarth/Shadowing.cpp b/src/osgEarth/Shadowing.cpp
index bbdd0d6..b16a6a0 100644
--- a/src/osgEarth/Shadowing.cpp
+++ b/src/osgEarth/Shadowing.cpp
@@ -30,13 +30,12 @@ using namespace osgEarth;
 void
 Shadowing::setIsShadowCamera(osg::Camera* camera)
 {
-    camera->setUserValue("oe_isShadowCamera", true);
+    camera->getOrCreateStateSet()->setDefine("OE_IS_SHADOW_CAMERA");
 }
 
 bool
 Shadowing::isShadowCamera(const osg::Camera* camera)
 {
-    bool is = false;
-    camera->getUserValue("oe_isShadowCamera", is);
-    return is;
+    const osg::StateSet* ss = camera->getStateSet();
+    return ss && ss->getDefinePair("OE_IS_SHADOW_CAMERA") != 0L;
 }
diff --git a/src/osgEarthUtil/SimplexNoise b/src/osgEarth/SimplexNoise
similarity index 92%
rename from src/osgEarthUtil/SimplexNoise
rename to src/osgEarth/SimplexNoise
index 4959b24..1a1c27d 100644
--- a/src/osgEarthUtil/SimplexNoise
+++ b/src/osgEarth/SimplexNoise
@@ -19,21 +19,23 @@
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
 */
-#ifndef OSGEARTH_UTIL_SIMPLEX_NOISE_H
-#define OSGEARTH_UTIL_SIMPLEX_NOISE_H 1
+#ifndef OSGEARTH_SIMPLEX_NOISE_H
+#define OSGEARTH_SIMPLEX_NOISE_H 1
 
-#include <osgEarthUtil/Common>
+#include <osgEarth/Common>
+#include <osg/Image>
 
-namespace osgEarth { namespace Util
+namespace osgEarth
 {
     /**
      * Simplex Noise Generator.
      * Adapted from https://github.com/Taywee/Noise
      */
-    class OSGEARTHUTIL_EXPORT SimplexNoise
+    class OSGEARTH_EXPORT SimplexNoise
     {
     public:
         SimplexNoise();
+        SimplexNoise(const SimplexNoise& rhs);
         virtual ~SimplexNoise() { }
 
         static const double DefaultFrequency;
@@ -129,6 +131,12 @@ namespace osgEarth { namespace Util
         
         double getTiledValueWithTurbulence(double x, double y, double F) const;
 
+        /**
+         * Creates a tileable image of the requested dimensions.
+         * The image will be histogram-stretched in the range [0..1].
+         */
+        class osg::Image* createSeamlessImage(unsigned dim) const;
+
     private:
         // Inner class to speed up gradient computations
         // (array access is a lot slower than member access)
@@ -173,6 +181,6 @@ namespace osgEarth { namespace Util
         bool _normalize;
     };
 
-} } // namespace osgEarth::Util
+} // namespace osgEarth
 
-#endif //OSGEARTH_UTIL_SIMPLEX_NOISE_H
+#endif //OSGEARTH_SIMPLEX_NOISE_H
diff --git a/src/osgEarthUtil/SimplexNoise.cpp b/src/osgEarth/SimplexNoise.cpp
similarity index 90%
rename from src/osgEarthUtil/SimplexNoise.cpp
rename to src/osgEarth/SimplexNoise.cpp
index cd44918..9e014c4 100644
--- a/src/osgEarthUtil/SimplexNoise.cpp
+++ b/src/osgEarth/SimplexNoise.cpp
@@ -17,12 +17,14 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>
  */
 
-#include <osgEarthUtil/SimplexNoise>
+#include <osgEarth/SimplexNoise>
+#include <osgEarth/ImageUtils>
+#include <osg/Image>
 #include <algorithm>
 
 #define POW2(x) ((double)(x==0 ? 1 : (2 << (x-1))))
 
-using namespace osgEarth::Util;
+using namespace osgEarth;
 
 const SimplexNoise::Grad SimplexNoise::grad3[12] = {
     Grad(1, 1, 0), Grad(-1, 1, 0), Grad(1, -1, 0), Grad(-1, -1, 0),
@@ -124,7 +126,7 @@ const double   SimplexNoise::DefaultLacunarity   =  2.0;
 const double   SimplexNoise::DefaultRangeLow     = -1.0;
 const double   SimplexNoise::DefaultRangeHigh    =  1.0;
 const unsigned SimplexNoise::DefaultOctaves      =  10;
-const bool     SimplexNoise::DefaultNormalize    =  true;
+const bool     SimplexNoise::DefaultNormalize    =  false;
 
 
 SimplexNoise::SimplexNoise() :
@@ -142,6 +144,21 @@ _normalize ( DefaultNormalize )
     }
 }
 
+SimplexNoise::SimplexNoise(const SimplexNoise& rhs) :
+_octaves   ( rhs._octaves ),
+_freq      ( rhs._freq ),
+_pers      ( rhs._pers ),
+_lacunarity( rhs._lacunarity ),
+_low       ( rhs._low ),
+_high      ( rhs._high ),
+_normalize ( rhs._normalize )
+{
+    for(unsigned int i=0; i<512; i++)
+    {
+        permMod12[i] = (unsigned char)(perm[i] % 12);
+    }
+}
+
 double SimplexNoise::getTiledValue(double x, double y) const
 {
     const double TwoPI = 2.0 * osg::PI;
@@ -637,3 +654,60 @@ double SimplexNoise::Noise(double x, double y, double z, double w) const
     // Sum up and scale the result to cover the range [-1,1]
     return 27.0 * (n0 + n1 + n2 + n3 + n4);
 }
+
+osg::Image*
+SimplexNoise::createSeamlessImage(unsigned dim) const
+{
+    if (dim == 0) return 0L;
+
+    // Copy this generator and set a [0..1] range.
+    SimplexNoise noise(*this);
+    noise.setRange(0.0, 1.0);
+    noise.setNormalize(true);
+
+    osg::Image* image = new osg::Image();
+    image->allocateImage(dim, dim, 1, GL_RED, GL_UNSIGNED_BYTE);
+    ImageUtils::PixelWriter write(image);
+
+    float minN =  FLT_MAX;
+    float maxN = -FLT_MAX;
+
+    // populate the image, tracking the min and max noise readings:
+    osg::Vec4f value;
+    for (unsigned s = 0; s < dim; ++s)
+    {
+        double u = (double)s / (double)dim;
+        for (unsigned t = 0; t < dim; ++t)
+        {
+            double v = (double)t / (double)dim;
+            value.r() = noise.getTiledValue(u, v);
+            minN = std::min(minN, value.r());
+            maxN = std::max(maxN, value.r());
+            write(value, s, t);
+        }        
+    }
+
+    if (getNormalize())
+    {
+        // Histogram stretch to [0..1]
+        float scale = 1.0/(maxN-minN);
+        float bias = -minN;
+
+        OE_INFO << "minN=" << minN << "; maxN=" << maxN << "; scale=" << scale << "; bias=" << bias << "\n";
+
+        ImageUtils::PixelReader read(image);
+        read.setBilinear(false);
+
+        for (unsigned s = 0; s < dim; ++s)
+        {
+            for (unsigned t = 0; t < dim; ++t)
+            {
+                value = read(s, t);
+                value.r() = (value.r()+bias)*scale;
+                write(value, s, t);
+            }
+        }
+    }
+
+    return image;
+}
diff --git a/src/osgEarth/SpatialReference b/src/osgEarth/SpatialReference
index 7be4c30..081c155 100644
--- a/src/osgEarth/SpatialReference
+++ b/src/osgEarth/SpatialReference
@@ -60,14 +60,11 @@ namespace osgEarth
         static SpatialReference* create( osg::CoordinateSystemNode* csn );
 
         /**
-         * Creates an SRS around a pre-existing OGR spatial reference handle. The new
-         * SpatialReference object takes ownership of the handle.
-         *
-         * @param xferOwnership
-         *      If true, the SpatialReference object is responsible for releasing the handle
-         *      upon destruction.
+         * Creates an SRS by cloning a pre-existing OGR spatial reference handle.
+         * The new SRS owns the cloned handle, and the caller retains responsibility
+         * for managing the original handle.
          */
-        static SpatialReference* createFromHandle( void* ogrHandle, bool xferOwnership =false );
+        static SpatialReference* createFromHandle(void* ogrHandle);
 
 
     public: // Basic transformations.
@@ -333,6 +330,10 @@ namespace osgEarth
 		 */
 		void* getHandle() const { return _handle;}
 
+        /**
+         * Guess at an appropriate bounding box for this SRS.
+         */
+        bool guessBounds(Bounds& output) const;
 
 
     protected:
diff --git a/src/osgEarth/SpatialReference.cpp b/src/osgEarth/SpatialReference.cpp
index 8e0368c..cf7c719 100644
--- a/src/osgEarth/SpatialReference.cpp
+++ b/src/osgEarth/SpatialReference.cpp
@@ -32,10 +32,6 @@
 
 using namespace osgEarth;
 
-// took this out, see issue #79
-//#define USE_CUSTOM_MERCATOR_TRANSFORM 1
-//#undef USE_CUSTOM_MERCATOR_TRANSFORM
-
 //------------------------------------------------------------------------
 
 namespace
@@ -50,44 +46,7 @@ namespace
             return lowercase ? toLower(val) : val;
         }
         return "";
-    }    
-
-    // http://en.wikipedia.org/wiki/Mercator_projection#Mathematics_of_the_projection
-    bool sphericalMercatorToGeographic( std::vector<osg::Vec3d>& points )
-    {
-        for( unsigned i=0; i<points.size(); ++i )
-        {
-            double x = osg::clampBetween(points[i].x(), MERC_MINX, MERC_MAXX);
-            double y = osg::clampBetween(points[i].y(), MERC_MINY, MERC_MAXY);
-            double xr = -osg::PI + ((x-MERC_MINX)/MERC_WIDTH)*2.0*osg::PI;
-            double yr = -osg::PI + ((y-MERC_MINY)/MERC_HEIGHT)*2.0*osg::PI;
-            points[i].x() = osg::RadiansToDegrees( xr );
-            points[i].y() = osg::RadiansToDegrees( 2.0 * atan( exp(yr) ) - osg::PI_2 );
-            // z doesn't change here.
-        }
-        return true;
-    }
-
-    // http://en.wikipedia.org/wiki/Mercator_projection#Mathematics_of_the_projection
-    bool geographicToSphericalMercator( std::vector<osg::Vec3d>& points )
-    {
-        for( unsigned i=0; i<points.size(); ++i )
-        {
-            double lon = osg::clampBetween(points[i].x(), -180.0, 180.0);
-            double lat = osg::clampBetween(points[i].y(), -90.0, 90.0);
-            double xr = (osg::DegreesToRadians(lon) - (-osg::PI)) / (2.0*osg::PI);
-            double sinLat = sin(osg::DegreesToRadians(lat));
-            double oneMinusSinLat = 1-sinLat;
-            if ( oneMinusSinLat != 0.0 )
-            {
-                double yr = ((0.5 * log( (1+sinLat)/oneMinusSinLat )) - (-osg::PI)) / (2.0*osg::PI);
-                points[i].x() = osg::clampBetween(MERC_MINX + (xr * MERC_WIDTH), MERC_MINX, MERC_MAXX);
-                points[i].y() = osg::clampBetween(MERC_MINY + (yr * MERC_HEIGHT), MERC_MINY, MERC_MAXY);
-                // z doesn't change here.
-            }
-        }
-        return true;
-    }
+    } 
 
     void geodeticToECEF(std::vector<osg::Vec3d>& points, const osg::EllipsoidModel* em)
     {
@@ -357,10 +316,22 @@ SpatialReference::create( osg::CoordinateSystemNode* csn )
 }
 
 SpatialReference*
-SpatialReference::createFromHandle( void* ogrHandle, bool xferOwnership )
+SpatialReference::createFromHandle(void* ogrHandle)
 {
-    SpatialReference* srs = new SpatialReference( ogrHandle, xferOwnership );
-    return srs;
+    if (!ogrHandle)
+    {
+        OE_WARN << LC << "Illegal call to createFromHandle(NULL)" << std::endl;
+        return 0L;
+    }
+
+    void* clonedHandle = OSRClone(ogrHandle);
+    if (!clonedHandle)
+    {
+        OE_WARN << LC << "Internal error: createFromHandle() failed to clone" << std::endl;
+        return 0L;
+    }
+
+    return new SpatialReference(clonedHandle);
 }
 
 SpatialReference*
@@ -1041,25 +1012,8 @@ SpatialReference::transform(std::vector<osg::Vec3d>& points,
     const SpatialReference* inputSRS = preTransform( points );
     if ( !inputSRS )
         return false;
-
-    // Spherical Mercator is a special case transformation, because we want to bypass
-    // any normal horizontal datum conversion. In other words we ignore the ellipsoid
-    // of the other SRS and just do a straight spherical conversion.
-    if ( inputSRS->isGeographic() && outputSRS->isSphericalMercator() )
-    {        
-        inputSRS->transformZ( points, outputSRS, true );
-        success = geographicToSphericalMercator( points );
-        return success;
-    }
-
-    else if ( inputSRS->isSphericalMercator() && outputSRS->isGeographic() )
-    {     
-        success = sphericalMercatorToGeographic( points );
-        inputSRS->transformZ( points, outputSRS, true );
-        return success;
-    }
-
-    else if ( inputSRS->isECEF() && !outputSRS->isECEF() )
+        
+    if ( inputSRS->isECEF() && !outputSRS->isECEF() )
     {
         const SpatialReference* outputGeoSRS = outputSRS->getGeodeticSRS();
         ECEFtoGeodetic(points, outputGeoSRS->getEllipsoid());
@@ -1159,6 +1113,10 @@ SpatialReference::transformXYPointArrays(double*  x,
     // Transform the X and Y values inside an exclusive GDAL/OGR lock
     GDAL_SCOPED_LOCK;
 
+    //OE_INFO << LC << "Attempt transfrom from \n"
+    //    << "    " << getHorizInitString() << "\n"
+    //    << " -> " << out_srs->getHorizInitString() << std::endl;
+
     void* xform_handle = NULL;
     TransformHandleCache::const_iterator itr = _transformHandleCache.find(out_srs->getWKT());
     if (itr != _transformHandleCache.end())
@@ -1179,6 +1137,10 @@ SpatialReference::transformXYPointArrays(double*  x,
             << "SRS xform not possible" << std::endl
             << "    From => " << getName() << std::endl
             << "    To   => " << out_srs->getName() << std::endl;
+
+        OE_WARN << LC << "INPUT: " << getWKT() << std::endl
+            << "OUTPUT: " << out_srs->getWKT() << std::endl;
+
         return false;
     }
 
@@ -1444,6 +1406,7 @@ SpatialReference::transformExtentToMBR(const SpatialReference* to_srs,
     
     if ( transform(v, to_srs) )
     {
+        bool swapXValues = ( isGeographic() && in_out_xmin > in_out_xmax );
         in_out_xmin = DBL_MAX;
         in_out_ymin = DBL_MAX;
         in_out_xmax = -DBL_MAX;
@@ -1457,6 +1420,9 @@ SpatialReference::transformExtentToMBR(const SpatialReference* to_srs,
             in_out_ymax = std::max( v[i].y(), in_out_ymax );
         }
 
+        if ( swapXValues )
+            std::swap( in_out_xmin, in_out_xmax );
+
         return true;
     }
 
@@ -1630,4 +1596,34 @@ SpatialReference::_init()
     }
 
     _initialized = true;
-}
\ No newline at end of file
+}
+
+bool
+SpatialReference::guessBounds(Bounds& bounds) const
+{
+    if (isGeographic())
+    {
+        bounds.set(-180.0, -90.0, 0.0, 180.0, 90.0, 0.0);
+        return true;
+    }
+    
+    if (isMercator() || isSphericalMercator())
+    {
+        bounds.set(MERC_MINX, MERC_MINY, 0.0, MERC_MAXX, MERC_MAXY, 0.0);
+        return true;
+    }
+
+    GDAL_SCOPED_LOCK;
+
+    int isNorth;
+    if (OSRGetUTMZone(_handle, &isNorth))
+    {
+        if (isNorth)
+            bounds.set(166000, 0, 0, 834000, 9330000, 0);
+        else
+            bounds.set(166000, 1116915, 0.0, 834000, 10000000, 0);
+        return true;
+    }
+
+    return false;
+}
diff --git a/src/osgEarth/StateSetCache.cpp b/src/osgEarth/StateSetCache.cpp
index 43a99f0..26c1d7f 100644
--- a/src/osgEarth/StateSetCache.cpp
+++ b/src/osgEarth/StateSetCache.cpp
@@ -25,9 +25,7 @@
 
 #define DEFAULT_PRUNE_ACCESS_COUNT 40
 
-#if OSG_MIN_VERSION_REQUIRED(3,1,4)
-#   define STATESET_SHARING_SUPPORTED 1
-#endif
+#define STATESET_SHARING_SUPPORTED 1
 
 using namespace osgEarth;
 
diff --git a/src/osgEarth/StateSetLOD b/src/osgEarth/StateSetLOD
index 342353f..1e0de95 100644
--- a/src/osgEarth/StateSetLOD
+++ b/src/osgEarth/StateSetLOD
@@ -35,6 +35,8 @@ namespace osgEarth
      * By default, this class works like osg::LOD and uses the camera's 
      * distance to the bounding center as the value used to select active
      * statesets. You can change the metric by calling setGetValueFunction().
+     *
+     * @deprecated
      */
     class OSGEARTH_EXPORT StateSetLOD : public osg::Group
     {
@@ -82,7 +84,8 @@ namespace osgEarth
         
         typedef std::pair<float, float> MinMaxPair;
         typedef std::vector<MinMaxPair> RangeList;
-        typedef std::vector<osg::ref_ptr<osg::StateSet> > StateSetList;
+        typedef std::vector<osg::ref_ptr<osg::StateSet> > RefStateSetVector;
+        typedef RefStateSetVector StateSetList;
 
         RangeList    _ranges;
         StateSetList _stateSets;
diff --git a/src/osgEarth/StringUtils b/src/osgEarth/StringUtils
index e2a0681..bf90678 100644
--- a/src/osgEarth/StringUtils
+++ b/src/osgEarth/StringUtils
@@ -157,7 +157,7 @@ namespace osgEarth
         TYPE temp = dv; \
         std::istringstream strin( trim(str) ); \
         if ( !strin.eof() ) { \
-            if ( str.length() >= 2 && str.at(0) == '0' && str.at(1) == 'x' ) { \
+            if ( str.length() >= 2 && str[0] == '0' && str[1] == 'x' ) { \
                 strin.seekg( 2 ); \
                 strin >> std::hex >> temp; \
             } \
diff --git a/src/osgEarth/StringUtils.cpp b/src/osgEarth/StringUtils.cpp
index 8cc7921..86ab424 100644
--- a/src/osgEarth/StringUtils.cpp
+++ b/src/osgEarth/StringUtils.cpp
@@ -56,7 +56,7 @@ void
 StringTokenizer::addDelims( const std::string& delims, bool keep )
 {
     for( unsigned i=0; i<delims.size(); ++i )
-        addDelim( delims.at(i), keep );
+        addDelim( delims[i], keep );
 }
 
 void
@@ -69,7 +69,7 @@ void
 StringTokenizer::addQuotes( const std::string& quotes, bool keep )
 {
     for( unsigned i=0; i<quotes.size(); ++i )
-        addQuote( quotes.at(i), keep );
+        addQuote( quotes[i], keep );
 }
 
 void
@@ -160,7 +160,7 @@ osgEarth::toLegalFileName( const std::string& input )
     std::stringstream buf;
     for( ; pos < input.size(); ++pos )
     {
-        std::string::const_reference c = input.at(pos);
+        std::string::const_reference c = input[pos];
         if ( ::isprint(c) && !::isspace(c) && illegal.find(c) == std::string::npos )
             buf << c;
         else
diff --git a/src/osgEarth/Terrain b/src/osgEarth/Terrain
index 260e405..112ae58 100644
--- a/src/osgEarth/Terrain
+++ b/src/osgEarth/Terrain
@@ -72,14 +72,15 @@ namespace osgEarth
          * A tile was added to the terrain graph.
          * @param key
          *      Tile key of the new tile, including the geographic extents
-         * @param tile
-         *      Geometry of the new tile
+         * @param graph
+         *      Scene graph that can be used to intersect the tile (though it
+         *      may include more than just the new tile)
          * @param context
          *      Contextual information about the callback
          */
         virtual void onTileAdded(
             const TileKey&          key, 
-            osg::Node*              tile, 
+            osg::Node*              graph, 
             TerrainCallbackContext& context) { }
 
         /** dtor */
@@ -166,11 +167,6 @@ namespace osgEarth
          */
         const SpatialReference* getSRS() const { return _profile->getSRS(); }
 
-        /**
-         * Whether the terrain is in geocentric (ECEF) coordinates
-         */
-        bool isGeocentric() const { return _geocentric; }
-
 
     public: // TerrainResolver interface
 
@@ -254,22 +250,25 @@ namespace osgEarth
         void accept( osg::NodeVisitor& nv );
 
         // access the raw terrain graph
-        osg::Node* getGraph() { return _graph.get(); }
+        osg::Node* getGraph() const { return _graph.get(); }
         
         // queues the onTileAdded callback (internal)
         void notifyTileAdded( const TileKey& key, osg::Node* tile );
-        // fires the onTileAdded callback (internal)
-        void fireTileAdded( const TileKey& key, osg::Node* tile );
 
         // queues the onTileRemoved callback (internal)
         void notifyTilesRemoved(const std::vector<TileKey>& keys);
-        void fireTilesRemoved(const std::vector<TileKey>& keys);
+
+        // internal
+        void notifyMapElevationChanged();
 
         /** dtor */
         virtual ~Terrain() { }
 
     private:
-        Terrain( osg::Node* graph, const Profile* profile, bool geocentric, const TerrainOptions& options );
+        Terrain( osg::Node* graph, const Profile* profile, const TerrainOptions& options );
+
+        /** update traversal. */
+        void update();
 
         friend class TerrainEngineNode;
 
@@ -281,42 +280,25 @@ namespace osgEarth
 
         osg::ref_ptr<const Profile>  _profile;
         osg::observer_ptr<osg::Node> _graph;
-        bool                         _geocentric;
         const TerrainOptions&        _terrainOptions;
 
-        osg::observer_ptr<osg::OperationQueue> _updateOperationQueue;
-    };
-
-
-    /**
-     * A TerrainPatch is a standalone subset of terrain that you can use for 
-     * height queries. The intention is that this be used for a "disconnected"
-     * terrain tile, i.e. one that is not in the scene graph yet -- making it
-     * MT-safe.
-     */
-    class OSGEARTH_EXPORT TerrainPatch : public TerrainResolver
-    {
-    public:
-        TerrainPatch( osg::Node* patch, const Terrain* terrain );
-
-
-    public: // TerrainResolver interface
-
-        /**
-        * Queries the elevation under the specified point. This method is
-        * identical to calling Terrain::getHeight with the specified patch.
-        */
-        bool getHeight(
-            const SpatialReference* srs,
-            double                  x,
-            double                  y,
-            double*                 out_heightAboveMSL,
-            double*                 out_heightAboveEllipsoid =0L) const;
+        osg::ref_ptr<osg::OperationQueue> _updateQueue;
+        
+        void fireMapElevationChanged();
+        void fireTileAdded( const TileKey& key, osg::Node* tile );
+        void fireTilesRemoved(const std::vector<TileKey>& keys);
 
-    protected:
-        osg::ref_ptr<osg::Node>     _patch;
-        osg::ref_ptr<const Terrain> _terrain;
+        struct OnTileAddedOperation : public osg::Operation {
+            osg::observer_ptr<Terrain> _terrain;
+            TileKey _key;
+            osg::observer_ptr<osg::Node> _node;
+            unsigned _count;
+            OnTileAddedOperation(const TileKey& key, osg::Node* node, Terrain* terrain);
+            void operator()(osg::Object*);
+            int _delay;
+        };
     };
+
 }
 
 #endif // OSGEARTH_COMPOSITING_H
diff --git a/src/osgEarth/Terrain.cpp b/src/osgEarth/Terrain.cpp
index 21d15f7..83cbe5a 100644
--- a/src/osgEarth/Terrain.cpp
+++ b/src/osgEarth/Terrain.cpp
@@ -18,7 +18,7 @@
  */
 
 #include <osgEarth/Terrain>
-#include <osgEarth/DPLineSegmentIntersector>
+#include <osgUtil/LineSegmentIntersector>
 #include <osgUtil/IntersectionVisitor>
 #include <osgUtil/LineSegmentIntersector>
 #include <osgViewer/View>
@@ -29,64 +29,53 @@ using namespace osgEarth;
 
 //---------------------------------------------------------------------------
 
-namespace
+Terrain::OnTileAddedOperation::OnTileAddedOperation(const TileKey& key, osg::Node* node, Terrain* terrain)
+    : osg::Operation("OnTileAdded", true),
+        _terrain(terrain), _key(key), _node(node), _count(0), _delay(0) { }
+
+void Terrain::OnTileAddedOperation::operator()(osg::Object*)
 {
-    struct BaseOp : public osg::Operation
-    {
-        BaseOp(Terrain* terrain, bool keepByDefault) : osg::Operation("",keepByDefault), _terrain(terrain) { }
-        osg::observer_ptr<Terrain> _terrain;
-    };
+    if ( getKeep() == false )
+        return;
 
-    struct OnTileAddedOperation : public BaseOp
+    if (_delay-- > 0)
+        return;
+
+    ++_count;
+    osg::ref_ptr<Terrain>   terrain;
+    osg::ref_ptr<osg::Node> node;
+    
+    if ( _terrain.lock(terrain) && (!_key.valid() || _node.lock(node)) )
     {
-        TileKey _key;
-        osg::observer_ptr<osg::Node> _node;
-        unsigned _count;
+        if (_key.valid())
+            terrain->fireTileAdded( _key, node.get() );
+        else
+            terrain->fireTileAdded( _key, 0L );
 
-        OnTileAddedOperation(const TileKey& key, osg::Node* node, Terrain* terrain)
-            : BaseOp(terrain, true), _key(key), _node(node), _count(0) { }
+    }
+    else
+    {
+        // nop; tile expired; let it go.
+        OE_DEBUG << "Tile expired before notification: " << _key.str() << std::endl;
+    }
 
-        void operator()(osg::Object*)
-        {
-            if ( getKeep() == false )
-                return;
-
-            ++_count;
-            osg::ref_ptr<Terrain>   terrain;
-            osg::ref_ptr<osg::Node> node;
-
-            if ( _terrain.lock(terrain) && _node.lock(node) )
-            {
-                if ( node->getNumParents() > 0 )
-                {
-                    //OE_NOTICE << LC << "FIRING onTileAdded for " << _key.str() << " (tries=" << _count << ")" << std::endl;
-                    terrain->fireTileAdded( _key, node.get() );
-                    this->setKeep( false );
-                }
-                else
-                {
-                    //OE_NOTICE << LC << "Deferring onTileAdded for " << _key.str() << std::endl;
-                }
-            }
-            else
-            {
-                // nop; tile expired; let it go.
-                //OE_NOTICE << "Tile expired before notification: " << _key.str() << std::endl;
-                this->setKeep( false );
-            }
-        }
-    };
+    this->setKeep( false );
 }
 
 //---------------------------------------------------------------------------
 
-Terrain::Terrain(osg::Node* graph, const Profile* mapProfile, bool geocentric, const TerrainOptions& terrainOptions ) :
+Terrain::Terrain(osg::Node* graph, const Profile* mapProfile, const TerrainOptions& terrainOptions ) :
 _graph         ( graph ),
 _profile       ( mapProfile ),
-_geocentric    ( geocentric ),
 _terrainOptions( terrainOptions )
 {
-    //nop
+    _updateQueue = new osg::OperationQueue();
+}
+
+void
+Terrain::update()
+{
+    _updateQueue->runOperations();
 }
 
 bool
@@ -129,14 +118,14 @@ Terrain::getHeight(osg::Node*              patch,
     osg::Vec3d start(x, y, r);
     osg::Vec3d end  (x, y, -r);
 
-    if ( isGeocentric() )
+    if ( getSRS()->isGeographic() )
     {
         const SpatialReference* ecef = getSRS()->getECEF();
         getSRS()->transform(start, ecef, start);
         getSRS()->transform(end,   ecef, end);
     }
 
-    DPLineSegmentIntersector* lsi = new DPLineSegmentIntersector( start, end );
+    osgUtil::LineSegmentIntersector* lsi = new osgUtil::LineSegmentIntersector( start, end );
     lsi->setIntersectionLimit(osgUtil::Intersector::LIMIT_NEAREST);
 
     osgUtil::IntersectionVisitor iv( lsi );
@@ -197,7 +186,7 @@ Terrain::getWorldCoordsUnderMouse(osg::View* view, float x, float y, osg::Vec3d&
     return false;    
 
 #else
-    // New code, uses the code from osg::View::computeIntersections but uses our DPLineSegmentIntersector instead to get around floating point precision issues.
+    // New code, uses the code from osg::View::computeIntersections but uses our osgUtil::LineSegmentIntersector instead to get around floating point precision issues.
     float local_x, local_y = 0.0;
     const osg::Camera* camera = view2->getCameraContainingPosition(x, y, local_x, local_y);
     if (!camera) camera = view2->getCamera();
@@ -229,7 +218,7 @@ Terrain::getWorldCoordsUnderMouse(osg::View* view, float x, float y, osg::Vec3d&
 
     // Use a double precision line segment intersector
     //osg::ref_ptr< osgUtil::LineSegmentIntersector > picker = new osgUtil::LineSegmentIntersector(osgUtil::Intersector::MODEL, startVertex, endVertex);
-    osg::ref_ptr< DPLineSegmentIntersector > picker = new DPLineSegmentIntersector(osgUtil::Intersector::MODEL, startVertex, endVertex);
+    osg::ref_ptr< osgUtil::LineSegmentIntersector > picker = new osgUtil::LineSegmentIntersector(osgUtil::Intersector::MODEL, startVertex, endVertex);
 
     // Limit it to one intersection, we only care about the first
     picker->setIntersectionLimit( osgUtil::Intersector::LIMIT_NEAREST );
@@ -322,10 +311,12 @@ Terrain::notifyTileAdded( const TileKey& key, osg::Node* node )
         OE_WARN << LC << "notify with a null node!" << std::endl;
     }
 
-    osg::ref_ptr<osg::OperationQueue> queue;
-    if ( _callbacksSize > 0 && _updateOperationQueue.lock(queue) )
+    if (_callbacksSize > 0)
     {
-        queue->add( new OnTileAddedOperation(key, node, this) );
+        if (!key.valid())
+            OE_WARN << LC << "notifyTileAdded with key = NULL\n";
+
+        _updateQueue->add(new OnTileAddedOperation(key, node, this));
     }
 }
 
@@ -347,44 +338,24 @@ Terrain::fireTileAdded( const TileKey& key, osg::Node* node )
     }
 }
 
-
 void
-Terrain::accept( osg::NodeVisitor& nv )
-{
-    _graph->accept( nv );
-}
-
-
-//---------------------------------------------------------------------------
-
-#undef  LC
-#define LC "[TerrainPatch] "
-
-TerrainPatch::TerrainPatch(osg::Node* patch, const Terrain* terrain) :
-_patch  ( patch ),
-_terrain( terrain )
+Terrain::notifyMapElevationChanged()
 {
-    //nop
-    if ( patch == 0L || terrain == 0L )
+    if (_callbacksSize > 0)
     {
-        OE_WARN << "ILLEGAL: Created a TerrainPatch with a NULL parameter" << std::endl;
+        OnTileAddedOperation* op = new OnTileAddedOperation(TileKey::INVALID, 0L, this);
+        op->_delay = 1; // let the terrain update before applying this
+        _updateQueue->add(op);
     }
 }
 
-
-bool
-TerrainPatch::getHeight(const SpatialReference* srs,
-                        double                  x, 
-                        double                  y,
-                        double*                 out_hamsl,
-                        double*                 out_hae ) const
+void
+Terrain::fireMapElevationChanged()
 {
-    if ( _terrain && _patch )
-    {
-        return _terrain->getHeight( _patch.get(), srs, x, y, out_hamsl, out_hae );
-    }
-    else
-    {
-        return false;
-    }
+    // nop
+}
+void
+Terrain::accept( osg::NodeVisitor& nv )
+{
+    _graph->accept( nv );
 }
diff --git a/src/osgEarth/TerrainEngineNode b/src/osgEarth/TerrainEngineNode
index 58988c4..90e4fbb 100644
--- a/src/osgEarth/TerrainEngineNode
+++ b/src/osgEarth/TerrainEngineNode
@@ -26,17 +26,18 @@
 #include <osgEarth/TerrainTileModelFactory>
 #include <osgEarth/TerrainTileNode>
 #include <osgEarth/TerrainEngineRequirements>
-#include <osgEarth/TextureCompositor>
+#include <osgEarth/TerrainResources>
 #include <osgEarth/ShaderUtils>
-#include <osgEarth/TilePatchCallback>
 #include <osgEarth/Progress>
 #include <osg/CoordinateSystemNode>
 #include <osg/Geode>
 #include <osg/NodeCallback>
 #include <osgUtil/RenderBin>
+#include <set>
 
 #define OSGEARTH_ENV_TERRAIN_ENGINE_DRIVER "OSGEARTH_TERRAIN_ENGINE"
 
+
 namespace osgUtil {
     class CullVisitor;
 }
@@ -75,15 +76,15 @@ namespace osgEarth
          * Creates a new data model for a terrain tile, calling any registered
          * CreateTileModelCallbacks before returning the new object.
          *
-         * @param frame Map frame from which to create the new tile model
-         * @param key   Key for which to create the new tile model
-         * @param modelStore Optional interface for accessing other previously
-         *                   created tile models (see TerrainTileModelFactory)
+         * @param frame  Map frame from which to create the new tile model
+         * @param key    Key for which to create the new tile model
+         * @param layers UIDs of layers to fetch data for; NULL => all layers
          * @param progress Optional progress/stats tracking callback
          */
         virtual TerrainTileModel* createTileModel(
             const MapFrame&              frame,
             const TileKey&               key,
+            const CreateTileModelFilter& filter,
             ProgressCallback*            progress ) =0;
 
         
@@ -133,7 +134,7 @@ namespace osgEarth
         virtual const TerrainOptions& getTerrainOptions() const =0;
 
         /** Accesses the object that keeps track of GPU resources in use */
-        TextureCompositor* getResources() const;
+        TerrainResources* getResources() const;
 
         /** Adds a terrain effect */
         void addEffect( TerrainEffect* effect );
@@ -169,12 +170,6 @@ namespace osgEarth
         /** Access the stateset used to render the terrain. */
         virtual osg::StateSet* getSurfaceStateSet() { return getOrCreateStateSet(); }
 
-        /** Adds a tile patch callback. */
-        virtual void addTilePatchCallback(TilePatchCallback*);
-
-        /** Removes a tile patch callback. */
-        virtual void removeTilePatchCallback(TilePatchCallback*);
-
         /** Gets the ComputeRangeCallback for this TerrainEngineNode */
         ComputeRangeCallback* getComputeRangeCallback() const;
 
@@ -191,7 +186,8 @@ namespace osgEarth
         TerrainTileModel* createTileModel(
             const MapFrame&              frame,
             const TileKey&               key,
-            ProgressCallback*            progress );
+            const CreateTileModelFilter& filter,
+            ProgressCallback*            progress);
 
         void notifyOfTerrainTileNodeCreation(
             const TileKey& key, 
@@ -223,22 +219,8 @@ namespace osgEarth
         bool normalTexturesRequired() const { return _requireNormalTextures; }
         bool elevationTexturesRequired() const { return _requireElevationTextures; }
         bool parentTexturesRequired() const { return _requireParentTextures; }
-            
-
-    public: // Runtime properties
-
-        /** Sets the scale factor to apply to elevation height values. Default is 1.0
-          * @deprecated */
-        void setVerticalScale( float value );
-
-        /** Gets the scale factor to apply to elevation height values.
-          * @deprecated */
-        float getVerticalScale() const { return _verticalScale; }
-
-    public: // deprecated
-        
-        /** @derepcated Use getResources() instead. */
-        TextureCompositor* getTextureCompositor() const { return getResources(); }
+        bool elevationBorderRequired() const { return _requireElevationBorder; }
+        bool fullDataAtFirstLodRequired() const { return _requireFullDataAtFirstLOD; }
 
     protected:
         TerrainEngineNode();
@@ -248,20 +230,11 @@ namespace osgEarth
         virtual osg::BoundingSphere computeBound() const;
         virtual void traverse( osg::NodeVisitor& );
 
-    protected: // implementation events
-
-        /** @deprecated */
-        virtual void onVerticalScaleChanged() { }
-        /** @deprecated */
-        virtual void onElevationSamplingRatioChanged() { }
-
     protected:
         friend class MapNode;
         friend class TerrainEngineNodeFactory;
 
-        /** Attaches a map to the terrain engine and initialized it.*/
-        virtual void preInitialize( const Map* map, const TerrainOptions& options );
-        virtual void postInitialize( const Map* map, const TerrainOptions& options );
+        virtual void setMap(const Map* map, const TerrainOptions& options);
 
         // signals that a redraw is needed because something changed.
         virtual void requestRedraw();
@@ -270,12 +243,13 @@ namespace osgEarth
         // shaders, state, etc.
         virtual void dirtyState() { }
 
-        // allow subclasses direct access for convenience.
-        osg::ref_ptr<TextureCompositor> _texCompositor;
+        osg::ref_ptr<TerrainResources> _textureResourceTracker;
 
         bool _requireElevationTextures;
         bool _requireNormalTextures;
         bool _requireParentTextures;
+        bool _requireElevationBorder;
+        bool _requireFullDataAtFirstLOD;
 
         // called by addTileNodeCallback to notify any already-existing nodes
         // of the new callback.
@@ -293,6 +267,28 @@ namespace osgEarth
          */
         virtual osg::Node* createTile(const TileKey& key) =0;
 
+        //! Flags that affect the createTile behavior.
+        enum CreateTileFlags
+        {
+            CREATE_TILE_INCLUDE_TILES_WITH_MASKS     = 1,
+            CREATE_TILE_INCLUDE_TILES_WITHOUT_MASKS  = 2,
+            CREATE_TILE_DEFAULT_FLAGS                = ~0 /* all */
+        };
+
+        /**
+         * Utility for creating standalone tile geometry from a tile model.
+         * Note: experimental.
+         * @param model Tile model for which to build a tile
+         * @param flags OR of the CreateTileFlags enums
+         * @param referenceLOD If non-zero, adjust the vertex dimensions of the returned tile
+         *        to match this LOD. Example: ask for a tile at tileKey.lod=15, tile is 17x17.
+         *        Specific a referenceLOD=16, tile will be 33x33.
+         */
+        virtual osg::Node* createTile(
+            const TerrainTileModel* model,
+            int createTileFlags =CREATE_TILE_DEFAULT_FLAGS,
+            unsigned referenceLOD =0u) { return 0L; }
+
     private:
         friend struct TerrainEngineNodeCallbackProxy;
         friend struct MapNodeMapLayerController;
@@ -321,6 +317,7 @@ namespace osgEarth
         float                              _verticalScale;
         osg::ref_ptr<Terrain>              _terrainInterface;
         unsigned                           _dirtyCount;
+        bool                               _updateScheduled;
         
         enum InitStage {
             INIT_NONE,
@@ -344,9 +341,6 @@ namespace osgEarth
 
         osg::ref_ptr<ComputeRangeCallback> _computeRangeCallback;
 
-    protected:
-
-        TilePatchCallbacks _tilePatchCallbacks;
 
     public:
 
@@ -367,20 +361,7 @@ namespace osgEarth
     class TerrainEngineNodeFactory
     {
     public:
-        static TerrainEngineNode* create( Map* map, const TerrainOptions& options );
-    };
-
-    /**
-     * Base class for a node that "decorates" the terrain engine node within a map node.
-     */
-    class TerrainDecorator : public osg::Group
-    {
-    public:
-        virtual void onInstall( TerrainEngineNode* engine );
-        virtual void onUninstall( TerrainEngineNode* engine );
-
-    protected:
-        virtual ~TerrainDecorator();
+        static TerrainEngineNode* create(const TerrainOptions& options );
     };
 
 } // namespace osgEarth
diff --git a/src/osgEarth/TerrainEngineNode.cpp b/src/osgEarth/TerrainEngineNode.cpp
index c41cff8..1e70cfe 100644
--- a/src/osgEarth/TerrainEngineNode.cpp
+++ b/src/osgEarth/TerrainEngineNode.cpp
@@ -20,7 +20,7 @@
 #include <osgEarth/Capabilities>
 #include <osgEarth/CullingUtils>
 #include <osgEarth/Registry>
-#include <osgEarth/TextureCompositor>
+#include <osgEarth/TerrainResources>
 #include <osgEarth/NodeUtils>
 #include <osgEarth/MapModelChange>
 #include <osgEarth/TerrainTileModelFactory>
@@ -41,19 +41,20 @@ namespace osgEarth
     struct TerrainEngineNodeCallbackProxy : public MapCallback
     {
         TerrainEngineNodeCallbackProxy(TerrainEngineNode* node) : _node(node) { }
+
         osg::observer_ptr<TerrainEngineNode> _node;
 
         void onMapInfoEstablished( const MapInfo& mapInfo )
         {
-            osg::ref_ptr<TerrainEngineNode> safeNode = _node.get();
-            if ( safeNode.valid() )
+            osg::ref_ptr<TerrainEngineNode> safeNode;
+            if (_node.lock(safeNode))
                 safeNode->onMapInfoEstablished( mapInfo );
         }
 
         void onMapModelChanged( const MapModelChange& change )
         {
-            osg::ref_ptr<TerrainEngineNode> safeNode = _node.get();
-            if ( safeNode.valid() )
+            osg::ref_ptr<TerrainEngineNode> safeNode;
+            if (_node.lock(safeNode))
                 safeNode->onMapModelChanged( change );
         }
     };
@@ -65,9 +66,9 @@ namespace osgEarth
 
 TerrainEngineNode::ImageLayerController::ImageLayerController(const Map*         map,
                                                               TerrainEngineNode* engine) :
-_mapf  ( map, Map::IMAGE_LAYERS ),
+_mapf  ( map ),
 _engine( engine )
-{
+{    
     //nop
 }
 
@@ -98,10 +99,10 @@ TerrainEngineNode::removeEffect(TerrainEffect* effect)
 }
 
 
-TextureCompositor*
+TerrainResources*
 TerrainEngineNode::getResources() const
 {
-    return _texCompositor.get();
+    return _textureResourceTracker.get();
 }
 
 void
@@ -121,19 +122,30 @@ _dirtyCount              ( 0 ),
 _requireElevationTextures( false ),
 _requireNormalTextures   ( false ),
 _requireParentTextures   ( false ),
-_redrawRequired          ( true )
+_requireElevationBorder  ( false ),
+_requireFullDataAtFirstLOD( false ),
+_redrawRequired          ( true ),
+_updateScheduled( false )
 {
     // register for event traversals so we can properly reset the dirtyCount
-    ADJUST_EVENT_TRAV_COUNT( this, 1 );
+    ADJUST_EVENT_TRAV_COUNT(this, 1);
+
+    // register for update traversals so we can process terrain callbacks
+    //ADJUST_UPDATE_TRAV_COUNT(this, 1);
 }
 
 TerrainEngineNode::~TerrainEngineNode()
 {
+    OE_DEBUG << LC << "~TerrainEngineNode\n";
+
     //Remove any callbacks added to the image layers
     if (_map.valid())
     {
-        MapFrame mapf( _map.get(), Map::IMAGE_LAYERS );
-        for( ImageLayerVector::const_iterator i = mapf.imageLayers().begin(); i != mapf.imageLayers().end(); ++i )
+        MapFrame mapf( _map.get() );        
+        ImageLayerVector imageLayers;
+        mapf.getLayers(imageLayers);
+
+        for( ImageLayerVector::const_iterator i = imageLayers.begin(); i != imageLayers.end(); ++i )
         {
             i->get()->removeCallback( _imageLayerController.get() );
         }
@@ -180,70 +192,60 @@ TerrainEngineNode::dirtyTerrain()
     requestRedraw();
 }
 
-
 void
-TerrainEngineNode::preInitialize( const Map* map, const TerrainOptions& options )
+TerrainEngineNode::setMap(const Map* map, const TerrainOptions& options)
 {
+    if (!map) return;
+
     _map = map;
     
-    // fire up a terrain utility interface
-    _terrainInterface = new Terrain( this, map->getProfile(), map->isGeocentric(), options );
+    // Create a terrain utility interface. This interface can be used
+    // to query the in-memory terrain graph, subscribe to tile events, etc.
+    _terrainInterface = new Terrain( this, map->getProfile(), options );
 
-    // set up the CSN values   
+    // Set up the CSN values. We support this because some manipulators look for it,
+    // but osgEarth itself doesn't use it.
     _map->getProfile()->getSRS()->populateCoordinateSystemNode( this );
     
     // OSG's CSN likes a NULL ellipsoid to represent projected mode.
     if ( !_map->isGeocentric() )
         this->setEllipsoidModel( NULL );
     
-    // install an object to manage texture image unit usage:
-    _texCompositor = new TextureCompositor();
+    // Install an object to manage texture image unit usage:
+    _textureResourceTracker = new TerrainResources();
     std::set<int> offLimits = osgEarth::Registry::instance()->getOffLimitsTextureImageUnits();
     for(std::set<int>::const_iterator i = offLimits.begin(); i != offLimits.end(); ++i)
-        _texCompositor->setTextureImageUnitOffLimits( *i );
+        _textureResourceTracker->setTextureImageUnitOffLimits( *i );
 
-    // then register the callback so we can process further map model changes
-    _map->addMapCallback( new TerrainEngineNodeCallbackProxy( this ) );
+    // Register a callback so we can process further map model changes
+    _map->addMapCallback( new TerrainEngineNodeCallbackProxy(this) );
 
-    // apply render bin if necessary
+    // Force a render bin if specified in the options
     if ( options.binNumber().isSet() )
     {
         osg::StateSet* set = getOrCreateStateSet();
         set->setRenderBinDetails( options.binNumber().get(), "RenderBin" );
     }
-
-    if ( options.enableMercatorFastPath().isSet() )
-    {
-        OE_INFO 
-            << LC << "Mercator fast path " 
-            << (options.enableMercatorFastPath()==true? "enabled" : "DISABLED") << std::endl;
-    }
-    
-    // a default factory - this is the object that creates the data model for
-    // each terrain tile.
+   
+    // This is the object that creates the data model for each terrain tile.
     _tileModelFactory = new TerrainTileModelFactory(options);
 
-    _initStage = INIT_PREINIT_COMPLETE;
-}
+    // Manually trigger the map callbacks the first time:
+    if (_map->getProfile())
+        onMapInfoEstablished(MapInfo(_map.get()));
 
-void
-TerrainEngineNode::postInitialize( const Map* map, const TerrainOptions& options )
-{
-    if ( _map.valid() ) // i think this is always true [gw]
-    {
-        // manually trigger the map callbacks the first time:
-        if ( _map->getProfile() )
-            onMapInfoEstablished( MapInfo(_map.get()) );
+    // Create a layer controller. This object affects the uniforms
+    // that control layer appearance properties
+    _imageLayerController = new ImageLayerController(_map.get(), this);
 
-        // create a layer controller. This object affects the uniforms that control layer appearance properties
-        _imageLayerController = new ImageLayerController( _map.get(), this );
+    // register the layer Controller it with all pre-existing image layers:
+    MapFrame mapf(_map.get());
+    ImageLayerVector imageLayers;
+    mapf.getLayers(imageLayers);
 
-        // register the layer Controller it with all pre-existing image layers:
-        MapFrame mapf( _map.get(), Map::IMAGE_LAYERS );
-        for( ImageLayerVector::const_iterator i = mapf.imageLayers().begin(); i != mapf.imageLayers().end(); ++i )
-        {
-            i->get()->addCallback( _imageLayerController.get() );
-        }
+    for (ImageLayerVector::const_iterator i = imageLayers.begin(); i != imageLayers.end(); ++i)
+    {
+        i->get()->addCallback(_imageLayerController.get());
     }
 
     _initStage = INIT_POSTINIT_COMPLETE;
@@ -267,13 +269,6 @@ TerrainEngineNode::computeBound() const
 }
 
 void
-TerrainEngineNode::setVerticalScale( float value )
-{
-    _verticalScale = value;
-    onVerticalScaleChanged();
-}
-
-void
 TerrainEngineNode::onMapInfoEstablished( const MapInfo& mapInfo )
 {
     // set up the CSN values   
@@ -287,16 +282,20 @@ TerrainEngineNode::onMapInfoEstablished( const MapInfo& mapInfo )
 void
 TerrainEngineNode::onMapModelChanged( const MapModelChange& change )
 {
-    if ( _initStage == INIT_POSTINIT_COMPLETE )
+    if (change.getAction() == MapModelChange::ADD_LAYER &&
+        change.getImageLayer() != 0L)
     {
-        if ( change.getAction() == MapModelChange::ADD_IMAGE_LAYER )
-        {
-            change.getImageLayer()->addCallback( _imageLayerController.get() );
-        }
-        else if ( change.getAction() == MapModelChange::REMOVE_IMAGE_LAYER )
-        {
-            change.getImageLayer()->removeCallback( _imageLayerController.get() );
-        }
+        change.getImageLayer()->addCallback( _imageLayerController.get() );
+    }
+    else if (change.getAction() == MapModelChange::REMOVE_LAYER &&
+        change.getImageLayer() != 0L)
+    {
+        change.getImageLayer()->removeCallback( _imageLayerController.get() );
+    }
+
+    if (change.getElevationLayer() != 0L)
+    {
+        getTerrain()->notifyMapElevationChanged();
     }
 
     // notify that a redraw is required.
@@ -304,9 +303,11 @@ TerrainEngineNode::onMapModelChanged( const MapModelChange& change )
 }
 
 TerrainTileModel*
-TerrainEngineNode::createTileModel(const MapFrame&   frame,
-                                   const TileKey&    key,
-                                   ProgressCallback* progress)
+TerrainEngineNode::createTileModel(const MapFrame&              frame,
+                                   const TileKey&               key,
+                                   const CreateTileModelFilter& filter,
+                                   ProgressCallback*            progress
+    )
 {
     TerrainEngineRequirements* requirements = this;
 
@@ -314,7 +315,8 @@ TerrainEngineNode::createTileModel(const MapFrame&   frame,
     osg::ref_ptr<TerrainTileModel> model = _tileModelFactory->createTileModel(
         frame, 
         key, 
-        requirements, 
+        filter,
+        requirements,         
         progress);
 
     if ( model.valid() )
@@ -361,37 +363,25 @@ namespace
 
 void
 TerrainEngineNode::traverse( osg::NodeVisitor& nv )
-{
-    if ( nv.getVisitorType() == osg::NodeVisitor::CULL_VISITOR )
+{    
+    if ( nv.getVisitorType() == nv.EVENT_VISITOR )
     {
-        // see if we need to set up the Terrain object with an update ops queue.
-        if ( !_terrainInterface->_updateOperationQueue.valid() )
+        _dirtyCount = 0;
+        if (_updateScheduled == false && _terrainInterface->_updateQueue->empty() == false)
         {
-            Threading::ScopedMutexLock lock(s_opqlock);
-            if ( !_terrainInterface->_updateOperationQueue.valid() ) // double check pattern
-            {
-                //TODO: think, will this work with >1 view?
-                osgUtil::CullVisitor* cv = Culling::asCullVisitor(nv);
-                if ( cv->getCurrentCamera() )
-                {
-                    osgViewer::View* view = dynamic_cast<osgViewer::View*>(cv->getCurrentCamera()->getView());
-                    if ( view && view->getViewerBase() )
-                    {
-                        osg::OperationQueue* q = view->getViewerBase()->getUpdateOperations();
-                        if ( !q ) {
-                            q = new osg::OperationQueue();
-                            view->getViewerBase()->setUpdateOperations( q );
-                        }
-                        _terrainInterface->_updateOperationQueue = q;
-                    }
-                }
-            }
+            ADJUST_UPDATE_TRAV_COUNT(this, +1);
+            _updateScheduled = true;
         }
     }
 
-    else if ( nv.getVisitorType() == osg::NodeVisitor::EVENT_VISITOR )
+    else if (nv.getVisitorType() == nv.UPDATE_VISITOR)
     {
-        _dirtyCount = 0;
+        if (_updateScheduled == true )
+        {
+            _terrainInterface->update();
+            ADJUST_UPDATE_TRAV_COUNT(this, -1);
+            _updateScheduled = false;
+        }
     }
 
     osg::CoordinateSystemNode::traverse( nv );
@@ -408,18 +398,6 @@ TerrainEngineNode::notifyOfTerrainTileNodeCreation(const TileKey& key, osg::Node
     }
 }
 
-void
-TerrainEngineNode::addTilePatchCallback(TilePatchCallback* cb)
-{
-    _tilePatchCallbacks.push_back( cb );
-}
-
-void
-TerrainEngineNode::removeTilePatchCallback(TilePatchCallback* cb)
-{
-    std::remove(_tilePatchCallbacks.begin(), _tilePatchCallbacks.end(), cb);
-}
-
 ComputeRangeCallback*
 TerrainEngineNode::getComputeRangeCallback() const
 {
@@ -439,34 +417,24 @@ TerrainEngineNode::setComputeRangeCallback(ComputeRangeCallback* computeRangeCal
 #define LC "[TerrainEngineNodeFactory] "
 
 TerrainEngineNode*
-TerrainEngineNodeFactory::create( Map* map, const TerrainOptions& options )
+TerrainEngineNodeFactory::create(const TerrainOptions& options )
 {
-    TerrainEngineNode* result = 0L;
+    osg::ref_ptr<TerrainEngineNode> node;
+
+    std::string driver =
+        Registry::instance()->overrideTerrainEngineDriverName().getOrUse(options.getDriver());
 
-    std::string driver = options.getDriver();
     if ( driver.empty() )
         driver = Registry::instance()->getDefaultTerrainEngineDriverName();
 
     std::string driverExt = std::string( ".osgearth_engine_" ) + driver;
-    result = dynamic_cast<TerrainEngineNode*>( osgDB::readObjectFile( driverExt ) );
-    if ( !result )
+    osg::ref_ptr<osg::Object> object = osgDB::readRefObjectFile( driverExt );
+    node = dynamic_cast<TerrainEngineNode*>( object.release() );
+    if ( !node )
     {
         OE_WARN << "WARNING: Failed to load terrain engine driver for \"" << driver << "\"" << std::endl;
     }
 
-    return result;
-}
-
-//------------------------------------------------------------------------
-TerrainDecorator::~TerrainDecorator()
-{
-}
-
-void TerrainDecorator::onInstall( TerrainEngineNode* engine )
-{
-}
-
-void TerrainDecorator::onUninstall( TerrainEngineNode* engine )
-{
+    return node.release();
 }
 
diff --git a/src/osgEarth/TerrainEngineRequirements b/src/osgEarth/TerrainEngineRequirements
index 96a31b9..bf4e2a6 100644
--- a/src/osgEarth/TerrainEngineRequirements
+++ b/src/osgEarth/TerrainEngineRequirements
@@ -30,6 +30,8 @@ namespace osgEarth
         virtual bool elevationTexturesRequired() const =0;
         virtual bool normalTexturesRequired() const =0;
         virtual bool parentTexturesRequired() const =0;
+        virtual bool elevationBorderRequired() const =0;
+        virtual bool fullDataAtFirstLodRequired() const =0;
 
     public:
         virtual ~TerrainEngineRequirements() { }
diff --git a/src/osgEarth/TerrainLayer b/src/osgEarth/TerrainLayer
index de86cf8..d7a88c2 100644
--- a/src/osgEarth/TerrainLayer
+++ b/src/osgEarth/TerrainLayer
@@ -23,7 +23,7 @@
 #include <osgEarth/Common>
 #include <osgEarth/CachePolicy>
 #include <osgEarth/Config>
-#include <osgEarth/Layer>
+#include <osgEarth/VisibleLayer>
 #include <osgEarth/TileSource>
 #include <osgEarth/Profile>
 #include <osgEarth/ThreadingUtils>
@@ -39,20 +39,20 @@ namespace osgEarth
     /**
      * Initialization (and serializable) options for a terrain layer.
      */
-    class OSGEARTH_EXPORT TerrainLayerOptions : public ConfigOptions
+    class OSGEARTH_EXPORT TerrainLayerOptions : public VisibleLayerOptions
     {
     public:
-        TerrainLayerOptions( const ConfigOptions& options =ConfigOptions() );
-        TerrainLayerOptions( const std::string& name, const TileSourceOptions& driverOptions );
+        //! Construct empty (default) options.
+        TerrainLayerOptions();
 
-        /** dtor */
-        virtual ~TerrainLayerOptions() { }
+        //! Deserialize options from a Config structure
+        TerrainLayerOptions(const ConfigOptions& options);
 
-        /**
-         * The readable name of the layer.
-         */
-        std::string& name() { return _name; }
-        const std::string& name() const { return _name; }
+        //! Construct empty named options.
+        TerrainLayerOptions(const std::string& name);
+
+        //! Construct empty options with TileSource configuration.
+        TerrainLayerOptions(const std::string& name, const TileSourceOptions& driverOptions);
 
         /**
          * Gets the explicity vertical datum identifier that will override a vertical
@@ -93,20 +93,13 @@ namespace osgEarth
          */
         optional<double>& maxResolution() { return _maxResolution; }
         const optional<double>& maxResolution() const { return _maxResolution; }
-        
-        /**
-         * Whether to use this layer with the map. Setting this to false means that 
-         * the layer will remain in its map model but will not be used by the 
-         * terrain engine. You cannot change the "enabled" state at runtime.
-         */        
-        optional<bool>& enabled() { return _enabled; }
-        const optional<bool>& enabled() const { return _enabled; }
 
         /**
-         * Whether to render (or otherwise use) the layer.
+         * The maximum level of detail for which this layer should generate new data.
+         * Data from this layer will be upsampled in map tiles above the maxDataLevel.
          */
-        optional<bool>& visible() { return _visible; }
-        const optional<bool>& visible() const { return _visible; }
+        optional<unsigned>& maxDataLevel() { return _maxDataLevel; }
+        const optional<unsigned>& maxDataLevel() const { return _maxDataLevel; }
 
         /**
          * Whether to use exact cropping if image cropping is necessary
@@ -116,31 +109,12 @@ namespace osgEarth
 
         /**
          * The desired tile size to reproject imagery to if necessary.
+         * TODO: evaluate for possible deprecation
          */
         optional<unsigned int>& reprojectedTileSize() { return _reprojectedTileSize; }
         const optional<unsigned int>& reprojectedTileSize() const { return _reprojectedTileSize; }
 
         /**
-         * Explicit cache ID to uniquely identify the cache that matched this
-         * layer's tile source properties. This is usually automatically
-         * generated but you can override it here.
-         */
-        optional<std::string>& cacheId() { return _cacheId; }
-        const optional<std::string>& cacheId() const { return _cacheId; }
-
-        /**
-         * Caching policy to use for this layer.
-         */
-        optional<CachePolicy>& cachePolicy() { return _cachePolicy; }
-        const optional<CachePolicy>& cachePolicy() const { return _cachePolicy; }
-        
-        /**
-         * The loading weight of this MapLayer (for threaded loading policies).
-         */
-        optional<float>& loadingWeight() { return _loadingWeight; }
-        const optional<float>& loadingWeight() const { return _loadingWeight; }
-
-        /**
          * The ratio used to expand the extent of a tile when the layer
          * needs to be mosaiced to projected.  This can be used to increase the
          * number of tiles grabbed to ensure that enough data is grabbed to
@@ -149,110 +123,95 @@ namespace osgEarth
         optional<double>& edgeBufferRatio() { return _edgeBufferRatio;}
         const optional<double>& edgeBufferRatio() const { return _edgeBufferRatio; }
 
-        /** 
-         * The hostname/port of a proxy server to use for HTTP communications on this layer
-         * Default = no proxy.
-         */
+        //! The hostname/port of a proxy server to use for HTTP communications on this layer
+        //! Default = no proxy.
         optional<ProxySettings>& proxySettings() { return _proxySettings; }
         const optional<ProxySettings>& proxySettings() const { return _proxySettings; }
 
+        //! Number of samples in each dimension.
+        optional<unsigned>& tileSize() { return _tileSize; }
+        const optional<unsigned>& tileSize() const { return _tileSize; }
+
+        //! Value to treat as a "no data" marker.
+        optional<float>& noDataValue() { return _noDataValue; }
+        const optional<float>& noDataValue() const { return _noDataValue; }
+
+        //! Treat any value less than this as a "no data" marker.
+        optional<float>& minValidValue() { return _minValidValue; }
+        const optional<float>& minValidValue() const { return _minValidValue; }
+
+        //! Treat any value greater than this as a "no data" marker.
+        optional<float>& maxValidValue() { return _maxValidValue; }
+        const optional<float>& maxValidValue() const { return _maxValidValue; }
+
+
     public:
-        virtual Config getConfig() const { return getConfig(false); }
-        virtual Config getConfig( bool isolate ) const;
+        virtual Config getConfig() const; // { return getConfig(false); }
+        //virtual Config getConfig( bool isolate ) const;
         virtual void mergeConfig( const Config& conf );
 
     private:
-        void fromConfig( const Config& conf );
         void setDefaults();
+        void fromConfig( const Config& conf );
        
-        std::string                 _name;
         optional<std::string>       _vertDatum;
         optional<TileSourceOptions> _driver;
         optional<unsigned>          _minLevel;
         optional<unsigned>          _maxLevel;
         optional<double>            _minResolution;
         optional<double>            _maxResolution;
-        optional<float>             _loadingWeight;
+        optional<unsigned>          _maxDataLevel;
         optional<bool>              _exactCropping;
-        optional<bool>              _enabled;
-        optional<bool>              _visible;
         optional<unsigned>          _reprojectedTileSize;
         optional<double>            _edgeBufferRatio;
-        optional<unsigned>          _maxDataLevel;
-
-        optional<std::string>       _cacheId;
-        optional<CachePolicy>       _cachePolicy;
+        optional<unsigned>          _tileSize;
         optional<ProxySettings>     _proxySettings;
+        optional<float>             _noDataValue;
+        optional<float>             _minValidValue;
+        optional<float>             _maxValidValue;
     };
 
-    /**
-     * Runtime property notification callback for TerrainLayers.
-     */
-    struct TerrainLayerCallback : public osg::Referenced
+
+    struct TerrainLayerCallback : public VisibleLayerCallback
     {
-        virtual void onVisibleChanged( class TerrainLayer* layer ) { }
-        virtual ~TerrainLayerCallback() { }
+        typedef void(TerrainLayerCallback::*MethodPtr)(class TerrainLayer*);
     };
 
-    typedef void (TerrainLayerCallback::*TerrainLayerCallbackMethodPtr)(class TerrainLayer* layer);
-
-
     /**
      * A layer that comprises the terrain skin (image or elevation layer)
      */
-    class OSGEARTH_EXPORT TerrainLayer : public Layer
+    class OSGEARTH_EXPORT TerrainLayer : public VisibleLayer
     {
+    public:
+        META_Layer(osgEarth, TerrainLayer, TerrainLayerOptions);
+
     protected:
-        TerrainLayer( 
-            const TerrainLayerOptions& initOptions, 
-            TerrainLayerOptions*       runtimeOptions );
-        
-        TerrainLayer( 
-            const TerrainLayerOptions& initOptions, 
-            TerrainLayerOptions*       runtimeOptions,
-            TileSource*                tileSource );
+        //! Construct from subclass with a pointer to the concerete options structure.
+        TerrainLayer(TerrainLayerOptions* =0L);
+
+        //! Construct from subclass with pointer to concrete options and premade tile source
+        TerrainLayer(TerrainLayerOptions*, TileSource*);
 
+        //! DTOR
         virtual ~TerrainLayer();
 
-    public:
+    public: // Layer
 
         /** Opens the layer and initializes the data source. */
-        const Status& open();
+        virtual const Status& open();
 
-        /** Gets the status of this layer, which will be unset if open() has not been called. */
-        const Status& getStatus() const { return _status; }
+        //! Cache ID for this layer
+        virtual std::string getCacheID() const;
 
-        /**
-         * The options data connected to this layer.
-         */
-        const TerrainLayerOptions& getInitialOptions() const { return _initOptions; }
-
-        const TerrainLayerOptions& getTerrainLayerRuntimeOptions() const { return *_runtimeOptions; }
-
-        /**
-         * Whether to use this layer. Note, a layer is enabled/disabled once and its
-         * status cannot be changed.
-         */
-        bool getEnabled() const { return *_runtimeOptions->enabled(); }
+    public: // VisibleLayer
 
-        /**
-         * Whether to draw (or otherwise use) this layer.
-         */
-        void setVisible( bool value );
-        bool getVisible() const { return getEnabled() && *_runtimeOptions->visible(); }
-
-        /**
-         * Gets the readable name of the map layer.
-         */
-        const std::string& getName() const { return getTerrainLayerRuntimeOptions().name(); }
+        //! Override the opacity setter
+        virtual void setOpacity(float value);
 
-		/**
-		 * Sets the readable name of the map layer
-		 */
-		void setName( const std::string& name ) { _runtimeOptions->name() = name; }
+    public:
 
         /**
-         * Gets the profile of this MapLayer
+         * Gets the profile of this layer
          */
         const Profile* getProfile() const;
 
@@ -265,18 +224,13 @@ namespace osgEarth
          * Gets the size (i.e. number of samples in each dimension) or the source
          * data for this layer.
          */
-        unsigned getTileSize() const;
+        virtual unsigned getTileSize() const;
         
         /**
-         * Whether the layer represents dynamic data, i.e. tile data that can change.
-         */
-        bool isDynamic() const;
-
-        /**
-         * Whether the given key falls within the range limits set in the options;
-         * i.e. min/maxLevel or min/maxResolution.
+         * Whether the layer represents dynamic data, i.e. it generates data that requires
+         * an update traversal.
          */
-        virtual bool isKeyInRange(const TileKey& key) const;
+        virtual bool isDynamic() const;
 
         /** 
          * Whether the data for the specified tile key is in the cache.
@@ -301,18 +255,66 @@ namespace osgEarth
          */
         void setReadOptions(const osgDB::Options* readOptions);
 
+
+    public: // Data availability methods
+
+        /**
+         * Given a TileKey, returns a TileKey representing the best known available.
+         * For example, if the input TileKey exceeds the layer's max LOD, the return
+         * value will be an ancestor key at that max LOD.
+         */
+        virtual TileKey getBestAvailableTileKey(const TileKey& key) const;
+
+        /**
+         * Whether the layer possibly has data in the provided extent.
+         * Best guess given available information.
+         *
+         * @param extent Extent for which to test for data availability
+         * @return True or false
+         */
+        virtual bool mayHaveDataInExtent(const GeoExtent& e) const;
+
+        /**
+         * Whether the layer possibly has real data for the provided TileKey.
+         * Best guess given available information.
+         */
+        virtual bool mayHaveData(const TileKey& key) const;
+
+        /**
+         * Whether the given key falls within the range limits set in the options;
+         * i.e. min/maxLevel or min/maxResolution. (This does not mean that the key
+         * will result in data.)
+         */
+        virtual bool isKeyInLegalRange(const TileKey& key) const;
+
         /**
          * Data Extents reported for this layer are copied into output.
          * Returns true on success, false is there are no extents to report.
          */
-        bool getDataExtents(DataExtentList& output) const;
+        const DataExtentList& getDataExtents() const;
+
+        /**
+         * Gets an extent that is the union of all the extents in getDataExtents().
+         */
+        const GeoExtent& getDataExtentsUnion() const;
+
+
+    public: // Data interpretation methods
+
+        //! Special value representing "no data" in a location
+        virtual float getNoDataValue() const;
+
+        //! Values less than this will be reinterpreted as "no data"
+        virtual float getMinValidValue() const;
+
+        //! Values greater than this weill be reinterpreted as "no data"
+        virtual float getMaxValidValue() const;
 
 
     public: // Layer interface
 
         virtual SequenceControl* getSequenceControl();
 
-
     public:
         
         /**
@@ -352,24 +354,38 @@ namespace osgEarth
          */
         CacheSettings* getCacheSettings() const;
 
+    protected: // Layer
+
+        // CTOR initialization; call from subclass.
+        virtual void init();
+
+        //! Extent of this layer's data.
+        virtual const GeoExtent& getExtent() const;
+
     protected:
 
-        /** Creates the driver the supplies the actual data. Internal function. */
+        // Creates the driver the supplies the actual data.
+        // By default, this function will either return the tile source passed
+        // into the CTOR or it will try to load it from a plugin based on the
+        // driver specification in the TileSourceOptions. You can override this
+        // method to create your own tile source.
         virtual TileSource* createTileSource();
 
         void applyProfileOverrides();
 
         CacheBin* getCacheBin(const Profile* profile);
 
-        /** Subclass can call this to indicate an error. */
-        void setStatus(const Status& value) { _status = value; }
+        DataExtentList& dataExtents();
+
+        //! Call this if you call dataExtents() and modify it.
+        void dirtyDataExtents();
 
     protected:
 
         osg::ref_ptr<const Profile>    _targetProfileHint;
         unsigned                       _tileSize;  
-        osg::ref_ptr<osgDB::Options>   _readOptions;
         osg::ref_ptr<MemCache>         _memCache;
+        bool _openCalled;
 
         // profile from tile source or cache, before any overrides applied
         mutable osg::ref_ptr<const Profile> _profileOriginal;
@@ -377,37 +393,42 @@ namespace osgEarth
         // profile to use
         mutable osg::ref_ptr<const Profile> _profile;
 
-        // CTOR initialization; call from subclass.
-        void init();
-
         // cache key for metadata
         std::string getMetadataKey(const Profile*) const;
 
-    private:
-        std::string                    _name;
-        std::string                    _referenceURI;
-        TerrainLayerOptions            _initOptions;
-        TerrainLayerOptions*           _runtimeOptions;
+        // Called by a subclass before open() to indicate whether this
+        // layer should try to open a tile source and fail if unsuccesful.
+        void setTileSourceExpected(bool value) { _tileSourceExpected = value; }
 
-        mutable Threading::Mutex       _initTileSourceMutex;
-        osg::ref_ptr<TileSource>       _tileSource;
+        //! Whether this layer expected a tilesource to be installed
+        bool isTileSourceExpected() const { return _tileSourceExpected; }
 
-        std::string                    _cacheId;
+        //! Subclass can set a profile on this layer before opening
+        void setProfile(const Profile* profile);
+
+    private:
+        bool                     _tileSourceExpected;
+        mutable Threading::Mutex _initTileSourceMutex;
+        osg::ref_ptr<TileSource> _tileSource;
+        DataExtentList           _dataExtents;
+        mutable GeoExtent        _dataExtentsUnion;
+
+        // The cache ID used at runtime. This will either be the cacheId found in
+        // the TerrainLayerOptions, or a dynamic cacheID generated at runtime.
+        std::string _runtimeCacheId;
         
         // cache policy that may be automatically set by the layer and will 
         // override the runtime options policy if set.
-        optional<CachePolicy>          _effectiveCachePolicy;
+        optional<CachePolicy> _runtimeCachePolicy;
 
         typedef std::map<std::string, osg::ref_ptr<CacheBinMetadata> > CacheBinMetadataMap;
         CacheBinMetadataMap _cacheBinMetadata;
 
-        mutable Threading::Mutex    _mutex;
+        mutable Threading::Mutex _mutex;
 
         mutable osg::ref_ptr<CacheSettings> _cacheSettings;
 
-        bool _openCalled;
-
-        virtual void fireCallback( TerrainLayerCallbackMethodPtr method ) =0;
+        void fireCallback(TerrainLayerCallback::MethodPtr method);
 
         // methods accesible by Map:
         friend class Map;
@@ -417,7 +438,15 @@ namespace osgEarth
         // read the tile source's cache policy hint and apply as necessary
         void refreshTileSourceCachePolicyHint(TileSource*);
 
-        Status _status;
+        TileSource* createAndOpenTileSource();
+
+        // Figure out the cache settings for this layer.
+        void establishCacheSettings();
+
+    protected:
+        /** Closes the layer, deleting its tile source and any other resources. */
+        virtual void close();
+
     };
 
     typedef std::vector<osg::ref_ptr<TerrainLayer> > TerrainLayerVector;
diff --git a/src/osgEarth/TerrainLayer.cpp b/src/osgEarth/TerrainLayer.cpp
index 507ee1e..8fa92ea 100644
--- a/src/osgEarth/TerrainLayer.cpp
+++ b/src/osgEarth/TerrainLayer.cpp
@@ -36,114 +36,98 @@ using namespace OpenThreads;
 
 //------------------------------------------------------------------------
 
-TerrainLayerOptions::TerrainLayerOptions( const ConfigOptions& options ) :
-ConfigOptions       ( options ),
-_minLevel           ( 0 ),
-_maxLevel           ( 23 ),
-_cachePolicy        ( CachePolicy::DEFAULT ),
-_loadingWeight      ( 1.0f ),
-_exactCropping      ( false ),
-_enabled            ( true ),
-_visible            ( true ),
-_reprojectedTileSize( 256 ),
-_maxDataLevel       ( 99 )
+TerrainLayerOptions::TerrainLayerOptions() :
+VisibleLayerOptions()
 {
     setDefaults();
-    fromConfig( _conf ); 
+    fromConfig(_conf);
 }
 
-TerrainLayerOptions::TerrainLayerOptions( const std::string& name, const TileSourceOptions& driverOptions ) :
-ConfigOptions()
+TerrainLayerOptions::TerrainLayerOptions(const ConfigOptions& co) :
+VisibleLayerOptions(co)
 {
     setDefaults();
-    fromConfig( _conf );
-    _name = name;
+    fromConfig(_conf);
+}
+
+TerrainLayerOptions::TerrainLayerOptions(const std::string& layerName) :
+VisibleLayerOptions()
+{
+    setDefaults();
+    fromConfig(_conf);
+    name() = layerName;
+}
+
+TerrainLayerOptions::TerrainLayerOptions(const std::string& layerName, const TileSourceOptions& driverOptions) :
+VisibleLayerOptions(driverOptions)
+{
+    setDefaults();
+    fromConfig(_conf);
     _driver = driverOptions;
+    name() = layerName;
 }
 
 void
 TerrainLayerOptions::setDefaults()
 {
-    _enabled.init( true );
-    _visible.init( true );
     _exactCropping.init( false );
     _reprojectedTileSize.init( 256 );
-    _cachePolicy.init( CachePolicy() );
-    _loadingWeight.init( 1.0f );
     _minLevel.init( 0 );
     _maxLevel.init( 23 );
     _maxDataLevel.init( 99 );
+    _tileSize.init( 256 );
 }
 
 Config
-TerrainLayerOptions::getConfig( bool isolate ) const
+TerrainLayerOptions::getConfig() const
 {
-    Config conf = isolate ? ConfigOptions::newConfig() : ConfigOptions::getConfig();
-
-    conf.set("name", _name);
-    conf.updateIfSet( "min_level", _minLevel );
-    conf.updateIfSet( "max_level", _maxLevel );
-    conf.updateIfSet( "min_resolution", _minResolution );
-    conf.updateIfSet( "max_resolution", _maxResolution );
-    conf.updateIfSet( "loading_weight", _loadingWeight );
-    conf.updateIfSet( "enabled", _enabled );
-    conf.updateIfSet( "visible", _visible );
-    conf.updateIfSet( "edge_buffer_ratio", _edgeBufferRatio);
-    conf.updateIfSet( "reprojected_tilesize", _reprojectedTileSize);
-    conf.updateIfSet( "max_data_level", _maxDataLevel );
-
-    conf.updateIfSet( "vdatum", _vertDatum );
-
-    conf.updateIfSet   ( "cacheid",      _cacheId );
-    conf.updateObjIfSet( "proxy",        _proxySettings );
-
-    if ( _cachePolicy.isSet() && !_cachePolicy->empty() )
-        conf.updateObjIfSet( "cache_policy", _cachePolicy );
-
-    // Merge the TileSource options
-    if ( !isolate && driver().isSet() )
-        conf.merge( driver()->getConfig() );
+    Config conf = VisibleLayerOptions::getConfig();
+
+    conf.set( "min_level", _minLevel );
+    conf.set( "max_level", _maxLevel );
+    conf.set( "min_resolution", _minResolution );
+    conf.set( "max_resolution", _maxResolution );
+    conf.set( "max_data_level", _maxDataLevel );
+    conf.set( "edge_buffer_ratio", _edgeBufferRatio);
+    conf.set( "reprojected_tilesize", _reprojectedTileSize);
+    conf.set( "vdatum", _vertDatum );
+    conf.setObj( "proxy", _proxySettings );
+    conf.set("no_data_value", _noDataValue);
+    conf.set("min_valid_value", _minValidValue);
+    conf.set("max_valid_value", _maxValidValue);
+    conf.set( "tile_size", _tileSize);
 
     return conf;
 }
 
 void
-TerrainLayerOptions::fromConfig( const Config& conf )
+TerrainLayerOptions::fromConfig(const Config& conf)
 {
-    _name = conf.value("name");
     conf.getIfSet( "min_level", _minLevel );
     conf.getIfSet( "max_level", _maxLevel );        
     conf.getIfSet( "min_resolution", _minResolution );
     conf.getIfSet( "max_resolution", _maxResolution );
-    conf.getIfSet( "loading_weight", _loadingWeight );
-    conf.getIfSet( "enabled", _enabled );
-    conf.getIfSet( "visible", _visible );
+    conf.getIfSet( "max_data_level", _maxDataLevel );
     conf.getIfSet( "edge_buffer_ratio", _edgeBufferRatio);    
     conf.getIfSet( "reprojected_tilesize", _reprojectedTileSize);
-    conf.getIfSet( "max_data_level", _maxDataLevel );
-
     conf.getIfSet( "vdatum", _vertDatum );
     conf.getIfSet( "vsrs", _vertDatum );    // back compat
-
-    conf.getIfSet   ( "cacheid",      _cacheId );
-    conf.getObjIfSet( "cache_policy", _cachePolicy );
     conf.getObjIfSet( "proxy",        _proxySettings );
+    conf.getIfSet("no_data_value", _noDataValue);
+    conf.getIfSet("nodata_value", _noDataValue); // back compat
+    conf.getIfSet("min_valid_value", _minValidValue);
+    conf.getIfSet("max_valid_value", _maxValidValue);
+    conf.getIfSet( "tile_size", _tileSize);
 
-    // legacy support:
-    if ( conf.value<bool>( "cache_only", false ) == true )
-        _cachePolicy->usage() = CachePolicy::USAGE_CACHE_ONLY;
-    if ( conf.value<bool>( "cache_enabled", true ) == false )
-        _cachePolicy->usage() = CachePolicy::USAGE_NO_CACHE;
-
-    if ( conf.hasValue("driver") )
+    if (conf.hasValue("driver"))
         driver() = TileSourceOptions(conf);
 }
 
 void
-TerrainLayerOptions::mergeConfig( const Config& conf )
+TerrainLayerOptions::mergeConfig(const Config& conf)
 {
-    ConfigOptions::mergeConfig( conf );
-    fromConfig( conf );
+    VisibleLayerOptions::mergeConfig(conf);
+    fromConfig(conf);
 }
 
 //------------------------------------------------------------------------
@@ -257,26 +241,23 @@ TerrainLayer::CacheBinMetadata::getConfig() const
 
 //------------------------------------------------------------------------
 
-TerrainLayer::TerrainLayer(const TerrainLayerOptions& initOptions,
-                           TerrainLayerOptions*       runtimeOptions ) :
-_initOptions   ( initOptions ),
-_runtimeOptions( runtimeOptions ),
-_openCalled( false ),
-_tileSize( 256 )
+TerrainLayer::TerrainLayer(TerrainLayerOptions* optionsPtr) :
+VisibleLayer(optionsPtr ? optionsPtr : &_optionsConcrete),
+_options(optionsPtr ? optionsPtr : &_optionsConcrete),
+_openCalled(false),
+_tileSourceExpected(true)
 {
-    // nop
+    //nop - init() called by subclass
 }
 
-TerrainLayer::TerrainLayer(const TerrainLayerOptions& initOptions,
-                           TerrainLayerOptions*       runtimeOptions,
-                           TileSource*                tileSource ) :
-_initOptions   ( initOptions ),
-_runtimeOptions( runtimeOptions ),
-_tileSource    ( tileSource ),
-_openCalled( false ),
-_tileSize( 256 )
+TerrainLayer::TerrainLayer(TerrainLayerOptions* optionsPtr, TileSource* tileSource) :
+VisibleLayer(optionsPtr ? optionsPtr : &_optionsConcrete),
+_options(optionsPtr ? optionsPtr : &_optionsConcrete),
+_tileSource(tileSource),
+_openCalled(false),
+_tileSourceExpected(true)
 {
-    // nop
+    //nop - init() called by subclass
 }
 
 TerrainLayer::~TerrainLayer()
@@ -287,184 +268,182 @@ TerrainLayer::~TerrainLayer()
 void
 TerrainLayer::init()
 {    
-    // intiailize out read-options, which store caching and IO information.
+    Layer::init();
+
+    // intiailize our read-options, which store caching and IO information.
     setReadOptions(0L);
 
-    // Create an L2 mem cache that sits atop the main cache, if necessary.
-    // For now: use the same L2 cache size at the driver.
-    int l2CacheSize = _initOptions.driver()->L2CacheSize().get();
-    
-    // See if it was overridden with an env var.
-    char const* l2env = ::getenv( "OSGEARTH_L2_CACHE_SIZE" );
-    if ( l2env )
-    {
-        l2CacheSize = as<int>( std::string(l2env), 0 );
-        OE_INFO << LC << "L2 cache size set from environment = " << l2CacheSize << "\n";
-    }
+    if (options().tileSize().isSet())
+        _tileSize = options().tileSize().get();
+    else
+        _tileSize = 256;
+}
 
-    // Env cache-only mode also disables the L2 cache.
-    char const* noCacheEnv = ::getenv( "OSGEARTH_MEMORY_PROFILE" );
-    if ( noCacheEnv )
+const Status&
+TerrainLayer::open()
+{
+    if ( !_openCalled )
     {
-        l2CacheSize = 0;
-    }
+        // Call base class
+        if (VisibleLayer::open().isError())
+            return getStatus();
 
-    // Initialize the l2 cache if it's size is > 0
-    if ( l2CacheSize > 0 )
-    {
-        _memCache = new MemCache( l2CacheSize );
-    }
+        // Create an L2 mem cache that sits atop the main cache, if necessary.
+        // For now: use the same L2 cache size at the driver.
+        int l2CacheSize = options().driver()->L2CacheSize().get();
+    
+        // See if it was overridden with an env var.
+        char const* l2env = ::getenv( "OSGEARTH_L2_CACHE_SIZE" );
+        if ( l2env )
+        {
+            l2CacheSize = as<int>( std::string(l2env), 0 );
+            OE_INFO << LC << "L2 cache size set from environment = " << l2CacheSize << "\n";
+        }
+
+        // Env cache-only mode also disables the L2 cache.
+        char const* noCacheEnv = ::getenv( "OSGEARTH_MEMORY_PROFILE" );
+        if ( noCacheEnv )
+        {
+            l2CacheSize = 0;
+        }
 
-    // create the unique cache ID for the cache bin.
-    std::string cacheId;
+        // Initialize the l2 cache if it's size is > 0
+        if ( l2CacheSize > 0 )
+        {
+            _memCache = new MemCache( l2CacheSize );
+        }
 
-    if (_runtimeOptions->cacheId().isSet() && !_runtimeOptions->cacheId()->empty())
-    {
-        // user expliticy set a cacheId in the terrain layer options.
-        // this appears to be a NOP; review for removal -gw
-        cacheId = *_runtimeOptions->cacheId();
-    }
-    else
-    {
-        // system will generate a cacheId.
-        // technically, this is not quite right, we need to remove everything that's
-        // an image layer property and just use the tilesource properties.
-        Config layerConf = _runtimeOptions->getConfig(true);
-        Config driverConf = _runtimeOptions->driver()->getConfig();
-        Config hashConf = driverConf - layerConf;
+        // create the unique cache ID for the cache bin.
+        //std::string cacheId;
 
-        // remove cache-control properties before hashing.
-        hashConf.remove("cache_only");
-        hashConf.remove("cache_enabled");
-        hashConf.remove("cache_policy");
-        hashConf.remove("cacheid");
-        hashConf.remove("l2_cache_size");
+        if (options().cacheId().isSet() && !options().cacheId()->empty())
+        {
+            // user expliticy set a cacheId in the terrain layer options.
+            // this appears to be a NOP; review for removal -gw
+            _runtimeCacheId = options().cacheId().get();
+        }
+        else
+        {
+            // system will generate a cacheId from the layer configuration.
+            Config hashConf = options().getConfig();
+
+            // remove non-data properties.
+            hashConf.remove("name");
+            hashConf.remove("enabled");
+            hashConf.remove("cacheid");
+            hashConf.remove("cache_only");
+            hashConf.remove("cache_enabled");
+            hashConf.remove("cache_policy");
+            hashConf.remove("visible");
+            hashConf.remove("l2_cache_size");
+
+            OE_DEBUG << "hashConfFinal = " << hashConf.toJSON(true) << std::endl;
+
+            unsigned hash = osgEarth::hashString(hashConf.toJSON());
+            _runtimeCacheId = Stringify() << std::hex << std::setw(8) << std::setfill('0') << hash;
+        }
 
-        // need this, b/c data is vdatum-transformed before caching.
-        if (layerConf.hasValue("vdatum"))
-            hashConf.add("vdatum", layerConf.value("vdatum"));
+        // Now that we know the cache ID, establish the cache settings for this Layer.
+        // Start by cloning whatever CacheSettings were inherited in the read options
+        // (typically from the Map).
+        CacheSettings* oldSettings = CacheSettings::get(_readOptions.get());
+        _cacheSettings = oldSettings ? new CacheSettings(*oldSettings) : new CacheSettings();
 
-        unsigned hash = osgEarth::hashString(hashConf.toJSON());
-        cacheId = Stringify() << std::hex << std::setw(8) << std::setfill('0') << hash;
+        // Store for further propagation!
+        _cacheSettings->store(_readOptions.get());
 
-        _runtimeOptions->cacheId().init(cacheId); // set as default value
-    }
-}
+        // Integrate a cache policy from this Layer's options:
+        _cacheSettings->integrateCachePolicy(options().cachePolicy());
 
-const Status&
-TerrainLayer::open()
-{
-    if ( !_openCalled )
-    {
-        CacheSettings* cacheSettings = getCacheSettings(); // guaranteed to return non-null
 
-        Threading::ScopedMutexLock lock(_mutex);
-        if (!_openCalled)
+        // If you created the layer with a pre-created tile source, it will already by set.
+        if (!_tileSource.valid())
         {
-            // If you created the layer with a pre-created tile source, it will already by set.
-            if (!_tileSource.valid())
-            {
-                osg::ref_ptr<TileSource> ts;
+            osg::ref_ptr<TileSource> ts;
 
-                // as long as we're not in cache-only mode, try to create the TileSource.
-                if (cacheSettings->cachePolicy()->isCacheOnly())
-                {
-                    OE_INFO << LC << "Opening in cache-only mode\n";
-                }
-                else
-                {
-                    // Initialize the tile source once and only once.
-                    ts = createTileSource();
-                }
-
-                if ( ts.valid() )
-                {
-                    if (cacheSettings->isCacheEnabled())
-                    {
-                        // read the cache policy hint from the tile source unless user expressly set 
-                        // a policy in the initialization options. In other words, the hint takes
-                        // ultimate priority (even over the Registry override) unless expressly
-                        // overridden in the layer options!
-                        refreshTileSourceCachePolicyHint( ts.get() );
-
-                        // Unless the user has already configured an expiration policy, use the "last modified"
-                        // timestamp of the TileSource to set a minimum valid cache entry timestamp.
-                        CachePolicy& cp = _runtimeOptions->cachePolicy().mutable_value();
-                        if ( !cp.minTime().isSet() && !cp.maxAge().isSet() && ts->getLastModifiedTime() > 0)
-                        {
-                            // The "effective" policy overrides the runtime policy, but it does not get serialized.
-                            _effectiveCachePolicy = cp;
-                            _effectiveCachePolicy->minTime() = ts->getLastModifiedTime();
-                            OE_INFO << LC << "driver says min valid timestamp = " << DateTime(*cp.minTime()).asRFC1123() << "\n";
-                        }
-                    }
+            // as long as we're not in cache-only mode, try to create the TileSource.
+            if (_cacheSettings->cachePolicy()->isCacheOnly())
+            {
+                OE_INFO << LC << "Opening in cache-only mode\n";
+            }
+            else if (isTileSourceExpected())
+            {
+                // Initialize the tile source once and only once.
+                ts = createAndOpenTileSource();
+            }
 
-                    // All is well - set the tile source.
-                    if ( !_tileSource.valid() )
-                    {
-                        _tileSource = ts.release();
-                    }
-                }
+            // All good
+            if (ts.valid() && !_tileSource.valid())
+            {
+                _tileSource = ts.release();
             }
-            else
+        }
+        else
+        {
+            // User supplied the tile source, so attempt to get its profile:
+            setProfile(_tileSource->getProfile() );
+            if (!_profile.valid())
             {
-                // User supplied the tile source, so attempt to get its profile:
-                _profile = _tileSource->getProfile();
-                if (!_profile.valid())
-                {
-                    setStatus( Status::Error(getName(), "Cannot establish profile") );
-                }
+                setStatus( Status::Error(getName(), "Cannot establish profile") );
             }
+        }
 
-            _openCalled = true;
-                        
-            OE_INFO << LC << cacheSettings->toString() << "\n";
+        // Finally, open and activate a caching bin for this layer if it
+        // hasn't already been created.
+        if (_cacheSettings->isCacheEnabled() && _cacheSettings->getCacheBin() == 0L)
+        {
+            CacheBin* bin = _cacheSettings->getCache()->addBin(_runtimeCacheId);
+            if (bin)
+            {
+                _cacheSettings->setCacheBin(bin);
+                OE_INFO << LC << "Cache bin is [" << bin->getID() << "]\n";
+            }
         }
 
+        OE_INFO << LC << _cacheSettings->toString() << "\n";
+
+        // Done!
+        _openCalled = true;
+                        
     }
 
-    return _status;
-    //return _runtimeOptions->enabled() == true;
+    return getStatus();
 }
 
-CacheSettings*
-TerrainLayer::getCacheSettings() const
+void
+TerrainLayer::close()
 {
-    if (!_cacheSettings.valid())
-    {
-        Threading::ScopedMutexLock lock(_mutex);
-        if (!_cacheSettings.valid())
-        {
-            // clone the existing one if it exists:
-            CacheSettings* oldSettings = CacheSettings::get(_readOptions.get());
-            _cacheSettings = oldSettings ? new CacheSettings(*oldSettings) : new CacheSettings();
-
-            // install the effective policy (which comes from the tile source). We don't call
-            // integrateCachePolicy here because we want this to take precedend over the registry's
-            // global overrides.
-            _cacheSettings->cachePolicy()->mergeAndOverride(_effectiveCachePolicy);
-
-            // install the layer policy
-            _cacheSettings->integrateCachePolicy(_runtimeOptions->cachePolicy());
+    setProfile(0L);
+    _tileSource = 0L;
+    _openCalled = false;
+    setStatus(Status());
+    _readOptions = 0L;
+    _cacheSettings = new CacheSettings();
+}
 
-            // if all it well, open and activate a caching bin for this layer.
-            if (_cacheSettings->isCacheEnabled())
-            {
-                CacheBin* bin = _cacheSettings->getCache()->addBin(_runtimeOptions->cacheId().get());
-                if (bin)
-                {
-                    _cacheSettings->setCacheBin(bin);
-                    OE_INFO << LC << "Cache bin is [" << bin->getID() << "]\n";
-                }
-            }
+void
+TerrainLayer::establishCacheSettings()
+{
+    //nop
+}
 
-            _cacheSettings->store(_readOptions.get());
-        }
-    }
+CacheSettings*
+TerrainLayer::getCacheSettings() const
+{
     return _cacheSettings.get();
 }
 
 void
+TerrainLayer::setOpacity(float value)
+{
+    // For terrain layers, we don't want to install the transparency program
+    // nor mess with the rendering bins. The terrain engine will handle
+    // opacity on its own
+    options().opacity() = value;
+    VisibleLayer::fireCallback(&VisibleLayerCallback::onOpacityChanged);
+}
+
+void
 TerrainLayer::setTargetProfileHint( const Profile* profile )
 {
     _targetProfileHint = profile;
@@ -476,7 +455,7 @@ TerrainLayer::setTargetProfileHint( const Profile* profile )
 void
 TerrainLayer::refreshTileSourceCachePolicyHint(TileSource* ts)
 {
-    if ( ts && getCacheSettings() && !_initOptions.cachePolicy().isSet() )
+    if ( ts && getCacheSettings() && !options().cachePolicy().isSet() )
     {
         CachePolicy hint = ts->getCachePolicyHint( _targetProfileHint.get() );
 
@@ -500,12 +479,10 @@ TerrainLayer::getProfile() const
     return _profile.get();
 }
 
-unsigned
-TerrainLayer::getTileSize() const
+void
+TerrainLayer::setProfile(const Profile* profile)
 {
-    // force tile source initialization (which sets _tileSize)
-    //getTileSource();
-    return _tileSize; //ts ? ts->getPixelsPerTile() : _tileSize;
+    _profile = profile;
 }
 
 bool
@@ -529,7 +506,7 @@ TerrainLayer::getCacheBin(const Profile* profile)
 {
     if ( !_openCalled )
     {
-        OE_WARN << LC << "Illegal- called getCacheBin() before calling open()\n";
+        OE_WARN << LC << "Illegal- called getCacheBin() before layer is open.. did you call open()?\n";
         return 0L;
     }
 
@@ -552,7 +529,7 @@ TerrainLayer::getCacheBin(const Profile* profile)
     CacheBinMetadataMap::iterator i = _cacheBinMetadata.find(metaKey);
     if (i == _cacheBinMetadata.end())
     {
-        std::string cacheId = _runtimeOptions->cacheId().get();
+        //std::string cacheId = _runtimeOptions->cacheId().get();
 
         // read the metadata record from the cache bin:
         ReadResult rr = bin->readString(metaKey, _readOptions.get());
@@ -593,7 +570,7 @@ TerrainLayer::getCacheBin(const Profile* profile)
                 {
                     // in cacheonly mode, create a profile from the first cache bin accessed
                     // (they SHOULD all be the same...)
-                    _profile = Profile::create( meta->_sourceProfile.get() );
+                    setProfile( Profile::create(meta->_sourceProfile.get()) );
                     _tileSize = meta->_sourceTileSize.get();
                 }
 
@@ -607,24 +584,29 @@ TerrainLayer::getCacheBin(const Profile* profile)
 
         if (!metadataOK)
         {
-            // cache metadata does not exist, so try to create it. A valid TileSource is necessary for this.
-            if ( getTileSource() && getProfile() )
+            // cache metadata does not exist, so try to create it.
+            if ( getProfile() )
             {
                 meta = new CacheBinMetadata();
 
                 // no existing metadata; create some.
-                meta->_cacheBinId      = cacheId;
+                meta->_cacheBinId      = _runtimeCacheId;
                 meta->_sourceName      = this->getName();
-                meta->_sourceDriver    = getTileSource()->getOptions().getDriver();
                 meta->_sourceTileSize  = getTileSize();
                 meta->_sourceProfile   = getProfile()->toProfileOptions();
                 meta->_cacheProfile    = profile->toProfileOptions();
                 meta->_cacheCreateTime = DateTime().asTimeStamp();
-                meta->_dataExtents     = getTileSource()->getDataExtents();
+                meta->_dataExtents     = getDataExtents();
+
+                if (getTileSource())
+                {
+                    meta->_sourceDriver = getTileSource()->getOptions().getDriver();
+                }
 
                 // store it in the cache bin.
                 std::string data = meta->getConfig().toJSON(false);
-                bin->write(metaKey, new StringObject(data), _readOptions.get());                   
+                osg::ref_ptr<StringObject> temp = new StringObject(data);
+                bin->write(metaKey, temp.get(), _readOptions.get());                   
 
                 bin->setMetadata(meta.get());
             }
@@ -633,7 +615,7 @@ TerrainLayer::getCacheBin(const Profile* profile)
             {
                 disable(Stringify() <<
                     "Failed to open a cache for layer "
-                    "because cache_only policy is in effect and bin [" << cacheId << "] "
+                    "because cache_only policy is in effect and bin [" << _runtimeCacheId << "] "
                     "could not be located.");
 
                 return 0L;
@@ -642,8 +624,8 @@ TerrainLayer::getCacheBin(const Profile* profile)
             else
             {
                 OE_WARN << LC <<
-                    "Failed to create cache bin [" << cacheId << "] "
-                    "because there is no valid tile source."
+                    "Failed to create cache bin [" << _runtimeCacheId << "] "
+                    "because there is no valid profile."
                     << std::endl;
 
                 cacheSettings->cachePolicy() = CachePolicy::NO_CACHE;
@@ -657,7 +639,7 @@ TerrainLayer::getCacheBin(const Profile* profile)
         if (meta.valid())
         {
             _cacheBinMetadata[metaKey] = meta.get();
-            OE_DEBUG << LC << "Established metadata for cache bin [" << cacheId << "]" << std::endl;
+            OE_DEBUG << LC << "Established metadata for cache bin [" << _runtimeCacheId << "]" << std::endl;
         }
     }
 
@@ -667,11 +649,7 @@ TerrainLayer::getCacheBin(const Profile* profile)
 void
 TerrainLayer::disable(const std::string& msg)
 {
-    if (!_runtimeOptions->enabled().isSetTo(false))
-    {
-        _runtimeOptions->enabled() = false;
-        _status = Status::Error(msg);
-    }
+    setStatus(Status::Error(msg));
 }
 
 TerrainLayer::CacheBinMetadata*
@@ -688,6 +666,21 @@ TerrainLayer::getCacheBinMetadata(const Profile* profile)
 
 TileSource*
 TerrainLayer::createTileSource()
+{    
+    if (options().driver().isSet())
+    {
+        OE_INFO << LC << "Creating \"" << options().driver()->getDriver() << "\" driver\n";
+
+        return TileSourceFactory::create(options().driver().get());
+    }
+    else
+    {
+        return 0L;
+    }
+}
+
+TileSource*
+TerrainLayer::createAndOpenTileSource()
 {
     osg::ref_ptr<TileSource> ts;
 
@@ -699,17 +692,12 @@ TerrainLayer::createTileSource()
 
     else
     {
-        // Instantiate it from driver options if it has not already been created.
-        // This will also set a manual "override" profile if the user provided one.
-        if ( _runtimeOptions->driver().isSet() )
+        ts = createTileSource();
+
+        if (!ts.valid())
         {
-            OE_INFO << LC << "Creating \"" << _runtimeOptions->driver()->getDriver() << "\" driver\n";
-            ts = TileSourceFactory::create( *_runtimeOptions->driver() );
-            if ( !ts.valid() )
-            {
-                setStatus(Status::Error(Status::ServiceUnavailable, Stringify()<<"Failed to find driver \"" << _runtimeOptions->driver()->getDriver() << "\"\n"));
-                return 0L;
-            }
+            setStatus(Status::Error(Status::ServiceUnavailable, "Failed to load tile source plugin"));
+            return 0L;
         }
     }
 
@@ -736,6 +724,35 @@ TerrainLayer::createTileSource()
             OE_INFO << LC << "Override profile: "  << ts->getProfile()->toString() << std::endl;
         }
 
+        // Now that the tile source exists, set up the cache.
+        if (_cacheSettings->isCacheEnabled())
+        {
+            // read the cache policy hint from the tile source unless user expressly set 
+            // a policy in the initialization options. In other words, the hint takes
+            // ultimate priority (even over the Registry override) unless expressly
+            // overridden in the layer options!
+            refreshTileSourceCachePolicyHint( ts.get() );
+
+            // Unless the user has already configured an expiration policy, use the "last modified"
+            // timestamp of the TileSource to set a minimum valid cache entry timestamp.
+            const CachePolicy& cp = options().cachePolicy().get();
+
+            if ( !cp.minTime().isSet() && !cp.maxAge().isSet() && ts->getLastModifiedTime() > 0)
+            {
+                // The "effective" policy overrides the runtime policy, but it does not get serialized.
+                _cacheSettings->cachePolicy()->mergeAndOverride( cp );
+                _cacheSettings->cachePolicy()->minTime() = ts->getLastModifiedTime();
+                OE_INFO << LC << "driver says min valid timestamp = " << DateTime(*cp.minTime()).asRFC1123() << "\n";
+            }
+            
+            CacheBin* bin = _cacheSettings->getCache()->addBin(_runtimeCacheId);
+            if (bin)
+            {
+                _cacheSettings->setCacheBin(bin);
+                OE_INFO << LC << "Cache bin is [" << bin->getID() << "]\n";
+            }
+        }
+
         // Open the tile source (if it hasn't already been started)
         tileSourceStatus = ts->getStatus();
         if (!tileSourceStatus.isOK())
@@ -743,9 +760,24 @@ TerrainLayer::createTileSource()
             tileSourceStatus = ts->open(TileSource::MODE_READ, _readOptions.get());
         }
 
+        // Now that the tile source is open and ready, propagate any user-set
+        // properties to and fro.
         if ( tileSourceStatus.isOK() )
         {
-            _tileSize = ts->getPixelsPerTile();
+            if (options().tileSize().isSet())
+                ts->setPixelsPerTile(options().tileSize().get());
+
+            if (!ts->getDataExtents().empty())
+                _dataExtents = ts->getDataExtents();
+
+            if (options().noDataValue().isSet())
+                ts->setNoDataValue(options().noDataValue().get());
+
+            if (options().minValidValue().isSet())
+                ts->setMinValidValue(options().minValidValue().get());
+
+            if (options().maxValidValue().isSet())
+                ts->setMaxValidValue(options().maxValidValue().get());
         }
         else
         {
@@ -757,27 +789,26 @@ TerrainLayer::createTileSource()
     // Set the profile from the TileSource if possible:
     if ( ts.valid() )
     {
-        if ( !_profile.valid() )
+        if (!_profile.valid())
         {
             OE_DEBUG << LC << "Get Profile from tile source" << std::endl;
-            _profile = ts->getProfile();
+            setProfile(ts->getProfile());
         }
 
 
-        if ( _profile.valid() )
+        if (_profile.valid())
         {
             // create the final profile from any overrides:
             applyProfileOverrides();
-
             OE_INFO << LC << "Profile=" << _profile->toString() << std::endl;
         }
     }
 
     // Otherwise, force cache-only mode (since there is no tilesource). The layer will try to 
     // establish a profile from the metadata in the cache instead.
-    else if (getCacheSettings()->isCacheEnabled())
+    else if (!tileSourceStatus.isError() && getCacheSettings()->isCacheEnabled())
     {
-        OE_NOTICE << LC << "Failed to create \"" << _runtimeOptions->driver()->getDriver() << "\" driver, but a cache may exist, so falling back on cache-only mode." << std::endl;
+        OE_NOTICE << LC << "Failed to create \"" << options().driver()->getDriver() << "\" driver, but a cache may exist, so falling back on cache-only mode." << std::endl;
         getCacheSettings()->cachePolicy() = CachePolicy::CACHE_ONLY;
     }
 
@@ -788,10 +819,6 @@ TerrainLayer::createTileSource()
         disable(tileSourceStatus.message());
         setStatus(tileSourceStatus);
     }
-    //if (!ts.valid() && getCacheSettings()->isCacheDisabled())
-    //{
-    //    disable("Could not initialize TileSource and no cache is available");
-    //}
 
     return ts.release();
 }
@@ -801,15 +828,15 @@ TerrainLayer::applyProfileOverrides()
 {
     // Check for a vertical datum override.
     bool changed = false;
-    if ( _profile.valid() && _runtimeOptions->verticalDatum().isSet() )
+    if ( _profile.valid() && options().verticalDatum().isSet() )
     {
-        std::string vdatum = *_runtimeOptions->verticalDatum();
+        std::string vdatum = options().verticalDatum().get();
         OE_INFO << "override vdatum = " << vdatum << ", profile vdatum = " << _profile->getSRS()->getVertInitString() << std::endl;
         if ( !ciEquals(_profile->getSRS()->getVertInitString(), vdatum) )
         {
             ProfileOptions po = _profile->toProfileOptions();
             po.vsrsString() = vdatum;
-            _profile = Profile::create(po);
+            setProfile( Profile::create(po) );
             changed = true;
         }
     }
@@ -821,23 +848,78 @@ TerrainLayer::applyProfileOverrides()
 }
 
 bool
-TerrainLayer::isKeyInRange(const TileKey& key) const
+TerrainLayer::mayHaveDataInExtent(const GeoExtent& ex) const
+{
+    if (!ex.isValid())
+    {
+        // bad extent; no data
+        return false;
+    }
+    
+    const DataExtentList& de = getDataExtents();
+    if (de.empty())
+    {
+        // not enough info, assume yes
+        return true;
+    }
+
+    // Get extent in local profile:
+    GeoExtent localExtent = ex;
+    if (getProfile() && !getProfile()->getSRS()->isHorizEquivalentTo(ex.getSRS()))
+    {
+        localExtent = getProfile()->clampAndTransformExtent(ex);
+    }
+
+    // Check union:
+    if (getDataExtentsUnion().intersects(localExtent))
+    {
+        // possible yes
+        return true;
+    }
+
+    // Check each extent in turn:
+    for (DataExtentList::const_iterator i = de.begin(); i != de.end(); ++i)
+    {
+        if (i->intersects(localExtent))
+        {
+            // possible yes
+            return true;
+        }
+    }
+
+    // definite no.
+    return false;
+}
+
+bool
+TerrainLayer::isKeyInLegalRange(const TileKey& key) const
 {    
     if ( !key.valid() )
     {
         return false;
     }
 
+    // We must use the equivalent lod b/c the input key can be in any profile.
+    unsigned localLOD = getProfile() ?
+        getProfile()->getEquivalentLOD(key.getProfile(), key.getLOD()) :
+        key.getLOD();
+
+
     // First check the key against the min/max level limits, it they are set.
-    if ((_runtimeOptions->maxLevel().isSet() && key.getLOD() > _runtimeOptions->maxLevel().value()) ||
-        (_runtimeOptions->minLevel().isSet() && key.getLOD() < _runtimeOptions->minLevel().value()))
+    if ((options().maxLevel().isSet() && localLOD > options().maxLevel().value()) ||
+        (options().minLevel().isSet() && localLOD < options().minLevel().value()))
+    {
+        return false;
+    }
+
+    // Next check the maxDataLevel if that is set.
+    if (options().maxDataLevel().isSet() && localLOD > options().maxDataLevel().get())
     {
         return false;
     }
 
     // Next, check against resolution limits (based on the source tile size).
-    if (_runtimeOptions->minResolution().isSet() ||
-        _runtimeOptions->maxResolution().isSet())
+    if (options().minResolution().isSet() || options().maxResolution().isSet())
     {
         const Profile* profile = getProfile();
         if ( profile )
@@ -847,14 +929,14 @@ TerrainLayer::isKeyInRange(const TileKey& key) const
             double resKey   = key.getExtent().width() / (double)getTileSize();
             double resLayer = key.getProfile()->getSRS()->transformUnits(resKey, profile->getSRS());
 
-            if (_runtimeOptions->maxResolution().isSet() &&
-                _runtimeOptions->maxResolution().value() > resLayer)
+            if (options().maxResolution().isSet() &&
+                options().maxResolution().value() > resLayer)
             {
                 return false;
             }
 
-            if (_runtimeOptions->minResolution().isSet() &&
-                _runtimeOptions->minResolution().value() < resLayer)
+            if (options().minResolution().isSet() &&
+                options().minResolution().value() < resLayer)
             {
                 return false;
             }
@@ -883,53 +965,93 @@ TerrainLayer::isCached(const TileKey& key) const
 }
 
 void
-TerrainLayer::setVisible( bool value )
-{
-    _runtimeOptions->visible() = value;
-    fireCallback( &TerrainLayerCallback::onVisibleChanged );
-}
-
-void
 TerrainLayer::setReadOptions(const osgDB::Options* readOptions)
 {
     // clone the options, or create it not set
     _readOptions = Registry::cloneOrCreateOptions(readOptions);
+    //Layer::setReadOptions(readOptions);
 
     // store HTTP proxy settings in the options:
-    storeProxySettings( _readOptions );
+    storeProxySettings( _readOptions.get() );
     
     // store the referrer for relative-path resolution
-    URIContext( _runtimeOptions->referrer() ).store( _readOptions.get() );
+    URIContext( options().referrer() ).store( _readOptions.get() );
 
     Threading::ScopedMutexLock lock(_mutex);
-    _cacheSettings = 0L;
+    _cacheSettings = new CacheSettings();
     _cacheBinMetadata.clear();
 }
 
-bool
-TerrainLayer::getDataExtents(DataExtentList& output) const
+std::string
+TerrainLayer::getCacheID() const
 {
-    output.clear();
+    return _runtimeCacheId;
+}
 
-    // if a tile source is available, get the extents directly from it:
-    if (getTileSource())
-        output = getTileSource()->getDataExtents();
+const DataExtentList&
+TerrainLayer::getDataExtents() const
+{
+    if (!_dataExtents.empty())
+    {
+        return _dataExtents;
+    }
 
-    // otherwise, try the cache. Extents are the same regardless of
-    // profile so just use the first one available:
     else if (!_cacheBinMetadata.empty())
-        output = _cacheBinMetadata.begin()->second->_dataExtents;
+    {
+        // There are extents in the cache bin, so use those. 
+        // The DE's are the same regardless of profile so just use the first one in there.
+        return _cacheBinMetadata.begin()->second->_dataExtents;
+    }
+
+    else
+    {
+        return _dataExtents;
+    }
+}
+
+void
+TerrainLayer::dirtyDataExtents()
+{
+    Threading::ScopedMutexLock lock(_mutex);
+    _dataExtentsUnion = GeoExtent::INVALID;
+}
+
+const GeoExtent&
+TerrainLayer::getDataExtentsUnion() const
+{
+    const DataExtentList& de = getDataExtents();
+
+    if (_dataExtentsUnion.isInvalid() && !de.empty())
+    {
+        Threading::ScopedMutexLock lock(_mutex);
+        {
+            if (_dataExtentsUnion.isInvalid() && !de.empty()) // double-check
+            {
+                GeoExtent e(de[0]);
+                for (unsigned int i = 1; i < de.size(); i++)
+                {
+                    e.expandToInclude(de[i]);
+                }
+                _dataExtentsUnion = e;
+            }
+        }
+    }
+    return _dataExtentsUnion;
+}
 
-    return !output.empty();
+const GeoExtent&
+TerrainLayer::getExtent() const
+{
+    return getDataExtentsUnion();
 }
 
 void
 TerrainLayer::storeProxySettings(osgDB::Options* readOptions)
 {
     //Store the proxy settings in the options structure.
-    if (_initOptions.proxySettings().isSet())
+    if (options().proxySettings().isSet())
     {        
-        _initOptions.proxySettings()->apply( readOptions );
+        options().proxySettings()->apply( readOptions );
     }
 }
 
@@ -938,3 +1060,140 @@ TerrainLayer::getSequenceControl()
 {
     return dynamic_cast<SequenceControl*>( getTileSource() );
 }
+
+TileKey
+TerrainLayer::getBestAvailableTileKey(const TileKey& key) const
+{
+    // trivial reject
+    if ( !key.valid() )
+        return TileKey::INVALID;
+
+    unsigned MDL = options().maxDataLevel().get();
+
+    // We must use the equivalent lod b/c the input key can be in any profile.
+    unsigned localLOD = getProfile() ?
+        getProfile()->getEquivalentLOD(key.getProfile(), key.getLOD()) :
+        key.getLOD();
+
+    // Check against level extrema:
+    if (localLOD < options().minLevel().get() || localLOD > options().maxLevel().get())
+    {
+        return TileKey::INVALID;
+    }
+
+    // Next, check against resolution limits (based on the source tile size).
+    if (options().minResolution().isSet() || options().maxResolution().isSet())
+    {
+        const Profile* profile = getProfile();
+        if ( profile )
+        {
+            // calculate the resolution in the layer's profile, which can
+            // be different that the key's profile.
+            double resKey   = key.getExtent().width() / (double)getTileSize();
+            double resLayer = key.getProfile()->getSRS()->transformUnits(resKey, profile->getSRS());
+
+            if (options().maxResolution().isSet() &&
+                options().maxResolution().value() > resLayer)
+            {
+                return TileKey::INVALID;
+            }
+
+            if (options().minResolution().isSet() &&
+                options().minResolution().value() < resLayer)
+            {
+                return TileKey::INVALID;
+            }
+        }
+    }
+
+    // Next check against the data extents.
+    const DataExtentList& de = getDataExtents();
+
+    // If we have mo data extents available, just return the MDL-limited input key.
+    if (de.empty())
+    {
+        return localLOD > MDL ? key.createAncestorKey(MDL) : key;
+    }
+
+    // Reject if the extents don't overlap at all.
+    if (!getDataExtentsUnion().intersects(key.getExtent()))
+    {
+        return TileKey::INVALID;
+    }
+
+    bool     intersects = false;
+    unsigned highestLOD = 0;
+    
+    // Check each data extent in turn:
+    for (DataExtentList::const_iterator itr = de.begin(); itr != de.end(); ++itr)
+    {
+        // check for 2D intersection:
+        if (key.getExtent().intersects(*itr))
+        {
+            // check that the extent isn't higher-resolution than our key:
+            if ( !itr->minLevel().isSet() || localLOD >= (int)itr->minLevel().get() )
+            {
+                // Got an intersetion; now test the LODs:
+                intersects = true;
+
+                // Is the high-LOD set? If not, there's not enough information
+                // so just assume our key might be good.
+                if ( itr->maxLevel().isSet() == false )
+                {
+                    return localLOD > MDL ? key.createAncestorKey(MDL) : key;
+                }
+
+                // Is our key at a lower or equal LOD than the max key in this extent?
+                // If so, our key is good.
+                else if ( localLOD <= (int)itr->maxLevel().get() )
+                {
+                    return localLOD > MDL ? key.createAncestorKey(MDL) : key;
+                }
+
+                // otherwise, record the highest encountered LOD that
+                // intersects our key.
+                else if ( itr->maxLevel().get() > highestLOD )
+                {
+                    highestLOD = itr->maxLevel().get();
+                }
+            }
+        }
+    }
+
+    if ( intersects )
+    {
+        return key.createAncestorKey(std::min(highestLOD, MDL));
+    }
+
+    return TileKey::INVALID;
+}
+
+bool
+TerrainLayer::mayHaveData(const TileKey& key) const
+{
+    return key == getBestAvailableTileKey(key);
+}
+
+unsigned
+TerrainLayer::getTileSize() const
+{
+    return getTileSource() ? getTileSource()->getPixelsPerTile() : options().tileSize().get();
+}
+
+float
+TerrainLayer::getNoDataValue() const
+{
+    return getTileSource() ? getTileSource()->getNoDataValue() : options().noDataValue().get();
+}
+
+float
+TerrainLayer::getMinValidValue() const
+{
+    return getTileSource() ? getTileSource()->getMinValidValue() : options().minValidValue().get();
+}
+
+float
+TerrainLayer::getMaxValidValue() const
+{
+    return getTileSource() ? getTileSource()->getMaxValidValue() : options().maxValidValue().get();
+}
diff --git a/src/osgEarth/TerrainOptions b/src/osgEarth/TerrainOptions
index a3db9c2..bbdf456 100644
--- a/src/osgEarth/TerrainOptions
+++ b/src/osgEarth/TerrainOptions
@@ -26,6 +26,7 @@
 #include <osgEarth/Config>
 #include <osgEarth/GeoCommon>
 #include <osg/Texture>
+#include <osg/LOD>
 
 namespace osgEarth
 {    
@@ -66,7 +67,7 @@ namespace osgEarth
 
         /**
          * The minimum tile LOD range as a factor of the tile's radius.
-         * Default = 6.0.
+         * Default = 7.0.
          */
         optional<float>& minTileRangeFactor() { return _minTileRangeFactor; }
         const optional<float>& minTileRangeFactor() const { return _minTileRangeFactor; }
@@ -76,7 +77,7 @@ namespace osgEarth
          * Default = 0.
          */
         optional<float>& attenuationDistance() { return _attenuationDistance; }
-        const optional<float>& attentuationDistance() const { return _attenuationDistance; }
+        const optional<float>& attenuationDistance() const { return _attenuationDistance; }
 
         /**
          * Transition time, in seconds, for tile image fade-in when LOD blending is enabled
@@ -189,6 +190,20 @@ namespace osgEarth
 
         optional<double>& minExpiryTime() { return _minExpiryTime; }
         const optional<double>& minExpiryTime() const { return _minExpiryTime; }
+
+        /**
+         * Whether the terrain should cast shadows - default is false
+         */
+        optional<bool>& castShadows() { return _castShadows; }
+        const optional<bool>& castShadows() const { return _castShadows; }
+
+        /** Mode to use when calculating LOD switching distances */
+        optional<osg::LOD::RangeMode>& rangeMode() { return _rangeMode;}
+        const optional<osg::LOD::RangeMode>& rangeMode() const { return _rangeMode;}
+
+        /** The size of the tile, in pixels, when using rangeMode = PIXEL_SIZE_ON_SCREEN */
+        optional<float>& tilePixelSize() { return _tilePixelSize; }
+        const optional<float>& tilePixelSize() const { return _tilePixelSize; }
    
     public:
         virtual Config getConfig() const;
@@ -228,6 +243,9 @@ namespace osgEarth
         optional<int> _binNumber;
         optional<int> _minExpiryFrames;
         optional<double> _minExpiryTime;
+        optional<bool> _castShadows;
+        optional<osg::LOD::RangeMode> _rangeMode;
+        optional<float>               _tilePixelSize;
     };
 }
 
diff --git a/src/osgEarth/TerrainOptions.cpp b/src/osgEarth/TerrainOptions.cpp
index 71e949b..a4404a5 100644
--- a/src/osgEarth/TerrainOptions.cpp
+++ b/src/osgEarth/TerrainOptions.cpp
@@ -47,7 +47,10 @@ _magFilter( osg::Texture::LINEAR),
 _minNormalMapLOD( 0u ),
 _gpuTessellation( false ),
 _debug( false ),
-_binNumber( 0 )
+_binNumber( 0 ),
+_castShadows(false),
+_rangeMode(osg::LOD::DISTANCE_FROM_EYE_POINT),
+_tilePixelSize(256)
 {
     fromConfig( _conf );
 }
@@ -58,41 +61,45 @@ TerrainOptions::getConfig() const
     Config conf = DriverConfigOptions::getConfig();
     conf.key() = "terrain";
     
-    conf.updateIfSet( "tile_size", _tileSize );
-    conf.updateIfSet( "vertical_scale", _verticalScale );
-    conf.updateIfSet( "vertical_offset", _verticalOffset );
-    conf.updateIfSet( "min_tile_range_factor", _minTileRangeFactor );
-    conf.updateIfSet( "range_factor", _minTileRangeFactor );  
-    conf.updateIfSet( "max_lod", _maxLOD );
-    conf.updateIfSet( "min_lod", _minLOD );
-    conf.updateIfSet( "first_lod", _firstLOD );
-    conf.updateIfSet( "lighting", _enableLighting );
-    conf.updateIfSet( "attenuation_distance", _attenuationDistance );
-    conf.updateIfSet( "lod_transition_time", _lodTransitionTimeSeconds );
-    conf.updateIfSet( "mipmapping", _enableMipmapping );
-    conf.updateIfSet( "cluster_culling", _clusterCulling );
-    conf.updateIfSet( "blending", _enableBlending );
-    conf.updateIfSet( "mercator_fast_path", _mercatorFastPath );
-    conf.updateIfSet( "min_normal_map_lod", _minNormalMapLOD );
-    conf.updateIfSet( "gpu_tessellation", _gpuTessellation );
-    conf.updateIfSet( "debug", _debug );
-    conf.updateIfSet( "bin_number", _binNumber );
-    conf.updateIfSet( "min_expiry_time", _minExpiryTime);
-    conf.updateIfSet( "min_expiry_frames", _minExpiryFrames);
+    conf.set( "tile_size", _tileSize );
+    conf.set( "vertical_scale", _verticalScale );
+    conf.set( "vertical_offset", _verticalOffset );
+    conf.set( "min_tile_range_factor", _minTileRangeFactor );
+    conf.set( "range_factor", _minTileRangeFactor );  
+    conf.set( "max_lod", _maxLOD );
+    conf.set( "min_lod", _minLOD );
+    conf.set( "first_lod", _firstLOD );
+    conf.set( "lighting", _enableLighting );
+    conf.set( "attenuation_distance", _attenuationDistance );
+    conf.set( "lod_transition_time", _lodTransitionTimeSeconds );
+    conf.set( "mipmapping", _enableMipmapping );
+    conf.set( "cluster_culling", _clusterCulling );
+    conf.set( "blending", _enableBlending );
+    conf.set( "mercator_fast_path", _mercatorFastPath );
+    conf.set( "min_normal_map_lod", _minNormalMapLOD );
+    conf.set( "gpu_tessellation", _gpuTessellation );
+    conf.set( "debug", _debug );
+    conf.set( "bin_number", _binNumber );
+    conf.set( "min_expiry_time", _minExpiryTime);
+    conf.set( "min_expiry_frames", _minExpiryFrames);
+    conf.set("cast_shadows", _castShadows);
+    conf.set("tile_pixel_size", _tilePixelSize);
+    conf.set("range_mode", "PIXEL_SIZE_ON_SCREEN", _rangeMode, osg::LOD::PIXEL_SIZE_ON_SCREEN);
+    conf.set("range_mode", "DISTANCE_FROM_EYE_POINT", _rangeMode, osg::LOD::DISTANCE_FROM_EYE_POINT);
 
     //Save the filter settings
-	conf.updateIfSet("mag_filter","LINEAR",                _magFilter,osg::Texture::LINEAR);
-    conf.updateIfSet("mag_filter","LINEAR_MIPMAP_LINEAR",  _magFilter,osg::Texture::LINEAR_MIPMAP_LINEAR);
-    conf.updateIfSet("mag_filter","LINEAR_MIPMAP_NEAREST", _magFilter,osg::Texture::LINEAR_MIPMAP_NEAREST);
-    conf.updateIfSet("mag_filter","NEAREST",               _magFilter,osg::Texture::NEAREST);
-    conf.updateIfSet("mag_filter","NEAREST_MIPMAP_LINEAR", _magFilter,osg::Texture::NEAREST_MIPMAP_LINEAR);
-    conf.updateIfSet("mag_filter","NEAREST_MIPMAP_NEAREST",_magFilter,osg::Texture::NEAREST_MIPMAP_NEAREST);
-    conf.updateIfSet("min_filter","LINEAR",                _minFilter,osg::Texture::LINEAR);
-    conf.updateIfSet("min_filter","LINEAR_MIPMAP_LINEAR",  _minFilter,osg::Texture::LINEAR_MIPMAP_LINEAR);
-    conf.updateIfSet("min_filter","LINEAR_MIPMAP_NEAREST", _minFilter,osg::Texture::LINEAR_MIPMAP_NEAREST);
-    conf.updateIfSet("min_filter","NEAREST",               _minFilter,osg::Texture::NEAREST);
-    conf.updateIfSet("min_filter","NEAREST_MIPMAP_LINEAR", _minFilter,osg::Texture::NEAREST_MIPMAP_LINEAR);
-    conf.updateIfSet("min_filter","NEAREST_MIPMAP_NEAREST",_minFilter,osg::Texture::NEAREST_MIPMAP_NEAREST);
+	conf.set("mag_filter","LINEAR",                _magFilter,osg::Texture::LINEAR);
+    conf.set("mag_filter","LINEAR_MIPMAP_LINEAR",  _magFilter,osg::Texture::LINEAR_MIPMAP_LINEAR);
+    conf.set("mag_filter","LINEAR_MIPMAP_NEAREST", _magFilter,osg::Texture::LINEAR_MIPMAP_NEAREST);
+    conf.set("mag_filter","NEAREST",               _magFilter,osg::Texture::NEAREST);
+    conf.set("mag_filter","NEAREST_MIPMAP_LINEAR", _magFilter,osg::Texture::NEAREST_MIPMAP_LINEAR);
+    conf.set("mag_filter","NEAREST_MIPMAP_NEAREST",_magFilter,osg::Texture::NEAREST_MIPMAP_NEAREST);
+    conf.set("min_filter","LINEAR",                _minFilter,osg::Texture::LINEAR);
+    conf.set("min_filter","LINEAR_MIPMAP_LINEAR",  _minFilter,osg::Texture::LINEAR_MIPMAP_LINEAR);
+    conf.set("min_filter","LINEAR_MIPMAP_NEAREST", _minFilter,osg::Texture::LINEAR_MIPMAP_NEAREST);
+    conf.set("min_filter","NEAREST",               _minFilter,osg::Texture::NEAREST);
+    conf.set("min_filter","NEAREST_MIPMAP_LINEAR", _minFilter,osg::Texture::NEAREST_MIPMAP_LINEAR);
+    conf.set("min_filter","NEAREST_MIPMAP_NEAREST",_minFilter,osg::Texture::NEAREST_MIPMAP_NEAREST);
 
     return conf;
 }
@@ -121,6 +128,10 @@ TerrainOptions::fromConfig( const Config& conf )
     conf.getIfSet( "bin_number", _binNumber );
     conf.getIfSet( "min_expiry_time", _minExpiryTime);
     conf.getIfSet( "min_expiry_frames", _minExpiryFrames);
+    conf.getIfSet("cast_shadows", _castShadows);
+    conf.getIfSet("tile_pixel_size", _tilePixelSize);
+    conf.getIfSet("range_mode", "PIXEL_SIZE_ON_SCREEN", _rangeMode, osg::LOD::PIXEL_SIZE_ON_SCREEN);
+    conf.getIfSet("range_mode", "DISTANCE_FROM_EYE_POINT", _rangeMode, osg::LOD::DISTANCE_FROM_EYE_POINT);
 
     //Load the filter settings
 	conf.getIfSet("mag_filter","LINEAR",                _magFilter,osg::Texture::LINEAR);
diff --git a/src/osgEarth/TerrainResources b/src/osgEarth/TerrainResources
new file mode 100644
index 0000000..deff433
--- /dev/null
+++ b/src/osgEarth/TerrainResources
@@ -0,0 +1,138 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+ * Copyright 2016 Pelican Mapping
+ * http://osgearth.org
+ *
+ * osgEarth is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+#ifndef OSGEARTH_TEXTURE_COMPOSITOR_H
+#define OSGEARTH_TEXTURE_COMPOSITOR_H 1
+
+#include <osgEarth/Common>
+#include <osgEarth/ThreadingUtils>
+#include <osg/observer_ptr>
+
+namespace osgEarth
+{
+    class Layer;
+    class TextureImageUnitReservation;
+
+    /**
+     * Utility class that manages GPU resources for a terrain engine
+     */
+    class OSGEARTH_EXPORT TerrainResources : public osg::Referenced
+    {
+    public:
+        /**
+         * Constructs a new resource tracker object.
+         */
+        TerrainResources();
+
+        /** dtor */
+        virtual ~TerrainResources() { }
+
+        /**
+         * Requests a texture image unit that is not in use, and marks is as reserved.
+         * You must release the reserved texture image unit by calling releaseTextureImageUnit().
+         * @param out_unit  Reserved unit is written to this variable.
+         * @param requestor Optional requestor string (for information purposes only)
+         */
+        bool reserveTextureImageUnit(
+            int&        out_unit,
+            const char* requestor =0L );
+
+        /**
+         * Requests a texture image unit that is not in use, and marks is as reserved
+         * for the specified Layer. The unit will still be available for reservation
+         * by other layers (but not globally).
+         * You must release the reserved texture image unit by calling releaseTextureImageUnit().
+         * @param out_unit  Reserved unit is written to this variable.
+         * @param requestor Optional requestor string (for information purposes only)
+         */
+        bool reserveTextureImageUnit(
+            int& out_unit,
+            const Layer* layer,
+            const char* requestor =0L);
+        
+        /**
+         * Requests a texture image unit that is not in use, and marks is as reserved.
+         * The reserveration is released when the "reservation" object goes out of
+         * scope and destructs.
+         * @param reservation Reserved unit is written to this variable.
+         * @param requestor   Optional requestor string (for information purposes only)
+         */
+        bool reserveTextureImageUnit(
+            TextureImageUnitReservation& reservation,
+            const char* requestor =0L);
+        
+        /**
+         * Requests a texture image unit that is not in use, and marks is as reserved
+         * for the specific Layer. The unit will still be available for other layers
+         * (but not globally).
+         * The reserveration is released when the "reservation" object goes out of
+         * scope and destructs.
+         * @param reservation Reserved unit is written to this variable.
+         * @param requestor   Optional requestor string (for information purposes only)
+         */
+        bool reserveTextureImageUnitForLayer(
+            TextureImageUnitReservation& reservation,
+            const Layer* layer,
+            const char* requestor =0L);
+
+        /**
+         * Releases a reserved texture image unit previously returned by reserveTextureImageUnit.
+         */
+        void releaseTextureImageUnit(int unit);
+
+        /**
+         * Releases a reserved texture image unit previously reserved by calling
+         * reserveTextureImageUnit(int, Layer). 
+         */
+        void releaseTextureImageUnit(int unit, const Layer* layer);
+
+        /**
+         * Marks a specific texture image as reserved. Only call this if your application
+         * is known to use a particular texture image unit and you don't want osgEarth
+         * touching it.
+         *
+         * Returns true upon success, and false if the unit has already been reserved.
+         */
+        bool setTextureImageUnitOffLimits(int unit);
+
+    private:
+        Threading::Mutex _reservedUnitsMutex;
+
+        typedef std::set<int> ReservedUnits;
+        ReservedUnits _globallyReservedUnits;
+
+        typedef std::map<const Layer*, ReservedUnits> PerLayerReservedUnits;
+        PerLayerReservedUnits _perLayerReservedUnits;
+    };
+
+    class OSGEARTH_EXPORT TextureImageUnitReservation
+    {
+    public:
+        TextureImageUnitReservation();
+        virtual ~TextureImageUnitReservation();
+        int unit() const { return _unit; }
+        bool valid() const { return _unit >= 0; }
+    private:
+        int _unit;
+        osg::observer_ptr<TerrainResources> _res;
+        const Layer* _layer; // only need the raw ptr.
+        friend class TerrainResources;
+    };
+}
+
+#endif // OSGEARTH_TEXTURE_COMPOSITOR_H
diff --git a/src/osgEarth/TerrainResources.cpp b/src/osgEarth/TerrainResources.cpp
new file mode 100755
index 0000000..d80e303
--- /dev/null
+++ b/src/osgEarth/TerrainResources.cpp
@@ -0,0 +1,262 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+ * Copyright 2016 Pelican Mapping
+ * http://osgearth.org
+ *
+ * osgEarth is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+
+#include <osgEarth/TerrainResources>
+#include <osgEarth/Registry>
+#include <osgEarth/Capabilities>
+#include <osgEarth/Layer>
+
+using namespace osgEarth;
+
+#define LC "[TerrainResources] "
+
+
+TerrainResources::TerrainResources()
+{
+    //nop
+}
+
+bool
+TerrainResources::reserveTextureImageUnit(int&        out_unit,
+                                          const char* requestor)
+{
+    out_unit = -1;
+    unsigned maxUnits = osgEarth::Registry::instance()->getCapabilities().getMaxGPUTextureUnits();
+    
+    Threading::ScopedMutexLock exclusiveLock( _reservedUnitsMutex );
+    
+    // first collect a list of units that are already in use.
+    std::set<int> taken;
+    taken.insert(_globallyReservedUnits.begin(), _globallyReservedUnits.end());
+    for (PerLayerReservedUnits::const_iterator i = _perLayerReservedUnits.begin();
+        i != _perLayerReservedUnits.end();
+        ++i)
+    {
+        taken.insert(i->second.begin(), i->second.end());
+    }
+
+    // now find the first unused one.
+    for( unsigned i=0; i<maxUnits; ++i )
+    {
+        if (taken.find(i) == taken.end())
+        {
+            _globallyReservedUnits.insert( i );
+            out_unit = i;
+            if ( requestor )
+            {
+                OE_INFO << LC << "Texture unit " << i << " reserved for " << requestor << "\n";
+            }
+            return true;
+        }
+    }
+    return false;
+}
+
+bool
+TerrainResources::reserveTextureImageUnit(int&         out_unit,
+                                          const Layer* layer,
+                                          const char*  requestor)
+{
+    OE_DEPRECATED(reserveTextureImageUnit, reserveTextureImageUnitForLayer) << std::endl;
+
+    if (layer == 0L)
+    {
+        return reserveTextureImageUnit(out_unit, requestor);
+    }
+
+    out_unit = -1;
+    unsigned maxUnits = osgEarth::Registry::instance()->getCapabilities().getMaxGPUTextureUnits();
+    
+    Threading::ScopedMutexLock exclusiveLock( _reservedUnitsMutex );
+    
+    // first collect a list of units that are already in use.
+    std::set<int> taken;
+    taken.insert(_globallyReservedUnits.begin(), _globallyReservedUnits.end());
+    ReservedUnits& layerUnits = _perLayerReservedUnits[layer];
+    taken.insert(layerUnits.begin(), layerUnits.end());
+
+    // now find the first unused one.
+    for( unsigned i=0; i<maxUnits; ++i )
+    {
+        if (taken.find(i) == taken.end())
+        {
+            layerUnits.insert( i );
+            out_unit = i;
+            if ( requestor )
+            {
+                OE_INFO << LC << "Texture unit " << i << " reserved by Layer "
+                    << layer->getName() << " for " << requestor << "\n";
+            }
+            return true;
+        }
+    }
+    return false;
+}
+
+bool
+TerrainResources::reserveTextureImageUnit(TextureImageUnitReservation& reservation,
+                                          const char* requestor)
+{
+    reservation._unit = -1;
+    unsigned maxUnits = osgEarth::Registry::instance()->getCapabilities().getMaxGPUTextureUnits();
+    
+    Threading::ScopedMutexLock exclusiveLock( _reservedUnitsMutex );
+    
+    // first collect a list of units that are already in use.
+    std::set<int> taken;
+    taken.insert(_globallyReservedUnits.begin(), _globallyReservedUnits.end());
+    for (PerLayerReservedUnits::const_iterator i = _perLayerReservedUnits.begin();
+        i != _perLayerReservedUnits.end();
+        ++i)
+    {
+        taken.insert(i->second.begin(), i->second.end());
+    }
+
+    for( unsigned i=0; i<maxUnits; ++i )
+    {
+        if (taken.find(i) == taken.end())
+        {
+            _globallyReservedUnits.insert( i );
+            reservation._unit = i;
+            reservation._layer = 0L;
+            reservation._res = this;
+            if ( requestor )
+            {
+                OE_INFO << LC << "Texture unit " << i << " reserved for " << requestor << "\n";
+            }
+            return true;
+        }
+    }
+    return false;
+}
+
+bool
+TerrainResources::reserveTextureImageUnitForLayer(TextureImageUnitReservation& reservation,
+                                                  const Layer* layer,
+                                                  const char* requestor)
+{
+    if (layer == 0L)
+    {
+        OE_WARN << LC << "ILLEGAL USAGE: layer must be non-null\n";
+        return false;
+    }
+
+    reservation._unit = -1;
+    unsigned maxUnits = osgEarth::Registry::instance()->getCapabilities().getMaxGPUTextureUnits();
+    
+    Threading::ScopedMutexLock exclusiveLock( _reservedUnitsMutex );
+    
+    // first collect a list of units that are already in use.
+    std::set<int> taken;
+    taken.insert(_globallyReservedUnits.begin(), _globallyReservedUnits.end());
+    ReservedUnits& layerReservedUnits = _perLayerReservedUnits[layer];
+    taken.insert(layerReservedUnits.begin(), layerReservedUnits.end());
+
+    for( unsigned i=0; i<maxUnits; ++i )
+    {
+        if (taken.find(i) == taken.end())
+        {
+            layerReservedUnits.insert( i );
+            reservation._unit = i;
+            reservation._layer = layer;
+            reservation._res = this;
+            if ( requestor )
+            {
+                OE_INFO << LC << "Texture unit " << i << " reserved (on layer "
+                    << layer->getName() << ") for " << requestor << "\n";
+            }
+            return true;
+        }
+    }
+    return false;
+}
+
+void
+TerrainResources::releaseTextureImageUnit(int unit)
+{
+    Threading::ScopedMutexLock exclusiveLock( _reservedUnitsMutex );
+    _globallyReservedUnits.erase( unit );
+    OE_INFO << LC << "Texture unit " << unit << " released\n";
+}
+
+void
+TerrainResources::releaseTextureImageUnit(int unit, const Layer* layer)
+{
+    if (layer == 0L)
+        releaseTextureImageUnit(unit);
+
+    Threading::ScopedMutexLock exclusiveLock( _reservedUnitsMutex );
+    PerLayerReservedUnits::iterator i = _perLayerReservedUnits.find(layer);
+    if (i != _perLayerReservedUnits.end())
+    {
+        ReservedUnits& reservedUnits = i->second;
+        reservedUnits.erase(unit);
+
+        // if there are no more units reserved for this layer, remove the record entirely
+        if (reservedUnits.empty())
+        {
+            _perLayerReservedUnits.erase(i);
+        }
+
+        OE_INFO << LC << "Texture unit " << unit << " released (by layer " << layer->getName() << ")\n";
+    }
+}
+
+bool
+TerrainResources::setTextureImageUnitOffLimits(int unit)
+{
+    Threading::ScopedMutexLock exclusiveLock( _reservedUnitsMutex );
+
+    // Make sure it's not already reserved:
+    if (_globallyReservedUnits.find(unit) != _globallyReservedUnits.end())
+    {
+        // no good! Already in use globally.
+        return false;
+    }
+
+    for (PerLayerReservedUnits::const_iterator i = _perLayerReservedUnits.begin();
+        i != _perLayerReservedUnits.end();
+        ++i)
+    {
+        if (i->second.find(unit) != i->second.end())
+        {
+            // no good! Already in use by a layer.
+            return false;
+        }
+    }
+       
+    _globallyReservedUnits.insert( unit );
+    return true;
+}
+
+//........................................................................
+TextureImageUnitReservation::TextureImageUnitReservation()
+{
+    _unit = -1;
+    _layer = 0L;
+}
+
+TextureImageUnitReservation::~TextureImageUnitReservation()
+{
+    osg::ref_ptr<TerrainResources> res;
+    if (_unit >= 0 && _res.lock(res))
+    {
+        res->releaseTextureImageUnit(_unit, _layer);
+    }
+}
diff --git a/src/osgEarth/TerrainTileModel b/src/osgEarth/TerrainTileModel
index 27d5c26..efc933d 100644
--- a/src/osgEarth/TerrainTileModel
+++ b/src/osgEarth/TerrainTileModel
@@ -22,6 +22,7 @@
 #include <osgEarth/Common>
 #include <osgEarth/TileKey>
 #include <osgEarth/ImageLayer>
+#include <osgEarth/PatchLayer>
 #include <osgEarth/Revisioning>
 #include <osgEarth/MapInfo>
 #include <osgEarth/Locators>
@@ -32,6 +33,7 @@
 
 namespace osgEarth
 {
+    //! @deprecated
     class TerrainData : public osg::Referenced
     {
     public:
@@ -55,7 +57,8 @@ namespace osgEarth
         virtual ~TerrainData() { }
     };
 
-
+    
+    //! @deprecated
     class OSGEARTH_EXPORT TerrainRaster : public TerrainData
     {
     public:
@@ -111,29 +114,67 @@ namespace osgEarth
     typedef std::vector< osg::ref_ptr<TerrainTileLayerModel> > TerrainTileLayerModelVector;
 
     /**
+     * Layer based on a color layer (ImageLayer or any other RENDERTYPE_TILE layer)
+     */
+    class OSGEARTH_EXPORT TerrainTileColorLayerModel : public TerrainTileLayerModel
+    {
+    public:
+        TerrainTileColorLayerModel() : TerrainTileLayerModel() { }
+
+        void setLayer(Layer* layer) { _layer = layer; }
+        Layer* getLayer() const { return _layer.get(); }
+
+    protected:
+        virtual ~TerrainTileColorLayerModel() { }
+        osg::ref_ptr<Layer> _layer;
+    };
+    typedef std::vector< osg::ref_ptr<TerrainTileColorLayerModel> > TerrainTileColorLayerModelVector;
+
+    /**
      * Layer based on an ImageLayer from the map.
      */
-    class OSGEARTH_EXPORT TerrainTileImageLayerModel : public TerrainTileLayerModel
+    class OSGEARTH_EXPORT TerrainTileImageLayerModel : public TerrainTileColorLayerModel
     {
     public:
-        TerrainTileImageLayerModel() : _order(0) { }
+        TerrainTileImageLayerModel() : TerrainTileColorLayerModel() { }
 
     public:
         /** Source layer */
-        void setImageLayer(ImageLayer* layer) { _layer = layer; }
-        const ImageLayer* getImageLayer() const { return _layer.get(); }
-
-        void setOrder(int order) { _order = order; }
-        int getOrder() const { return _order; }
+        void setImageLayer(ImageLayer* layer) { _imageLayer = layer; setLayer(layer); }
+        const ImageLayer* getImageLayer() const { return _imageLayer.get(); }
 
     protected:
         virtual ~TerrainTileImageLayerModel() { }
-        osg::ref_ptr<ImageLayer> _layer;
-        int _order;
+        osg::ref_ptr<ImageLayer> _imageLayer;
+        osg::ref_ptr<osg::Node> _node;
     };
     typedef std::vector< osg::ref_ptr<TerrainTileImageLayerModel> > TerrainTileImageLayerModelVector;
 
     /**
+     * Layer based on an Layer with RENDERTYPE_PATCH from the map.
+     */
+    class OSGEARTH_EXPORT TerrainTilePatchLayerModel : public TerrainTileLayerModel
+    {
+    public:
+        TerrainTilePatchLayerModel() { }
+
+    public:
+        /** Source layer */
+        void setPatchLayer(PatchLayer* layer) { _layer = layer; }
+        const PatchLayer* getPatchLayer() const { return _layer.get(); }
+
+        /** Patch data */
+        void setTileData(PatchLayer::TileData* data) { _tileData = data; }
+        PatchLayer::TileData* getTileData() const { return _tileData.get(); }
+
+    protected:
+        virtual ~TerrainTilePatchLayerModel() { }
+        osg::ref_ptr<PatchLayer> _layer;
+        osg::ref_ptr<PatchLayer::TileData> _tileData;
+    };
+    typedef std::vector< osg::ref_ptr<TerrainTilePatchLayerModel> > TerrainTilePatchLayerModelVector;
+
+    /**
      * Layer based on aggregated elevation data.
      */
     class OSGEARTH_EXPORT TerrainTileElevationModel : public TerrainTileLayerModel
@@ -143,7 +184,7 @@ namespace osgEarth
         TerrainTileElevationModel();
 
         void setHeightField(const osg::HeightField* value) { _heightField = value; }
-        const osg::HeightField* getHeightField() const { return _heightField; }
+        const osg::HeightField* getHeightField() const { return _heightField.get(); }
 
         /** Minimum height in the tile */
         void setMinHeight(float value) { _minHeight = value; }
@@ -178,8 +219,11 @@ namespace osgEarth
         const TileKey& getKey() const { return _key; }
 
         /** Color layers (rendered one at a time in order) */
-        TerrainTileImageLayerModelVector& colorLayers() { return _colorLayers; }
-        const TerrainTileImageLayerModelVector& colorLayers() const { return _colorLayers; }
+        TerrainTileColorLayerModelVector& colorLayers() { return _colorLayers; }
+        const TerrainTileColorLayerModelVector& colorLayers() const { return _colorLayers; }
+
+        TerrainTilePatchLayerModelVector& patchLayers() { return _patchLayers; }
+        const TerrainTilePatchLayerModelVector& patchLayers() const { return _patchLayers; }
 
         /** Elevation Layer model (one) */
         osg::ref_ptr<TerrainTileElevationModel>& elevationModel() { return _elevationLayer; }
@@ -193,11 +237,6 @@ namespace osgEarth
         TerrainTileImageLayerModelVector& sharedLayers() { return _sharedLayers; }
         const TerrainTileImageLayerModelVector& sharedLayers() const { return _sharedLayers; }
 
-        /** Try to find a layer */
-        const TerrainTileImageLayerModel* findColorLayerByUID(const UID& uid) const;
-        const TerrainTileImageLayerModel* findSharedLayerByUID(const UID& uid) const;
-        const TerrainTileImageLayerModel* findSharedLayerByName(const std::string& name) const;
-
         /** Height field neighborhood surrounding the tile */
         /** TODO: does this need to be in the model itself? probably not */
         HeightFieldNeighborhood& heightFields() { return _heightFields; }
@@ -217,7 +256,8 @@ namespace osgEarth
     protected:
         TileKey                                 _key;
         Revision                                _revision;
-        TerrainTileImageLayerModelVector        _colorLayers;
+        TerrainTileColorLayerModelVector        _colorLayers;
+        TerrainTilePatchLayerModelVector        _patchLayers;
         TerrainTileImageLayerModelVector        _sharedLayers;
         osg::ref_ptr<TerrainTileElevationModel> _elevationLayer;
         osg::ref_ptr<TerrainTileLayerModel>     _normalLayer;
diff --git a/src/osgEarth/TerrainTileModel.cpp b/src/osgEarth/TerrainTileModel.cpp
index dd9be80..d8b13aa 100644
--- a/src/osgEarth/TerrainTileModel.cpp
+++ b/src/osgEarth/TerrainTileModel.cpp
@@ -54,50 +54,50 @@ _requiresUpdateTraverse( false )
     //NOP
 }
 
-const TerrainTileImageLayerModel*
-TerrainTileModel::findSharedLayerByName(const std::string& name) const
-{
-    for(TerrainTileImageLayerModelVector::const_iterator i = _sharedLayers.begin();
-        i != _sharedLayers.end();
-        ++i)
-    {
-        if ( i->get()->getName() == name )
-        {
-            return i->get();
-        }
-    }
-    return 0L;
-}
-
-const TerrainTileImageLayerModel*
-TerrainTileModel::findSharedLayerByUID(const UID& uid) const
-{
-    for(TerrainTileImageLayerModelVector::const_iterator i = _sharedLayers.begin();
-        i != _sharedLayers.end();
-        ++i)
-    {
-        if ( i->get()->getImageLayer() && i->get()->getImageLayer()->getUID() == uid )
-        {
-            return i->get();
-        }
-    }
-    return 0L;
-}
-
-const TerrainTileImageLayerModel*
-TerrainTileModel::findColorLayerByUID(const UID& uid) const
-{
-    for(TerrainTileImageLayerModelVector::const_iterator i = _colorLayers.begin();
-        i != _colorLayers.end();
-        ++i)
-    {
-        if ( i->get()->getImageLayer() && i->get()->getImageLayer()->getUID() == uid )
-        {
-            return i->get();
-        }
-    }
-    return 0L;
-}
+//const TerrainTileImageLayerModel*
+//TerrainTileModel::findSharedLayerByName(const std::string& name) const
+//{
+//    for(TerrainTileImageLayerModelVector::const_iterator i = _sharedLayers.begin();
+//        i != _sharedLayers.end();
+//        ++i)
+//    {
+//        if ( i->get()->getName() == name )
+//        {
+//            return i->get();
+//        }
+//    }
+//    return 0L;
+//}
+//
+//const TerrainTileImageLayerModel*
+//TerrainTileModel::findSharedLayerByUID(const UID& uid) const
+//{
+//    for(TerrainTileImageLayerModelVector::const_iterator i = _sharedLayers.begin();
+//        i != _sharedLayers.end();
+//        ++i)
+//    {
+//        if ( i->get()->getImageLayer() && i->get()->getImageLayer()->getUID() == uid )
+//        {
+//            return i->get();
+//        }
+//    }
+//    return 0L;
+//}
+//
+//const TerrainTileImageLayerModel*
+//TerrainTileModel::findColorLayerByUID(const UID& uid) const
+//{
+//    for(TerrainTileImageLayerModelVector::const_iterator i = _colorLayers.begin();
+//        i != _colorLayers.end();
+//        ++i)
+//    {
+//        if ( i->get()->getImageLayer() && i->get()->getImageLayer()->getUID() == uid )
+//        {
+//            return i->get();
+//        }
+//    }
+//    return 0L;
+//}
 
 osg::Texture* 
 TerrainTileModel::getNormalTexture() const
diff --git a/src/osgEarth/TerrainTileModelFactory b/src/osgEarth/TerrainTileModelFactory
index a31d083..d73f303 100644
--- a/src/osgEarth/TerrainTileModelFactory
+++ b/src/osgEarth/TerrainTileModelFactory
@@ -23,11 +23,46 @@
 #include <osgEarth/TerrainTileModel>
 #include <osgEarth/TerrainOptions>
 #include <osgEarth/TerrainEngineRequirements>
-#include <osgEarth/MapFrame>
+#include <osgEarth/ImageLayer>
 #include <osgEarth/Progress>
 
 namespace osgEarth
 {
+    class MapFrame;
+
+    /**
+     * Filter you can pass to TerrainTileModelFactory::createTileModel
+     */
+    class CreateTileModelFilter
+    {
+    public:
+        CreateTileModelFilter() : _elevation(false) { }
+
+        /** Whether to fetch elevation data and normal maps */
+        optional<bool>& elevation() { return _elevation; }
+        const optional<bool>& elevation() const { return _elevation; }
+
+        /** List of other layers to accept */
+        std::set<UID>& layers() { return _layers; }
+        const std::set<UID>& layers() const { return _layers; }
+
+        bool accept(const Layer* layer) const {
+            return empty() || _layers.find(layer->getUID()) != _layers.end();
+        }
+
+        bool empty() const {
+            return !_elevation.isSet() && _layers.empty();
+        }
+
+        void clear() {
+            _elevation.unset();
+            _layers.clear();
+        }
+    protected:
+        optional<bool> _elevation;
+        std::set<UID> _layers;
+    };
+
     /**
      * Builds a TerrainTileModel from a map frame.
      */
@@ -41,37 +76,56 @@ namespace osgEarth
         /**
          * Creates a tile model and populates it with data from the map.
          *
-         * @param frame       Map frame from which to read source data
-         * @param key         Tile key for which to create the model
-         * @param modelStore  Access to other tile models; if set, the factory
-         *                    will be able to access parent and neighbor data
-         *                    where appropriate
-         * @param progress    Progress tracking callback
+         * @param frame        Map frame from which to read source data
+         * @param key          Tile key for which to create the model
+         * @param layers       Set of layer UIDs for which to fetch data; NULL => all layers
+         * @param requirements Hints that tell the factory what types of data to add
+         * @param progress     Progress tracking callback
          */
         virtual TerrainTileModel* createTileModel(
             const MapFrame&                  frame,
             const TileKey&                   key,
+            const CreateTileModelFilter&     filter,
             const TerrainEngineRequirements* requirements,
             ProgressCallback*                progress);
 
     protected:
 
         virtual void addImageLayers(
+            TerrainTileModel*                model,
+            const MapFrame&                  frame,
+            const TerrainEngineRequirements* reqs,
+            const TileKey&                   key,
+            const CreateTileModelFilter&     filter,
+            ProgressCallback*                progress);
+
+        virtual void addColorLayers(
+            TerrainTileModel*                model,
+            const MapFrame&                  frame,
+            const TerrainEngineRequirements* reqs,
+            const TileKey&                   key,
+            const CreateTileModelFilter&     filter,
+            ProgressCallback*                progress);
+
+        virtual void addElevation(
             TerrainTileModel*            model,
             const MapFrame&              frame,
             const TileKey&               key,
+            const CreateTileModelFilter& filter,
+            unsigned                     border,
             ProgressCallback*            progress);
 
-        virtual void addElevation(
+        virtual void addNormalMap(
             TerrainTileModel*            model,
             const MapFrame&              frame,
             const TileKey&               key,
             ProgressCallback*            progress);
 
-        virtual void addNormalMap(
+        virtual void addPatchLayers(
             TerrainTileModel*            model,
             const MapFrame&              frame,
             const TileKey&               key,
+            const CreateTileModelFilter& filter,
             ProgressCallback*            progress);
 
     protected:
@@ -82,7 +136,9 @@ namespace osgEarth
             const TileKey&                  key,
             ElevationSamplePolicy           samplePolicy,
             ElevationInterpolation          interpolation,
+            unsigned                        border,
             osg::ref_ptr<osg::HeightField>& out_hf,
+            osg::ref_ptr<NormalMap>&        out_normalMap,
             ProgressCallback*               progress);
 
         osg::Texture* createImageTexture(
@@ -118,10 +174,16 @@ namespace osgEarth
             }
         };
 
-        typedef osg::ref_ptr<osg::HeightField> HFCacheValue;
+        struct HFCacheValue
+        {
+            osg::ref_ptr<osg::HeightField> _hf;
+            osg::ref_ptr<NormalMap> _normalMap;
+        };
+        //typedef osg::ref_ptr<osg::HeightField> HFCacheValue;
         typedef LRUCache<HFCacheKey, HFCacheValue> HFCache;
         HFCache _heightFieldCache;
         bool    _heightFieldCacheEnabled;
+        osg::ref_ptr<osg::Texture> _emptyTexture;
     };
 }
 
diff --git a/src/osgEarth/TerrainTileModelFactory.cpp b/src/osgEarth/TerrainTileModelFactory.cpp
index 122558c..c82477a 100644
--- a/src/osgEarth/TerrainTileModelFactory.cpp
+++ b/src/osgEarth/TerrainTileModelFactory.cpp
@@ -20,6 +20,9 @@
 #include <osgEarth/Registry>
 #include <osgEarth/ImageUtils>
 #include <osgEarth/ImageToHeightFieldConverter>
+#include <osgEarth/PatchLayer>
+#include <osgEarth/MapOptions>
+#include <osgEarth/MapFrame>
 
 #include <osg/Texture2D>
 
@@ -34,11 +37,15 @@ _options         ( options ),
 _heightFieldCache( true, 128 )
 {
     _heightFieldCacheEnabled = (::getenv("OSGEARTH_MEMORY_PROFILE") == 0L);
+
+    // Create an empty texture that we can use as a placeholder
+    _emptyTexture = new osg::Texture2D(ImageUtils::createEmptyImage());
 }
 
 TerrainTileModel*
 TerrainTileModelFactory::createTileModel(const MapFrame&                  frame,
                                          const TileKey&                   key,
+                                         const CreateTileModelFilter&     filter,
                                          const TerrainEngineRequirements* requirements,
                                          ProgressCallback*                progress)
 {
@@ -48,104 +55,199 @@ TerrainTileModelFactory::createTileModel(const MapFrame&                  frame,
         frame.getRevision() );
 
     // assemble all the components:
-    addImageLayers( model.get(), frame, key, progress );
+    //addImageLayers(model.get(), frame, requirements, key, filter, progress);
+    addColorLayers(model.get(), frame, requirements, key, filter, progress);
+
+    addPatchLayers(model.get(), frame, key, filter, progress);
 
     if ( requirements == 0L || requirements->elevationTexturesRequired() )
     {
-        addElevation( model.get(), frame, key, progress );
+        unsigned border = requirements->elevationBorderRequired() ? 1u : 0u;
+
+        addElevation( model.get(), frame, key, filter, border, progress );
     }
 
+#if 0
     if ( requirements == 0L || requirements->normalTexturesRequired() )
     {
         addNormalMap( model.get(), frame, key, progress );
     }
+#endif
 
     // done.
     return model.release();
 }
 
 void
-TerrainTileModelFactory::addImageLayers(TerrainTileModel*            model,
-                                        const MapFrame&              frame,
-                                        const TileKey&               key,
-                                        ProgressCallback*            progress)
+TerrainTileModelFactory::addColorLayers(TerrainTileModel* model,
+                                        const MapFrame&   frame,
+                                        const TerrainEngineRequirements* reqs,
+                                        const TileKey&    key,
+                                        const CreateTileModelFilter& filter,
+                                        ProgressCallback* progress)
 {
     OE_START_TIMER(fetch_image_layers);
 
     int order = 0;
 
-    for(ImageLayerVector::const_iterator i = frame.imageLayers().begin();
-        i != frame.imageLayers().end();
-        ++i, ++order )
+    for (LayerVector::const_iterator i = frame.layers().begin();
+        i != frame.layers().end();
+        ++i)
     {
-        ImageLayer* layer = i->get();
+        Layer* layer = i->get();
 
-        if ( layer->getEnabled() && layer->isKeyInRange(key) )
-        {
-            // This will only go true if we are requesting a ROOT TILE but we have to
-            // fall back on lower resolution data to create it.
-            bool isFallback = false;
+        if (layer->getRenderType() != layer->RENDERTYPE_TILE)
+            continue;
 
-            GeoImage geoImage;
+        if (!layer->getEnabled())
+            continue;
 
-            const Profile* layerProfile = layer->getProfile();
-            
-            // If this is a ROOT tile, we will try to fall back on lower-resolution
-            // data if we can't find something at the optimal LOD.
-            bool isRootKey =
-                (key.getLOD() == 0) || // should never be
-                (key.getLOD()-1 == _options.firstLOD().get() );
+        if (!filter.accept(layer))
+            continue;
 
-            TileSource* tileSource = layer->getTileSource();
+        ImageLayer* imageLayer = dynamic_cast<ImageLayer*>(layer);
+        if (imageLayer)
+        {
+            osg::Texture* tex = 0L;
+            osg::Matrixf textureMatrix;
 
-            // Only try to get data from the source if it actually intersects the key extent
-            bool hasDataInExtent = true;
-            if ( tileSource && layerProfile )
+            if (imageLayer->isKeyInLegalRange(key) && imageLayer->mayHaveDataInExtent(key.getExtent()))
             {
-                GeoExtent ext = key.getExtent();
-                if (!layerProfile->getSRS()->isEquivalentTo( ext.getSRS() ))
+                if (imageLayer->createTextureSupported())
                 {
-                    ext = layerProfile->clampAndTransformExtent( ext );
+                    tex = imageLayer->createTexture( key, progress, textureMatrix );
+                }
+
+                else
+                {
+                    GeoImage geoImage = imageLayer->createImage( key, progress );
+           
+                    if ( geoImage.valid() )
+                    {
+                        if ( imageLayer->isCoverage() )
+                            tex = createCoverageTexture(geoImage.getImage(), imageLayer);
+                        else
+                            tex = createImageTexture(geoImage.getImage(), imageLayer);
+                    }
                 }
-                hasDataInExtent = tileSource->hasDataInExtent( ext );
             }
-            
-            // fetch the image from the layer if it's available:
-            if ( hasDataInExtent && layer->isKeyInRange(key) )
+        
+            // if this is the first LOD, and the engine requires that the first LOD
+            // be populated, make an empty texture if we didn't get one.
+            if (tex == 0L &&
+                _options.firstLOD() == key.getLOD() &&
+                reqs && reqs->fullDataAtFirstLodRequired())
             {
-                geoImage = layer->createImage( key, progress );
+                tex = _emptyTexture.get();
             }
-            
-            if ( geoImage.valid() )
+         
+            if (tex)
             {
                 TerrainTileImageLayerModel* layerModel = new TerrainTileImageLayerModel();
-                layerModel->setImageLayer( layer );
-
-                // preserve layer ordering. Without this, layer draw order can get out of whack
-                // if you have a layer that doesn't appear in the model until a higher LOD. Instead
-                // of just getting appended to the draw set, the Order will make sure it gets 
-                // inserted in the correct position according to the map model.
-                layerModel->setOrder( order );
-
-                // made an image. Store as a texture with an identity matrix.
-                osg::Texture* texture;
-                if ( layer->isCoverage() )
-                    texture = createCoverageTexture(geoImage.getImage(), layer);
-                else
-                    texture = createImageTexture(geoImage.getImage(), layer);
 
-                layerModel->setTexture( texture );
+                layerModel->setImageLayer(imageLayer);
+
+                layerModel->setTexture(tex);
+                layerModel->setMatrix(new osg::RefMatrixf(textureMatrix));
+
+                model->colorLayers().push_back(layerModel);
+
+                if (imageLayer->isShared())
+                    model->sharedLayers().push_back(layerModel);
+
+                if (imageLayer->isDynamic())
+                    model->setRequiresUpdateTraverse(true);
+            }
+        }
+
+        else // non-image kind of TILE layer:
+        {
+            TerrainTileColorLayerModel* colorModel = new TerrainTileColorLayerModel();
+            colorModel->setLayer(layer);
+            model->colorLayers().push_back(colorModel);
+        }
+    }
+
+    if (progress)
+        progress->stats()["fetch_imagery_time"] += OE_STOP_TIMER(fetch_image_layers);
+}
+
+void
+TerrainTileModelFactory::addImageLayers(TerrainTileModel* model,
+                                        const MapFrame&   frame,
+                                        const TerrainEngineRequirements* reqs,
+                                        const TileKey&    key,
+                                        const CreateTileModelFilter& filter,
+                                        ProgressCallback* progress)
+{
+    OE_START_TIMER(fetch_image_layers);
+
+    int order = 0;
+
+    ImageLayerVector imageLayers;
+    frame.getLayers(imageLayers);
 
+    for(ImageLayerVector::const_iterator i = imageLayers.begin();
+        i != imageLayers.end();
+        ++i, ++order )
+    {
+        ImageLayer* layer = i->get();
+
+        if (!filter.accept(layer))
+            continue;
 
-                if ( layer->isShared() )
-                    model->sharedLayers().push_back( layerModel );
+        if (!layer->getEnabled())
+            continue;
 
-                if ( layer->getVisible() )
-                    model->colorLayers().push_back( layerModel );
+        osg::Texture* tex = 0L;
+        osg::Matrixf textureMatrix;
 
-                if ( layer->isDynamic() )
-                    model->setRequiresUpdateTraverse( true );
+        if (layer->isKeyInLegalRange(key) && layer->mayHaveDataInExtent(key.getExtent()))
+        {
+            if (layer->createTextureSupported())
+            {
+                tex = layer->createTexture( key, progress, textureMatrix );
             }
+
+            else
+            {
+                GeoImage geoImage = layer->createImage( key, progress );
+
+                if ( geoImage.valid() )
+                {
+                    if ( layer->isCoverage() )
+                        tex = createCoverageTexture(geoImage.getImage(), layer);
+                    else
+                        tex = createImageTexture(geoImage.getImage(), layer);
+                }
+            }
+        }
+
+        // if this is the first LOD, and the engine requires that the first LOD
+        // be populated, make an empty texture if we didn't get one.
+        if (tex == 0L &&
+            _options.firstLOD() == key.getLOD() &&
+            reqs && reqs->fullDataAtFirstLodRequired())
+        {
+            tex = _emptyTexture.get();
+        }
+
+        if (tex)
+        {
+            TerrainTileImageLayerModel* layerModel = new TerrainTileImageLayerModel();
+
+            layerModel->setImageLayer(layer);
+
+            layerModel->setTexture(tex);
+            layerModel->setMatrix(new osg::RefMatrixf(textureMatrix));
+
+            model->colorLayers().push_back(layerModel);
+
+            if (layer->isShared())
+                model->sharedLayers().push_back(layerModel);
+
+            if (layer->isDynamic())
+                model->setRequiresUpdateTraverse(true);
         }
     }
 
@@ -155,14 +257,62 @@ TerrainTileModelFactory::addImageLayers(TerrainTileModel*            model,
 
 
 void
+TerrainTileModelFactory::addPatchLayers(TerrainTileModel* model,
+                                        const MapFrame&   frame,
+                                        const TileKey&    key,
+                                        const CreateTileModelFilter& filter,
+                                        ProgressCallback* progress)
+{
+    OE_START_TIMER(fetch_patch_layers);
+
+    PatchLayerVector patchLayers;
+    frame.getLayers(patchLayers);
+
+    for(PatchLayerVector::const_iterator i = patchLayers.begin();
+        i != patchLayers.end();
+        ++i )
+    {
+        PatchLayer* layer = i->get();
+
+        if (!filter.accept(layer))
+            continue;
+
+        if (!layer->getEnabled())
+            continue;
+
+        if (layer->getAcceptCallback() == 0L || layer->getAcceptCallback()->acceptKey(key))
+        {
+            PatchLayer::TileData* tileData = layer->createTileData(key);
+            if (tileData)
+            {
+                TerrainTilePatchLayerModel* patchModel = new TerrainTilePatchLayerModel();
+                patchModel->setPatchLayer(layer);
+                patchModel->setTileData(tileData);
+
+                model->patchLayers().push_back(patchModel);
+            }
+        }
+    }
+
+    if (progress)
+        progress->stats()["fetch_patches_time"] += OE_STOP_TIMER(fetch_patch_layers);
+}
+
+
+void
 TerrainTileModelFactory::addElevation(TerrainTileModel*            model,
                                       const MapFrame&              frame,
                                       const TileKey&               key,
+                                      const CreateTileModelFilter& filter,
+                                      unsigned                     border,
                                       ProgressCallback*            progress)
-{    
+{
     // make an elevation layer.
     OE_START_TIMER(fetch_elevation);
 
+    if (!filter.empty() && !filter.elevation().isSetTo(true))
+        return;
+
     const MapInfo& mapInfo = frame.getMapInfo();
 
     const osgEarth::ElevationInterpolation& interp =
@@ -170,8 +320,20 @@ TerrainTileModelFactory::addElevation(TerrainTileModel*            model,
 
     // Request a heightfield from the map.
     osg::ref_ptr<osg::HeightField> mainHF;
+    osg::ref_ptr<NormalMap> normalMap;
+
+    bool hfOK = getOrCreateHeightField(frame, key, SAMPLE_FIRST_VALID, interp, border, mainHF, normalMap, progress) && mainHF.valid();
+
+    if (hfOK == false && key.getLOD() == _options.firstLOD().get())
+    {
+        OE_DEBUG << LC << "No HF at key " << key.str() << ", making placeholder" << std::endl;
+        mainHF = new osg::HeightField();
+        mainHF->allocate(1, 1);
+        mainHF->setHeight(0, 0, 0.0f);
+        hfOK = true;
+    }
 
-    if (getOrCreateHeightField(frame, key, SAMPLE_FIRST_VALID, interp, mainHF, progress) && mainHF.valid())
+    if (hfOK && mainHF.valid())
     {
         osg::ref_ptr<TerrainTileElevationModel> layerModel = new TerrainTileElevationModel();
         layerModel->setHeightField( mainHF.get() );
@@ -187,22 +349,33 @@ TerrainTileModelFactory::addElevation(TerrainTileModel*            model,
                 if ( h < layerModel->getMinHeight() )
                     layerModel->setMinHeight( h );
             }
-        }        
+        }
 
         // needed for normal map generation
         model->heightFields().setNeighbor(0, 0, mainHF.get());
 
         // convert the heightfield to a 1-channel 32-bit fp image:
         ImageToHeightFieldConverter conv;
-        osg::Image* image = conv.convert( mainHF.get(), 32 ); // 32 = GL_FLOAT
+        osg::Image* hfImage = conv.convertToR32F(mainHF.get());
 
-        if ( image )
+        if ( hfImage )
         {
             // Made an image, so store this as a texture with no matrix.
-            osg::Texture* texture = createElevationTexture( image );
+            osg::Texture* texture = createElevationTexture( hfImage );
             layerModel->setTexture( texture );
             model->elevationModel() = layerModel.get();
         }
+
+        if (normalMap.valid())
+        {
+            TerrainTileImageLayerModel* layerModel = new TerrainTileImageLayerModel();
+            layerModel->setName( "oe_normal_map" );
+
+            // Made an image, so store this as a texture with no matrix.
+            osg::Texture* texture = createNormalTexture(normalMap.get());
+            layerModel->setTexture( texture );
+            model->normalModel() = layerModel;
+        }
     }
 
     if (progress)
@@ -210,30 +383,33 @@ TerrainTileModelFactory::addElevation(TerrainTileModel*            model,
 }
 
 void
-TerrainTileModelFactory::addNormalMap(TerrainTileModel*            model,
-                                      const MapFrame&              frame,
-                                      const TileKey&               key,
-                                      ProgressCallback*            progress)
+TerrainTileModelFactory::addNormalMap(TerrainTileModel* model,
+                                      const MapFrame&   frame,
+                                      const TileKey&    key,
+                                      ProgressCallback* progress)
 {
     OE_START_TIMER(fetch_normalmap);
 
-    const osgEarth::ElevationInterpolation& interp =
-        frame.getMapOptions().elevationInterpolation().get();
+    if (model->elevationModel().valid())
+    {
+        const osgEarth::ElevationInterpolation& interp =
+            frame.getMapOptions().elevationInterpolation().get();
 
-    // Can only generate the normal map if the center heightfield was built:
-    osg::Image* image = HeightFieldUtils::convertToNormalMap(
-        model->heightFields(),
-        key.getProfile()->getSRS() );
+        // Can only generate the normal map if the center heightfield was built:
+        osg::ref_ptr<osg::Image> image = HeightFieldUtils::convertToNormalMap(
+            model->heightFields(),
+            key.getProfile()->getSRS() );
 
-    if ( image )
-    {
-        TerrainTileImageLayerModel* layerModel = new TerrainTileImageLayerModel();
-        layerModel->setName( "oe_normal_map" );
+        if (image.valid())
+        {
+            TerrainTileImageLayerModel* layerModel = new TerrainTileImageLayerModel();
+            layerModel->setName( "oe_normal_map" );
 
-        // Made an image, so store this as a texture with no matrix.
-        osg::Texture* texture = createNormalTexture( image );
-        layerModel->setTexture( texture );
-        model->normalModel() = layerModel;
+            // Made an image, so store this as a texture with no matrix.
+            osg::Texture* texture = createNormalTexture( image.get() );
+            layerModel->setTexture( texture );
+            model->normalModel() = layerModel;
+        }
     }
 
     if (progress)
@@ -245,7 +421,9 @@ TerrainTileModelFactory::getOrCreateHeightField(const MapFrame&
                                                 const TileKey&                  key,
                                                 ElevationSamplePolicy           samplePolicy,
                                                 ElevationInterpolation          interpolation,
+                                                unsigned                        border,
                                                 osg::ref_ptr<osg::HeightField>& out_hf,
+                                                osg::ref_ptr<NormalMap>&        out_normalMap,
                                                 ProgressCallback*               progress)
 {
     // check the quick cache.
@@ -261,7 +439,8 @@ TerrainTileModelFactory::getOrCreateHeightField(const MapFrame&
     HFCache::Record rec;
     if ( _heightFieldCacheEnabled && _heightFieldCache.get(cachekey, rec) )
     {
-        out_hf = rec.value().get();
+        out_hf = rec.value()._hf.get();
+        out_normalMap = rec.value()._normalMap.get();
 
         if (progress)
         {
@@ -274,13 +453,22 @@ TerrainTileModelFactory::getOrCreateHeightField(const MapFrame&
 
     if ( !out_hf.valid() )
     {
-        // This sets the elevation tile size; query size for all tiles.
         out_hf = HeightFieldUtils::createReferenceHeightField(
-            key.getExtent(), 257, 257, true );
+            key.getExtent(),
+            257, 257,           // base tile size for elevation data
+            border,             // 1 sample border around the data makes it 259x259
+            true);              // initialize to HAE (0.0) heights
     }
 
-    bool populated = frame.populateHeightField(
+    if (!out_normalMap.valid())
+    {
+        //OE_INFO << "TODO: check terrain reqs\n";
+        out_normalMap = new NormalMap(257, 257); // ImageUtils::createEmptyImage(257, 257);
+    }
+
+    bool populated = frame.populateHeightFieldAndNormalMap(
         out_hf,
+        out_normalMap,
         key,
         true, // convertToHAE
         progress );
@@ -317,7 +505,13 @@ TerrainTileModelFactory::getOrCreateHeightField(const MapFrame&
 
         // cache it.
         if (_heightFieldCacheEnabled )
-            _heightFieldCache.insert( cachekey, out_hf.get() );
+        {
+            HFCacheValue newValue;
+            newValue._hf = out_hf.get();
+            newValue._normalMap = out_normalMap.get();
+
+            _heightFieldCache.insert( cachekey, newValue );
+        }
     }
 
     return populated;
@@ -333,10 +527,10 @@ TerrainTileModelFactory::createImageTexture(osg::Image*       image,
     tex->setWrap( osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE );
     tex->setResizeNonPowerOfTwoHint(false);
 
-    osg::Texture::FilterMode magFilter = 
-        layer ? layer->getImageLayerOptions().magFilter().get() : osg::Texture::LINEAR;
+    osg::Texture::FilterMode magFilter =
+        layer ? layer->options().magFilter().get() : osg::Texture::LINEAR;
     osg::Texture::FilterMode minFilter =
-        layer ? layer->getImageLayerOptions().minFilter().get() : osg::Texture::LINEAR;
+        layer ? layer->options().minFilter().get() : osg::Texture::LINEAR;
 
     tex->setFilter( osg::Texture::MAG_FILTER, magFilter );
     tex->setFilter( osg::Texture::MIN_FILTER, minFilter );
@@ -348,6 +542,10 @@ TerrainTileModelFactory::createImageTexture(osg::Image*       image,
         tex->setFilter( osg::Texture::MIN_FILTER, osg::Texture::LINEAR );
     }
 
+    layer->applyTextureCompressionMode(tex);
+
+    ImageUtils::activateMipMaps(tex);
+
     return tex;
 }
 
@@ -373,8 +571,7 @@ osg::Texture*
 TerrainTileModelFactory::createElevationTexture(osg::Image* image) const
 {
     osg::Texture2D* tex = new osg::Texture2D( image );
-    tex->setInternalFormat(GL_LUMINANCE32F_ARB);
-    tex->setSourceFormat(GL_LUMINANCE);
+    tex->setInternalFormat(GL_R32F);
     tex->setFilter( osg::Texture::MAG_FILTER, osg::Texture::LINEAR );
     tex->setFilter( osg::Texture::MIN_FILTER, osg::Texture::NEAREST );
     tex->setWrap  ( osg::Texture::WRAP_S,     osg::Texture::CLAMP_TO_EDGE );
diff --git a/src/osgEarth/TerrainTileNode b/src/osgEarth/TerrainTileNode
index 77ea5d7..238abc6 100644
--- a/src/osgEarth/TerrainTileNode
+++ b/src/osgEarth/TerrainTileNode
@@ -33,17 +33,24 @@ namespace osgEarth
      * This is largely for internal use and subject to change, so
      * be careful relying on the structure of this object.
      */
-    class /*header-only*/ TerrainTileNode
+    class OSGEARTH_EXPORT TerrainTileNode
     {
-    public:
-        const TerrainTileModel* getModel() const { return _model.get(); }
+    public:    
+        //! TileKey represented by this tile node
+        virtual const TileKey& getKey() const = 0;
+
+        virtual double getMinimumExpirationTime() const = 0;
+        virtual void setMinimumExpirationTime(double minExpiryTime) = 0;
+        
+        virtual unsigned int getMinimumExpirationFrames() const = 0;
+        virtual void setMinimumExpirationFrames(unsigned int minExpiryFrames) = 0;
+
+
+        virtual void loadChildren() = 0;
 
-        const TileKey& getKey() const { return _model->getKey(); }
 
     protected:
         virtual ~TerrainTileNode() { }
-
-        osg::ref_ptr<TerrainTileModel> _model;
     };
 };
 
diff --git a/src/osgEarth/Tessellator.cpp b/src/osgEarth/Tessellator.cpp
index ab1b470..8ae44a3 100644
--- a/src/osgEarth/Tessellator.cpp
+++ b/src/osgEarth/Tessellator.cpp
@@ -162,7 +162,7 @@ Tessellator::tessellateGeometry(osg::Geometry &geom)
                     else
                     {
                         // tessellation failed, add old primitive set back
-                        geom.addPrimitiveSet(primitive);
+                        geom.addPrimitiveSet(primitive.get());
                         success = false;
                     }
 
@@ -181,7 +181,7 @@ Tessellator::tessellateGeometry(osg::Geometry &geom)
                     else
                     {
                         // tessellation failed, add old primitive set back
-                        geom.addPrimitiveSet(primitive);
+                        geom.addPrimitiveSet(primitive.get());
                         success = false;
                     }
                 }
diff --git a/src/osgEarth/TextureBufferSerializer.cpp b/src/osgEarth/TextureBufferSerializer.cpp
index d94ff68..117f9dc 100644
--- a/src/osgEarth/TextureBufferSerializer.cpp
+++ b/src/osgEarth/TextureBufferSerializer.cpp
@@ -19,6 +19,10 @@
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
 */
+#include <osg/Version>
+
+#if OSG_VERSION_LESS_THAN(3,5,4)
+
 #include <osg/TextureBuffer>
 #include <osgDB/ObjectWrapper>
 #include <osgDB/InputStream>
@@ -35,3 +39,21 @@ namespace
         ADD_INT_SERIALIZER( TextureWidth, 0 );  // _textureWidth
     }
 }
+
+#elif OSG_VERSION_LESS_THAN(3,5,6)
+
+#include <osg/TextureBuffer>
+#include <osgDB/ObjectWrapper>
+#include <osgDB/InputStream>
+#include <osgDB/OutputStream>
+
+REGISTER_OBJECT_WRAPPER( TextureBuffer,
+                         new osg::TextureBuffer,
+                         osg::TextureBuffer,
+                         "osg::Object osg::StateAttribute osg::Texture osg::TextureBuffer" )
+{
+    ADD_INT_SERIALIZER( TextureWidth, 0 );                       // _textureWidth
+    ADD_OBJECT_SERIALIZER( BufferData, osg::BufferData, NULL );  // _bufferData
+}
+
+#endif
diff --git a/src/osgEarth/TextureCompositor b/src/osgEarth/TextureCompositor
deleted file mode 100644
index 3275890..0000000
--- a/src/osgEarth/TextureCompositor
+++ /dev/null
@@ -1,71 +0,0 @@
-/* -*-c++-*- */
-/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2016 Pelican Mapping
- * http://osgearth.org
- *
- * osgEarth is free software; you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>
- */
-#ifndef OSGEARTH_TEXTURE_COMPOSITOR_H
-#define OSGEARTH_TEXTURE_COMPOSITOR_H 1
-
-#include <osgEarth/Common>
-#include <osgEarth/ThreadingUtils>
-
-namespace osgEarth
-{
-    /**
-     * Utility class that tracks texture allocation units
-     */
-    class OSGEARTH_EXPORT TextureCompositor : public osg::Referenced
-    {
-    public:
-        /**
-         * Constructs a new compositor.
-         */
-        TextureCompositor();
-
-        /** dtor */
-        virtual ~TextureCompositor() { }
-
-        /**
-         * Requests a texture image unit that is not in use, and marks is as reserved.
-         * You can release the reserved texture image unit by calling releaseTextureImageUnit().
-         * @param out_unit  Reserved unit is written to this variable.
-         * @param requestor Optional requestor string (for information purposes only)
-         */
-        bool reserveTextureImageUnit(
-            int&        out_unit,
-            const char* requestor =0L );
-
-        /**
-         * Releases a reserved texture image unit previously returned by reserveTextureImageUnit.
-         */
-        void releaseTextureImageUnit(int unit);
-
-        /**
-         * Marks a specific texture image as reserved. Only call this if your application
-         * is known to use a particular texture image unit and you don't want osgEarth
-         * touching it.
-         *
-         * Returns true upon success, and false if the unit has already been reserved.
-         */
-        bool setTextureImageUnitOffLimits(int unit);
-
-    private:
-        Threading::Mutex _reservedUnitsMutex;
-        std::set<int>    _reservedUnits;
-    };
-}
-
-#endif // OSGEARTH_TEXTURE_COMPOSITOR_H
diff --git a/src/osgEarth/TextureCompositor.cpp b/src/osgEarth/TextureCompositor.cpp
deleted file mode 100755
index 6fe9318..0000000
--- a/src/osgEarth/TextureCompositor.cpp
+++ /dev/null
@@ -1,79 +0,0 @@
-/* -*-c++-*- */
-/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2016 Pelican Mapping
- * http://osgearth.org
- *
- * osgEarth is free software; you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>
- */
-
-#include <osgEarth/TextureCompositor>
-#include <osgEarth/Registry>
-#include <osgEarth/Capabilities>
-
-using namespace osgEarth;
-
-#define LC "[TextureCompositor] "
-
-
-TextureCompositor::TextureCompositor()
-{
-    //nop
-}
-
-bool
-TextureCompositor::reserveTextureImageUnit(int&        out_unit,
-                                           const char* requestor)
-{
-    out_unit = -1;
-    unsigned maxUnits = osgEarth::Registry::instance()->getCapabilities().getMaxGPUTextureUnits();
-    
-    Threading::ScopedMutexLock exclusiveLock( _reservedUnitsMutex );
-    for( unsigned i=0; i<maxUnits; ++i )
-    {
-        if (_reservedUnits.find(i) == _reservedUnits.end())
-        {
-            _reservedUnits.insert( i );
-            out_unit = i;
-            if ( requestor )
-            {
-                OE_INFO << LC << "Texture unit " << i << " reserved for " << requestor << "\n";
-            }
-            return true;
-        }
-    }
-    return false;
-}
-
-void
-TextureCompositor::releaseTextureImageUnit(int unit)
-{
-    Threading::ScopedMutexLock exclusiveLock( _reservedUnitsMutex );
-    _reservedUnits.erase( unit );
-}
-
-bool
-TextureCompositor::setTextureImageUnitOffLimits(int unit)
-{
-    Threading::ScopedMutexLock exclusiveLock( _reservedUnitsMutex );
-    if (_reservedUnits.find(unit) != _reservedUnits.end())
-    {
-        // uh-on. Already in use!
-        return false;
-    }
-    else
-    {
-        _reservedUnits.insert( unit );
-        return true;
-    }
-}
diff --git a/src/osgEarth/ThreadingUtils b/src/osgEarth/ThreadingUtils
index 583fc66..c5cfe5d 100644
--- a/src/osgEarth/ThreadingUtils
+++ b/src/osgEarth/ThreadingUtils
@@ -28,9 +28,6 @@
 #include <map>
 
 #define USE_CUSTOM_READ_WRITE_LOCK 1
-//#ifdef _DEBUG
-//#  define TRACE_THREADS 1
-//#endif
 
 namespace osgEarth { namespace Threading
 {   
@@ -46,58 +43,46 @@ namespace osgEarth { namespace Threading
     extern OSGEARTH_EXPORT unsigned getCurrentThreadId();
 
 
-#ifdef USE_CUSTOM_READ_WRITE_LOCK
 
     /**
-     * Event with a toggled signal state.
+     * Event with a binary signaled state, for multi-threaded sychronization.
+     *
+     * The event has two states:
+     *  "set" means that a call to wait() will not block;
+     *  "unset" means that calls to wait() will block until another thread calls set().
+     *
+     * The event starts out unset. 
+     *
+     * Typical usage: Thread A creates Thread B to run asynchronous code. Thread A
+     * then calls wait(), which blocks Thread A. When Thread B is finished, it calls
+     * set(). Thread A then wakes up and continues execution.
      */
-    class Event 
+    class OSGEARTH_EXPORT Event 
     {
     public:
-        Event() : _set( false ) { }
+        //! Construct a new event
+        Event();
 
-        ~Event() { 
-            reset(); 
-            for( int i=0; i<255; ++i ) // workaround buggy broadcast
-                _cond.signal();
-        }
+        //! DTOR
+        ~Event();
 
-        inline bool wait() {
-            OpenThreads::ScopedLock<OpenThreads::Mutex> lock( _m );
-            return _set ? true : (_cond.wait( &_m ) == 0);
-        }
+        //! Block until the event is set, then return true if set, false on error.
+        bool wait();
 
-        /** waits on a signal, and then automatically resets it before returning. */
-        inline bool waitAndReset() {
-            OpenThreads::ScopedLock<OpenThreads::Mutex> lock( _m );
-            if ( _set ) {
-                _set = false;
-                return true;
-            }
-            else {
-                bool value = _cond.wait( &_m ) == 0;
-                _set = false;
-                return value;
-            }
-        }
+        //! Like wait(), but also returns false on timeout.
+        bool wait(unsigned timeout_ms);
 
-        inline void set() {
-            OpenThreads::ScopedLock<OpenThreads::Mutex> lock( _m );
-            if ( !_set ) {
-                _set = true;
-                _cond.broadcast(); // possible deadlock before OSG r10457 on windows
-                //_cond.signal();
-            }
-        }
+        //! Like wait(), but resets the state before returning.
+        bool waitAndReset();
 
-        inline void reset() {
-            OpenThreads::ScopedLock<OpenThreads::Mutex> lock( _m );
-            _set = false;
-        }
+        //! Set the event state, causing any waiters to unblock.
+        void set();
 
-        inline bool isSet() const {
-            return _set;
-        }
+        //! Reset (unset) the event state; new waiters will block until set() is called.
+        void reset();
+
+        //! Whether the event state is set (waiters will not block).
+        inline bool isSet() const { return _set; }
 
     protected:
         OpenThreads::Mutex _m;
@@ -105,50 +90,36 @@ namespace osgEarth { namespace Threading
         bool _set;
     };
 
-    /** Same as an Event, but waits on multiple notifications before releasing its wait. */
-    class MultiEvent 
+    /** Wraps an Event in a Referenced. */
+    class OSGEARTH_EXPORT RefEvent : public Event, public osg::Referenced
+    {
+    };
+
+    /**
+     * Same as Event, but set() must be called N times before unblocking
+     * waiting thread(s).
+     */
+    class OSGEARTH_EXPORT MultiEvent 
     {
     public:
-        MultiEvent( int num =1 ) : _set( num ), _num(num)  { }
+        //! Construct a multi-event with the number of setters.
+        MultiEvent(int num =1);
 
-        ~MultiEvent() {
-            reset();
-            for( int i=0; i<255; ++i ) // workaround buggy broadcast
-                _cond.signal();
-        }
+        //! DTOR
+        ~MultiEvent();
 
-        inline bool wait() {
-            OpenThreads::ScopedLock<OpenThreads::Mutex> lock( _m );
-            while( _set > 0 )
-                if ( _cond.wait( &_m ) != 0 )
-                    return false;
-            return true;
-        }
+        //! Block the calling thread until all N set()s are called.
+        bool wait();
 
-        /** waits on a signal, and then automatically resets it before returning. */
-        inline bool waitAndReset() {
-            OpenThreads::ScopedLock<OpenThreads::Mutex> lock( _m );
-            while( _set > 0 )
-                if ( _cond.wait( &_m ) != 0 )
-                    return false;
-            _set = _num;
-            return true;
-        }
+        //! Same as wait(), but resets the state after returning.
+        bool waitAndReset();
 
-        inline void notify() {
-            OpenThreads::ScopedLock<OpenThreads::Mutex> lock( _m );
-            if ( _set > 0 )
-                --_set;
-            if ( _set == 0 )
-                _cond.broadcast(); // possible deadlock before OSG r10457 on windows
-            //_cond.signal();
-        }
+        //! Adds one signal to the event; when all signals are set, waiters will unblock.
+        void set();
+        void notify() { set(); }
 
-        inline void reset( int num =0 ) {
-            OpenThreads::ScopedLock<OpenThreads::Mutex> lock( _m );
-            if ( num > 0 ) _num = num;
-            _set = _num;
-        }
+        //! Resets the state.
+        void reset();
 
     protected:
         OpenThreads::Mutex _m;
@@ -157,120 +128,135 @@ namespace osgEarth { namespace Threading
     };
 
     /**
-     * Custom read/write lock. The read/write lock in OSG can unlock mutexes from a different
-     * thread than the one that locked them - this can hang the thread in Windows.
+     * Future is the consumer-side interface to an asynchronous operation.
      *
-     * Adapted from:
-     * http://www.codeproject.com/KB/threads/ReadWriteLock.aspx
+     * Usage: 
+     *   Producer (usually an asynchronous function call) creates a Promise<Object>
+     *   and immediately returns promise.getFuture(). The Consumer then performs other
+     *   work, and eventually (or immediately) called Future.get() or Future.release().
+     *   Either call will block until the asynchronous operation is complete and the
+     *   result in Future is available.
      */
-    class ReadWriteMutex
+    template<typename T>
+    class Future
     {
-#ifdef TRACE_THREADS
-        typedef std::set<unsigned> TracedThreads;
-        TracedThreads _trace;
-        OpenThreads::Mutex _traceMutex;
-#endif
+    private:
+        // internal structure to track referenced to the result
+        struct RefPtrRef : public osg::Referenced {
+            RefPtrRef(T* obj = 0L) : _obj(obj) { }
+            osg::ref_ptr<T> _obj;
+        };
 
     public:
-        ReadWriteMutex() :
-          _readerCount(0)
-        { 
-            _noWriterEvent.set();
-            _noReadersEvent.set();
+        //! Blank CTOR
+        Future() {
+            _ev = new RefEvent();
+            _objRef = new RefPtrRef();
         }
 
-        void readLock()
-        {
+        //! Copy CTOR
+        Future(const Future& rhs) : _ev(rhs._ev), _objRef(rhs._objRef.get()) { }
 
-#ifdef TRACE_THREADS
-            {
-                OpenThreads::ScopedLock<OpenThreads::Mutex> ttLock(_traceMutex);
-                if( _trace.find(getCurrentThreadId()) != _trace.end() )
-                    OE_WARN << "TRACE: tried to double-lock" << std::endl;
-            }
-#endif
-            for( ; ; )
-            {
-                _noWriterEvent.wait();             // wait for a writer to quit if there is one
-                incrementReaderCount();            // register this reader
-                if ( !_noWriterEvent.isSet() )     // double lock check, in case a writer snuck in while inrementing
-                    decrementReaderCount();        // if it did, undo the registration and try again
-                else
-                    break;                         // otherwise, we're in
-            }
-
-#ifdef TRACE_THREADS
-            {
-                OpenThreads::ScopedLock<OpenThreads::Mutex> ttLock(_traceMutex);
-                _trace.insert(getCurrentThreadId());
-            }
-#endif
+        //! True if the promise was resolved and a result if available.
+        bool isAvailable() const {
+            return _ev->isSet();
         }
 
-        void readUnlock()
-        {
-            decrementReaderCount();                // unregister this reader
-            
-#ifdef TRACE_THREADS
-            {
-                OpenThreads::ScopedLock<OpenThreads::Mutex> ttLock(_traceMutex);
-                _trace.erase(getCurrentThreadId());
-            }
-#endif
+        //! True if the Promise that generated this Future no longer exists.
+        bool isAbandoned() const {
+            return _objRef->referenceCount() == 1;
         }
 
-        void writeLock()
-        {
-#ifdef TRACE_THREADS
-            {
-                OpenThreads::ScopedLock<OpenThreads::Mutex> ttLock(_traceMutex);
-                if( _trace.find(getCurrentThreadId()) != _trace.end() )
-                    OE_WARN << "TRACE: tried to double-lock" << std::endl;
-            }
-#endif
-            OpenThreads::ScopedLock<OpenThreads::Mutex> lock( _lockWriterMutex ); // one at a time please
-            _noWriterEvent.wait();    // wait for a writer to quit if there is one
-            _noWriterEvent.reset();   // prevent further writers from joining
-            _noReadersEvent.wait();   // wait for all readers to quit
-
-#ifdef TRACE_THREADS
-            {
-                OpenThreads::ScopedLock<OpenThreads::Mutex> ttLock(_traceMutex);
-                _trace.insert(getCurrentThreadId());
-            }
-#endif
+        //! The result value; blocks until it is available (or abandonded) and then returns it.
+        T* get() {
+            while(!_ev->wait(1000u))
+                if (isAbandoned()) return 0L;
+            return _objRef->_obj.get();
         }
 
-        void writeUnlock()
-        {
-            _noWriterEvent.set();
-
-#ifdef TRACE_THREADS
-            {
-                OpenThreads::ScopedLock<OpenThreads::Mutex> ttLock(_traceMutex);
-                _trace.erase(getCurrentThreadId());
-            }
-#endif
+        //! The result value; blocks until available (or abandoned) and returns it; then resets to initial state.
+        T* release() {
+            while(!_ev->wait(1000u))
+                if (isAbandoned()) return 0L;
+            T* out = _objRef->_obj.release();
+            _ev->reset();
+            return out;
         }
 
-    protected:
+    private:
+        osg::ref_ptr<RefEvent> _ev;
+        osg::ref_ptr<RefPtrRef> _objRef;
+        template<typename U> friend class Promise;
+    };
+    
+    /**
+     * Promise is the producer-side interface to an asynchronous operation.
+     *
+     * Usage: The code that initiates an asychronous operation creates a Promise
+     *   object, dispatches the asynchronous code, and immediately returns 
+     *   Promise.getFuture(). The caller can then call future.get() to block until
+     *   the result is available.
+     */
+    template<typename T>
+    class Promise
+    {
+    public:
+        //! This promise's future result.
+        const Future<T> getFuture() const { return _future; }
 
-        void incrementReaderCount()
-        {
-            OpenThreads::ScopedLock<OpenThreads::Mutex> lock( _readerCountMutex );
-            _readerCount++;            // add a reader
-            _noReadersEvent.reset();   // there's at least one reader now so clear the flag
+        //! Resolve (fulfill) the promise with the provided result value.
+        void resolve(T* value) {
+            _future._objRef->_obj = value;
+            _future._ev->set();
         }
 
-        void decrementReaderCount()
-        {
-            OpenThreads::ScopedLock<OpenThreads::Mutex> lock( _readerCountMutex );
-            _readerCount--;               // remove a reader
-            if ( _readerCount <= 0 )      // if that was the last one, signal that writers are now allowed
-                _noReadersEvent.set();
+        //! True if the promise is resolved and the Future holds a valid result.
+        bool isResolved() const {
+            return _future._ev->isSet();
+        }
+
+        //! True is there are no Future objects waiting on this Promise.
+        bool isAbandoned() const {
+            return _future._objRef->referenceCount() == 1;
         }
 
     private:
+        Future<T> _future;
+    };
+    
+#ifdef USE_CUSTOM_READ_WRITE_LOCK
+
+    /**
+     * Custom read/write lock. The read/write lock in OSG can unlock mutexes from a different
+     * thread than the one that locked them - this can hang the thread in Windows.
+     *
+     * Adapted from:
+     * http://www.codeproject.com/KB/threads/ReadWriteLock.aspx
+     */
+    class OSGEARTH_EXPORT ReadWriteMutex
+    {
+    public:
+        //! Construct a read/write mutex.
+        ReadWriteMutex();
+
+        //! Lock for reading. Multiple threads can take a read lock simultaneously.
+        void readLock();
+
+        //! Unlock for reading.
+        void readUnlock();
+
+        //! Lock for writing. This is exclusive; no other read OR write locks may be taken.
+        void writeLock();
+
+        //! Release a write lock.
+        void writeUnlock();
+
+    protected:
+
+        void incrementReaderCount();
+        void decrementReaderCount();
+
+    private:
         int    _readerCount;
         Mutex  _lockWriterMutex;
         Mutex  _readerCountMutex;
@@ -307,4 +293,3 @@ namespace osgEarth { namespace Threading
 
 
 #endif // OSGEARTH_THREADING_UTILS_H
-
diff --git a/src/osgEarth/ThreadingUtils.cpp b/src/osgEarth/ThreadingUtils.cpp
index 9c22e1c..7cf3999 100644
--- a/src/osgEarth/ThreadingUtils.cpp
+++ b/src/osgEarth/ThreadingUtils.cpp
@@ -53,3 +53,173 @@ unsigned osgEarth::Threading::getCurrentThreadId()
   return (unsigned)pthread_self();
 #endif
 }
+
+//...................................................................
+
+Event::Event() :
+_set(false)
+{
+    //nop
+}
+
+Event::~Event()
+{
+    reset();
+    for (int i = 0; i < 255; ++i) // workaround buggy broadcast
+        _cond.signal();
+}
+
+bool Event::wait()
+{
+    OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_m);
+    return _set ? true : (_cond.wait(&_m) == 0);
+}
+
+bool Event::wait(unsigned timeout_ms)
+{
+    OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_m);
+    return _set ? true : (_cond.wait(&_m, timeout_ms) == 0);
+}
+
+bool Event::waitAndReset()
+{
+    OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_m);
+    if (_set) {
+        _set = false;
+        return true;
+    }
+    else {
+        bool value = _cond.wait(&_m) == 0;
+        _set = false;
+        return value;
+    }
+}
+
+void Event::set()
+{
+    OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_m);
+    if (!_set) {
+        _set = true;
+        _cond.broadcast(); // possible deadlock before OSG r10457 on windows
+    }
+}
+
+void Event::reset()
+{
+    OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_m);
+    _set = false;
+}
+
+//...................................................................
+
+MultiEvent::MultiEvent(int num) :
+_set(num), _num(num) 
+{
+    //nop
+}
+
+MultiEvent::~MultiEvent()
+{
+    reset();
+    for (int i = 0; i < 255; ++i) // workaround buggy broadcast
+        _cond.signal();
+}
+
+bool MultiEvent::wait()
+{
+    OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_m);
+    while (_set > 0)
+        if (_cond.wait(&_m) != 0)
+            return false;
+    return true;
+}
+
+bool MultiEvent::waitAndReset()
+{
+    OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_m);
+    while (_set > 0)
+        if (_cond.wait(&_m) != 0)
+            return false;
+    _set = _num;
+    return true;
+}
+
+void MultiEvent::set()
+{
+    OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_m);
+    if (_set > 0)
+        --_set;
+    if (_set == 0)
+        _cond.broadcast(); // possible deadlock before OSG r10457 on windows
+    //_cond.signal();
+}
+
+void MultiEvent::reset()
+{
+    OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_m);
+    _set = _num;
+}
+
+//...................................................................
+
+ReadWriteMutex::ReadWriteMutex() :
+_readerCount(0)
+{
+    _noWriterEvent.set();
+    _noReadersEvent.set();
+}
+
+void ReadWriteMutex::readLock()
+{
+    for (;;)
+    {
+        _noWriterEvent.wait();           // wait for a writer to quit if there is one
+        incrementReaderCount();          // register this reader
+        if (!_noWriterEvent.isSet())     // double lock check, in case a writer snuck in while inrementing
+            decrementReaderCount();      // if it did, undo the registration and try again
+        else
+            break;                       // otherwise, we're in
+    }
+}
+
+void ReadWriteMutex::readUnlock()
+{
+    decrementReaderCount();              // unregister this reader
+}
+
+void ReadWriteMutex::writeLock()
+{
+    for (;;)
+    {
+        _noReadersEvent.wait(); // wait for no readers
+
+        OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_lockWriterMutex);
+        _noWriterEvent.wait();  // wait for no writers
+        _noWriterEvent.reset(); // signal that there is now a writer
+
+        if (_noReadersEvent.isSet()) // still no readers? done.
+            break;
+        else
+            _noWriterEvent.set(); // otherwise, a reader snuck in, so try again.
+    }
+}
+
+void ReadWriteMutex::writeUnlock()
+{
+    _noWriterEvent.set();
+}
+
+void ReadWriteMutex::incrementReaderCount()
+{
+    OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_readerCountMutex);
+    _readerCount++;            // add a reader
+    _noReadersEvent.reset();   // there's at least one reader now so clear the flag
+}
+
+void ReadWriteMutex::decrementReaderCount()
+{
+    OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_readerCountMutex);
+    _readerCount--;               // remove a reader
+    if (_readerCount <= 0)      // if that was the last one, signal that writers are now allowed
+        _noReadersEvent.set();
+}
diff --git a/src/osgEarth/TileKeyDataStore b/src/osgEarth/TileKeyDataStore
index dd1538b..2d4ba1e 100644
--- a/src/osgEarth/TileKeyDataStore
+++ b/src/osgEarth/TileKeyDataStore
@@ -27,6 +27,7 @@ namespace osgEarth
     /**
      * Interface that provides access to a registry of in-memory
      * tile nodes managed by a terrain engine.
+     * @deprecated
      */
     template<typename T>
     class /*header-only*/ TileKeyDataStore
diff --git a/src/osgEarth/TilePatchCallback b/src/osgEarth/TilePatchCallback
deleted file mode 100644
index e785bc0..0000000
--- a/src/osgEarth/TilePatchCallback
+++ /dev/null
@@ -1,79 +0,0 @@
-/* -*-c++-*- */
-/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2016 Pelican Mapping
- * http://osgearth.org
- *
- * osgEarth is free software; you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>
- */
-#ifndef OSGEARTH_TILE_PATCH_CALLBACK_H
-#define OSGEARTH_TILE_PATCH_CALLBACK_H 1
-
-#include <osgEarth/TileKey>
-#include <vector>
-
-// forward declarations
-namespace osg {
-    class Node;
-    class StateSet;
-}
-namespace osgUtil {
-    class CullVisitor;
-}
-
-namespace osgEarth
-{
-    /**
-     * A "Tile Patch" is a patch of surface geometry that corresponds to a terrain tile.
-     * For an engine that supports patches, this callback will let you install custom
-     * rendering state for a tile patch.
-     */
-    class OSGEARTH_EXPORT TilePatchCallback : public osg::Referenced
-    {
-    public:
-        /**
-         * Cull the patch for a given tile. You must push the passed-in stateset, traverse
-         * the patch, and then pop the stateset yourself. You can of course push any additional
-         * state or cull any additional drawables that you want to.
-         *
-         * @param[in ] nv Cull visitor that is traversing the scene graph
-         * @param[in ] key Tile key of the tile currenting being culled
-         * @param[in ] stateSet StateSet corresponding to the current TileKey; the implementation
-         *             is responsible for pushing/popping this
-         * @param[in ] patch Optional node to traverse if you want to render a tessellation patch.
-         */
-        virtual void cull(
-            osgUtil::CullVisitor* nv, 
-            const TileKey&        key, 
-            osg::StateSet*        stateSet, 
-            osg::Node*            patch) =0;
-
-        /**
-         * Release any resources associated with the provided tile key. The engine will call
-         * this automatically then a tile is removed from the scene.
-         *
-         * @param[in ] key TileKey for which to release resources.
-         */
-        virtual void release(
-            const TileKey& key) { }
-
-    protected:
-        virtual ~TilePatchCallback() { }
-    };
-
-    typedef std::vector< osg::ref_ptr<TilePatchCallback> > TilePatchCallbacks;
-
-
-} // namespace osgEarth
-
-#endif // OSGEARTH_TILE_PATCH_CALLBACK_H
diff --git a/src/osgEarth/TileRasterizer b/src/osgEarth/TileRasterizer
new file mode 100644
index 0000000..09f5ebb
--- /dev/null
+++ b/src/osgEarth/TileRasterizer
@@ -0,0 +1,116 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+* Copyright 2016 Pelican Mapping
+* http://osgearth.org
+*
+* osgEarth is free software; you can redistribute it and/or modify
+* it under the terms of the GNU Lesser General Public License as published by
+* the Free Software Foundation; either version 2 of the License, or
+* (at your option) any later version.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+* IN THE SOFTWARE.
+*
+* You should have received a copy of the GNU Lesser General Public License
+* along with this program.  If not, see <http://www.gnu.org/licenses/>
+*/
+#ifndef OSGEARTH_TILE_RASTERIZER_H
+#define OSGEARTH_TILE_RASTERIZER_H 1
+
+#include <osgEarth/Common>
+#include <osgEarth/GeoData>
+#include <osgEarth/ThreadingUtils>
+#include <osgEarth/TileKey>
+#include <osg/Camera>
+#include <osg/BufferObject>
+#include <osg/Texture2D>
+#include <queue>
+
+namespace osgEarth
+{
+    // placeholder - might go away -gw
+    class OSGEARTH_EXPORT GeoNode : public osg::Object
+    {
+    public:
+        META_Object(osgEarth, GeoNode);
+        GeoNode() { }
+        GeoNode(const GeoNode& rhs, const osg::CopyOp& copy) { }
+        GeoNode(osg::Node* node, const GeoExtent& extent) : _node(node), _extent(extent) { }
+
+        osg::ref_ptr<osg::Node> _node;
+        GeoExtent _extent;
+    };
+
+    /**
+     * Node that will render node graphs to textures, one at a time.
+     */
+    class OSGEARTH_EXPORT TileRasterizer : public osg::Camera
+    {
+    public:
+        /** Construct a new tile rasterizer camera */
+        TileRasterizer();
+
+        /**
+         * Schedule a rasterization to an osg::Image.
+         * @param node Node to render to the image
+         * @param size of the target image (both dimensions)
+         * @param extent geospatial extent of the node to render.
+         * @return Future image - blocks on .get()
+         */
+        Threading::Future<osg::Image> push(osg::Node* node, unsigned size, const GeoExtent& extent);
+
+        /**
+         * Schedule a rasterization to a texture.
+         * @param node    Node to render to the texture
+         * @param texture Texture to which to render the node
+         * @param extent  Geographic extent of the output texture
+         */
+        void push(osg::Node* node, osg::Texture* texture, const GeoExtent& extent);
+
+    public: // osg::Node
+
+        void traverse(osg::NodeVisitor&);
+
+    private:
+        virtual ~TileRasterizer();
+
+        // internal - image with custom readback
+        struct ReadbackImage : public osg::Image
+        {
+            osg::RenderInfo* _ri;
+            void readPixels(int x, int y, int width, int height, GLenum pixelFormat, GLenum type, int packing);
+        };
+
+        // internal - scheduled rasterization job
+        struct Job
+        {
+            osg::ref_ptr<osg::Node> _node;
+            GeoExtent _extent;
+            osg::ref_ptr<osg::Texture> _texture;
+            osg::ref_ptr<ReadbackImage> _image;
+            osg::ref_ptr<osg::PixelBufferObject> _imagePBO;
+            Threading::Promise<osg::Image> _imagePromise;
+        };
+
+        mutable Threading::Mutex _mutex;
+        typedef std::queue<Job> JobQueue;
+        mutable JobQueue _pendingJobs;  // queue for jobs waiting to render
+        mutable JobQueue _readbackJobs; // queue for jobs waiting for rtt/glReadPixels to finish
+        mutable JobQueue _finishedJobs; // queue for jobs waiting for the promise to resolve
+
+        //osg::ref_ptr<osg::Uniform> _distortionU;
+
+    public: // internal
+
+        void preDraw(osg::RenderInfo&) const;
+        void postDraw(osg::RenderInfo&) const;
+    };
+
+} // namespace osgEarth
+
+#endif // OSGEARTH_TILE_RASTERIZER_H
diff --git a/src/osgEarth/TileRasterizer.cpp b/src/osgEarth/TileRasterizer.cpp
new file mode 100644
index 0000000..7183728
--- /dev/null
+++ b/src/osgEarth/TileRasterizer.cpp
@@ -0,0 +1,284 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+ * Copyright 2016 Pelican Mapping
+ * http://osgearth.org
+ *
+ * osgEarth is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+#include <osgEarth/TileRasterizer>
+#include <osgEarth/NodeUtils>
+#include <osgEarth/VirtualProgram>
+#include <osgEarth/Registry>
+#include <osg/MatrixTransform>
+#include <osg/FrameBufferObject>
+#include <osgDB/ReadFile>
+
+#define LC "[TileRasterizer] "
+
+using namespace osgEarth;
+
+namespace
+{
+    template<typename T>
+    struct PreDrawRouter : public osg::Camera::DrawCallback
+    {
+        T* _object;
+        PreDrawRouter(T* object) : _object(object) { }
+        void operator()(osg::RenderInfo& renderInfo) const {
+            _object->preDraw(renderInfo);
+        }
+    };
+
+    template<typename T>
+    struct PostDrawRouter : public osg::Camera::DrawCallback
+    {
+        T* _object;
+        PostDrawRouter(T* object) : _object(object) { }
+        void operator()(osg::RenderInfo& renderInfo) const {
+            _object->postDraw(renderInfo);
+        }
+    };
+}
+
+namespace
+{
+    const char* distort =
+        "#version " GLSL_VERSION_STR "\n"
+        "uniform float oe_rasterizer_f; \n"
+        "void oe_rasterizer_clip(inout vec4 vert) { \n"
+        "    float h = (vert.y + vert.w)/(2.0*vert.w); \n"
+        "    float d = 1.0; \n" //1.2299; \n" //mix(1.0, oe_rasterizer_f, h); \n"
+        "    vert.x *= d; \n"
+        "} \n";
+}
+
+TileRasterizer::TileRasterizer() :
+osg::Camera()
+{
+    // active an update traversal.
+    setNumChildrenRequiringUpdateTraversal(1);
+    setCullingActive(false);
+
+    // set up the RTT camera.
+    setClearColor(osg::Vec4(0,0,0,0));
+    setClearMask(GL_COLOR_BUFFER_BIT);
+    setReferenceFrame(ABSOLUTE_RF);
+    //setComputeNearFarMode( osg::CullSettings::DO_NOT_COMPUTE_NEAR_FAR );
+    setRenderOrder(PRE_RENDER);
+    setRenderTargetImplementation(FRAME_BUFFER_OBJECT);
+    setImplicitBufferAttachmentMask(0, 0);
+    setSmallFeatureCullingPixelSize(0.0f);
+    setViewMatrix(osg::Matrix::identity());
+
+    osg::StateSet* ss = getOrCreateStateSet();
+    //ss->setAttribute(new osg::Program(), osg::StateAttribute::OFF | osg::StateAttribute::OVERRIDE);
+
+    ss->setMode(GL_BLEND, 1);
+    ss->setMode(GL_LIGHTING, 0);
+    ss->setMode(GL_CULL_FACE, 0);
+    
+    this->setPreDrawCallback(new PreDrawRouter<TileRasterizer>(this));
+    this->setPostDrawCallback(new PostDrawRouter<TileRasterizer>(this));
+
+#if 0 // works in OE, not in VRV :(
+    osg::ref_ptr<osg::GraphicsContext::Traits> traits = new osg::GraphicsContext::Traits();
+    traits->sharedContext = 0L;
+    traits->doubleBuffer = false;
+    traits->x = 0, traits->y = 0, traits->width = 256, traits->height = 256;
+    traits->format = GL_RGBA;
+    traits->red = 8;
+    traits->green = 8;
+    traits->blue = 8;
+    traits->alpha = 8;
+    traits->depth = 0;
+    osg::GraphicsContext* gc = osg::GraphicsContext::createGraphicsContext(traits);
+    setGraphicsContext(gc);
+#endif
+    setDrawBuffer(GL_FRONT);
+    setReadBuffer(GL_FRONT);
+
+    VirtualProgram* vp = VirtualProgram::getOrCreate(ss);
+    vp->setInheritShaders(false);
+
+    // Someday we might need this to undistort rasterizer cells. We'll see
+#if 0
+    vp->setFunction("oe_rasterizer_clip", distort, ShaderComp::LOCATION_VERTEX_CLIP);
+    _distortionU = new osg::Uniform("oe_rasterizer_f", 1.0f);
+    ss->addUniform(_distortionU.get());
+#endif
+}
+
+TileRasterizer::~TileRasterizer()
+{
+    OE_DEBUG << LC << "~TileRasterizer\n";
+}
+
+void
+TileRasterizer::push(osg::Node* node, osg::Texture* texture, const GeoExtent& extent)
+{
+    Threading::ScopedMutexLock lock(_mutex);
+
+    _pendingJobs.push(Job());
+    Job& job = _pendingJobs.back();
+    job._node = node;
+    job._texture = texture;
+    job._extent = extent;
+}
+
+void
+TileRasterizer::ReadbackImage::readPixels(
+    int x, int y, int width, int height,
+    GLenum pixelFormat, GLenum type, int packing)
+{
+    OE_DEBUG << LC << "ReadPixels in context " << _ri->getContextID() << std::endl;
+
+    glPixelStorei(GL_PACK_ALIGNMENT, _packing);
+    glPixelStorei(GL_PACK_ROW_LENGTH, _rowLength);
+
+    if (getPixelBufferObject())
+    {
+        _ri->getState()->bindPixelBufferObject(getPixelBufferObject()->getOrCreateGLBufferObject(_ri->getContextID()));
+        glReadPixels(x, y, width, height, getPixelFormat(), getDataType(), 0L);
+    }
+    else
+    {
+        glReadPixels(x, y, width, height, getPixelFormat(), getDataType(), _data);
+    }
+}
+
+
+Threading::Future<osg::Image>
+TileRasterizer::push(osg::Node* node, unsigned size, const GeoExtent& extent)
+{    
+    Threading::ScopedMutexLock lock(_mutex);
+
+    _pendingJobs.push(Job());
+    Job& job = _pendingJobs.back();
+
+    job._node = node;
+    job._extent = extent;
+    job._image = new ReadbackImage();
+    job._image->allocateImage(size, size, 1, GL_RGBA, GL_UNSIGNED_BYTE);
+
+    //job._imagePBO = new osg::PixelBufferObject(job._image.get());
+    //job._imagePBO->setTarget(GL_PIXEL_PACK_BUFFER);
+    //job._imagePBO->setUsage(GL_STREAM_READ);
+    //job._image->setPixelBufferObject(job._imagePBO.get());
+
+    return job._imagePromise.getFuture();
+}
+
+void
+TileRasterizer::traverse(osg::NodeVisitor& nv)
+{
+    if (nv.getVisitorType() == nv.UPDATE_VISITOR)
+    {
+        Threading::ScopedMutexLock lock(_mutex);
+
+        if (!_finishedJobs.empty())
+        {
+            Job& job = _finishedJobs.front();
+            removeChild(job._node.get());
+            job._imagePromise.resolve(job._image.get());
+            _finishedJobs.pop(); 
+            detach(osg::Camera::COLOR_BUFFER);
+            dirtyAttachmentMap();
+        }
+
+        if (!_pendingJobs.empty() && _readbackJobs.empty() && _finishedJobs.empty())
+        {
+            Job& job = _pendingJobs.front();
+
+            // Configure a top-down orothographic camera:
+            setProjectionMatrixAsOrtho2D(
+                job._extent.xMin(), job._extent.xMax(),
+                job._extent.yMin(), job._extent.yMax());
+
+            // Job includes a texture to populate:
+            if (job._texture.valid())
+            {
+                // Setup the viewport and attach to the new texture
+                setViewport(0, 0, job._texture->getTextureWidth(), job._texture->getTextureHeight());
+                attach(COLOR_BUFFER, job._texture.get(), 0u, 0u, /*mipmap=*/false);
+                dirtyAttachmentMap();
+            }
+
+            // Job includes an image to populate, so use the built-in FBO target texture:
+            else if (job._image.valid())
+            {
+                setViewport(0, 0, job._image->s(), job._image->t());
+                attach(COLOR_BUFFER, job._image.get(), 0u, 0u);
+                dirtyAttachmentMap();
+            }
+
+            // Add the node to the scene graph so it'll get rendered.
+            addChild(job._node.get());
+
+            // If this job has a readback image, push the job to the next queue
+            // where it will be picked up for readback.
+            if (job._image.valid())
+            {
+                _readbackJobs.push(job);
+            }
+
+            // Remove the texture from the queue.
+            _pendingJobs.pop();
+            //OE_INFO << LC
+            //    << "P=" << _pendingJobs.size()
+            //    << ", R=" << _readbackJobs.size()
+            //    << ", F=" << _finishedJobs.size()
+            //    << std::endl;
+        }
+    }
+
+    //if (!getBufferAttachmentMap().empty())
+    else if (nv.getVisitorType() == nv.CULL_VISITOR)
+    {
+        osg::Camera::traverse(nv);
+    }
+}
+
+void
+TileRasterizer::preDraw(osg::RenderInfo& ri) const
+{
+    if (!_readbackJobs.empty())
+    {
+        Threading::ScopedMutexLock lock(_mutex);
+
+        if (!_readbackJobs.empty()) // double check!
+        {
+            Job& job = _readbackJobs.front();
+            if (job._image.valid())
+            {
+                job._image.get()->_ri = &ri;
+            }
+        }
+    }
+}
+
+void
+TileRasterizer::postDraw(osg::RenderInfo& ri) const
+{
+    if (!_readbackJobs.empty())
+    {
+        Threading::ScopedMutexLock lock(_mutex);
+
+        if (!_readbackJobs.empty()) // double check!
+        {
+            Job& job = _readbackJobs.front();
+            _finishedJobs.push(job);
+            _readbackJobs.pop();
+        }
+    }
+}
diff --git a/src/osgEarth/TileSource b/src/osgEarth/TileSource
index 5ba9e38..8e07d3b 100644
--- a/src/osgEarth/TileSource
+++ b/src/osgEarth/TileSource
@@ -58,22 +58,6 @@ namespace osgEarth
     {
     public:
 
-        /** Size (in each dimension) of tiles to generate. */
-        optional<int>& tileSize() { return _tileSize; }
-        const optional<int>& tileSize() const { return _tileSize; }
-
-        /** For heightfields, treat this value as a "no data" marker. */
-        optional<float>& noDataValue() { return _noDataValue; }
-        const optional<float>& noDataValue() const { return _noDataValue; }
-
-        /** For heightfields, treat everything below this value as a "no data" marker. */
-        optional<float>& minValidValue() { return _minValidValue; }
-        const optional<float>& minValidValue() const { return _minValidValue; }
-
-        /** For heightfields, treat everything above this value as a "no data" marker. */
-        optional<float>& maxValidValue() { return _maxValidValue; }
-        const optional<float>& maxValidValue() const { return _maxValidValue; }
-
         /** File in which to store a tile blacklist. */
         optional<std::string>& blacklistFilename() { return _blacklistFilename; }
         const optional<std::string>& blacklistFilename() const { return _blacklistFilename; }
@@ -91,10 +75,6 @@ namespace osgEarth
         optional<bool>& bilinearReprojection() { return _bilinearReprojection; }
         const optional<bool>& bilinearReprojection() const { return _bilinearReprojection; }
 
-        /** Force the tilesource to report this as the maximum available LOD */
-        optional<unsigned>& maxDataLevel() { return _maxDataLevel; }
-        const optional<unsigned>& maxDataLevel() const { return _maxDataLevel; }
-
         /** Whether to rasterize into coverage data, which contains discrete non-interpolable values. */
         optional<bool>& coverage() { return _coverage; }
         const optional<bool>& coverage() const { return _coverage; }
@@ -104,17 +84,6 @@ namespace osgEarth
         const optional<std::string>& osgOptionString() const { return _osgOptionString; }
 
     public:
-        /** For backwards-compatibility; use minValidValue() instead 
-         *  @deprecated */
-        optional<float>& noDataMinValue() { return _minValidValue; }
-        const optional<float>& noDataMinValue() const { return _minValidValue; }
-
-        /** For backwards-compatibility; use maxValidValue() instead 
-         *  @deprecated */
-        optional<float>& noDataMaxValue() { return _maxValidValue; }
-        const optional<float>& noDataMaxValue() const { return _maxValidValue; }
-
-    public:
         TileSourceOptions( const ConfigOptions& options =ConfigOptions() );
 
         /** dtor */
@@ -129,13 +98,10 @@ namespace osgEarth
     private:
         void fromConfig( const Config& conf );
 
-        optional<int>            _tileSize;
-        optional<float>          _noDataValue, _minValidValue, _maxValidValue;
         optional<ProfileOptions> _profileOptions;
         optional<std::string>    _blacklistFilename;
         optional<int>            _L2CacheSize;
         optional<bool>           _bilinearReprojection;
-        optional<unsigned>       _maxDataLevel;
         optional<bool>           _coverage;
         optional<std::string>    _osgOptionString;
     };
@@ -196,8 +162,6 @@ namespace osgEarth
         void write(const std::string &filename) const;
 
     private:
-        //typedef std::set<TileKey> BlacklistedTiles;
-        //BlacklistedTiles _tiles;
         mutable LRUCache<TileKey, bool> _tiles; // using as a set (value unused)
     };
 
@@ -224,9 +188,6 @@ namespace osgEarth
         /** interface name, used by the plugin system. */
         static const char* INTERFACE_NAME;
 
-        //typedef osgEarth::Status Status;
-        //static Status STATUS_OK;
-
     public:
         struct ImageOperation : public osg::Referenced {
             virtual void operator()( osg::ref_ptr<osg::Image>& in_out_image ) =0;
@@ -247,32 +208,28 @@ namespace osgEarth
             const Mode&           openMode  =MODE_READ,
             const osgDB::Options* dbOptions =0L);
 
-        /**
-         * Gets the status of this tile source.
-         */
+        //! Gets the status of this tile source.
         const Status& getStatus() const { return _status; }
 
-        /**
-         * Gets the number of pixels per tile for this TileSource.
-         */
+        //! Gets the number of pixels per tile for this TileSource.
         virtual int getPixelsPerTile() const;
+        virtual void setPixelsPerTile(unsigned size);
 
-        /**
-         * Gets the list of areas with data for this TileSource
-         */
+        //! Gets the list of areas with data for this TileSource
         const DataExtentList& getDataExtents() const { return _dataExtents; }
         DataExtentList& getDataExtents() { return _dataExtents; }
 
-        /**
-         * Call when you modify the data extents list.
-         */
-        void dirtyDataExtents();
+        //! Value representing no data
+        float getNoDataValue() const { return _noDataValue; }
+        void setNoDataValue(float value) { _noDataValue = value; }
 
-        /**
-         * Gets the union of all the data extents
-         */
-        const GeoExtent& getDataExtentsUnion() const;
+        //! Values less than this are considered "no data"
+        float getMinValidValue() const { return _minValidValue; }
+        void setMinValidValue(float value) { _minValidValue = value; }
 
+        //! Values greater than this are considered "no data"
+        float getMaxValidValue() const { return _maxValidValue; }
+        void setMaxValidValue(float value) { _maxValidValue = value; }
 
         /**
          * Creates an image for the given TileKey. The TileKey's profile must match
@@ -333,24 +290,6 @@ namespace osgEarth
         virtual const Profile* getProfile() const;
 
         /**
-         * Gets the nodata elevation value
-         */
-        virtual float getNoDataValue() {
-            return _options.noDataValue().value(); }
-
-        /**
-         * Gets the minimum valid value. Any value below this is treated as "nodata"
-         */
-        virtual float getMinValidValue() {
-            return _options.minValidValue().value(); }
-
-        /**
-         * Gets the maximum valid value. Any value above this is considered "nodata"
-         */
-        virtual float getMaxValidValue() {
-            return _options.maxValidValue().value(); }
-
-        /**
          * Gets the preferred extension for this TileSource
          * @deprecated No longer used
          */
@@ -363,53 +302,6 @@ namespace osgEarth
         const TileBlacklist* getBlacklist() const;
 
         /**
-         * Whether or not the source has data for the given TileKey
-         */
-        virtual bool hasData(const TileKey& key) const;
-
-        /**
-         * Whether or not the source has data to create fallback tile for 
-         * the given TileKey
-         */
-        virtual bool hasDataForFallback(const TileKey& key) const;
-
-        /**
-         * Whether the tile source can generate data for the specified LOD.
-         */
-        virtual bool hasDataAtLOD( unsigned lod ) const;
-
-        /**
-         * Whether the tile source can generate data within the specified extent
-         */
-        virtual bool hasDataInExtent( const GeoExtent& extent ) const;
-
-        /**
-         * Whether the tile source has data at the given location
-         * @param location
-         *     The location to check
-         * @param exact
-         *     If true, check all the data extents for this TileSource.  If false, just do a quick check of the union of all the data extents.
-         */
-        virtual bool hasDataAt( const GeoPoint& location, bool exact = false) const;
-
-        /**
-         * Returns (in "output") the TileKey of the best available data that intersects
-         * the input TileKey (according to the DataExtents). For example, if the highest
-         * DataExtent reports an LOD of 9, and the input TileKey is LOD 13, the function
-         * will return the LOD 9 ancestor of the input TileKey IF they intersect.
-         *
-         * If you DataExtents or LOD information is available, the function copies the
-         * input to the output and returns true.
-         *
-         * @param key    TileKey to test
-         * @param output Corresponding ancestor written here if available
-         * @return True if an intersecting key was found; false if not.
-         */
-        virtual bool getBestAvailableTileKey(
-            const osgEarth::TileKey& key,
-            osgEarth::TileKey&       output) const;
-
-        /**
          * Whether this TileSource produces tiles whose data can change after
          * it's been created.
          */
@@ -434,6 +326,12 @@ namespace osgEarth
          */
         const TileSourceOptions& getOptions() const { return _options; }
 
+        /**
+         * Set the L2 cache size default (if the size was not set in 
+         * the options or environment). Must call this before open().
+         */
+        void setDefaultL2CacheSize(int size);
+
     public:
 
         /* methods required by osg::Object */
@@ -499,6 +397,9 @@ namespace osgEarth
          */
         MapFrame& getMapFrame() const;
 
+        //! Compat - do not override
+        unsigned getTileSize() const { return getPixelsPerTile(); }
+
 
     protected: // deprecated
 
@@ -516,7 +417,7 @@ namespace osgEarth
     private:
 
         osg::ref_ptr<const Profile> _profile;
-        const TileSourceOptions     _options;
+        TileSourceOptions _options;
 
         friend class Map;
         friend class MapEngine;
@@ -528,10 +429,13 @@ namespace osgEarth
 
         osg::ref_ptr<MemCache> _memCache;
 
-        DataExtentList _dataExtents;
-        GeoExtent      _dataExtentsUnion;
-        Status         _status;
-        Mode           _mode;
+        DataExtentList  _dataExtents;
+        Status          _status;
+        Mode            _mode;
+        unsigned        _tileSize;     
+        float           _noDataValue;
+        float           _minValidValue;
+        float           _maxValidValue;
 
         bool _openCalled;
 
diff --git a/src/osgEarth/TileSource.cpp b/src/osgEarth/TileSource.cpp
index fcdf2b8..6dc7ff6 100644
--- a/src/osgEarth/TileSource.cpp
+++ b/src/osgEarth/TileSource.cpp
@@ -26,6 +26,7 @@
 #include <osgEarth/ThreadingUtils>
 #include <osgEarth/MemCache>
 #include <osgEarth/MapFrame>
+#include <osgEarth/Progress>
 #include <osgDB/FileUtils>
 #include <osgDB/FileNameUtils>
 #include <osgDB/ReadFile>
@@ -143,10 +144,6 @@ TileBlacklist::write(std::ostream &output) const
 
 TileSourceOptions::TileSourceOptions( const ConfigOptions& options ) :
 DriverConfigOptions   ( options ),
-_tileSize             ( 256 ),
-_noDataValue          ( (float)SHRT_MIN ),
-_minValidValue        ( -32000.0f ),
-_maxValidValue        (  32000.0f ),
 _L2CacheSize          ( 16 ),
 _bilinearReprojection ( true ),
 _coverage             ( false )
@@ -159,17 +156,12 @@ Config
 TileSourceOptions::getConfig() const
 {
     Config conf = DriverConfigOptions::getConfig();
-    conf.updateIfSet( "tile_size", _tileSize );
-    conf.updateIfSet( "nodata_value", _noDataValue );
-    conf.updateIfSet( "min_valid_value", _minValidValue );
-    conf.updateIfSet( "max_valid_value", _maxValidValue );
-    conf.updateIfSet( "blacklist_filename", _blacklistFilename);
-    conf.updateIfSet( "l2_cache_size", _L2CacheSize );
-    conf.updateIfSet( "bilinear_reprojection", _bilinearReprojection );
-    conf.updateIfSet( "max_data_level", _maxDataLevel );
-    conf.updateIfSet( "coverage", _coverage );
-    conf.updateIfSet( "osg_option_string", _osgOptionString );
-    conf.updateObjIfSet( "profile", _profileOptions );
+    conf.set( "blacklist_filename", _blacklistFilename);
+    conf.set( "l2_cache_size", _L2CacheSize );
+    conf.set( "bilinear_reprojection", _bilinearReprojection );
+    conf.set( "coverage", _coverage );
+    conf.set( "osg_option_string", _osgOptionString );
+    conf.setObj( "profile", _profileOptions );
     return conf;
 }
 
@@ -185,16 +177,9 @@ TileSourceOptions::mergeConfig( const Config& conf )
 void
 TileSourceOptions::fromConfig( const Config& conf )
 {
-    conf.getIfSet( "tile_size", _tileSize );
-    conf.getIfSet( "nodata_value", _noDataValue );
-    conf.getIfSet( "min_valid_value", _minValidValue );
-    conf.getIfSet( "nodata_min", _minValidValue ); // backcompat
-    conf.getIfSet( "max_valid_value", _maxValidValue );
-    conf.getIfSet( "nodata_max", _maxValidValue ); // backcompat
     conf.getIfSet( "blacklist_filename", _blacklistFilename);
     conf.getIfSet( "l2_cache_size", _L2CacheSize );
     conf.getIfSet( "bilinear_reprojection", _bilinearReprojection );
-    conf.getIfSet( "max_data_level", _maxDataLevel );
     conf.getIfSet( "coverage", _coverage );
     conf.getIfSet( "osg_option_string", _osgOptionString );
     conf.getObjIfSet( "profile", _profileOptions );
@@ -216,33 +201,12 @@ TileSource::TileSource(const TileSourceOptions& options) :
 _options( options ),
 _status ( Status::Error("Not initialized") ),
 _mode   ( 0 ),
-_openCalled( false )
+_openCalled( false ),
+_tileSize(256),
+_noDataValue( (float)SHRT_MIN ),
+_minValidValue( -32000.0f ),
+_maxValidValue(  32000.0f )
 {
-    this->setThreadSafeRefUnref( true );
-
-    // Initialize the l2 cache size to the options.
-    int l2CacheSize = *options.L2CacheSize();
-
-    // See if it was overridden with an env var.
-    char const* l2env = ::getenv( "OSGEARTH_L2_CACHE_SIZE" );
-    if ( l2env )
-    {
-        l2CacheSize = as<int>( std::string(l2env), 0 );
-    }
-
-    // Env cache-only mode also disables the L2 cache.
-    char const* noCacheEnv = ::getenv( "OSGEARTH_MEMORY_PROFILE" );
-    if ( noCacheEnv )
-    {
-        l2CacheSize = 0;
-    }
-
-    // Initialize the l2 cache if it's size is > 0
-    if ( l2CacheSize > 0 )
-    {
-        _memCache = new MemCache( l2CacheSize );
-    }
-
     if (_options.blacklistFilename().isSet())
     {
         _blacklistFilename = _options.blacklistFilename().value();
@@ -273,16 +237,48 @@ TileSource::~TileSource()
     }
 }
 
+void
+TileSource::setDefaultL2CacheSize(int size)
+{
+    if (_options.L2CacheSize().isSet() == false)
+    {
+        _options.L2CacheSize().init(size);
+    }
+}
+
 const Status&
 TileSource::open(const Mode&           openMode,
-                 const osgDB::Options* options)
+                 const osgDB::Options* readOptions)
 {
     if (!_openCalled)
     {
         _mode = openMode;
 
+        // Initialize the l2 cache size to the options.
+        int l2CacheSize = _options.L2CacheSize().get();
+
+        // See if it was overridden with an env var.
+        char const* l2env = ::getenv( "OSGEARTH_L2_CACHE_SIZE" );
+        if ( l2env )
+        {
+            l2CacheSize = as<int>( std::string(l2env), 0 );
+        }
+
+        // Env cache-only mode also disables the L2 cache.
+        char const* noCacheEnv = ::getenv( "OSGEARTH_MEMORY_PROFILE" );
+        if ( noCacheEnv )
+        {
+            l2CacheSize = 0;
+        }
+
+        // Initialize the l2 cache if it's size is > 0
+        if ( l2CacheSize > 0 )
+        {
+            _memCache = new MemCache( l2CacheSize );
+        }
+
         // Initialize the underlying data store
-        Status status = initialize(options);
+        Status status = initialize(readOptions);
 
         // Check the return status. The TileSource MUST have a valid
         // Profile after initialization.
@@ -311,33 +307,13 @@ TileSource::open(const Mode&           openMode,
 int
 TileSource::getPixelsPerTile() const
 {
-    return _options.tileSize().value();
+    return _tileSize;
 }
 
-
-void TileSource::dirtyDataExtents()
-{
-    _dataExtentsUnion = GeoExtent::INVALID;
-}
-
-const GeoExtent& TileSource::getDataExtentsUnion() const
+void
+TileSource::setPixelsPerTile(unsigned size)
 {
-    if (_dataExtentsUnion.isInvalid() && _dataExtents.size() > 0)
-    {
-        Threading::ScopedMutexLock lock(_mutex);
-        {
-            if (_dataExtentsUnion.isInvalid() && _dataExtents.size() > 0) // double-check
-            {
-                GeoExtent e(_dataExtents[0]);
-                for (unsigned int i = 1; i < _dataExtents.size(); i++)
-                {
-                    e.expandToInclude(_dataExtents[i]);
-                }
-                const_cast<TileSource*>(this)->_dataExtentsUnion = e;
-            }
-        }
-    }
-    return _dataExtentsUnion;
+    _tileSize = size;
 }
 
 osg::Image*
@@ -358,12 +334,21 @@ TileSource::createImage(const TileKey&        key,
 
     osg::ref_ptr<osg::Image> newImage = createImage(key, progress);
 
+    // Check for cancelation. The TileSource implementation should do this
+    // internally but we check here once last time just in case the 
+    // implementation does not.
+    if (progress && progress->isCanceled())
+    {
+        return 0L;
+    }
+
+    // Run the pre-caching operation if there is one:
     if ( prepOp )
         (*prepOp)( newImage );
 
+    // Cache to the L2 cache:
     if ( newImage.valid() && _memCache.valid() )
     {
-        // cache it to the memory cache.
         _memCache->getOrCreateDefaultBin()->write(key.str(), newImage.get(), 0L);
     }
 
@@ -383,10 +368,20 @@ TileSource::createHeightField(const TileKey&        key,
     {
         ReadResult r = _memCache->getOrCreateDefaultBin()->readObject(key.str(), 0L);
         if ( r.succeeded() )
+        {
             return r.release<osg::HeightField>();
+        }
     }
 
     osg::ref_ptr<osg::HeightField> newHF = createHeightField( key, progress );
+    
+    // Check for cancelation. The TileSource implementation should do this
+    // internally but we check here once last time just in case the 
+    // implementation does not.
+    if (progress && progress->isCanceled())
+    {
+        return 0L;
+    }
 
     if ( prepOp )
         (*prepOp)( newHF );
@@ -396,8 +391,7 @@ TileSource::createHeightField(const TileKey&        key,
         _memCache->getOrCreateDefaultBin()->write(key.str(), newHF.get(), 0L);
     }
 
-    //TODO: why not just newHF.release()? -gw
-    return newHF.valid() ? new osg::HeightField( *newHF.get() ) : 0L;
+    return newHF.release();
 }
 
 osg::Image*
@@ -459,239 +453,6 @@ TileSource::getProfile() const
     return _profile.get();
 }
 
-bool
-TileSource::hasDataAtLOD( unsigned lod ) const
-{
-    // the sematics here are really "MIGHT have data at LOD".
-
-    // Explicit max data level?
-    if ( _options.maxDataLevel().isSet() && lod > _options.maxDataLevel().value() )
-        return false;
-
-    // If no data extents are provided, just return true
-    if ( _dataExtents.size() == 0 )
-        return true;
-
-    for (DataExtentList::const_iterator itr = _dataExtents.begin(); itr != _dataExtents.end(); ++itr)
-    {
-        if ((!itr->minLevel().isSet() || itr->minLevel() <= lod) &&
-            (!itr->maxLevel().isSet() || itr->maxLevel() >= lod))
-        {
-            return true;
-        }
-    }
-
-    return false;
-}
-
-
-bool
-TileSource::hasDataInExtent( const GeoExtent& extent ) const
-{
-    // if the extent is invalid, no intersection.
-    if ( !extent.isValid() )
-        return false;
-
-    // If no data extents are provided, just return true
-    if ( _dataExtents.size() == 0 )
-        return true;
-
-    bool intersects = false;
-
-    for (DataExtentList::const_iterator itr = _dataExtents.begin(); itr != _dataExtents.end(); ++itr)
-    {
-        if ( extent.intersects( *itr ) )
-        {
-            intersects = true;
-            break;
-        }
-    }
-    return intersects;
-}
-
-bool
-TileSource::hasDataAt( const GeoPoint& location, bool exact) const
-{
-    // If the location is invalid then return false
-    if (!location.isValid())
-        return false;
-
-    // If no data extents are provided, just return true
-    if (_dataExtents.size() == 0)
-        return true;
-
-    if (!exact)
-    {
-        return getDataExtentsUnion().contains(location);
-    }
-   
-
-    for (DataExtentList::const_iterator itr = _dataExtents.begin(); itr != _dataExtents.end(); ++itr)
-    {
-        if (itr->contains( location ) )
-        {
-            return true;
-        }
-    }
-    return false;
-}
-
-
-bool
-TileSource::hasData(const osgEarth::TileKey& key) const
-{
-    //sematics: "might have data"
-
-    if ( !key.valid() )
-        return false;
-
-    // If no data extents are provided, and there's no data level override,
-    // return true because there might be data but there's no way to tell.
-    if (_dataExtents.size() == 0 && !_options.maxDataLevel().isSet())
-    {
-        return true;
-    }
-
-    unsigned int lod = key.getLevelOfDetail();
-
-    // Remap the lod to an appropriate lod if it's not in the same SRS        
-    if (!key.getProfile()->isHorizEquivalentTo( getProfile() ) )
-    {        
-        lod = getProfile()->getEquivalentLOD( key.getProfile(), key.getLevelOfDetail() );        
-    }
-
-    // If there's an explicit LOD override and we've exceeded it, no data.
-    if (_options.maxDataLevel().isSet() && lod > _options.maxDataLevel().value())
-    {
-        return false;
-    }
-
-    // If there are no extents to check, there might be data.
-    if (_dataExtents.size() == 0)
-    {
-        return true;
-    }
-
-    bool intersectsData = false;
-    const osgEarth::GeoExtent& keyExtent = key.getExtent();
-    
-    for (DataExtentList::const_iterator itr = _dataExtents.begin(); itr != _dataExtents.end(); ++itr)
-    {
-        if ((keyExtent.intersects( *itr )) && 
-            (!itr->minLevel().isSet() || itr->minLevel() <= lod ) &&
-            (!itr->maxLevel().isSet() || itr->maxLevel() >= lod ))
-        {
-            intersectsData = true;
-            break;
-        }
-    }
-
-    return intersectsData;
-}
-
-bool
-TileSource::getBestAvailableTileKey(const osgEarth::TileKey& key,
-                                    osgEarth::TileKey&       output) const
-{
-    // trivial reject
-    if ( !key.valid() )
-        return false;
-
-    // trivial accept: no data extents = not enough info.
-    if (_dataExtents.size() == 0)
-    {
-        output = key;
-        return true;
-    }
-
-    // trivial reject: key doesn't intersect the union of data extents at all.
-    if ( !getDataExtentsUnion().intersects(key.getExtent()) )
-    {
-        return false;
-    }
-
-    bool     intersects = false;
-    unsigned highestLOD = 0;
-
-    // We must use the equivalent lod b/c the key can be in any profile.
-    int layerLOD = getProfile()->getEquivalentLOD( key.getProfile(), key.getLOD() );
-    
-    for (DataExtentList::const_iterator itr = _dataExtents.begin(); itr != _dataExtents.end(); ++itr)
-    {
-        // check for 2D intersection:
-        if (key.getExtent().intersects( *itr ))
-        {
-            // check that the extent isn't higher-resolution than our key:
-            if ( !itr->minLevel().isSet() || layerLOD >= (int)itr->minLevel().get() )
-            {
-                // Got an intersetion; now test the LODs:
-                intersects = true;
-                
-                // Is the high-LOD set? If not, there's not enough information
-                // so just assume our key might be good.
-                if ( itr->maxLevel().isSet() == false )
-                {
-                    output = key;
-                    return true;
-                }
-
-                // Is our key at a lower or equal LOD than the max key in this extent?
-                // If so, our key is good.
-                else if ( layerLOD <= (int)itr->maxLevel().get() )
-                {
-                    output = key;
-                    return true;
-                }
-
-                // otherwise, record the highest encountered LOD that
-                // intersects our key.
-                else if ( itr->maxLevel().get() > highestLOD )
-                {
-                    highestLOD = itr->maxLevel().get();
-                }
-            }
-        }
-    }
-
-    if ( intersects )
-    {
-        output = key.createAncestorKey( highestLOD );
-        return true;
-    }
-    else
-    {
-        return false;
-    }
-}
-
-bool
-TileSource::hasDataForFallback(const osgEarth::TileKey& key) const
-{
-    //sematics: might have data.
-
-    if ( !key.valid() )
-        return false;
-
-    //If no data extents are provided, just return true
-    if (_dataExtents.size() == 0) 
-        return true;
-
-    const osgEarth::GeoExtent& keyExtent = key.getExtent();
-    bool intersectsData = false;
-
-    for (DataExtentList::const_iterator itr = _dataExtents.begin(); itr != _dataExtents.end(); ++itr)
-    {
-        if ((keyExtent.intersects( *itr )) && 
-            (!itr->minLevel().isSet() || itr->minLevel() <= key.getLOD()))
-        {
-            intersectsData = true;
-            break;
-        }
-    }
-
-    return intersectsData;
-}
-
 TileBlacklist*
 TileSource::getBlacklist()
 {
@@ -714,7 +475,7 @@ TileSource::getBlacklist() const
 TileSource*
 TileSourceFactory::create(const TileSourceOptions& options)
 {
-    TileSource* result = 0L;
+    osg::ref_ptr<TileSource> source;
 
     std::string driver = options.getDriver();
     if ( driver.empty() )
@@ -728,15 +489,15 @@ TileSourceFactory::create(const TileSourceOptions& options)
     dbopt->setPluginStringData( TILESOURCE_INTERFACE_TAG, TileSource::INTERFACE_NAME );
 
     std::string driverExt = std::string( ".osgearth_" ) + driver;
-    result = dynamic_cast<TileSource*>( osgDB::readObjectFile( driverExt, dbopt.get() ) );
-    if ( !result )
+    osg::ref_ptr<osg::Object> object = osgDB::readRefObjectFile( driverExt, dbopt.get() );
+    source = dynamic_cast<TileSource*>( object.release() );
+    if ( !source )
     {
         OE_INFO << LC << "Failed to load TileSource driver \"" << driver << "\"" << std::endl;
     }
-
     else
     {
-        OE_DEBUG << LC << "Tile source Profile = " << (result->getProfile() ? result->getProfile()->toString() : "NULL") << std::endl;
+        OE_DEBUG << LC << "Tile source Profile = " << (source->getProfile() ? source->getProfile()->toString() : "NULL") << std::endl;
 
         // apply an Override Profile if provided.
         if ( options.profile().isSet() )
@@ -744,12 +505,12 @@ TileSourceFactory::create(const TileSourceOptions& options)
             const Profile* profile = Profile::create(*options.profile());
             if ( profile )
             {
-                result->setProfile( profile );
+                source->setProfile( profile );
             }
         }
     }
 
-    return result;
+    return source.release();
 }
 
 
diff --git a/src/osgEarth/TileVisitor.cpp b/src/osgEarth/TileVisitor.cpp
index c1b0f1f..abb95cc 100644
--- a/src/osgEarth/TileVisitor.cpp
+++ b/src/osgEarth/TileVisitor.cpp
@@ -103,7 +103,7 @@ void TileVisitor::estimate()
     CacheEstimator est;
     est.setMinLevel( _minLevel );
     est.setMaxLevel( _maxLevel );
-    est.setProfile( _profile ); 
+    est.setProfile( _profile.get() ); 
     for (unsigned int i = 0; i < _extents.size(); i++)
     {                
         est.addExtent( _extents[ i ] );
@@ -270,7 +270,7 @@ void MultithreadedTileVisitor::run(const Profile* mapProfile)
 bool MultithreadedTileVisitor::handleTile( const TileKey& key )        
 {    
     // Add the tile to the task queue.
-    _taskService->add( new HandleTileTask(_tileHandler, this, key ) );
+    _taskService->add( new HandleTileTask(_tileHandler.get(), this, key ) );
     return true;
 }
 
@@ -297,7 +297,7 @@ bool TaskList::load( const std::string &filename)
                 as<unsigned int>(parts[0], 0u), 
                 as<unsigned int>(parts[1], 0u), 
                 as<unsigned int>(parts[2], 0u),
-                _profile ) );
+                _profile.get() ) );
         }
     }
 
@@ -470,7 +470,7 @@ void MultiprocessTileVisitor::processBatch()
     // Add the task file as a temp file to the task to make sure it gets deleted
     task->addTempFile( filename );
 
-    _taskService->add(task);
+    _taskService->add(task.get());
     _batch.clear();
 }
 
diff --git a/src/osgEarth/TraversalData b/src/osgEarth/TraversalData
index 16c5356..ccfd75e 100644
--- a/src/osgEarth/TraversalData
+++ b/src/osgEarth/TraversalData
@@ -50,6 +50,8 @@ namespace osgEarth
         bool exists(osg::Referenced* owner, const std::string& key) const;
 
         int size() const;
+
+        void remove(osg::Referenced* owner, const std::string& key);
         
     protected:
         typedef fast_map<std::string, osg::ref_ptr<osg::Referenced> > StringTable;
@@ -81,7 +83,7 @@ namespace osgEarth
             std::string _key;
 
             Install(const std::string& key) : _key(key) { }
-            Install(const std::string& key, osg::Referenced* data) : _key(key), _data(data) { }
+            Install(const std::string& key, osg::Referenced* data) : _data(data), _key(key) { }
 
             void operator()(osg::Node* node, osg::NodeVisitor* nv)
             {
@@ -96,6 +98,8 @@ namespace osgEarth
         template<typename T> static T* fetch(osg::NodeVisitor& nv, const std::string& key) {
             return dynamic_cast<T*>( _fetch(nv, key) ); }
 
+        static void remove(osg::NodeVisitor& nv, const std::string& key);
+
         static osg::Referenced* _fetch(osg::NodeVisitor& nv, const std::string& key);
     };
 
diff --git a/src/osgEarth/TraversalData.cpp b/src/osgEarth/TraversalData.cpp
index 50d845d..fefa466 100644
--- a/src/osgEarth/TraversalData.cpp
+++ b/src/osgEarth/TraversalData.cpp
@@ -85,6 +85,24 @@ TransientUserDataStore::exists(osg::Referenced* owner, const std::string& key) c
     return ( j != i->second._data.end() );
 }
 
+void
+TransientUserDataStore::remove(osg::Referenced* owner, const std::string& key)
+{
+    if (!owner)
+        return;
+    
+    Threading::ScopedMutexLock lock(_mutex);
+    Table::iterator i = _table.find(owner);
+    if ( i != _table.end() )
+    {
+        i->second._data.erase(key);
+        if (i->second._data.empty())
+        {
+            _table.erase(i);
+        }
+    }
+}
+
 bool
 TransientUserDataStore::unitTest()
 {
@@ -95,25 +113,25 @@ TransientUserDataStore::unitTest()
   osg::ref_ptr<osg::Referenced> data1 = new osg::Referenced;
   osg::ref_ptr<osg::Referenced> data2 = new osg::Referenced;
   osg::ref_ptr<osg::Referenced> data3 = new osg::Referenced;
-  tuds.store(owner1, "foo", data1);
-  tuds.store(owner1, "foo", data2);
-  tuds.store(owner1, "foo", data1);
+  tuds.store(owner1.get(), "foo", data1.get());
+  tuds.store(owner1.get(), "foo", data2.get());
+  tuds.store(owner1.get(), "foo", data1.get());
   data1 = NULL;
-  tuds.store(owner1, "foo", data2);
+  tuds.store(owner1.get(), "foo", data2.get());
   data2 = new osg::Referenced;
-  tuds.store(owner1, "foo", data3);
-  tuds.store(owner1, "foo", data2);
+  tuds.store(owner1.get(), "foo", data3.get());
+  tuds.store(owner1.get(), "foo", data2.get());
   data1 = new osg::Referenced;
   owner1 = owner2;
-  tuds.store(owner1, "foo", data3);
-  tuds.store(owner1, "foo", data1);
-  tuds.store(owner3, "foo", data1);
+  tuds.store(owner1.get(), "foo", data3.get());
+  tuds.store(owner1.get(), "foo", data1.get());
+  tuds.store(owner3.get(), "foo", data1.get());
   owner3 = NULL;
-  tuds.store(owner1, "foo", data2);
-  tuds.store(owner2, "foo", data2);
+  tuds.store(owner1.get(), "foo", data2.get());
+  tuds.store(owner2.get(), "foo", data2.get());
   owner1 = owner2 = owner3 = NULL;
   owner1 = new osg::Referenced;
-  tuds.store(owner1, "foo", data3);
+  tuds.store(owner1.get(), "foo", data3.get());
   return true;
 }
 
@@ -130,6 +148,12 @@ VisitorData::_fetch(osg::NodeVisitor& nv, const std::string& key)
     return Registry::instance()->dataStore().fetch( &nv, key );
 }
 
+void
+VisitorData::remove(osg::NodeVisitor& nv, const std::string& key)
+{
+    Registry::instance()->dataStore().remove( &nv, key );
+}
+
 bool
 VisitorData::isSet(osg::NodeVisitor& nv, const std::string& key)
 {
diff --git a/src/osgEarth/URI b/src/osgEarth/URI
index d886f05..9f31c7e 100644
--- a/src/osgEarth/URI
+++ b/src/osgEarth/URI
@@ -269,6 +269,11 @@ namespace osgEarth
             conf.getIfSet("option_string", _optionString);
         }
 
+    public: // Static convenience methods
+
+        /** Encodes text to URL safe test. Escapes special charaters */
+        inline static std::string urlEncode(const std::string &value);
+
     protected:
         std::string _baseURI;
         std::string _fullURI;
@@ -479,6 +484,36 @@ namespace osgEarth
             return false;
         }
     }
+
+
+//------------------------------------------------------------------------
+
+
+    // Ref.: https://stackoverflow.com/questions/154536/encode-decode-urls-in-c
+    std::string URI::urlEncode(const std::string &value)
+    {
+        std::ostringstream escaped;
+        escaped.fill('0');
+        escaped << std::hex;
+
+        for (std::string::const_iterator i = value.begin(), n = value.end(); i != n; ++i) {
+            std::string::value_type c = (*i);
+
+            // Keep alphanumeric and other accepted characters intact
+            if (isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~') {
+                escaped << c;
+                continue;
+            }
+
+            // Any other characters are percent-encoded
+            escaped << std::uppercase;
+            escaped << '%' << std::setw(2) << int((unsigned char)c);
+            escaped << std::nouppercase;
+        }
+
+        return escaped.str();
+    }
+
 }
 
 #endif // OSGEARTH_URI
diff --git a/src/osgEarth/URI.cpp b/src/osgEarth/URI.cpp
index 9519e01..0119ada 100644
--- a/src/osgEarth/URI.cpp
+++ b/src/osgEarth/URI.cpp
@@ -300,7 +300,9 @@ namespace
             }
             return HTTPClient::readObject(req, opt, p);
         }
-        ReadResult fromFile( const std::string& uri, const osgDB::Options* opt ) { return ReadResult(osgDB::readObjectFile(uri, opt)); }
+        ReadResult fromFile( const std::string& uri, const osgDB::Options* opt ) {
+            return ReadResult(osgDB::readRefObjectFile(uri, opt).get());
+        }
     };
 
     struct ReadNode
@@ -317,7 +319,9 @@ namespace
             }
             return HTTPClient::readNode(req, opt, p);
         }
-        ReadResult fromFile( const std::string& uri, const osgDB::Options* opt ) { return ReadResult(osgDB::readNodeFile(uri, opt)); }
+        ReadResult fromFile( const std::string& uri, const osgDB::Options* opt ) {
+            return ReadResult(osgDB::readRefNodeFile(uri, opt));
+        }
     };
 
     struct ReadImage
@@ -346,7 +350,7 @@ namespace
             return r;
         }
         ReadResult fromFile( const std::string& uri, const osgDB::Options* opt ) { 
-            ReadResult r = ReadResult(osgDB::readImageFile(uri, opt));
+            ReadResult r = ReadResult(osgDB::readRefImageFile(uri, opt));
             if ( r.getImage() ) r.getImage()->setFileName( uri );
             return r;
         }
@@ -445,7 +449,7 @@ namespace
                     if ( !gotResultFromCallback )
                     {
                         // no callback, just read from a local file.
-                        result = reader.fromFile( uri.full(), localOptions );
+                        result = reader.fromFile( uri.full(), localOptions.get() );
                     }
                 }
 
@@ -471,7 +475,7 @@ namespace
                     // first try to go to the cache if there is one:
                     if ( bin && cp->isCacheReadable() )
                     {                                                
-                        result = reader.fromCache( bin, uri.cacheKey() );                        
+                        result = reader.fromCache( bin.get(), uri.cacheKey() );                        
                         if ( result.succeeded() )
                         {                                        
                             expired = cp->isExpired(result.lastModifiedTime());
@@ -484,7 +488,7 @@ namespace
                     {                        
                         // Need to do this to support nested PLODs and Proxynodes.
                         osg::ref_ptr<osgDB::Options> remoteOptions =
-                            Registry::instance()->cloneOrCreateOptions( localOptions );
+                            Registry::instance()->cloneOrCreateOptions( localOptions.get() );
                         remoteOptions->getDatabasePathList().push_front( osgDB::getFilePath(uri.full()) );
 
                         // Store the existing object from the cache if there is one.
@@ -526,17 +530,10 @@ namespace
                             if ( result.succeeded() && !result.isFromCache() && bin && cp->isCacheWriteable() && bin )
                             {
                                 OE_DEBUG << LC << "Writing " << uri.cacheKey() << " to cache" << std::endl;
-                                bin->write( uri.cacheKey(), result.getObject(), result.metadata(), remoteOptions );
+                                bin->write( uri.cacheKey(), result.getObject(), result.metadata(), remoteOptions.get() );
                             }
                         }
                     }
-
-                    OE_TEST << LC 
-                        << uri.base() << ": " 
-                        << (result.succeeded() ? "OK" : "FAILED") 
-                        << "; policy=" << cp->usageString()
-                        << (result.isFromCache() && result.succeeded() ? "; (from cache)" : "")
-                        << std::endl;
                 }
 
 
@@ -550,6 +547,13 @@ namespace
                     }
                 }
             }
+
+            OE_TEST << LC
+                << uri.base() << ": "
+                << (result.succeeded() ? "OK" : "FAILED")
+                //<< "; policy=" << cp->usageString()
+                << (result.isFromCache() && result.succeeded() ? "; (from cache)" : "")
+                << std::endl;
         }
 
         // post-process if there's a post-URI callback.
diff --git a/src/osgEarth/Units b/src/osgEarth/Units
index 56629cb..0c8259f 100644
--- a/src/osgEarth/Units
+++ b/src/osgEarth/Units
@@ -23,6 +23,14 @@
 #include <osgEarth/Config>
 #include <ostream>
 
+#define GCC_VERSION (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__)
+
+#if GCC_VERSION >= 50300
+#define OPTIMIZE __attribute__((optimize("no-ipa-sra")))
+#else
+#define OPTIMIZE
+#endif
+
 namespace osgEarth
 {
     class Registry;
@@ -105,7 +113,7 @@ namespace osgEarth
             return false;
         }
 
-        static double convert( const Units& from, const Units& to, double input ) {
+        static OPTIMIZE double convert( const Units& from, const Units& to, double input ) {
             double output = input;
             convert( from, to, input, output );
             return output;
diff --git a/src/osgEarth/Units.cpp b/src/osgEarth/Units.cpp
index 133e546..dde4193 100644
--- a/src/osgEarth/Units.cpp
+++ b/src/osgEarth/Units.cpp
@@ -48,7 +48,7 @@ namespace
         std::string::size_type pos = input.find_first_of("eE");
         if (pos != std::string::npos && 
             input.length() > (pos+2) &&
-            (input.at(pos+1) == '-' || input.at(pos+1) == '+'))
+            (input[pos+1] == '-' || input[pos+1] == '+'))
         {
             start = input.begin() + pos + 2;
         }
diff --git a/src/osgEarth/Utils b/src/osgEarth/Utils
index ed7ee72..124c662 100644
--- a/src/osgEarth/Utils
+++ b/src/osgEarth/Utils
@@ -45,6 +45,33 @@ namespace osgEarth
 {    
     //------------------------------------------------------------------------
 
+    /**
+     * Utility for storing observables in an osg::Object
+     */
+    template<typename T>
+    class OptionsData : public osg::Object {
+    public:
+        static void set(osg::Object* o, const std::string& name, T* obj) {            
+            osg::UserDataContainer* udc = o->getOrCreateUserDataContainer();
+            unsigned i = udc->getUserObjectIndex(name);
+            if (i == udc->getNumUserObjects()) udc->removeUserObject(i);
+            udc->addUserObject(new OptionsData<T>(name, obj));
+        }
+        static bool lock(const osg::Object* o, const std::string& name, osg::ref_ptr<T>& out) {
+            if (!o) return false;
+            const OptionsData<T>* data = dynamic_cast<const OptionsData<T>*>(osg::getUserObject(o, name));
+            return data ? data->_obj.lock(out) : false;
+        }
+    public:
+        META_Object(osgEarth,OptionsData);
+        OptionsData(const std::string& name, T* obj) : _obj(obj) { setName(name); }
+        OptionsData() { }
+        OptionsData(const OptionsData& rhs, const osg::CopyOp& copy) : osg::Object(rhs, copy), _obj(rhs._obj) { }
+    private:
+        osg::observer_ptr<T> _obj;
+    };
+
+
     struct Utils
     {
         /**
@@ -58,12 +85,9 @@ namespace osgEarth
         
 
         // Polyfill
+        // @deprecated (since we require OSG 3.4.x now)
         static const osg::BoundingBox& getBoundingBox(const osg::Drawable* d) {
-#if OSG_VERSION_GREATER_THAN(3,3,1)
             return d->getBoundingBox();
-#else
-            return d->getBound();
-#endif
         }
     };
 
@@ -71,10 +95,12 @@ namespace osgEarth
      * Removes the given event handler from the view.
      * This is the equivalent of osgViewer::View::removeEventHandler which is not available
      * in older versions of OSG
+     * @deprecated
      */
     extern OSGEARTH_EXPORT void removeEventHandler(osgViewer::View* view, osgGA::GUIEventHandler* handler);
 
     // utility: functor for traversing a target node
+    // @deprecated
     template<typename T> struct TraverseFunctor {
         T* _target;
         TraverseFunctor(T* target) : _target(target) { }
@@ -82,6 +108,7 @@ namespace osgEarth
     };
 
     // utility: node that traverses another node via a functor
+    // @deprecated
     template<typename T>
     struct TraverseNode : public osg::Node {
         TraverseFunctor<T> _tf;
@@ -92,6 +119,7 @@ namespace osgEarth
     };
 
     // utility: cull callback that traverses another node
+    // @deprecated
     struct TraverseNodeCallback : public osg::NodeCallback {
         osg::ref_ptr<osg::Node> _node;
         TraverseNodeCallback(osg::Node* node) : _node(node) { }
@@ -101,8 +129,12 @@ namespace osgEarth
         }
     };
 
+#if OSG_VERSION_LESS_THAN(3,5,6)
+#define OE_HAVE_PIXEL_AUTO_TRANSFORM 1
     /**
      * A pixel-based AutoTransform variant.
+     * NOTE: AutoTransform was refactored for OSG 3.5.6 and this class does
+     * not work with the new version.
      */
     class OSGEARTH_EXPORT PixelAutoTransform : public osg::AutoTransform
     {
@@ -151,6 +183,7 @@ namespace osgEarth
         double _screenSpaceRotationRadians;
         osg::observer_ptr<osg::Node> _sizingNode;
     };
+#endif // OSG_VERSION_LESS_THAN(3,5,6)
 
     /**
      * Proxy class that registers a custom render bin's prototype with the
diff --git a/src/osgEarth/Utils.cpp b/src/osgEarth/Utils.cpp
index eb3582c..bc0f1d2 100644
--- a/src/osgEarth/Utils.cpp
+++ b/src/osgEarth/Utils.cpp
@@ -39,6 +39,8 @@ void osgEarth::removeEventHandler(osgViewer::View* view, osgGA::GUIEventHandler*
 
 //------------------------------------------------------------------------
 
+#ifdef OE_HAVE_PIXEL_AUTO_TRANSFORM
+
 #undef LC
 #define LC "[PixelAutoTransform] "
 
@@ -62,6 +64,8 @@ PixelAutoTransform::accept( osg::NodeVisitor& nv )
     if ( !nv.validNodeMask(*this) )
         return;
 
+    bool resetLodScale = false;
+    double oldLodScale = 1.0;
     if ( nv.getVisitorType() == osg::NodeVisitor::CULL_VISITOR )
     {
         // re-activate culling now that the first cull traversal has taken place.
@@ -293,13 +297,27 @@ PixelAutoTransform::accept( osg::NodeVisitor& nv )
             _dirty = false;
 
             // update the LOD Scale based on the auto-scale.
-            cv->setLODScale( 1.0/getScale().x() );
+            const double xScale = getScale().x();
+            if (xScale != 1.0 && xScale != 0.0)
+            {
+                oldLodScale = cv->getLODScale();
+                resetLodScale = true;
+                cv->setLODScale( 1.0/xScale );
+            }
 
         } // if (cv)
     } // if is cull visitor
 
     // finally, skip AT's accept and do Transform.
     Transform::accept(nv);
+
+    // Reset the LOD scale if we changed it
+    if (resetLodScale)
+    {
+        osgUtil::CullVisitor* cv = dynamic_cast<osgUtil::CullVisitor*>(&nv);
+        if ( cv )
+            cv->setLODScale( oldLodScale );
+    }
 }
 
 void
@@ -309,6 +327,8 @@ PixelAutoTransform::dirty()
     setCullingActive( false );
 }
 
+#endif // OE_HAVE_PIXEL_AUTO_TRANSFORM
+
 //-----------------------------------------------------------------------------
 
 #undef  LC
@@ -446,12 +466,11 @@ GeometryValidator::apply(osg::Geometry& geom)
         return;
     }
 
-#if OSG_VERSION_GREATER_OR_EQUAL(3,1,9)
-
     std::set<osg::BufferObject*> _vbos;
 
     osg::Geometry::ArrayList arrays;
     geom.getArrayList(arrays);
+
     for(unsigned i=0; i<arrays.size(); ++i)
     {
         osg::Array* a = arrays[i].get();
@@ -480,34 +499,6 @@ GeometryValidator::apply(osg::Geometry& geom)
         OE_NOTICE << LC << "Found a Geometry that uses more than one VBO (non-optimal sharing)\n";
     }
 
-#else // pre-3.1.9 ... phase out.
-
-    if ( geom.getColorArray() )
-    {
-        if ( geom.getColorBinding() == osg::Geometry::BIND_OVERALL && geom.getColorArray()->getNumElements() != 1 )
-        {
-            OE_NOTICE << "Color: BIND_OVERALL with wrong number of elements" << std::endl;
-        }
-        else if ( geom.getColorBinding() == osg::Geometry::BIND_PER_VERTEX && geom.getColorArray()->getNumElements() != numVerts )
-        {
-            OE_NOTICE << "Color: BIND_PER_VERTEX with colors.size != verts.size" << std::endl;
-        }
-    }
-
-    if ( geom.getNormalArray() )
-    {
-        if ( geom.getNormalBinding() == osg::Geometry::BIND_OVERALL && geom.getNormalArray()->getNumElements() != 1 )
-        {
-            OE_NOTICE << "Normal: BIND_OVERALL with wrong number of elements" << std::endl;
-        }
-        else if ( geom.getNormalBinding() == osg::Geometry::BIND_PER_VERTEX && geom.getNormalArray()->getNumElements() != numVerts )
-        {
-            OE_NOTICE << "Normal: BIND_PER_VERTEX with normals.size != verts.size" << std::endl;
-        }
-    }
-
-#endif
-
     const osg::Geometry::PrimitiveSetList& plist = geom.getPrimitiveSetList();
     
     std::set<osg::BufferObject*> _ebos;
diff --git a/src/osgEarth/Version b/src/osgEarth/Version
index fce8cca..2005429 100644
--- a/src/osgEarth/Version
+++ b/src/osgEarth/Version
@@ -28,10 +28,10 @@
 extern "C" {
 
 #define OSGEARTH_MAJOR_VERSION    2
-#define OSGEARTH_MINOR_VERSION    8
+#define OSGEARTH_MINOR_VERSION    9
 #define OSGEARTH_PATCH_VERSION    0
 #define OSGEARTH_SOVERSION        0
-#define OSGEARTH_RC_VERSION       0
+#define OSGEARTH_RC_VERSION       1
 #define OSGEARTH_DEVEL_VERSION    0     // 0 = release; >0 = interim devel version
 
 /* Convenience macro that can be used to decide whether a feature is present or not i.e.
diff --git a/src/osgEarth/VerticalDatum.cpp b/src/osgEarth/VerticalDatum.cpp
index 377a8e1..3003e32 100644
--- a/src/osgEarth/VerticalDatum.cpp
+++ b/src/osgEarth/VerticalDatum.cpp
@@ -218,14 +218,15 @@ VerticalDatum::isEquivalentTo( const VerticalDatum* rhs ) const
 VerticalDatum*
 VerticalDatumFactory::create( const std::string& init )
 {
-    VerticalDatum* result = 0L;
+    osg::ref_ptr<VerticalDatum> datum;
 
     std::string driverExt = Stringify() << ".osgearth_vdatum_" << init;
-    result = dynamic_cast<VerticalDatum*>( osgDB::readObjectFile(driverExt) );
-    if ( !result )
+    osg::ref_ptr<osg::Object> object = osgDB::readRefObjectFile( driverExt );
+    datum = dynamic_cast<VerticalDatum*>( object.release() );
+    if ( !datum )
     {
         OE_WARN << "WARNING: Failed to load Vertical Datum driver for \"" << init << "\"" << std::endl;
     }
 
-    return result;
+    return datum.release();
 }
diff --git a/src/osgEarth/VideoLayer b/src/osgEarth/VideoLayer
new file mode 100644
index 0000000..73ad35f
--- /dev/null
+++ b/src/osgEarth/VideoLayer
@@ -0,0 +1,93 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+* Copyright 2016 Pelican Mapping
+* http://osgearth.org
+*
+* osgEarth is free software; you can redistribute it and/or modify
+* it under the terms of the GNU Lesser General Public License as published by
+* the Free Software Foundation; either version 2 of the License, or
+* (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+* GNU Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public License
+* along with this program.  If not, see <http://www.gnu.org/licenses/>
+*/
+
+#ifndef OSGEARTH_VIDEO_LAYER_H
+#define OSGEARTH_VIDEO_LAYER_H 1
+
+#include <osgEarth/Common>
+#include <osgEarth/ImageLayer>
+#include <osg/Texture2D>
+
+namespace osgEarth
+{  
+    /**
+     * Initialization and serialization options for a video layer
+     */
+    class OSGEARTH_EXPORT VideoLayerOptions : public ImageLayerOptions
+    {
+    public:
+        /** Constructs new video layer options. */
+        VideoLayerOptions();
+        
+        /** Deserializes new video layer options. */
+        VideoLayerOptions(const ConfigOptions& options);
+
+        // Constructs new options with a layer name
+        VideoLayerOptions(const std::string& name);
+
+        /** dtor */
+        virtual ~VideoLayerOptions()
+        {
+        }
+
+    public:
+
+        optional<URI>& url() { return _url; }
+        const optional<URI>& url() const { return _url; }
+
+
+    public:
+        virtual Config getConfig() const;
+        virtual void mergeConfig( const Config& conf );
+        
+    private:
+        void fromConfig( const Config& conf );
+        void setDefaults();
+
+        optional<URI> _url;
+    };
+
+
+    /**
+     * A layer that displays a video texture on the earth.
+     */
+    class OSGEARTH_EXPORT VideoLayer : public osgEarth::ImageLayer
+    {
+    public:
+        META_Layer(osgEarth, VideoLayer, VideoLayerOptions);
+
+        VideoLayer();        
+        
+        VideoLayer( const VideoLayerOptions& options );
+
+        const Status& open();
+
+        virtual bool createTextureSupported() const { return true; }
+
+        virtual osg::Texture* createTexture(const TileKey& key, ProgressCallback* progress, osg::Matrixf& textureMatrix);
+
+        osg::Texture2D* getTexture() const { return _texture.get(); }
+
+    protected:
+
+        osg::ref_ptr< osg::Texture2D > _texture;
+    };
+} // namespace osgEarth
+
+#endif // OSGEARTH_VIDEO_LAYER_H
diff --git a/src/osgEarth/VideoLayer.cpp b/src/osgEarth/VideoLayer.cpp
new file mode 100644
index 0000000..5a05c00
--- /dev/null
+++ b/src/osgEarth/VideoLayer.cpp
@@ -0,0 +1,155 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+* Copyright 2016 Pelican Mapping
+* http://osgearth.org
+*
+* osgEarth is free software; you can redistribute it and/or modify
+* it under the terms of the GNU Lesser General Public License as published by
+* the Free Software Foundation; either version 2 of the License, or
+* (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+* GNU Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public License
+* along with this program.  If not, see <http://www.gnu.org/licenses/>
+*/
+#include <osgEarth/VideoLayer>
+#include <osg/ImageStream>
+#include <osgEarth/Registry>
+
+using namespace osgEarth;
+
+REGISTER_OSGEARTH_LAYER(video, VideoLayer);
+
+VideoLayerOptions::VideoLayerOptions() :
+ImageLayerOptions()
+{
+    setDefaults();
+    fromConfig(_conf);
+}
+
+VideoLayerOptions::VideoLayerOptions(const ConfigOptions& options) :
+ImageLayerOptions( options )
+{
+    setDefaults();
+    fromConfig( _conf );
+}
+
+VideoLayerOptions::VideoLayerOptions(const std::string& name) :
+ImageLayerOptions( name )
+{
+    setDefaults();
+    fromConfig( _conf );
+}
+
+void
+VideoLayerOptions::setDefaults()
+{
+}
+
+Config
+VideoLayerOptions::getConfig() const
+{
+    Config conf = ImageLayerOptions::getConfig();
+    conf.key() = "video";
+
+    conf.set("url", _url);
+
+    return conf;
+}
+
+void
+VideoLayerOptions::fromConfig( const Config& conf )
+{
+    conf.getIfSet("url", _url );
+}
+
+void
+VideoLayerOptions::mergeConfig( const Config& conf )
+{
+    ImageLayerOptions::mergeConfig( conf );
+    fromConfig( conf );
+}
+
+
+VideoLayer::VideoLayer()
+{
+    init();
+}
+
+VideoLayer::VideoLayer( const VideoLayerOptions& options ):
+ImageLayer(&_optionsConcrete),
+_options(&_optionsConcrete),
+_optionsConcrete(options)
+{    
+    init();    
+}
+
+const Status&
+VideoLayer::open()
+{
+    if ( !_openCalled )
+    {
+
+        if (!options().url().isSet())
+        {
+            return setStatus(Status::Error(Status::ConfigurationError, "Missing required url"));
+        }
+
+        osg::ref_ptr< osg::Image > image = options().url()->readImage().getImage();
+        if (image.valid())
+        {             
+            osg::ImageStream* is = dynamic_cast< osg::ImageStream*>( image.get() );
+            if (is)
+            {
+                is->setLoopingMode(osg::ImageStream::LOOPING);
+                is->play();                 
+            }
+
+            _texture = new osg::Texture2D( image );
+            _texture->setResizeNonPowerOfTwoHint( false );
+            _texture->setFilter(osg::Texture::MIN_FILTER, osg::Texture2D::LINEAR);
+            _texture->setFilter(osg::Texture::MAG_FILTER, osg::Texture2D::LINEAR);
+            _texture->setWrap( osg::Texture::WRAP_S, osg::Texture::REPEAT );
+            _texture->setWrap( osg::Texture::WRAP_T, osg::Texture::REPEAT );
+            _texture->setUnRefImageDataAfterApply(false);
+        }
+        else
+        {
+            std::stringstream buf;
+            buf << "Failed to load " << options().url()->full();
+            return setStatus(Status::Error(Status::ServiceUnavailable, buf.str()));
+        }
+
+        setProfile(osgEarth::Registry::instance()->getGlobalGeodeticProfile());
+
+        if (getStatus().isOK())
+        {
+            return ImageLayer::open();
+        }
+    }
+
+    return getStatus();
+}
+
+osg::Texture* VideoLayer::createTexture(const TileKey& key, ProgressCallback* progress, osg::Matrixf& textureMatrix)
+{    
+    if (key.getLOD() > 0) return 0;
+
+    bool flip = _texture->getImage()->getOrigin()==osg::Image::TOP_LEFT;
+    osg::Matrixf scale = osg::Matrixf::scale(0.5, flip? -1.0 : 1.0, 1.0);         
+
+    if (key.getTileX() == 0)
+    {
+        textureMatrix = scale;
+    }
+    else if (key.getTileX() == 1)
+    {
+        textureMatrix =  scale * osg::Matrixf::translate(0.5, 0.0, 0.0);
+    }
+
+    return _texture.get();
+}
\ No newline at end of file
diff --git a/src/osgEarth/VirtualProgram b/src/osgEarth/VirtualProgram
index 797face..03b84c8 100644
--- a/src/osgEarth/VirtualProgram
+++ b/src/osgEarth/VirtualProgram
@@ -23,10 +23,9 @@
 #define OSGEARTH_VIRTUAL_PROGRAM_H 1
 
 #include <osgEarth/Common>
-#include <osgEarth/Revisioning>
 #include <osgEarth/ThreadingUtils>
-#include <osgEarth/ColorFilter>
 #include <osgEarth/Containers>
+#include <osgEarth/optional>
 #include <osg/Shader>
 #include <osg/Program>
 #include <osg/StateAttribute>
@@ -38,6 +37,10 @@
 #    define GLSL_VERSION                 100
 #    define GLSL_VERSION_STR             "100"
 #    define GLSL_DEFAULT_PRECISION_FLOAT "precision highp float;"
+#elif defined(OSG_GLES3_AVAILABLE)
+#    define GLSL_VERSION                 300
+#    define GLSL_VERSION_STR             "300 es"
+#    define GLSL_DEFAULT_PRECISION_FLOAT "precision highp float;"
 #elif defined(OSG_GL3_AVAILABLE)
 #    define GLSL_VERSION                 330
 #    define GLSL_VERSION_STR             "330"
@@ -297,6 +300,12 @@ namespace osgEarth
         void setInheritShaders( bool value );
         bool getInheritShaders() const { return _inherit; }
 
+        /**
+        * Ability to add an extension to the VP
+        */
+        bool addGLSLExtension(const std::string& extension);
+        bool hasGLSLExtension(const std::string& extension) const;
+        bool removeGLSLExtension(const std::string& extension);
     public: 
         /**
          * Constructs a new VP
@@ -378,6 +387,10 @@ namespace osgEarth
         void setMask(unsigned mask) { _mask = mask; }
         unsigned getMask() const { return _mask; }
 
+        /** Sets whether this VP is "abstract", i.e. pure virtual, and cannot be compiled on its own */
+        void setIsAbstract(bool value) { _isAbstract = value; }
+        bool getIsAbstract() const { return _isAbstract; }
+
     public: // StateAttribute
         virtual void compileGLObjects(osg::State& state) const;
         virtual void resizeGLObjectBuffers(unsigned maxSize);
@@ -409,6 +422,7 @@ namespace osgEarth
 
         typedef unsigned                              ShaderID;
         typedef vector_map<ShaderID, ShaderEntry>     ShaderMap;
+        typedef std::set<std::string>                 ExtensionsSet;
         typedef std::map< std::string, std::string >  AttribAliasMap;
         typedef std::pair< std::string, std::string > AttribAlias;
         typedef std::vector< AttribAlias >            AttribAliasVector;
@@ -469,6 +483,8 @@ namespace osgEarth
         // _dataModelMutex protects access to this member.
         ShaderMap _shaderMap;
 
+        ExtensionsSet _globalExtensions;
+
         // per-context cached shader map for thread-safe reuse without constant reallocation.
         struct ApplyVars
         {
@@ -494,6 +510,8 @@ namespace osgEarth
         bool _logShaders;
         std::string _logPath;
 
+        bool _isAbstract;
+
         AttribAliasMap _attribAliases;
 
         bool _acceptCallbacksVaryPerFrame;
diff --git a/src/osgEarth/VirtualProgram.cpp b/src/osgEarth/VirtualProgram.cpp
index d137f4a..8cdc0fa 100644
--- a/src/osgEarth/VirtualProgram.cpp
+++ b/src/osgEarth/VirtualProgram.cpp
@@ -50,6 +50,10 @@ using namespace osgEarth::ShaderComp;
 
 #define MAX_PROGRAM_CACHE_SIZE 128
 
+#define PREALLOCATE_APPLY_VARS 1
+
+#define MAX_CONTEXTS 16
+
 #define MAKE_SHADER_ID(X) osgEarth::hashString( X )
 
 //------------------------------------------------------------------------
@@ -85,7 +89,7 @@ namespace
 
 namespace
 {
-#ifdef OSG_GLES2_AVAILABLE
+#if defined(OSG_GLES2_AVAILABLE) || defined(OSG_GLES3_AVAILABLE)
     // GLES requires all shader code be merged into a since source
     bool s_mergeShaders = true;
 #else
@@ -119,7 +123,7 @@ namespace
         std::stringstream buf;
         for( unsigned i=0; i<in.length(); ++i )
         {
-            char c = in.at(i);
+            char c = in[i];
             if ( ::isspace(c) )
             {
                 if ( !inwhite )
@@ -141,12 +145,20 @@ namespace
     }
 
 
-    void parseShaderForMerging( const std::string& source, unsigned& version, HeaderMap& headers, std::stringstream& body )
+    void parseShaderForMerging( const std::string& source, unsigned& version, std::string& subversion, HeaderMap& precisions, HeaderMap& headers, std::stringstream& body )
     {
+    
+        // types we consider declarations
+        std::string dectypesarray[] = {"void", "uniform", "in", "out", "varying", "bool", "int", "float", "vec2", "vec3", "vec4", "bvec2", "bvec3", "bvec4", "ivec2", "ivec3", "ivec4", "mat2", "mat3", "mat4", "sampler2D", "samplerCube", "lowp", "mediump", "highp", "struct", "attribute", "#extension", "#define"};
+        std::vector<std::string> dectypes (dectypesarray, dectypesarray + sizeof(dectypesarray) / sizeof(dectypesarray[0]) );
+        
         // break into lines:
         StringVector lines;
         StringTokenizer( source, lines, "\n", "", true, false );
 
+        // keep track of brackets and if we're in global scope indent==0
+        int indent = 0;
+
         for( StringVector::const_iterator line_iter = lines.begin(); line_iter != lines.end(); ++line_iter )
         {
             std::string line = trimAndCompress(*line_iter);
@@ -155,6 +167,16 @@ namespace
             {
                 StringVector tokens;
                 StringTokenizer( line, tokens, " \t", "", false, true );
+                
+                indent += std::count(line.begin(), line.end(), '{');
+                indent -= std::count(line.begin(), line.end(), '}');
+                
+                // we say it's a declaration if it starts with one of the dectyps, has a ; at the end and is in the global scope
+                bool isdec = (std::find(dectypes.begin(), dectypes.end(), tokens[0]) != dectypes.end() && line[line.size()-1] == ';' && indent == 0) || (tokens[0] == "#extension" || tokens[0] == "#define");
+                
+                // discard forward declarations of functions, we know it's a declaration so just see if it has brackets (should be safe)
+                bool isfunc = isdec && (line.find("(") != std::string::npos && line.find(")") != std::string::npos);
+                if(isfunc) continue;
 
                 if (tokens[0] == "#version")
                 {
@@ -165,18 +187,33 @@ namespace
                         if ( newVersion > version )
                         {
                             version = newVersion;
+                            if(tokens.size() > 2)
+                            {
+                                subversion = tokens[2];
+                            }
                         }
                     }
                 }
+                
+                else if(tokens[0] == "precision") {
+                    // precision stored in map of value type to current highest value precision
+                    // dec should be keyword valueprecision type, precision highp float
+                    if(tokens.size() < 3) continue;
+                    std::string& currentlevel = precisions[tokens[2]];
+                    
+                    // see if it's higher
+                    if(currentlevel.empty() || (currentlevel == "lowp" && (tokens[1] == "mediump" || tokens[1] == "highp"))) currentlevel = tokens[1];
+                    if(currentlevel == "mediump" && tokens[1] == "highp") currentlevel = tokens[1];
+                }
 
-                else if (
-                    tokens[0] == "#extension"   ||
+                else if (isdec
+                    /*tokens[0] == "#extension"   ||
                     tokens[0] == "#define"      ||
                     tokens[0] == "precision"    ||
                     tokens[0] == "struct"       ||
                     tokens[0] == "varying"      ||
                     tokens[0] == "uniform"      ||
-                    tokens[0] == "attribute")
+                    tokens[0] == "attribute"*/)
                 {
                     std::string& header = headers[line];
                     header = line;
@@ -188,6 +225,12 @@ namespace
                 }
             }
         }
+        
+#if defined(OSG_GLES3_AVAILABLE)
+        // just force gles 3 to use correct version number as shaders in earth files might include a version
+        version = 300;
+        subversion = "es";
+#endif
     }
 
 
@@ -266,7 +309,7 @@ namespace
     }
 
     struct SortByType {
-        bool operator()(osg::Shader* lhs, osg::Shader* rhs) {
+        bool operator()(const osg::ref_ptr<osg::Shader>& lhs, const osg::ref_ptr<osg::Shader>& rhs) {
             return (int)lhs->getType() < (int)rhs->getType();
         }
     };
@@ -310,10 +353,14 @@ namespace
         if ( s_mergeShaders )
         {
             unsigned          vertVersion = 0;
+            std::string       vertSubversion = "";
+            HeaderMap         vertPrecisions;
             HeaderMap         vertHeaders;
             std::stringstream vertBody;
 
             unsigned          fragVersion = 0;
+            std::string       fragSubversion = "";
+            HeaderMap         fragPrecisions;
             HeaderMap         fragHeaders;
             std::stringstream fragBody;
 
@@ -325,11 +372,11 @@ namespace
                 {
                     if ( s->getType() == osg::Shader::VERTEX )
                     {
-                        parseShaderForMerging( s->getShaderSource(), vertVersion, vertHeaders, vertBody );
+                        parseShaderForMerging( s->getShaderSource(), vertVersion, vertSubversion, vertPrecisions, vertHeaders, vertBody );
                     }
                     else if ( s->getType() == osg::Shader::FRAGMENT )
                     {
-                        parseShaderForMerging( s->getShaderSource(), fragVersion, fragHeaders, fragBody );
+                        parseShaderForMerging( s->getShaderSource(), fragVersion, fragSubversion, fragPrecisions, fragHeaders, fragBody );
                     }
                 }
             }
@@ -338,30 +385,60 @@ namespace
             std::string vertBodyText;
             vertBodyText = vertBody.str();
             std::stringstream vertShaderBuf;
-            if ( vertVersion > 0 )
-                vertShaderBuf << "#version " << vertVersion << "\n";
+            if ( vertVersion > 0 ) {
+                vertShaderBuf << "#version " << vertVersion;
+                if(vertSubversion.size() > 0)
+                   vertShaderBuf << " " << vertSubversion;
+                vertShaderBuf << "\n";
+            }
+
+            if(vertPrecisions.size() > 0) {
+                for(HeaderMap::iterator pitr = vertPrecisions.begin(); pitr != vertPrecisions.end(); ++pitr) {
+                    vertShaderBuf << "precision " << pitr->second << " " << pitr->first << "\n";
+                }
+            }
+
             for( HeaderMap::const_iterator h = vertHeaders.begin(); h != vertHeaders.end(); ++h )
                 vertShaderBuf << h->second << "\n";
             vertShaderBuf << vertBodyText << "\n";
             vertBodyText = vertShaderBuf.str();
 
+
+
             std::string fragBodyText;
             fragBodyText = fragBody.str();
             std::stringstream fragShaderBuf;
-            if ( fragVersion > 0 )
-                fragShaderBuf << "#version " << fragVersion << "\n";
+            if ( fragVersion > 0 ) {
+                fragShaderBuf << "#version " << fragVersion;
+                if(fragSubversion.size() > 0)
+                    fragShaderBuf << " " << fragSubversion;
+                fragShaderBuf << "\n";
+            }
+
+#if defined(OSG_GLES3_AVAILABLE)
+            // ensure there's a default for floats in the frag shader
+            std::string& defaultFragFloat = fragPrecisions["float;"];
+            if(defaultFragFloat.size() == 0) defaultFragFloat = "highp";
+#endif
+            if(fragPrecisions.size() > 0) {
+                for(HeaderMap::iterator pitr = fragPrecisions.begin(); pitr != fragPrecisions.end(); ++pitr) {
+                    fragShaderBuf << "precision " << pitr->second << " " << pitr->first << "\n";
+                }
+            }
+
             for( HeaderMap::const_iterator h = fragHeaders.begin(); h != fragHeaders.end(); ++h )
                 fragShaderBuf << h->second << "\n";
             fragShaderBuf << fragBodyText << "\n";
             fragBodyText = fragShaderBuf.str();
 
-            // add them to the program.            
+
+            // add them to the program.
             program->addShader( new osg::Shader(osg::Shader::VERTEX, vertBodyText) );
             program->addShader( new osg::Shader(osg::Shader::FRAGMENT, fragBodyText) );
 
             if ( s_dumpShaders )
             {
-                OE_NOTICE << LC 
+                OE_NOTICE << LC
                     << "\nMERGED VERTEX SHADER: \n\n" << vertBodyText << "\n\n"
                     << "MERGED FRAGMENT SHADER: \n\n" << fragBodyText << "\n" << std::endl;
             }
@@ -416,6 +493,7 @@ namespace
                                osg::State&                         state,
                                ShaderComp::FunctionLocationMap&    accumFunctions,
                                VirtualProgram::ShaderMap&          accumShaderMap,
+                               const VirtualProgram::ExtensionsSet& extensionsSet,
                                VirtualProgram::AttribBindingList&  accumAttribBindings,
                                VirtualProgram::AttribAliasMap&     accumAttribAliases,
                                osg::Program*                       templateProgram,
@@ -459,7 +537,7 @@ namespace
 
         // create new MAINs for this function stack.
         VirtualProgram::ShaderVector mains;
-        ShaderComp::StageMask stages = Registry::shaderFactory()->createMains( accumFunctions, accumShaderMap, mains );
+        ShaderComp::StageMask stages = Registry::shaderFactory()->createMains(accumFunctions, accumShaderMap, extensionsSet, mains);
 
         // build a new "key vector" now that we've changed the shader map.
         // we call is a key vector because it uniquely identifies this shader program
@@ -671,7 +749,8 @@ _inherit           ( true ),
 _inheritSet        ( false ),
 _logShaders        ( false ),
 _logPath           ( "" ),
-_acceptCallbacksVaryPerFrame( false )
+_acceptCallbacksVaryPerFrame( false ),
+_isAbstract        ( false )
 {
     // Note: we cannot set _active here. Wait until apply().
     // It will cause a conflict in the Registry.
@@ -691,6 +770,14 @@ _acceptCallbacksVaryPerFrame( false )
     // a template object to hold program data (so we don't have to dupliate all the 
     // osg::Program methods..)
     _template = new osg::Program();
+
+#ifdef PREALLOCATE_APPLY_VARS
+    _apply.resize(MAX_CONTEXTS);
+#endif
+
+#ifdef USE_STACK_MEMORY
+    _vpStackMemory._item.resize(MAX_CONTEXTS);
+#endif
 }
 
 
@@ -704,7 +791,8 @@ _inheritSet        ( rhs._inheritSet ),
 _logShaders        ( rhs._logShaders ),
 _logPath           ( rhs._logPath ),
 _template          ( osg::clone(rhs._template.get()) ),
-_acceptCallbacksVaryPerFrame( rhs._acceptCallbacksVaryPerFrame )
+_acceptCallbacksVaryPerFrame( rhs._acceptCallbacksVaryPerFrame ),
+_isAbstract        ( rhs._isAbstract )
 {    
     // Attribute bindings.
     const osg::Program::AttribBindingList &abl = rhs.getAttribBindingList();
@@ -712,6 +800,14 @@ _acceptCallbacksVaryPerFrame( rhs._acceptCallbacksVaryPerFrame )
     {
         addBindAttribLocation( attribute->first, attribute->second );
     }
+    
+#ifdef PREALLOCATE_APPLY_VARS
+    _apply.resize(MAX_CONTEXTS);
+#endif
+
+#ifdef USE_STACK_MEMORY
+    _vpStackMemory._item.resize(MAX_CONTEXTS);
+#endif
 }
 
 VirtualProgram::~VirtualProgram()
@@ -729,6 +825,7 @@ VirtualProgram::compare(const osg::StateAttribute& sa) const
     // compare each parameter in turn against the rhs.
     COMPARE_StateAttribute_Parameter(_mask);
     COMPARE_StateAttribute_Parameter(_inherit);
+    COMPARE_StateAttribute_Parameter(_isAbstract);
 
     // compare the shader maps. Need to lock them while comparing.
     {
@@ -830,9 +927,9 @@ VirtualProgram::resizeGLObjectBuffers(unsigned maxSize)
     }
 
     // Resize the buffered_object
-    _apply.resize(maxSize);
+    //_apply.resize(maxSize);
 
-    _vpStackMemory._item.resize(maxSize);
+    //_vpStackMemory._item.resize(maxSize);
 
     _programCacheMutex.unlock();
 }
@@ -1048,6 +1145,27 @@ VirtualProgram::setFunctionMaxRange(const std::string& name, float maxRange)
     _dataModelMutex.unlock();
 }
 
+bool VirtualProgram::addGLSLExtension(const std::string& extension)
+{
+   _dataModelMutex.lock();
+   std::pair<std::set<std::string>::const_iterator, bool> insertPair = _globalExtensions.insert(extension);
+   _dataModelMutex.unlock();
+   return insertPair.second;
+}
+bool VirtualProgram::hasGLSLExtension(const std::string& extension) const
+{
+   _dataModelMutex.lock();
+   bool doesHave = _globalExtensions.find(extension)!=_globalExtensions.end();
+   _dataModelMutex.unlock();
+   return doesHave;
+}
+bool VirtualProgram::removeGLSLExtension(const std::string& extension)
+{
+   _dataModelMutex.lock();
+   int erased = _globalExtensions.erase(extension);
+   _dataModelMutex.unlock();
+   return erased > 0;
+}
 void
 VirtualProgram::removeShader( const std::string& shaderID )
 {
@@ -1109,6 +1227,12 @@ VirtualProgram::apply( osg::State& state ) const
         // cannot use capabilities here; it breaks serialization.
         _active = true; //Registry::capabilities().supportsGLSL();
     }
+
+    // An abstract (pure virtual) program cannot be applied.
+    if (_isAbstract)
+    {
+        return;
+    }
     
     const unsigned contextID = state.getContextID();
 
@@ -1160,6 +1284,7 @@ VirtualProgram::apply( osg::State& state ) const
 
     if ( !program.valid() )
     {
+#ifdef PREALLOCATE_APPLY_VARS
         // Access the resuable shader map for this context. Bypasses reallocation overhead.
         ApplyVars& local = _apply[contextID];
 
@@ -1167,6 +1292,9 @@ VirtualProgram::apply( osg::State& state ) const
         local.accumAttribBindings.clear();
         local.accumAttribAliases.clear();
         local.programKey.clear();
+#else
+        ApplyVars local;
+#endif
     
         // If we are inheriting, build the active shader map up to this point
         // (but not including this VP).
@@ -1251,6 +1379,7 @@ VirtualProgram::apply( osg::State& state ) const
                         state,
                         accumFunctions,
                         local.accumShaderMap, 
+                        _globalExtensions,
                         local.accumAttribBindings, 
                         local.accumAttribAliases, 
                         _template.get(),
@@ -1978,7 +2107,8 @@ namespace
         ADD_UINT_SERIALIZER( Mask, ~0 );
 
         ADD_USER_SERIALIZER( AttribBinding );
-        //ADD_USER_SERIALIZER( FragDataBinding );
         ADD_USER_SERIALIZER( Functions );
+
+        ADD_BOOL_SERIALIZER( IsAbstract, false );
     }
 }
diff --git a/src/osgEarth/VisibleLayer b/src/osgEarth/VisibleLayer
new file mode 100644
index 0000000..7b6e4e2
--- /dev/null
+++ b/src/osgEarth/VisibleLayer
@@ -0,0 +1,108 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+ * Copyright 2016 Pelican Mapping
+ * http://osgearth.org
+ *
+ * osgEarth is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+
+#ifndef OSGEARTH_VISIBLE_LAYER_H
+#define OSGEARTH_VISIBLE_LAYER_H 1
+
+#include <osgEarth/Layer>
+
+namespace osgEarth
+{
+    /**
+     * Serializable configuration options for a VisibleLayer.
+     */
+    class OSGEARTH_EXPORT VisibleLayerOptions : public LayerOptions
+    {
+    public:
+        VisibleLayerOptions();
+        VisibleLayerOptions(const ConfigOptions& options);
+
+        /** dtor */
+        virtual ~VisibleLayerOptions() { }
+
+        /** Whether the layer is initially visible. */
+        optional<bool>& visible() { return _visible; }
+        const optional<bool>& visible() const { return _visible; }
+
+        /** Opacity of the visible layer [0..1] */
+        optional<float>& opacity() { return _opacity; }
+        const optional<float>& opacity() const { return _opacity; }
+
+    public:
+        virtual Config getConfig() const;
+        virtual void mergeConfig( const Config& conf );
+        void fromConfig(const Config& conf);
+
+    private:
+        void setDefaults();       
+        optional<bool> _visible;
+        optional<float> _opacity;
+    };
+
+
+    struct VisibleLayerCallback : public LayerCallback
+    {
+        virtual void onVisibleChanged(class VisibleLayer* layer) { }
+        virtual void onOpacityChanged(class VisibleLayer* layer) { }
+        typedef void (VisibleLayerCallback::*MethodPtr)(class VisibleLayer* layer);
+    };
+
+
+    /**
+     * Base class for a layer supporting visibility control.
+     */
+    class OSGEARTH_EXPORT VisibleLayer : public Layer
+    {
+    public:
+        META_Layer(osgEarth, VisibleLayer, VisibleLayerOptions);
+
+    protected:
+        // Construcable from a base class only.
+        VisibleLayer(VisibleLayerOptions* ptr =0L);
+
+        virtual ~VisibleLayer();
+
+    public:
+        /** Whether to draw this layer. */
+        virtual void setVisible(bool value);
+        bool getVisible() const;
+
+        /** Opacity with which to draw this layer. */
+        virtual void setOpacity(float value);
+        float getOpacity() const;
+
+    public: // Layer
+
+        virtual const Status& open();
+
+    protected: // Layer
+
+        virtual void init();
+
+        void fireCallback(VisibleLayerCallback::MethodPtr);
+
+    private: 
+        osg::ref_ptr<osg::Uniform> _opacityU;
+    };
+
+    typedef std::vector< osg::ref_ptr<VisibleLayer> > VisibleLayerVector;
+
+} // namespace TerrainLayer
+
+#endif // OSGEARTH_RENDER_LAYER_H
diff --git a/src/osgEarth/VisibleLayer.cpp b/src/osgEarth/VisibleLayer.cpp
new file mode 100644
index 0000000..cce6531
--- /dev/null
+++ b/src/osgEarth/VisibleLayer.cpp
@@ -0,0 +1,177 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+ * Copyright 2016 Pelican Mapping
+ * http://osgearth.org
+ *
+ * osgEarth is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+#include <osgEarth/VisibleLayer>
+#include <osgEarth/VirtualProgram>
+
+using namespace osgEarth;
+
+#define LC "[VisibleLayer] Layer \"" << getName() << "\" "
+
+//------------------------------------------------------------------------
+
+VisibleLayerOptions::VisibleLayerOptions() :
+LayerOptions()
+{
+    setDefaults();
+    fromConfig( _conf ); 
+}
+
+VisibleLayerOptions::VisibleLayerOptions(const ConfigOptions& co) :
+LayerOptions(co)
+{
+    setDefaults();
+    fromConfig(_conf);
+}
+
+void
+VisibleLayerOptions::setDefaults()
+{
+    _visible.init( true );
+    _opacity.init( 1.0f );
+}
+
+Config
+VisibleLayerOptions::getConfig() const
+{
+    Config conf = LayerOptions::getConfig();
+    conf.set("visible", _visible);
+    conf.set("opacity", _opacity);
+    return conf;
+}
+
+void
+VisibleLayerOptions::fromConfig(const Config& conf)
+{
+    conf.getIfSet( "visible", _visible );
+    conf.getIfSet("opacity", _opacity);
+}
+
+void
+VisibleLayerOptions::mergeConfig(const Config& conf)
+{
+    LayerOptions::mergeConfig(conf);
+    fromConfig(conf);
+}
+
+//........................................................................
+
+VisibleLayer::VisibleLayer(VisibleLayerOptions* optionsPtr) :
+Layer(optionsPtr ? optionsPtr : &_optionsConcrete),
+_options(optionsPtr ? optionsPtr : &_optionsConcrete)
+{
+    //nop
+}
+
+VisibleLayer::~VisibleLayer()
+{
+    //nop
+}
+
+void
+VisibleLayer::init()
+{
+    Layer::init();
+}
+
+const Status&
+VisibleLayer::open()
+{
+    if (options().visible().isSet())
+    {
+        setVisible(options().visible().get());
+    }
+    if (options().opacity().isSet())
+    {
+        setOpacity(options().opacity().get());
+    }
+    return Layer::open();
+}
+
+void
+VisibleLayer::setVisible(bool value)
+{
+    options().visible() = value;
+
+    // if this layer has a scene graph node, toggle its node mask
+    osg::Node* node = getOrCreateNode();
+    if (node)
+        node->setNodeMask(value? ~0 : 0);
+
+    fireCallback(&VisibleLayerCallback::onVisibleChanged);
+}
+
+bool
+VisibleLayer::getVisible() const
+{
+    return options().visible().get();
+}
+
+namespace
+{
+    const char* opacityFS =
+        "#version " GLSL_VERSION_STR "\n"
+        "uniform float oe_visibleLayer_opacity; \n"
+        "void oe_visibleLayer_setOpacity(inout vec4 color) { \n"
+        "    color.a *= oe_visibleLayer_opacity; \n"
+        "} \n";
+}
+
+void
+VisibleLayer::setOpacity(float value)
+{
+    options().opacity() = value;
+
+    // On-demand installation of the opacity shader and rendering hint,
+    // since they do incur a small performance penalty.
+    if (value < 1.0f)
+    {
+        if (!_opacityU.valid())
+        {
+            osg::StateSet* stateSet = getOrCreateStateSet();
+            _opacityU = new osg::Uniform("oe_visibleLayer_opacity", value);
+            stateSet->addUniform(_opacityU.get());
+            VirtualProgram* vp = VirtualProgram::getOrCreate(stateSet);
+            vp->setFunction("oe_visibleLayer_setOpacity", opacityFS, ShaderComp::LOCATION_FRAGMENT_COLORING, 1.1f);
+            // NOTE: do not alter the render bin here - it will screw up terrain rendering order!
+        }
+    }
+    
+    if (_opacityU.valid())
+    {
+        _opacityU->set(value);
+    }
+
+    fireCallback(&VisibleLayerCallback::onOpacityChanged);
+}
+
+float
+VisibleLayer::getOpacity() const
+{
+    return options().opacity().get();
+}
+
+void
+VisibleLayer::fireCallback(VisibleLayerCallback::MethodPtr method)
+{
+    for (CallbackVector::iterator i = _callbacks.begin(); i != _callbacks.end(); ++i)
+    {
+        VisibleLayerCallback* cb = dynamic_cast<VisibleLayerCallback*>(i->get());
+        if (cb) (cb->*method)(this);
+    }
+}
diff --git a/src/osgEarth/WrapperLayer b/src/osgEarth/WrapperLayer
new file mode 100644
index 0000000..14f6b84
--- /dev/null
+++ b/src/osgEarth/WrapperLayer
@@ -0,0 +1,74 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+ * Copyright 2016 Pelican Mapping
+ * http://osgearth.org
+ *
+ * osgEarth is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+#ifndef OSGEARTH_WRAPPER_LAYER
+#define OSGEARTH_WRAPPER_LAYER 1
+
+#include <osgEarth/Layer>
+
+namespace osgEarth
+{
+    /** Template to support the Wrapper Layer template
+      * @deprecated
+      */
+    template<typename OPTIONS>
+    class WrapperLayerOptions : public LayerOptions
+    {
+    public:
+        WrapperLayerOptions(const ConfigOptions& co = ConfigOptions()) : LayerOptions(co) {
+            _data = co;
+        }
+
+        Config getConfig() const {
+            return _data->getConfig();
+        }
+
+        optional<OPTIONS>& data() { return _data; }
+        const optional<OPTIONS>& data() const { return _data; }
+
+        optional<OPTIONS> _data;
+    };
+
+    /** Template for creating a Map Layer that holds a single serializable resource object. */
+    /** @deprecated */
+    template<typename OPTIONS>
+    class WrapperLayer : public Layer
+    {
+    public:
+        WrapperLayer(const OPTIONS& options) :
+            Layer(&_optionsConcrete),
+            _options(&_optionsConcrete),
+            _optionsConcrete(options)
+        {
+            setRenderType(RENDERTYPE_NONE);
+            Layer::init();
+        }
+
+        virtual Config getConfig() const { return options().getConfig(); }
+
+    private:
+        OPTIONS* _options;
+        OPTIONS  _optionsConcrete;
+
+    protected:
+        const OPTIONS& options() const { return *_options; }
+        OPTIONS& mutableOptions() { return *_options; }
+    };
+}
+
+#endif // OSGEARTH_WRAPPER_LAYER
diff --git a/src/osgEarth/catch.hpp b/src/osgEarth/catch.hpp
new file mode 100644
index 0000000..6bcdd04
--- /dev/null
+++ b/src/osgEarth/catch.hpp
@@ -0,0 +1,10663 @@
+/*
+ *  Catch v1.6.1
+ *  Generated: 2017-01-20 12:33:53.497767
+ *  ----------------------------------------------------------
+ *  This file has been merged from multiple headers. Please don't edit it directly
+ *  Copyright (c) 2012 Two Blue Cubes Ltd. All rights reserved.
+ *
+ *  Distributed under the Boost Software License, Version 1.0. (See accompanying
+ *  file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+ */
+#ifndef TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED
+#define TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED
+
+#define TWOBLUECUBES_CATCH_HPP_INCLUDED
+
+#ifdef __clang__
+#    pragma clang system_header
+#elif defined __GNUC__
+#    pragma GCC system_header
+#endif
+
+// #included from: internal/catch_suppress_warnings.h
+
+#ifdef __clang__
+#   ifdef __ICC // icpc defines the __clang__ macro
+#       pragma warning(push)
+#       pragma warning(disable: 161 1682)
+#   else // __ICC
+#       pragma clang diagnostic ignored "-Wglobal-constructors"
+#       pragma clang diagnostic ignored "-Wvariadic-macros"
+#       pragma clang diagnostic ignored "-Wc99-extensions"
+#       pragma clang diagnostic ignored "-Wunused-variable"
+#       pragma clang diagnostic push
+#       pragma clang diagnostic ignored "-Wpadded"
+#       pragma clang diagnostic ignored "-Wc++98-compat"
+#       pragma clang diagnostic ignored "-Wc++98-compat-pedantic"
+#       pragma clang diagnostic ignored "-Wswitch-enum"
+#       pragma clang diagnostic ignored "-Wcovered-switch-default"
+#    endif
+#elif defined __GNUC__
+#    pragma GCC diagnostic ignored "-Wvariadic-macros"
+#    pragma GCC diagnostic ignored "-Wunused-variable"
+#    pragma GCC diagnostic push
+#    pragma GCC diagnostic ignored "-Wpadded"
+#endif
+#if defined(CATCH_CONFIG_MAIN) || defined(CATCH_CONFIG_RUNNER)
+#  define CATCH_IMPL
+#endif
+
+#ifdef CATCH_IMPL
+#  ifndef CLARA_CONFIG_MAIN
+#    define CLARA_CONFIG_MAIN_NOT_DEFINED
+#    define CLARA_CONFIG_MAIN
+#  endif
+#endif
+
+// #included from: internal/catch_notimplemented_exception.h
+#define TWOBLUECUBES_CATCH_NOTIMPLEMENTED_EXCEPTION_H_INCLUDED
+
+// #included from: catch_common.h
+#define TWOBLUECUBES_CATCH_COMMON_H_INCLUDED
+
+// #included from: catch_compiler_capabilities.h
+#define TWOBLUECUBES_CATCH_COMPILER_CAPABILITIES_HPP_INCLUDED
+
+// Detect a number of compiler features - mostly C++11/14 conformance - by compiler
+// The following features are defined:
+//
+// CATCH_CONFIG_CPP11_NULLPTR : is nullptr supported?
+// CATCH_CONFIG_CPP11_NOEXCEPT : is noexcept supported?
+// CATCH_CONFIG_CPP11_GENERATED_METHODS : The delete and default keywords for compiler generated methods
+// CATCH_CONFIG_CPP11_IS_ENUM : std::is_enum is supported?
+// CATCH_CONFIG_CPP11_TUPLE : std::tuple is supported
+// CATCH_CONFIG_CPP11_LONG_LONG : is long long supported?
+// CATCH_CONFIG_CPP11_OVERRIDE : is override supported?
+// CATCH_CONFIG_CPP11_UNIQUE_PTR : is unique_ptr supported (otherwise use auto_ptr)
+
+// CATCH_CONFIG_CPP11_OR_GREATER : Is C++11 supported?
+
+// CATCH_CONFIG_VARIADIC_MACROS : are variadic macros supported?
+// CATCH_CONFIG_COUNTER : is the __COUNTER__ macro supported?
+// ****************
+// Note to maintainers: if new toggles are added please document them
+// in configuration.md, too
+// ****************
+
+// In general each macro has a _NO_<feature name> form
+// (e.g. CATCH_CONFIG_CPP11_NO_NULLPTR) which disables the feature.
+// Many features, at point of detection, define an _INTERNAL_ macro, so they
+// can be combined, en-mass, with the _NO_ forms later.
+
+// All the C++11 features can be disabled with CATCH_CONFIG_NO_CPP11
+
+#ifdef __cplusplus
+
+#  if __cplusplus >= 201103L
+#    define CATCH_CPP11_OR_GREATER
+#  endif
+
+#  if __cplusplus >= 201402L
+#    define CATCH_CPP14_OR_GREATER
+#  endif
+
+#endif
+
+#ifdef __clang__
+
+#  if __has_feature(cxx_nullptr)
+#    define CATCH_INTERNAL_CONFIG_CPP11_NULLPTR
+#  endif
+
+#  if __has_feature(cxx_noexcept)
+#    define CATCH_INTERNAL_CONFIG_CPP11_NOEXCEPT
+#  endif
+
+#   if defined(CATCH_CPP11_OR_GREATER)
+#       define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS _Pragma( "clang diagnostic ignored \"-Wparentheses\"" )
+#   endif
+
+#endif // __clang__
+
+////////////////////////////////////////////////////////////////////////////////
+// Borland
+#ifdef __BORLANDC__
+
+#endif // __BORLANDC__
+
+////////////////////////////////////////////////////////////////////////////////
+// EDG
+#ifdef __EDG_VERSION__
+
+#endif // __EDG_VERSION__
+
+////////////////////////////////////////////////////////////////////////////////
+// Digital Mars
+#ifdef __DMC__
+
+#endif // __DMC__
+
+////////////////////////////////////////////////////////////////////////////////
+// GCC
+#ifdef __GNUC__
+
+#   if __GNUC__ == 4 && __GNUC_MINOR__ >= 6 && defined(__GXX_EXPERIMENTAL_CXX0X__)
+#       define CATCH_INTERNAL_CONFIG_CPP11_NULLPTR
+#   endif
+
+#   if !defined(CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS) && defined(CATCH_CPP11_OR_GREATER)
+#       define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS _Pragma( "GCC diagnostic ignored \"-Wparentheses\"" )
+#   endif
+
+// - otherwise more recent versions define __cplusplus >= 201103L
+// and will get picked up below
+
+#endif // __GNUC__
+
+////////////////////////////////////////////////////////////////////////////////
+// Visual C++
+#ifdef _MSC_VER
+
+#if (_MSC_VER >= 1600)
+#   define CATCH_INTERNAL_CONFIG_CPP11_NULLPTR
+#   define CATCH_INTERNAL_CONFIG_CPP11_UNIQUE_PTR
+#endif
+
+#if (_MSC_VER >= 1900 ) // (VC++ 13 (VS2015))
+#define CATCH_INTERNAL_CONFIG_CPP11_NOEXCEPT
+#define CATCH_INTERNAL_CONFIG_CPP11_GENERATED_METHODS
+#define CATCH_INTERNAL_CONFIG_CPP11_SHUFFLE
+#endif
+
+#endif // _MSC_VER
+
+////////////////////////////////////////////////////////////////////////////////
+
+// Use variadic macros if the compiler supports them
+#if ( defined _MSC_VER && _MSC_VER > 1400 && !defined __EDGE__) || \
+    ( defined __WAVE__ && __WAVE_HAS_VARIADICS ) || \
+    ( defined __GNUC__ && __GNUC__ >= 3 ) || \
+    ( !defined __cplusplus && __STDC_VERSION__ >= 199901L || __cplusplus >= 201103L )
+
+#define CATCH_INTERNAL_CONFIG_VARIADIC_MACROS
+
+#endif
+
+// Use __COUNTER__ if the compiler supports it
+#if ( defined _MSC_VER && _MSC_VER >= 1300 ) || \
+    ( defined __GNUC__  && __GNUC__ >= 4 && __GNUC_MINOR__ >= 3 ) || \
+    ( defined __clang__ && __clang_major__ >= 3 )
+
+#define CATCH_INTERNAL_CONFIG_COUNTER
+
+#endif
+
+////////////////////////////////////////////////////////////////////////////////
+// C++ language feature support
+
+// catch all support for C++11
+#if defined(CATCH_CPP11_OR_GREATER)
+
+#  if !defined(CATCH_INTERNAL_CONFIG_CPP11_NULLPTR)
+#    define CATCH_INTERNAL_CONFIG_CPP11_NULLPTR
+#  endif
+
+#  ifndef CATCH_INTERNAL_CONFIG_CPP11_NOEXCEPT
+#    define CATCH_INTERNAL_CONFIG_CPP11_NOEXCEPT
+#  endif
+
+#  ifndef CATCH_INTERNAL_CONFIG_CPP11_GENERATED_METHODS
+#    define CATCH_INTERNAL_CONFIG_CPP11_GENERATED_METHODS
+#  endif
+
+#  ifndef CATCH_INTERNAL_CONFIG_CPP11_IS_ENUM
+#    define CATCH_INTERNAL_CONFIG_CPP11_IS_ENUM
+#  endif
+
+#  ifndef CATCH_INTERNAL_CONFIG_CPP11_TUPLE
+#    define CATCH_INTERNAL_CONFIG_CPP11_TUPLE
+#  endif
+
+#  ifndef CATCH_INTERNAL_CONFIG_VARIADIC_MACROS
+#    define CATCH_INTERNAL_CONFIG_VARIADIC_MACROS
+#  endif
+
+#  if !defined(CATCH_INTERNAL_CONFIG_CPP11_LONG_LONG)
+#    define CATCH_INTERNAL_CONFIG_CPP11_LONG_LONG
+#  endif
+
+#  if !defined(CATCH_INTERNAL_CONFIG_CPP11_OVERRIDE)
+#    define CATCH_INTERNAL_CONFIG_CPP11_OVERRIDE
+#  endif
+#  if !defined(CATCH_INTERNAL_CONFIG_CPP11_UNIQUE_PTR)
+#    define CATCH_INTERNAL_CONFIG_CPP11_UNIQUE_PTR
+#  endif
+# if !defined(CATCH_INTERNAL_CONFIG_CPP11_SHUFFLE)
+#   define CATCH_INTERNAL_CONFIG_CPP11_SHUFFLE
+#  endif
+
+#endif // __cplusplus >= 201103L
+
+// Now set the actual defines based on the above + anything the user has configured
+#if defined(CATCH_INTERNAL_CONFIG_CPP11_NULLPTR) && !defined(CATCH_CONFIG_CPP11_NO_NULLPTR) && !defined(CATCH_CONFIG_CPP11_NULLPTR) && !defined(CATCH_CONFIG_NO_CPP11)
+#   define CATCH_CONFIG_CPP11_NULLPTR
+#endif
+#if defined(CATCH_INTERNAL_CONFIG_CPP11_NOEXCEPT) && !defined(CATCH_CONFIG_CPP11_NO_NOEXCEPT) && !defined(CATCH_CONFIG_CPP11_NOEXCEPT) && !defined(CATCH_CONFIG_NO_CPP11)
+#   define CATCH_CONFIG_CPP11_NOEXCEPT
+#endif
+#if defined(CATCH_INTERNAL_CONFIG_CPP11_GENERATED_METHODS) && !defined(CATCH_CONFIG_CPP11_NO_GENERATED_METHODS) && !defined(CATCH_CONFIG_CPP11_GENERATED_METHODS) && !defined(CATCH_CONFIG_NO_CPP11)
+#   define CATCH_CONFIG_CPP11_GENERATED_METHODS
+#endif
+#if defined(CATCH_INTERNAL_CONFIG_CPP11_IS_ENUM) && !defined(CATCH_CONFIG_CPP11_NO_IS_ENUM) && !defined(CATCH_CONFIG_CPP11_IS_ENUM) && !defined(CATCH_CONFIG_NO_CPP11)
+#   define CATCH_CONFIG_CPP11_IS_ENUM
+#endif
+#if defined(CATCH_INTERNAL_CONFIG_CPP11_TUPLE) && !defined(CATCH_CONFIG_CPP11_NO_TUPLE) && !defined(CATCH_CONFIG_CPP11_TUPLE) && !defined(CATCH_CONFIG_NO_CPP11)
+#   define CATCH_CONFIG_CPP11_TUPLE
+#endif
+#if defined(CATCH_INTERNAL_CONFIG_VARIADIC_MACROS) && !defined(CATCH_CONFIG_NO_VARIADIC_MACROS) && !defined(CATCH_CONFIG_VARIADIC_MACROS)
+#   define CATCH_CONFIG_VARIADIC_MACROS
+#endif
+#if defined(CATCH_INTERNAL_CONFIG_CPP11_LONG_LONG) && !defined(CATCH_CONFIG_CPP11_NO_LONG_LONG) && !defined(CATCH_CONFIG_CPP11_LONG_LONG) && !defined(CATCH_CONFIG_NO_CPP11)
+#   define CATCH_CONFIG_CPP11_LONG_LONG
+#endif
+#if defined(CATCH_INTERNAL_CONFIG_CPP11_OVERRIDE) && !defined(CATCH_CONFIG_CPP11_NO_OVERRIDE) && !defined(CATCH_CONFIG_CPP11_OVERRIDE) && !defined(CATCH_CONFIG_NO_CPP11)
+#   define CATCH_CONFIG_CPP11_OVERRIDE
+#endif
+#if defined(CATCH_INTERNAL_CONFIG_CPP11_UNIQUE_PTR) && !defined(CATCH_CONFIG_CPP11_NO_UNIQUE_PTR) && !defined(CATCH_CONFIG_CPP11_UNIQUE_PTR) && !defined(CATCH_CONFIG_NO_CPP11)
+#   define CATCH_CONFIG_CPP11_UNIQUE_PTR
+#endif
+// Use of __COUNTER__ is suppressed if __JETBRAINS_IDE__ is #defined (meaning we're being parsed by a JetBrains IDE for
+// analytics) because, at time of writing, __COUNTER__ is not properly handled by it.
+// This does not affect compilation
+#if defined(CATCH_INTERNAL_CONFIG_COUNTER) && !defined(CATCH_CONFIG_NO_COUNTER) && !defined(CATCH_CONFIG_COUNTER) && !defined(__JETBRAINS_IDE__)
+#   define CATCH_CONFIG_COUNTER
+#endif
+#if defined(CATCH_INTERNAL_CONFIG_CPP11_SHUFFLE) && !defined(CATCH_CONFIG_CPP11_NO_SHUFFLE) && !defined(CATCH_CONFIG_CPP11_SHUFFLE) && !defined(CATCH_CONFIG_NO_CPP11)
+#   define CATCH_CONFIG_CPP11_SHUFFLE
+#endif
+
+#if !defined(CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS)
+#   define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS
+#endif
+
+// noexcept support:
+#if defined(CATCH_CONFIG_CPP11_NOEXCEPT) && !defined(CATCH_NOEXCEPT)
+#  define CATCH_NOEXCEPT noexcept
+#  define CATCH_NOEXCEPT_IS(x) noexcept(x)
+#else
+#  define CATCH_NOEXCEPT throw()
+#  define CATCH_NOEXCEPT_IS(x)
+#endif
+
+// nullptr support
+#ifdef CATCH_CONFIG_CPP11_NULLPTR
+#   define CATCH_NULL nullptr
+#else
+#   define CATCH_NULL NULL
+#endif
+
+// override support
+#ifdef CATCH_CONFIG_CPP11_OVERRIDE
+#   define CATCH_OVERRIDE override
+#else
+#   define CATCH_OVERRIDE
+#endif
+
+// unique_ptr support
+#ifdef CATCH_CONFIG_CPP11_UNIQUE_PTR
+#   define CATCH_AUTO_PTR( T ) std::unique_ptr<T>
+#else
+#   define CATCH_AUTO_PTR( T ) std::auto_ptr<T>
+#endif
+
+#define INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) name##line
+#define INTERNAL_CATCH_UNIQUE_NAME_LINE( name, line ) INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line )
+#ifdef CATCH_CONFIG_COUNTER
+#  define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __COUNTER__ )
+#else
+#  define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __LINE__ )
+#endif
+
+#define INTERNAL_CATCH_STRINGIFY2( expr ) #expr
+#define INTERNAL_CATCH_STRINGIFY( expr ) INTERNAL_CATCH_STRINGIFY2( expr )
+
+#include <sstream>
+#include <stdexcept>
+#include <algorithm>
+
+namespace Catch {
+
+    struct IConfig;
+
+    struct CaseSensitive { enum Choice {
+        Yes,
+        No
+    }; };
+
+    class NonCopyable {
+#ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS
+        NonCopyable( NonCopyable const& )              = delete;
+        NonCopyable( NonCopyable && )                  = delete;
+        NonCopyable& operator = ( NonCopyable const& ) = delete;
+        NonCopyable& operator = ( NonCopyable && )     = delete;
+#else
+        NonCopyable( NonCopyable const& info );
+        NonCopyable& operator = ( NonCopyable const& );
+#endif
+
+    protected:
+        NonCopyable() {}
+        virtual ~NonCopyable();
+    };
+
+    class SafeBool {
+    public:
+        typedef void (SafeBool::*type)() const;
+
+        static type makeSafe( bool value ) {
+            return value ? &SafeBool::trueValue : 0;
+        }
+    private:
+        void trueValue() const {}
+    };
+
+    template<typename ContainerT>
+    inline void deleteAll( ContainerT& container ) {
+        typename ContainerT::const_iterator it = container.begin();
+        typename ContainerT::const_iterator itEnd = container.end();
+        for(; it != itEnd; ++it )
+            delete *it;
+    }
+    template<typename AssociativeContainerT>
+    inline void deleteAllValues( AssociativeContainerT& container ) {
+        typename AssociativeContainerT::const_iterator it = container.begin();
+        typename AssociativeContainerT::const_iterator itEnd = container.end();
+        for(; it != itEnd; ++it )
+            delete it->second;
+    }
+
+    bool startsWith( std::string const& s, std::string const& prefix );
+    bool endsWith( std::string const& s, std::string const& suffix );
+    bool contains( std::string const& s, std::string const& infix );
+    void toLowerInPlace( std::string& s );
+    std::string toLower( std::string const& s );
+    std::string trim( std::string const& str );
+    bool replaceInPlace( std::string& str, std::string const& replaceThis, std::string const& withThis );
+
+    struct pluralise {
+        pluralise( std::size_t count, std::string const& label );
+
+        friend std::ostream& operator << ( std::ostream& os, pluralise const& pluraliser );
+
+        std::size_t m_count;
+        std::string m_label;
+    };
+
+    struct SourceLineInfo {
+
+        SourceLineInfo();
+        SourceLineInfo( char const* _file, std::size_t _line );
+        SourceLineInfo( SourceLineInfo const& other );
+#  ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS
+        SourceLineInfo( SourceLineInfo && )                  = default;
+        SourceLineInfo& operator = ( SourceLineInfo const& ) = default;
+        SourceLineInfo& operator = ( SourceLineInfo && )     = default;
+#  endif
+        bool empty() const;
+        bool operator == ( SourceLineInfo const& other ) const;
+        bool operator < ( SourceLineInfo const& other ) const;
+
+        std::string file;
+        std::size_t line;
+    };
+
+    std::ostream& operator << ( std::ostream& os, SourceLineInfo const& info );
+
+    // This is just here to avoid compiler warnings with macro constants and boolean literals
+    inline bool alwaysTrue( std::size_t = 0 ) { return true; }
+    inline bool alwaysFalse( std::size_t = 0 ) { return false; }
+
+    void throwLogicError( std::string const& message, SourceLineInfo const& locationInfo );
+
+    void seedRng( IConfig const& config );
+    unsigned int rngSeed();
+
+    // Use this in variadic streaming macros to allow
+    //    >> +StreamEndStop
+    // as well as
+    //    >> stuff +StreamEndStop
+    struct StreamEndStop {
+        std::string operator+() {
+            return std::string();
+        }
+    };
+    template<typename T>
+    T const& operator + ( T const& value, StreamEndStop ) {
+        return value;
+    }
+}
+
+#define CATCH_INTERNAL_LINEINFO ::Catch::SourceLineInfo( __FILE__, static_cast<std::size_t>( __LINE__ ) )
+#define CATCH_INTERNAL_ERROR( msg ) ::Catch::throwLogicError( msg, CATCH_INTERNAL_LINEINFO );
+
+#include <ostream>
+
+namespace Catch {
+
+    class NotImplementedException : public std::exception
+    {
+    public:
+        NotImplementedException( SourceLineInfo const& lineInfo );
+        NotImplementedException( NotImplementedException const& ) {}
+
+        virtual ~NotImplementedException() CATCH_NOEXCEPT {}
+
+        virtual const char* what() const CATCH_NOEXCEPT;
+
+    private:
+        std::string m_what;
+        SourceLineInfo m_lineInfo;
+    };
+
+} // end namespace Catch
+
+///////////////////////////////////////////////////////////////////////////////
+#define CATCH_NOT_IMPLEMENTED throw Catch::NotImplementedException( CATCH_INTERNAL_LINEINFO )
+
+// #included from: internal/catch_context.h
+#define TWOBLUECUBES_CATCH_CONTEXT_H_INCLUDED
+
+// #included from: catch_interfaces_generators.h
+#define TWOBLUECUBES_CATCH_INTERFACES_GENERATORS_H_INCLUDED
+
+#include <string>
+
+namespace Catch {
+
+    struct IGeneratorInfo {
+        virtual ~IGeneratorInfo();
+        virtual bool moveNext() = 0;
+        virtual std::size_t getCurrentIndex() const = 0;
+    };
+
+    struct IGeneratorsForTest {
+        virtual ~IGeneratorsForTest();
+
+        virtual IGeneratorInfo& getGeneratorInfo( std::string const& fileInfo, std::size_t size ) = 0;
+        virtual bool moveNext() = 0;
+    };
+
+    IGeneratorsForTest* createGeneratorsForTest();
+
+} // end namespace Catch
+
+// #included from: catch_ptr.hpp
+#define TWOBLUECUBES_CATCH_PTR_HPP_INCLUDED
+
+#ifdef __clang__
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wpadded"
+#endif
+
+namespace Catch {
+
+    // An intrusive reference counting smart pointer.
+    // T must implement addRef() and release() methods
+    // typically implementing the IShared interface
+    template<typename T>
+    class Ptr {
+    public:
+        Ptr() : m_p( CATCH_NULL ){}
+        Ptr( T* p ) : m_p( p ){
+            if( m_p )
+                m_p->addRef();
+        }
+        Ptr( Ptr const& other ) : m_p( other.m_p ){
+            if( m_p )
+                m_p->addRef();
+        }
+        ~Ptr(){
+            if( m_p )
+                m_p->release();
+        }
+        void reset() {
+            if( m_p )
+                m_p->release();
+            m_p = CATCH_NULL;
+        }
+        Ptr& operator = ( T* p ){
+            Ptr temp( p );
+            swap( temp );
+            return *this;
+        }
+        Ptr& operator = ( Ptr const& other ){
+            Ptr temp( other );
+            swap( temp );
+            return *this;
+        }
+        void swap( Ptr& other ) { std::swap( m_p, other.m_p ); }
+        T* get() const{ return m_p; }
+        T& operator*() const { return *m_p; }
+        T* operator->() const { return m_p; }
+        bool operator !() const { return m_p == CATCH_NULL; }
+        operator SafeBool::type() const { return SafeBool::makeSafe( m_p != CATCH_NULL ); }
+
+    private:
+        T* m_p;
+    };
+
+    struct IShared : NonCopyable {
+        virtual ~IShared();
+        virtual void addRef() const = 0;
+        virtual void release() const = 0;
+    };
+
+    template<typename T = IShared>
+    struct SharedImpl : T {
+
+        SharedImpl() : m_rc( 0 ){}
+
+        virtual void addRef() const {
+            ++m_rc;
+        }
+        virtual void release() const {
+            if( --m_rc == 0 )
+                delete this;
+        }
+
+        mutable unsigned int m_rc;
+    };
+
+} // end namespace Catch
+
+#ifdef __clang__
+#pragma clang diagnostic pop
+#endif
+
+#include <memory>
+#include <vector>
+#include <stdlib.h>
+
+namespace Catch {
+
+    class TestCase;
+    class Stream;
+    struct IResultCapture;
+    struct IRunner;
+    struct IGeneratorsForTest;
+    struct IConfig;
+
+    struct IContext
+    {
+        virtual ~IContext();
+
+        virtual IResultCapture* getResultCapture() = 0;
+        virtual IRunner* getRunner() = 0;
+        virtual size_t getGeneratorIndex( std::string const& fileInfo, size_t totalSize ) = 0;
+        virtual bool advanceGeneratorsForCurrentTest() = 0;
+        virtual Ptr<IConfig const> getConfig() const = 0;
+    };
+
+    struct IMutableContext : IContext
+    {
+        virtual ~IMutableContext();
+        virtual void setResultCapture( IResultCapture* resultCapture ) = 0;
+        virtual void setRunner( IRunner* runner ) = 0;
+        virtual void setConfig( Ptr<IConfig const> const& config ) = 0;
+    };
+
+    IContext& getCurrentContext();
+    IMutableContext& getCurrentMutableContext();
+    void cleanUpContext();
+    Stream createStream( std::string const& streamName );
+
+}
+
+// #included from: internal/catch_test_registry.hpp
+#define TWOBLUECUBES_CATCH_TEST_REGISTRY_HPP_INCLUDED
+
+// #included from: catch_interfaces_testcase.h
+#define TWOBLUECUBES_CATCH_INTERFACES_TESTCASE_H_INCLUDED
+
+#include <vector>
+
+namespace Catch {
+
+    class TestSpec;
+
+    struct ITestCase : IShared {
+        virtual void invoke () const = 0;
+    protected:
+        virtual ~ITestCase();
+    };
+
+    class TestCase;
+    struct IConfig;
+
+    struct ITestCaseRegistry {
+        virtual ~ITestCaseRegistry();
+        virtual std::vector<TestCase> const& getAllTests() const = 0;
+        virtual std::vector<TestCase> const& getAllTestsSorted( IConfig const& config ) const = 0;
+    };
+
+    bool matchTest( TestCase const& testCase, TestSpec const& testSpec, IConfig const& config );
+    std::vector<TestCase> filterTests( std::vector<TestCase> const& testCases, TestSpec const& testSpec, IConfig const& config );
+    std::vector<TestCase> const& getAllTestCasesSorted( IConfig const& config );
+
+}
+
+namespace Catch {
+
+template<typename C>
+class MethodTestCase : public SharedImpl<ITestCase> {
+
+public:
+    MethodTestCase( void (C::*method)() ) : m_method( method ) {}
+
+    virtual void invoke() const {
+        C obj;
+        (obj.*m_method)();
+    }
+
+private:
+    virtual ~MethodTestCase() {}
+
+    void (C::*m_method)();
+};
+
+typedef void(*TestFunction)();
+
+struct NameAndDesc {
+    NameAndDesc( const char* _name = "", const char* _description= "" )
+    : name( _name ), description( _description )
+    {}
+
+    const char* name;
+    const char* description;
+};
+
+void registerTestCase
+    (   ITestCase* testCase,
+        char const* className,
+        NameAndDesc const& nameAndDesc,
+        SourceLineInfo const& lineInfo );
+
+struct AutoReg {
+
+    AutoReg
+        (   TestFunction function,
+            SourceLineInfo const& lineInfo,
+            NameAndDesc const& nameAndDesc );
+
+    template<typename C>
+    AutoReg
+        (   void (C::*method)(),
+            char const* className,
+            NameAndDesc const& nameAndDesc,
+            SourceLineInfo const& lineInfo ) {
+
+        registerTestCase
+            (   new MethodTestCase<C>( method ),
+                className,
+                nameAndDesc,
+                lineInfo );
+    }
+
+    ~AutoReg();
+
+private:
+    AutoReg( AutoReg const& );
+    void operator= ( AutoReg const& );
+};
+
+void registerTestCaseFunction
+    (   TestFunction function,
+        SourceLineInfo const& lineInfo,
+        NameAndDesc const& nameAndDesc );
+
+} // end namespace Catch
+
+#ifdef CATCH_CONFIG_VARIADIC_MACROS
+    ///////////////////////////////////////////////////////////////////////////////
+    #define INTERNAL_CATCH_TESTCASE2( TestName, ... ) \
+        static void TestName(); \
+        namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( &TestName, CATCH_INTERNAL_LINEINFO, Catch::NameAndDesc( __VA_ARGS__ ) ); }\
+        static void TestName()
+    #define INTERNAL_CATCH_TESTCASE( ... ) \
+        INTERNAL_CATCH_TESTCASE2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), __VA_ARGS__ )
+
+    ///////////////////////////////////////////////////////////////////////////////
+    #define INTERNAL_CATCH_METHOD_AS_TEST_CASE( QualifiedMethod, ... ) \
+        namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( &QualifiedMethod, "&" #QualifiedMethod, Catch::NameAndDesc( __VA_ARGS__ ), CATCH_INTERNAL_LINEINFO ); }
+
+    ///////////////////////////////////////////////////////////////////////////////
+    #define INTERNAL_CATCH_TEST_CASE_METHOD2( TestName, ClassName, ... )\
+        namespace{ \
+            struct TestName : ClassName{ \
+                void test(); \
+            }; \
+            Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar ) ( &TestName::test, #ClassName, Catch::NameAndDesc( __VA_ARGS__ ), CATCH_INTERNAL_LINEINFO ); \
+        } \
+        void TestName::test()
+    #define INTERNAL_CATCH_TEST_CASE_METHOD( ClassName, ... ) \
+        INTERNAL_CATCH_TEST_CASE_METHOD2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), ClassName, __VA_ARGS__ )
+
+    ///////////////////////////////////////////////////////////////////////////////
+    #define INTERNAL_CATCH_REGISTER_TESTCASE( Function, ... ) \
+        Catch::AutoReg( Function, CATCH_INTERNAL_LINEINFO, Catch::NameAndDesc( __VA_ARGS__ ) );
+
+#else
+    ///////////////////////////////////////////////////////////////////////////////
+    #define INTERNAL_CATCH_TESTCASE2( TestName, Name, Desc ) \
+        static void TestName(); \
+        namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( &TestName, CATCH_INTERNAL_LINEINFO, Catch::NameAndDesc( Name, Desc ) ); }\
+        static void TestName()
+    #define INTERNAL_CATCH_TESTCASE( Name, Desc ) \
+        INTERNAL_CATCH_TESTCASE2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), Name, Desc )
+
+    ///////////////////////////////////////////////////////////////////////////////
+    #define INTERNAL_CATCH_METHOD_AS_TEST_CASE( QualifiedMethod, Name, Desc ) \
+        namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( &QualifiedMethod, "&" #QualifiedMethod, Catch::NameAndDesc( Name, Desc ), CATCH_INTERNAL_LINEINFO ); }
+
+    ///////////////////////////////////////////////////////////////////////////////
+    #define INTERNAL_CATCH_TEST_CASE_METHOD2( TestCaseName, ClassName, TestName, Desc )\
+        namespace{ \
+            struct TestCaseName : ClassName{ \
+                void test(); \
+            }; \
+            Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar ) ( &TestCaseName::test, #ClassName, Catch::NameAndDesc( TestName, Desc ), CATCH_INTERNAL_LINEINFO ); \
+        } \
+        void TestCaseName::test()
+    #define INTERNAL_CATCH_TEST_CASE_METHOD( ClassName, TestName, Desc )\
+        INTERNAL_CATCH_TEST_CASE_METHOD2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), ClassName, TestName, Desc )
+
+    ///////////////////////////////////////////////////////////////////////////////
+    #define INTERNAL_CATCH_REGISTER_TESTCASE( Function, Name, Desc ) \
+        Catch::AutoReg( Function, CATCH_INTERNAL_LINEINFO, Catch::NameAndDesc( Name, Desc ) );
+#endif
+
+// #included from: internal/catch_capture.hpp
+#define TWOBLUECUBES_CATCH_CAPTURE_HPP_INCLUDED
+
+// #included from: catch_result_builder.h
+#define TWOBLUECUBES_CATCH_RESULT_BUILDER_H_INCLUDED
+
+// #included from: catch_result_type.h
+#define TWOBLUECUBES_CATCH_RESULT_TYPE_H_INCLUDED
+
+namespace Catch {
+
+    // ResultWas::OfType enum
+    struct ResultWas { enum OfType {
+        Unknown = -1,
+        Ok = 0,
+        Info = 1,
+        Warning = 2,
+
+        FailureBit = 0x10,
+
+        ExpressionFailed = FailureBit | 1,
+        ExplicitFailure = FailureBit | 2,
+
+        Exception = 0x100 | FailureBit,
+
+        ThrewException = Exception | 1,
+        DidntThrowException = Exception | 2,
+
+        FatalErrorCondition = 0x200 | FailureBit
+
+    }; };
+
+    inline bool isOk( ResultWas::OfType resultType ) {
+        return ( resultType & ResultWas::FailureBit ) == 0;
+    }
+    inline bool isJustInfo( int flags ) {
+        return flags == ResultWas::Info;
+    }
+
+    // ResultDisposition::Flags enum
+    struct ResultDisposition { enum Flags {
+        Normal = 0x01,
+
+        ContinueOnFailure = 0x02,   // Failures fail test, but execution continues
+        FalseTest = 0x04,           // Prefix expression with !
+        SuppressFail = 0x08         // Failures are reported but do not fail the test
+    }; };
+
+    inline ResultDisposition::Flags operator | ( ResultDisposition::Flags lhs, ResultDisposition::Flags rhs ) {
+        return static_cast<ResultDisposition::Flags>( static_cast<int>( lhs ) | static_cast<int>( rhs ) );
+    }
+
+    inline bool shouldContinueOnFailure( int flags )    { return ( flags & ResultDisposition::ContinueOnFailure ) != 0; }
+    inline bool isFalseTest( int flags )                { return ( flags & ResultDisposition::FalseTest ) != 0; }
+    inline bool shouldSuppressFailure( int flags )      { return ( flags & ResultDisposition::SuppressFail ) != 0; }
+
+} // end namespace Catch
+
+// #included from: catch_assertionresult.h
+#define TWOBLUECUBES_CATCH_ASSERTIONRESULT_H_INCLUDED
+
+#include <string>
+
+namespace Catch {
+
+    struct AssertionInfo
+    {
+        AssertionInfo() {}
+        AssertionInfo(  std::string const& _macroName,
+                        SourceLineInfo const& _lineInfo,
+                        std::string const& _capturedExpression,
+                        ResultDisposition::Flags _resultDisposition );
+
+        std::string macroName;
+        SourceLineInfo lineInfo;
+        std::string capturedExpression;
+        ResultDisposition::Flags resultDisposition;
+    };
+
+    struct AssertionResultData
+    {
+        AssertionResultData() : resultType( ResultWas::Unknown ) {}
+
+        std::string reconstructedExpression;
+        std::string message;
+        ResultWas::OfType resultType;
+    };
+
+    class AssertionResult {
+    public:
+        AssertionResult();
+        AssertionResult( AssertionInfo const& info, AssertionResultData const& data );
+        ~AssertionResult();
+#  ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS
+         AssertionResult( AssertionResult const& )              = default;
+         AssertionResult( AssertionResult && )                  = default;
+         AssertionResult& operator = ( AssertionResult const& ) = default;
+         AssertionResult& operator = ( AssertionResult && )     = default;
+#  endif
+
+        bool isOk() const;
+        bool succeeded() const;
+        ResultWas::OfType getResultType() const;
+        bool hasExpression() const;
+        bool hasMessage() const;
+        std::string getExpression() const;
+        std::string getExpressionInMacro() const;
+        bool hasExpandedExpression() const;
+        std::string getExpandedExpression() const;
+        std::string getMessage() const;
+        SourceLineInfo getSourceInfo() const;
+        std::string getTestMacroName() const;
+
+    protected:
+        AssertionInfo m_info;
+        AssertionResultData m_resultData;
+    };
+
+} // end namespace Catch
+
+// #included from: catch_matchers.hpp
+#define TWOBLUECUBES_CATCH_MATCHERS_HPP_INCLUDED
+
+namespace Catch {
+namespace Matchers {
+    namespace Impl {
+
+    namespace Generic {
+        template<typename ExpressionT> class AllOf;
+        template<typename ExpressionT> class AnyOf;
+        template<typename ExpressionT> class Not;
+    }
+
+    template<typename ExpressionT>
+    struct Matcher : SharedImpl<IShared>
+    {
+        typedef ExpressionT ExpressionType;
+
+        virtual ~Matcher() {}
+        virtual Ptr<Matcher> clone() const = 0;
+        virtual bool match( ExpressionT const& expr ) const = 0;
+        virtual std::string toString() const = 0;
+
+        Generic::AllOf<ExpressionT> operator && ( Matcher<ExpressionT> const& other ) const;
+        Generic::AnyOf<ExpressionT> operator || ( Matcher<ExpressionT> const& other ) const;
+        Generic::Not<ExpressionT> operator ! () const;
+    };
+
+    template<typename DerivedT, typename ExpressionT>
+    struct MatcherImpl : Matcher<ExpressionT> {
+
+        virtual Ptr<Matcher<ExpressionT> > clone() const {
+            return Ptr<Matcher<ExpressionT> >( new DerivedT( static_cast<DerivedT const&>( *this ) ) );
+        }
+    };
+
+    namespace Generic {
+        template<typename ExpressionT>
+        class Not : public MatcherImpl<Not<ExpressionT>, ExpressionT> {
+        public:
+            explicit Not( Matcher<ExpressionT> const& matcher ) : m_matcher(matcher.clone()) {}
+            Not( Not const& other ) : m_matcher( other.m_matcher ) {}
+
+            virtual bool match( ExpressionT const& expr ) const CATCH_OVERRIDE {
+                return !m_matcher->match( expr );
+            }
+
+            virtual std::string toString() const CATCH_OVERRIDE {
+                return "not " + m_matcher->toString();
+            }
+        private:
+            Ptr< Matcher<ExpressionT> > m_matcher;
+        };
+
+        template<typename ExpressionT>
+        class AllOf : public MatcherImpl<AllOf<ExpressionT>, ExpressionT> {
+        public:
+
+            AllOf() {}
+            AllOf( AllOf const& other ) : m_matchers( other.m_matchers ) {}
+
+            AllOf& add( Matcher<ExpressionT> const& matcher ) {
+                m_matchers.push_back( matcher.clone() );
+                return *this;
+            }
+            virtual bool match( ExpressionT const& expr ) const
+            {
+                for( std::size_t i = 0; i < m_matchers.size(); ++i )
+                    if( !m_matchers[i]->match( expr ) )
+                        return false;
+                return true;
+            }
+            virtual std::string toString() const {
+                std::ostringstream oss;
+                oss << "( ";
+                for( std::size_t i = 0; i < m_matchers.size(); ++i ) {
+                    if( i != 0 )
+                        oss << " and ";
+                    oss << m_matchers[i]->toString();
+                }
+                oss << " )";
+                return oss.str();
+            }
+
+            AllOf operator && ( Matcher<ExpressionT> const& other ) const {
+                AllOf allOfExpr( *this );
+                allOfExpr.add( other );
+                return allOfExpr;
+            }
+
+        private:
+            std::vector<Ptr<Matcher<ExpressionT> > > m_matchers;
+        };
+
+        template<typename ExpressionT>
+        class AnyOf : public MatcherImpl<AnyOf<ExpressionT>, ExpressionT> {
+        public:
+
+            AnyOf() {}
+            AnyOf( AnyOf const& other ) : m_matchers( other.m_matchers ) {}
+
+            AnyOf& add( Matcher<ExpressionT> const& matcher ) {
+                m_matchers.push_back( matcher.clone() );
+                return *this;
+            }
+            virtual bool match( ExpressionT const& expr ) const
+            {
+                for( std::size_t i = 0; i < m_matchers.size(); ++i )
+                    if( m_matchers[i]->match( expr ) )
+                        return true;
+                return false;
+            }
+            virtual std::string toString() const {
+                std::ostringstream oss;
+                oss << "( ";
+                for( std::size_t i = 0; i < m_matchers.size(); ++i ) {
+                    if( i != 0 )
+                        oss << " or ";
+                    oss << m_matchers[i]->toString();
+                }
+                oss << " )";
+                return oss.str();
+            }
+
+            AnyOf operator || ( Matcher<ExpressionT> const& other ) const {
+                AnyOf anyOfExpr( *this );
+                anyOfExpr.add( other );
+                return anyOfExpr;
+            }
+
+        private:
+            std::vector<Ptr<Matcher<ExpressionT> > > m_matchers;
+        };
+
+    } // namespace Generic
+
+    template<typename ExpressionT>
+    Generic::AllOf<ExpressionT> Matcher<ExpressionT>::operator && ( Matcher<ExpressionT> const& other ) const {
+        Generic::AllOf<ExpressionT> allOfExpr;
+        allOfExpr.add( *this );
+        allOfExpr.add( other );
+        return allOfExpr;
+    }
+
+    template<typename ExpressionT>
+    Generic::AnyOf<ExpressionT> Matcher<ExpressionT>::operator || ( Matcher<ExpressionT> const& other ) const {
+        Generic::AnyOf<ExpressionT> anyOfExpr;
+        anyOfExpr.add( *this );
+        anyOfExpr.add( other );
+        return anyOfExpr;
+    }
+
+    template<typename ExpressionT>
+    Generic::Not<ExpressionT> Matcher<ExpressionT>::operator ! () const {
+        return Generic::Not<ExpressionT>( *this );
+    }
+
+    namespace StdString {
+
+        inline std::string makeString( std::string const& str ) { return str; }
+        inline std::string makeString( const char* str ) { return str ? std::string( str ) : std::string(); }
+
+        struct CasedString
+        {
+            CasedString( std::string const& str, CaseSensitive::Choice caseSensitivity )
+            :   m_caseSensitivity( caseSensitivity ),
+                m_str( adjustString( str ) )
+            {}
+            std::string adjustString( std::string const& str ) const {
+                return m_caseSensitivity == CaseSensitive::No
+                    ? toLower( str )
+                    : str;
+
+            }
+            std::string toStringSuffix() const
+            {
+                return m_caseSensitivity == CaseSensitive::No
+                    ? " (case insensitive)"
+                    : "";
+            }
+            CaseSensitive::Choice m_caseSensitivity;
+            std::string m_str;
+        };
+
+        struct Equals : MatcherImpl<Equals, std::string> {
+            Equals( std::string const& str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes )
+            :   m_data( str, caseSensitivity )
+            {}
+            Equals( Equals const& other ) : m_data( other.m_data ){}
+
+            virtual ~Equals();
+
+            virtual bool match( std::string const& expr ) const {
+                return m_data.m_str == m_data.adjustString( expr );;
+            }
+            virtual std::string toString() const {
+                return "equals: \"" + m_data.m_str + "\"" + m_data.toStringSuffix();
+            }
+
+            CasedString m_data;
+        };
+
+        struct Contains : MatcherImpl<Contains, std::string> {
+            Contains( std::string const& substr, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes )
+            : m_data( substr, caseSensitivity ){}
+            Contains( Contains const& other ) : m_data( other.m_data ){}
+
+            virtual ~Contains();
+
+            virtual bool match( std::string const& expr ) const {
+                return m_data.adjustString( expr ).find( m_data.m_str ) != std::string::npos;
+            }
+            virtual std::string toString() const {
+                return "contains: \"" + m_data.m_str  + "\"" + m_data.toStringSuffix();
+            }
+
+            CasedString m_data;
+        };
+
+        struct StartsWith : MatcherImpl<StartsWith, std::string> {
+            StartsWith( std::string const& substr, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes )
+            : m_data( substr, caseSensitivity ){}
+
+            StartsWith( StartsWith const& other ) : m_data( other.m_data ){}
+
+            virtual ~StartsWith();
+
+            virtual bool match( std::string const& expr ) const {
+                return startsWith( m_data.adjustString( expr ), m_data.m_str );
+            }
+            virtual std::string toString() const {
+                return "starts with: \"" + m_data.m_str + "\"" + m_data.toStringSuffix();
+            }
+
+            CasedString m_data;
+        };
+
+        struct EndsWith : MatcherImpl<EndsWith, std::string> {
+            EndsWith( std::string const& substr, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes )
+            : m_data( substr, caseSensitivity ){}
+            EndsWith( EndsWith const& other ) : m_data( other.m_data ){}
+
+            virtual ~EndsWith();
+
+            virtual bool match( std::string const& expr ) const {
+                return endsWith( m_data.adjustString( expr ), m_data.m_str );
+            }
+            virtual std::string toString() const {
+                return "ends with: \"" + m_data.m_str + "\"" + m_data.toStringSuffix();
+            }
+
+            CasedString m_data;
+        };
+    } // namespace StdString
+    } // namespace Impl
+
+    // The following functions create the actual matcher objects.
+    // This allows the types to be inferred
+    template<typename ExpressionT>
+    inline Impl::Generic::Not<ExpressionT> Not( Impl::Matcher<ExpressionT> const& m ) {
+        return Impl::Generic::Not<ExpressionT>( m );
+    }
+
+    template<typename ExpressionT>
+    inline Impl::Generic::AllOf<ExpressionT> AllOf( Impl::Matcher<ExpressionT> const& m1,
+                                                    Impl::Matcher<ExpressionT> const& m2 ) {
+        return Impl::Generic::AllOf<ExpressionT>().add( m1 ).add( m2 );
+    }
+    template<typename ExpressionT>
+    inline Impl::Generic::AllOf<ExpressionT> AllOf( Impl::Matcher<ExpressionT> const& m1,
+                                                    Impl::Matcher<ExpressionT> const& m2,
+                                                    Impl::Matcher<ExpressionT> const& m3 ) {
+        return Impl::Generic::AllOf<ExpressionT>().add( m1 ).add( m2 ).add( m3 );
+    }
+    template<typename ExpressionT>
+    inline Impl::Generic::AnyOf<ExpressionT> AnyOf( Impl::Matcher<ExpressionT> const& m1,
+                                                    Impl::Matcher<ExpressionT> const& m2 ) {
+        return Impl::Generic::AnyOf<ExpressionT>().add( m1 ).add( m2 );
+    }
+    template<typename ExpressionT>
+    inline Impl::Generic::AnyOf<ExpressionT> AnyOf( Impl::Matcher<ExpressionT> const& m1,
+                                                    Impl::Matcher<ExpressionT> const& m2,
+                                                    Impl::Matcher<ExpressionT> const& m3 ) {
+        return Impl::Generic::AnyOf<ExpressionT>().add( m1 ).add( m2 ).add( m3 );
+    }
+
+    inline Impl::StdString::Equals      Equals( std::string const& str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ) {
+        return Impl::StdString::Equals( str, caseSensitivity );
+    }
+    inline Impl::StdString::Equals      Equals( const char* str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ) {
+        return Impl::StdString::Equals( Impl::StdString::makeString( str ), caseSensitivity );
+    }
+    inline Impl::StdString::Contains    Contains( std::string const& substr, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ) {
+        return Impl::StdString::Contains( substr, caseSensitivity );
+    }
+    inline Impl::StdString::Contains    Contains( const char* substr, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ) {
+        return Impl::StdString::Contains( Impl::StdString::makeString( substr ), caseSensitivity );
+    }
+    inline Impl::StdString::StartsWith  StartsWith( std::string const& substr ) {
+        return Impl::StdString::StartsWith( substr );
+    }
+    inline Impl::StdString::StartsWith  StartsWith( const char* substr ) {
+        return Impl::StdString::StartsWith( Impl::StdString::makeString( substr ) );
+    }
+    inline Impl::StdString::EndsWith    EndsWith( std::string const& substr ) {
+        return Impl::StdString::EndsWith( substr );
+    }
+    inline Impl::StdString::EndsWith    EndsWith( const char* substr ) {
+        return Impl::StdString::EndsWith( Impl::StdString::makeString( substr ) );
+    }
+
+} // namespace Matchers
+
+using namespace Matchers;
+
+} // namespace Catch
+
+namespace Catch {
+
+    struct TestFailureException{};
+
+    template<typename T> class ExpressionLhs;
+
+    struct STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison;
+
+    struct CopyableStream {
+        CopyableStream() {}
+        CopyableStream( CopyableStream const& other ) {
+            oss << other.oss.str();
+        }
+        CopyableStream& operator=( CopyableStream const& other ) {
+            oss.str("");
+            oss << other.oss.str();
+            return *this;
+        }
+        std::ostringstream oss;
+    };
+
+    class ResultBuilder {
+    public:
+        ResultBuilder(  char const* macroName,
+                        SourceLineInfo const& lineInfo,
+                        char const* capturedExpression,
+                        ResultDisposition::Flags resultDisposition,
+                        char const* secondArg = "" );
+
+        template<typename T>
+        ExpressionLhs<T const&> operator <= ( T const& operand );
+        ExpressionLhs<bool> operator <= ( bool value );
+
+        template<typename T>
+        ResultBuilder& operator << ( T const& value ) {
+            m_stream.oss << value;
+            return *this;
+        }
+
+        template<typename RhsT> STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator && ( RhsT const& );
+        template<typename RhsT> STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator || ( RhsT const& );
+
+        ResultBuilder& setResultType( ResultWas::OfType result );
+        ResultBuilder& setResultType( bool result );
+        ResultBuilder& setLhs( std::string const& lhs );
+        ResultBuilder& setRhs( std::string const& rhs );
+        ResultBuilder& setOp( std::string const& op );
+
+        void endExpression();
+
+        std::string reconstructExpression() const;
+        AssertionResult build() const;
+
+        void useActiveException( ResultDisposition::Flags resultDisposition = ResultDisposition::Normal );
+        void captureResult( ResultWas::OfType resultType );
+        void captureExpression();
+        void captureExpectedException( std::string const& expectedMessage );
+        void captureExpectedException( Matchers::Impl::Matcher<std::string> const& matcher );
+        void handleResult( AssertionResult const& result );
+        void react();
+        bool shouldDebugBreak() const;
+        bool allowThrows() const;
+
+    private:
+        AssertionInfo m_assertionInfo;
+        AssertionResultData m_data;
+        struct ExprComponents {
+            ExprComponents() : testFalse( false ) {}
+            bool testFalse;
+            std::string lhs, rhs, op;
+        } m_exprComponents;
+        CopyableStream m_stream;
+
+        bool m_shouldDebugBreak;
+        bool m_shouldThrow;
+    };
+
+} // namespace Catch
+
+// Include after due to circular dependency:
+// #included from: catch_expression_lhs.hpp
+#define TWOBLUECUBES_CATCH_EXPRESSION_LHS_HPP_INCLUDED
+
+// #included from: catch_evaluate.hpp
+#define TWOBLUECUBES_CATCH_EVALUATE_HPP_INCLUDED
+
+#ifdef _MSC_VER
+#pragma warning(push)
+#pragma warning(disable:4389) // '==' : signed/unsigned mismatch
+#endif
+
+#include <cstddef>
+
+namespace Catch {
+namespace Internal {
+
+    enum Operator {
+        IsEqualTo,
+        IsNotEqualTo,
+        IsLessThan,
+        IsGreaterThan,
+        IsLessThanOrEqualTo,
+        IsGreaterThanOrEqualTo
+    };
+
+    template<Operator Op> struct OperatorTraits             { static const char* getName(){ return "*error*"; } };
+    template<> struct OperatorTraits<IsEqualTo>             { static const char* getName(){ return "=="; } };
+    template<> struct OperatorTraits<IsNotEqualTo>          { static const char* getName(){ return "!="; } };
+    template<> struct OperatorTraits<IsLessThan>            { static const char* getName(){ return "<"; } };
+    template<> struct OperatorTraits<IsGreaterThan>         { static const char* getName(){ return ">"; } };
+    template<> struct OperatorTraits<IsLessThanOrEqualTo>   { static const char* getName(){ return "<="; } };
+    template<> struct OperatorTraits<IsGreaterThanOrEqualTo>{ static const char* getName(){ return ">="; } };
+
+    template<typename T>
+    inline T& opCast(T const& t) { return const_cast<T&>(t); }
+
+// nullptr_t support based on pull request #154 from Konstantin Baumann
+#ifdef CATCH_CONFIG_CPP11_NULLPTR
+    inline std::nullptr_t opCast(std::nullptr_t) { return nullptr; }
+#endif // CATCH_CONFIG_CPP11_NULLPTR
+
+    // So the compare overloads can be operator agnostic we convey the operator as a template
+    // enum, which is used to specialise an Evaluator for doing the comparison.
+    template<typename T1, typename T2, Operator Op>
+    class Evaluator{};
+
+    template<typename T1, typename T2>
+    struct Evaluator<T1, T2, IsEqualTo> {
+        static bool evaluate( T1 const& lhs, T2 const& rhs) {
+            return bool( opCast( lhs ) ==  opCast( rhs ) );
+        }
+    };
+    template<typename T1, typename T2>
+    struct Evaluator<T1, T2, IsNotEqualTo> {
+        static bool evaluate( T1 const& lhs, T2 const& rhs ) {
+            return bool( opCast( lhs ) != opCast( rhs ) );
+        }
+    };
+    template<typename T1, typename T2>
+    struct Evaluator<T1, T2, IsLessThan> {
+        static bool evaluate( T1 const& lhs, T2 const& rhs ) {
+            return bool( opCast( lhs ) < opCast( rhs ) );
+        }
+    };
+    template<typename T1, typename T2>
+    struct Evaluator<T1, T2, IsGreaterThan> {
+        static bool evaluate( T1 const& lhs, T2 const& rhs ) {
+            return bool( opCast( lhs ) > opCast( rhs ) );
+        }
+    };
+    template<typename T1, typename T2>
+    struct Evaluator<T1, T2, IsGreaterThanOrEqualTo> {
+        static bool evaluate( T1 const& lhs, T2 const& rhs ) {
+            return bool( opCast( lhs ) >= opCast( rhs ) );
+        }
+    };
+    template<typename T1, typename T2>
+    struct Evaluator<T1, T2, IsLessThanOrEqualTo> {
+        static bool evaluate( T1 const& lhs, T2 const& rhs ) {
+            return bool( opCast( lhs ) <= opCast( rhs ) );
+        }
+    };
+
+    template<Operator Op, typename T1, typename T2>
+    bool applyEvaluator( T1 const& lhs, T2 const& rhs ) {
+        return Evaluator<T1, T2, Op>::evaluate( lhs, rhs );
+    }
+
+    // This level of indirection allows us to specialise for integer types
+    // to avoid signed/ unsigned warnings
+
+    // "base" overload
+    template<Operator Op, typename T1, typename T2>
+    bool compare( T1 const& lhs, T2 const& rhs ) {
+        return Evaluator<T1, T2, Op>::evaluate( lhs, rhs );
+    }
+
+    // unsigned X to int
+    template<Operator Op> bool compare( unsigned int lhs, int rhs ) {
+        return applyEvaluator<Op>( lhs, static_cast<unsigned int>( rhs ) );
+    }
+    template<Operator Op> bool compare( unsigned long lhs, int rhs ) {
+        return applyEvaluator<Op>( lhs, static_cast<unsigned int>( rhs ) );
+    }
+    template<Operator Op> bool compare( unsigned char lhs, int rhs ) {
+        return applyEvaluator<Op>( lhs, static_cast<unsigned int>( rhs ) );
+    }
+
+    // unsigned X to long
+    template<Operator Op> bool compare( unsigned int lhs, long rhs ) {
+        return applyEvaluator<Op>( lhs, static_cast<unsigned long>( rhs ) );
+    }
+    template<Operator Op> bool compare( unsigned long lhs, long rhs ) {
+        return applyEvaluator<Op>( lhs, static_cast<unsigned long>( rhs ) );
+    }
+    template<Operator Op> bool compare( unsigned char lhs, long rhs ) {
+        return applyEvaluator<Op>( lhs, static_cast<unsigned long>( rhs ) );
+    }
+
+    // int to unsigned X
+    template<Operator Op> bool compare( int lhs, unsigned int rhs ) {
+        return applyEvaluator<Op>( static_cast<unsigned int>( lhs ), rhs );
+    }
+    template<Operator Op> bool compare( int lhs, unsigned long rhs ) {
+        return applyEvaluator<Op>( static_cast<unsigned int>( lhs ), rhs );
+    }
+    template<Operator Op> bool compare( int lhs, unsigned char rhs ) {
+        return applyEvaluator<Op>( static_cast<unsigned int>( lhs ), rhs );
+    }
+
+    // long to unsigned X
+    template<Operator Op> bool compare( long lhs, unsigned int rhs ) {
+        return applyEvaluator<Op>( static_cast<unsigned long>( lhs ), rhs );
+    }
+    template<Operator Op> bool compare( long lhs, unsigned long rhs ) {
+        return applyEvaluator<Op>( static_cast<unsigned long>( lhs ), rhs );
+    }
+    template<Operator Op> bool compare( long lhs, unsigned char rhs ) {
+        return applyEvaluator<Op>( static_cast<unsigned long>( lhs ), rhs );
+    }
+
+    // pointer to long (when comparing against NULL)
+    template<Operator Op, typename T> bool compare( long lhs, T* rhs ) {
+        return Evaluator<T*, T*, Op>::evaluate( reinterpret_cast<T*>( lhs ), rhs );
+    }
+    template<Operator Op, typename T> bool compare( T* lhs, long rhs ) {
+        return Evaluator<T*, T*, Op>::evaluate( lhs, reinterpret_cast<T*>( rhs ) );
+    }
+
+    // pointer to int (when comparing against NULL)
+    template<Operator Op, typename T> bool compare( int lhs, T* rhs ) {
+        return Evaluator<T*, T*, Op>::evaluate( reinterpret_cast<T*>( lhs ), rhs );
+    }
+    template<Operator Op, typename T> bool compare( T* lhs, int rhs ) {
+        return Evaluator<T*, T*, Op>::evaluate( lhs, reinterpret_cast<T*>( rhs ) );
+    }
+
+#ifdef CATCH_CONFIG_CPP11_LONG_LONG
+    // long long to unsigned X
+    template<Operator Op> bool compare( long long lhs, unsigned int rhs ) {
+        return applyEvaluator<Op>( static_cast<unsigned long>( lhs ), rhs );
+    }
+    template<Operator Op> bool compare( long long lhs, unsigned long rhs ) {
+        return applyEvaluator<Op>( static_cast<unsigned long>( lhs ), rhs );
+    }
+    template<Operator Op> bool compare( long long lhs, unsigned long long rhs ) {
+        return applyEvaluator<Op>( static_cast<unsigned long>( lhs ), rhs );
+    }
+    template<Operator Op> bool compare( long long lhs, unsigned char rhs ) {
+        return applyEvaluator<Op>( static_cast<unsigned long>( lhs ), rhs );
+    }
+
+    // unsigned long long to X
+    template<Operator Op> bool compare( unsigned long long lhs, int rhs ) {
+        return applyEvaluator<Op>( static_cast<long>( lhs ), rhs );
+    }
+    template<Operator Op> bool compare( unsigned long long lhs, long rhs ) {
+        return applyEvaluator<Op>( static_cast<long>( lhs ), rhs );
+    }
+    template<Operator Op> bool compare( unsigned long long lhs, long long rhs ) {
+        return applyEvaluator<Op>( static_cast<long>( lhs ), rhs );
+    }
+    template<Operator Op> bool compare( unsigned long long lhs, char rhs ) {
+        return applyEvaluator<Op>( static_cast<long>( lhs ), rhs );
+    }
+
+    // pointer to long long (when comparing against NULL)
+    template<Operator Op, typename T> bool compare( long long lhs, T* rhs ) {
+        return Evaluator<T*, T*, Op>::evaluate( reinterpret_cast<T*>( lhs ), rhs );
+    }
+    template<Operator Op, typename T> bool compare( T* lhs, long long rhs ) {
+        return Evaluator<T*, T*, Op>::evaluate( lhs, reinterpret_cast<T*>( rhs ) );
+    }
+#endif // CATCH_CONFIG_CPP11_LONG_LONG
+
+#ifdef CATCH_CONFIG_CPP11_NULLPTR
+    // pointer to nullptr_t (when comparing against nullptr)
+    template<Operator Op, typename T> bool compare( std::nullptr_t, T* rhs ) {
+        return Evaluator<T*, T*, Op>::evaluate( nullptr, rhs );
+    }
+    template<Operator Op, typename T> bool compare( T* lhs, std::nullptr_t ) {
+        return Evaluator<T*, T*, Op>::evaluate( lhs, nullptr );
+    }
+#endif // CATCH_CONFIG_CPP11_NULLPTR
+
+} // end of namespace Internal
+} // end of namespace Catch
+
+#ifdef _MSC_VER
+#pragma warning(pop)
+#endif
+
+// #included from: catch_tostring.h
+#define TWOBLUECUBES_CATCH_TOSTRING_H_INCLUDED
+
+#include <sstream>
+#include <iomanip>
+#include <limits>
+#include <vector>
+#include <cstddef>
+
+#ifdef __OBJC__
+// #included from: catch_objc_arc.hpp
+#define TWOBLUECUBES_CATCH_OBJC_ARC_HPP_INCLUDED
+
+#import <Foundation/Foundation.h>
+
+#ifdef __has_feature
+#define CATCH_ARC_ENABLED __has_feature(objc_arc)
+#else
+#define CATCH_ARC_ENABLED 0
+#endif
+
+void arcSafeRelease( NSObject* obj );
+id performOptionalSelector( id obj, SEL sel );
+
+#if !CATCH_ARC_ENABLED
+inline void arcSafeRelease( NSObject* obj ) {
+    [obj release];
+}
+inline id performOptionalSelector( id obj, SEL sel ) {
+    if( [obj respondsToSelector: sel] )
+        return [obj performSelector: sel];
+    return nil;
+}
+#define CATCH_UNSAFE_UNRETAINED
+#define CATCH_ARC_STRONG
+#else
+inline void arcSafeRelease( NSObject* ){}
+inline id performOptionalSelector( id obj, SEL sel ) {
+#ifdef __clang__
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
+#endif
+    if( [obj respondsToSelector: sel] )
+        return [obj performSelector: sel];
+#ifdef __clang__
+#pragma clang diagnostic pop
+#endif
+    return nil;
+}
+#define CATCH_UNSAFE_UNRETAINED __unsafe_unretained
+#define CATCH_ARC_STRONG __strong
+#endif
+
+#endif
+
+#ifdef CATCH_CONFIG_CPP11_TUPLE
+#include <tuple>
+#endif
+
+#ifdef CATCH_CONFIG_CPP11_IS_ENUM
+#include <type_traits>
+#endif
+
+namespace Catch {
+
+// Why we're here.
+template<typename T>
+std::string toString( T const& value );
+
+// Built in overloads
+
+std::string toString( std::string const& value );
+std::string toString( std::wstring const& value );
+std::string toString( const char* const value );
+std::string toString( char* const value );
+std::string toString( const wchar_t* const value );
+std::string toString( wchar_t* const value );
+std::string toString( int value );
+std::string toString( unsigned long value );
+std::string toString( unsigned int value );
+std::string toString( const double value );
+std::string toString( const float value );
+std::string toString( bool value );
+std::string toString( char value );
+std::string toString( signed char value );
+std::string toString( unsigned char value );
+
+#ifdef CATCH_CONFIG_CPP11_LONG_LONG
+std::string toString( long long value );
+std::string toString( unsigned long long value );
+#endif
+
+#ifdef CATCH_CONFIG_CPP11_NULLPTR
+std::string toString( std::nullptr_t );
+#endif
+
+#ifdef __OBJC__
+    std::string toString( NSString const * const& nsstring );
+    std::string toString( NSString * CATCH_ARC_STRONG const& nsstring );
+    std::string toString( NSObject* const& nsObject );
+#endif
+
+namespace Detail {
+
+    extern const std::string unprintableString;
+
+    struct BorgType {
+        template<typename T> BorgType( T const& );
+    };
+
+    struct TrueType { char sizer[1]; };
+    struct FalseType { char sizer[2]; };
+
+    TrueType& testStreamable( std::ostream& );
+    FalseType testStreamable( FalseType );
+
+    FalseType operator<<( std::ostream const&, BorgType const& );
+
+    template<typename T>
+    struct IsStreamInsertable {
+        static std::ostream &s;
+        static T  const&t;
+        enum { value = sizeof( testStreamable(s << t) ) == sizeof( TrueType ) };
+    };
+
+#if defined(CATCH_CONFIG_CPP11_IS_ENUM)
+    template<typename T,
+             bool IsEnum = std::is_enum<T>::value
+             >
+    struct EnumStringMaker
+    {
+        static std::string convert( T const& ) { return unprintableString; }
+    };
+
+    template<typename T>
+    struct EnumStringMaker<T,true>
+    {
+        static std::string convert( T const& v )
+        {
+            return ::Catch::toString(
+                static_cast<typename std::underlying_type<T>::type>(v)
+                );
+        }
+    };
+#endif
+    template<bool C>
+    struct StringMakerBase {
+#if defined(CATCH_CONFIG_CPP11_IS_ENUM)
+        template<typename T>
+        static std::string convert( T const& v )
+        {
+            return EnumStringMaker<T>::convert( v );
+        }
+#else
+        template<typename T>
+        static std::string convert( T const& ) { return unprintableString; }
+#endif
+    };
+
+    template<>
+    struct StringMakerBase<true> {
+        template<typename T>
+        static std::string convert( T const& _value ) {
+            std::ostringstream oss;
+            oss << _value;
+            return oss.str();
+        }
+    };
+
+    std::string rawMemoryToString( const void *object, std::size_t size );
+
+    template<typename T>
+    inline std::string rawMemoryToString( const T& object ) {
+      return rawMemoryToString( &object, sizeof(object) );
+    }
+
+} // end namespace Detail
+
+template<typename T>
+struct StringMaker :
+    Detail::StringMakerBase<Detail::IsStreamInsertable<T>::value> {};
+
+template<typename T>
+struct StringMaker<T*> {
+    template<typename U>
+    static std::string convert( U* p ) {
+        if( !p )
+            return "NULL";
+        else
+            return Detail::rawMemoryToString( p );
+    }
+};
+
+template<typename R, typename C>
+struct StringMaker<R C::*> {
+    static std::string convert( R C::* p ) {
+        if( !p )
+            return "NULL";
+        else
+            return Detail::rawMemoryToString( p );
+    }
+};
+
+namespace Detail {
+    template<typename InputIterator>
+    std::string rangeToString( InputIterator first, InputIterator last );
+}
+
+//template<typename T, typename Allocator>
+//struct StringMaker<std::vector<T, Allocator> > {
+//    static std::string convert( std::vector<T,Allocator> const& v ) {
+//        return Detail::rangeToString( v.begin(), v.end() );
+//    }
+//};
+
+template<typename T, typename Allocator>
+std::string toString( std::vector<T,Allocator> const& v ) {
+    return Detail::rangeToString( v.begin(), v.end() );
+}
+
+#ifdef CATCH_CONFIG_CPP11_TUPLE
+
+// toString for tuples
+namespace TupleDetail {
+  template<
+      typename Tuple,
+      std::size_t N = 0,
+      bool = (N < std::tuple_size<Tuple>::value)
+      >
+  struct ElementPrinter {
+      static void print( const Tuple& tuple, std::ostream& os )
+      {
+          os << ( N ? ", " : " " )
+             << Catch::toString(std::get<N>(tuple));
+          ElementPrinter<Tuple,N+1>::print(tuple,os);
+      }
+  };
+
+  template<
+      typename Tuple,
+      std::size_t N
+      >
+  struct ElementPrinter<Tuple,N,false> {
+      static void print( const Tuple&, std::ostream& ) {}
+  };
+
+}
+
+template<typename ...Types>
+struct StringMaker<std::tuple<Types...>> {
+
+    static std::string convert( const std::tuple<Types...>& tuple )
+    {
+        std::ostringstream os;
+        os << '{';
+        TupleDetail::ElementPrinter<std::tuple<Types...>>::print( tuple, os );
+        os << " }";
+        return os.str();
+    }
+};
+#endif // CATCH_CONFIG_CPP11_TUPLE
+
+namespace Detail {
+    template<typename T>
+    std::string makeString( T const& value ) {
+        return StringMaker<T>::convert( value );
+    }
+} // end namespace Detail
+
+/// \brief converts any type to a string
+///
+/// The default template forwards on to ostringstream - except when an
+/// ostringstream overload does not exist - in which case it attempts to detect
+/// that and writes {?}.
+/// Overload (not specialise) this template for custom typs that you don't want
+/// to provide an ostream overload for.
+template<typename T>
+std::string toString( T const& value ) {
+    return StringMaker<T>::convert( value );
+}
+
+    namespace Detail {
+    template<typename InputIterator>
+    std::string rangeToString( InputIterator first, InputIterator last ) {
+        std::ostringstream oss;
+        oss << "{ ";
+        if( first != last ) {
+            oss << Catch::toString( *first );
+            for( ++first ; first != last ; ++first )
+                oss << ", " << Catch::toString( *first );
+        }
+        oss << " }";
+        return oss.str();
+    }
+}
+
+} // end namespace Catch
+
+namespace Catch {
+
+// Wraps the LHS of an expression and captures the operator and RHS (if any) -
+// wrapping them all in a ResultBuilder object
+template<typename T>
+class ExpressionLhs {
+    ExpressionLhs& operator = ( ExpressionLhs const& );
+#  ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS
+    ExpressionLhs& operator = ( ExpressionLhs && ) = delete;
+#  endif
+
+public:
+    ExpressionLhs( ResultBuilder& rb, T lhs ) : m_rb( rb ), m_lhs( lhs ) {}
+#  ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS
+    ExpressionLhs( ExpressionLhs const& ) = default;
+    ExpressionLhs( ExpressionLhs && )     = default;
+#  endif
+
+    template<typename RhsT>
+    ResultBuilder& operator == ( RhsT const& rhs ) {
+        return captureExpression<Internal::IsEqualTo>( rhs );
+    }
+
+    template<typename RhsT>
+    ResultBuilder& operator != ( RhsT const& rhs ) {
+        return captureExpression<Internal::IsNotEqualTo>( rhs );
+    }
+
+    template<typename RhsT>
+    ResultBuilder& operator < ( RhsT const& rhs ) {
+        return captureExpression<Internal::IsLessThan>( rhs );
+    }
+
+    template<typename RhsT>
+    ResultBuilder& operator > ( RhsT const& rhs ) {
+        return captureExpression<Internal::IsGreaterThan>( rhs );
+    }
+
+    template<typename RhsT>
+    ResultBuilder& operator <= ( RhsT const& rhs ) {
+        return captureExpression<Internal::IsLessThanOrEqualTo>( rhs );
+    }
+
+    template<typename RhsT>
+    ResultBuilder& operator >= ( RhsT const& rhs ) {
+        return captureExpression<Internal::IsGreaterThanOrEqualTo>( rhs );
+    }
+
+    ResultBuilder& operator == ( bool rhs ) {
+        return captureExpression<Internal::IsEqualTo>( rhs );
+    }
+
+    ResultBuilder& operator != ( bool rhs ) {
+        return captureExpression<Internal::IsNotEqualTo>( rhs );
+    }
+
+    void endExpression() {
+        bool value = m_lhs ? true : false;
+        m_rb
+            .setLhs( Catch::toString( value ) )
+            .setResultType( value )
+            .endExpression();
+    }
+
+    // Only simple binary expressions are allowed on the LHS.
+    // If more complex compositions are required then place the sub expression in parentheses
+    template<typename RhsT> STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator + ( RhsT const& );
+    template<typename RhsT> STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator - ( RhsT const& );
+    template<typename RhsT> STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator / ( RhsT const& );
+    template<typename RhsT> STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator * ( RhsT const& );
+    template<typename RhsT> STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator && ( RhsT const& );
+    template<typename RhsT> STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator || ( RhsT const& );
+
+private:
+    template<Internal::Operator Op, typename RhsT>
+    ResultBuilder& captureExpression( RhsT const& rhs ) {
+        return m_rb
+            .setResultType( Internal::compare<Op>( m_lhs, rhs ) )
+            .setLhs( Catch::toString( m_lhs ) )
+            .setRhs( Catch::toString( rhs ) )
+            .setOp( Internal::OperatorTraits<Op>::getName() );
+    }
+
+private:
+    ResultBuilder& m_rb;
+    T m_lhs;
+};
+
+} // end namespace Catch
+
+
+namespace Catch {
+
+    template<typename T>
+    inline ExpressionLhs<T const&> ResultBuilder::operator <= ( T const& operand ) {
+        return ExpressionLhs<T const&>( *this, operand );
+    }
+
+    inline ExpressionLhs<bool> ResultBuilder::operator <= ( bool value ) {
+        return ExpressionLhs<bool>( *this, value );
+    }
+
+} // namespace Catch
+
+// #included from: catch_message.h
+#define TWOBLUECUBES_CATCH_MESSAGE_H_INCLUDED
+
+#include <string>
+
+namespace Catch {
+
+    struct MessageInfo {
+        MessageInfo(    std::string const& _macroName,
+                        SourceLineInfo const& _lineInfo,
+                        ResultWas::OfType _type );
+
+        std::string macroName;
+        SourceLineInfo lineInfo;
+        ResultWas::OfType type;
+        std::string message;
+        unsigned int sequence;
+
+        bool operator == ( MessageInfo const& other ) const {
+            return sequence == other.sequence;
+        }
+        bool operator < ( MessageInfo const& other ) const {
+            return sequence < other.sequence;
+        }
+    private:
+        static unsigned int globalCount;
+    };
+
+    struct MessageBuilder {
+        MessageBuilder( std::string const& macroName,
+                        SourceLineInfo const& lineInfo,
+                        ResultWas::OfType type )
+        : m_info( macroName, lineInfo, type )
+        {}
+
+        template<typename T>
+        MessageBuilder& operator << ( T const& value ) {
+            m_stream << value;
+            return *this;
+        }
+
+        MessageInfo m_info;
+        std::ostringstream m_stream;
+    };
+
+    class ScopedMessage {
+    public:
+        ScopedMessage( MessageBuilder const& builder );
+        ScopedMessage( ScopedMessage const& other );
+        ~ScopedMessage();
+
+        MessageInfo m_info;
+    };
+
+} // end namespace Catch
+
+// #included from: catch_interfaces_capture.h
+#define TWOBLUECUBES_CATCH_INTERFACES_CAPTURE_H_INCLUDED
+
+#include <string>
+
+namespace Catch {
+
+    class TestCase;
+    class AssertionResult;
+    struct AssertionInfo;
+    struct SectionInfo;
+    struct SectionEndInfo;
+    struct MessageInfo;
+    class ScopedMessageBuilder;
+    struct Counts;
+
+    struct IResultCapture {
+
+        virtual ~IResultCapture();
+
+        virtual void assertionEnded( AssertionResult const& result ) = 0;
+        virtual bool sectionStarted(    SectionInfo const& sectionInfo,
+                                        Counts& assertions ) = 0;
+        virtual void sectionEnded( SectionEndInfo const& endInfo ) = 0;
+        virtual void sectionEndedEarly( SectionEndInfo const& endInfo ) = 0;
+        virtual void pushScopedMessage( MessageInfo const& message ) = 0;
+        virtual void popScopedMessage( MessageInfo const& message ) = 0;
+
+        virtual std::string getCurrentTestName() const = 0;
+        virtual const AssertionResult* getLastResult() const = 0;
+
+        virtual void handleFatalErrorCondition( std::string const& message ) = 0;
+    };
+
+    IResultCapture& getResultCapture();
+}
+
+// #included from: catch_debugger.h
+#define TWOBLUECUBES_CATCH_DEBUGGER_H_INCLUDED
+
+// #included from: catch_platform.h
+#define TWOBLUECUBES_CATCH_PLATFORM_H_INCLUDED
+
+#if defined(__MAC_OS_X_VERSION_MIN_REQUIRED)
+#  define CATCH_PLATFORM_MAC
+#elif  defined(__IPHONE_OS_VERSION_MIN_REQUIRED)
+#  define CATCH_PLATFORM_IPHONE
+#elif defined(linux) || defined(__linux) || defined(__linux__)
+#  define CATCH_PLATFORM_LINUX
+#elif defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER)
+#  define CATCH_PLATFORM_WINDOWS
+#  if !defined(NOMINMAX) && !defined(CATCH_CONFIG_NO_NOMINMAX)
+#    define CATCH_DEFINES_NOMINMAX
+#  endif
+#  if !defined(WIN32_LEAN_AND_MEAN) && !defined(CATCH_CONFIG_NO_WIN32_LEAN_AND_MEAN)
+#    define CATCH_DEFINES_WIN32_LEAN_AND_MEAN
+#  endif
+#endif
+
+#include <string>
+
+namespace Catch{
+
+    bool isDebuggerActive();
+    void writeToDebugConsole( std::string const& text );
+}
+
+#ifdef CATCH_PLATFORM_MAC
+
+    // The following code snippet based on:
+    // http://cocoawithlove.com/2008/03/break-into-debugger.html
+    #ifdef DEBUG
+        #if defined(__ppc64__) || defined(__ppc__)
+            #define CATCH_TRAP() \
+                    __asm__("li r0, 20\nsc\nnop\nli r0, 37\nli r4, 2\nsc\nnop\n" \
+                    : : : "memory","r0","r3","r4" )
+        #else
+            #define CATCH_TRAP() _asm__("int $3\n" : : )
+        #endif
+    #endif
+
+#elif defined(CATCH_PLATFORM_LINUX)
+    // If we can use inline assembler, do it because this allows us to break
+    // directly at the location of the failing check instead of breaking inside
+    // raise() called from it, i.e. one stack frame below.
+    #if defined(__GNUC__) && (defined(__i386) || defined(__x86_64))
+        #define CATCH_TRAP() asm volatile ("int $3")
+    #else // Fall back to the generic way.
+        #include <signal.h>
+
+        #define CATCH_TRAP() raise(SIGTRAP)
+    #endif
+#elif defined(_MSC_VER)
+    #define CATCH_TRAP() __debugbreak()
+#elif defined(__MINGW32__)
+    extern "C" __declspec(dllimport) void __stdcall DebugBreak();
+    #define CATCH_TRAP() DebugBreak()
+#endif
+
+#ifdef CATCH_TRAP
+    #define CATCH_BREAK_INTO_DEBUGGER() if( Catch::isDebuggerActive() ) { CATCH_TRAP(); }
+#else
+    #define CATCH_BREAK_INTO_DEBUGGER() Catch::alwaysTrue();
+#endif
+
+// #included from: catch_interfaces_runner.h
+#define TWOBLUECUBES_CATCH_INTERFACES_RUNNER_H_INCLUDED
+
+namespace Catch {
+    class TestCase;
+
+    struct IRunner {
+        virtual ~IRunner();
+        virtual bool aborting() const = 0;
+    };
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// In the event of a failure works out if the debugger needs to be invoked
+// and/or an exception thrown and takes appropriate action.
+// This needs to be done as a macro so the debugger will stop in the user
+// source code rather than in Catch library code
+#define INTERNAL_CATCH_REACT( resultBuilder ) \
+    if( resultBuilder.shouldDebugBreak() ) CATCH_BREAK_INTO_DEBUGGER(); \
+    resultBuilder.react();
+
+///////////////////////////////////////////////////////////////////////////////
+#define INTERNAL_CATCH_TEST( expr, resultDisposition, macroName ) \
+    do { \
+        Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #expr, resultDisposition ); \
+        try { \
+            CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS \
+            ( __catchResult <= expr ).endExpression(); \
+        } \
+        catch( ... ) { \
+            __catchResult.useActiveException( Catch::ResultDisposition::Normal ); \
+        } \
+        INTERNAL_CATCH_REACT( __catchResult ) \
+    } while( Catch::alwaysFalse( sizeof(expr) ) ) // expr here is never evaluated at runtime but it forces the compiler to give it a look
+
+///////////////////////////////////////////////////////////////////////////////
+#define INTERNAL_CATCH_IF( expr, resultDisposition, macroName ) \
+    INTERNAL_CATCH_TEST( expr, resultDisposition, macroName ); \
+    if( Catch::getResultCapture().getLastResult()->succeeded() )
+
+///////////////////////////////////////////////////////////////////////////////
+#define INTERNAL_CATCH_ELSE( expr, resultDisposition, macroName ) \
+    INTERNAL_CATCH_TEST( expr, resultDisposition, macroName ); \
+    if( !Catch::getResultCapture().getLastResult()->succeeded() )
+
+///////////////////////////////////////////////////////////////////////////////
+#define INTERNAL_CATCH_NO_THROW( expr, resultDisposition, macroName ) \
+    do { \
+        Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #expr, resultDisposition ); \
+        try { \
+            expr; \
+            __catchResult.captureResult( Catch::ResultWas::Ok ); \
+        } \
+        catch( ... ) { \
+            __catchResult.useActiveException( resultDisposition ); \
+        } \
+        INTERNAL_CATCH_REACT( __catchResult ) \
+    } while( Catch::alwaysFalse() )
+
+///////////////////////////////////////////////////////////////////////////////
+#define INTERNAL_CATCH_THROWS( expr, resultDisposition, matcher, macroName ) \
+    do { \
+        Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #expr, resultDisposition, #matcher ); \
+        if( __catchResult.allowThrows() ) \
+            try { \
+                expr; \
+                __catchResult.captureResult( Catch::ResultWas::DidntThrowException ); \
+            } \
+            catch( ... ) { \
+                __catchResult.captureExpectedException( matcher ); \
+            } \
+        else \
+            __catchResult.captureResult( Catch::ResultWas::Ok ); \
+        INTERNAL_CATCH_REACT( __catchResult ) \
+    } while( Catch::alwaysFalse() )
+
+///////////////////////////////////////////////////////////////////////////////
+#define INTERNAL_CATCH_THROWS_AS( expr, exceptionType, resultDisposition, macroName ) \
+    do { \
+        Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #expr, resultDisposition ); \
+        if( __catchResult.allowThrows() ) \
+            try { \
+                expr; \
+                __catchResult.captureResult( Catch::ResultWas::DidntThrowException ); \
+            } \
+            catch( exceptionType ) { \
+                __catchResult.captureResult( Catch::ResultWas::Ok ); \
+            } \
+            catch( ... ) { \
+                __catchResult.useActiveException( resultDisposition ); \
+            } \
+        else \
+            __catchResult.captureResult( Catch::ResultWas::Ok ); \
+        INTERNAL_CATCH_REACT( __catchResult ) \
+    } while( Catch::alwaysFalse() )
+
+///////////////////////////////////////////////////////////////////////////////
+#ifdef CATCH_CONFIG_VARIADIC_MACROS
+    #define INTERNAL_CATCH_MSG( messageType, resultDisposition, macroName, ... ) \
+        do { \
+            Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, "", resultDisposition ); \
+            __catchResult << __VA_ARGS__ + ::Catch::StreamEndStop(); \
+            __catchResult.captureResult( messageType ); \
+            INTERNAL_CATCH_REACT( __catchResult ) \
+        } while( Catch::alwaysFalse() )
+#else
+    #define INTERNAL_CATCH_MSG( messageType, resultDisposition, macroName, log ) \
+        do { \
+            Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, "", resultDisposition ); \
+            __catchResult << log + ::Catch::StreamEndStop(); \
+            __catchResult.captureResult( messageType ); \
+            INTERNAL_CATCH_REACT( __catchResult ) \
+        } while( Catch::alwaysFalse() )
+#endif
+
+///////////////////////////////////////////////////////////////////////////////
+#define INTERNAL_CATCH_INFO( log, macroName ) \
+    Catch::ScopedMessage INTERNAL_CATCH_UNIQUE_NAME( scopedMessage ) = Catch::MessageBuilder( macroName, CATCH_INTERNAL_LINEINFO, Catch::ResultWas::Info ) << log;
+
+///////////////////////////////////////////////////////////////////////////////
+#define INTERNAL_CHECK_THAT( arg, matcher, resultDisposition, macroName ) \
+    do { \
+        Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #arg ", " #matcher, resultDisposition ); \
+        try { \
+            std::string matcherAsString = (matcher).toString(); \
+            __catchResult \
+                .setLhs( Catch::toString( arg ) ) \
+                .setRhs( matcherAsString == Catch::Detail::unprintableString ? #matcher : matcherAsString ) \
+                .setOp( "matches" ) \
+                .setResultType( (matcher).match( arg ) ); \
+            __catchResult.captureExpression(); \
+        } catch( ... ) { \
+            __catchResult.useActiveException( resultDisposition | Catch::ResultDisposition::ContinueOnFailure ); \
+        } \
+        INTERNAL_CATCH_REACT( __catchResult ) \
+    } while( Catch::alwaysFalse() )
+
+// #included from: internal/catch_section.h
+#define TWOBLUECUBES_CATCH_SECTION_H_INCLUDED
+
+// #included from: catch_section_info.h
+#define TWOBLUECUBES_CATCH_SECTION_INFO_H_INCLUDED
+
+// #included from: catch_totals.hpp
+#define TWOBLUECUBES_CATCH_TOTALS_HPP_INCLUDED
+
+#include <cstddef>
+
+namespace Catch {
+
+    struct Counts {
+        Counts() : passed( 0 ), failed( 0 ), failedButOk( 0 ) {}
+
+        Counts operator - ( Counts const& other ) const {
+            Counts diff;
+            diff.passed = passed - other.passed;
+            diff.failed = failed - other.failed;
+            diff.failedButOk = failedButOk - other.failedButOk;
+            return diff;
+        }
+        Counts& operator += ( Counts const& other ) {
+            passed += other.passed;
+            failed += other.failed;
+            failedButOk += other.failedButOk;
+            return *this;
+        }
+
+        std::size_t total() const {
+            return passed + failed + failedButOk;
+        }
+        bool allPassed() const {
+            return failed == 0 && failedButOk == 0;
+        }
+        bool allOk() const {
+            return failed == 0;
+        }
+
+        std::size_t passed;
+        std::size_t failed;
+        std::size_t failedButOk;
+    };
+
+    struct Totals {
+
+        Totals operator - ( Totals const& other ) const {
+            Totals diff;
+            diff.assertions = assertions - other.assertions;
+            diff.testCases = testCases - other.testCases;
+            return diff;
+        }
+
+        Totals delta( Totals const& prevTotals ) const {
+            Totals diff = *this - prevTotals;
+            if( diff.assertions.failed > 0 )
+                ++diff.testCases.failed;
+            else if( diff.assertions.failedButOk > 0 )
+                ++diff.testCases.failedButOk;
+            else
+                ++diff.testCases.passed;
+            return diff;
+        }
+
+        Totals& operator += ( Totals const& other ) {
+            assertions += other.assertions;
+            testCases += other.testCases;
+            return *this;
+        }
+
+        Counts assertions;
+        Counts testCases;
+    };
+}
+
+namespace Catch {
+
+    struct SectionInfo {
+        SectionInfo
+            (   SourceLineInfo const& _lineInfo,
+                std::string const& _name,
+                std::string const& _description = std::string() );
+
+        std::string name;
+        std::string description;
+        SourceLineInfo lineInfo;
+    };
+
+    struct SectionEndInfo {
+        SectionEndInfo( SectionInfo const& _sectionInfo, Counts const& _prevAssertions, double _durationInSeconds )
+        : sectionInfo( _sectionInfo ), prevAssertions( _prevAssertions ), durationInSeconds( _durationInSeconds )
+        {}
+
+        SectionInfo sectionInfo;
+        Counts prevAssertions;
+        double durationInSeconds;
+    };
+
+} // end namespace Catch
+
+// #included from: catch_timer.h
+#define TWOBLUECUBES_CATCH_TIMER_H_INCLUDED
+
+#ifdef CATCH_PLATFORM_WINDOWS
+typedef unsigned long long uint64_t;
+#else
+#include <stdint.h>
+#endif
+
+namespace Catch {
+
+    class Timer {
+    public:
+        Timer() : m_ticks( 0 ) {}
+        void start();
+        unsigned int getElapsedMicroseconds() const;
+        unsigned int getElapsedMilliseconds() const;
+        double getElapsedSeconds() const;
+
+    private:
+        uint64_t m_ticks;
+    };
+
+} // namespace Catch
+
+#include <string>
+
+namespace Catch {
+
+    class Section : NonCopyable {
+    public:
+        Section( SectionInfo const& info );
+        ~Section();
+
+        // This indicates whether the section should be executed or not
+        operator bool() const;
+
+    private:
+        SectionInfo m_info;
+
+        std::string m_name;
+        Counts m_assertions;
+        bool m_sectionIncluded;
+        Timer m_timer;
+    };
+
+} // end namespace Catch
+
+#ifdef CATCH_CONFIG_VARIADIC_MACROS
+    #define INTERNAL_CATCH_SECTION( ... ) \
+        if( Catch::Section const& INTERNAL_CATCH_UNIQUE_NAME( catch_internal_Section ) = Catch::SectionInfo( CATCH_INTERNAL_LINEINFO, __VA_ARGS__ ) )
+#else
+    #define INTERNAL_CATCH_SECTION( name, desc ) \
+        if( Catch::Section const& INTERNAL_CATCH_UNIQUE_NAME( catch_internal_Section ) = Catch::SectionInfo( CATCH_INTERNAL_LINEINFO, name, desc ) )
+#endif
+
+// #included from: internal/catch_generators.hpp
+#define TWOBLUECUBES_CATCH_GENERATORS_HPP_INCLUDED
+
+#include <iterator>
+#include <vector>
+#include <string>
+#include <stdlib.h>
+
+namespace Catch {
+
+template<typename T>
+struct IGenerator {
+    virtual ~IGenerator() {}
+    virtual T getValue( std::size_t index ) const = 0;
+    virtual std::size_t size () const = 0;
+};
+
+template<typename T>
+class BetweenGenerator : public IGenerator<T> {
+public:
+    BetweenGenerator( T from, T to ) : m_from( from ), m_to( to ){}
+
+    virtual T getValue( std::size_t index ) const {
+        return m_from+static_cast<int>( index );
+    }
+
+    virtual std::size_t size() const {
+        return static_cast<std::size_t>( 1+m_to-m_from );
+    }
+
+private:
+
+    T m_from;
+    T m_to;
+};
+
+template<typename T>
+class ValuesGenerator : public IGenerator<T> {
+public:
+    ValuesGenerator(){}
+
+    void add( T value ) {
+        m_values.push_back( value );
+    }
+
+    virtual T getValue( std::size_t index ) const {
+        return m_values[index];
+    }
+
+    virtual std::size_t size() const {
+        return m_values.size();
+    }
+
+private:
+    std::vector<T> m_values;
+};
+
+template<typename T>
+class CompositeGenerator {
+public:
+    CompositeGenerator() : m_totalSize( 0 ) {}
+
+    // *** Move semantics, similar to auto_ptr ***
+    CompositeGenerator( CompositeGenerator& other )
+    :   m_fileInfo( other.m_fileInfo ),
+        m_totalSize( 0 )
+    {
+        move( other );
+    }
+
+    CompositeGenerator& setFileInfo( const char* fileInfo ) {
+        m_fileInfo = fileInfo;
+        return *this;
+    }
+
+    ~CompositeGenerator() {
+        deleteAll( m_composed );
+    }
+
+    operator T () const {
+        size_t overallIndex = getCurrentContext().getGeneratorIndex( m_fileInfo, m_totalSize );
+
+        typename std::vector<const IGenerator<T>*>::const_iterator it = m_composed.begin();
+        typename std::vector<const IGenerator<T>*>::const_iterator itEnd = m_composed.end();
+        for( size_t index = 0; it != itEnd; ++it )
+        {
+            const IGenerator<T>* generator = *it;
+            if( overallIndex >= index && overallIndex < index + generator->size() )
+            {
+                return generator->getValue( overallIndex-index );
+            }
+            index += generator->size();
+        }
+        CATCH_INTERNAL_ERROR( "Indexed past end of generated range" );
+        return T(); // Suppress spurious "not all control paths return a value" warning in Visual Studio - if you know how to fix this please do so
+    }
+
+    void add( const IGenerator<T>* generator ) {
+        m_totalSize += generator->size();
+        m_composed.push_back( generator );
+    }
+
+    CompositeGenerator& then( CompositeGenerator& other ) {
+        move( other );
+        return *this;
+    }
+
+    CompositeGenerator& then( T value ) {
+        ValuesGenerator<T>* valuesGen = new ValuesGenerator<T>();
+        valuesGen->add( value );
+        add( valuesGen );
+        return *this;
+    }
+
+private:
+
+    void move( CompositeGenerator& other ) {
+        std::copy( other.m_composed.begin(), other.m_composed.end(), std::back_inserter( m_composed ) );
+        m_totalSize += other.m_totalSize;
+        other.m_composed.clear();
+    }
+
+    std::vector<const IGenerator<T>*> m_composed;
+    std::string m_fileInfo;
+    size_t m_totalSize;
+};
+
+namespace Generators
+{
+    template<typename T>
+    CompositeGenerator<T> between( T from, T to ) {
+        CompositeGenerator<T> generators;
+        generators.add( new BetweenGenerator<T>( from, to ) );
+        return generators;
+    }
+
+    template<typename T>
+    CompositeGenerator<T> values( T val1, T val2 ) {
+        CompositeGenerator<T> generators;
+        ValuesGenerator<T>* valuesGen = new ValuesGenerator<T>();
+        valuesGen->add( val1 );
+        valuesGen->add( val2 );
+        generators.add( valuesGen );
+        return generators;
+    }
+
+    template<typename T>
+    CompositeGenerator<T> values( T val1, T val2, T val3 ){
+        CompositeGenerator<T> generators;
+        ValuesGenerator<T>* valuesGen = new ValuesGenerator<T>();
+        valuesGen->add( val1 );
+        valuesGen->add( val2 );
+        valuesGen->add( val3 );
+        generators.add( valuesGen );
+        return generators;
+    }
+
+    template<typename T>
+    CompositeGenerator<T> values( T val1, T val2, T val3, T val4 ) {
+        CompositeGenerator<T> generators;
+        ValuesGenerator<T>* valuesGen = new ValuesGenerator<T>();
+        valuesGen->add( val1 );
+        valuesGen->add( val2 );
+        valuesGen->add( val3 );
+        valuesGen->add( val4 );
+        generators.add( valuesGen );
+        return generators;
+    }
+
+} // end namespace Generators
+
+using namespace Generators;
+
+} // end namespace Catch
+
+#define INTERNAL_CATCH_LINESTR2( line ) #line
+#define INTERNAL_CATCH_LINESTR( line ) INTERNAL_CATCH_LINESTR2( line )
+
+#define INTERNAL_CATCH_GENERATE( expr ) expr.setFileInfo( __FILE__ "(" INTERNAL_CATCH_LINESTR( __LINE__ ) ")" )
+
+// #included from: internal/catch_interfaces_exception.h
+#define TWOBLUECUBES_CATCH_INTERFACES_EXCEPTION_H_INCLUDED
+
+#include <string>
+#include <vector>
+
+// #included from: catch_interfaces_registry_hub.h
+#define TWOBLUECUBES_CATCH_INTERFACES_REGISTRY_HUB_H_INCLUDED
+
+#include <string>
+
+namespace Catch {
+
+    class TestCase;
+    struct ITestCaseRegistry;
+    struct IExceptionTranslatorRegistry;
+    struct IExceptionTranslator;
+    struct IReporterRegistry;
+    struct IReporterFactory;
+
+    struct IRegistryHub {
+        virtual ~IRegistryHub();
+
+        virtual IReporterRegistry const& getReporterRegistry() const = 0;
+        virtual ITestCaseRegistry const& getTestCaseRegistry() const = 0;
+        virtual IExceptionTranslatorRegistry& getExceptionTranslatorRegistry() = 0;
+    };
+
+    struct IMutableRegistryHub {
+        virtual ~IMutableRegistryHub();
+        virtual void registerReporter( std::string const& name, Ptr<IReporterFactory> const& factory ) = 0;
+        virtual void registerListener( Ptr<IReporterFactory> const& factory ) = 0;
+        virtual void registerTest( TestCase const& testInfo ) = 0;
+        virtual void registerTranslator( const IExceptionTranslator* translator ) = 0;
+    };
+
+    IRegistryHub& getRegistryHub();
+    IMutableRegistryHub& getMutableRegistryHub();
+    void cleanUp();
+    std::string translateActiveException();
+
+}
+
+namespace Catch {
+
+    typedef std::string(*exceptionTranslateFunction)();
+
+    struct IExceptionTranslator;
+    typedef std::vector<const IExceptionTranslator*> ExceptionTranslators;
+
+    struct IExceptionTranslator {
+        virtual ~IExceptionTranslator();
+        virtual std::string translate( ExceptionTranslators::const_iterator it, ExceptionTranslators::const_iterator itEnd ) const = 0;
+    };
+
+    struct IExceptionTranslatorRegistry {
+        virtual ~IExceptionTranslatorRegistry();
+
+        virtual std::string translateActiveException() const = 0;
+    };
+
+    class ExceptionTranslatorRegistrar {
+        template<typename T>
+        class ExceptionTranslator : public IExceptionTranslator {
+        public:
+
+            ExceptionTranslator( std::string(*translateFunction)( T& ) )
+            : m_translateFunction( translateFunction )
+            {}
+
+            virtual std::string translate( ExceptionTranslators::const_iterator it, ExceptionTranslators::const_iterator itEnd ) const CATCH_OVERRIDE {
+                try {
+                    if( it == itEnd )
+                        throw;
+                    else
+                        return (*it)->translate( it+1, itEnd );
+                }
+                catch( T& ex ) {
+                    return m_translateFunction( ex );
+                }
+            }
+
+        protected:
+            std::string(*m_translateFunction)( T& );
+        };
+
+    public:
+        template<typename T>
+        ExceptionTranslatorRegistrar( std::string(*translateFunction)( T& ) ) {
+            getMutableRegistryHub().registerTranslator
+                ( new ExceptionTranslator<T>( translateFunction ) );
+        }
+    };
+}
+
+///////////////////////////////////////////////////////////////////////////////
+#define INTERNAL_CATCH_TRANSLATE_EXCEPTION2( translatorName, signature ) \
+    static std::string translatorName( signature ); \
+    namespace{ Catch::ExceptionTranslatorRegistrar INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionRegistrar )( &translatorName ); }\
+    static std::string translatorName( signature )
+
+#define INTERNAL_CATCH_TRANSLATE_EXCEPTION( signature ) INTERNAL_CATCH_TRANSLATE_EXCEPTION2( INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionTranslator ), signature )
+
+// #included from: internal/catch_approx.hpp
+#define TWOBLUECUBES_CATCH_APPROX_HPP_INCLUDED
+
+#include <cmath>
+#include <limits>
+
+namespace Catch {
+namespace Detail {
+
+    class Approx {
+    public:
+        explicit Approx ( double value )
+        :   m_epsilon( std::numeric_limits<float>::epsilon()*100 ),
+            m_scale( 1.0 ),
+            m_value( value )
+        {}
+
+        Approx( Approx const& other )
+        :   m_epsilon( other.m_epsilon ),
+            m_scale( other.m_scale ),
+            m_value( other.m_value )
+        {}
+
+        static Approx custom() {
+            return Approx( 0 );
+        }
+
+        Approx operator()( double value ) {
+            Approx approx( value );
+            approx.epsilon( m_epsilon );
+            approx.scale( m_scale );
+            return approx;
+        }
+
+        friend bool operator == ( double lhs, Approx const& rhs ) {
+            // Thanks to Richard Harris for his help refining this formula
+            return fabs( lhs - rhs.m_value ) < rhs.m_epsilon * (rhs.m_scale + (std::max)( fabs(lhs), fabs(rhs.m_value) ) );
+        }
+
+        friend bool operator == ( Approx const& lhs, double rhs ) {
+            return operator==( rhs, lhs );
+        }
+
+        friend bool operator != ( double lhs, Approx const& rhs ) {
+            return !operator==( lhs, rhs );
+        }
+
+        friend bool operator != ( Approx const& lhs, double rhs ) {
+            return !operator==( rhs, lhs );
+        }
+
+        friend bool operator <= ( double lhs, Approx const& rhs )
+        {
+          return lhs < rhs.m_value || lhs == rhs;
+        }
+
+        friend bool operator <= ( Approx const& lhs, double rhs )
+        {
+          return lhs.m_value < rhs || lhs == rhs;
+        }
+
+        friend bool operator >= ( double lhs, Approx const& rhs )
+        {
+          return lhs > rhs.m_value || lhs == rhs;
+        }
+
+        friend bool operator >= ( Approx const& lhs, double rhs )
+        {
+          return lhs.m_value > rhs || lhs == rhs;
+        }
+
+        Approx& epsilon( double newEpsilon ) {
+            m_epsilon = newEpsilon;
+            return *this;
+        }
+
+        Approx& scale( double newScale ) {
+            m_scale = newScale;
+            return *this;
+        }
+
+        std::string toString() const {
+            std::ostringstream oss;
+            oss << "Approx( " << Catch::toString( m_value ) << " )";
+            return oss.str();
+        }
+
+    private:
+        double m_epsilon;
+        double m_scale;
+        double m_value;
+    };
+}
+
+template<>
+inline std::string toString<Detail::Approx>( Detail::Approx const& value ) {
+    return value.toString();
+}
+
+} // end namespace Catch
+
+// #included from: internal/catch_interfaces_tag_alias_registry.h
+#define TWOBLUECUBES_CATCH_INTERFACES_TAG_ALIAS_REGISTRY_H_INCLUDED
+
+// #included from: catch_tag_alias.h
+#define TWOBLUECUBES_CATCH_TAG_ALIAS_H_INCLUDED
+
+#include <string>
+
+namespace Catch {
+
+    struct TagAlias {
+        TagAlias( std::string _tag, SourceLineInfo _lineInfo ) : tag( _tag ), lineInfo( _lineInfo ) {}
+
+        std::string tag;
+        SourceLineInfo lineInfo;
+    };
+
+    struct RegistrarForTagAliases {
+        RegistrarForTagAliases( char const* alias, char const* tag, SourceLineInfo const& lineInfo );
+    };
+
+} // end namespace Catch
+
+#define CATCH_REGISTER_TAG_ALIAS( alias, spec ) namespace{ Catch::RegistrarForTagAliases INTERNAL_CATCH_UNIQUE_NAME( AutoRegisterTagAlias )( alias, spec, CATCH_INTERNAL_LINEINFO ); }
+// #included from: catch_option.hpp
+#define TWOBLUECUBES_CATCH_OPTION_HPP_INCLUDED
+
+namespace Catch {
+
+    // An optional type
+    template<typename T>
+    class Option {
+    public:
+        Option() : nullableValue( CATCH_NULL ) {}
+        Option( T const& _value )
+        : nullableValue( new( storage ) T( _value ) )
+        {}
+        Option( Option const& _other )
+        : nullableValue( _other ? new( storage ) T( *_other ) : CATCH_NULL )
+        {}
+
+        ~Option() {
+            reset();
+        }
+
+        Option& operator= ( Option const& _other ) {
+            if( &_other != this ) {
+                reset();
+                if( _other )
+                    nullableValue = new( storage ) T( *_other );
+            }
+            return *this;
+        }
+        Option& operator = ( T const& _value ) {
+            reset();
+            nullableValue = new( storage ) T( _value );
+            return *this;
+        }
+
+        void reset() {
+            if( nullableValue )
+                nullableValue->~T();
+            nullableValue = CATCH_NULL;
+        }
+
+        T& operator*() { return *nullableValue; }
+        T const& operator*() const { return *nullableValue; }
+        T* operator->() { return nullableValue; }
+        const T* operator->() const { return nullableValue; }
+
+        T valueOr( T const& defaultValue ) const {
+            return nullableValue ? *nullableValue : defaultValue;
+        }
+
+        bool some() const { return nullableValue != CATCH_NULL; }
+        bool none() const { return nullableValue == CATCH_NULL; }
+
+        bool operator !() const { return nullableValue == CATCH_NULL; }
+        operator SafeBool::type() const {
+            return SafeBool::makeSafe( some() );
+        }
+
+    private:
+        T* nullableValue;
+        char storage[sizeof(T)];
+    };
+
+} // end namespace Catch
+
+namespace Catch {
+
+    struct ITagAliasRegistry {
+        virtual ~ITagAliasRegistry();
+        virtual Option<TagAlias> find( std::string const& alias ) const = 0;
+        virtual std::string expandAliases( std::string const& unexpandedTestSpec ) const = 0;
+
+        static ITagAliasRegistry const& get();
+    };
+
+} // end namespace Catch
+
+// These files are included here so the single_include script doesn't put them
+// in the conditionally compiled sections
+// #included from: internal/catch_test_case_info.h
+#define TWOBLUECUBES_CATCH_TEST_CASE_INFO_H_INCLUDED
+
+#include <string>
+#include <set>
+
+#ifdef __clang__
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wpadded"
+#endif
+
+namespace Catch {
+
+    struct ITestCase;
+
+    struct TestCaseInfo {
+        enum SpecialProperties{
+            None = 0,
+            IsHidden = 1 << 1,
+            ShouldFail = 1 << 2,
+            MayFail = 1 << 3,
+            Throws = 1 << 4
+        };
+
+        TestCaseInfo(   std::string const& _name,
+                        std::string const& _className,
+                        std::string const& _description,
+                        std::set<std::string> const& _tags,
+                        SourceLineInfo const& _lineInfo );
+
+        TestCaseInfo( TestCaseInfo const& other );
+
+        friend void setTags( TestCaseInfo& testCaseInfo, std::set<std::string> const& tags );
+
+        bool isHidden() const;
+        bool throws() const;
+        bool okToFail() const;
+        bool expectedToFail() const;
+
+        std::string name;
+        std::string className;
+        std::string description;
+        std::set<std::string> tags;
+        std::set<std::string> lcaseTags;
+        std::string tagsAsString;
+        SourceLineInfo lineInfo;
+        SpecialProperties properties;
+    };
+
+    class TestCase : public TestCaseInfo {
+    public:
+
+        TestCase( ITestCase* testCase, TestCaseInfo const& info );
+        TestCase( TestCase const& other );
+
+        TestCase withName( std::string const& _newName ) const;
+
+        void invoke() const;
+
+        TestCaseInfo const& getTestCaseInfo() const;
+
+        void swap( TestCase& other );
+        bool operator == ( TestCase const& other ) const;
+        bool operator < ( TestCase const& other ) const;
+        TestCase& operator = ( TestCase const& other );
+
+    private:
+        Ptr<ITestCase> test;
+    };
+
+    TestCase makeTestCase(  ITestCase* testCase,
+                            std::string const& className,
+                            std::string const& name,
+                            std::string const& description,
+                            SourceLineInfo const& lineInfo );
+}
+
+#ifdef __clang__
+#pragma clang diagnostic pop
+#endif
+
+
+#ifdef __OBJC__
+// #included from: internal/catch_objc.hpp
+#define TWOBLUECUBES_CATCH_OBJC_HPP_INCLUDED
+
+#import <objc/runtime.h>
+
+#include <string>
+
+// NB. Any general catch headers included here must be included
+// in catch.hpp first to make sure they are included by the single
+// header for non obj-usage
+
+///////////////////////////////////////////////////////////////////////////////
+// This protocol is really only here for (self) documenting purposes, since
+// all its methods are optional.
+ at protocol OcFixture
+
+ at optional
+
+-(void) setUp;
+-(void) tearDown;
+
+ at end
+
+namespace Catch {
+
+    class OcMethod : public SharedImpl<ITestCase> {
+
+    public:
+        OcMethod( Class cls, SEL sel ) : m_cls( cls ), m_sel( sel ) {}
+
+        virtual void invoke() const {
+            id obj = [[m_cls alloc] init];
+
+            performOptionalSelector( obj, @selector(setUp)  );
+            performOptionalSelector( obj, m_sel );
+            performOptionalSelector( obj, @selector(tearDown)  );
+
+            arcSafeRelease( obj );
+        }
+    private:
+        virtual ~OcMethod() {}
+
+        Class m_cls;
+        SEL m_sel;
+    };
+
+    namespace Detail{
+
+        inline std::string getAnnotation(   Class cls,
+                                            std::string const& annotationName,
+                                            std::string const& testCaseName ) {
+            NSString* selStr = [[NSString alloc] initWithFormat:@"Catch_%s_%s", annotationName.c_str(), testCaseName.c_str()];
+            SEL sel = NSSelectorFromString( selStr );
+            arcSafeRelease( selStr );
+            id value = performOptionalSelector( cls, sel );
+            if( value )
+                return [(NSString*)value UTF8String];
+            return "";
+        }
+    }
+
+    inline size_t registerTestMethods() {
+        size_t noTestMethods = 0;
+        int noClasses = objc_getClassList( CATCH_NULL, 0 );
+
+        Class* classes = (CATCH_UNSAFE_UNRETAINED Class *)malloc( sizeof(Class) * noClasses);
+        objc_getClassList( classes, noClasses );
+
+        for( int c = 0; c < noClasses; c++ ) {
+            Class cls = classes[c];
+            {
+                u_int count;
+                Method* methods = class_copyMethodList( cls, &count );
+                for( u_int m = 0; m < count ; m++ ) {
+                    SEL selector = method_getName(methods[m]);
+                    std::string methodName = sel_getName(selector);
+                    if( startsWith( methodName, "Catch_TestCase_" ) ) {
+                        std::string testCaseName = methodName.substr( 15 );
+                        std::string name = Detail::getAnnotation( cls, "Name", testCaseName );
+                        std::string desc = Detail::getAnnotation( cls, "Description", testCaseName );
+                        const char* className = class_getName( cls );
+
+                        getMutableRegistryHub().registerTest( makeTestCase( new OcMethod( cls, selector ), className, name.c_str(), desc.c_str(), SourceLineInfo() ) );
+                        noTestMethods++;
+                    }
+                }
+                free(methods);
+            }
+        }
+        return noTestMethods;
+    }
+
+    namespace Matchers {
+        namespace Impl {
+        namespace NSStringMatchers {
+
+            template<typename MatcherT>
+            struct StringHolder : MatcherImpl<MatcherT, NSString*>{
+                StringHolder( NSString* substr ) : m_substr( [substr copy] ){}
+                StringHolder( StringHolder const& other ) : m_substr( [other.m_substr copy] ){}
+                StringHolder() {
+                    arcSafeRelease( m_substr );
+                }
+
+                NSString* m_substr;
+            };
+
+            struct Equals : StringHolder<Equals> {
+                Equals( NSString* substr ) : StringHolder( substr ){}
+
+                virtual bool match( ExpressionType const& str ) const {
+                    return  (str != nil || m_substr == nil ) &&
+                            [str isEqualToString:m_substr];
+                }
+
+                virtual std::string toString() const {
+                    return "equals string: " + Catch::toString( m_substr );
+                }
+            };
+
+            struct Contains : StringHolder<Contains> {
+                Contains( NSString* substr ) : StringHolder( substr ){}
+
+                virtual bool match( ExpressionType const& str ) const {
+                    return  (str != nil || m_substr == nil ) &&
+                            [str rangeOfString:m_substr].location != NSNotFound;
+                }
+
+                virtual std::string toString() const {
+                    return "contains string: " + Catch::toString( m_substr );
+                }
+            };
+
+            struct StartsWith : StringHolder<StartsWith> {
+                StartsWith( NSString* substr ) : StringHolder( substr ){}
+
+                virtual bool match( ExpressionType const& str ) const {
+                    return  (str != nil || m_substr == nil ) &&
+                            [str rangeOfString:m_substr].location == 0;
+                }
+
+                virtual std::string toString() const {
+                    return "starts with: " + Catch::toString( m_substr );
+                }
+            };
+            struct EndsWith : StringHolder<EndsWith> {
+                EndsWith( NSString* substr ) : StringHolder( substr ){}
+
+                virtual bool match( ExpressionType const& str ) const {
+                    return  (str != nil || m_substr == nil ) &&
+                            [str rangeOfString:m_substr].location == [str length] - [m_substr length];
+                }
+
+                virtual std::string toString() const {
+                    return "ends with: " + Catch::toString( m_substr );
+                }
+            };
+
+        } // namespace NSStringMatchers
+        } // namespace Impl
+
+        inline Impl::NSStringMatchers::Equals
+            Equals( NSString* substr ){ return Impl::NSStringMatchers::Equals( substr ); }
+
+        inline Impl::NSStringMatchers::Contains
+            Contains( NSString* substr ){ return Impl::NSStringMatchers::Contains( substr ); }
+
+        inline Impl::NSStringMatchers::StartsWith
+            StartsWith( NSString* substr ){ return Impl::NSStringMatchers::StartsWith( substr ); }
+
+        inline Impl::NSStringMatchers::EndsWith
+            EndsWith( NSString* substr ){ return Impl::NSStringMatchers::EndsWith( substr ); }
+
+    } // namespace Matchers
+
+    using namespace Matchers;
+
+} // namespace Catch
+
+///////////////////////////////////////////////////////////////////////////////
+#define OC_TEST_CASE( name, desc )\
++(NSString*) INTERNAL_CATCH_UNIQUE_NAME( Catch_Name_test ) \
+{\
+return @ name; \
+}\
++(NSString*) INTERNAL_CATCH_UNIQUE_NAME( Catch_Description_test ) \
+{ \
+return @ desc; \
+} \
+-(void) INTERNAL_CATCH_UNIQUE_NAME( Catch_TestCase_test )
+
+#endif
+
+#ifdef CATCH_IMPL
+// #included from: internal/catch_impl.hpp
+#define TWOBLUECUBES_CATCH_IMPL_HPP_INCLUDED
+
+// Collect all the implementation files together here
+// These are the equivalent of what would usually be cpp files
+
+#ifdef __clang__
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wweak-vtables"
+#endif
+
+// #included from: ../catch_session.hpp
+#define TWOBLUECUBES_CATCH_RUNNER_HPP_INCLUDED
+
+// #included from: internal/catch_commandline.hpp
+#define TWOBLUECUBES_CATCH_COMMANDLINE_HPP_INCLUDED
+
+// #included from: catch_config.hpp
+#define TWOBLUECUBES_CATCH_CONFIG_HPP_INCLUDED
+
+// #included from: catch_test_spec_parser.hpp
+#define TWOBLUECUBES_CATCH_TEST_SPEC_PARSER_HPP_INCLUDED
+
+#ifdef __clang__
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wpadded"
+#endif
+
+// #included from: catch_test_spec.hpp
+#define TWOBLUECUBES_CATCH_TEST_SPEC_HPP_INCLUDED
+
+#ifdef __clang__
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wpadded"
+#endif
+
+// #included from: catch_wildcard_pattern.hpp
+#define TWOBLUECUBES_CATCH_WILDCARD_PATTERN_HPP_INCLUDED
+
+namespace Catch
+{
+    class WildcardPattern {
+        enum WildcardPosition {
+            NoWildcard = 0,
+            WildcardAtStart = 1,
+            WildcardAtEnd = 2,
+            WildcardAtBothEnds = WildcardAtStart | WildcardAtEnd
+        };
+
+    public:
+
+        WildcardPattern( std::string const& pattern, CaseSensitive::Choice caseSensitivity )
+        :   m_caseSensitivity( caseSensitivity ),
+            m_wildcard( NoWildcard ),
+            m_pattern( adjustCase( pattern ) )
+        {
+            if( startsWith( m_pattern, "*" ) ) {
+                m_pattern = m_pattern.substr( 1 );
+                m_wildcard = WildcardAtStart;
+            }
+            if( endsWith( m_pattern, "*" ) ) {
+                m_pattern = m_pattern.substr( 0, m_pattern.size()-1 );
+                m_wildcard = static_cast<WildcardPosition>( m_wildcard | WildcardAtEnd );
+            }
+        }
+        virtual ~WildcardPattern();
+        virtual bool matches( std::string const& str ) const {
+            switch( m_wildcard ) {
+                case NoWildcard:
+                    return m_pattern == adjustCase( str );
+                case WildcardAtStart:
+                    return endsWith( adjustCase( str ), m_pattern );
+                case WildcardAtEnd:
+                    return startsWith( adjustCase( str ), m_pattern );
+                case WildcardAtBothEnds:
+                    return contains( adjustCase( str ), m_pattern );
+            }
+
+#ifdef __clang__
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wunreachable-code"
+#endif
+            throw std::logic_error( "Unknown enum" );
+#ifdef __clang__
+#pragma clang diagnostic pop
+#endif
+        }
+    private:
+        std::string adjustCase( std::string const& str ) const {
+            return m_caseSensitivity == CaseSensitive::No ? toLower( str ) : str;
+        }
+        CaseSensitive::Choice m_caseSensitivity;
+        WildcardPosition m_wildcard;
+        std::string m_pattern;
+    };
+}
+
+#include <string>
+#include <vector>
+
+namespace Catch {
+
+    class TestSpec {
+        struct Pattern : SharedImpl<> {
+            virtual ~Pattern();
+            virtual bool matches( TestCaseInfo const& testCase ) const = 0;
+        };
+        class NamePattern : public Pattern {
+        public:
+            NamePattern( std::string const& name )
+            : m_wildcardPattern( toLower( name ), CaseSensitive::No )
+            {}
+            virtual ~NamePattern();
+            virtual bool matches( TestCaseInfo const& testCase ) const {
+                return m_wildcardPattern.matches( toLower( testCase.name ) );
+            }
+        private:
+            WildcardPattern m_wildcardPattern;
+        };
+
+        class TagPattern : public Pattern {
+        public:
+            TagPattern( std::string const& tag ) : m_tag( toLower( tag ) ) {}
+            virtual ~TagPattern();
+            virtual bool matches( TestCaseInfo const& testCase ) const {
+                return testCase.lcaseTags.find( m_tag ) != testCase.lcaseTags.end();
+            }
+        private:
+            std::string m_tag;
+        };
+
+        class ExcludedPattern : public Pattern {
+        public:
+            ExcludedPattern( Ptr<Pattern> const& underlyingPattern ) : m_underlyingPattern( underlyingPattern ) {}
+            virtual ~ExcludedPattern();
+            virtual bool matches( TestCaseInfo const& testCase ) const { return !m_underlyingPattern->matches( testCase ); }
+        private:
+            Ptr<Pattern> m_underlyingPattern;
+        };
+
+        struct Filter {
+            std::vector<Ptr<Pattern> > m_patterns;
+
+            bool matches( TestCaseInfo const& testCase ) const {
+                // All patterns in a filter must match for the filter to be a match
+                for( std::vector<Ptr<Pattern> >::const_iterator it = m_patterns.begin(), itEnd = m_patterns.end(); it != itEnd; ++it ) {
+                    if( !(*it)->matches( testCase ) )
+                        return false;
+                }
+                return true;
+            }
+        };
+
+    public:
+        bool hasFilters() const {
+            return !m_filters.empty();
+        }
+        bool matches( TestCaseInfo const& testCase ) const {
+            // A TestSpec matches if any filter matches
+            for( std::vector<Filter>::const_iterator it = m_filters.begin(), itEnd = m_filters.end(); it != itEnd; ++it )
+                if( it->matches( testCase ) )
+                    return true;
+            return false;
+        }
+
+    private:
+        std::vector<Filter> m_filters;
+
+        friend class TestSpecParser;
+    };
+}
+
+#ifdef __clang__
+#pragma clang diagnostic pop
+#endif
+
+namespace Catch {
+
+    class TestSpecParser {
+        enum Mode{ None, Name, QuotedName, Tag, EscapedName };
+        Mode m_mode;
+        bool m_exclusion;
+        std::size_t m_start, m_pos;
+        std::string m_arg;
+        std::vector<std::size_t> m_escapeChars;
+        TestSpec::Filter m_currentFilter;
+        TestSpec m_testSpec;
+        ITagAliasRegistry const* m_tagAliases;
+
+    public:
+        TestSpecParser( ITagAliasRegistry const& tagAliases ) : m_tagAliases( &tagAliases ) {}
+
+        TestSpecParser& parse( std::string const& arg ) {
+            m_mode = None;
+            m_exclusion = false;
+            m_start = std::string::npos;
+            m_arg = m_tagAliases->expandAliases( arg );
+            m_escapeChars.clear();
+            for( m_pos = 0; m_pos < m_arg.size(); ++m_pos )
+                visitChar( m_arg[m_pos] );
+            if( m_mode == Name )
+                addPattern<TestSpec::NamePattern>();
+            return *this;
+        }
+        TestSpec testSpec() {
+            addFilter();
+            return m_testSpec;
+        }
+    private:
+        void visitChar( char c ) {
+            if( m_mode == None ) {
+                switch( c ) {
+                case ' ': return;
+                case '~': m_exclusion = true; return;
+                case '[': return startNewMode( Tag, ++m_pos );
+                case '"': return startNewMode( QuotedName, ++m_pos );
+                case '\\': return escape();
+                default: startNewMode( Name, m_pos ); break;
+                }
+            }
+            if( m_mode == Name ) {
+                if( c == ',' ) {
+                    addPattern<TestSpec::NamePattern>();
+                    addFilter();
+                }
+                else if( c == '[' ) {
+                    if( subString() == "exclude:" )
+                        m_exclusion = true;
+                    else
+                        addPattern<TestSpec::NamePattern>();
+                    startNewMode( Tag, ++m_pos );
+                }
+                else if( c == '\\' )
+                    escape();
+            }
+            else if( m_mode == EscapedName )
+                m_mode = Name;
+            else if( m_mode == QuotedName && c == '"' )
+                addPattern<TestSpec::NamePattern>();
+            else if( m_mode == Tag && c == ']' )
+                addPattern<TestSpec::TagPattern>();
+        }
+        void startNewMode( Mode mode, std::size_t start ) {
+            m_mode = mode;
+            m_start = start;
+        }
+        void escape() {
+            m_mode = EscapedName;
+            m_escapeChars.push_back( m_pos );
+        }
+        std::string subString() const { return m_arg.substr( m_start, m_pos - m_start ); }
+        template<typename T>
+        void addPattern() {
+            std::string token = subString();
+            for( size_t i = 0; i < m_escapeChars.size(); ++i )
+                token = token.substr( 0, m_escapeChars[i] ) + token.substr( m_escapeChars[i]+1 );
+            m_escapeChars.clear();
+            if( startsWith( token, "exclude:" ) ) {
+                m_exclusion = true;
+                token = token.substr( 8 );
+            }
+            if( !token.empty() ) {
+                Ptr<TestSpec::Pattern> pattern = new T( token );
+                if( m_exclusion )
+                    pattern = new TestSpec::ExcludedPattern( pattern );
+                m_currentFilter.m_patterns.push_back( pattern );
+            }
+            m_exclusion = false;
+            m_mode = None;
+        }
+        void addFilter() {
+            if( !m_currentFilter.m_patterns.empty() ) {
+                m_testSpec.m_filters.push_back( m_currentFilter );
+                m_currentFilter = TestSpec::Filter();
+            }
+        }
+    };
+    inline TestSpec parseTestSpec( std::string const& arg ) {
+        return TestSpecParser( ITagAliasRegistry::get() ).parse( arg ).testSpec();
+    }
+
+} // namespace Catch
+
+#ifdef __clang__
+#pragma clang diagnostic pop
+#endif
+
+// #included from: catch_interfaces_config.h
+#define TWOBLUECUBES_CATCH_INTERFACES_CONFIG_H_INCLUDED
+
+#include <iostream>
+#include <string>
+#include <vector>
+
+namespace Catch {
+
+    struct Verbosity { enum Level {
+        NoOutput = 0,
+        Quiet,
+        Normal
+    }; };
+
+    struct WarnAbout { enum What {
+        Nothing = 0x00,
+        NoAssertions = 0x01
+    }; };
+
+    struct ShowDurations { enum OrNot {
+        DefaultForReporter,
+        Always,
+        Never
+    }; };
+    struct RunTests { enum InWhatOrder {
+        InDeclarationOrder,
+        InLexicographicalOrder,
+        InRandomOrder
+    }; };
+    struct UseColour { enum YesOrNo {
+        Auto,
+        Yes,
+        No
+    }; };
+
+    class TestSpec;
+
+    struct IConfig : IShared {
+
+        virtual ~IConfig();
+
+        virtual bool allowThrows() const = 0;
+        virtual std::ostream& stream() const = 0;
+        virtual std::string name() const = 0;
+        virtual bool includeSuccessfulResults() const = 0;
+        virtual bool shouldDebugBreak() const = 0;
+        virtual bool warnAboutMissingAssertions() const = 0;
+        virtual int abortAfter() const = 0;
+        virtual bool showInvisibles() const = 0;
+        virtual ShowDurations::OrNot showDurations() const = 0;
+        virtual TestSpec const& testSpec() const = 0;
+        virtual RunTests::InWhatOrder runOrder() const = 0;
+        virtual unsigned int rngSeed() const = 0;
+        virtual UseColour::YesOrNo useColour() const = 0;
+    };
+}
+
+// #included from: catch_stream.h
+#define TWOBLUECUBES_CATCH_STREAM_H_INCLUDED
+
+// #included from: catch_streambuf.h
+#define TWOBLUECUBES_CATCH_STREAMBUF_H_INCLUDED
+
+#include <streambuf>
+
+namespace Catch {
+
+    class StreamBufBase : public std::streambuf {
+    public:
+        virtual ~StreamBufBase() CATCH_NOEXCEPT;
+    };
+}
+
+#include <streambuf>
+#include <ostream>
+#include <fstream>
+#include <memory>
+
+namespace Catch {
+
+    std::ostream& cout();
+    std::ostream& cerr();
+
+    struct IStream {
+        virtual ~IStream() CATCH_NOEXCEPT;
+        virtual std::ostream& stream() const = 0;
+    };
+
+    class FileStream : public IStream {
+        mutable std::ofstream m_ofs;
+    public:
+        FileStream( std::string const& filename );
+        virtual ~FileStream() CATCH_NOEXCEPT;
+    public: // IStream
+        virtual std::ostream& stream() const CATCH_OVERRIDE;
+    };
+
+    class CoutStream : public IStream {
+        mutable std::ostream m_os;
+    public:
+        CoutStream();
+        virtual ~CoutStream() CATCH_NOEXCEPT;
+
+    public: // IStream
+        virtual std::ostream& stream() const CATCH_OVERRIDE;
+    };
+
+    class DebugOutStream : public IStream {
+        CATCH_AUTO_PTR( StreamBufBase ) m_streamBuf;
+        mutable std::ostream m_os;
+    public:
+        DebugOutStream();
+        virtual ~DebugOutStream() CATCH_NOEXCEPT;
+
+    public: // IStream
+        virtual std::ostream& stream() const CATCH_OVERRIDE;
+    };
+}
+
+#include <memory>
+#include <vector>
+#include <string>
+#include <iostream>
+#include <ctime>
+
+#ifndef CATCH_CONFIG_CONSOLE_WIDTH
+#define CATCH_CONFIG_CONSOLE_WIDTH 80
+#endif
+
+namespace Catch {
+
+    struct ConfigData {
+
+        ConfigData()
+        :   listTests( false ),
+            listTags( false ),
+            listReporters( false ),
+            listTestNamesOnly( false ),
+            showSuccessfulTests( false ),
+            shouldDebugBreak( false ),
+            noThrow( false ),
+            showHelp( false ),
+            showInvisibles( false ),
+            filenamesAsTags( false ),
+            abortAfter( -1 ),
+            rngSeed( 0 ),
+            verbosity( Verbosity::Normal ),
+            warnings( WarnAbout::Nothing ),
+            showDurations( ShowDurations::DefaultForReporter ),
+            runOrder( RunTests::InDeclarationOrder ),
+            useColour( UseColour::Auto )
+        {}
+
+        bool listTests;
+        bool listTags;
+        bool listReporters;
+        bool listTestNamesOnly;
+
+        bool showSuccessfulTests;
+        bool shouldDebugBreak;
+        bool noThrow;
+        bool showHelp;
+        bool showInvisibles;
+        bool filenamesAsTags;
+
+        int abortAfter;
+        unsigned int rngSeed;
+
+        Verbosity::Level verbosity;
+        WarnAbout::What warnings;
+        ShowDurations::OrNot showDurations;
+        RunTests::InWhatOrder runOrder;
+        UseColour::YesOrNo useColour;
+
+        std::string outputFilename;
+        std::string name;
+        std::string processName;
+
+        std::vector<std::string> reporterNames;
+        std::vector<std::string> testsOrTags;
+    };
+
+    class Config : public SharedImpl<IConfig> {
+    private:
+        Config( Config const& other );
+        Config& operator = ( Config const& other );
+        virtual void dummy();
+    public:
+
+        Config()
+        {}
+
+        Config( ConfigData const& data )
+        :   m_data( data ),
+            m_stream( openStream() )
+        {
+            if( !data.testsOrTags.empty() ) {
+                TestSpecParser parser( ITagAliasRegistry::get() );
+                for( std::size_t i = 0; i < data.testsOrTags.size(); ++i )
+                    parser.parse( data.testsOrTags[i] );
+                m_testSpec = parser.testSpec();
+            }
+        }
+
+        virtual ~Config() {
+        }
+
+        std::string const& getFilename() const {
+            return m_data.outputFilename ;
+        }
+
+        bool listTests() const { return m_data.listTests; }
+        bool listTestNamesOnly() const { return m_data.listTestNamesOnly; }
+        bool listTags() const { return m_data.listTags; }
+        bool listReporters() const { return m_data.listReporters; }
+
+        std::string getProcessName() const { return m_data.processName; }
+
+        bool shouldDebugBreak() const { return m_data.shouldDebugBreak; }
+
+        std::vector<std::string> getReporterNames() const { return m_data.reporterNames; }
+
+        int abortAfter() const { return m_data.abortAfter; }
+
+        TestSpec const& testSpec() const { return m_testSpec; }
+
+        bool showHelp() const { return m_data.showHelp; }
+        bool showInvisibles() const { return m_data.showInvisibles; }
+
+        // IConfig interface
+        virtual bool allowThrows() const        { return !m_data.noThrow; }
+        virtual std::ostream& stream() const    { return m_stream->stream(); }
+        virtual std::string name() const        { return m_data.name.empty() ? m_data.processName : m_data.name; }
+        virtual bool includeSuccessfulResults() const   { return m_data.showSuccessfulTests; }
+        virtual bool warnAboutMissingAssertions() const { return m_data.warnings & WarnAbout::NoAssertions; }
+        virtual ShowDurations::OrNot showDurations() const { return m_data.showDurations; }
+        virtual RunTests::InWhatOrder runOrder() const  { return m_data.runOrder; }
+        virtual unsigned int rngSeed() const    { return m_data.rngSeed; }
+        virtual UseColour::YesOrNo useColour() const { return m_data.useColour; }
+
+    private:
+
+        IStream const* openStream() {
+            if( m_data.outputFilename.empty() )
+                return new CoutStream();
+            else if( m_data.outputFilename[0] == '%' ) {
+                if( m_data.outputFilename == "%debug" )
+                    return new DebugOutStream();
+                else
+                    throw std::domain_error( "Unrecognised stream: " + m_data.outputFilename );
+            }
+            else
+                return new FileStream( m_data.outputFilename );
+        }
+        ConfigData m_data;
+
+        CATCH_AUTO_PTR( IStream const ) m_stream;
+        TestSpec m_testSpec;
+    };
+
+} // end namespace Catch
+
+// #included from: catch_clara.h
+#define TWOBLUECUBES_CATCH_CLARA_H_INCLUDED
+
+// Use Catch's value for console width (store Clara's off to the side, if present)
+#ifdef CLARA_CONFIG_CONSOLE_WIDTH
+#define CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH CLARA_CONFIG_CONSOLE_WIDTH
+#undef CLARA_CONFIG_CONSOLE_WIDTH
+#endif
+#define CLARA_CONFIG_CONSOLE_WIDTH CATCH_CONFIG_CONSOLE_WIDTH
+
+// Declare Clara inside the Catch namespace
+#define STITCH_CLARA_OPEN_NAMESPACE namespace Catch {
+// #included from: ../external/clara.h
+
+// Version 0.0.2.4
+
+// Only use header guard if we are not using an outer namespace
+#if !defined(TWOBLUECUBES_CLARA_H_INCLUDED) || defined(STITCH_CLARA_OPEN_NAMESPACE)
+
+#ifndef STITCH_CLARA_OPEN_NAMESPACE
+#define TWOBLUECUBES_CLARA_H_INCLUDED
+#define STITCH_CLARA_OPEN_NAMESPACE
+#define STITCH_CLARA_CLOSE_NAMESPACE
+#else
+#define STITCH_CLARA_CLOSE_NAMESPACE }
+#endif
+
+#define STITCH_TBC_TEXT_FORMAT_OPEN_NAMESPACE STITCH_CLARA_OPEN_NAMESPACE
+
+// ----------- #included from tbc_text_format.h -----------
+
+// Only use header guard if we are not using an outer namespace
+#if !defined(TBC_TEXT_FORMAT_H_INCLUDED) || defined(STITCH_TBC_TEXT_FORMAT_OUTER_NAMESPACE)
+#ifndef STITCH_TBC_TEXT_FORMAT_OUTER_NAMESPACE
+#define TBC_TEXT_FORMAT_H_INCLUDED
+#endif
+
+#include <string>
+#include <vector>
+#include <sstream>
+#include <algorithm>
+
+// Use optional outer namespace
+#ifdef STITCH_TBC_TEXT_FORMAT_OUTER_NAMESPACE
+namespace STITCH_TBC_TEXT_FORMAT_OUTER_NAMESPACE {
+#endif
+
+namespace Tbc {
+
+#ifdef TBC_TEXT_FORMAT_CONSOLE_WIDTH
+    const unsigned int consoleWidth = TBC_TEXT_FORMAT_CONSOLE_WIDTH;
+#else
+    const unsigned int consoleWidth = 80;
+#endif
+
+    struct TextAttributes {
+        TextAttributes()
+        :   initialIndent( std::string::npos ),
+            indent( 0 ),
+            width( consoleWidth-1 ),
+            tabChar( '\t' )
+        {}
+
+        TextAttributes& setInitialIndent( std::size_t _value )  { initialIndent = _value; return *this; }
+        TextAttributes& setIndent( std::size_t _value )         { indent = _value; return *this; }
+        TextAttributes& setWidth( std::size_t _value )          { width = _value; return *this; }
+        TextAttributes& setTabChar( char _value )               { tabChar = _value; return *this; }
+
+        std::size_t initialIndent;  // indent of first line, or npos
+        std::size_t indent;         // indent of subsequent lines, or all if initialIndent is npos
+        std::size_t width;          // maximum width of text, including indent. Longer text will wrap
+        char tabChar;               // If this char is seen the indent is changed to current pos
+    };
+
+    class Text {
+    public:
+        Text( std::string const& _str, TextAttributes const& _attr = TextAttributes() )
+        : attr( _attr )
+        {
+            std::string wrappableChars = " [({.,/|\\-";
+            std::size_t indent = _attr.initialIndent != std::string::npos
+                ? _attr.initialIndent
+                : _attr.indent;
+            std::string remainder = _str;
+
+            while( !remainder.empty() ) {
+                if( lines.size() >= 1000 ) {
+                    lines.push_back( "... message truncated due to excessive size" );
+                    return;
+                }
+                std::size_t tabPos = std::string::npos;
+                std::size_t width = (std::min)( remainder.size(), _attr.width - indent );
+                std::size_t pos = remainder.find_first_of( '\n' );
+                if( pos <= width ) {
+                    width = pos;
+                }
+                pos = remainder.find_last_of( _attr.tabChar, width );
+                if( pos != std::string::npos ) {
+                    tabPos = pos;
+                    if( remainder[width] == '\n' )
+                        width--;
+                    remainder = remainder.substr( 0, tabPos ) + remainder.substr( tabPos+1 );
+                }
+
+                if( width == remainder.size() ) {
+                    spliceLine( indent, remainder, width );
+                }
+                else if( remainder[width] == '\n' ) {
+                    spliceLine( indent, remainder, width );
+                    if( width <= 1 || remainder.size() != 1 )
+                        remainder = remainder.substr( 1 );
+                    indent = _attr.indent;
+                }
+                else {
+                    pos = remainder.find_last_of( wrappableChars, width );
+                    if( pos != std::string::npos && pos > 0 ) {
+                        spliceLine( indent, remainder, pos );
+                        if( remainder[0] == ' ' )
+                            remainder = remainder.substr( 1 );
+                    }
+                    else {
+                        spliceLine( indent, remainder, width-1 );
+                        lines.back() += "-";
+                    }
+                    if( lines.size() == 1 )
+                        indent = _attr.indent;
+                    if( tabPos != std::string::npos )
+                        indent += tabPos;
+                }
+            }
+        }
+
+        void spliceLine( std::size_t _indent, std::string& _remainder, std::size_t _pos ) {
+            lines.push_back( std::string( _indent, ' ' ) + _remainder.substr( 0, _pos ) );
+            _remainder = _remainder.substr( _pos );
+        }
+
+        typedef std::vector<std::string>::const_iterator const_iterator;
+
+        const_iterator begin() const { return lines.begin(); }
+        const_iterator end() const { return lines.end(); }
+        std::string const& last() const { return lines.back(); }
+        std::size_t size() const { return lines.size(); }
+        std::string const& operator[]( std::size_t _index ) const { return lines[_index]; }
+        std::string toString() const {
+            std::ostringstream oss;
+            oss << *this;
+            return oss.str();
+        }
+
+        inline friend std::ostream& operator << ( std::ostream& _stream, Text const& _text ) {
+            for( Text::const_iterator it = _text.begin(), itEnd = _text.end();
+                it != itEnd; ++it ) {
+                if( it != _text.begin() )
+                    _stream << "\n";
+                _stream << *it;
+            }
+            return _stream;
+        }
+
+    private:
+        std::string str;
+        TextAttributes attr;
+        std::vector<std::string> lines;
+    };
+
+} // end namespace Tbc
+
+#ifdef STITCH_TBC_TEXT_FORMAT_OUTER_NAMESPACE
+} // end outer namespace
+#endif
+
+#endif // TBC_TEXT_FORMAT_H_INCLUDED
+
+// ----------- end of #include from tbc_text_format.h -----------
+// ........... back in clara.h
+
+#undef STITCH_TBC_TEXT_FORMAT_OPEN_NAMESPACE
+
+// ----------- #included from clara_compilers.h -----------
+
+#ifndef TWOBLUECUBES_CLARA_COMPILERS_H_INCLUDED
+#define TWOBLUECUBES_CLARA_COMPILERS_H_INCLUDED
+
+// Detect a number of compiler features - mostly C++11/14 conformance - by compiler
+// The following features are defined:
+//
+// CLARA_CONFIG_CPP11_NULLPTR : is nullptr supported?
+// CLARA_CONFIG_CPP11_NOEXCEPT : is noexcept supported?
+// CLARA_CONFIG_CPP11_GENERATED_METHODS : The delete and default keywords for compiler generated methods
+// CLARA_CONFIG_CPP11_OVERRIDE : is override supported?
+// CLARA_CONFIG_CPP11_UNIQUE_PTR : is unique_ptr supported (otherwise use auto_ptr)
+
+// CLARA_CONFIG_CPP11_OR_GREATER : Is C++11 supported?
+
+// CLARA_CONFIG_VARIADIC_MACROS : are variadic macros supported?
+
+// In general each macro has a _NO_<feature name> form
+// (e.g. CLARA_CONFIG_CPP11_NO_NULLPTR) which disables the feature.
+// Many features, at point of detection, define an _INTERNAL_ macro, so they
+// can be combined, en-mass, with the _NO_ forms later.
+
+// All the C++11 features can be disabled with CLARA_CONFIG_NO_CPP11
+
+#ifdef __clang__
+
+#if __has_feature(cxx_nullptr)
+#define CLARA_INTERNAL_CONFIG_CPP11_NULLPTR
+#endif
+
+#if __has_feature(cxx_noexcept)
+#define CLARA_INTERNAL_CONFIG_CPP11_NOEXCEPT
+#endif
+
+#endif // __clang__
+
+////////////////////////////////////////////////////////////////////////////////
+// GCC
+#ifdef __GNUC__
+
+#if __GNUC__ == 4 && __GNUC_MINOR__ >= 6 && defined(__GXX_EXPERIMENTAL_CXX0X__)
+#define CLARA_INTERNAL_CONFIG_CPP11_NULLPTR
+#endif
+
+// - otherwise more recent versions define __cplusplus >= 201103L
+// and will get picked up below
+
+#endif // __GNUC__
+
+////////////////////////////////////////////////////////////////////////////////
+// Visual C++
+#ifdef _MSC_VER
+
+#if (_MSC_VER >= 1600)
+#define CLARA_INTERNAL_CONFIG_CPP11_NULLPTR
+#define CLARA_INTERNAL_CONFIG_CPP11_UNIQUE_PTR
+#endif
+
+#if (_MSC_VER >= 1900 ) // (VC++ 13 (VS2015))
+#define CLARA_INTERNAL_CONFIG_CPP11_NOEXCEPT
+#define CLARA_INTERNAL_CONFIG_CPP11_GENERATED_METHODS
+#endif
+
+#endif // _MSC_VER
+
+////////////////////////////////////////////////////////////////////////////////
+// C++ language feature support
+
+// catch all support for C++11
+#if defined(__cplusplus) && __cplusplus >= 201103L
+
+#define CLARA_CPP11_OR_GREATER
+
+#if !defined(CLARA_INTERNAL_CONFIG_CPP11_NULLPTR)
+#define CLARA_INTERNAL_CONFIG_CPP11_NULLPTR
+#endif
+
+#ifndef CLARA_INTERNAL_CONFIG_CPP11_NOEXCEPT
+#define CLARA_INTERNAL_CONFIG_CPP11_NOEXCEPT
+#endif
+
+#ifndef CLARA_INTERNAL_CONFIG_CPP11_GENERATED_METHODS
+#define CLARA_INTERNAL_CONFIG_CPP11_GENERATED_METHODS
+#endif
+
+#if !defined(CLARA_INTERNAL_CONFIG_CPP11_OVERRIDE)
+#define CLARA_INTERNAL_CONFIG_CPP11_OVERRIDE
+#endif
+#if !defined(CLARA_INTERNAL_CONFIG_CPP11_UNIQUE_PTR)
+#define CLARA_INTERNAL_CONFIG_CPP11_UNIQUE_PTR
+#endif
+
+#endif // __cplusplus >= 201103L
+
+// Now set the actual defines based on the above + anything the user has configured
+#if defined(CLARA_INTERNAL_CONFIG_CPP11_NULLPTR) && !defined(CLARA_CONFIG_CPP11_NO_NULLPTR) && !defined(CLARA_CONFIG_CPP11_NULLPTR) && !defined(CLARA_CONFIG_NO_CPP11)
+#define CLARA_CONFIG_CPP11_NULLPTR
+#endif
+#if defined(CLARA_INTERNAL_CONFIG_CPP11_NOEXCEPT) && !defined(CLARA_CONFIG_CPP11_NO_NOEXCEPT) && !defined(CLARA_CONFIG_CPP11_NOEXCEPT) && !defined(CLARA_CONFIG_NO_CPP11)
+#define CLARA_CONFIG_CPP11_NOEXCEPT
+#endif
+#if defined(CLARA_INTERNAL_CONFIG_CPP11_GENERATED_METHODS) && !defined(CLARA_CONFIG_CPP11_NO_GENERATED_METHODS) && !defined(CLARA_CONFIG_CPP11_GENERATED_METHODS) && !defined(CLARA_CONFIG_NO_CPP11)
+#define CLARA_CONFIG_CPP11_GENERATED_METHODS
+#endif
+#if defined(CLARA_INTERNAL_CONFIG_CPP11_OVERRIDE) && !defined(CLARA_CONFIG_NO_OVERRIDE) && !defined(CLARA_CONFIG_CPP11_OVERRIDE) && !defined(CLARA_CONFIG_NO_CPP11)
+#define CLARA_CONFIG_CPP11_OVERRIDE
+#endif
+#if defined(CLARA_INTERNAL_CONFIG_CPP11_UNIQUE_PTR) && !defined(CLARA_CONFIG_NO_UNIQUE_PTR) && !defined(CLARA_CONFIG_CPP11_UNIQUE_PTR) && !defined(CLARA_CONFIG_NO_CPP11)
+#define CLARA_CONFIG_CPP11_UNIQUE_PTR
+#endif
+
+// noexcept support:
+#if defined(CLARA_CONFIG_CPP11_NOEXCEPT) && !defined(CLARA_NOEXCEPT)
+#define CLARA_NOEXCEPT noexcept
+#  define CLARA_NOEXCEPT_IS(x) noexcept(x)
+#else
+#define CLARA_NOEXCEPT throw()
+#  define CLARA_NOEXCEPT_IS(x)
+#endif
+
+// nullptr support
+#ifdef CLARA_CONFIG_CPP11_NULLPTR
+#define CLARA_NULL nullptr
+#else
+#define CLARA_NULL NULL
+#endif
+
+// override support
+#ifdef CLARA_CONFIG_CPP11_OVERRIDE
+#define CLARA_OVERRIDE override
+#else
+#define CLARA_OVERRIDE
+#endif
+
+// unique_ptr support
+#ifdef CLARA_CONFIG_CPP11_UNIQUE_PTR
+#   define CLARA_AUTO_PTR( T ) std::unique_ptr<T>
+#else
+#   define CLARA_AUTO_PTR( T ) std::auto_ptr<T>
+#endif
+
+#endif // TWOBLUECUBES_CLARA_COMPILERS_H_INCLUDED
+
+// ----------- end of #include from clara_compilers.h -----------
+// ........... back in clara.h
+
+#include <map>
+#include <stdexcept>
+#include <memory>
+
+#if defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER)
+#define CLARA_PLATFORM_WINDOWS
+#endif
+
+// Use optional outer namespace
+#ifdef STITCH_CLARA_OPEN_NAMESPACE
+STITCH_CLARA_OPEN_NAMESPACE
+#endif
+
+namespace Clara {
+
+    struct UnpositionalTag {};
+
+    extern UnpositionalTag _;
+
+#ifdef CLARA_CONFIG_MAIN
+    UnpositionalTag _;
+#endif
+
+    namespace Detail {
+
+#ifdef CLARA_CONSOLE_WIDTH
+    const unsigned int consoleWidth = CLARA_CONFIG_CONSOLE_WIDTH;
+#else
+    const unsigned int consoleWidth = 80;
+#endif
+
+        using namespace Tbc;
+
+        inline bool startsWith( std::string const& str, std::string const& prefix ) {
+            return str.size() >= prefix.size() && str.substr( 0, prefix.size() ) == prefix;
+        }
+
+        template<typename T> struct RemoveConstRef{ typedef T type; };
+        template<typename T> struct RemoveConstRef<T&>{ typedef T type; };
+        template<typename T> struct RemoveConstRef<T const&>{ typedef T type; };
+        template<typename T> struct RemoveConstRef<T const>{ typedef T type; };
+
+        template<typename T>    struct IsBool       { static const bool value = false; };
+        template<>              struct IsBool<bool> { static const bool value = true; };
+
+        template<typename T>
+        void convertInto( std::string const& _source, T& _dest ) {
+            std::stringstream ss;
+            ss << _source;
+            ss >> _dest;
+            if( ss.fail() )
+                throw std::runtime_error( "Unable to convert " + _source + " to destination type" );
+        }
+        inline void convertInto( std::string const& _source, std::string& _dest ) {
+            _dest = _source;
+        }
+        char toLowerCh(char c) {
+            return static_cast<char>( ::tolower( c ) );
+        }
+        inline void convertInto( std::string const& _source, bool& _dest ) {
+            std::string sourceLC = _source;
+            std::transform( sourceLC.begin(), sourceLC.end(), sourceLC.begin(), toLowerCh );
+            if( sourceLC == "y" || sourceLC == "1" || sourceLC == "true" || sourceLC == "yes" || sourceLC == "on" )
+                _dest = true;
+            else if( sourceLC == "n" || sourceLC == "0" || sourceLC == "false" || sourceLC == "no" || sourceLC == "off" )
+                _dest = false;
+            else
+                throw std::runtime_error( "Expected a boolean value but did not recognise:\n  '" + _source + "'" );
+        }
+
+        template<typename ConfigT>
+        struct IArgFunction {
+            virtual ~IArgFunction() {}
+#ifdef CLARA_CONFIG_CPP11_GENERATED_METHODS
+            IArgFunction()                      = default;
+            IArgFunction( IArgFunction const& ) = default;
+#endif
+            virtual void set( ConfigT& config, std::string const& value ) const = 0;
+            virtual bool takesArg() const = 0;
+            virtual IArgFunction* clone() const = 0;
+        };
+
+        template<typename ConfigT>
+        class BoundArgFunction {
+        public:
+            BoundArgFunction() : functionObj( CLARA_NULL ) {}
+            BoundArgFunction( IArgFunction<ConfigT>* _functionObj ) : functionObj( _functionObj ) {}
+            BoundArgFunction( BoundArgFunction const& other ) : functionObj( other.functionObj ? other.functionObj->clone() : CLARA_NULL ) {}
+            BoundArgFunction& operator = ( BoundArgFunction const& other ) {
+                IArgFunction<ConfigT>* newFunctionObj = other.functionObj ? other.functionObj->clone() : CLARA_NULL;
+                delete functionObj;
+                functionObj = newFunctionObj;
+                return *this;
+            }
+            ~BoundArgFunction() { delete functionObj; }
+
+            void set( ConfigT& config, std::string const& value ) const {
+                functionObj->set( config, value );
+            }
+            bool takesArg() const { return functionObj->takesArg(); }
+
+            bool isSet() const {
+                return functionObj != CLARA_NULL;
+            }
+        private:
+            IArgFunction<ConfigT>* functionObj;
+        };
+
+        template<typename C>
+        struct NullBinder : IArgFunction<C>{
+            virtual void set( C&, std::string const& ) const {}
+            virtual bool takesArg() const { return true; }
+            virtual IArgFunction<C>* clone() const { return new NullBinder( *this ); }
+        };
+
+        template<typename C, typename M>
+        struct BoundDataMember : IArgFunction<C>{
+            BoundDataMember( M C::* _member ) : member( _member ) {}
+            virtual void set( C& p, std::string const& stringValue ) const {
+                convertInto( stringValue, p.*member );
+            }
+            virtual bool takesArg() const { return !IsBool<M>::value; }
+            virtual IArgFunction<C>* clone() const { return new BoundDataMember( *this ); }
+            M C::* member;
+        };
+        template<typename C, typename M>
+        struct BoundUnaryMethod : IArgFunction<C>{
+            BoundUnaryMethod( void (C::*_member)( M ) ) : member( _member ) {}
+            virtual void set( C& p, std::string const& stringValue ) const {
+                typename RemoveConstRef<M>::type value;
+                convertInto( stringValue, value );
+                (p.*member)( value );
+            }
+            virtual bool takesArg() const { return !IsBool<M>::value; }
+            virtual IArgFunction<C>* clone() const { return new BoundUnaryMethod( *this ); }
+            void (C::*member)( M );
+        };
+        template<typename C>
+        struct BoundNullaryMethod : IArgFunction<C>{
+            BoundNullaryMethod( void (C::*_member)() ) : member( _member ) {}
+            virtual void set( C& p, std::string const& stringValue ) const {
+                bool value;
+                convertInto( stringValue, value );
+                if( value )
+                    (p.*member)();
+            }
+            virtual bool takesArg() const { return false; }
+            virtual IArgFunction<C>* clone() const { return new BoundNullaryMethod( *this ); }
+            void (C::*member)();
+        };
+
+        template<typename C>
+        struct BoundUnaryFunction : IArgFunction<C>{
+            BoundUnaryFunction( void (*_function)( C& ) ) : function( _function ) {}
+            virtual void set( C& obj, std::string const& stringValue ) const {
+                bool value;
+                convertInto( stringValue, value );
+                if( value )
+                    function( obj );
+            }
+            virtual bool takesArg() const { return false; }
+            virtual IArgFunction<C>* clone() const { return new BoundUnaryFunction( *this ); }
+            void (*function)( C& );
+        };
+
+        template<typename C, typename T>
+        struct BoundBinaryFunction : IArgFunction<C>{
+            BoundBinaryFunction( void (*_function)( C&, T ) ) : function( _function ) {}
+            virtual void set( C& obj, std::string const& stringValue ) const {
+                typename RemoveConstRef<T>::type value;
+                convertInto( stringValue, value );
+                function( obj, value );
+            }
+            virtual bool takesArg() const { return !IsBool<T>::value; }
+            virtual IArgFunction<C>* clone() const { return new BoundBinaryFunction( *this ); }
+            void (*function)( C&, T );
+        };
+
+    } // namespace Detail
+
+    inline std::vector<std::string> argsToVector( int argc, char const* const* const argv ) {
+        std::vector<std::string> args( static_cast<std::size_t>( argc ) );
+        for( std::size_t i = 0; i < static_cast<std::size_t>( argc ); ++i )
+            args[i] = argv[i];
+
+        return args;
+    }
+
+    class Parser {
+        enum Mode { None, MaybeShortOpt, SlashOpt, ShortOpt, LongOpt, Positional };
+        Mode mode;
+        std::size_t from;
+        bool inQuotes;
+    public:
+
+        struct Token {
+            enum Type { Positional, ShortOpt, LongOpt };
+            Token( Type _type, std::string const& _data ) : type( _type ), data( _data ) {}
+            Type type;
+            std::string data;
+        };
+
+        Parser() : mode( None ), from( 0 ), inQuotes( false ){}
+
+        void parseIntoTokens( std::vector<std::string> const& args, std::vector<Token>& tokens ) {
+            const std::string doubleDash = "--";
+            for( std::size_t i = 1; i < args.size() && args[i] != doubleDash; ++i )
+                parseIntoTokens( args[i], tokens);
+        }
+
+        void parseIntoTokens( std::string const& arg, std::vector<Token>& tokens ) {
+            for( std::size_t i = 0; i <= arg.size(); ++i ) {
+                char c = arg[i];
+                if( c == '"' )
+                    inQuotes = !inQuotes;
+                mode = handleMode( i, c, arg, tokens );
+            }
+        }
+        Mode handleMode( std::size_t i, char c, std::string const& arg, std::vector<Token>& tokens ) {
+            switch( mode ) {
+                case None: return handleNone( i, c );
+                case MaybeShortOpt: return handleMaybeShortOpt( i, c );
+                case ShortOpt:
+                case LongOpt:
+                case SlashOpt: return handleOpt( i, c, arg, tokens );
+                case Positional: return handlePositional( i, c, arg, tokens );
+                default: throw std::logic_error( "Unknown mode" );
+            }
+        }
+
+        Mode handleNone( std::size_t i, char c ) {
+            if( inQuotes ) {
+                from = i;
+                return Positional;
+            }
+            switch( c ) {
+                case '-': return MaybeShortOpt;
+#ifdef CLARA_PLATFORM_WINDOWS
+                case '/': from = i+1; return SlashOpt;
+#endif
+                default: from = i; return Positional;
+            }
+        }
+        Mode handleMaybeShortOpt( std::size_t i, char c ) {
+            switch( c ) {
+                case '-': from = i+1; return LongOpt;
+                default: from = i; return ShortOpt;
+            }
+        }
+        Mode handleOpt( std::size_t i, char c, std::string const& arg, std::vector<Token>& tokens ) {
+            if( std::string( ":=\0", 3 ).find( c ) == std::string::npos )
+                return mode;
+
+            std::string optName = arg.substr( from, i-from );
+            if( mode == ShortOpt )
+                for( std::size_t j = 0; j < optName.size(); ++j )
+                    tokens.push_back( Token( Token::ShortOpt, optName.substr( j, 1 ) ) );
+            else if( mode == SlashOpt && optName.size() == 1 )
+                tokens.push_back( Token( Token::ShortOpt, optName ) );
+            else
+                tokens.push_back( Token( Token::LongOpt, optName ) );
+            return None;
+        }
+        Mode handlePositional( std::size_t i, char c, std::string const& arg, std::vector<Token>& tokens ) {
+            if( inQuotes || std::string( "\0", 1 ).find( c ) == std::string::npos )
+                return mode;
+
+            std::string data = arg.substr( from, i-from );
+            tokens.push_back( Token( Token::Positional, data ) );
+            return None;
+        }
+    };
+
+    template<typename ConfigT>
+    struct CommonArgProperties {
+        CommonArgProperties() {}
+        CommonArgProperties( Detail::BoundArgFunction<ConfigT> const& _boundField ) : boundField( _boundField ) {}
+
+        Detail::BoundArgFunction<ConfigT> boundField;
+        std::string description;
+        std::string detail;
+        std::string placeholder; // Only value if boundField takes an arg
+
+        bool takesArg() const {
+            return !placeholder.empty();
+        }
+        void validate() const {
+            if( !boundField.isSet() )
+                throw std::logic_error( "option not bound" );
+        }
+    };
+    struct OptionArgProperties {
+        std::vector<std::string> shortNames;
+        std::string longName;
+
+        bool hasShortName( std::string const& shortName ) const {
+            return std::find( shortNames.begin(), shortNames.end(), shortName ) != shortNames.end();
+        }
+        bool hasLongName( std::string const& _longName ) const {
+            return _longName == longName;
+        }
+    };
+    struct PositionalArgProperties {
+        PositionalArgProperties() : position( -1 ) {}
+        int position; // -1 means non-positional (floating)
+
+        bool isFixedPositional() const {
+            return position != -1;
+        }
+    };
+
+    template<typename ConfigT>
+    class CommandLine {
+
+        struct Arg : CommonArgProperties<ConfigT>, OptionArgProperties, PositionalArgProperties {
+            Arg() {}
+            Arg( Detail::BoundArgFunction<ConfigT> const& _boundField ) : CommonArgProperties<ConfigT>( _boundField ) {}
+
+            using CommonArgProperties<ConfigT>::placeholder; // !TBD
+
+            std::string dbgName() const {
+                if( !longName.empty() )
+                    return "--" + longName;
+                if( !shortNames.empty() )
+                    return "-" + shortNames[0];
+                return "positional args";
+            }
+            std::string commands() const {
+                std::ostringstream oss;
+                bool first = true;
+                std::vector<std::string>::const_iterator it = shortNames.begin(), itEnd = shortNames.end();
+                for(; it != itEnd; ++it ) {
+                    if( first )
+                        first = false;
+                    else
+                        oss << ", ";
+                    oss << "-" << *it;
+                }
+                if( !longName.empty() ) {
+                    if( !first )
+                        oss << ", ";
+                    oss << "--" << longName;
+                }
+                if( !placeholder.empty() )
+                    oss << " <" << placeholder << ">";
+                return oss.str();
+            }
+        };
+
+        typedef CLARA_AUTO_PTR( Arg ) ArgAutoPtr;
+
+        friend void addOptName( Arg& arg, std::string const& optName )
+        {
+            if( optName.empty() )
+                return;
+            if( Detail::startsWith( optName, "--" ) ) {
+                if( !arg.longName.empty() )
+                    throw std::logic_error( "Only one long opt may be specified. '"
+                        + arg.longName
+                        + "' already specified, now attempting to add '"
+                        + optName + "'" );
+                arg.longName = optName.substr( 2 );
+            }
+            else if( Detail::startsWith( optName, "-" ) )
+                arg.shortNames.push_back( optName.substr( 1 ) );
+            else
+                throw std::logic_error( "option must begin with - or --. Option was: '" + optName + "'" );
+        }
+        friend void setPositionalArg( Arg& arg, int position )
+        {
+            arg.position = position;
+        }
+
+        class ArgBuilder {
+        public:
+            ArgBuilder( Arg* arg ) : m_arg( arg ) {}
+
+            // Bind a non-boolean data member (requires placeholder string)
+            template<typename C, typename M>
+            void bind( M C::* field, std::string const& placeholder ) {
+                m_arg->boundField = new Detail::BoundDataMember<C,M>( field );
+                m_arg->placeholder = placeholder;
+            }
+            // Bind a boolean data member (no placeholder required)
+            template<typename C>
+            void bind( bool C::* field ) {
+                m_arg->boundField = new Detail::BoundDataMember<C,bool>( field );
+            }
+
+            // Bind a method taking a single, non-boolean argument (requires a placeholder string)
+            template<typename C, typename M>
+            void bind( void (C::* unaryMethod)( M ), std::string const& placeholder ) {
+                m_arg->boundField = new Detail::BoundUnaryMethod<C,M>( unaryMethod );
+                m_arg->placeholder = placeholder;
+            }
+
+            // Bind a method taking a single, boolean argument (no placeholder string required)
+            template<typename C>
+            void bind( void (C::* unaryMethod)( bool ) ) {
+                m_arg->boundField = new Detail::BoundUnaryMethod<C,bool>( unaryMethod );
+            }
+
+            // Bind a method that takes no arguments (will be called if opt is present)
+            template<typename C>
+            void bind( void (C::* nullaryMethod)() ) {
+                m_arg->boundField = new Detail::BoundNullaryMethod<C>( nullaryMethod );
+            }
+
+            // Bind a free function taking a single argument - the object to operate on (no placeholder string required)
+            template<typename C>
+            void bind( void (* unaryFunction)( C& ) ) {
+                m_arg->boundField = new Detail::BoundUnaryFunction<C>( unaryFunction );
+            }
+
+            // Bind a free function taking a single argument - the object to operate on (requires a placeholder string)
+            template<typename C, typename T>
+            void bind( void (* binaryFunction)( C&, T ), std::string const& placeholder ) {
+                m_arg->boundField = new Detail::BoundBinaryFunction<C, T>( binaryFunction );
+                m_arg->placeholder = placeholder;
+            }
+
+            ArgBuilder& describe( std::string const& description ) {
+                m_arg->description = description;
+                return *this;
+            }
+            ArgBuilder& detail( std::string const& detail ) {
+                m_arg->detail = detail;
+                return *this;
+            }
+
+        protected:
+            Arg* m_arg;
+        };
+
+        class OptBuilder : public ArgBuilder {
+        public:
+            OptBuilder( Arg* arg ) : ArgBuilder( arg ) {}
+            OptBuilder( OptBuilder& other ) : ArgBuilder( other ) {}
+
+            OptBuilder& operator[]( std::string const& optName ) {
+                addOptName( *ArgBuilder::m_arg, optName );
+                return *this;
+            }
+        };
+
+    public:
+
+        CommandLine()
+        :   m_boundProcessName( new Detail::NullBinder<ConfigT>() ),
+            m_highestSpecifiedArgPosition( 0 ),
+            m_throwOnUnrecognisedTokens( false )
+        {}
+        CommandLine( CommandLine const& other )
+        :   m_boundProcessName( other.m_boundProcessName ),
+            m_options ( other.m_options ),
+            m_positionalArgs( other.m_positionalArgs ),
+            m_highestSpecifiedArgPosition( other.m_highestSpecifiedArgPosition ),
+            m_throwOnUnrecognisedTokens( other.m_throwOnUnrecognisedTokens )
+        {
+            if( other.m_floatingArg.get() )
+                m_floatingArg.reset( new Arg( *other.m_floatingArg ) );
+        }
+
+        CommandLine& setThrowOnUnrecognisedTokens( bool shouldThrow = true ) {
+            m_throwOnUnrecognisedTokens = shouldThrow;
+            return *this;
+        }
+
+        OptBuilder operator[]( std::string const& optName ) {
+            m_options.push_back( Arg() );
+            addOptName( m_options.back(), optName );
+            OptBuilder builder( &m_options.back() );
+            return builder;
+        }
+
+        ArgBuilder operator[]( int position ) {
+            m_positionalArgs.insert( std::make_pair( position, Arg() ) );
+            if( position > m_highestSpecifiedArgPosition )
+                m_highestSpecifiedArgPosition = position;
+            setPositionalArg( m_positionalArgs[position], position );
+            ArgBuilder builder( &m_positionalArgs[position] );
+            return builder;
+        }
+
+        // Invoke this with the _ instance
+        ArgBuilder operator[]( UnpositionalTag ) {
+            if( m_floatingArg.get() )
+                throw std::logic_error( "Only one unpositional argument can be added" );
+            m_floatingArg.reset( new Arg() );
+            ArgBuilder builder( m_floatingArg.get() );
+            return builder;
+        }
+
+        template<typename C, typename M>
+        void bindProcessName( M C::* field ) {
+            m_boundProcessName = new Detail::BoundDataMember<C,M>( field );
+        }
+        template<typename C, typename M>
+        void bindProcessName( void (C::*_unaryMethod)( M ) ) {
+            m_boundProcessName = new Detail::BoundUnaryMethod<C,M>( _unaryMethod );
+        }
+
+        void optUsage( std::ostream& os, std::size_t indent = 0, std::size_t width = Detail::consoleWidth ) const {
+            typename std::vector<Arg>::const_iterator itBegin = m_options.begin(), itEnd = m_options.end(), it;
+            std::size_t maxWidth = 0;
+            for( it = itBegin; it != itEnd; ++it )
+                maxWidth = (std::max)( maxWidth, it->commands().size() );
+
+            for( it = itBegin; it != itEnd; ++it ) {
+                Detail::Text usage( it->commands(), Detail::TextAttributes()
+                                                        .setWidth( maxWidth+indent )
+                                                        .setIndent( indent ) );
+                Detail::Text desc( it->description, Detail::TextAttributes()
+                                                        .setWidth( width - maxWidth - 3 ) );
+
+                for( std::size_t i = 0; i < (std::max)( usage.size(), desc.size() ); ++i ) {
+                    std::string usageCol = i < usage.size() ? usage[i] : "";
+                    os << usageCol;
+
+                    if( i < desc.size() && !desc[i].empty() )
+                        os  << std::string( indent + 2 + maxWidth - usageCol.size(), ' ' )
+                            << desc[i];
+                    os << "\n";
+                }
+            }
+        }
+        std::string optUsage() const {
+            std::ostringstream oss;
+            optUsage( oss );
+            return oss.str();
+        }
+
+        void argSynopsis( std::ostream& os ) const {
+            for( int i = 1; i <= m_highestSpecifiedArgPosition; ++i ) {
+                if( i > 1 )
+                    os << " ";
+                typename std::map<int, Arg>::const_iterator it = m_positionalArgs.find( i );
+                if( it != m_positionalArgs.end() )
+                    os << "<" << it->second.placeholder << ">";
+                else if( m_floatingArg.get() )
+                    os << "<" << m_floatingArg->placeholder << ">";
+                else
+                    throw std::logic_error( "non consecutive positional arguments with no floating args" );
+            }
+            // !TBD No indication of mandatory args
+            if( m_floatingArg.get() ) {
+                if( m_highestSpecifiedArgPosition > 1 )
+                    os << " ";
+                os << "[<" << m_floatingArg->placeholder << "> ...]";
+            }
+        }
+        std::string argSynopsis() const {
+            std::ostringstream oss;
+            argSynopsis( oss );
+            return oss.str();
+        }
+
+        void usage( std::ostream& os, std::string const& procName ) const {
+            validate();
+            os << "usage:\n  " << procName << " ";
+            argSynopsis( os );
+            if( !m_options.empty() ) {
+                os << " [options]\n\nwhere options are: \n";
+                optUsage( os, 2 );
+            }
+            os << "\n";
+        }
+        std::string usage( std::string const& procName ) const {
+            std::ostringstream oss;
+            usage( oss, procName );
+            return oss.str();
+        }
+
+        ConfigT parse( std::vector<std::string> const& args ) const {
+            ConfigT config;
+            parseInto( args, config );
+            return config;
+        }
+
+        std::vector<Parser::Token> parseInto( std::vector<std::string> const& args, ConfigT& config ) const {
+            std::string processName = args[0];
+            std::size_t lastSlash = processName.find_last_of( "/\\" );
+            if( lastSlash != std::string::npos )
+                processName = processName.substr( lastSlash+1 );
+            m_boundProcessName.set( config, processName );
+            std::vector<Parser::Token> tokens;
+            Parser parser;
+            parser.parseIntoTokens( args, tokens );
+            return populate( tokens, config );
+        }
+
+        std::vector<Parser::Token> populate( std::vector<Parser::Token> const& tokens, ConfigT& config ) const {
+            validate();
+            std::vector<Parser::Token> unusedTokens = populateOptions( tokens, config );
+            unusedTokens = populateFixedArgs( unusedTokens, config );
+            unusedTokens = populateFloatingArgs( unusedTokens, config );
+            return unusedTokens;
+        }
+
+        std::vector<Parser::Token> populateOptions( std::vector<Parser::Token> const& tokens, ConfigT& config ) const {
+            std::vector<Parser::Token> unusedTokens;
+            std::vector<std::string> errors;
+            for( std::size_t i = 0; i < tokens.size(); ++i ) {
+                Parser::Token const& token = tokens[i];
+                typename std::vector<Arg>::const_iterator it = m_options.begin(), itEnd = m_options.end();
+                for(; it != itEnd; ++it ) {
+                    Arg const& arg = *it;
+
+                    try {
+                        if( ( token.type == Parser::Token::ShortOpt && arg.hasShortName( token.data ) ) ||
+                            ( token.type == Parser::Token::LongOpt && arg.hasLongName( token.data ) ) ) {
+                            if( arg.takesArg() ) {
+                                if( i == tokens.size()-1 || tokens[i+1].type != Parser::Token::Positional )
+                                    errors.push_back( "Expected argument to option: " + token.data );
+                                else
+                                    arg.boundField.set( config, tokens[++i].data );
+                            }
+                            else {
+                                arg.boundField.set( config, "true" );
+                            }
+                            break;
+                        }
+                    }
+                    catch( std::exception& ex ) {
+                        errors.push_back( std::string( ex.what() ) + "\n- while parsing: (" + arg.commands() + ")" );
+                    }
+                }
+                if( it == itEnd ) {
+                    if( token.type == Parser::Token::Positional || !m_throwOnUnrecognisedTokens )
+                        unusedTokens.push_back( token );
+                    else if( errors.empty() && m_throwOnUnrecognisedTokens )
+                        errors.push_back( "unrecognised option: " + token.data );
+                }
+            }
+            if( !errors.empty() ) {
+                std::ostringstream oss;
+                for( std::vector<std::string>::const_iterator it = errors.begin(), itEnd = errors.end();
+                        it != itEnd;
+                        ++it ) {
+                    if( it != errors.begin() )
+                        oss << "\n";
+                    oss << *it;
+                }
+                throw std::runtime_error( oss.str() );
+            }
+            return unusedTokens;
+        }
+        std::vector<Parser::Token> populateFixedArgs( std::vector<Parser::Token> const& tokens, ConfigT& config ) const {
+            std::vector<Parser::Token> unusedTokens;
+            int position = 1;
+            for( std::size_t i = 0; i < tokens.size(); ++i ) {
+                Parser::Token const& token = tokens[i];
+                typename std::map<int, Arg>::const_iterator it = m_positionalArgs.find( position );
+                if( it != m_positionalArgs.end() )
+                    it->second.boundField.set( config, token.data );
+                else
+                    unusedTokens.push_back( token );
+                if( token.type == Parser::Token::Positional )
+                    position++;
+            }
+            return unusedTokens;
+        }
+        std::vector<Parser::Token> populateFloatingArgs( std::vector<Parser::Token> const& tokens, ConfigT& config ) const {
+            if( !m_floatingArg.get() )
+                return tokens;
+            std::vector<Parser::Token> unusedTokens;
+            for( std::size_t i = 0; i < tokens.size(); ++i ) {
+                Parser::Token const& token = tokens[i];
+                if( token.type == Parser::Token::Positional )
+                    m_floatingArg->boundField.set( config, token.data );
+                else
+                    unusedTokens.push_back( token );
+            }
+            return unusedTokens;
+        }
+
+        void validate() const
+        {
+            if( m_options.empty() && m_positionalArgs.empty() && !m_floatingArg.get() )
+                throw std::logic_error( "No options or arguments specified" );
+
+            for( typename std::vector<Arg>::const_iterator  it = m_options.begin(),
+                                                            itEnd = m_options.end();
+                    it != itEnd; ++it )
+                it->validate();
+        }
+
+    private:
+        Detail::BoundArgFunction<ConfigT> m_boundProcessName;
+        std::vector<Arg> m_options;
+        std::map<int, Arg> m_positionalArgs;
+        ArgAutoPtr m_floatingArg;
+        int m_highestSpecifiedArgPosition;
+        bool m_throwOnUnrecognisedTokens;
+    };
+
+} // end namespace Clara
+
+STITCH_CLARA_CLOSE_NAMESPACE
+#undef STITCH_CLARA_OPEN_NAMESPACE
+#undef STITCH_CLARA_CLOSE_NAMESPACE
+
+#endif // TWOBLUECUBES_CLARA_H_INCLUDED
+#undef STITCH_CLARA_OPEN_NAMESPACE
+
+// Restore Clara's value for console width, if present
+#ifdef CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH
+#define CLARA_CONFIG_CONSOLE_WIDTH CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH
+#undef CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH
+#endif
+
+#include <fstream>
+
+namespace Catch {
+
+    inline void abortAfterFirst( ConfigData& config ) { config.abortAfter = 1; }
+    inline void abortAfterX( ConfigData& config, int x ) {
+        if( x < 1 )
+            throw std::runtime_error( "Value after -x or --abortAfter must be greater than zero" );
+        config.abortAfter = x;
+    }
+    inline void addTestOrTags( ConfigData& config, std::string const& _testSpec ) { config.testsOrTags.push_back( _testSpec ); }
+    inline void addReporterName( ConfigData& config, std::string const& _reporterName ) { config.reporterNames.push_back( _reporterName ); }
+
+    inline void addWarning( ConfigData& config, std::string const& _warning ) {
+        if( _warning == "NoAssertions" )
+            config.warnings = static_cast<WarnAbout::What>( config.warnings | WarnAbout::NoAssertions );
+        else
+            throw std::runtime_error( "Unrecognised warning: '" + _warning + "'" );
+    }
+    inline void setOrder( ConfigData& config, std::string const& order ) {
+        if( startsWith( "declared", order ) )
+            config.runOrder = RunTests::InDeclarationOrder;
+        else if( startsWith( "lexical", order ) )
+            config.runOrder = RunTests::InLexicographicalOrder;
+        else if( startsWith( "random", order ) )
+            config.runOrder = RunTests::InRandomOrder;
+        else
+            throw std::runtime_error( "Unrecognised ordering: '" + order + "'" );
+    }
+    inline void setRngSeed( ConfigData& config, std::string const& seed ) {
+        if( seed == "time" ) {
+            config.rngSeed = static_cast<unsigned int>( std::time(0) );
+        }
+        else {
+            std::stringstream ss;
+            ss << seed;
+            ss >> config.rngSeed;
+            if( ss.fail() )
+                throw std::runtime_error( "Argment to --rng-seed should be the word 'time' or a number" );
+        }
+    }
+    inline void setVerbosity( ConfigData& config, int level ) {
+        // !TBD: accept strings?
+        config.verbosity = static_cast<Verbosity::Level>( level );
+    }
+    inline void setShowDurations( ConfigData& config, bool _showDurations ) {
+        config.showDurations = _showDurations
+            ? ShowDurations::Always
+            : ShowDurations::Never;
+    }
+    inline void setUseColour( ConfigData& config, std::string const& value ) {
+        std::string mode = toLower( value );
+
+        if( mode == "yes" )
+            config.useColour = UseColour::Yes;
+        else if( mode == "no" )
+            config.useColour = UseColour::No;
+        else if( mode == "auto" )
+            config.useColour = UseColour::Auto;
+        else
+            throw std::runtime_error( "colour mode must be one of: auto, yes or no" );
+    }
+    inline void forceColour( ConfigData& config ) {
+        config.useColour = UseColour::Yes;
+    }
+    inline void loadTestNamesFromFile( ConfigData& config, std::string const& _filename ) {
+        std::ifstream f( _filename.c_str() );
+        if( !f.is_open() )
+            throw std::domain_error( "Unable to load input file: " + _filename );
+
+        std::string line;
+        while( std::getline( f, line ) ) {
+            line = trim(line);
+            if( !line.empty() && !startsWith( line, "#" ) ) {
+                if( !startsWith( line, "\"" ) )
+                    line = "\"" + line + "\"";
+                addTestOrTags( config, line + "," );
+            }
+        }
+    }
+
+    inline Clara::CommandLine<ConfigData> makeCommandLineParser() {
+
+        using namespace Clara;
+        CommandLine<ConfigData> cli;
+
+        cli.bindProcessName( &ConfigData::processName );
+
+        cli["-?"]["-h"]["--help"]
+            .describe( "display usage information" )
+            .bind( &ConfigData::showHelp );
+
+        cli["-l"]["--list-tests"]
+            .describe( "list all/matching test cases" )
+            .bind( &ConfigData::listTests );
+
+        cli["-t"]["--list-tags"]
+            .describe( "list all/matching tags" )
+            .bind( &ConfigData::listTags );
+
+        cli["-s"]["--success"]
+            .describe( "include successful tests in output" )
+            .bind( &ConfigData::showSuccessfulTests );
+
+        cli["-b"]["--break"]
+            .describe( "break into debugger on failure" )
+            .bind( &ConfigData::shouldDebugBreak );
+
+        cli["-e"]["--nothrow"]
+            .describe( "skip exception tests" )
+            .bind( &ConfigData::noThrow );
+
+        cli["-i"]["--invisibles"]
+            .describe( "show invisibles (tabs, newlines)" )
+            .bind( &ConfigData::showInvisibles );
+
+        cli["-o"]["--out"]
+            .describe( "output filename" )
+            .bind( &ConfigData::outputFilename, "filename" );
+
+        cli["-r"]["--reporter"]
+//            .placeholder( "name[:filename]" )
+            .describe( "reporter to use (defaults to console)" )
+            .bind( &addReporterName, "name" );
+
+        cli["-n"]["--name"]
+            .describe( "suite name" )
+            .bind( &ConfigData::name, "name" );
+
+        cli["-a"]["--abort"]
+            .describe( "abort at first failure" )
+            .bind( &abortAfterFirst );
+
+        cli["-x"]["--abortx"]
+            .describe( "abort after x failures" )
+            .bind( &abortAfterX, "no. failures" );
+
+        cli["-w"]["--warn"]
+            .describe( "enable warnings" )
+            .bind( &addWarning, "warning name" );
+
+// - needs updating if reinstated
+//        cli.into( &setVerbosity )
+//            .describe( "level of verbosity (0=no output)" )
+//            .shortOpt( "v")
+//            .longOpt( "verbosity" )
+//            .placeholder( "level" );
+
+        cli[_]
+            .describe( "which test or tests to use" )
+            .bind( &addTestOrTags, "test name, pattern or tags" );
+
+        cli["-d"]["--durations"]
+            .describe( "show test durations" )
+            .bind( &setShowDurations, "yes|no" );
+
+        cli["-f"]["--input-file"]
+            .describe( "load test names to run from a file" )
+            .bind( &loadTestNamesFromFile, "filename" );
+
+        cli["-#"]["--filenames-as-tags"]
+            .describe( "adds a tag for the filename" )
+            .bind( &ConfigData::filenamesAsTags );
+
+        // Less common commands which don't have a short form
+        cli["--list-test-names-only"]
+            .describe( "list all/matching test cases names only" )
+            .bind( &ConfigData::listTestNamesOnly );
+
+        cli["--list-reporters"]
+            .describe( "list all reporters" )
+            .bind( &ConfigData::listReporters );
+
+        cli["--order"]
+            .describe( "test case order (defaults to decl)" )
+            .bind( &setOrder, "decl|lex|rand" );
+
+        cli["--rng-seed"]
+            .describe( "set a specific seed for random numbers" )
+            .bind( &setRngSeed, "'time'|number" );
+
+        cli["--force-colour"]
+            .describe( "force colourised output (deprecated)" )
+            .bind( &forceColour );
+
+        cli["--use-colour"]
+            .describe( "should output be colourised" )
+            .bind( &setUseColour, "yes|no" );
+
+        return cli;
+    }
+
+} // end namespace Catch
+
+// #included from: internal/catch_list.hpp
+#define TWOBLUECUBES_CATCH_LIST_HPP_INCLUDED
+
+// #included from: catch_text.h
+#define TWOBLUECUBES_CATCH_TEXT_H_INCLUDED
+
+#define TBC_TEXT_FORMAT_CONSOLE_WIDTH CATCH_CONFIG_CONSOLE_WIDTH
+
+#define CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE Catch
+// #included from: ../external/tbc_text_format.h
+// Only use header guard if we are not using an outer namespace
+#ifndef CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE
+# ifdef TWOBLUECUBES_TEXT_FORMAT_H_INCLUDED
+#  ifndef TWOBLUECUBES_TEXT_FORMAT_H_ALREADY_INCLUDED
+#   define TWOBLUECUBES_TEXT_FORMAT_H_ALREADY_INCLUDED
+#  endif
+# else
+#  define TWOBLUECUBES_TEXT_FORMAT_H_INCLUDED
+# endif
+#endif
+#ifndef TWOBLUECUBES_TEXT_FORMAT_H_ALREADY_INCLUDED
+#include <string>
+#include <vector>
+#include <sstream>
+
+// Use optional outer namespace
+#ifdef CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE
+namespace CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE {
+#endif
+
+namespace Tbc {
+
+#ifdef TBC_TEXT_FORMAT_CONSOLE_WIDTH
+    const unsigned int consoleWidth = TBC_TEXT_FORMAT_CONSOLE_WIDTH;
+#else
+    const unsigned int consoleWidth = 80;
+#endif
+
+    struct TextAttributes {
+        TextAttributes()
+        :   initialIndent( std::string::npos ),
+            indent( 0 ),
+            width( consoleWidth-1 ),
+            tabChar( '\t' )
+        {}
+
+        TextAttributes& setInitialIndent( std::size_t _value )  { initialIndent = _value; return *this; }
+        TextAttributes& setIndent( std::size_t _value )         { indent = _value; return *this; }
+        TextAttributes& setWidth( std::size_t _value )          { width = _value; return *this; }
+        TextAttributes& setTabChar( char _value )               { tabChar = _value; return *this; }
+
+        std::size_t initialIndent;  // indent of first line, or npos
+        std::size_t indent;         // indent of subsequent lines, or all if initialIndent is npos
+        std::size_t width;          // maximum width of text, including indent. Longer text will wrap
+        char tabChar;               // If this char is seen the indent is changed to current pos
+    };
+
+    class Text {
+    public:
+        Text( std::string const& _str, TextAttributes const& _attr = TextAttributes() )
+        : attr( _attr )
+        {
+            std::string wrappableChars = " [({.,/|\\-";
+            std::size_t indent = _attr.initialIndent != std::string::npos
+                ? _attr.initialIndent
+                : _attr.indent;
+            std::string remainder = _str;
+
+            while( !remainder.empty() ) {
+                if( lines.size() >= 1000 ) {
+                    lines.push_back( "... message truncated due to excessive size" );
+                    return;
+                }
+                std::size_t tabPos = std::string::npos;
+                std::size_t width = (std::min)( remainder.size(), _attr.width - indent );
+                std::size_t pos = remainder.find_first_of( '\n' );
+                if( pos <= width ) {
+                    width = pos;
+                }
+                pos = remainder.find_last_of( _attr.tabChar, width );
+                if( pos != std::string::npos ) {
+                    tabPos = pos;
+                    if( remainder[width] == '\n' )
+                        width--;
+                    remainder = remainder.substr( 0, tabPos ) + remainder.substr( tabPos+1 );
+                }
+
+                if( width == remainder.size() ) {
+                    spliceLine( indent, remainder, width );
+                }
+                else if( remainder[width] == '\n' ) {
+                    spliceLine( indent, remainder, width );
+                    if( width <= 1 || remainder.size() != 1 )
+                        remainder = remainder.substr( 1 );
+                    indent = _attr.indent;
+                }
+                else {
+                    pos = remainder.find_last_of( wrappableChars, width );
+                    if( pos != std::string::npos && pos > 0 ) {
+                        spliceLine( indent, remainder, pos );
+                        if( remainder[0] == ' ' )
+                            remainder = remainder.substr( 1 );
+                    }
+                    else {
+                        spliceLine( indent, remainder, width-1 );
+                        lines.back() += "-";
+                    }
+                    if( lines.size() == 1 )
+                        indent = _attr.indent;
+                    if( tabPos != std::string::npos )
+                        indent += tabPos;
+                }
+            }
+        }
+
+        void spliceLine( std::size_t _indent, std::string& _remainder, std::size_t _pos ) {
+            lines.push_back( std::string( _indent, ' ' ) + _remainder.substr( 0, _pos ) );
+            _remainder = _remainder.substr( _pos );
+        }
+
+        typedef std::vector<std::string>::const_iterator const_iterator;
+
+        const_iterator begin() const { return lines.begin(); }
+        const_iterator end() const { return lines.end(); }
+        std::string const& last() const { return lines.back(); }
+        std::size_t size() const { return lines.size(); }
+        std::string const& operator[]( std::size_t _index ) const { return lines[_index]; }
+        std::string toString() const {
+            std::ostringstream oss;
+            oss << *this;
+            return oss.str();
+        }
+
+        inline friend std::ostream& operator << ( std::ostream& _stream, Text const& _text ) {
+            for( Text::const_iterator it = _text.begin(), itEnd = _text.end();
+                it != itEnd; ++it ) {
+                if( it != _text.begin() )
+                    _stream << "\n";
+                _stream << *it;
+            }
+            return _stream;
+        }
+
+    private:
+        std::string str;
+        TextAttributes attr;
+        std::vector<std::string> lines;
+    };
+
+} // end namespace Tbc
+
+#ifdef CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE
+} // end outer namespace
+#endif
+
+#endif // TWOBLUECUBES_TEXT_FORMAT_H_ALREADY_INCLUDED
+#undef CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE
+
+namespace Catch {
+    using Tbc::Text;
+    using Tbc::TextAttributes;
+}
+
+// #included from: catch_console_colour.hpp
+#define TWOBLUECUBES_CATCH_CONSOLE_COLOUR_HPP_INCLUDED
+
+namespace Catch {
+
+    struct Colour {
+        enum Code {
+            None = 0,
+
+            White,
+            Red,
+            Green,
+            Blue,
+            Cyan,
+            Yellow,
+            Grey,
+
+            Bright = 0x10,
+
+            BrightRed = Bright | Red,
+            BrightGreen = Bright | Green,
+            LightGrey = Bright | Grey,
+            BrightWhite = Bright | White,
+
+            // By intention
+            FileName = LightGrey,
+            Warning = Yellow,
+            ResultError = BrightRed,
+            ResultSuccess = BrightGreen,
+            ResultExpectedFailure = Warning,
+
+            Error = BrightRed,
+            Success = Green,
+
+            OriginalExpression = Cyan,
+            ReconstructedExpression = Yellow,
+
+            SecondaryText = LightGrey,
+            Headers = White
+        };
+
+        // Use constructed object for RAII guard
+        Colour( Code _colourCode );
+        Colour( Colour const& other );
+        ~Colour();
+
+        // Use static method for one-shot changes
+        static void use( Code _colourCode );
+
+    private:
+        bool m_moved;
+    };
+
+    inline std::ostream& operator << ( std::ostream& os, Colour const& ) { return os; }
+
+} // end namespace Catch
+
+// #included from: catch_interfaces_reporter.h
+#define TWOBLUECUBES_CATCH_INTERFACES_REPORTER_H_INCLUDED
+
+#include <string>
+#include <ostream>
+#include <map>
+#include <assert.h>
+
+namespace Catch
+{
+    struct ReporterConfig {
+        explicit ReporterConfig( Ptr<IConfig const> const& _fullConfig )
+        :   m_stream( &_fullConfig->stream() ), m_fullConfig( _fullConfig ) {}
+
+        ReporterConfig( Ptr<IConfig const> const& _fullConfig, std::ostream& _stream )
+        :   m_stream( &_stream ), m_fullConfig( _fullConfig ) {}
+
+        std::ostream& stream() const    { return *m_stream; }
+        Ptr<IConfig const> fullConfig() const { return m_fullConfig; }
+
+    private:
+        std::ostream* m_stream;
+        Ptr<IConfig const> m_fullConfig;
+    };
+
+    struct ReporterPreferences {
+        ReporterPreferences()
+        : shouldRedirectStdOut( false )
+        {}
+
+        bool shouldRedirectStdOut;
+    };
+
+    template<typename T>
+    struct LazyStat : Option<T> {
+        LazyStat() : used( false ) {}
+        LazyStat& operator=( T const& _value ) {
+            Option<T>::operator=( _value );
+            used = false;
+            return *this;
+        }
+        void reset() {
+            Option<T>::reset();
+            used = false;
+        }
+        bool used;
+    };
+
+    struct TestRunInfo {
+        TestRunInfo( std::string const& _name ) : name( _name ) {}
+        std::string name;
+    };
+    struct GroupInfo {
+        GroupInfo(  std::string const& _name,
+                    std::size_t _groupIndex,
+                    std::size_t _groupsCount )
+        :   name( _name ),
+            groupIndex( _groupIndex ),
+            groupsCounts( _groupsCount )
+        {}
+
+        std::string name;
+        std::size_t groupIndex;
+        std::size_t groupsCounts;
+    };
+
+    struct AssertionStats {
+        AssertionStats( AssertionResult const& _assertionResult,
+                        std::vector<MessageInfo> const& _infoMessages,
+                        Totals const& _totals )
+        :   assertionResult( _assertionResult ),
+            infoMessages( _infoMessages ),
+            totals( _totals )
+        {
+            if( assertionResult.hasMessage() ) {
+                // Copy message into messages list.
+                // !TBD This should have been done earlier, somewhere
+                MessageBuilder builder( assertionResult.getTestMacroName(), assertionResult.getSourceInfo(), assertionResult.getResultType() );
+                builder << assertionResult.getMessage();
+                builder.m_info.message = builder.m_stream.str();
+
+                infoMessages.push_back( builder.m_info );
+            }
+        }
+        virtual ~AssertionStats();
+
+#  ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS
+        AssertionStats( AssertionStats const& )              = default;
+        AssertionStats( AssertionStats && )                  = default;
+        AssertionStats& operator = ( AssertionStats const& ) = default;
+        AssertionStats& operator = ( AssertionStats && )     = default;
+#  endif
+
+        AssertionResult assertionResult;
+        std::vector<MessageInfo> infoMessages;
+        Totals totals;
+    };
+
+    struct SectionStats {
+        SectionStats(   SectionInfo const& _sectionInfo,
+                        Counts const& _assertions,
+                        double _durationInSeconds,
+                        bool _missingAssertions )
+        :   sectionInfo( _sectionInfo ),
+            assertions( _assertions ),
+            durationInSeconds( _durationInSeconds ),
+            missingAssertions( _missingAssertions )
+        {}
+        virtual ~SectionStats();
+#  ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS
+        SectionStats( SectionStats const& )              = default;
+        SectionStats( SectionStats && )                  = default;
+        SectionStats& operator = ( SectionStats const& ) = default;
+        SectionStats& operator = ( SectionStats && )     = default;
+#  endif
+
+        SectionInfo sectionInfo;
+        Counts assertions;
+        double durationInSeconds;
+        bool missingAssertions;
+    };
+
+    struct TestCaseStats {
+        TestCaseStats(  TestCaseInfo const& _testInfo,
+                        Totals const& _totals,
+                        std::string const& _stdOut,
+                        std::string const& _stdErr,
+                        bool _aborting )
+        : testInfo( _testInfo ),
+            totals( _totals ),
+            stdOut( _stdOut ),
+            stdErr( _stdErr ),
+            aborting( _aborting )
+        {}
+        virtual ~TestCaseStats();
+
+#  ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS
+        TestCaseStats( TestCaseStats const& )              = default;
+        TestCaseStats( TestCaseStats && )                  = default;
+        TestCaseStats& operator = ( TestCaseStats const& ) = default;
+        TestCaseStats& operator = ( TestCaseStats && )     = default;
+#  endif
+
+        TestCaseInfo testInfo;
+        Totals totals;
+        std::string stdOut;
+        std::string stdErr;
+        bool aborting;
+    };
+
+    struct TestGroupStats {
+        TestGroupStats( GroupInfo const& _groupInfo,
+                        Totals const& _totals,
+                        bool _aborting )
+        :   groupInfo( _groupInfo ),
+            totals( _totals ),
+            aborting( _aborting )
+        {}
+        TestGroupStats( GroupInfo const& _groupInfo )
+        :   groupInfo( _groupInfo ),
+            aborting( false )
+        {}
+        virtual ~TestGroupStats();
+
+#  ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS
+        TestGroupStats( TestGroupStats const& )              = default;
+        TestGroupStats( TestGroupStats && )                  = default;
+        TestGroupStats& operator = ( TestGroupStats const& ) = default;
+        TestGroupStats& operator = ( TestGroupStats && )     = default;
+#  endif
+
+        GroupInfo groupInfo;
+        Totals totals;
+        bool aborting;
+    };
+
+    struct TestRunStats {
+        TestRunStats(   TestRunInfo const& _runInfo,
+                        Totals const& _totals,
+                        bool _aborting )
+        :   runInfo( _runInfo ),
+            totals( _totals ),
+            aborting( _aborting )
+        {}
+        virtual ~TestRunStats();
+
+#  ifndef CATCH_CONFIG_CPP11_GENERATED_METHODS
+        TestRunStats( TestRunStats const& _other )
+        :   runInfo( _other.runInfo ),
+            totals( _other.totals ),
+            aborting( _other.aborting )
+        {}
+#  else
+        TestRunStats( TestRunStats const& )              = default;
+        TestRunStats( TestRunStats && )                  = default;
+        TestRunStats& operator = ( TestRunStats const& ) = default;
+        TestRunStats& operator = ( TestRunStats && )     = default;
+#  endif
+
+        TestRunInfo runInfo;
+        Totals totals;
+        bool aborting;
+    };
+
+    class MultipleReporters;
+
+    struct IStreamingReporter : IShared {
+        virtual ~IStreamingReporter();
+
+        // Implementing class must also provide the following static method:
+        // static std::string getDescription();
+
+        virtual ReporterPreferences getPreferences() const = 0;
+
+        virtual void noMatchingTestCases( std::string const& spec ) = 0;
+
+        virtual void testRunStarting( TestRunInfo const& testRunInfo ) = 0;
+        virtual void testGroupStarting( GroupInfo const& groupInfo ) = 0;
+
+        virtual void testCaseStarting( TestCaseInfo const& testInfo ) = 0;
+        virtual void sectionStarting( SectionInfo const& sectionInfo ) = 0;
+
+        virtual void assertionStarting( AssertionInfo const& assertionInfo ) = 0;
+
+        // The return value indicates if the messages buffer should be cleared:
+        virtual bool assertionEnded( AssertionStats const& assertionStats ) = 0;
+
+        virtual void sectionEnded( SectionStats const& sectionStats ) = 0;
+        virtual void testCaseEnded( TestCaseStats const& testCaseStats ) = 0;
+        virtual void testGroupEnded( TestGroupStats const& testGroupStats ) = 0;
+        virtual void testRunEnded( TestRunStats const& testRunStats ) = 0;
+
+        virtual void skipTest( TestCaseInfo const& testInfo ) = 0;
+
+        virtual MultipleReporters* tryAsMulti() { return CATCH_NULL; }
+    };
+
+    struct IReporterFactory : IShared {
+        virtual ~IReporterFactory();
+        virtual IStreamingReporter* create( ReporterConfig const& config ) const = 0;
+        virtual std::string getDescription() const = 0;
+    };
+
+    struct IReporterRegistry {
+        typedef std::map<std::string, Ptr<IReporterFactory> > FactoryMap;
+        typedef std::vector<Ptr<IReporterFactory> > Listeners;
+
+        virtual ~IReporterRegistry();
+        virtual IStreamingReporter* create( std::string const& name, Ptr<IConfig const> const& config ) const = 0;
+        virtual FactoryMap const& getFactories() const = 0;
+        virtual Listeners const& getListeners() const = 0;
+    };
+
+    Ptr<IStreamingReporter> addReporter( Ptr<IStreamingReporter> const& existingReporter, Ptr<IStreamingReporter> const& additionalReporter );
+
+}
+
+#include <limits>
+#include <algorithm>
+
+namespace Catch {
+
+    inline std::size_t listTests( Config const& config ) {
+
+        TestSpec testSpec = config.testSpec();
+        if( config.testSpec().hasFilters() )
+            Catch::cout() << "Matching test cases:\n";
+        else {
+            Catch::cout() << "All available test cases:\n";
+            testSpec = TestSpecParser( ITagAliasRegistry::get() ).parse( "*" ).testSpec();
+        }
+
+        std::size_t matchedTests = 0;
+        TextAttributes nameAttr, tagsAttr;
+        nameAttr.setInitialIndent( 2 ).setIndent( 4 );
+        tagsAttr.setIndent( 6 );
+
+        std::vector<TestCase> matchedTestCases = filterTests( getAllTestCasesSorted( config ), testSpec, config );
+        for( std::vector<TestCase>::const_iterator it = matchedTestCases.begin(), itEnd = matchedTestCases.end();
+                it != itEnd;
+                ++it ) {
+            matchedTests++;
+            TestCaseInfo const& testCaseInfo = it->getTestCaseInfo();
+            Colour::Code colour = testCaseInfo.isHidden()
+                ? Colour::SecondaryText
+                : Colour::None;
+            Colour colourGuard( colour );
+
+            Catch::cout() << Text( testCaseInfo.name, nameAttr ) << std::endl;
+            if( !testCaseInfo.tags.empty() )
+                Catch::cout() << Text( testCaseInfo.tagsAsString, tagsAttr ) << std::endl;
+        }
+
+        if( !config.testSpec().hasFilters() )
+            Catch::cout() << pluralise( matchedTests, "test case" ) << "\n" << std::endl;
+        else
+            Catch::cout() << pluralise( matchedTests, "matching test case" ) << "\n" << std::endl;
+        return matchedTests;
+    }
+
+    inline std::size_t listTestsNamesOnly( Config const& config ) {
+        TestSpec testSpec = config.testSpec();
+        if( !config.testSpec().hasFilters() )
+            testSpec = TestSpecParser( ITagAliasRegistry::get() ).parse( "*" ).testSpec();
+        std::size_t matchedTests = 0;
+        std::vector<TestCase> matchedTestCases = filterTests( getAllTestCasesSorted( config ), testSpec, config );
+        for( std::vector<TestCase>::const_iterator it = matchedTestCases.begin(), itEnd = matchedTestCases.end();
+                it != itEnd;
+                ++it ) {
+            matchedTests++;
+            TestCaseInfo const& testCaseInfo = it->getTestCaseInfo();
+            if( startsWith( testCaseInfo.name, "#" ) )
+               Catch::cout() << "\"" << testCaseInfo.name << "\"" << std::endl;
+            else
+               Catch::cout() << testCaseInfo.name << std::endl;
+        }
+        return matchedTests;
+    }
+
+    struct TagInfo {
+        TagInfo() : count ( 0 ) {}
+        void add( std::string const& spelling ) {
+            ++count;
+            spellings.insert( spelling );
+        }
+        std::string all() const {
+            std::string out;
+            for( std::set<std::string>::const_iterator it = spellings.begin(), itEnd = spellings.end();
+                        it != itEnd;
+                        ++it )
+                out += "[" + *it + "]";
+            return out;
+        }
+        std::set<std::string> spellings;
+        std::size_t count;
+    };
+
+    inline std::size_t listTags( Config const& config ) {
+        TestSpec testSpec = config.testSpec();
+        if( config.testSpec().hasFilters() )
+            Catch::cout() << "Tags for matching test cases:\n";
+        else {
+            Catch::cout() << "All available tags:\n";
+            testSpec = TestSpecParser( ITagAliasRegistry::get() ).parse( "*" ).testSpec();
+        }
+
+        std::map<std::string, TagInfo> tagCounts;
+
+        std::vector<TestCase> matchedTestCases = filterTests( getAllTestCasesSorted( config ), testSpec, config );
+        for( std::vector<TestCase>::const_iterator it = matchedTestCases.begin(), itEnd = matchedTestCases.end();
+                it != itEnd;
+                ++it ) {
+            for( std::set<std::string>::const_iterator  tagIt = it->getTestCaseInfo().tags.begin(),
+                                                        tagItEnd = it->getTestCaseInfo().tags.end();
+                    tagIt != tagItEnd;
+                    ++tagIt ) {
+                std::string tagName = *tagIt;
+                std::string lcaseTagName = toLower( tagName );
+                std::map<std::string, TagInfo>::iterator countIt = tagCounts.find( lcaseTagName );
+                if( countIt == tagCounts.end() )
+                    countIt = tagCounts.insert( std::make_pair( lcaseTagName, TagInfo() ) ).first;
+                countIt->second.add( tagName );
+            }
+        }
+
+        for( std::map<std::string, TagInfo>::const_iterator countIt = tagCounts.begin(),
+                                                            countItEnd = tagCounts.end();
+                countIt != countItEnd;
+                ++countIt ) {
+            std::ostringstream oss;
+            oss << "  " << std::setw(2) << countIt->second.count << "  ";
+            Text wrapper( countIt->second.all(), TextAttributes()
+                                                    .setInitialIndent( 0 )
+                                                    .setIndent( oss.str().size() )
+                                                    .setWidth( CATCH_CONFIG_CONSOLE_WIDTH-10 ) );
+            Catch::cout() << oss.str() << wrapper << "\n";
+        }
+        Catch::cout() << pluralise( tagCounts.size(), "tag" ) << "\n" << std::endl;
+        return tagCounts.size();
+    }
+
+    inline std::size_t listReporters( Config const& /*config*/ ) {
+        Catch::cout() << "Available reporters:\n";
+        IReporterRegistry::FactoryMap const& factories = getRegistryHub().getReporterRegistry().getFactories();
+        IReporterRegistry::FactoryMap::const_iterator itBegin = factories.begin(), itEnd = factories.end(), it;
+        std::size_t maxNameLen = 0;
+        for(it = itBegin; it != itEnd; ++it )
+            maxNameLen = (std::max)( maxNameLen, it->first.size() );
+
+        for(it = itBegin; it != itEnd; ++it ) {
+            Text wrapper( it->second->getDescription(), TextAttributes()
+                                                        .setInitialIndent( 0 )
+                                                        .setIndent( 7+maxNameLen )
+                                                        .setWidth( CATCH_CONFIG_CONSOLE_WIDTH - maxNameLen-8 ) );
+            Catch::cout() << "  "
+                    << it->first
+                    << ":"
+                    << std::string( maxNameLen - it->first.size() + 2, ' ' )
+                    << wrapper << "\n";
+        }
+        Catch::cout() << std::endl;
+        return factories.size();
+    }
+
+    inline Option<std::size_t> list( Config const& config ) {
+        Option<std::size_t> listedCount;
+        if( config.listTests() )
+            listedCount = listedCount.valueOr(0) + listTests( config );
+        if( config.listTestNamesOnly() )
+            listedCount = listedCount.valueOr(0) + listTestsNamesOnly( config );
+        if( config.listTags() )
+            listedCount = listedCount.valueOr(0) + listTags( config );
+        if( config.listReporters() )
+            listedCount = listedCount.valueOr(0) + listReporters( config );
+        return listedCount;
+    }
+
+} // end namespace Catch
+
+// #included from: internal/catch_run_context.hpp
+#define TWOBLUECUBES_CATCH_RUNNER_IMPL_HPP_INCLUDED
+
+// #included from: catch_test_case_tracker.hpp
+#define TWOBLUECUBES_CATCH_TEST_CASE_TRACKER_HPP_INCLUDED
+
+#include <map>
+#include <string>
+#include <assert.h>
+#include <vector>
+
+namespace Catch {
+namespace TestCaseTracking {
+
+    struct ITracker : SharedImpl<> {
+        virtual ~ITracker();
+
+        // static queries
+        virtual std::string name() const = 0;
+
+        // dynamic queries
+        virtual bool isComplete() const = 0; // Successfully completed or failed
+        virtual bool isSuccessfullyCompleted() const = 0;
+        virtual bool isOpen() const = 0; // Started but not complete
+        virtual bool hasChildren() const = 0;
+
+        virtual ITracker& parent() = 0;
+
+        // actions
+        virtual void close() = 0; // Successfully complete
+        virtual void fail() = 0;
+        virtual void markAsNeedingAnotherRun() = 0;
+
+        virtual void addChild( Ptr<ITracker> const& child ) = 0;
+        virtual ITracker* findChild( std::string const& name ) = 0;
+        virtual void openChild() = 0;
+
+        // Debug/ checking
+        virtual bool isSectionTracker() const = 0;
+        virtual bool isIndexTracker() const = 0;
+    };
+
+    class TrackerContext {
+
+        enum RunState {
+            NotStarted,
+            Executing,
+            CompletedCycle
+        };
+
+        Ptr<ITracker> m_rootTracker;
+        ITracker* m_currentTracker;
+        RunState m_runState;
+
+    public:
+
+        static TrackerContext& instance() {
+            static TrackerContext s_instance;
+            return s_instance;
+        }
+
+        TrackerContext()
+        :   m_currentTracker( CATCH_NULL ),
+            m_runState( NotStarted )
+        {}
+
+        ITracker& startRun();
+
+        void endRun() {
+            m_rootTracker.reset();
+            m_currentTracker = CATCH_NULL;
+            m_runState = NotStarted;
+        }
+
+        void startCycle() {
+            m_currentTracker = m_rootTracker.get();
+            m_runState = Executing;
+        }
+        void completeCycle() {
+            m_runState = CompletedCycle;
+        }
+
+        bool completedCycle() const {
+            return m_runState == CompletedCycle;
+        }
+        ITracker& currentTracker() {
+            return *m_currentTracker;
+        }
+        void setCurrentTracker( ITracker* tracker ) {
+            m_currentTracker = tracker;
+        }
+    };
+
+    class TrackerBase : public ITracker {
+    protected:
+        enum CycleState {
+            NotStarted,
+            Executing,
+            ExecutingChildren,
+            NeedsAnotherRun,
+            CompletedSuccessfully,
+            Failed
+        };
+        class TrackerHasName {
+            std::string m_name;
+        public:
+            TrackerHasName( std::string const& name ) : m_name( name ) {}
+            bool operator ()( Ptr<ITracker> const& tracker ) {
+                return tracker->name() == m_name;
+            }
+        };
+        typedef std::vector<Ptr<ITracker> > Children;
+        std::string m_name;
+        TrackerContext& m_ctx;
+        ITracker* m_parent;
+        Children m_children;
+        CycleState m_runState;
+    public:
+        TrackerBase( std::string const& name, TrackerContext& ctx, ITracker* parent )
+        :   m_name( name ),
+            m_ctx( ctx ),
+            m_parent( parent ),
+            m_runState( NotStarted )
+        {}
+        virtual ~TrackerBase();
+
+        virtual std::string name() const CATCH_OVERRIDE {
+            return m_name;
+        }
+        virtual bool isComplete() const CATCH_OVERRIDE {
+            return m_runState == CompletedSuccessfully || m_runState == Failed;
+        }
+        virtual bool isSuccessfullyCompleted() const CATCH_OVERRIDE {
+            return m_runState == CompletedSuccessfully;
+        }
+        virtual bool isOpen() const CATCH_OVERRIDE {
+            return m_runState != NotStarted && !isComplete();
+        }
+        virtual bool hasChildren() const CATCH_OVERRIDE {
+            return !m_children.empty();
+        }
+
+        virtual void addChild( Ptr<ITracker> const& child ) CATCH_OVERRIDE {
+            m_children.push_back( child );
+        }
+
+        virtual ITracker* findChild( std::string const& name ) CATCH_OVERRIDE {
+            Children::const_iterator it = std::find_if( m_children.begin(), m_children.end(), TrackerHasName( name ) );
+            return( it != m_children.end() )
+                ? it->get()
+                : CATCH_NULL;
+        }
+        virtual ITracker& parent() CATCH_OVERRIDE {
+            assert( m_parent ); // Should always be non-null except for root
+            return *m_parent;
+        }
+
+        virtual void openChild() CATCH_OVERRIDE {
+            if( m_runState != ExecutingChildren ) {
+                m_runState = ExecutingChildren;
+                if( m_parent )
+                    m_parent->openChild();
+            }
+        }
+
+        virtual bool isSectionTracker() const CATCH_OVERRIDE { return false; }
+        virtual bool isIndexTracker() const CATCH_OVERRIDE { return false; }
+
+        void open() {
+            m_runState = Executing;
+            moveToThis();
+            if( m_parent )
+                m_parent->openChild();
+        }
+
+        virtual void close() CATCH_OVERRIDE {
+
+            // Close any still open children (e.g. generators)
+            while( &m_ctx.currentTracker() != this )
+                m_ctx.currentTracker().close();
+
+            switch( m_runState ) {
+                case NotStarted:
+                case CompletedSuccessfully:
+                case Failed:
+                    throw std::logic_error( "Illogical state" );
+
+                case NeedsAnotherRun:
+                    break;;
+
+                case Executing:
+                    m_runState = CompletedSuccessfully;
+                    break;
+                case ExecutingChildren:
+                    if( m_children.empty() || m_children.back()->isComplete() )
+                        m_runState = CompletedSuccessfully;
+                    break;
+
+                default:
+                    throw std::logic_error( "Unexpected state" );
+            }
+            moveToParent();
+            m_ctx.completeCycle();
+        }
+        virtual void fail() CATCH_OVERRIDE {
+            m_runState = Failed;
+            if( m_parent )
+                m_parent->markAsNeedingAnotherRun();
+            moveToParent();
+            m_ctx.completeCycle();
+        }
+        virtual void markAsNeedingAnotherRun() CATCH_OVERRIDE {
+            m_runState = NeedsAnotherRun;
+        }
+    private:
+        void moveToParent() {
+            assert( m_parent );
+            m_ctx.setCurrentTracker( m_parent );
+        }
+        void moveToThis() {
+            m_ctx.setCurrentTracker( this );
+        }
+    };
+
+    class SectionTracker : public TrackerBase {
+    public:
+        SectionTracker( std::string const& name, TrackerContext& ctx, ITracker* parent )
+        :   TrackerBase( name, ctx, parent )
+        {}
+        virtual ~SectionTracker();
+
+        virtual bool isSectionTracker() const CATCH_OVERRIDE { return true; }
+
+        static SectionTracker& acquire( TrackerContext& ctx, std::string const& name ) {
+            SectionTracker* section = CATCH_NULL;
+
+            ITracker& currentTracker = ctx.currentTracker();
+            if( ITracker* childTracker = currentTracker.findChild( name ) ) {
+                assert( childTracker );
+                assert( childTracker->isSectionTracker() );
+                section = static_cast<SectionTracker*>( childTracker );
+            }
+            else {
+                section = new SectionTracker( name, ctx, &currentTracker );
+                currentTracker.addChild( section );
+            }
+            if( !ctx.completedCycle() && !section->isComplete() ) {
+
+                section->open();
+            }
+            return *section;
+        }
+    };
+
+    class IndexTracker : public TrackerBase {
+        int m_size;
+        int m_index;
+    public:
+        IndexTracker( std::string const& name, TrackerContext& ctx, ITracker* parent, int size )
+        :   TrackerBase( name, ctx, parent ),
+            m_size( size ),
+            m_index( -1 )
+        {}
+        virtual ~IndexTracker();
+
+        virtual bool isIndexTracker() const CATCH_OVERRIDE { return true; }
+
+        static IndexTracker& acquire( TrackerContext& ctx, std::string const& name, int size ) {
+            IndexTracker* tracker = CATCH_NULL;
+
+            ITracker& currentTracker = ctx.currentTracker();
+            if( ITracker* childTracker = currentTracker.findChild( name ) ) {
+                assert( childTracker );
+                assert( childTracker->isIndexTracker() );
+                tracker = static_cast<IndexTracker*>( childTracker );
+            }
+            else {
+                tracker = new IndexTracker( name, ctx, &currentTracker, size );
+                currentTracker.addChild( tracker );
+            }
+
+            if( !ctx.completedCycle() && !tracker->isComplete() ) {
+                if( tracker->m_runState != ExecutingChildren && tracker->m_runState != NeedsAnotherRun )
+                    tracker->moveNext();
+                tracker->open();
+            }
+
+            return *tracker;
+        }
+
+        int index() const { return m_index; }
+
+        void moveNext() {
+            m_index++;
+            m_children.clear();
+        }
+
+        virtual void close() CATCH_OVERRIDE {
+            TrackerBase::close();
+            if( m_runState == CompletedSuccessfully && m_index < m_size-1 )
+                m_runState = Executing;
+        }
+    };
+
+    inline ITracker& TrackerContext::startRun() {
+        m_rootTracker = new SectionTracker( "{root}", *this, CATCH_NULL );
+        m_currentTracker = CATCH_NULL;
+        m_runState = Executing;
+        return *m_rootTracker;
+    }
+
+} // namespace TestCaseTracking
+
+using TestCaseTracking::ITracker;
+using TestCaseTracking::TrackerContext;
+using TestCaseTracking::SectionTracker;
+using TestCaseTracking::IndexTracker;
+
+} // namespace Catch
+
+// #included from: catch_fatal_condition.hpp
+#define TWOBLUECUBES_CATCH_FATAL_CONDITION_H_INCLUDED
+
+namespace Catch {
+
+    // Report the error condition then exit the process
+    inline void fatal( std::string const& message, int exitCode ) {
+        IContext& context = Catch::getCurrentContext();
+        IResultCapture* resultCapture = context.getResultCapture();
+        resultCapture->handleFatalErrorCondition( message );
+
+		if( Catch::alwaysTrue() ) // avoids "no return" warnings
+            exit( exitCode );
+    }
+
+} // namespace Catch
+
+#if defined ( CATCH_PLATFORM_WINDOWS ) /////////////////////////////////////////
+
+namespace Catch {
+
+    struct FatalConditionHandler {
+		void reset() {}
+	};
+
+} // namespace Catch
+
+#else // Not Windows - assumed to be POSIX compatible //////////////////////////
+
+#include <signal.h>
+
+namespace Catch {
+
+    struct SignalDefs { int id; const char* name; };
+    extern SignalDefs signalDefs[];
+    SignalDefs signalDefs[] = {
+            { SIGINT,  "SIGINT - Terminal interrupt signal" },
+            { SIGILL,  "SIGILL - Illegal instruction signal" },
+            { SIGFPE,  "SIGFPE - Floating point error signal" },
+            { SIGSEGV, "SIGSEGV - Segmentation violation signal" },
+            { SIGTERM, "SIGTERM - Termination request signal" },
+            { SIGABRT, "SIGABRT - Abort (abnormal termination) signal" }
+        };
+
+    struct FatalConditionHandler {
+
+        static void handleSignal( int sig ) {
+            for( std::size_t i = 0; i < sizeof(signalDefs)/sizeof(SignalDefs); ++i )
+                if( sig == signalDefs[i].id )
+                    fatal( signalDefs[i].name, -sig );
+            fatal( "<unknown signal>", -sig );
+        }
+
+        FatalConditionHandler() : m_isSet( true ) {
+            for( std::size_t i = 0; i < sizeof(signalDefs)/sizeof(SignalDefs); ++i )
+                signal( signalDefs[i].id, handleSignal );
+        }
+        ~FatalConditionHandler() {
+            reset();
+        }
+        void reset() {
+            if( m_isSet ) {
+                for( std::size_t i = 0; i < sizeof(signalDefs)/sizeof(SignalDefs); ++i )
+                    signal( signalDefs[i].id, SIG_DFL );
+                m_isSet = false;
+            }
+        }
+
+        bool m_isSet;
+    };
+
+} // namespace Catch
+
+#endif // not Windows
+
+#include <set>
+#include <string>
+
+namespace Catch {
+
+    class StreamRedirect {
+
+    public:
+        StreamRedirect( std::ostream& stream, std::string& targetString )
+        :   m_stream( stream ),
+            m_prevBuf( stream.rdbuf() ),
+            m_targetString( targetString )
+        {
+            stream.rdbuf( m_oss.rdbuf() );
+        }
+
+        ~StreamRedirect() {
+            m_targetString += m_oss.str();
+            m_stream.rdbuf( m_prevBuf );
+        }
+
+    private:
+        std::ostream& m_stream;
+        std::streambuf* m_prevBuf;
+        std::ostringstream m_oss;
+        std::string& m_targetString;
+    };
+
+    ///////////////////////////////////////////////////////////////////////////
+
+    class RunContext : public IResultCapture, public IRunner {
+
+        RunContext( RunContext const& );
+        void operator =( RunContext const& );
+
+    public:
+
+        explicit RunContext( Ptr<IConfig const> const& _config, Ptr<IStreamingReporter> const& reporter )
+        :   m_runInfo( _config->name() ),
+            m_context( getCurrentMutableContext() ),
+            m_activeTestCase( CATCH_NULL ),
+            m_config( _config ),
+            m_reporter( reporter )
+        {
+            m_context.setRunner( this );
+            m_context.setConfig( m_config );
+            m_context.setResultCapture( this );
+            m_reporter->testRunStarting( m_runInfo );
+        }
+
+        virtual ~RunContext() {
+            m_reporter->testRunEnded( TestRunStats( m_runInfo, m_totals, aborting() ) );
+        }
+
+        void testGroupStarting( std::string const& testSpec, std::size_t groupIndex, std::size_t groupsCount ) {
+            m_reporter->testGroupStarting( GroupInfo( testSpec, groupIndex, groupsCount ) );
+        }
+        void testGroupEnded( std::string const& testSpec, Totals const& totals, std::size_t groupIndex, std::size_t groupsCount ) {
+            m_reporter->testGroupEnded( TestGroupStats( GroupInfo( testSpec, groupIndex, groupsCount ), totals, aborting() ) );
+        }
+
+        Totals runTest( TestCase const& testCase ) {
+            Totals prevTotals = m_totals;
+
+            std::string redirectedCout;
+            std::string redirectedCerr;
+
+            TestCaseInfo testInfo = testCase.getTestCaseInfo();
+
+            m_reporter->testCaseStarting( testInfo );
+
+            m_activeTestCase = &testCase;
+
+            do {
+                m_trackerContext.startRun();
+                do {
+                    m_trackerContext.startCycle();
+                    m_testCaseTracker = &SectionTracker::acquire( m_trackerContext, testInfo.name );
+                    runCurrentTest( redirectedCout, redirectedCerr );
+                }
+                while( !m_testCaseTracker->isSuccessfullyCompleted() && !aborting() );
+            }
+            // !TBD: deprecated - this will be replaced by indexed trackers
+            while( getCurrentContext().advanceGeneratorsForCurrentTest() && !aborting() );
+
+            Totals deltaTotals = m_totals.delta( prevTotals );
+            if( testInfo.expectedToFail() && deltaTotals.testCases.passed > 0 ) {
+                deltaTotals.assertions.failed++;
+                deltaTotals.testCases.passed--;
+                deltaTotals.testCases.failed++;
+            }
+            m_totals.testCases += deltaTotals.testCases;
+            m_reporter->testCaseEnded( TestCaseStats(   testInfo,
+                                                        deltaTotals,
+                                                        redirectedCout,
+                                                        redirectedCerr,
+                                                        aborting() ) );
+
+            m_activeTestCase = CATCH_NULL;
+            m_testCaseTracker = CATCH_NULL;
+
+            return deltaTotals;
+        }
+
+        Ptr<IConfig const> config() const {
+            return m_config;
+        }
+
+    private: // IResultCapture
+
+        virtual void assertionEnded( AssertionResult const& result ) {
+            if( result.getResultType() == ResultWas::Ok ) {
+                m_totals.assertions.passed++;
+            }
+            else if( !result.isOk() ) {
+                m_totals.assertions.failed++;
+            }
+
+            if( m_reporter->assertionEnded( AssertionStats( result, m_messages, m_totals ) ) )
+                m_messages.clear();
+
+            // Reset working state
+            m_lastAssertionInfo = AssertionInfo( "", m_lastAssertionInfo.lineInfo, "{Unknown expression after the reported line}" , m_lastAssertionInfo.resultDisposition );
+            m_lastResult = result;
+        }
+
+        virtual bool sectionStarted (
+            SectionInfo const& sectionInfo,
+            Counts& assertions
+        )
+        {
+            std::ostringstream oss;
+            oss << sectionInfo.name << "@" << sectionInfo.lineInfo;
+
+            ITracker& sectionTracker = SectionTracker::acquire( m_trackerContext, oss.str() );
+            if( !sectionTracker.isOpen() )
+                return false;
+            m_activeSections.push_back( &sectionTracker );
+
+            m_lastAssertionInfo.lineInfo = sectionInfo.lineInfo;
+
+            m_reporter->sectionStarting( sectionInfo );
+
+            assertions = m_totals.assertions;
+
+            return true;
+        }
+        bool testForMissingAssertions( Counts& assertions ) {
+            if( assertions.total() != 0 )
+                return false;
+            if( !m_config->warnAboutMissingAssertions() )
+                return false;
+            if( m_trackerContext.currentTracker().hasChildren() )
+                return false;
+            m_totals.assertions.failed++;
+            assertions.failed++;
+            return true;
+        }
+
+        virtual void sectionEnded( SectionEndInfo const& endInfo ) {
+            Counts assertions = m_totals.assertions - endInfo.prevAssertions;
+            bool missingAssertions = testForMissingAssertions( assertions );
+
+            if( !m_activeSections.empty() ) {
+                m_activeSections.back()->close();
+                m_activeSections.pop_back();
+            }
+
+            m_reporter->sectionEnded( SectionStats( endInfo.sectionInfo, assertions, endInfo.durationInSeconds, missingAssertions ) );
+            m_messages.clear();
+        }
+
+        virtual void sectionEndedEarly( SectionEndInfo const& endInfo ) {
+            if( m_unfinishedSections.empty() )
+                m_activeSections.back()->fail();
+            else
+                m_activeSections.back()->close();
+            m_activeSections.pop_back();
+
+            m_unfinishedSections.push_back( endInfo );
+        }
+
+        virtual void pushScopedMessage( MessageInfo const& message ) {
+            m_messages.push_back( message );
+        }
+
+        virtual void popScopedMessage( MessageInfo const& message ) {
+            m_messages.erase( std::remove( m_messages.begin(), m_messages.end(), message ), m_messages.end() );
+        }
+
+        virtual std::string getCurrentTestName() const {
+            return m_activeTestCase
+                ? m_activeTestCase->getTestCaseInfo().name
+                : "";
+        }
+
+        virtual const AssertionResult* getLastResult() const {
+            return &m_lastResult;
+        }
+
+        virtual void handleFatalErrorCondition( std::string const& message ) {
+            ResultBuilder resultBuilder = makeUnexpectedResultBuilder();
+            resultBuilder.setResultType( ResultWas::FatalErrorCondition );
+            resultBuilder << message;
+            resultBuilder.captureExpression();
+
+            handleUnfinishedSections();
+
+            // Recreate section for test case (as we will lose the one that was in scope)
+            TestCaseInfo const& testCaseInfo = m_activeTestCase->getTestCaseInfo();
+            SectionInfo testCaseSection( testCaseInfo.lineInfo, testCaseInfo.name, testCaseInfo.description );
+
+            Counts assertions;
+            assertions.failed = 1;
+            SectionStats testCaseSectionStats( testCaseSection, assertions, 0, false );
+            m_reporter->sectionEnded( testCaseSectionStats );
+
+            TestCaseInfo testInfo = m_activeTestCase->getTestCaseInfo();
+
+            Totals deltaTotals;
+            deltaTotals.testCases.failed = 1;
+            m_reporter->testCaseEnded( TestCaseStats(   testInfo,
+                                                        deltaTotals,
+                                                        "",
+                                                        "",
+                                                        false ) );
+            m_totals.testCases.failed++;
+            testGroupEnded( "", m_totals, 1, 1 );
+            m_reporter->testRunEnded( TestRunStats( m_runInfo, m_totals, false ) );
+        }
+
+    public:
+        // !TBD We need to do this another way!
+        bool aborting() const {
+            return m_totals.assertions.failed == static_cast<std::size_t>( m_config->abortAfter() );
+        }
+
+    private:
+
+        void runCurrentTest( std::string& redirectedCout, std::string& redirectedCerr ) {
+            TestCaseInfo const& testCaseInfo = m_activeTestCase->getTestCaseInfo();
+            SectionInfo testCaseSection( testCaseInfo.lineInfo, testCaseInfo.name, testCaseInfo.description );
+            m_reporter->sectionStarting( testCaseSection );
+            Counts prevAssertions = m_totals.assertions;
+            double duration = 0;
+            try {
+                m_lastAssertionInfo = AssertionInfo( "TEST_CASE", testCaseInfo.lineInfo, "", ResultDisposition::Normal );
+
+                seedRng( *m_config );
+
+                Timer timer;
+                timer.start();
+                if( m_reporter->getPreferences().shouldRedirectStdOut ) {
+                    StreamRedirect coutRedir( Catch::cout(), redirectedCout );
+                    StreamRedirect cerrRedir( Catch::cerr(), redirectedCerr );
+                    invokeActiveTestCase();
+                }
+                else {
+                    invokeActiveTestCase();
+                }
+                duration = timer.getElapsedSeconds();
+            }
+            catch( TestFailureException& ) {
+                // This just means the test was aborted due to failure
+            }
+            catch(...) {
+                makeUnexpectedResultBuilder().useActiveException();
+            }
+            m_testCaseTracker->close();
+            handleUnfinishedSections();
+            m_messages.clear();
+
+            Counts assertions = m_totals.assertions - prevAssertions;
+            bool missingAssertions = testForMissingAssertions( assertions );
+
+            if( testCaseInfo.okToFail() ) {
+                std::swap( assertions.failedButOk, assertions.failed );
+                m_totals.assertions.failed -= assertions.failedButOk;
+                m_totals.assertions.failedButOk += assertions.failedButOk;
+            }
+
+            SectionStats testCaseSectionStats( testCaseSection, assertions, duration, missingAssertions );
+            m_reporter->sectionEnded( testCaseSectionStats );
+        }
+
+        void invokeActiveTestCase() {
+            FatalConditionHandler fatalConditionHandler; // Handle signals
+            m_activeTestCase->invoke();
+            fatalConditionHandler.reset();
+        }
+
+    private:
+
+        ResultBuilder makeUnexpectedResultBuilder() const {
+            return ResultBuilder(   m_lastAssertionInfo.macroName.c_str(),
+                                    m_lastAssertionInfo.lineInfo,
+                                    m_lastAssertionInfo.capturedExpression.c_str(),
+                                    m_lastAssertionInfo.resultDisposition );
+        }
+
+        void handleUnfinishedSections() {
+            // If sections ended prematurely due to an exception we stored their
+            // infos here so we can tear them down outside the unwind process.
+            for( std::vector<SectionEndInfo>::const_reverse_iterator it = m_unfinishedSections.rbegin(),
+                        itEnd = m_unfinishedSections.rend();
+                    it != itEnd;
+                    ++it )
+                sectionEnded( *it );
+            m_unfinishedSections.clear();
+        }
+
+        TestRunInfo m_runInfo;
+        IMutableContext& m_context;
+        TestCase const* m_activeTestCase;
+        ITracker* m_testCaseTracker;
+        ITracker* m_currentSectionTracker;
+        AssertionResult m_lastResult;
+
+        Ptr<IConfig const> m_config;
+        Totals m_totals;
+        Ptr<IStreamingReporter> m_reporter;
+        std::vector<MessageInfo> m_messages;
+        AssertionInfo m_lastAssertionInfo;
+        std::vector<SectionEndInfo> m_unfinishedSections;
+        std::vector<ITracker*> m_activeSections;
+        TrackerContext m_trackerContext;
+    };
+
+    IResultCapture& getResultCapture() {
+        if( IResultCapture* capture = getCurrentContext().getResultCapture() )
+            return *capture;
+        else
+            throw std::logic_error( "No result capture instance" );
+    }
+
+} // end namespace Catch
+
+// #included from: internal/catch_version.h
+#define TWOBLUECUBES_CATCH_VERSION_H_INCLUDED
+
+namespace Catch {
+
+    // Versioning information
+    struct Version {
+        Version(    unsigned int _majorVersion,
+                    unsigned int _minorVersion,
+                    unsigned int _patchNumber,
+                    std::string const& _branchName,
+                    unsigned int _buildNumber );
+
+        unsigned int const majorVersion;
+        unsigned int const minorVersion;
+        unsigned int const patchNumber;
+
+        // buildNumber is only used if branchName is not null
+        std::string const branchName;
+        unsigned int const buildNumber;
+
+        friend std::ostream& operator << ( std::ostream& os, Version const& version );
+
+    private:
+        void operator=( Version const& );
+    };
+
+    extern Version libraryVersion;
+}
+
+#include <fstream>
+#include <stdlib.h>
+#include <limits>
+
+namespace Catch {
+
+    Ptr<IStreamingReporter> createReporter( std::string const& reporterName, Ptr<Config> const& config ) {
+        Ptr<IStreamingReporter> reporter = getRegistryHub().getReporterRegistry().create( reporterName, config.get() );
+        if( !reporter ) {
+            std::ostringstream oss;
+            oss << "No reporter registered with name: '" << reporterName << "'";
+            throw std::domain_error( oss.str() );
+        }
+        return reporter;
+    }
+
+    Ptr<IStreamingReporter> makeReporter( Ptr<Config> const& config ) {
+        std::vector<std::string> reporters = config->getReporterNames();
+        if( reporters.empty() )
+            reporters.push_back( "console" );
+
+        Ptr<IStreamingReporter> reporter;
+        for( std::vector<std::string>::const_iterator it = reporters.begin(), itEnd = reporters.end();
+                it != itEnd;
+                ++it )
+            reporter = addReporter( reporter, createReporter( *it, config ) );
+        return reporter;
+    }
+    Ptr<IStreamingReporter> addListeners( Ptr<IConfig const> const& config, Ptr<IStreamingReporter> reporters ) {
+        IReporterRegistry::Listeners listeners = getRegistryHub().getReporterRegistry().getListeners();
+        for( IReporterRegistry::Listeners::const_iterator it = listeners.begin(), itEnd = listeners.end();
+                it != itEnd;
+                ++it )
+            reporters = addReporter(reporters, (*it)->create( ReporterConfig( config ) ) );
+        return reporters;
+    }
+
+    Totals runTests( Ptr<Config> const& config ) {
+
+        Ptr<IConfig const> iconfig = config.get();
+
+        Ptr<IStreamingReporter> reporter = makeReporter( config );
+        reporter = addListeners( iconfig, reporter );
+
+        RunContext context( iconfig, reporter );
+
+        Totals totals;
+
+        context.testGroupStarting( config->name(), 1, 1 );
+
+        TestSpec testSpec = config->testSpec();
+        if( !testSpec.hasFilters() )
+            testSpec = TestSpecParser( ITagAliasRegistry::get() ).parse( "~[.]" ).testSpec(); // All not hidden tests
+
+        std::vector<TestCase> const& allTestCases = getAllTestCasesSorted( *iconfig );
+        for( std::vector<TestCase>::const_iterator it = allTestCases.begin(), itEnd = allTestCases.end();
+                it != itEnd;
+                ++it ) {
+            if( !context.aborting() && matchTest( *it, testSpec, *iconfig ) )
+                totals += context.runTest( *it );
+            else
+                reporter->skipTest( *it );
+        }
+
+        context.testGroupEnded( iconfig->name(), totals, 1, 1 );
+        return totals;
+    }
+
+    void applyFilenamesAsTags( IConfig const& config ) {
+        std::vector<TestCase> const& tests = getAllTestCasesSorted( config );
+        for(std::size_t i = 0; i < tests.size(); ++i ) {
+            TestCase& test = const_cast<TestCase&>( tests[i] );
+            std::set<std::string> tags = test.tags;
+
+            std::string filename = test.lineInfo.file;
+            std::string::size_type lastSlash = filename.find_last_of( "\\/" );
+            if( lastSlash != std::string::npos )
+                filename = filename.substr( lastSlash+1 );
+
+            std::string::size_type lastDot = filename.find_last_of( "." );
+            if( lastDot != std::string::npos )
+                filename = filename.substr( 0, lastDot );
+
+            tags.insert( "#" + filename );
+            setTags( test, tags );
+        }
+    }
+
+    class Session : NonCopyable {
+        static bool alreadyInstantiated;
+
+    public:
+
+        struct OnUnusedOptions { enum DoWhat { Ignore, Fail }; };
+
+        Session()
+        : m_cli( makeCommandLineParser() ) {
+            if( alreadyInstantiated ) {
+                std::string msg = "Only one instance of Catch::Session can ever be used";
+                Catch::cerr() << msg << std::endl;
+                throw std::logic_error( msg );
+            }
+            alreadyInstantiated = true;
+        }
+        ~Session() {
+            Catch::cleanUp();
+        }
+
+        void showHelp( std::string const& processName ) {
+            Catch::cout() << "\nCatch v" << libraryVersion << "\n";
+
+            m_cli.usage( Catch::cout(), processName );
+            Catch::cout() << "For more detail usage please see the project docs\n" << std::endl;
+        }
+
+        int applyCommandLine( int argc, char const* const* const argv, OnUnusedOptions::DoWhat unusedOptionBehaviour = OnUnusedOptions::Fail ) {
+            try {
+                m_cli.setThrowOnUnrecognisedTokens( unusedOptionBehaviour == OnUnusedOptions::Fail );
+                m_unusedTokens = m_cli.parseInto( Clara::argsToVector( argc, argv ), m_configData );
+                if( m_configData.showHelp )
+                    showHelp( m_configData.processName );
+                m_config.reset();
+            }
+            catch( std::exception& ex ) {
+                {
+                    Colour colourGuard( Colour::Red );
+                    Catch::cerr()
+                        << "\nError(s) in input:\n"
+                        << Text( ex.what(), TextAttributes().setIndent(2) )
+                        << "\n\n";
+                }
+                m_cli.usage( Catch::cout(), m_configData.processName );
+                return (std::numeric_limits<int>::max)();
+            }
+            return 0;
+        }
+
+        void useConfigData( ConfigData const& _configData ) {
+            m_configData = _configData;
+            m_config.reset();
+        }
+
+        int run( int argc, char const* const* const argv ) {
+
+            int returnCode = applyCommandLine( argc, argv );
+            if( returnCode == 0 )
+                returnCode = run();
+            return returnCode;
+        }
+
+        int run() {
+            if( m_configData.showHelp )
+                return 0;
+
+            try
+            {
+                config(); // Force config to be constructed
+
+                seedRng( *m_config );
+
+                if( m_configData.filenamesAsTags )
+                    applyFilenamesAsTags( *m_config );
+
+                // Handle list request
+                if( Option<std::size_t> listed = list( config() ) )
+                    return static_cast<int>( *listed );
+
+                return static_cast<int>( runTests( m_config ).assertions.failed );
+            }
+            catch( std::exception& ex ) {
+                Catch::cerr() << ex.what() << std::endl;
+                return (std::numeric_limits<int>::max)();
+            }
+        }
+
+        Clara::CommandLine<ConfigData> const& cli() const {
+            return m_cli;
+        }
+        std::vector<Clara::Parser::Token> const& unusedTokens() const {
+            return m_unusedTokens;
+        }
+        ConfigData& configData() {
+            return m_configData;
+        }
+        Config& config() {
+            if( !m_config )
+                m_config = new Config( m_configData );
+            return *m_config;
+        }
+    private:
+        Clara::CommandLine<ConfigData> m_cli;
+        std::vector<Clara::Parser::Token> m_unusedTokens;
+        ConfigData m_configData;
+        Ptr<Config> m_config;
+    };
+
+    bool Session::alreadyInstantiated = false;
+
+} // end namespace Catch
+
+// #included from: catch_registry_hub.hpp
+#define TWOBLUECUBES_CATCH_REGISTRY_HUB_HPP_INCLUDED
+
+// #included from: catch_test_case_registry_impl.hpp
+#define TWOBLUECUBES_CATCH_TEST_CASE_REGISTRY_IMPL_HPP_INCLUDED
+
+#include <vector>
+#include <set>
+#include <sstream>
+#include <iostream>
+#include <algorithm>
+
+namespace Catch {
+
+    struct RandomNumberGenerator {
+        typedef std::ptrdiff_t result_type;
+
+        result_type operator()( result_type n ) const { return std::rand() % n; }
+
+#ifdef CATCH_CONFIG_CPP11_SHUFFLE
+        static constexpr result_type min() { return 0; }
+        static constexpr result_type max() { return 1000000; }
+        result_type operator()() const { return std::rand() % max(); }
+#endif
+        template<typename V>
+        static void shuffle( V& vector ) {
+            RandomNumberGenerator rng;
+#ifdef CATCH_CONFIG_CPP11_SHUFFLE
+            std::shuffle( vector.begin(), vector.end(), rng );
+#else
+            std::random_shuffle( vector.begin(), vector.end(), rng );
+#endif
+        }
+    };
+
+    inline std::vector<TestCase> sortTests( IConfig const& config, std::vector<TestCase> const& unsortedTestCases ) {
+
+        std::vector<TestCase> sorted = unsortedTestCases;
+
+        switch( config.runOrder() ) {
+            case RunTests::InLexicographicalOrder:
+                std::sort( sorted.begin(), sorted.end() );
+                break;
+            case RunTests::InRandomOrder:
+                {
+                    seedRng( config );
+                    RandomNumberGenerator::shuffle( sorted );
+                }
+                break;
+            case RunTests::InDeclarationOrder:
+                // already in declaration order
+                break;
+        }
+        return sorted;
+    }
+    bool matchTest( TestCase const& testCase, TestSpec const& testSpec, IConfig const& config ) {
+        return testSpec.matches( testCase ) && ( config.allowThrows() || !testCase.throws() );
+    }
+
+    void enforceNoDuplicateTestCases( std::vector<TestCase> const& functions ) {
+        std::set<TestCase> seenFunctions;
+        for( std::vector<TestCase>::const_iterator it = functions.begin(), itEnd = functions.end();
+            it != itEnd;
+            ++it ) {
+            std::pair<std::set<TestCase>::const_iterator, bool> prev = seenFunctions.insert( *it );
+            if( !prev.second ) {
+                std::ostringstream ss;
+
+                ss  << Colour( Colour::Red )
+                    << "error: TEST_CASE( \"" << it->name << "\" ) already defined.\n"
+                    << "\tFirst seen at " << prev.first->getTestCaseInfo().lineInfo << "\n"
+                    << "\tRedefined at " << it->getTestCaseInfo().lineInfo << std::endl;
+
+                throw std::runtime_error(ss.str());
+            }
+        }
+    }
+
+    std::vector<TestCase> filterTests( std::vector<TestCase> const& testCases, TestSpec const& testSpec, IConfig const& config ) {
+        std::vector<TestCase> filtered;
+        filtered.reserve( testCases.size() );
+        for( std::vector<TestCase>::const_iterator it = testCases.begin(), itEnd = testCases.end();
+                it != itEnd;
+                ++it )
+            if( matchTest( *it, testSpec, config ) )
+                filtered.push_back( *it );
+        return filtered;
+    }
+    std::vector<TestCase> const& getAllTestCasesSorted( IConfig const& config ) {
+        return getRegistryHub().getTestCaseRegistry().getAllTestsSorted( config );
+    }
+
+    class TestRegistry : public ITestCaseRegistry {
+    public:
+        TestRegistry()
+        :   m_currentSortOrder( RunTests::InDeclarationOrder ),
+            m_unnamedCount( 0 )
+        {}
+        virtual ~TestRegistry();
+
+        virtual void registerTest( TestCase const& testCase ) {
+            std::string name = testCase.getTestCaseInfo().name;
+            if( name == "" ) {
+                std::ostringstream oss;
+                oss << "Anonymous test case " << ++m_unnamedCount;
+                return registerTest( testCase.withName( oss.str() ) );
+            }
+            m_functions.push_back( testCase );
+        }
+
+        virtual std::vector<TestCase> const& getAllTests() const {
+            return m_functions;
+        }
+        virtual std::vector<TestCase> const& getAllTestsSorted( IConfig const& config ) const {
+            if( m_sortedFunctions.empty() )
+                enforceNoDuplicateTestCases( m_functions );
+
+            if(  m_currentSortOrder != config.runOrder() || m_sortedFunctions.empty() ) {
+                m_sortedFunctions = sortTests( config, m_functions );
+                m_currentSortOrder = config.runOrder();
+            }
+            return m_sortedFunctions;
+        }
+
+    private:
+        std::vector<TestCase> m_functions;
+        mutable RunTests::InWhatOrder m_currentSortOrder;
+        mutable std::vector<TestCase> m_sortedFunctions;
+        size_t m_unnamedCount;
+        std::ios_base::Init m_ostreamInit; // Forces cout/ cerr to be initialised
+    };
+
+    ///////////////////////////////////////////////////////////////////////////
+
+    class FreeFunctionTestCase : public SharedImpl<ITestCase> {
+    public:
+
+        FreeFunctionTestCase( TestFunction fun ) : m_fun( fun ) {}
+
+        virtual void invoke() const {
+            m_fun();
+        }
+
+    private:
+        virtual ~FreeFunctionTestCase();
+
+        TestFunction m_fun;
+    };
+
+    inline std::string extractClassName( std::string const& classOrQualifiedMethodName ) {
+        std::string className = classOrQualifiedMethodName;
+        if( startsWith( className, "&" ) )
+        {
+            std::size_t lastColons = className.rfind( "::" );
+            std::size_t penultimateColons = className.rfind( "::", lastColons-1 );
+            if( penultimateColons == std::string::npos )
+                penultimateColons = 1;
+            className = className.substr( penultimateColons, lastColons-penultimateColons );
+        }
+        return className;
+    }
+
+    void registerTestCase
+        (   ITestCase* testCase,
+            char const* classOrQualifiedMethodName,
+            NameAndDesc const& nameAndDesc,
+            SourceLineInfo const& lineInfo ) {
+
+        getMutableRegistryHub().registerTest
+            ( makeTestCase
+                (   testCase,
+                    extractClassName( classOrQualifiedMethodName ),
+                    nameAndDesc.name,
+                    nameAndDesc.description,
+                    lineInfo ) );
+    }
+    void registerTestCaseFunction
+        (   TestFunction function,
+            SourceLineInfo const& lineInfo,
+            NameAndDesc const& nameAndDesc ) {
+        registerTestCase( new FreeFunctionTestCase( function ), "", nameAndDesc, lineInfo );
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+
+    AutoReg::AutoReg
+        (   TestFunction function,
+            SourceLineInfo const& lineInfo,
+            NameAndDesc const& nameAndDesc ) {
+        registerTestCaseFunction( function, lineInfo, nameAndDesc );
+    }
+
+    AutoReg::~AutoReg() {}
+
+} // end namespace Catch
+
+// #included from: catch_reporter_registry.hpp
+#define TWOBLUECUBES_CATCH_REPORTER_REGISTRY_HPP_INCLUDED
+
+#include <map>
+
+namespace Catch {
+
+    class ReporterRegistry : public IReporterRegistry {
+
+    public:
+
+        virtual ~ReporterRegistry() CATCH_OVERRIDE {}
+
+        virtual IStreamingReporter* create( std::string const& name, Ptr<IConfig const> const& config ) const CATCH_OVERRIDE {
+            FactoryMap::const_iterator it =  m_factories.find( name );
+            if( it == m_factories.end() )
+                return CATCH_NULL;
+            return it->second->create( ReporterConfig( config ) );
+        }
+
+        void registerReporter( std::string const& name, Ptr<IReporterFactory> const& factory ) {
+            m_factories.insert( std::make_pair( name, factory ) );
+        }
+        void registerListener( Ptr<IReporterFactory> const& factory ) {
+            m_listeners.push_back( factory );
+        }
+
+        virtual FactoryMap const& getFactories() const CATCH_OVERRIDE {
+            return m_factories;
+        }
+        virtual Listeners const& getListeners() const CATCH_OVERRIDE {
+            return m_listeners;
+        }
+
+    private:
+        FactoryMap m_factories;
+        Listeners m_listeners;
+    };
+}
+
+// #included from: catch_exception_translator_registry.hpp
+#define TWOBLUECUBES_CATCH_EXCEPTION_TRANSLATOR_REGISTRY_HPP_INCLUDED
+
+#ifdef __OBJC__
+#import "Foundation/Foundation.h"
+#endif
+
+namespace Catch {
+
+    class ExceptionTranslatorRegistry : public IExceptionTranslatorRegistry {
+    public:
+        ~ExceptionTranslatorRegistry() {
+            deleteAll( m_translators );
+        }
+
+        virtual void registerTranslator( const IExceptionTranslator* translator ) {
+            m_translators.push_back( translator );
+        }
+
+        virtual std::string translateActiveException() const {
+            try {
+#ifdef __OBJC__
+                // In Objective-C try objective-c exceptions first
+                @try {
+                    return tryTranslators();
+                }
+                @catch (NSException *exception) {
+                    return Catch::toString( [exception description] );
+                }
+#else
+                return tryTranslators();
+#endif
+            }
+            catch( TestFailureException& ) {
+                throw;
+            }
+            catch( std::exception& ex ) {
+                return ex.what();
+            }
+            catch( std::string& msg ) {
+                return msg;
+            }
+            catch( const char* msg ) {
+                return msg;
+            }
+            catch(...) {
+                return "Unknown exception";
+            }
+        }
+
+        std::string tryTranslators() const {
+            if( m_translators.empty() )
+                throw;
+            else
+                return m_translators[0]->translate( m_translators.begin()+1, m_translators.end() );
+        }
+
+    private:
+        std::vector<const IExceptionTranslator*> m_translators;
+    };
+}
+
+namespace Catch {
+
+    namespace {
+
+        class RegistryHub : public IRegistryHub, public IMutableRegistryHub {
+
+            RegistryHub( RegistryHub const& );
+            void operator=( RegistryHub const& );
+
+        public: // IRegistryHub
+            RegistryHub() {
+            }
+            virtual IReporterRegistry const& getReporterRegistry() const CATCH_OVERRIDE {
+                return m_reporterRegistry;
+            }
+            virtual ITestCaseRegistry const& getTestCaseRegistry() const CATCH_OVERRIDE {
+                return m_testCaseRegistry;
+            }
+            virtual IExceptionTranslatorRegistry& getExceptionTranslatorRegistry() CATCH_OVERRIDE {
+                return m_exceptionTranslatorRegistry;
+            }
+
+        public: // IMutableRegistryHub
+            virtual void registerReporter( std::string const& name, Ptr<IReporterFactory> const& factory ) CATCH_OVERRIDE {
+                m_reporterRegistry.registerReporter( name, factory );
+            }
+            virtual void registerListener( Ptr<IReporterFactory> const& factory ) CATCH_OVERRIDE {
+                m_reporterRegistry.registerListener( factory );
+            }
+            virtual void registerTest( TestCase const& testInfo ) CATCH_OVERRIDE {
+                m_testCaseRegistry.registerTest( testInfo );
+            }
+            virtual void registerTranslator( const IExceptionTranslator* translator ) CATCH_OVERRIDE {
+                m_exceptionTranslatorRegistry.registerTranslator( translator );
+            }
+
+        private:
+            TestRegistry m_testCaseRegistry;
+            ReporterRegistry m_reporterRegistry;
+            ExceptionTranslatorRegistry m_exceptionTranslatorRegistry;
+        };
+
+        // Single, global, instance
+        inline RegistryHub*& getTheRegistryHub() {
+            static RegistryHub* theRegistryHub = CATCH_NULL;
+            if( !theRegistryHub )
+                theRegistryHub = new RegistryHub();
+            return theRegistryHub;
+        }
+    }
+
+    IRegistryHub& getRegistryHub() {
+        return *getTheRegistryHub();
+    }
+    IMutableRegistryHub& getMutableRegistryHub() {
+        return *getTheRegistryHub();
+    }
+    void cleanUp() {
+        delete getTheRegistryHub();
+        getTheRegistryHub() = CATCH_NULL;
+        cleanUpContext();
+    }
+    std::string translateActiveException() {
+        return getRegistryHub().getExceptionTranslatorRegistry().translateActiveException();
+    }
+
+} // end namespace Catch
+
+// #included from: catch_notimplemented_exception.hpp
+#define TWOBLUECUBES_CATCH_NOTIMPLEMENTED_EXCEPTION_HPP_INCLUDED
+
+#include <ostream>
+
+namespace Catch {
+
+    NotImplementedException::NotImplementedException( SourceLineInfo const& lineInfo )
+    :   m_lineInfo( lineInfo ) {
+        std::ostringstream oss;
+        oss << lineInfo << ": function ";
+        oss << "not implemented";
+        m_what = oss.str();
+    }
+
+    const char* NotImplementedException::what() const CATCH_NOEXCEPT {
+        return m_what.c_str();
+    }
+
+} // end namespace Catch
+
+// #included from: catch_context_impl.hpp
+#define TWOBLUECUBES_CATCH_CONTEXT_IMPL_HPP_INCLUDED
+
+// #included from: catch_stream.hpp
+#define TWOBLUECUBES_CATCH_STREAM_HPP_INCLUDED
+
+#include <stdexcept>
+#include <cstdio>
+#include <iostream>
+
+namespace Catch {
+
+    template<typename WriterF, size_t bufferSize=256>
+    class StreamBufImpl : public StreamBufBase {
+        char data[bufferSize];
+        WriterF m_writer;
+
+    public:
+        StreamBufImpl() {
+            setp( data, data + sizeof(data) );
+        }
+
+        ~StreamBufImpl() CATCH_NOEXCEPT {
+            sync();
+        }
+
+    private:
+        int overflow( int c ) {
+            sync();
+
+            if( c != EOF ) {
+                if( pbase() == epptr() )
+                    m_writer( std::string( 1, static_cast<char>( c ) ) );
+                else
+                    sputc( static_cast<char>( c ) );
+            }
+            return 0;
+        }
+
+        int sync() {
+            if( pbase() != pptr() ) {
+                m_writer( std::string( pbase(), static_cast<std::string::size_type>( pptr() - pbase() ) ) );
+                setp( pbase(), epptr() );
+            }
+            return 0;
+        }
+    };
+
+    ///////////////////////////////////////////////////////////////////////////
+
+    FileStream::FileStream( std::string const& filename ) {
+        m_ofs.open( filename.c_str() );
+        if( m_ofs.fail() ) {
+            std::ostringstream oss;
+            oss << "Unable to open file: '" << filename << "'";
+            throw std::domain_error( oss.str() );
+        }
+    }
+
+    std::ostream& FileStream::stream() const {
+        return m_ofs;
+    }
+
+    struct OutputDebugWriter {
+
+        void operator()( std::string const&str ) {
+            writeToDebugConsole( str );
+        }
+    };
+
+    DebugOutStream::DebugOutStream()
+    :   m_streamBuf( new StreamBufImpl<OutputDebugWriter>() ),
+        m_os( m_streamBuf.get() )
+    {}
+
+    std::ostream& DebugOutStream::stream() const {
+        return m_os;
+    }
+
+    // Store the streambuf from cout up-front because
+    // cout may get redirected when running tests
+    CoutStream::CoutStream()
+    :   m_os( Catch::cout().rdbuf() )
+    {}
+
+    std::ostream& CoutStream::stream() const {
+        return m_os;
+    }
+
+#ifndef CATCH_CONFIG_NOSTDOUT // If you #define this you must implement these functions
+    std::ostream& cout() {
+        return std::cout;
+    }
+    std::ostream& cerr() {
+        return std::cerr;
+    }
+#endif
+}
+
+namespace Catch {
+
+    class Context : public IMutableContext {
+
+        Context() : m_config( CATCH_NULL ), m_runner( CATCH_NULL ), m_resultCapture( CATCH_NULL ) {}
+        Context( Context const& );
+        void operator=( Context const& );
+
+    public:
+        virtual ~Context() {
+            deleteAllValues( m_generatorsByTestName );
+        }
+
+    public: // IContext
+        virtual IResultCapture* getResultCapture() {
+            return m_resultCapture;
+        }
+        virtual IRunner* getRunner() {
+            return m_runner;
+        }
+        virtual size_t getGeneratorIndex( std::string const& fileInfo, size_t totalSize ) {
+            return getGeneratorsForCurrentTest()
+            .getGeneratorInfo( fileInfo, totalSize )
+            .getCurrentIndex();
+        }
+        virtual bool advanceGeneratorsForCurrentTest() {
+            IGeneratorsForTest* generators = findGeneratorsForCurrentTest();
+            return generators && generators->moveNext();
+        }
+
+        virtual Ptr<IConfig const> getConfig() const {
+            return m_config;
+        }
+
+    public: // IMutableContext
+        virtual void setResultCapture( IResultCapture* resultCapture ) {
+            m_resultCapture = resultCapture;
+        }
+        virtual void setRunner( IRunner* runner ) {
+            m_runner = runner;
+        }
+        virtual void setConfig( Ptr<IConfig const> const& config ) {
+            m_config = config;
+        }
+
+        friend IMutableContext& getCurrentMutableContext();
+
+    private:
+        IGeneratorsForTest* findGeneratorsForCurrentTest() {
+            std::string testName = getResultCapture()->getCurrentTestName();
+
+            std::map<std::string, IGeneratorsForTest*>::const_iterator it =
+                m_generatorsByTestName.find( testName );
+            return it != m_generatorsByTestName.end()
+                ? it->second
+                : CATCH_NULL;
+        }
+
+        IGeneratorsForTest& getGeneratorsForCurrentTest() {
+            IGeneratorsForTest* generators = findGeneratorsForCurrentTest();
+            if( !generators ) {
+                std::string testName = getResultCapture()->getCurrentTestName();
+                generators = createGeneratorsForTest();
+                m_generatorsByTestName.insert( std::make_pair( testName, generators ) );
+            }
+            return *generators;
+        }
+
+    private:
+        Ptr<IConfig const> m_config;
+        IRunner* m_runner;
+        IResultCapture* m_resultCapture;
+        std::map<std::string, IGeneratorsForTest*> m_generatorsByTestName;
+    };
+
+    namespace {
+        Context* currentContext = CATCH_NULL;
+    }
+    IMutableContext& getCurrentMutableContext() {
+        if( !currentContext )
+            currentContext = new Context();
+        return *currentContext;
+    }
+    IContext& getCurrentContext() {
+        return getCurrentMutableContext();
+    }
+
+    void cleanUpContext() {
+        delete currentContext;
+        currentContext = CATCH_NULL;
+    }
+}
+
+// #included from: catch_console_colour_impl.hpp
+#define TWOBLUECUBES_CATCH_CONSOLE_COLOUR_IMPL_HPP_INCLUDED
+
+namespace Catch {
+    namespace {
+
+        struct IColourImpl {
+            virtual ~IColourImpl() {}
+            virtual void use( Colour::Code _colourCode ) = 0;
+        };
+
+        struct NoColourImpl : IColourImpl {
+            void use( Colour::Code ) {}
+
+            static IColourImpl* instance() {
+                static NoColourImpl s_instance;
+                return &s_instance;
+            }
+        };
+
+    } // anon namespace
+} // namespace Catch
+
+#if !defined( CATCH_CONFIG_COLOUR_NONE ) && !defined( CATCH_CONFIG_COLOUR_WINDOWS ) && !defined( CATCH_CONFIG_COLOUR_ANSI )
+#   ifdef CATCH_PLATFORM_WINDOWS
+#       define CATCH_CONFIG_COLOUR_WINDOWS
+#   else
+#       define CATCH_CONFIG_COLOUR_ANSI
+#   endif
+#endif
+
+#if defined ( CATCH_CONFIG_COLOUR_WINDOWS ) /////////////////////////////////////////
+
+// #included from: catch_windows_h_proxy.h
+
+#define TWOBLUECUBES_CATCH_WINDOWS_H_PROXY_H_INCLUDED
+
+#ifdef CATCH_DEFINES_NOMINMAX
+#  define NOMINMAX
+#endif
+#ifdef CATCH_DEFINES_WIN32_LEAN_AND_MEAN
+#  define WIN32_LEAN_AND_MEAN
+#endif
+
+#ifdef __AFXDLL
+#include <AfxWin.h>
+#else
+#include <windows.h>
+#endif
+
+#ifdef CATCH_DEFINES_NOMINMAX
+#  undef NOMINMAX
+#endif
+#ifdef CATCH_DEFINES_WIN32_LEAN_AND_MEAN
+#  undef WIN32_LEAN_AND_MEAN
+#endif
+
+namespace Catch {
+namespace {
+
+    class Win32ColourImpl : public IColourImpl {
+    public:
+        Win32ColourImpl() : stdoutHandle( GetStdHandle(STD_OUTPUT_HANDLE) )
+        {
+            CONSOLE_SCREEN_BUFFER_INFO csbiInfo;
+            GetConsoleScreenBufferInfo( stdoutHandle, &csbiInfo );
+            originalForegroundAttributes = csbiInfo.wAttributes & ~( BACKGROUND_GREEN | BACKGROUND_RED | BACKGROUND_BLUE | BACKGROUND_INTENSITY );
+            originalBackgroundAttributes = csbiInfo.wAttributes & ~( FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_INTENSITY );
+        }
+
+        virtual void use( Colour::Code _colourCode ) {
+            switch( _colourCode ) {
+                case Colour::None:      return setTextAttribute( originalForegroundAttributes );
+                case Colour::White:     return setTextAttribute( FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE );
+                case Colour::Red:       return setTextAttribute( FOREGROUND_RED );
+                case Colour::Green:     return setTextAttribute( FOREGROUND_GREEN );
+                case Colour::Blue:      return setTextAttribute( FOREGROUND_BLUE );
+                case Colour::Cyan:      return setTextAttribute( FOREGROUND_BLUE | FOREGROUND_GREEN );
+                case Colour::Yellow:    return setTextAttribute( FOREGROUND_RED | FOREGROUND_GREEN );
+                case Colour::Grey:      return setTextAttribute( 0 );
+
+                case Colour::LightGrey:     return setTextAttribute( FOREGROUND_INTENSITY );
+                case Colour::BrightRed:     return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_RED );
+                case Colour::BrightGreen:   return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_GREEN );
+                case Colour::BrightWhite:   return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE );
+
+                case Colour::Bright: throw std::logic_error( "not a colour" );
+            }
+        }
+
+    private:
+        void setTextAttribute( WORD _textAttribute ) {
+            SetConsoleTextAttribute( stdoutHandle, _textAttribute | originalBackgroundAttributes );
+        }
+        HANDLE stdoutHandle;
+        WORD originalForegroundAttributes;
+        WORD originalBackgroundAttributes;
+    };
+
+    IColourImpl* platformColourInstance() {
+        static Win32ColourImpl s_instance;
+
+        Ptr<IConfig const> config = getCurrentContext().getConfig();
+        UseColour::YesOrNo colourMode = config
+            ? config->useColour()
+            : UseColour::Auto;
+        if( colourMode == UseColour::Auto )
+            colourMode = !isDebuggerActive()
+                ? UseColour::Yes
+                : UseColour::No;
+        return colourMode == UseColour::Yes
+            ? &s_instance
+            : NoColourImpl::instance();
+    }
+
+} // end anon namespace
+} // end namespace Catch
+
+#elif defined( CATCH_CONFIG_COLOUR_ANSI ) //////////////////////////////////////
+
+#include <unistd.h>
+
+namespace Catch {
+namespace {
+
+    // use POSIX/ ANSI console terminal codes
+    // Thanks to Adam Strzelecki for original contribution
+    // (http://github.com/nanoant)
+    // https://github.com/philsquared/Catch/pull/131
+    class PosixColourImpl : public IColourImpl {
+    public:
+        virtual void use( Colour::Code _colourCode ) {
+            switch( _colourCode ) {
+                case Colour::None:
+                case Colour::White:     return setColour( "[0m" );
+                case Colour::Red:       return setColour( "[0;31m" );
+                case Colour::Green:     return setColour( "[0;32m" );
+                case Colour::Blue:      return setColour( "[0;34m" );
+                case Colour::Cyan:      return setColour( "[0;36m" );
+                case Colour::Yellow:    return setColour( "[0;33m" );
+                case Colour::Grey:      return setColour( "[1;30m" );
+
+                case Colour::LightGrey:     return setColour( "[0;37m" );
+                case Colour::BrightRed:     return setColour( "[1;31m" );
+                case Colour::BrightGreen:   return setColour( "[1;32m" );
+                case Colour::BrightWhite:   return setColour( "[1;37m" );
+
+                case Colour::Bright: throw std::logic_error( "not a colour" );
+            }
+        }
+        static IColourImpl* instance() {
+            static PosixColourImpl s_instance;
+            return &s_instance;
+        }
+
+    private:
+        void setColour( const char* _escapeCode ) {
+            Catch::cout() << '\033' << _escapeCode;
+        }
+    };
+
+    IColourImpl* platformColourInstance() {
+        Ptr<IConfig const> config = getCurrentContext().getConfig();
+        UseColour::YesOrNo colourMode = config
+            ? config->useColour()
+            : UseColour::Auto;
+        if( colourMode == UseColour::Auto )
+            colourMode = (!isDebuggerActive() && isatty(STDOUT_FILENO) )
+                ? UseColour::Yes
+                : UseColour::No;
+        return colourMode == UseColour::Yes
+            ? PosixColourImpl::instance()
+            : NoColourImpl::instance();
+    }
+
+} // end anon namespace
+} // end namespace Catch
+
+#else  // not Windows or ANSI ///////////////////////////////////////////////
+
+namespace Catch {
+
+    static IColourImpl* platformColourInstance() { return NoColourImpl::instance(); }
+
+} // end namespace Catch
+
+#endif // Windows/ ANSI/ None
+
+namespace Catch {
+
+    Colour::Colour( Code _colourCode ) : m_moved( false ) { use( _colourCode ); }
+    Colour::Colour( Colour const& _other ) : m_moved( false ) { const_cast<Colour&>( _other ).m_moved = true; }
+    Colour::~Colour(){ if( !m_moved ) use( None ); }
+
+    void Colour::use( Code _colourCode ) {
+        static IColourImpl* impl = platformColourInstance();
+        impl->use( _colourCode );
+    }
+
+} // end namespace Catch
+
+// #included from: catch_generators_impl.hpp
+#define TWOBLUECUBES_CATCH_GENERATORS_IMPL_HPP_INCLUDED
+
+#include <vector>
+#include <string>
+#include <map>
+
+namespace Catch {
+
+    struct GeneratorInfo : IGeneratorInfo {
+
+        GeneratorInfo( std::size_t size )
+        :   m_size( size ),
+            m_currentIndex( 0 )
+        {}
+
+        bool moveNext() {
+            if( ++m_currentIndex == m_size ) {
+                m_currentIndex = 0;
+                return false;
+            }
+            return true;
+        }
+
+        std::size_t getCurrentIndex() const {
+            return m_currentIndex;
+        }
+
+        std::size_t m_size;
+        std::size_t m_currentIndex;
+    };
+
+    ///////////////////////////////////////////////////////////////////////////
+
+    class GeneratorsForTest : public IGeneratorsForTest {
+
+    public:
+        ~GeneratorsForTest() {
+            deleteAll( m_generatorsInOrder );
+        }
+
+        IGeneratorInfo& getGeneratorInfo( std::string const& fileInfo, std::size_t size ) {
+            std::map<std::string, IGeneratorInfo*>::const_iterator it = m_generatorsByName.find( fileInfo );
+            if( it == m_generatorsByName.end() ) {
+                IGeneratorInfo* info = new GeneratorInfo( size );
+                m_generatorsByName.insert( std::make_pair( fileInfo, info ) );
+                m_generatorsInOrder.push_back( info );
+                return *info;
+            }
+            return *it->second;
+        }
+
+        bool moveNext() {
+            std::vector<IGeneratorInfo*>::const_iterator it = m_generatorsInOrder.begin();
+            std::vector<IGeneratorInfo*>::const_iterator itEnd = m_generatorsInOrder.end();
+            for(; it != itEnd; ++it ) {
+                if( (*it)->moveNext() )
+                    return true;
+            }
+            return false;
+        }
+
+    private:
+        std::map<std::string, IGeneratorInfo*> m_generatorsByName;
+        std::vector<IGeneratorInfo*> m_generatorsInOrder;
+    };
+
+    IGeneratorsForTest* createGeneratorsForTest()
+    {
+        return new GeneratorsForTest();
+    }
+
+} // end namespace Catch
+
+// #included from: catch_assertionresult.hpp
+#define TWOBLUECUBES_CATCH_ASSERTIONRESULT_HPP_INCLUDED
+
+namespace Catch {
+
+    AssertionInfo::AssertionInfo(   std::string const& _macroName,
+                                    SourceLineInfo const& _lineInfo,
+                                    std::string const& _capturedExpression,
+                                    ResultDisposition::Flags _resultDisposition )
+    :   macroName( _macroName ),
+        lineInfo( _lineInfo ),
+        capturedExpression( _capturedExpression ),
+        resultDisposition( _resultDisposition )
+    {}
+
+    AssertionResult::AssertionResult() {}
+
+    AssertionResult::AssertionResult( AssertionInfo const& info, AssertionResultData const& data )
+    :   m_info( info ),
+        m_resultData( data )
+    {}
+
+    AssertionResult::~AssertionResult() {}
+
+    // Result was a success
+    bool AssertionResult::succeeded() const {
+        return Catch::isOk( m_resultData.resultType );
+    }
+
+    // Result was a success, or failure is suppressed
+    bool AssertionResult::isOk() const {
+        return Catch::isOk( m_resultData.resultType ) || shouldSuppressFailure( m_info.resultDisposition );
+    }
+
+    ResultWas::OfType AssertionResult::getResultType() const {
+        return m_resultData.resultType;
+    }
+
+    bool AssertionResult::hasExpression() const {
+        return !m_info.capturedExpression.empty();
+    }
+
+    bool AssertionResult::hasMessage() const {
+        return !m_resultData.message.empty();
+    }
+
+    std::string AssertionResult::getExpression() const {
+        if( isFalseTest( m_info.resultDisposition ) )
+            return "!" + m_info.capturedExpression;
+        else
+            return m_info.capturedExpression;
+    }
+    std::string AssertionResult::getExpressionInMacro() const {
+        if( m_info.macroName.empty() )
+            return m_info.capturedExpression;
+        else
+            return m_info.macroName + "( " + m_info.capturedExpression + " )";
+    }
+
+    bool AssertionResult::hasExpandedExpression() const {
+        return hasExpression() && getExpandedExpression() != getExpression();
+    }
+
+    std::string AssertionResult::getExpandedExpression() const {
+        return m_resultData.reconstructedExpression;
+    }
+
+    std::string AssertionResult::getMessage() const {
+        return m_resultData.message;
+    }
+    SourceLineInfo AssertionResult::getSourceInfo() const {
+        return m_info.lineInfo;
+    }
+
+    std::string AssertionResult::getTestMacroName() const {
+        return m_info.macroName;
+    }
+
+} // end namespace Catch
+
+// #included from: catch_test_case_info.hpp
+#define TWOBLUECUBES_CATCH_TEST_CASE_INFO_HPP_INCLUDED
+
+namespace Catch {
+
+    inline TestCaseInfo::SpecialProperties parseSpecialTag( std::string const& tag ) {
+        if( startsWith( tag, "." ) ||
+            tag == "hide" ||
+            tag == "!hide" )
+            return TestCaseInfo::IsHidden;
+        else if( tag == "!throws" )
+            return TestCaseInfo::Throws;
+        else if( tag == "!shouldfail" )
+            return TestCaseInfo::ShouldFail;
+        else if( tag == "!mayfail" )
+            return TestCaseInfo::MayFail;
+        else
+            return TestCaseInfo::None;
+    }
+    inline bool isReservedTag( std::string const& tag ) {
+        return parseSpecialTag( tag ) == TestCaseInfo::None && tag.size() > 0 && !isalnum( tag[0] );
+    }
+    inline void enforceNotReservedTag( std::string const& tag, SourceLineInfo const& _lineInfo ) {
+        if( isReservedTag( tag ) ) {
+            {
+                Colour colourGuard( Colour::Red );
+                Catch::cerr()
+                    << "Tag name [" << tag << "] not allowed.\n"
+                    << "Tag names starting with non alpha-numeric characters are reserved\n";
+            }
+            {
+                Colour colourGuard( Colour::FileName );
+                Catch::cerr() << _lineInfo << std::endl;
+            }
+            exit(1);
+        }
+    }
+
+    TestCase makeTestCase(  ITestCase* _testCase,
+                            std::string const& _className,
+                            std::string const& _name,
+                            std::string const& _descOrTags,
+                            SourceLineInfo const& _lineInfo )
+    {
+        bool isHidden( startsWith( _name, "./" ) ); // Legacy support
+
+        // Parse out tags
+        std::set<std::string> tags;
+        std::string desc, tag;
+        bool inTag = false;
+        for( std::size_t i = 0; i < _descOrTags.size(); ++i ) {
+            char c = _descOrTags[i];
+            if( !inTag ) {
+                if( c == '[' )
+                    inTag = true;
+                else
+                    desc += c;
+            }
+            else {
+                if( c == ']' ) {
+                    TestCaseInfo::SpecialProperties prop = parseSpecialTag( tag );
+                    if( prop == TestCaseInfo::IsHidden )
+                        isHidden = true;
+                    else if( prop == TestCaseInfo::None )
+                        enforceNotReservedTag( tag, _lineInfo );
+
+                    tags.insert( tag );
+                    tag.clear();
+                    inTag = false;
+                }
+                else
+                    tag += c;
+            }
+        }
+        if( isHidden ) {
+            tags.insert( "hide" );
+            tags.insert( "." );
+        }
+
+        TestCaseInfo info( _name, _className, desc, tags, _lineInfo );
+        return TestCase( _testCase, info );
+    }
+
+    void setTags( TestCaseInfo& testCaseInfo, std::set<std::string> const& tags )
+    {
+        testCaseInfo.tags = tags;
+        testCaseInfo.lcaseTags.clear();
+
+        std::ostringstream oss;
+        for( std::set<std::string>::const_iterator it = tags.begin(), itEnd = tags.end(); it != itEnd; ++it ) {
+            oss << "[" << *it << "]";
+            std::string lcaseTag = toLower( *it );
+            testCaseInfo.properties = static_cast<TestCaseInfo::SpecialProperties>( testCaseInfo.properties | parseSpecialTag( lcaseTag ) );
+            testCaseInfo.lcaseTags.insert( lcaseTag );
+        }
+        testCaseInfo.tagsAsString = oss.str();
+    }
+
+    TestCaseInfo::TestCaseInfo( std::string const& _name,
+                                std::string const& _className,
+                                std::string const& _description,
+                                std::set<std::string> const& _tags,
+                                SourceLineInfo const& _lineInfo )
+    :   name( _name ),
+        className( _className ),
+        description( _description ),
+        lineInfo( _lineInfo ),
+        properties( None )
+    {
+        setTags( *this, _tags );
+    }
+
+    TestCaseInfo::TestCaseInfo( TestCaseInfo const& other )
+    :   name( other.name ),
+        className( other.className ),
+        description( other.description ),
+        tags( other.tags ),
+        lcaseTags( other.lcaseTags ),
+        tagsAsString( other.tagsAsString ),
+        lineInfo( other.lineInfo ),
+        properties( other.properties )
+    {}
+
+    bool TestCaseInfo::isHidden() const {
+        return ( properties & IsHidden ) != 0;
+    }
+    bool TestCaseInfo::throws() const {
+        return ( properties & Throws ) != 0;
+    }
+    bool TestCaseInfo::okToFail() const {
+        return ( properties & (ShouldFail | MayFail ) ) != 0;
+    }
+    bool TestCaseInfo::expectedToFail() const {
+        return ( properties & (ShouldFail ) ) != 0;
+    }
+
+    TestCase::TestCase( ITestCase* testCase, TestCaseInfo const& info ) : TestCaseInfo( info ), test( testCase ) {}
+
+    TestCase::TestCase( TestCase const& other )
+    :   TestCaseInfo( other ),
+        test( other.test )
+    {}
+
+    TestCase TestCase::withName( std::string const& _newName ) const {
+        TestCase other( *this );
+        other.name = _newName;
+        return other;
+    }
+
+    void TestCase::swap( TestCase& other ) {
+        test.swap( other.test );
+        name.swap( other.name );
+        className.swap( other.className );
+        description.swap( other.description );
+        tags.swap( other.tags );
+        lcaseTags.swap( other.lcaseTags );
+        tagsAsString.swap( other.tagsAsString );
+        std::swap( TestCaseInfo::properties, static_cast<TestCaseInfo&>( other ).properties );
+        std::swap( lineInfo, other.lineInfo );
+    }
+
+    void TestCase::invoke() const {
+        test->invoke();
+    }
+
+    bool TestCase::operator == ( TestCase const& other ) const {
+        return  test.get() == other.test.get() &&
+                name == other.name &&
+                className == other.className;
+    }
+
+    bool TestCase::operator < ( TestCase const& other ) const {
+        return name < other.name;
+    }
+    TestCase& TestCase::operator = ( TestCase const& other ) {
+        TestCase temp( other );
+        swap( temp );
+        return *this;
+    }
+
+    TestCaseInfo const& TestCase::getTestCaseInfo() const
+    {
+        return *this;
+    }
+
+} // end namespace Catch
+
+// #included from: catch_version.hpp
+#define TWOBLUECUBES_CATCH_VERSION_HPP_INCLUDED
+
+namespace Catch {
+
+    Version::Version
+        (   unsigned int _majorVersion,
+            unsigned int _minorVersion,
+            unsigned int _patchNumber,
+            std::string const& _branchName,
+            unsigned int _buildNumber )
+    :   majorVersion( _majorVersion ),
+        minorVersion( _minorVersion ),
+        patchNumber( _patchNumber ),
+        branchName( _branchName ),
+        buildNumber( _buildNumber )
+    {}
+
+    std::ostream& operator << ( std::ostream& os, Version const& version ) {
+        os  << version.majorVersion << "."
+            << version.minorVersion << "."
+            << version.patchNumber;
+
+        if( !version.branchName.empty() ) {
+            os  << "-" << version.branchName
+                << "." << version.buildNumber;
+        }
+        return os;
+    }
+
+    Version libraryVersion( 1, 6, 1, "", 0 );
+
+}
+
+// #included from: catch_message.hpp
+#define TWOBLUECUBES_CATCH_MESSAGE_HPP_INCLUDED
+
+namespace Catch {
+
+    MessageInfo::MessageInfo(   std::string const& _macroName,
+                                SourceLineInfo const& _lineInfo,
+                                ResultWas::OfType _type )
+    :   macroName( _macroName ),
+        lineInfo( _lineInfo ),
+        type( _type ),
+        sequence( ++globalCount )
+    {}
+
+    // This may need protecting if threading support is added
+    unsigned int MessageInfo::globalCount = 0;
+
+    ////////////////////////////////////////////////////////////////////////////
+
+    ScopedMessage::ScopedMessage( MessageBuilder const& builder )
+    : m_info( builder.m_info )
+    {
+        m_info.message = builder.m_stream.str();
+        getResultCapture().pushScopedMessage( m_info );
+    }
+    ScopedMessage::ScopedMessage( ScopedMessage const& other )
+    : m_info( other.m_info )
+    {}
+
+    ScopedMessage::~ScopedMessage() {
+        getResultCapture().popScopedMessage( m_info );
+    }
+
+} // end namespace Catch
+
+// #included from: catch_legacy_reporter_adapter.hpp
+#define TWOBLUECUBES_CATCH_LEGACY_REPORTER_ADAPTER_HPP_INCLUDED
+
+// #included from: catch_legacy_reporter_adapter.h
+#define TWOBLUECUBES_CATCH_LEGACY_REPORTER_ADAPTER_H_INCLUDED
+
+namespace Catch
+{
+    // Deprecated
+    struct IReporter : IShared {
+        virtual ~IReporter();
+
+        virtual bool shouldRedirectStdout() const = 0;
+
+        virtual void StartTesting() = 0;
+        virtual void EndTesting( Totals const& totals ) = 0;
+        virtual void StartGroup( std::string const& groupName ) = 0;
+        virtual void EndGroup( std::string const& groupName, Totals const& totals ) = 0;
+        virtual void StartTestCase( TestCaseInfo const& testInfo ) = 0;
+        virtual void EndTestCase( TestCaseInfo const& testInfo, Totals const& totals, std::string const& stdOut, std::string const& stdErr ) = 0;
+        virtual void StartSection( std::string const& sectionName, std::string const& description ) = 0;
+        virtual void EndSection( std::string const& sectionName, Counts const& assertions ) = 0;
+        virtual void NoAssertionsInSection( std::string const& sectionName ) = 0;
+        virtual void NoAssertionsInTestCase( std::string const& testName ) = 0;
+        virtual void Aborted() = 0;
+        virtual void Result( AssertionResult const& result ) = 0;
+    };
+
+    class LegacyReporterAdapter : public SharedImpl<IStreamingReporter>
+    {
+    public:
+        LegacyReporterAdapter( Ptr<IReporter> const& legacyReporter );
+        virtual ~LegacyReporterAdapter();
+
+        virtual ReporterPreferences getPreferences() const;
+        virtual void noMatchingTestCases( std::string const& );
+        virtual void testRunStarting( TestRunInfo const& );
+        virtual void testGroupStarting( GroupInfo const& groupInfo );
+        virtual void testCaseStarting( TestCaseInfo const& testInfo );
+        virtual void sectionStarting( SectionInfo const& sectionInfo );
+        virtual void assertionStarting( AssertionInfo const& );
+        virtual bool assertionEnded( AssertionStats const& assertionStats );
+        virtual void sectionEnded( SectionStats const& sectionStats );
+        virtual void testCaseEnded( TestCaseStats const& testCaseStats );
+        virtual void testGroupEnded( TestGroupStats const& testGroupStats );
+        virtual void testRunEnded( TestRunStats const& testRunStats );
+        virtual void skipTest( TestCaseInfo const& );
+
+    private:
+        Ptr<IReporter> m_legacyReporter;
+    };
+}
+
+namespace Catch
+{
+    LegacyReporterAdapter::LegacyReporterAdapter( Ptr<IReporter> const& legacyReporter )
+    :   m_legacyReporter( legacyReporter )
+    {}
+    LegacyReporterAdapter::~LegacyReporterAdapter() {}
+
+    ReporterPreferences LegacyReporterAdapter::getPreferences() const {
+        ReporterPreferences prefs;
+        prefs.shouldRedirectStdOut = m_legacyReporter->shouldRedirectStdout();
+        return prefs;
+    }
+
+    void LegacyReporterAdapter::noMatchingTestCases( std::string const& ) {}
+    void LegacyReporterAdapter::testRunStarting( TestRunInfo const& ) {
+        m_legacyReporter->StartTesting();
+    }
+    void LegacyReporterAdapter::testGroupStarting( GroupInfo const& groupInfo ) {
+        m_legacyReporter->StartGroup( groupInfo.name );
+    }
+    void LegacyReporterAdapter::testCaseStarting( TestCaseInfo const& testInfo ) {
+        m_legacyReporter->StartTestCase( testInfo );
+    }
+    void LegacyReporterAdapter::sectionStarting( SectionInfo const& sectionInfo ) {
+        m_legacyReporter->StartSection( sectionInfo.name, sectionInfo.description );
+    }
+    void LegacyReporterAdapter::assertionStarting( AssertionInfo const& ) {
+        // Not on legacy interface
+    }
+
+    bool LegacyReporterAdapter::assertionEnded( AssertionStats const& assertionStats ) {
+        if( assertionStats.assertionResult.getResultType() != ResultWas::Ok ) {
+            for( std::vector<MessageInfo>::const_iterator it = assertionStats.infoMessages.begin(), itEnd = assertionStats.infoMessages.end();
+                    it != itEnd;
+                    ++it ) {
+                if( it->type == ResultWas::Info ) {
+                    ResultBuilder rb( it->macroName.c_str(), it->lineInfo, "", ResultDisposition::Normal );
+                    rb << it->message;
+                    rb.setResultType( ResultWas::Info );
+                    AssertionResult result = rb.build();
+                    m_legacyReporter->Result( result );
+                }
+            }
+        }
+        m_legacyReporter->Result( assertionStats.assertionResult );
+        return true;
+    }
+    void LegacyReporterAdapter::sectionEnded( SectionStats const& sectionStats ) {
+        if( sectionStats.missingAssertions )
+            m_legacyReporter->NoAssertionsInSection( sectionStats.sectionInfo.name );
+        m_legacyReporter->EndSection( sectionStats.sectionInfo.name, sectionStats.assertions );
+    }
+    void LegacyReporterAdapter::testCaseEnded( TestCaseStats const& testCaseStats ) {
+        m_legacyReporter->EndTestCase
+            (   testCaseStats.testInfo,
+                testCaseStats.totals,
+                testCaseStats.stdOut,
+                testCaseStats.stdErr );
+    }
+    void LegacyReporterAdapter::testGroupEnded( TestGroupStats const& testGroupStats ) {
+        if( testGroupStats.aborting )
+            m_legacyReporter->Aborted();
+        m_legacyReporter->EndGroup( testGroupStats.groupInfo.name, testGroupStats.totals );
+    }
+    void LegacyReporterAdapter::testRunEnded( TestRunStats const& testRunStats ) {
+        m_legacyReporter->EndTesting( testRunStats.totals );
+    }
+    void LegacyReporterAdapter::skipTest( TestCaseInfo const& ) {
+    }
+}
+
+// #included from: catch_timer.hpp
+
+#ifdef __clang__
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wc++11-long-long"
+#endif
+
+#ifdef CATCH_PLATFORM_WINDOWS
+#else
+#include <sys/time.h>
+#endif
+
+namespace Catch {
+
+    namespace {
+#ifdef CATCH_PLATFORM_WINDOWS
+        uint64_t getCurrentTicks() {
+            static uint64_t hz=0, hzo=0;
+            if (!hz) {
+                QueryPerformanceFrequency( reinterpret_cast<LARGE_INTEGER*>( &hz ) );
+                QueryPerformanceCounter( reinterpret_cast<LARGE_INTEGER*>( &hzo ) );
+            }
+            uint64_t t;
+            QueryPerformanceCounter( reinterpret_cast<LARGE_INTEGER*>( &t ) );
+            return ((t-hzo)*1000000)/hz;
+        }
+#else
+        uint64_t getCurrentTicks() {
+            timeval t;
+            gettimeofday(&t,CATCH_NULL);
+            return static_cast<uint64_t>( t.tv_sec ) * 1000000ull + static_cast<uint64_t>( t.tv_usec );
+        }
+#endif
+    }
+
+    void Timer::start() {
+        m_ticks = getCurrentTicks();
+    }
+    unsigned int Timer::getElapsedMicroseconds() const {
+        return static_cast<unsigned int>(getCurrentTicks() - m_ticks);
+    }
+    unsigned int Timer::getElapsedMilliseconds() const {
+        return static_cast<unsigned int>(getElapsedMicroseconds()/1000);
+    }
+    double Timer::getElapsedSeconds() const {
+        return getElapsedMicroseconds()/1000000.0;
+    }
+
+} // namespace Catch
+
+#ifdef __clang__
+#pragma clang diagnostic pop
+#endif
+// #included from: catch_common.hpp
+#define TWOBLUECUBES_CATCH_COMMON_HPP_INCLUDED
+
+namespace Catch {
+
+    bool startsWith( std::string const& s, std::string const& prefix ) {
+        return s.size() >= prefix.size() && s.substr( 0, prefix.size() ) == prefix;
+    }
+    bool endsWith( std::string const& s, std::string const& suffix ) {
+        return s.size() >= suffix.size() && s.substr( s.size()-suffix.size(), suffix.size() ) == suffix;
+    }
+    bool contains( std::string const& s, std::string const& infix ) {
+        return s.find( infix ) != std::string::npos;
+    }
+    char toLowerCh(char c) {
+        return static_cast<char>( ::tolower( c ) );
+    }
+    void toLowerInPlace( std::string& s ) {
+        std::transform( s.begin(), s.end(), s.begin(), toLowerCh );
+    }
+    std::string toLower( std::string const& s ) {
+        std::string lc = s;
+        toLowerInPlace( lc );
+        return lc;
+    }
+    std::string trim( std::string const& str ) {
+        static char const* whitespaceChars = "\n\r\t ";
+        std::string::size_type start = str.find_first_not_of( whitespaceChars );
+        std::string::size_type end = str.find_last_not_of( whitespaceChars );
+
+        return start != std::string::npos ? str.substr( start, 1+end-start ) : "";
+    }
+
+    bool replaceInPlace( std::string& str, std::string const& replaceThis, std::string const& withThis ) {
+        bool replaced = false;
+        std::size_t i = str.find( replaceThis );
+        while( i != std::string::npos ) {
+            replaced = true;
+            str = str.substr( 0, i ) + withThis + str.substr( i+replaceThis.size() );
+            if( i < str.size()-withThis.size() )
+                i = str.find( replaceThis, i+withThis.size() );
+            else
+                i = std::string::npos;
+        }
+        return replaced;
+    }
+
+    pluralise::pluralise( std::size_t count, std::string const& label )
+    :   m_count( count ),
+        m_label( label )
+    {}
+
+    std::ostream& operator << ( std::ostream& os, pluralise const& pluraliser ) {
+        os << pluraliser.m_count << " " << pluraliser.m_label;
+        if( pluraliser.m_count != 1 )
+            os << "s";
+        return os;
+    }
+
+    SourceLineInfo::SourceLineInfo() : line( 0 ){}
+    SourceLineInfo::SourceLineInfo( char const* _file, std::size_t _line )
+    :   file( _file ),
+        line( _line )
+    {}
+    SourceLineInfo::SourceLineInfo( SourceLineInfo const& other )
+    :   file( other.file ),
+        line( other.line )
+    {}
+    bool SourceLineInfo::empty() const {
+        return file.empty();
+    }
+    bool SourceLineInfo::operator == ( SourceLineInfo const& other ) const {
+        return line == other.line && file == other.file;
+    }
+    bool SourceLineInfo::operator < ( SourceLineInfo const& other ) const {
+        return line < other.line || ( line == other.line  && file < other.file );
+    }
+
+    void seedRng( IConfig const& config ) {
+        if( config.rngSeed() != 0 )
+            std::srand( config.rngSeed() );
+    }
+    unsigned int rngSeed() {
+        return getCurrentContext().getConfig()->rngSeed();
+    }
+
+    std::ostream& operator << ( std::ostream& os, SourceLineInfo const& info ) {
+#ifndef __GNUG__
+        os << info.file << "(" << info.line << ")";
+#else
+        os << info.file << ":" << info.line;
+#endif
+        return os;
+    }
+
+    void throwLogicError( std::string const& message, SourceLineInfo const& locationInfo ) {
+        std::ostringstream oss;
+        oss << locationInfo << ": Internal Catch error: '" << message << "'";
+        if( alwaysTrue() )
+            throw std::logic_error( oss.str() );
+    }
+}
+
+// #included from: catch_section.hpp
+#define TWOBLUECUBES_CATCH_SECTION_HPP_INCLUDED
+
+namespace Catch {
+
+    SectionInfo::SectionInfo
+        (   SourceLineInfo const& _lineInfo,
+            std::string const& _name,
+            std::string const& _description )
+    :   name( _name ),
+        description( _description ),
+        lineInfo( _lineInfo )
+    {}
+
+    Section::Section( SectionInfo const& info )
+    :   m_info( info ),
+        m_sectionIncluded( getResultCapture().sectionStarted( m_info, m_assertions ) )
+    {
+        m_timer.start();
+    }
+
+    Section::~Section() {
+        if( m_sectionIncluded ) {
+            SectionEndInfo endInfo( m_info, m_assertions, m_timer.getElapsedSeconds() );
+            if( std::uncaught_exception() )
+                getResultCapture().sectionEndedEarly( endInfo );
+            else
+                getResultCapture().sectionEnded( endInfo );
+        }
+    }
+
+    // This indicates whether the section should be executed or not
+    Section::operator bool() const {
+        return m_sectionIncluded;
+    }
+
+} // end namespace Catch
+
+// #included from: catch_debugger.hpp
+#define TWOBLUECUBES_CATCH_DEBUGGER_HPP_INCLUDED
+
+#include <iostream>
+
+#ifdef CATCH_PLATFORM_MAC
+
+    #include <assert.h>
+    #include <stdbool.h>
+    #include <sys/types.h>
+    #include <unistd.h>
+    #include <sys/sysctl.h>
+
+    namespace Catch{
+
+        // The following function is taken directly from the following technical note:
+        // http://developer.apple.com/library/mac/#qa/qa2004/qa1361.html
+
+        // Returns true if the current process is being debugged (either
+        // running under the debugger or has a debugger attached post facto).
+        bool isDebuggerActive(){
+
+            int                 mib[4];
+            struct kinfo_proc   info;
+            size_t              size;
+
+            // Initialize the flags so that, if sysctl fails for some bizarre
+            // reason, we get a predictable result.
+
+            info.kp_proc.p_flag = 0;
+
+            // Initialize mib, which tells sysctl the info we want, in this case
+            // we're looking for information about a specific process ID.
+
+            mib[0] = CTL_KERN;
+            mib[1] = KERN_PROC;
+            mib[2] = KERN_PROC_PID;
+            mib[3] = getpid();
+
+            // Call sysctl.
+
+            size = sizeof(info);
+            if( sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &size, CATCH_NULL, 0) != 0 ) {
+                Catch::cerr() << "\n** Call to sysctl failed - unable to determine if debugger is active **\n" << std::endl;
+                return false;
+            }
+
+            // We're being debugged if the P_TRACED flag is set.
+
+            return ( (info.kp_proc.p_flag & P_TRACED) != 0 );
+        }
+    } // namespace Catch
+
+#elif defined(CATCH_PLATFORM_LINUX)
+    #include <fstream>
+    #include <string>
+
+    namespace Catch{
+        // The standard POSIX way of detecting a debugger is to attempt to
+        // ptrace() the process, but this needs to be done from a child and not
+        // this process itself to still allow attaching to this process later
+        // if wanted, so is rather heavy. Under Linux we have the PID of the
+        // "debugger" (which doesn't need to be gdb, of course, it could also
+        // be strace, for example) in /proc/$PID/status, so just get it from
+        // there instead.
+        bool isDebuggerActive(){
+            std::ifstream in("/proc/self/status");
+            for( std::string line; std::getline(in, line); ) {
+                static const int PREFIX_LEN = 11;
+                if( line.compare(0, PREFIX_LEN, "TracerPid:\t") == 0 ) {
+                    // We're traced if the PID is not 0 and no other PID starts
+                    // with 0 digit, so it's enough to check for just a single
+                    // character.
+                    return line.length() > PREFIX_LEN && line[PREFIX_LEN] != '0';
+                }
+            }
+
+            return false;
+        }
+    } // namespace Catch
+#elif defined(_MSC_VER)
+    extern "C" __declspec(dllimport) int __stdcall IsDebuggerPresent();
+    namespace Catch {
+        bool isDebuggerActive() {
+            return IsDebuggerPresent() != 0;
+        }
+    }
+#elif defined(__MINGW32__)
+    extern "C" __declspec(dllimport) int __stdcall IsDebuggerPresent();
+    namespace Catch {
+        bool isDebuggerActive() {
+            return IsDebuggerPresent() != 0;
+        }
+    }
+#else
+    namespace Catch {
+       inline bool isDebuggerActive() { return false; }
+    }
+#endif // Platform
+
+#ifdef CATCH_PLATFORM_WINDOWS
+    extern "C" __declspec(dllimport) void __stdcall OutputDebugStringA( const char* );
+    namespace Catch {
+        void writeToDebugConsole( std::string const& text ) {
+            ::OutputDebugStringA( text.c_str() );
+        }
+    }
+#else
+    namespace Catch {
+        void writeToDebugConsole( std::string const& text ) {
+            // !TBD: Need a version for Mac/ XCode and other IDEs
+            Catch::cout() << text;
+        }
+    }
+#endif // Platform
+
+// #included from: catch_tostring.hpp
+#define TWOBLUECUBES_CATCH_TOSTRING_HPP_INCLUDED
+
+namespace Catch {
+
+namespace Detail {
+
+    const std::string unprintableString = "{?}";
+
+    namespace {
+        const int hexThreshold = 255;
+
+        struct Endianness {
+            enum Arch { Big, Little };
+
+            static Arch which() {
+                union _{
+                    int asInt;
+                    char asChar[sizeof (int)];
+                } u;
+
+                u.asInt = 1;
+                return ( u.asChar[sizeof(int)-1] == 1 ) ? Big : Little;
+            }
+        };
+    }
+
+    std::string rawMemoryToString( const void *object, std::size_t size )
+    {
+        // Reverse order for little endian architectures
+        int i = 0, end = static_cast<int>( size ), inc = 1;
+        if( Endianness::which() == Endianness::Little ) {
+            i = end-1;
+            end = inc = -1;
+        }
+
+        unsigned char const *bytes = static_cast<unsigned char const *>(object);
+        std::ostringstream os;
+        os << "0x" << std::setfill('0') << std::hex;
+        for( ; i != end; i += inc )
+             os << std::setw(2) << static_cast<unsigned>(bytes[i]);
+       return os.str();
+    }
+}
+
+std::string toString( std::string const& value ) {
+    std::string s = value;
+    if( getCurrentContext().getConfig()->showInvisibles() ) {
+        for(size_t i = 0; i < s.size(); ++i ) {
+            std::string subs;
+            switch( s[i] ) {
+            case '\n': subs = "\\n"; break;
+            case '\t': subs = "\\t"; break;
+            default: break;
+            }
+            if( !subs.empty() ) {
+                s = s.substr( 0, i ) + subs + s.substr( i+1 );
+                ++i;
+            }
+        }
+    }
+    return "\"" + s + "\"";
+}
+std::string toString( std::wstring const& value ) {
+
+    std::string s;
+    s.reserve( value.size() );
+    for(size_t i = 0; i < value.size(); ++i )
+        s += value[i] <= 0xff ? static_cast<char>( value[i] ) : '?';
+    return Catch::toString( s );
+}
+
+std::string toString( const char* const value ) {
+    return value ? Catch::toString( std::string( value ) ) : std::string( "{null string}" );
+}
+
+std::string toString( char* const value ) {
+    return Catch::toString( static_cast<const char*>( value ) );
+}
+
+std::string toString( const wchar_t* const value )
+{
+	return value ? Catch::toString( std::wstring(value) ) : std::string( "{null string}" );
+}
+
+std::string toString( wchar_t* const value )
+{
+	return Catch::toString( static_cast<const wchar_t*>( value ) );
+}
+
+std::string toString( int value ) {
+    std::ostringstream oss;
+    oss << value;
+    if( value > Detail::hexThreshold )
+        oss << " (0x" << std::hex << value << ")";
+    return oss.str();
+}
+
+std::string toString( unsigned long value ) {
+    std::ostringstream oss;
+    oss << value;
+    if( value > Detail::hexThreshold )
+        oss << " (0x" << std::hex << value << ")";
+    return oss.str();
+}
+
+std::string toString( unsigned int value ) {
+    return Catch::toString( static_cast<unsigned long>( value ) );
+}
+
+template<typename T>
+std::string fpToString( T value, int precision ) {
+    std::ostringstream oss;
+    oss << std::setprecision( precision )
+        << std::fixed
+        << value;
+    std::string d = oss.str();
+    std::size_t i = d.find_last_not_of( '0' );
+    if( i != std::string::npos && i != d.size()-1 ) {
+        if( d[i] == '.' )
+            i++;
+        d = d.substr( 0, i+1 );
+    }
+    return d;
+}
+
+std::string toString( const double value ) {
+    return fpToString( value, 10 );
+}
+std::string toString( const float value ) {
+    return fpToString( value, 5 ) + "f";
+}
+
+std::string toString( bool value ) {
+    return value ? "true" : "false";
+}
+
+std::string toString( char value ) {
+    return value < ' '
+        ? toString( static_cast<unsigned int>( value ) )
+        : Detail::makeString( value );
+}
+
+std::string toString( signed char value ) {
+    return toString( static_cast<char>( value ) );
+}
+
+std::string toString( unsigned char value ) {
+    return toString( static_cast<char>( value ) );
+}
+
+#ifdef CATCH_CONFIG_CPP11_LONG_LONG
+std::string toString( long long value ) {
+    std::ostringstream oss;
+    oss << value;
+    if( value > Detail::hexThreshold )
+        oss << " (0x" << std::hex << value << ")";
+    return oss.str();
+}
+std::string toString( unsigned long long value ) {
+    std::ostringstream oss;
+    oss << value;
+    if( value > Detail::hexThreshold )
+        oss << " (0x" << std::hex << value << ")";
+    return oss.str();
+}
+#endif
+
+#ifdef CATCH_CONFIG_CPP11_NULLPTR
+std::string toString( std::nullptr_t ) {
+    return "nullptr";
+}
+#endif
+
+#ifdef __OBJC__
+    std::string toString( NSString const * const& nsstring ) {
+        if( !nsstring )
+            return "nil";
+        return "@" + toString([nsstring UTF8String]);
+    }
+    std::string toString( NSString * CATCH_ARC_STRONG const& nsstring ) {
+        if( !nsstring )
+            return "nil";
+        return "@" + toString([nsstring UTF8String]);
+    }
+    std::string toString( NSObject* const& nsObject ) {
+        return toString( [nsObject description] );
+    }
+#endif
+
+} // end namespace Catch
+
+// #included from: catch_result_builder.hpp
+#define TWOBLUECUBES_CATCH_RESULT_BUILDER_HPP_INCLUDED
+
+namespace Catch {
+
+    std::string capturedExpressionWithSecondArgument( std::string const& capturedExpression, std::string const& secondArg ) {
+        return secondArg.empty() || secondArg == "\"\""
+            ? capturedExpression
+            : capturedExpression + ", " + secondArg;
+    }
+    ResultBuilder::ResultBuilder(   char const* macroName,
+                                    SourceLineInfo const& lineInfo,
+                                    char const* capturedExpression,
+                                    ResultDisposition::Flags resultDisposition,
+                                    char const* secondArg )
+    :   m_assertionInfo( macroName, lineInfo, capturedExpressionWithSecondArgument( capturedExpression, secondArg ), resultDisposition ),
+        m_shouldDebugBreak( false ),
+        m_shouldThrow( false )
+    {}
+
+    ResultBuilder& ResultBuilder::setResultType( ResultWas::OfType result ) {
+        m_data.resultType = result;
+        return *this;
+    }
+    ResultBuilder& ResultBuilder::setResultType( bool result ) {
+        m_data.resultType = result ? ResultWas::Ok : ResultWas::ExpressionFailed;
+        return *this;
+    }
+    ResultBuilder& ResultBuilder::setLhs( std::string const& lhs ) {
+        m_exprComponents.lhs = lhs;
+        return *this;
+    }
+    ResultBuilder& ResultBuilder::setRhs( std::string const& rhs ) {
+        m_exprComponents.rhs = rhs;
+        return *this;
+    }
+    ResultBuilder& ResultBuilder::setOp( std::string const& op ) {
+        m_exprComponents.op = op;
+        return *this;
+    }
+
+    void ResultBuilder::endExpression() {
+        m_exprComponents.testFalse = isFalseTest( m_assertionInfo.resultDisposition );
+        captureExpression();
+    }
+
+    void ResultBuilder::useActiveException( ResultDisposition::Flags resultDisposition ) {
+        m_assertionInfo.resultDisposition = resultDisposition;
+        m_stream.oss << Catch::translateActiveException();
+        captureResult( ResultWas::ThrewException );
+    }
+
+    void ResultBuilder::captureResult( ResultWas::OfType resultType ) {
+        setResultType( resultType );
+        captureExpression();
+    }
+    void ResultBuilder::captureExpectedException( std::string const& expectedMessage ) {
+        if( expectedMessage.empty() )
+            captureExpectedException( Matchers::Impl::Generic::AllOf<std::string>() );
+        else
+            captureExpectedException( Matchers::Equals( expectedMessage ) );
+    }
+
+    void ResultBuilder::captureExpectedException( Matchers::Impl::Matcher<std::string> const& matcher ) {
+
+        assert( m_exprComponents.testFalse == false );
+        AssertionResultData data = m_data;
+        data.resultType = ResultWas::Ok;
+        data.reconstructedExpression = m_assertionInfo.capturedExpression;
+
+        std::string actualMessage = Catch::translateActiveException();
+        if( !matcher.match( actualMessage ) ) {
+            data.resultType = ResultWas::ExpressionFailed;
+            data.reconstructedExpression = actualMessage;
+        }
+        AssertionResult result( m_assertionInfo, data );
+        handleResult( result );
+    }
+
+    void ResultBuilder::captureExpression() {
+        AssertionResult result = build();
+        handleResult( result );
+    }
+    void ResultBuilder::handleResult( AssertionResult const& result )
+    {
+        getResultCapture().assertionEnded( result );
+
+        if( !result.isOk() ) {
+            if( getCurrentContext().getConfig()->shouldDebugBreak() )
+                m_shouldDebugBreak = true;
+            if( getCurrentContext().getRunner()->aborting() || (m_assertionInfo.resultDisposition & ResultDisposition::Normal) )
+                m_shouldThrow = true;
+        }
+    }
+    void ResultBuilder::react() {
+        if( m_shouldThrow )
+            throw Catch::TestFailureException();
+    }
+
+    bool ResultBuilder::shouldDebugBreak() const { return m_shouldDebugBreak; }
+    bool ResultBuilder::allowThrows() const { return getCurrentContext().getConfig()->allowThrows(); }
+
+    AssertionResult ResultBuilder::build() const
+    {
+        assert( m_data.resultType != ResultWas::Unknown );
+
+        AssertionResultData data = m_data;
+
+        // Flip bool results if testFalse is set
+        if( m_exprComponents.testFalse ) {
+            if( data.resultType == ResultWas::Ok )
+                data.resultType = ResultWas::ExpressionFailed;
+            else if( data.resultType == ResultWas::ExpressionFailed )
+                data.resultType = ResultWas::Ok;
+        }
+
+        data.message = m_stream.oss.str();
+        data.reconstructedExpression = reconstructExpression();
+        if( m_exprComponents.testFalse ) {
+            if( m_exprComponents.op == "" )
+                data.reconstructedExpression = "!" + data.reconstructedExpression;
+            else
+                data.reconstructedExpression = "!(" + data.reconstructedExpression + ")";
+        }
+        return AssertionResult( m_assertionInfo, data );
+    }
+    std::string ResultBuilder::reconstructExpression() const {
+        if( m_exprComponents.op == "" )
+            return m_exprComponents.lhs.empty() ? m_assertionInfo.capturedExpression : m_exprComponents.lhs;
+        else if( m_exprComponents.op == "matches" )
+            return m_exprComponents.lhs + " " + m_exprComponents.rhs;
+        else if( m_exprComponents.op != "!" ) {
+            if( m_exprComponents.lhs.size() + m_exprComponents.rhs.size() < 40 &&
+                m_exprComponents.lhs.find("\n") == std::string::npos &&
+                m_exprComponents.rhs.find("\n") == std::string::npos )
+                return m_exprComponents.lhs + " " + m_exprComponents.op + " " + m_exprComponents.rhs;
+            else
+                return m_exprComponents.lhs + "\n" + m_exprComponents.op + "\n" + m_exprComponents.rhs;
+        }
+        else
+            return "{can't expand - use " + m_assertionInfo.macroName + "_FALSE( " + m_assertionInfo.capturedExpression.substr(1) + " ) instead of " + m_assertionInfo.macroName + "( " + m_assertionInfo.capturedExpression + " ) for better diagnostics}";
+    }
+
+} // end namespace Catch
+
+// #included from: catch_tag_alias_registry.hpp
+#define TWOBLUECUBES_CATCH_TAG_ALIAS_REGISTRY_HPP_INCLUDED
+
+// #included from: catch_tag_alias_registry.h
+#define TWOBLUECUBES_CATCH_TAG_ALIAS_REGISTRY_H_INCLUDED
+
+#include <map>
+
+namespace Catch {
+
+    class TagAliasRegistry : public ITagAliasRegistry {
+    public:
+        virtual ~TagAliasRegistry();
+        virtual Option<TagAlias> find( std::string const& alias ) const;
+        virtual std::string expandAliases( std::string const& unexpandedTestSpec ) const;
+        void add( char const* alias, char const* tag, SourceLineInfo const& lineInfo );
+        static TagAliasRegistry& get();
+
+    private:
+        std::map<std::string, TagAlias> m_registry;
+    };
+
+} // end namespace Catch
+
+#include <map>
+#include <iostream>
+
+namespace Catch {
+
+    TagAliasRegistry::~TagAliasRegistry() {}
+
+    Option<TagAlias> TagAliasRegistry::find( std::string const& alias ) const {
+        std::map<std::string, TagAlias>::const_iterator it = m_registry.find( alias );
+        if( it != m_registry.end() )
+            return it->second;
+        else
+            return Option<TagAlias>();
+    }
+
+    std::string TagAliasRegistry::expandAliases( std::string const& unexpandedTestSpec ) const {
+        std::string expandedTestSpec = unexpandedTestSpec;
+        for( std::map<std::string, TagAlias>::const_iterator it = m_registry.begin(), itEnd = m_registry.end();
+                it != itEnd;
+                ++it ) {
+            std::size_t pos = expandedTestSpec.find( it->first );
+            if( pos != std::string::npos ) {
+                expandedTestSpec =  expandedTestSpec.substr( 0, pos ) +
+                                    it->second.tag +
+                                    expandedTestSpec.substr( pos + it->first.size() );
+            }
+        }
+        return expandedTestSpec;
+    }
+
+    void TagAliasRegistry::add( char const* alias, char const* tag, SourceLineInfo const& lineInfo ) {
+
+        if( !startsWith( alias, "[@" ) || !endsWith( alias, "]" ) ) {
+            std::ostringstream oss;
+            oss << "error: tag alias, \"" << alias << "\" is not of the form [@alias name].\n" << lineInfo;
+            throw std::domain_error( oss.str().c_str() );
+        }
+        if( !m_registry.insert( std::make_pair( alias, TagAlias( tag, lineInfo ) ) ).second ) {
+            std::ostringstream oss;
+            oss << "error: tag alias, \"" << alias << "\" already registered.\n"
+                << "\tFirst seen at " << find(alias)->lineInfo << "\n"
+                << "\tRedefined at " << lineInfo;
+            throw std::domain_error( oss.str().c_str() );
+        }
+    }
+
+    TagAliasRegistry& TagAliasRegistry::get() {
+        static TagAliasRegistry instance;
+        return instance;
+
+    }
+
+    ITagAliasRegistry::~ITagAliasRegistry() {}
+    ITagAliasRegistry const& ITagAliasRegistry::get() { return TagAliasRegistry::get(); }
+
+    RegistrarForTagAliases::RegistrarForTagAliases( char const* alias, char const* tag, SourceLineInfo const& lineInfo ) {
+        try {
+            TagAliasRegistry::get().add( alias, tag, lineInfo );
+        }
+        catch( std::exception& ex ) {
+            Colour colourGuard( Colour::Red );
+            Catch::cerr() << ex.what() << std::endl;
+            exit(1);
+        }
+    }
+
+} // end namespace Catch
+
+// #included from: ../reporters/catch_reporter_multi.hpp
+#define TWOBLUECUBES_CATCH_REPORTER_MULTI_HPP_INCLUDED
+
+namespace Catch {
+
+class MultipleReporters : public SharedImpl<IStreamingReporter> {
+    typedef std::vector<Ptr<IStreamingReporter> > Reporters;
+    Reporters m_reporters;
+
+public:
+    void add( Ptr<IStreamingReporter> const& reporter ) {
+        m_reporters.push_back( reporter );
+    }
+
+public: // IStreamingReporter
+
+    virtual ReporterPreferences getPreferences() const CATCH_OVERRIDE {
+        return m_reporters[0]->getPreferences();
+    }
+
+    virtual void noMatchingTestCases( std::string const& spec ) CATCH_OVERRIDE {
+        for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end();
+                it != itEnd;
+                ++it )
+            (*it)->noMatchingTestCases( spec );
+    }
+
+    virtual void testRunStarting( TestRunInfo const& testRunInfo ) CATCH_OVERRIDE {
+        for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end();
+                it != itEnd;
+                ++it )
+            (*it)->testRunStarting( testRunInfo );
+    }
+
+    virtual void testGroupStarting( GroupInfo const& groupInfo ) CATCH_OVERRIDE {
+        for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end();
+                it != itEnd;
+                ++it )
+            (*it)->testGroupStarting( groupInfo );
+    }
+
+    virtual void testCaseStarting( TestCaseInfo const& testInfo ) CATCH_OVERRIDE {
+        for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end();
+                it != itEnd;
+                ++it )
+            (*it)->testCaseStarting( testInfo );
+    }
+
+    virtual void sectionStarting( SectionInfo const& sectionInfo ) CATCH_OVERRIDE {
+        for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end();
+                it != itEnd;
+                ++it )
+            (*it)->sectionStarting( sectionInfo );
+    }
+
+    virtual void assertionStarting( AssertionInfo const& assertionInfo ) CATCH_OVERRIDE {
+        for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end();
+                it != itEnd;
+                ++it )
+            (*it)->assertionStarting( assertionInfo );
+    }
+
+    // The return value indicates if the messages buffer should be cleared:
+    virtual bool assertionEnded( AssertionStats const& assertionStats ) CATCH_OVERRIDE {
+        bool clearBuffer = false;
+        for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end();
+                it != itEnd;
+                ++it )
+            clearBuffer |= (*it)->assertionEnded( assertionStats );
+        return clearBuffer;
+    }
+
+    virtual void sectionEnded( SectionStats const& sectionStats ) CATCH_OVERRIDE {
+        for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end();
+                it != itEnd;
+                ++it )
+            (*it)->sectionEnded( sectionStats );
+    }
+
+    virtual void testCaseEnded( TestCaseStats const& testCaseStats ) CATCH_OVERRIDE {
+        for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end();
+                it != itEnd;
+                ++it )
+            (*it)->testCaseEnded( testCaseStats );
+    }
+
+    virtual void testGroupEnded( TestGroupStats const& testGroupStats ) CATCH_OVERRIDE {
+        for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end();
+                it != itEnd;
+                ++it )
+            (*it)->testGroupEnded( testGroupStats );
+    }
+
+    virtual void testRunEnded( TestRunStats const& testRunStats ) CATCH_OVERRIDE {
+        for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end();
+                it != itEnd;
+                ++it )
+            (*it)->testRunEnded( testRunStats );
+    }
+
+    virtual void skipTest( TestCaseInfo const& testInfo ) CATCH_OVERRIDE {
+        for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end();
+                it != itEnd;
+                ++it )
+            (*it)->skipTest( testInfo );
+    }
+
+    virtual MultipleReporters* tryAsMulti() CATCH_OVERRIDE {
+        return this;
+    }
+
+};
+
+Ptr<IStreamingReporter> addReporter( Ptr<IStreamingReporter> const& existingReporter, Ptr<IStreamingReporter> const& additionalReporter ) {
+    Ptr<IStreamingReporter> resultingReporter;
+
+    if( existingReporter ) {
+        MultipleReporters* multi = existingReporter->tryAsMulti();
+        if( !multi ) {
+            multi = new MultipleReporters;
+            resultingReporter = Ptr<IStreamingReporter>( multi );
+            if( existingReporter )
+                multi->add( existingReporter );
+        }
+        else
+            resultingReporter = existingReporter;
+        multi->add( additionalReporter );
+    }
+    else
+        resultingReporter = additionalReporter;
+
+    return resultingReporter;
+}
+
+} // end namespace Catch
+
+// #included from: ../reporters/catch_reporter_xml.hpp
+#define TWOBLUECUBES_CATCH_REPORTER_XML_HPP_INCLUDED
+
+// #included from: catch_reporter_bases.hpp
+#define TWOBLUECUBES_CATCH_REPORTER_BASES_HPP_INCLUDED
+
+#include <cstring>
+
+namespace Catch {
+
+    struct StreamingReporterBase : SharedImpl<IStreamingReporter> {
+
+        StreamingReporterBase( ReporterConfig const& _config )
+        :   m_config( _config.fullConfig() ),
+            stream( _config.stream() )
+        {
+            m_reporterPrefs.shouldRedirectStdOut = false;
+        }
+
+        virtual ReporterPreferences getPreferences() const CATCH_OVERRIDE {
+            return m_reporterPrefs;
+        }
+
+        virtual ~StreamingReporterBase() CATCH_OVERRIDE;
+
+        virtual void noMatchingTestCases( std::string const& ) CATCH_OVERRIDE {}
+
+        virtual void testRunStarting( TestRunInfo const& _testRunInfo ) CATCH_OVERRIDE {
+            currentTestRunInfo = _testRunInfo;
+        }
+        virtual void testGroupStarting( GroupInfo const& _groupInfo ) CATCH_OVERRIDE {
+            currentGroupInfo = _groupInfo;
+        }
+
+        virtual void testCaseStarting( TestCaseInfo const& _testInfo ) CATCH_OVERRIDE {
+            currentTestCaseInfo = _testInfo;
+        }
+        virtual void sectionStarting( SectionInfo const& _sectionInfo ) CATCH_OVERRIDE {
+            m_sectionStack.push_back( _sectionInfo );
+        }
+
+        virtual void sectionEnded( SectionStats const& /* _sectionStats */ ) CATCH_OVERRIDE {
+            m_sectionStack.pop_back();
+        }
+        virtual void testCaseEnded( TestCaseStats const& /* _testCaseStats */ ) CATCH_OVERRIDE {
+            currentTestCaseInfo.reset();
+        }
+        virtual void testGroupEnded( TestGroupStats const& /* _testGroupStats */ ) CATCH_OVERRIDE {
+            currentGroupInfo.reset();
+        }
+        virtual void testRunEnded( TestRunStats const& /* _testRunStats */ ) CATCH_OVERRIDE {
+            currentTestCaseInfo.reset();
+            currentGroupInfo.reset();
+            currentTestRunInfo.reset();
+        }
+
+        virtual void skipTest( TestCaseInfo const& ) CATCH_OVERRIDE {
+            // Don't do anything with this by default.
+            // It can optionally be overridden in the derived class.
+        }
+
+        Ptr<IConfig const> m_config;
+        std::ostream& stream;
+
+        LazyStat<TestRunInfo> currentTestRunInfo;
+        LazyStat<GroupInfo> currentGroupInfo;
+        LazyStat<TestCaseInfo> currentTestCaseInfo;
+
+        std::vector<SectionInfo> m_sectionStack;
+        ReporterPreferences m_reporterPrefs;
+    };
+
+    struct CumulativeReporterBase : SharedImpl<IStreamingReporter> {
+        template<typename T, typename ChildNodeT>
+        struct Node : SharedImpl<> {
+            explicit Node( T const& _value ) : value( _value ) {}
+            virtual ~Node() {}
+
+            typedef std::vector<Ptr<ChildNodeT> > ChildNodes;
+            T value;
+            ChildNodes children;
+        };
+        struct SectionNode : SharedImpl<> {
+            explicit SectionNode( SectionStats const& _stats ) : stats( _stats ) {}
+            virtual ~SectionNode();
+
+            bool operator == ( SectionNode const& other ) const {
+                return stats.sectionInfo.lineInfo == other.stats.sectionInfo.lineInfo;
+            }
+            bool operator == ( Ptr<SectionNode> const& other ) const {
+                return operator==( *other );
+            }
+
+            SectionStats stats;
+            typedef std::vector<Ptr<SectionNode> > ChildSections;
+            typedef std::vector<AssertionStats> Assertions;
+            ChildSections childSections;
+            Assertions assertions;
+            std::string stdOut;
+            std::string stdErr;
+        };
+
+        struct BySectionInfo {
+            BySectionInfo( SectionInfo const& other ) : m_other( other ) {}
+			BySectionInfo( BySectionInfo const& other ) : m_other( other.m_other ) {}
+            bool operator() ( Ptr<SectionNode> const& node ) const {
+                return node->stats.sectionInfo.lineInfo == m_other.lineInfo;
+            }
+        private:
+			void operator=( BySectionInfo const& );
+            SectionInfo const& m_other;
+        };
+
+        typedef Node<TestCaseStats, SectionNode> TestCaseNode;
+        typedef Node<TestGroupStats, TestCaseNode> TestGroupNode;
+        typedef Node<TestRunStats, TestGroupNode> TestRunNode;
+
+        CumulativeReporterBase( ReporterConfig const& _config )
+        :   m_config( _config.fullConfig() ),
+            stream( _config.stream() )
+        {
+            m_reporterPrefs.shouldRedirectStdOut = false;
+        }
+        ~CumulativeReporterBase();
+
+        virtual ReporterPreferences getPreferences() const CATCH_OVERRIDE {
+            return m_reporterPrefs;
+        }
+
+        virtual void testRunStarting( TestRunInfo const& ) CATCH_OVERRIDE {}
+        virtual void testGroupStarting( GroupInfo const& ) CATCH_OVERRIDE {}
+
+        virtual void testCaseStarting( TestCaseInfo const& ) CATCH_OVERRIDE {}
+
+        virtual void sectionStarting( SectionInfo const& sectionInfo ) CATCH_OVERRIDE {
+            SectionStats incompleteStats( sectionInfo, Counts(), 0, false );
+            Ptr<SectionNode> node;
+            if( m_sectionStack.empty() ) {
+                if( !m_rootSection )
+                    m_rootSection = new SectionNode( incompleteStats );
+                node = m_rootSection;
+            }
+            else {
+                SectionNode& parentNode = *m_sectionStack.back();
+                SectionNode::ChildSections::const_iterator it =
+                    std::find_if(   parentNode.childSections.begin(),
+                                    parentNode.childSections.end(),
+                                    BySectionInfo( sectionInfo ) );
+                if( it == parentNode.childSections.end() ) {
+                    node = new SectionNode( incompleteStats );
+                    parentNode.childSections.push_back( node );
+                }
+                else
+                    node = *it;
+            }
+            m_sectionStack.push_back( node );
+            m_deepestSection = node;
+        }
+
+        virtual void assertionStarting( AssertionInfo const& ) CATCH_OVERRIDE {}
+
+        virtual bool assertionEnded( AssertionStats const& assertionStats ) CATCH_OVERRIDE {
+            assert( !m_sectionStack.empty() );
+            SectionNode& sectionNode = *m_sectionStack.back();
+            sectionNode.assertions.push_back( assertionStats );
+            return true;
+        }
+        virtual void sectionEnded( SectionStats const& sectionStats ) CATCH_OVERRIDE {
+            assert( !m_sectionStack.empty() );
+            SectionNode& node = *m_sectionStack.back();
+            node.stats = sectionStats;
+            m_sectionStack.pop_back();
+        }
+        virtual void testCaseEnded( TestCaseStats const& testCaseStats ) CATCH_OVERRIDE {
+            Ptr<TestCaseNode> node = new TestCaseNode( testCaseStats );
+            assert( m_sectionStack.size() == 0 );
+            node->children.push_back( m_rootSection );
+            m_testCases.push_back( node );
+            m_rootSection.reset();
+
+            assert( m_deepestSection );
+            m_deepestSection->stdOut = testCaseStats.stdOut;
+            m_deepestSection->stdErr = testCaseStats.stdErr;
+        }
+        virtual void testGroupEnded( TestGroupStats const& testGroupStats ) CATCH_OVERRIDE {
+            Ptr<TestGroupNode> node = new TestGroupNode( testGroupStats );
+            node->children.swap( m_testCases );
+            m_testGroups.push_back( node );
+        }
+        virtual void testRunEnded( TestRunStats const& testRunStats ) CATCH_OVERRIDE {
+            Ptr<TestRunNode> node = new TestRunNode( testRunStats );
+            node->children.swap( m_testGroups );
+            m_testRuns.push_back( node );
+            testRunEndedCumulative();
+        }
+        virtual void testRunEndedCumulative() = 0;
+
+        virtual void skipTest( TestCaseInfo const& ) CATCH_OVERRIDE {}
+
+        Ptr<IConfig const> m_config;
+        std::ostream& stream;
+        std::vector<AssertionStats> m_assertions;
+        std::vector<std::vector<Ptr<SectionNode> > > m_sections;
+        std::vector<Ptr<TestCaseNode> > m_testCases;
+        std::vector<Ptr<TestGroupNode> > m_testGroups;
+
+        std::vector<Ptr<TestRunNode> > m_testRuns;
+
+        Ptr<SectionNode> m_rootSection;
+        Ptr<SectionNode> m_deepestSection;
+        std::vector<Ptr<SectionNode> > m_sectionStack;
+        ReporterPreferences m_reporterPrefs;
+
+    };
+
+    template<char C>
+    char const* getLineOfChars() {
+        static char line[CATCH_CONFIG_CONSOLE_WIDTH] = {0};
+        if( !*line ) {
+            memset( line, C, CATCH_CONFIG_CONSOLE_WIDTH-1 );
+            line[CATCH_CONFIG_CONSOLE_WIDTH-1] = 0;
+        }
+        return line;
+    }
+
+    struct TestEventListenerBase : StreamingReporterBase {
+        TestEventListenerBase( ReporterConfig const& _config )
+        :   StreamingReporterBase( _config )
+        {}
+
+        virtual void assertionStarting( AssertionInfo const& ) CATCH_OVERRIDE {}
+        virtual bool assertionEnded( AssertionStats const& ) CATCH_OVERRIDE {
+            return false;
+        }
+    };
+
+} // end namespace Catch
+
+// #included from: ../internal/catch_reporter_registrars.hpp
+#define TWOBLUECUBES_CATCH_REPORTER_REGISTRARS_HPP_INCLUDED
+
+namespace Catch {
+
+    template<typename T>
+    class LegacyReporterRegistrar {
+
+        class ReporterFactory : public IReporterFactory {
+            virtual IStreamingReporter* create( ReporterConfig const& config ) const {
+                return new LegacyReporterAdapter( new T( config ) );
+            }
+
+            virtual std::string getDescription() const {
+                return T::getDescription();
+            }
+        };
+
+    public:
+
+        LegacyReporterRegistrar( std::string const& name ) {
+            getMutableRegistryHub().registerReporter( name, new ReporterFactory() );
+        }
+    };
+
+    template<typename T>
+    class ReporterRegistrar {
+
+        class ReporterFactory : public SharedImpl<IReporterFactory> {
+
+            // *** Please Note ***:
+            // - If you end up here looking at a compiler error because it's trying to register
+            // your custom reporter class be aware that the native reporter interface has changed
+            // to IStreamingReporter. The "legacy" interface, IReporter, is still supported via
+            // an adapter. Just use REGISTER_LEGACY_REPORTER to take advantage of the adapter.
+            // However please consider updating to the new interface as the old one is now
+            // deprecated and will probably be removed quite soon!
+            // Please contact me via github if you have any questions at all about this.
+            // In fact, ideally, please contact me anyway to let me know you've hit this - as I have
+            // no idea who is actually using custom reporters at all (possibly no-one!).
+            // The new interface is designed to minimise exposure to interface changes in the future.
+            virtual IStreamingReporter* create( ReporterConfig const& config ) const {
+                return new T( config );
+            }
+
+            virtual std::string getDescription() const {
+                return T::getDescription();
+            }
+        };
+
+    public:
+
+        ReporterRegistrar( std::string const& name ) {
+            getMutableRegistryHub().registerReporter( name, new ReporterFactory() );
+        }
+    };
+
+    template<typename T>
+    class ListenerRegistrar {
+
+        class ListenerFactory : public SharedImpl<IReporterFactory> {
+
+            virtual IStreamingReporter* create( ReporterConfig const& config ) const {
+                return new T( config );
+            }
+            virtual std::string getDescription() const {
+                return "";
+            }
+        };
+
+    public:
+
+        ListenerRegistrar() {
+            getMutableRegistryHub().registerListener( new ListenerFactory() );
+        }
+    };
+}
+
+#define INTERNAL_CATCH_REGISTER_LEGACY_REPORTER( name, reporterType ) \
+    namespace{ Catch::LegacyReporterRegistrar<reporterType> catch_internal_RegistrarFor##reporterType( name ); }
+
+#define INTERNAL_CATCH_REGISTER_REPORTER( name, reporterType ) \
+    namespace{ Catch::ReporterRegistrar<reporterType> catch_internal_RegistrarFor##reporterType( name ); }
+
+#define INTERNAL_CATCH_REGISTER_LISTENER( listenerType ) \
+    namespace{ Catch::ListenerRegistrar<listenerType> catch_internal_RegistrarFor##listenerType; }
+
+// #included from: ../internal/catch_xmlwriter.hpp
+#define TWOBLUECUBES_CATCH_XMLWRITER_HPP_INCLUDED
+
+#include <sstream>
+#include <string>
+#include <vector>
+#include <iomanip>
+
+namespace Catch {
+
+    class XmlEncode {
+    public:
+        enum ForWhat { ForTextNodes, ForAttributes };
+
+        XmlEncode( std::string const& str, ForWhat forWhat = ForTextNodes )
+        :   m_str( str ),
+            m_forWhat( forWhat )
+        {}
+
+        void encodeTo( std::ostream& os ) const {
+
+            // Apostrophe escaping not necessary if we always use " to write attributes
+            // (see: http://www.w3.org/TR/xml/#syntax)
+
+            for( std::size_t i = 0; i < m_str.size(); ++ i ) {
+                char c = m_str[i];
+                switch( c ) {
+                    case '<':   os << "<"; break;
+                    case '&':   os << "&"; break;
+
+                    case '>':
+                        // See: http://www.w3.org/TR/xml/#syntax
+                        if( i > 2 && m_str[i-1] == ']' && m_str[i-2] == ']' )
+                            os << ">";
+                        else
+                            os << c;
+                        break;
+
+                    case '\"':
+                        if( m_forWhat == ForAttributes )
+                            os << """;
+                        else
+                            os << c;
+                        break;
+
+                    default:
+                        // Escape control chars - based on contribution by @espenalb in PR #465 and
+                        // by @mrpi PR #588
+                        if ( ( c >= 0 && c < '\x09' ) || ( c > '\x0D' && c < '\x20') || c=='\x7F' )
+                            os << "&#x" << std::uppercase << std::hex << std::setfill('0') << std::setw(2) << static_cast<int>( c ) << ';';
+                        else
+                            os << c;
+                }
+            }
+        }
+
+        friend std::ostream& operator << ( std::ostream& os, XmlEncode const& xmlEncode ) {
+            xmlEncode.encodeTo( os );
+            return os;
+        }
+
+    private:
+        std::string m_str;
+        ForWhat m_forWhat;
+    };
+
+    class XmlWriter {
+    public:
+
+        class ScopedElement {
+        public:
+            ScopedElement( XmlWriter* writer )
+            :   m_writer( writer )
+            {}
+
+            ScopedElement( ScopedElement const& other )
+            :   m_writer( other.m_writer ){
+                other.m_writer = CATCH_NULL;
+            }
+
+            ~ScopedElement() {
+                if( m_writer )
+                    m_writer->endElement();
+            }
+
+            ScopedElement& writeText( std::string const& text, bool indent = true ) {
+                m_writer->writeText( text, indent );
+                return *this;
+            }
+
+            template<typename T>
+            ScopedElement& writeAttribute( std::string const& name, T const& attribute ) {
+                m_writer->writeAttribute( name, attribute );
+                return *this;
+            }
+
+        private:
+            mutable XmlWriter* m_writer;
+        };
+
+        XmlWriter()
+        :   m_tagIsOpen( false ),
+            m_needsNewline( false ),
+            m_os( &Catch::cout() )
+        {
+            // We encode control characters, which requires
+            // XML 1.1
+            // see http://stackoverflow.com/questions/404107/why-are-control-characters-illegal-in-xml-1-0
+            *m_os << "<?xml version=\"1.1\" encoding=\"UTF-8\"?>\n";
+        }
+
+        XmlWriter( std::ostream& os )
+        :   m_tagIsOpen( false ),
+            m_needsNewline( false ),
+            m_os( &os )
+        {
+            *m_os << "<?xml version=\"1.1\" encoding=\"UTF-8\"?>\n";
+        }
+
+        ~XmlWriter() {
+            while( !m_tags.empty() )
+                endElement();
+        }
+
+        XmlWriter& startElement( std::string const& name ) {
+            ensureTagClosed();
+            newlineIfNecessary();
+            stream() << m_indent << "<" << name;
+            m_tags.push_back( name );
+            m_indent += "  ";
+            m_tagIsOpen = true;
+            return *this;
+        }
+
+        ScopedElement scopedElement( std::string const& name ) {
+            ScopedElement scoped( this );
+            startElement( name );
+            return scoped;
+        }
+
+        XmlWriter& endElement() {
+            newlineIfNecessary();
+            m_indent = m_indent.substr( 0, m_indent.size()-2 );
+            if( m_tagIsOpen ) {
+                stream() << "/>\n";
+                m_tagIsOpen = false;
+            }
+            else {
+                stream() << m_indent << "</" << m_tags.back() << ">\n";
+            }
+            m_tags.pop_back();
+            return *this;
+        }
+
+        XmlWriter& writeAttribute( std::string const& name, std::string const& attribute ) {
+            if( !name.empty() && !attribute.empty() )
+                stream() << " " << name << "=\"" << XmlEncode( attribute, XmlEncode::ForAttributes ) << "\"";
+            return *this;
+        }
+
+        XmlWriter& writeAttribute( std::string const& name, bool attribute ) {
+            stream() << " " << name << "=\"" << ( attribute ? "true" : "false" ) << "\"";
+            return *this;
+        }
+
+        template<typename T>
+        XmlWriter& writeAttribute( std::string const& name, T const& attribute ) {
+            std::ostringstream oss;
+            oss << attribute;
+            return writeAttribute( name, oss.str() );
+        }
+
+        XmlWriter& writeText( std::string const& text, bool indent = true ) {
+            if( !text.empty() ){
+                bool tagWasOpen = m_tagIsOpen;
+                ensureTagClosed();
+                if( tagWasOpen && indent )
+                    stream() << m_indent;
+                stream() << XmlEncode( text );
+                m_needsNewline = true;
+            }
+            return *this;
+        }
+
+        XmlWriter& writeComment( std::string const& text ) {
+            ensureTagClosed();
+            stream() << m_indent << "<!--" << text << "-->";
+            m_needsNewline = true;
+            return *this;
+        }
+
+        XmlWriter& writeBlankLine() {
+            ensureTagClosed();
+            stream() << "\n";
+            return *this;
+        }
+
+        void setStream( std::ostream& os ) {
+            m_os = &os;
+        }
+
+    private:
+        XmlWriter( XmlWriter const& );
+        void operator=( XmlWriter const& );
+
+        std::ostream& stream() {
+            return *m_os;
+        }
+
+        void ensureTagClosed() {
+            if( m_tagIsOpen ) {
+                stream() << ">\n";
+                m_tagIsOpen = false;
+            }
+        }
+
+        void newlineIfNecessary() {
+            if( m_needsNewline ) {
+                stream() << "\n";
+                m_needsNewline = false;
+            }
+        }
+
+        bool m_tagIsOpen;
+        bool m_needsNewline;
+        std::vector<std::string> m_tags;
+        std::string m_indent;
+        std::ostream* m_os;
+    };
+
+}
+// #included from: catch_reenable_warnings.h
+
+#define TWOBLUECUBES_CATCH_REENABLE_WARNINGS_H_INCLUDED
+
+#ifdef __clang__
+#    ifdef __ICC // icpc defines the __clang__ macro
+#        pragma warning(pop)
+#    else
+#        pragma clang diagnostic pop
+#    endif
+#elif defined __GNUC__
+#    pragma GCC diagnostic pop
+#endif
+
+
+namespace Catch {
+    class XmlReporter : public StreamingReporterBase {
+    public:
+        XmlReporter( ReporterConfig const& _config )
+        :   StreamingReporterBase( _config ),
+            m_xml(_config.stream()),
+            m_sectionDepth( 0 )
+        {
+            m_reporterPrefs.shouldRedirectStdOut = true;
+        }
+
+        virtual ~XmlReporter() CATCH_OVERRIDE;
+
+        static std::string getDescription() {
+            return "Reports test results as an XML document";
+        }
+
+    public: // StreamingReporterBase
+
+        virtual void noMatchingTestCases( std::string const& s ) CATCH_OVERRIDE {
+            StreamingReporterBase::noMatchingTestCases( s );
+        }
+
+        virtual void testRunStarting( TestRunInfo const& testInfo ) CATCH_OVERRIDE {
+            StreamingReporterBase::testRunStarting( testInfo );
+            m_xml.startElement( "Catch" );
+            if( !m_config->name().empty() )
+                m_xml.writeAttribute( "name", m_config->name() );
+        }
+
+        virtual void testGroupStarting( GroupInfo const& groupInfo ) CATCH_OVERRIDE {
+            StreamingReporterBase::testGroupStarting( groupInfo );
+            m_xml.startElement( "Group" )
+                .writeAttribute( "name", groupInfo.name );
+        }
+
+        virtual void testCaseStarting( TestCaseInfo const& testInfo ) CATCH_OVERRIDE {
+            StreamingReporterBase::testCaseStarting(testInfo);
+            m_xml.startElement( "TestCase" ).writeAttribute( "name", testInfo.name );
+
+            if ( m_config->showDurations() == ShowDurations::Always )
+                m_testCaseTimer.start();
+        }
+
+        virtual void sectionStarting( SectionInfo const& sectionInfo ) CATCH_OVERRIDE {
+            StreamingReporterBase::sectionStarting( sectionInfo );
+            if( m_sectionDepth++ > 0 ) {
+                m_xml.startElement( "Section" )
+                    .writeAttribute( "name", trim( sectionInfo.name ) )
+                    .writeAttribute( "description", sectionInfo.description );
+            }
+        }
+
+        virtual void assertionStarting( AssertionInfo const& ) CATCH_OVERRIDE { }
+
+        virtual bool assertionEnded( AssertionStats const& assertionStats ) CATCH_OVERRIDE {
+            const AssertionResult& assertionResult = assertionStats.assertionResult;
+
+            // Print any info messages in <Info> tags.
+            if( assertionStats.assertionResult.getResultType() != ResultWas::Ok ) {
+                for( std::vector<MessageInfo>::const_iterator it = assertionStats.infoMessages.begin(), itEnd = assertionStats.infoMessages.end();
+                        it != itEnd;
+                        ++it ) {
+                    if( it->type == ResultWas::Info ) {
+                        m_xml.scopedElement( "Info" )
+                            .writeText( it->message );
+                    } else if ( it->type == ResultWas::Warning ) {
+                        m_xml.scopedElement( "Warning" )
+                            .writeText( it->message );
+                    }
+                }
+            }
+
+            // Drop out if result was successful but we're not printing them.
+            if( !m_config->includeSuccessfulResults() && isOk(assertionResult.getResultType()) )
+                return true;
+
+            // Print the expression if there is one.
+            if( assertionResult.hasExpression() ) {
+                m_xml.startElement( "Expression" )
+                    .writeAttribute( "success", assertionResult.succeeded() )
+					.writeAttribute( "type", assertionResult.getTestMacroName() )
+                    .writeAttribute( "filename", assertionResult.getSourceInfo().file )
+                    .writeAttribute( "line", assertionResult.getSourceInfo().line );
+
+                m_xml.scopedElement( "Original" )
+                    .writeText( assertionResult.getExpression() );
+                m_xml.scopedElement( "Expanded" )
+                    .writeText( assertionResult.getExpandedExpression() );
+            }
+
+            // And... Print a result applicable to each result type.
+            switch( assertionResult.getResultType() ) {
+                case ResultWas::ThrewException:
+                    m_xml.scopedElement( "Exception" )
+                        .writeAttribute( "filename", assertionResult.getSourceInfo().file )
+                        .writeAttribute( "line", assertionResult.getSourceInfo().line )
+                        .writeText( assertionResult.getMessage() );
+                    break;
+                case ResultWas::FatalErrorCondition:
+                    m_xml.scopedElement( "FatalErrorCondition" )
+                        .writeAttribute( "filename", assertionResult.getSourceInfo().file )
+                        .writeAttribute( "line", assertionResult.getSourceInfo().line )
+                        .writeText( assertionResult.getMessage() );
+                    break;
+                case ResultWas::Info:
+                    m_xml.scopedElement( "Info" )
+                        .writeText( assertionResult.getMessage() );
+                    break;
+                case ResultWas::Warning:
+                    // Warning will already have been written
+                    break;
+                case ResultWas::ExplicitFailure:
+                    m_xml.scopedElement( "Failure" )
+                        .writeText( assertionResult.getMessage() );
+                    break;
+                default:
+                    break;
+            }
+
+            if( assertionResult.hasExpression() )
+                m_xml.endElement();
+
+            return true;
+        }
+
+        virtual void sectionEnded( SectionStats const& sectionStats ) CATCH_OVERRIDE {
+            StreamingReporterBase::sectionEnded( sectionStats );
+            if( --m_sectionDepth > 0 ) {
+                XmlWriter::ScopedElement e = m_xml.scopedElement( "OverallResults" );
+                e.writeAttribute( "successes", sectionStats.assertions.passed );
+                e.writeAttribute( "failures", sectionStats.assertions.failed );
+                e.writeAttribute( "expectedFailures", sectionStats.assertions.failedButOk );
+
+                if ( m_config->showDurations() == ShowDurations::Always )
+                    e.writeAttribute( "durationInSeconds", sectionStats.durationInSeconds );
+
+                m_xml.endElement();
+            }
+        }
+
+        virtual void testCaseEnded( TestCaseStats const& testCaseStats ) CATCH_OVERRIDE {
+            StreamingReporterBase::testCaseEnded( testCaseStats );
+            XmlWriter::ScopedElement e = m_xml.scopedElement( "OverallResult" );
+            e.writeAttribute( "success", testCaseStats.totals.assertions.allOk() );
+
+            if ( m_config->showDurations() == ShowDurations::Always )
+                e.writeAttribute( "durationInSeconds", m_testCaseTimer.getElapsedSeconds() );
+
+            m_xml.endElement();
+        }
+
+        virtual void testGroupEnded( TestGroupStats const& testGroupStats ) CATCH_OVERRIDE {
+            StreamingReporterBase::testGroupEnded( testGroupStats );
+            // TODO: Check testGroupStats.aborting and act accordingly.
+            m_xml.scopedElement( "OverallResults" )
+                .writeAttribute( "successes", testGroupStats.totals.assertions.passed )
+                .writeAttribute( "failures", testGroupStats.totals.assertions.failed )
+                .writeAttribute( "expectedFailures", testGroupStats.totals.assertions.failedButOk );
+            m_xml.endElement();
+        }
+
+        virtual void testRunEnded( TestRunStats const& testRunStats ) CATCH_OVERRIDE {
+            StreamingReporterBase::testRunEnded( testRunStats );
+            m_xml.scopedElement( "OverallResults" )
+                .writeAttribute( "successes", testRunStats.totals.assertions.passed )
+                .writeAttribute( "failures", testRunStats.totals.assertions.failed )
+                .writeAttribute( "expectedFailures", testRunStats.totals.assertions.failedButOk );
+            m_xml.endElement();
+        }
+
+    private:
+        Timer m_testCaseTimer;
+        XmlWriter m_xml;
+        int m_sectionDepth;
+    };
+
+     INTERNAL_CATCH_REGISTER_REPORTER( "xml", XmlReporter )
+
+} // end namespace Catch
+
+// #included from: ../reporters/catch_reporter_junit.hpp
+#define TWOBLUECUBES_CATCH_REPORTER_JUNIT_HPP_INCLUDED
+
+#include <assert.h>
+
+namespace Catch {
+
+    namespace {
+        std::string getCurrentTimestamp() {
+            // Beware, this is not reentrant because of backward compatibility issues
+            // Also, UTC only, again because of backward compatibility (%z is C++11)
+            time_t rawtime;
+            std::time(&rawtime);
+            const size_t timeStampSize = sizeof("2017-01-16T17:06:45Z");
+
+#ifdef CATCH_PLATFORM_WINDOWS
+            std::tm timeInfo = {};
+            gmtime_s(&timeInfo, &rawtime);
+#else
+            std::tm* timeInfo;
+            timeInfo = std::gmtime(&rawtime);
+#endif
+
+            char timeStamp[timeStampSize];
+            const char * const fmt = "%Y-%m-%dT%H:%M:%SZ";
+
+#ifdef CATCH_PLATFORM_WINDOWS
+            std::strftime(timeStamp, timeStampSize, fmt, &timeInfo);
+#else
+            std::strftime(timeStamp, timeStampSize, fmt, timeInfo);
+#endif
+            return std::string(timeStamp);
+        }
+
+    }
+
+    class JunitReporter : public CumulativeReporterBase {
+    public:
+        JunitReporter( ReporterConfig const& _config )
+        :   CumulativeReporterBase( _config ),
+            xml( _config.stream() )
+        {
+            m_reporterPrefs.shouldRedirectStdOut = true;
+        }
+
+        virtual ~JunitReporter() CATCH_OVERRIDE;
+
+        static std::string getDescription() {
+            return "Reports test results in an XML format that looks like Ant's junitreport target";
+        }
+
+        virtual void noMatchingTestCases( std::string const& /*spec*/ ) CATCH_OVERRIDE {}
+
+        virtual void testRunStarting( TestRunInfo const& runInfo ) CATCH_OVERRIDE {
+            CumulativeReporterBase::testRunStarting( runInfo );
+            xml.startElement( "testsuites" );
+        }
+
+        virtual void testGroupStarting( GroupInfo const& groupInfo ) CATCH_OVERRIDE {
+            suiteTimer.start();
+            stdOutForSuite.str("");
+            stdErrForSuite.str("");
+            unexpectedExceptions = 0;
+            CumulativeReporterBase::testGroupStarting( groupInfo );
+        }
+
+        virtual bool assertionEnded( AssertionStats const& assertionStats ) CATCH_OVERRIDE {
+            if( assertionStats.assertionResult.getResultType() == ResultWas::ThrewException )
+                unexpectedExceptions++;
+            return CumulativeReporterBase::assertionEnded( assertionStats );
+        }
+
+        virtual void testCaseEnded( TestCaseStats const& testCaseStats ) CATCH_OVERRIDE {
+            stdOutForSuite << testCaseStats.stdOut;
+            stdErrForSuite << testCaseStats.stdErr;
+            CumulativeReporterBase::testCaseEnded( testCaseStats );
+        }
+
+        virtual void testGroupEnded( TestGroupStats const& testGroupStats ) CATCH_OVERRIDE {
+            double suiteTime = suiteTimer.getElapsedSeconds();
+            CumulativeReporterBase::testGroupEnded( testGroupStats );
+            writeGroup( *m_testGroups.back(), suiteTime );
+        }
+
+        virtual void testRunEndedCumulative() CATCH_OVERRIDE {
+            xml.endElement();
+        }
+
+        void writeGroup( TestGroupNode const& groupNode, double suiteTime ) {
+            XmlWriter::ScopedElement e = xml.scopedElement( "testsuite" );
+            TestGroupStats const& stats = groupNode.value;
+            xml.writeAttribute( "name", stats.groupInfo.name );
+            xml.writeAttribute( "errors", unexpectedExceptions );
+            xml.writeAttribute( "failures", stats.totals.assertions.failed-unexpectedExceptions );
+            xml.writeAttribute( "tests", stats.totals.assertions.total() );
+            xml.writeAttribute( "hostname", "tbd" ); // !TBD
+            if( m_config->showDurations() == ShowDurations::Never )
+                xml.writeAttribute( "time", "" );
+            else
+                xml.writeAttribute( "time", suiteTime );
+            xml.writeAttribute( "timestamp", getCurrentTimestamp() );
+
+            // Write test cases
+            for( TestGroupNode::ChildNodes::const_iterator
+                    it = groupNode.children.begin(), itEnd = groupNode.children.end();
+                    it != itEnd;
+                    ++it )
+                writeTestCase( **it );
+
+            xml.scopedElement( "system-out" ).writeText( trim( stdOutForSuite.str() ), false );
+            xml.scopedElement( "system-err" ).writeText( trim( stdErrForSuite.str() ), false );
+        }
+
+        void writeTestCase( TestCaseNode const& testCaseNode ) {
+            TestCaseStats const& stats = testCaseNode.value;
+
+            // All test cases have exactly one section - which represents the
+            // test case itself. That section may have 0-n nested sections
+            assert( testCaseNode.children.size() == 1 );
+            SectionNode const& rootSection = *testCaseNode.children.front();
+
+            std::string className = stats.testInfo.className;
+
+            if( className.empty() ) {
+                if( rootSection.childSections.empty() )
+                    className = "global";
+            }
+            writeSection( className, "", rootSection );
+        }
+
+        void writeSection(  std::string const& className,
+                            std::string const& rootName,
+                            SectionNode const& sectionNode ) {
+            std::string name = trim( sectionNode.stats.sectionInfo.name );
+            if( !rootName.empty() )
+                name = rootName + "/" + name;
+
+            if( !sectionNode.assertions.empty() ||
+                !sectionNode.stdOut.empty() ||
+                !sectionNode.stdErr.empty() ) {
+                XmlWriter::ScopedElement e = xml.scopedElement( "testcase" );
+                if( className.empty() ) {
+                    xml.writeAttribute( "classname", name );
+                    xml.writeAttribute( "name", "root" );
+                }
+                else {
+                    xml.writeAttribute( "classname", className );
+                    xml.writeAttribute( "name", name );
+                }
+                xml.writeAttribute( "time", Catch::toString( sectionNode.stats.durationInSeconds ) );
+
+                writeAssertions( sectionNode );
+
+                if( !sectionNode.stdOut.empty() )
+                    xml.scopedElement( "system-out" ).writeText( trim( sectionNode.stdOut ), false );
+                if( !sectionNode.stdErr.empty() )
+                    xml.scopedElement( "system-err" ).writeText( trim( sectionNode.stdErr ), false );
+            }
+            for( SectionNode::ChildSections::const_iterator
+                    it = sectionNode.childSections.begin(),
+                    itEnd = sectionNode.childSections.end();
+                    it != itEnd;
+                    ++it )
+                if( className.empty() )
+                    writeSection( name, "", **it );
+                else
+                    writeSection( className, name, **it );
+        }
+
+        void writeAssertions( SectionNode const& sectionNode ) {
+            for( SectionNode::Assertions::const_iterator
+                    it = sectionNode.assertions.begin(), itEnd = sectionNode.assertions.end();
+                    it != itEnd;
+                    ++it )
+                writeAssertion( *it );
+        }
+        void writeAssertion( AssertionStats const& stats ) {
+            AssertionResult const& result = stats.assertionResult;
+            if( !result.isOk() ) {
+                std::string elementName;
+                switch( result.getResultType() ) {
+                    case ResultWas::ThrewException:
+                    case ResultWas::FatalErrorCondition:
+                        elementName = "error";
+                        break;
+                    case ResultWas::ExplicitFailure:
+                        elementName = "failure";
+                        break;
+                    case ResultWas::ExpressionFailed:
+                        elementName = "failure";
+                        break;
+                    case ResultWas::DidntThrowException:
+                        elementName = "failure";
+                        break;
+
+                    // We should never see these here:
+                    case ResultWas::Info:
+                    case ResultWas::Warning:
+                    case ResultWas::Ok:
+                    case ResultWas::Unknown:
+                    case ResultWas::FailureBit:
+                    case ResultWas::Exception:
+                        elementName = "internalError";
+                        break;
+                }
+
+                XmlWriter::ScopedElement e = xml.scopedElement( elementName );
+
+                xml.writeAttribute( "message", result.getExpandedExpression() );
+                xml.writeAttribute( "type", result.getTestMacroName() );
+
+                std::ostringstream oss;
+                if( !result.getMessage().empty() )
+                    oss << result.getMessage() << "\n";
+                for( std::vector<MessageInfo>::const_iterator
+                        it = stats.infoMessages.begin(),
+                        itEnd = stats.infoMessages.end();
+                            it != itEnd;
+                            ++it )
+                    if( it->type == ResultWas::Info )
+                        oss << it->message << "\n";
+
+                oss << "at " << result.getSourceInfo();
+                xml.writeText( oss.str(), false );
+            }
+        }
+
+        XmlWriter xml;
+        Timer suiteTimer;
+        std::ostringstream stdOutForSuite;
+        std::ostringstream stdErrForSuite;
+        unsigned int unexpectedExceptions;
+    };
+
+    INTERNAL_CATCH_REGISTER_REPORTER( "junit", JunitReporter )
+
+} // end namespace Catch
+
+// #included from: ../reporters/catch_reporter_console.hpp
+#define TWOBLUECUBES_CATCH_REPORTER_CONSOLE_HPP_INCLUDED
+
+namespace Catch {
+
+    struct ConsoleReporter : StreamingReporterBase {
+        ConsoleReporter( ReporterConfig const& _config )
+        :   StreamingReporterBase( _config ),
+            m_headerPrinted( false )
+        {}
+
+        virtual ~ConsoleReporter() CATCH_OVERRIDE;
+        static std::string getDescription() {
+            return "Reports test results as plain lines of text";
+        }
+
+        virtual void noMatchingTestCases( std::string const& spec ) CATCH_OVERRIDE {
+            stream << "No test cases matched '" << spec << "'" << std::endl;
+        }
+
+        virtual void assertionStarting( AssertionInfo const& ) CATCH_OVERRIDE {
+        }
+
+        virtual bool assertionEnded( AssertionStats const& _assertionStats ) CATCH_OVERRIDE {
+            AssertionResult const& result = _assertionStats.assertionResult;
+
+            bool printInfoMessages = true;
+
+            // Drop out if result was successful and we're not printing those
+            if( !m_config->includeSuccessfulResults() && result.isOk() ) {
+                if( result.getResultType() != ResultWas::Warning )
+                    return false;
+                printInfoMessages = false;
+            }
+
+            lazyPrint();
+
+            AssertionPrinter printer( stream, _assertionStats, printInfoMessages );
+            printer.print();
+            stream << std::endl;
+            return true;
+        }
+
+        virtual void sectionStarting( SectionInfo const& _sectionInfo ) CATCH_OVERRIDE {
+            m_headerPrinted = false;
+            StreamingReporterBase::sectionStarting( _sectionInfo );
+        }
+        virtual void sectionEnded( SectionStats const& _sectionStats ) CATCH_OVERRIDE {
+            if( _sectionStats.missingAssertions ) {
+                lazyPrint();
+                Colour colour( Colour::ResultError );
+                if( m_sectionStack.size() > 1 )
+                    stream << "\nNo assertions in section";
+                else
+                    stream << "\nNo assertions in test case";
+                stream << " '" << _sectionStats.sectionInfo.name << "'\n" << std::endl;
+            }
+            if( m_headerPrinted ) {
+                if( m_config->showDurations() == ShowDurations::Always )
+                    stream << "Completed in " << _sectionStats.durationInSeconds << "s" << std::endl;
+                m_headerPrinted = false;
+            }
+            else {
+                if( m_config->showDurations() == ShowDurations::Always )
+                    stream << _sectionStats.sectionInfo.name << " completed in " << _sectionStats.durationInSeconds << "s" << std::endl;
+            }
+            StreamingReporterBase::sectionEnded( _sectionStats );
+        }
+
+        virtual void testCaseEnded( TestCaseStats const& _testCaseStats ) CATCH_OVERRIDE {
+            StreamingReporterBase::testCaseEnded( _testCaseStats );
+            m_headerPrinted = false;
+        }
+        virtual void testGroupEnded( TestGroupStats const& _testGroupStats ) CATCH_OVERRIDE {
+            if( currentGroupInfo.used ) {
+                printSummaryDivider();
+                stream << "Summary for group '" << _testGroupStats.groupInfo.name << "':\n";
+                printTotals( _testGroupStats.totals );
+                stream << "\n" << std::endl;
+            }
+            StreamingReporterBase::testGroupEnded( _testGroupStats );
+        }
+        virtual void testRunEnded( TestRunStats const& _testRunStats ) CATCH_OVERRIDE {
+            printTotalsDivider( _testRunStats.totals );
+            printTotals( _testRunStats.totals );
+            stream << std::endl;
+            StreamingReporterBase::testRunEnded( _testRunStats );
+        }
+
+    private:
+
+        class AssertionPrinter {
+            void operator= ( AssertionPrinter const& );
+        public:
+            AssertionPrinter( std::ostream& _stream, AssertionStats const& _stats, bool _printInfoMessages )
+            :   stream( _stream ),
+                stats( _stats ),
+                result( _stats.assertionResult ),
+                colour( Colour::None ),
+                message( result.getMessage() ),
+                messages( _stats.infoMessages ),
+                printInfoMessages( _printInfoMessages )
+            {
+                switch( result.getResultType() ) {
+                    case ResultWas::Ok:
+                        colour = Colour::Success;
+                        passOrFail = "PASSED";
+                        //if( result.hasMessage() )
+                        if( _stats.infoMessages.size() == 1 )
+                            messageLabel = "with message";
+                        if( _stats.infoMessages.size() > 1 )
+                            messageLabel = "with messages";
+                        break;
+                    case ResultWas::ExpressionFailed:
+                        if( result.isOk() ) {
+                            colour = Colour::Success;
+                            passOrFail = "FAILED - but was ok";
+                        }
+                        else {
+                            colour = Colour::Error;
+                            passOrFail = "FAILED";
+                        }
+                        if( _stats.infoMessages.size() == 1 )
+                            messageLabel = "with message";
+                        if( _stats.infoMessages.size() > 1 )
+                            messageLabel = "with messages";
+                        break;
+                    case ResultWas::ThrewException:
+                        colour = Colour::Error;
+                        passOrFail = "FAILED";
+                        messageLabel = "due to unexpected exception with message";
+                        break;
+                    case ResultWas::FatalErrorCondition:
+                        colour = Colour::Error;
+                        passOrFail = "FAILED";
+                        messageLabel = "due to a fatal error condition";
+                        break;
+                    case ResultWas::DidntThrowException:
+                        colour = Colour::Error;
+                        passOrFail = "FAILED";
+                        messageLabel = "because no exception was thrown where one was expected";
+                        break;
+                    case ResultWas::Info:
+                        messageLabel = "info";
+                        break;
+                    case ResultWas::Warning:
+                        messageLabel = "warning";
+                        break;
+                    case ResultWas::ExplicitFailure:
+                        passOrFail = "FAILED";
+                        colour = Colour::Error;
+                        if( _stats.infoMessages.size() == 1 )
+                            messageLabel = "explicitly with message";
+                        if( _stats.infoMessages.size() > 1 )
+                            messageLabel = "explicitly with messages";
+                        break;
+                    // These cases are here to prevent compiler warnings
+                    case ResultWas::Unknown:
+                    case ResultWas::FailureBit:
+                    case ResultWas::Exception:
+                        passOrFail = "** internal error **";
+                        colour = Colour::Error;
+                        break;
+                }
+            }
+
+            void print() const {
+                printSourceInfo();
+                if( stats.totals.assertions.total() > 0 ) {
+                    if( result.isOk() )
+                        stream << "\n";
+                    printResultType();
+                    printOriginalExpression();
+                    printReconstructedExpression();
+                }
+                else {
+                    stream << "\n";
+                }
+                printMessage();
+            }
+
+        private:
+            void printResultType() const {
+                if( !passOrFail.empty() ) {
+                    Colour colourGuard( colour );
+                    stream << passOrFail << ":\n";
+                }
+            }
+            void printOriginalExpression() const {
+                if( result.hasExpression() ) {
+                    Colour colourGuard( Colour::OriginalExpression );
+                    stream  << "  ";
+                    stream << result.getExpressionInMacro();
+                    stream << "\n";
+                }
+            }
+            void printReconstructedExpression() const {
+                if( result.hasExpandedExpression() ) {
+                    stream << "with expansion:\n";
+                    Colour colourGuard( Colour::ReconstructedExpression );
+                    stream << Text( result.getExpandedExpression(), TextAttributes().setIndent(2) ) << "\n";
+                }
+            }
+            void printMessage() const {
+                if( !messageLabel.empty() )
+                    stream << messageLabel << ":" << "\n";
+                for( std::vector<MessageInfo>::const_iterator it = messages.begin(), itEnd = messages.end();
+                        it != itEnd;
+                        ++it ) {
+                    // If this assertion is a warning ignore any INFO messages
+                    if( printInfoMessages || it->type != ResultWas::Info )
+                        stream << Text( it->message, TextAttributes().setIndent(2) ) << "\n";
+                }
+            }
+            void printSourceInfo() const {
+                Colour colourGuard( Colour::FileName );
+                stream << result.getSourceInfo() << ": ";
+            }
+
+            std::ostream& stream;
+            AssertionStats const& stats;
+            AssertionResult const& result;
+            Colour::Code colour;
+            std::string passOrFail;
+            std::string messageLabel;
+            std::string message;
+            std::vector<MessageInfo> messages;
+            bool printInfoMessages;
+        };
+
+        void lazyPrint() {
+
+            if( !currentTestRunInfo.used )
+                lazyPrintRunInfo();
+            if( !currentGroupInfo.used )
+                lazyPrintGroupInfo();
+
+            if( !m_headerPrinted ) {
+                printTestCaseAndSectionHeader();
+                m_headerPrinted = true;
+            }
+        }
+        void lazyPrintRunInfo() {
+            stream  << "\n" << getLineOfChars<'~'>() << "\n";
+            Colour colour( Colour::SecondaryText );
+            stream  << currentTestRunInfo->name
+                    << " is a Catch v"  << libraryVersion << " host application.\n"
+                    << "Run with -? for options\n\n";
+
+            if( m_config->rngSeed() != 0 )
+                stream << "Randomness seeded to: " << m_config->rngSeed() << "\n\n";
+
+            currentTestRunInfo.used = true;
+        }
+        void lazyPrintGroupInfo() {
+            if( !currentGroupInfo->name.empty() && currentGroupInfo->groupsCounts > 1 ) {
+                printClosedHeader( "Group: " + currentGroupInfo->name );
+                currentGroupInfo.used = true;
+            }
+        }
+        void printTestCaseAndSectionHeader() {
+            assert( !m_sectionStack.empty() );
+            printOpenHeader( currentTestCaseInfo->name );
+
+            if( m_sectionStack.size() > 1 ) {
+                Colour colourGuard( Colour::Headers );
+
+                std::vector<SectionInfo>::const_iterator
+                    it = m_sectionStack.begin()+1, // Skip first section (test case)
+                    itEnd = m_sectionStack.end();
+                for( ; it != itEnd; ++it )
+                    printHeaderString( it->name, 2 );
+            }
+
+            SourceLineInfo lineInfo = m_sectionStack.front().lineInfo;
+
+            if( !lineInfo.empty() ){
+                stream << getLineOfChars<'-'>() << "\n";
+                Colour colourGuard( Colour::FileName );
+                stream << lineInfo << "\n";
+            }
+            stream << getLineOfChars<'.'>() << "\n" << std::endl;
+        }
+
+        void printClosedHeader( std::string const& _name ) {
+            printOpenHeader( _name );
+            stream << getLineOfChars<'.'>() << "\n";
+        }
+        void printOpenHeader( std::string const& _name ) {
+            stream  << getLineOfChars<'-'>() << "\n";
+            {
+                Colour colourGuard( Colour::Headers );
+                printHeaderString( _name );
+            }
+        }
+
+        // if string has a : in first line will set indent to follow it on
+        // subsequent lines
+        void printHeaderString( std::string const& _string, std::size_t indent = 0 ) {
+            std::size_t i = _string.find( ": " );
+            if( i != std::string::npos )
+                i+=2;
+            else
+                i = 0;
+            stream << Text( _string, TextAttributes()
+                                        .setIndent( indent+i)
+                                        .setInitialIndent( indent ) ) << "\n";
+        }
+
+        struct SummaryColumn {
+
+            SummaryColumn( std::string const& _label, Colour::Code _colour )
+            :   label( _label ),
+                colour( _colour )
+            {}
+            SummaryColumn addRow( std::size_t count ) {
+                std::ostringstream oss;
+                oss << count;
+                std::string row = oss.str();
+                for( std::vector<std::string>::iterator it = rows.begin(); it != rows.end(); ++it ) {
+                    while( it->size() < row.size() )
+                        *it = " " + *it;
+                    while( it->size() > row.size() )
+                        row = " " + row;
+                }
+                rows.push_back( row );
+                return *this;
+            }
+
+            std::string label;
+            Colour::Code colour;
+            std::vector<std::string> rows;
+
+        };
+
+        void printTotals( Totals const& totals ) {
+            if( totals.testCases.total() == 0 ) {
+                stream << Colour( Colour::Warning ) << "No tests ran\n";
+            }
+            else if( totals.assertions.total() > 0 && totals.testCases.allPassed() ) {
+                stream << Colour( Colour::ResultSuccess ) << "All tests passed";
+                stream << " ("
+                        << pluralise( totals.assertions.passed, "assertion" ) << " in "
+                        << pluralise( totals.testCases.passed, "test case" ) << ")"
+                        << "\n";
+            }
+            else {
+
+                std::vector<SummaryColumn> columns;
+                columns.push_back( SummaryColumn( "", Colour::None )
+                                        .addRow( totals.testCases.total() )
+                                        .addRow( totals.assertions.total() ) );
+                columns.push_back( SummaryColumn( "passed", Colour::Success )
+                                        .addRow( totals.testCases.passed )
+                                        .addRow( totals.assertions.passed ) );
+                columns.push_back( SummaryColumn( "failed", Colour::ResultError )
+                                        .addRow( totals.testCases.failed )
+                                        .addRow( totals.assertions.failed ) );
+                columns.push_back( SummaryColumn( "failed as expected", Colour::ResultExpectedFailure )
+                                        .addRow( totals.testCases.failedButOk )
+                                        .addRow( totals.assertions.failedButOk ) );
+
+                printSummaryRow( "test cases", columns, 0 );
+                printSummaryRow( "assertions", columns, 1 );
+            }
+        }
+        void printSummaryRow( std::string const& label, std::vector<SummaryColumn> const& cols, std::size_t row ) {
+            for( std::vector<SummaryColumn>::const_iterator it = cols.begin(); it != cols.end(); ++it ) {
+                std::string value = it->rows[row];
+                if( it->label.empty() ) {
+                    stream << label << ": ";
+                    if( value != "0" )
+                        stream << value;
+                    else
+                        stream << Colour( Colour::Warning ) << "- none -";
+                }
+                else if( value != "0" ) {
+                    stream  << Colour( Colour::LightGrey ) << " | ";
+                    stream  << Colour( it->colour )
+                            << value << " " << it->label;
+                }
+            }
+            stream << "\n";
+        }
+
+        static std::size_t makeRatio( std::size_t number, std::size_t total ) {
+            std::size_t ratio = total > 0 ? CATCH_CONFIG_CONSOLE_WIDTH * number/ total : 0;
+            return ( ratio == 0 && number > 0 ) ? 1 : ratio;
+        }
+        static std::size_t& findMax( std::size_t& i, std::size_t& j, std::size_t& k ) {
+            if( i > j && i > k )
+                return i;
+            else if( j > k )
+                return j;
+            else
+                return k;
+        }
+
+        void printTotalsDivider( Totals const& totals ) {
+            if( totals.testCases.total() > 0 ) {
+                std::size_t failedRatio = makeRatio( totals.testCases.failed, totals.testCases.total() );
+                std::size_t failedButOkRatio = makeRatio( totals.testCases.failedButOk, totals.testCases.total() );
+                std::size_t passedRatio = makeRatio( totals.testCases.passed, totals.testCases.total() );
+                while( failedRatio + failedButOkRatio + passedRatio < CATCH_CONFIG_CONSOLE_WIDTH-1 )
+                    findMax( failedRatio, failedButOkRatio, passedRatio )++;
+                while( failedRatio + failedButOkRatio + passedRatio > CATCH_CONFIG_CONSOLE_WIDTH-1 )
+                    findMax( failedRatio, failedButOkRatio, passedRatio )--;
+
+                stream << Colour( Colour::Error ) << std::string( failedRatio, '=' );
+                stream << Colour( Colour::ResultExpectedFailure ) << std::string( failedButOkRatio, '=' );
+                if( totals.testCases.allPassed() )
+                    stream << Colour( Colour::ResultSuccess ) << std::string( passedRatio, '=' );
+                else
+                    stream << Colour( Colour::Success ) << std::string( passedRatio, '=' );
+            }
+            else {
+                stream << Colour( Colour::Warning ) << std::string( CATCH_CONFIG_CONSOLE_WIDTH-1, '=' );
+            }
+            stream << "\n";
+        }
+        void printSummaryDivider() {
+            stream << getLineOfChars<'-'>() << "\n";
+        }
+
+    private:
+        bool m_headerPrinted;
+    };
+
+    INTERNAL_CATCH_REGISTER_REPORTER( "console", ConsoleReporter )
+
+} // end namespace Catch
+
+// #included from: ../reporters/catch_reporter_compact.hpp
+#define TWOBLUECUBES_CATCH_REPORTER_COMPACT_HPP_INCLUDED
+
+namespace Catch {
+
+    struct CompactReporter : StreamingReporterBase {
+
+        CompactReporter( ReporterConfig const& _config )
+        : StreamingReporterBase( _config )
+        {}
+
+        virtual ~CompactReporter();
+
+        static std::string getDescription() {
+            return "Reports test results on a single line, suitable for IDEs";
+        }
+
+        virtual ReporterPreferences getPreferences() const {
+            ReporterPreferences prefs;
+            prefs.shouldRedirectStdOut = false;
+            return prefs;
+        }
+
+        virtual void noMatchingTestCases( std::string const& spec ) {
+            stream << "No test cases matched '" << spec << "'" << std::endl;
+        }
+
+        virtual void assertionStarting( AssertionInfo const& ) {
+        }
+
+        virtual bool assertionEnded( AssertionStats const& _assertionStats ) {
+            AssertionResult const& result = _assertionStats.assertionResult;
+
+            bool printInfoMessages = true;
+
+            // Drop out if result was successful and we're not printing those
+            if( !m_config->includeSuccessfulResults() && result.isOk() ) {
+                if( result.getResultType() != ResultWas::Warning )
+                    return false;
+                printInfoMessages = false;
+            }
+
+            AssertionPrinter printer( stream, _assertionStats, printInfoMessages );
+            printer.print();
+
+            stream << std::endl;
+            return true;
+        }
+
+        virtual void testRunEnded( TestRunStats const& _testRunStats ) {
+            printTotals( _testRunStats.totals );
+            stream << "\n" << std::endl;
+            StreamingReporterBase::testRunEnded( _testRunStats );
+        }
+
+    private:
+        class AssertionPrinter {
+            void operator= ( AssertionPrinter const& );
+        public:
+            AssertionPrinter( std::ostream& _stream, AssertionStats const& _stats, bool _printInfoMessages )
+            : stream( _stream )
+            , stats( _stats )
+            , result( _stats.assertionResult )
+            , messages( _stats.infoMessages )
+            , itMessage( _stats.infoMessages.begin() )
+            , printInfoMessages( _printInfoMessages )
+            {}
+
+            void print() {
+                printSourceInfo();
+
+                itMessage = messages.begin();
+
+                switch( result.getResultType() ) {
+                    case ResultWas::Ok:
+                        printResultType( Colour::ResultSuccess, passedString() );
+                        printOriginalExpression();
+                        printReconstructedExpression();
+                        if ( ! result.hasExpression() )
+                            printRemainingMessages( Colour::None );
+                        else
+                            printRemainingMessages();
+                        break;
+                    case ResultWas::ExpressionFailed:
+                        if( result.isOk() )
+                            printResultType( Colour::ResultSuccess, failedString() + std::string( " - but was ok" ) );
+                        else
+                            printResultType( Colour::Error, failedString() );
+                        printOriginalExpression();
+                        printReconstructedExpression();
+                        printRemainingMessages();
+                        break;
+                    case ResultWas::ThrewException:
+                        printResultType( Colour::Error, failedString() );
+                        printIssue( "unexpected exception with message:" );
+                        printMessage();
+                        printExpressionWas();
+                        printRemainingMessages();
+                        break;
+                    case ResultWas::FatalErrorCondition:
+                        printResultType( Colour::Error, failedString() );
+                        printIssue( "fatal error condition with message:" );
+                        printMessage();
+                        printExpressionWas();
+                        printRemainingMessages();
+                        break;
+                    case ResultWas::DidntThrowException:
+                        printResultType( Colour::Error, failedString() );
+                        printIssue( "expected exception, got none" );
+                        printExpressionWas();
+                        printRemainingMessages();
+                        break;
+                    case ResultWas::Info:
+                        printResultType( Colour::None, "info" );
+                        printMessage();
+                        printRemainingMessages();
+                        break;
+                    case ResultWas::Warning:
+                        printResultType( Colour::None, "warning" );
+                        printMessage();
+                        printRemainingMessages();
+                        break;
+                    case ResultWas::ExplicitFailure:
+                        printResultType( Colour::Error, failedString() );
+                        printIssue( "explicitly" );
+                        printRemainingMessages( Colour::None );
+                        break;
+                    // These cases are here to prevent compiler warnings
+                    case ResultWas::Unknown:
+                    case ResultWas::FailureBit:
+                    case ResultWas::Exception:
+                        printResultType( Colour::Error, "** internal error **" );
+                        break;
+                }
+            }
+
+        private:
+            // Colour::LightGrey
+
+            static Colour::Code dimColour() { return Colour::FileName; }
+
+#ifdef CATCH_PLATFORM_MAC
+            static const char* failedString() { return "FAILED"; }
+            static const char* passedString() { return "PASSED"; }
+#else
+            static const char* failedString() { return "failed"; }
+            static const char* passedString() { return "passed"; }
+#endif
+
+            void printSourceInfo() const {
+                Colour colourGuard( Colour::FileName );
+                stream << result.getSourceInfo() << ":";
+            }
+
+            void printResultType( Colour::Code colour, std::string passOrFail ) const {
+                if( !passOrFail.empty() ) {
+                    {
+                        Colour colourGuard( colour );
+                        stream << " " << passOrFail;
+                    }
+                    stream << ":";
+                }
+            }
+
+            void printIssue( std::string issue ) const {
+                stream << " " << issue;
+            }
+
+            void printExpressionWas() {
+                if( result.hasExpression() ) {
+                    stream << ";";
+                    {
+                        Colour colour( dimColour() );
+                        stream << " expression was:";
+                    }
+                    printOriginalExpression();
+                }
+            }
+
+            void printOriginalExpression() const {
+                if( result.hasExpression() ) {
+                    stream << " " << result.getExpression();
+                }
+            }
+
+            void printReconstructedExpression() const {
+                if( result.hasExpandedExpression() ) {
+                    {
+                        Colour colour( dimColour() );
+                        stream << " for: ";
+                    }
+                    stream << result.getExpandedExpression();
+                }
+            }
+
+            void printMessage() {
+                if ( itMessage != messages.end() ) {
+                    stream << " '" << itMessage->message << "'";
+                    ++itMessage;
+                }
+            }
+
+            void printRemainingMessages( Colour::Code colour = dimColour() ) {
+                if ( itMessage == messages.end() )
+                    return;
+
+                // using messages.end() directly yields compilation error:
+                std::vector<MessageInfo>::const_iterator itEnd = messages.end();
+                const std::size_t N = static_cast<std::size_t>( std::distance( itMessage, itEnd ) );
+
+                {
+                    Colour colourGuard( colour );
+                    stream << " with " << pluralise( N, "message" ) << ":";
+                }
+
+                for(; itMessage != itEnd; ) {
+                    // If this assertion is a warning ignore any INFO messages
+                    if( printInfoMessages || itMessage->type != ResultWas::Info ) {
+                        stream << " '" << itMessage->message << "'";
+                        if ( ++itMessage != itEnd ) {
+                            Colour colourGuard( dimColour() );
+                            stream << " and";
+                        }
+                    }
+                }
+            }
+
+        private:
+            std::ostream& stream;
+            AssertionStats const& stats;
+            AssertionResult const& result;
+            std::vector<MessageInfo> messages;
+            std::vector<MessageInfo>::const_iterator itMessage;
+            bool printInfoMessages;
+        };
+
+        // Colour, message variants:
+        // - white: No tests ran.
+        // -   red: Failed [both/all] N test cases, failed [both/all] M assertions.
+        // - white: Passed [both/all] N test cases (no assertions).
+        // -   red: Failed N tests cases, failed M assertions.
+        // - green: Passed [both/all] N tests cases with M assertions.
+
+        std::string bothOrAll( std::size_t count ) const {
+            return count == 1 ? "" : count == 2 ? "both " : "all " ;
+        }
+
+        void printTotals( const Totals& totals ) const {
+            if( totals.testCases.total() == 0 ) {
+                stream << "No tests ran.";
+            }
+            else if( totals.testCases.failed == totals.testCases.total() ) {
+                Colour colour( Colour::ResultError );
+                const std::string qualify_assertions_failed =
+                    totals.assertions.failed == totals.assertions.total() ?
+                        bothOrAll( totals.assertions.failed ) : "";
+                stream <<
+                    "Failed " << bothOrAll( totals.testCases.failed )
+                              << pluralise( totals.testCases.failed, "test case"  ) << ", "
+                    "failed " << qualify_assertions_failed <<
+                                 pluralise( totals.assertions.failed, "assertion" ) << ".";
+            }
+            else if( totals.assertions.total() == 0 ) {
+                stream <<
+                    "Passed " << bothOrAll( totals.testCases.total() )
+                              << pluralise( totals.testCases.total(), "test case" )
+                              << " (no assertions).";
+            }
+            else if( totals.assertions.failed ) {
+                Colour colour( Colour::ResultError );
+                stream <<
+                    "Failed " << pluralise( totals.testCases.failed, "test case"  ) << ", "
+                    "failed " << pluralise( totals.assertions.failed, "assertion" ) << ".";
+            }
+            else {
+                Colour colour( Colour::ResultSuccess );
+                stream <<
+                    "Passed " << bothOrAll( totals.testCases.passed )
+                              << pluralise( totals.testCases.passed, "test case"  ) <<
+                    " with "  << pluralise( totals.assertions.passed, "assertion" ) << ".";
+            }
+        }
+    };
+
+    INTERNAL_CATCH_REGISTER_REPORTER( "compact", CompactReporter )
+
+} // end namespace Catch
+
+namespace Catch {
+    // These are all here to avoid warnings about not having any out of line
+    // virtual methods
+    NonCopyable::~NonCopyable() {}
+    IShared::~IShared() {}
+    IStream::~IStream() CATCH_NOEXCEPT {}
+    FileStream::~FileStream() CATCH_NOEXCEPT {}
+    CoutStream::~CoutStream() CATCH_NOEXCEPT {}
+    DebugOutStream::~DebugOutStream() CATCH_NOEXCEPT {}
+    StreamBufBase::~StreamBufBase() CATCH_NOEXCEPT {}
+    IContext::~IContext() {}
+    IResultCapture::~IResultCapture() {}
+    ITestCase::~ITestCase() {}
+    ITestCaseRegistry::~ITestCaseRegistry() {}
+    IRegistryHub::~IRegistryHub() {}
+    IMutableRegistryHub::~IMutableRegistryHub() {}
+    IExceptionTranslator::~IExceptionTranslator() {}
+    IExceptionTranslatorRegistry::~IExceptionTranslatorRegistry() {}
+    IReporter::~IReporter() {}
+    IReporterFactory::~IReporterFactory() {}
+    IReporterRegistry::~IReporterRegistry() {}
+    IStreamingReporter::~IStreamingReporter() {}
+    AssertionStats::~AssertionStats() {}
+    SectionStats::~SectionStats() {}
+    TestCaseStats::~TestCaseStats() {}
+    TestGroupStats::~TestGroupStats() {}
+    TestRunStats::~TestRunStats() {}
+    CumulativeReporterBase::SectionNode::~SectionNode() {}
+    CumulativeReporterBase::~CumulativeReporterBase() {}
+
+    StreamingReporterBase::~StreamingReporterBase() {}
+    ConsoleReporter::~ConsoleReporter() {}
+    CompactReporter::~CompactReporter() {}
+    IRunner::~IRunner() {}
+    IMutableContext::~IMutableContext() {}
+    IConfig::~IConfig() {}
+    XmlReporter::~XmlReporter() {}
+    JunitReporter::~JunitReporter() {}
+    TestRegistry::~TestRegistry() {}
+    FreeFunctionTestCase::~FreeFunctionTestCase() {}
+    IGeneratorInfo::~IGeneratorInfo() {}
+    IGeneratorsForTest::~IGeneratorsForTest() {}
+    WildcardPattern::~WildcardPattern() {}
+    TestSpec::Pattern::~Pattern() {}
+    TestSpec::NamePattern::~NamePattern() {}
+    TestSpec::TagPattern::~TagPattern() {}
+    TestSpec::ExcludedPattern::~ExcludedPattern() {}
+
+    Matchers::Impl::StdString::Equals::~Equals() {}
+    Matchers::Impl::StdString::Contains::~Contains() {}
+    Matchers::Impl::StdString::StartsWith::~StartsWith() {}
+    Matchers::Impl::StdString::EndsWith::~EndsWith() {}
+
+    void Config::dummy() {}
+
+    namespace TestCaseTracking {
+        ITracker::~ITracker() {}
+        TrackerBase::~TrackerBase() {}
+        SectionTracker::~SectionTracker() {}
+        IndexTracker::~IndexTracker() {}
+    }
+}
+
+#ifdef __clang__
+#pragma clang diagnostic pop
+#endif
+
+#endif
+
+#ifdef CATCH_CONFIG_MAIN
+// #included from: internal/catch_default_main.hpp
+#define TWOBLUECUBES_CATCH_DEFAULT_MAIN_HPP_INCLUDED
+
+#ifndef __OBJC__
+
+// Standard C/C++ main entry point
+int main (int argc, char * argv[]) {
+    return Catch::Session().run( argc, argv );
+}
+
+#else // __OBJC__
+
+// Objective-C entry point
+int main (int argc, char * const argv[]) {
+#if !CATCH_ARC_ENABLED
+    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
+#endif
+
+    Catch::registerTestMethods();
+    int result = Catch::Session().run( argc, (char* const*)argv );
+
+#if !CATCH_ARC_ENABLED
+    [pool drain];
+#endif
+
+    return result;
+}
+
+#endif // __OBJC__
+
+#endif
+
+#ifdef CLARA_CONFIG_MAIN_NOT_DEFINED
+#  undef CLARA_CONFIG_MAIN
+#endif
+
+//////
+
+// If this config identifier is defined then all CATCH macros are prefixed with CATCH_
+#ifdef CATCH_CONFIG_PREFIX_ALL
+
+#define CATCH_REQUIRE( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::Normal, "CATCH_REQUIRE" )
+#define CATCH_REQUIRE_FALSE( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::Normal | Catch::ResultDisposition::FalseTest, "CATCH_REQUIRE_FALSE" )
+
+#define CATCH_REQUIRE_THROWS( expr ) INTERNAL_CATCH_THROWS( expr, Catch::ResultDisposition::Normal, "", "CATCH_REQUIRE_THROWS" )
+#define CATCH_REQUIRE_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( expr, exceptionType, Catch::ResultDisposition::Normal, "CATCH_REQUIRE_THROWS_AS" )
+#define CATCH_REQUIRE_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS( expr, Catch::ResultDisposition::Normal, matcher, "CATCH_REQUIRE_THROWS_WITH" )
+#define CATCH_REQUIRE_NOTHROW( expr ) INTERNAL_CATCH_NO_THROW( expr, Catch::ResultDisposition::Normal, "CATCH_REQUIRE_NOTHROW" )
+
+#define CATCH_CHECK( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECK" )
+#define CATCH_CHECK_FALSE( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::FalseTest, "CATCH_CHECK_FALSE" )
+#define CATCH_CHECKED_IF( expr ) INTERNAL_CATCH_IF( expr, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECKED_IF" )
+#define CATCH_CHECKED_ELSE( expr ) INTERNAL_CATCH_ELSE( expr, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECKED_ELSE" )
+#define CATCH_CHECK_NOFAIL( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, "CATCH_CHECK_NOFAIL" )
+
+#define CATCH_CHECK_THROWS( expr )  INTERNAL_CATCH_THROWS( expr, Catch::ResultDisposition::ContinueOnFailure, "", "CATCH_CHECK_THROWS" )
+#define CATCH_CHECK_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( expr, exceptionType, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECK_THROWS_AS" )
+#define CATCH_CHECK_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS( expr, Catch::ResultDisposition::ContinueOnFailure, matcher, "CATCH_CHECK_THROWS_WITH" )
+#define CATCH_CHECK_NOTHROW( expr ) INTERNAL_CATCH_NO_THROW( expr, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECK_NOTHROW" )
+
+#define CATCH_CHECK_THAT( arg, matcher ) INTERNAL_CHECK_THAT( arg, matcher, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECK_THAT" )
+#define CATCH_REQUIRE_THAT( arg, matcher ) INTERNAL_CHECK_THAT( arg, matcher, Catch::ResultDisposition::Normal, "CATCH_REQUIRE_THAT" )
+
+#define CATCH_INFO( msg ) INTERNAL_CATCH_INFO( msg, "CATCH_INFO" )
+#define CATCH_WARN( msg ) INTERNAL_CATCH_MSG( Catch::ResultWas::Warning, Catch::ResultDisposition::ContinueOnFailure, "CATCH_WARN", msg )
+#define CATCH_SCOPED_INFO( msg ) INTERNAL_CATCH_INFO( msg, "CATCH_INFO" )
+#define CATCH_CAPTURE( msg ) INTERNAL_CATCH_INFO( #msg " := " << msg, "CATCH_CAPTURE" )
+#define CATCH_SCOPED_CAPTURE( msg ) INTERNAL_CATCH_INFO( #msg " := " << msg, "CATCH_CAPTURE" )
+
+#ifdef CATCH_CONFIG_VARIADIC_MACROS
+    #define CATCH_TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE( __VA_ARGS__ )
+    #define CATCH_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, __VA_ARGS__ )
+    #define CATCH_METHOD_AS_TEST_CASE( method, ... ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, __VA_ARGS__ )
+    #define CATCH_REGISTER_TEST_CASE( Function, ... ) INTERNAL_CATCH_REGISTER_TESTCASE( Function, __VA_ARGS__ )
+    #define CATCH_SECTION( ... ) INTERNAL_CATCH_SECTION( __VA_ARGS__ )
+    #define CATCH_FAIL( ... ) INTERNAL_CATCH_MSG( Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, "CATCH_FAIL", __VA_ARGS__ )
+    #define CATCH_SUCCEED( ... ) INTERNAL_CATCH_MSG( Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, "CATCH_SUCCEED", __VA_ARGS__ )
+#else
+    #define CATCH_TEST_CASE( name, description ) INTERNAL_CATCH_TESTCASE( name, description )
+    #define CATCH_TEST_CASE_METHOD( className, name, description ) INTERNAL_CATCH_TEST_CASE_METHOD( className, name, description )
+    #define CATCH_METHOD_AS_TEST_CASE( method, name, description ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, name, description )
+    #define CATCH_REGISTER_TEST_CASE( function, name, description ) INTERNAL_CATCH_REGISTER_TESTCASE( function, name, description )
+    #define CATCH_SECTION( name, description ) INTERNAL_CATCH_SECTION( name, description )
+    #define CATCH_FAIL( msg ) INTERNAL_CATCH_MSG( Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, "CATCH_FAIL", msg )
+    #define CATCH_SUCCEED( msg ) INTERNAL_CATCH_MSG( Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, "CATCH_SUCCEED", msg )
+#endif
+#define CATCH_ANON_TEST_CASE() INTERNAL_CATCH_TESTCASE( "", "" )
+
+#define CATCH_REGISTER_REPORTER( name, reporterType ) INTERNAL_CATCH_REGISTER_REPORTER( name, reporterType )
+#define CATCH_REGISTER_LEGACY_REPORTER( name, reporterType ) INTERNAL_CATCH_REGISTER_LEGACY_REPORTER( name, reporterType )
+
+#define CATCH_GENERATE( expr) INTERNAL_CATCH_GENERATE( expr )
+
+// "BDD-style" convenience wrappers
+#ifdef CATCH_CONFIG_VARIADIC_MACROS
+#define CATCH_SCENARIO( ... ) CATCH_TEST_CASE( "Scenario: " __VA_ARGS__ )
+#define CATCH_SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, "Scenario: " __VA_ARGS__ )
+#else
+#define CATCH_SCENARIO( name, tags ) CATCH_TEST_CASE( "Scenario: " name, tags )
+#define CATCH_SCENARIO_METHOD( className, name, tags ) INTERNAL_CATCH_TEST_CASE_METHOD( className, "Scenario: " name, tags )
+#endif
+#define CATCH_GIVEN( desc )    CATCH_SECTION( std::string( "Given: ") + desc, "" )
+#define CATCH_WHEN( desc )     CATCH_SECTION( std::string( " When: ") + desc, "" )
+#define CATCH_AND_WHEN( desc ) CATCH_SECTION( std::string( "  And: ") + desc, "" )
+#define CATCH_THEN( desc )     CATCH_SECTION( std::string( " Then: ") + desc, "" )
+#define CATCH_AND_THEN( desc ) CATCH_SECTION( std::string( "  And: ") + desc, "" )
+
+// If CATCH_CONFIG_PREFIX_ALL is not defined then the CATCH_ prefix is not required
+#else
+
+#define REQUIRE( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::Normal, "REQUIRE" )
+#define REQUIRE_FALSE( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::Normal | Catch::ResultDisposition::FalseTest, "REQUIRE_FALSE" )
+
+#define REQUIRE_THROWS( expr ) INTERNAL_CATCH_THROWS( expr, Catch::ResultDisposition::Normal, "", "REQUIRE_THROWS" )
+#define REQUIRE_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( expr, exceptionType, Catch::ResultDisposition::Normal, "REQUIRE_THROWS_AS" )
+#define REQUIRE_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS( expr, Catch::ResultDisposition::Normal, matcher, "REQUIRE_THROWS_WITH" )
+#define REQUIRE_NOTHROW( expr ) INTERNAL_CATCH_NO_THROW( expr, Catch::ResultDisposition::Normal, "REQUIRE_NOTHROW" )
+
+#define CHECK( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::ContinueOnFailure, "CHECK" )
+#define CHECK_FALSE( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::FalseTest, "CHECK_FALSE" )
+#define CHECKED_IF( expr ) INTERNAL_CATCH_IF( expr, Catch::ResultDisposition::ContinueOnFailure, "CHECKED_IF" )
+#define CHECKED_ELSE( expr ) INTERNAL_CATCH_ELSE( expr, Catch::ResultDisposition::ContinueOnFailure, "CHECKED_ELSE" )
+#define CHECK_NOFAIL( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, "CHECK_NOFAIL" )
+
+#define CHECK_THROWS( expr )  INTERNAL_CATCH_THROWS( expr, Catch::ResultDisposition::ContinueOnFailure, "", "CHECK_THROWS" )
+#define CHECK_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( expr, exceptionType, Catch::ResultDisposition::ContinueOnFailure, "CHECK_THROWS_AS" )
+#define CHECK_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS( expr, Catch::ResultDisposition::ContinueOnFailure, matcher, "CHECK_THROWS_WITH" )
+#define CHECK_NOTHROW( expr ) INTERNAL_CATCH_NO_THROW( expr, Catch::ResultDisposition::ContinueOnFailure, "CHECK_NOTHROW" )
+
+#define CHECK_THAT( arg, matcher ) INTERNAL_CHECK_THAT( arg, matcher, Catch::ResultDisposition::ContinueOnFailure, "CHECK_THAT" )
+#define REQUIRE_THAT( arg, matcher ) INTERNAL_CHECK_THAT( arg, matcher, Catch::ResultDisposition::Normal, "REQUIRE_THAT" )
+
+#define INFO( msg ) INTERNAL_CATCH_INFO( msg, "INFO" )
+#define WARN( msg ) INTERNAL_CATCH_MSG( Catch::ResultWas::Warning, Catch::ResultDisposition::ContinueOnFailure, "WARN", msg )
+#define SCOPED_INFO( msg ) INTERNAL_CATCH_INFO( msg, "INFO" )
+#define CAPTURE( msg ) INTERNAL_CATCH_INFO( #msg " := " << msg, "CAPTURE" )
+#define SCOPED_CAPTURE( msg ) INTERNAL_CATCH_INFO( #msg " := " << msg, "CAPTURE" )
+
+#ifdef CATCH_CONFIG_VARIADIC_MACROS
+    #define TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE( __VA_ARGS__ )
+    #define TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, __VA_ARGS__ )
+    #define METHOD_AS_TEST_CASE( method, ... ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, __VA_ARGS__ )
+    #define REGISTER_TEST_CASE( Function, ... ) INTERNAL_CATCH_REGISTER_TESTCASE( Function, __VA_ARGS__ )
+    #define SECTION( ... ) INTERNAL_CATCH_SECTION( __VA_ARGS__ )
+    #define FAIL( ... ) INTERNAL_CATCH_MSG( Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, "FAIL", __VA_ARGS__ )
+    #define SUCCEED( ... ) INTERNAL_CATCH_MSG( Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, "SUCCEED", __VA_ARGS__ )
+#else
+    #define TEST_CASE( name, description ) INTERNAL_CATCH_TESTCASE( name, description )
+    #define TEST_CASE_METHOD( className, name, description ) INTERNAL_CATCH_TEST_CASE_METHOD( className, name, description )
+    #define METHOD_AS_TEST_CASE( method, name, description ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, name, description )
+    #define REGISTER_TEST_CASE( method, name, description ) INTERNAL_CATCH_REGISTER_TESTCASE( method, name, description )
+    #define SECTION( name, description ) INTERNAL_CATCH_SECTION( name, description )
+    #define FAIL( msg ) INTERNAL_CATCH_MSG( Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, "FAIL", msg )
+    #define SUCCEED( msg ) INTERNAL_CATCH_MSG( Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, "SUCCEED", msg )
+#endif
+#define ANON_TEST_CASE() INTERNAL_CATCH_TESTCASE( "", "" )
+
+#define REGISTER_REPORTER( name, reporterType ) INTERNAL_CATCH_REGISTER_REPORTER( name, reporterType )
+#define REGISTER_LEGACY_REPORTER( name, reporterType ) INTERNAL_CATCH_REGISTER_LEGACY_REPORTER( name, reporterType )
+
+#define GENERATE( expr) INTERNAL_CATCH_GENERATE( expr )
+
+#endif
+
+#define CATCH_TRANSLATE_EXCEPTION( signature ) INTERNAL_CATCH_TRANSLATE_EXCEPTION( signature )
+
+// "BDD-style" convenience wrappers
+#ifdef CATCH_CONFIG_VARIADIC_MACROS
+#define SCENARIO( ... ) TEST_CASE( "Scenario: " __VA_ARGS__ )
+#define SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, "Scenario: " __VA_ARGS__ )
+#else
+#define SCENARIO( name, tags ) TEST_CASE( "Scenario: " name, tags )
+#define SCENARIO_METHOD( className, name, tags ) INTERNAL_CATCH_TEST_CASE_METHOD( className, "Scenario: " name, tags )
+#endif
+#define GIVEN( desc )    SECTION( std::string("   Given: ") + desc, "" )
+#define WHEN( desc )     SECTION( std::string("    When: ") + desc, "" )
+#define AND_WHEN( desc ) SECTION( std::string("And when: ") + desc, "" )
+#define THEN( desc )     SECTION( std::string("    Then: ") + desc, "" )
+#define AND_THEN( desc ) SECTION( std::string("     And: ") + desc, "" )
+
+using Catch::Detail::Approx;
+
+#endif // TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED
+
diff --git a/src/osgEarth/tinyxml.cpp b/src/osgEarth/tinyxml.cpp
index f981d7a..130182d 100644
--- a/src/osgEarth/tinyxml.cpp
+++ b/src/osgEarth/tinyxml.cpp
@@ -34,6 +34,8 @@ distribution.
 
 FILE* TiXmlFOpen( const char* filename, const char* mode );
 
+using namespace osgEarth;
+
 bool TiXmlBase::condenseWhiteSpace = true;
 
 // Microsoft compiler security
diff --git a/src/osgEarth/tinyxml.h b/src/osgEarth/tinyxml.h
index 6fb396d..6543877 100644
--- a/src/osgEarth/tinyxml.h
+++ b/src/osgEarth/tinyxml.h
@@ -38,6 +38,8 @@ distribution.
 #include <string.h>
 #include <assert.h>
 
+#include <osgEarth/Export>
+
 // Help out windows:
 #if defined( _DEBUG ) && !defined( DEBUG )
 #define DEBUG
@@ -80,13 +82,17 @@ distribution.
 	#endif
 #endif	
 
-class TiXmlDocument;
-class TiXmlElement;
-class TiXmlComment;
-class TiXmlUnknown;
-class TiXmlAttribute;
-class TiXmlText;
-class TiXmlDeclaration;
+namespace osgEarth
+{
+    class TiXmlDocument;
+    class TiXmlElement;
+    class TiXmlComment;
+    class TiXmlUnknown;
+    class TiXmlAttribute;
+    class TiXmlText;
+    class TiXmlDeclaration;
+}
+
 class TiXmlParsingData;
 
 const int TIXML_MAJOR_VERSION = 2;
@@ -96,6 +102,8 @@ const int TIXML_PATCH_VERSION = 1;
 /*	Internal structure for tracking location of items 
 	in the XML file.
 */
+namespace osgEarth
+{
 struct TiXmlCursor
 {
 	TiXmlCursor()		{ Clear(); }
@@ -104,7 +112,7 @@ struct TiXmlCursor
 	int row;	// 0 based.
 	int col;	// 0 based.
 };
-
+} // namespace osgEarth
 
 /**
 	Implements the interface to the "Visitor pattern" (see the Accept() method.)
@@ -125,7 +133,9 @@ struct TiXmlCursor
 
 	@sa TiXmlNode::Accept()
 */
-class TiXmlVisitor
+namespace osgEarth
+{
+class OSGEARTH_EXPORT TiXmlVisitor
 {
 public:
 	virtual ~TiXmlVisitor() {}
@@ -149,6 +159,7 @@ public:
 	/// Visit an unknow node
 	virtual bool Visit( const TiXmlUnknown& /*unknown*/ )			{ return true; }
 };
+} // namespace osgEarth
 
 // Only used by Attribute::Query functions
 enum 
@@ -191,7 +202,9 @@ const TiXmlEncoding TIXML_DEFAULT_ENCODING = TIXML_ENCODING_UNKNOWN;
 	A Decleration contains: Attributes (not on tree)
 	@endverbatim
 */
-class TiXmlBase
+namespace osgEarth
+{
+class OSGEARTH_EXPORT TiXmlBase
 {
 	friend class TiXmlNode;
 	friend class TiXmlElement;
@@ -420,7 +433,7 @@ private:
 	in a document, or stand on its own. The type of a TiXmlNode
 	can be queried, and it can be cast to its more defined type.
 */
-class TiXmlNode : public TiXmlBase
+class OSGEARTH_EXPORT TiXmlNode : public TiXmlBase
 {
 	friend class TiXmlDocument;
 	friend class TiXmlElement;
@@ -737,6 +750,11 @@ public:
 	*/
 	virtual bool Accept( TiXmlVisitor* visitor ) const = 0;
 
+#ifdef TIXML_USE_STL
+    // The real work of the input operator.
+    virtual void StreamIn( std::istream* in, TIXML_STRING* tag ) = 0;
+#endif
+
 protected:
 	TiXmlNode( NodeType _type );
 
@@ -744,11 +762,6 @@ protected:
 	// and the assignment operator.
 	void CopyTo( TiXmlNode* target ) const;
 
-	#ifdef TIXML_USE_STL
-	    // The real work of the input operator.
-	virtual void StreamIn( std::istream* in, TIXML_STRING* tag ) = 0;
-	#endif
-
 	// Figure out what is at *p, and parse it. Returns null if it is not an xml node.
 	TiXmlNode* Identify( const char* start, TiXmlEncoding encoding );
 
@@ -776,7 +789,7 @@ private:
 		  part of the tinyXML document object model. There are other
 		  suggested ways to look at this problem.
 */
-class TiXmlAttribute : public TiXmlBase
+class OSGEARTH_EXPORT TiXmlAttribute : public TiXmlBase
 {
 	friend class TiXmlAttributeSet;
 
@@ -900,7 +913,7 @@ private:
 		- I like circular lists
 		- it demonstrates some independence from the (typical) doubly linked list.
 */
-class TiXmlAttributeSet
+class OSGEARTH_EXPORT TiXmlAttributeSet
 {
 public:
 	TiXmlAttributeSet();
@@ -937,7 +950,7 @@ private:
 	and can contain other elements, text, comments, and unknowns.
 	Elements also contain an arbitrary number of attributes.
 */
-class TiXmlElement : public TiXmlNode
+class OSGEARTH_EXPORT TiXmlElement : public TiXmlNode
 {
 public:
 	/// Construct an element.
@@ -1152,7 +1165,7 @@ private:
 
 /**	An XML comment.
 */
-class TiXmlComment : public TiXmlNode
+class OSGEARTH_EXPORT TiXmlComment : public TiXmlNode
 {
 public:
 	/// Constructs an empty comment.
@@ -1202,7 +1215,7 @@ private:
 	you generally want to leave it alone, but you can change the output mode with 
 	SetCDATA() and query it with CDATA().
 */
-class TiXmlText : public TiXmlNode
+class OSGEARTH_EXPORT TiXmlText : public TiXmlNode
 {
 	friend class TiXmlElement;
 public:
@@ -1275,7 +1288,7 @@ private:
 	handled as special cases, not generic attributes, simply
 	because there can only be at most 3 and they are always the same.
 */
-class TiXmlDeclaration : public TiXmlNode
+class OSGEARTH_EXPORT TiXmlDeclaration : public TiXmlNode
 {
 public:
 	/// Construct an empty declaration.
@@ -1344,7 +1357,7 @@ private:
 
 	DTD tags get thrown into TiXmlUnknowns.
 */
-class TiXmlUnknown : public TiXmlNode
+class OSGEARTH_EXPORT TiXmlUnknown : public TiXmlNode
 {
 public:
 	TiXmlUnknown() : TiXmlNode( TiXmlNode::TINYXML_UNKNOWN )	{}
@@ -1383,7 +1396,7 @@ private:
 	XML pieces. It can be saved, loaded, and printed to the screen.
 	The 'value' of a document node is the xml file name.
 */
-class TiXmlDocument : public TiXmlNode
+class OSGEARTH_EXPORT TiXmlDocument : public TiXmlNode
 {
 public:
 	/// Create an empty document, that has no name.
@@ -1546,7 +1559,7 @@ private:
 	TiXmlCursor errorLocation;
 	bool useMicrosoftBOM;		// the UTF-8 BOM were found when read. Note this, and try to write.
 };
-
+} // namespace osgEarth
 
 /**
 	A TiXmlHandle is a class that wraps a node pointer with null checks; this is
@@ -1628,11 +1641,12 @@ private:
 	}
 	@endverbatim
 */
-class TiXmlHandle
+
+class OSGEARTH_EXPORT TiXmlHandle
 {
 public:
 	/// Create a handle from any node (at any depth of the tree.) This can be a null pointer.
-	TiXmlHandle( TiXmlNode* _node )					{ this->node = _node; }
+    TiXmlHandle( osgEarth::TiXmlNode* _node )					{ this->node = _node; }
 	/// Copy constructor
 	TiXmlHandle( const TiXmlHandle& ref )			{ this->node = ref.node; }
 	TiXmlHandle operator=( const TiXmlHandle& ref ) { this->node = ref.node; return *this; }
@@ -1675,36 +1689,36 @@ public:
 
 	/** Return the handle as a TiXmlNode. This may return null.
 	*/
-	TiXmlNode* ToNode() const			{ return node; } 
+    osgEarth::TiXmlNode* ToNode() const			{ return node; }
 	/** Return the handle as a TiXmlElement. This may return null.
 	*/
-	TiXmlElement* ToElement() const		{ return ( ( node && node->ToElement() ) ? node->ToElement() : 0 ); }
+    osgEarth::TiXmlElement* ToElement() const		{ return ( ( node && node->ToElement() ) ? node->ToElement() : 0 ); }
 	/**	Return the handle as a TiXmlText. This may return null.
 	*/
-	TiXmlText* ToText() const			{ return ( ( node && node->ToText() ) ? node->ToText() : 0 ); }
+    osgEarth::TiXmlText* ToText() const			{ return ( ( node && node->ToText() ) ? node->ToText() : 0 ); }
 	/** Return the handle as a TiXmlUnknown. This may return null.
 	*/
-	TiXmlUnknown* ToUnknown() const		{ return ( ( node && node->ToUnknown() ) ? node->ToUnknown() : 0 ); }
+    osgEarth::TiXmlUnknown* ToUnknown() const		{ return ( ( node && node->ToUnknown() ) ? node->ToUnknown() : 0 ); }
 
 	/** @deprecated use ToNode. 
 		Return the handle as a TiXmlNode. This may return null.
 	*/
-	TiXmlNode* Node() const			{ return ToNode(); } 
+    osgEarth::TiXmlNode* Node() const			{ return ToNode(); }
 	/** @deprecated use ToElement. 
 		Return the handle as a TiXmlElement. This may return null.
 	*/
-	TiXmlElement* Element() const	{ return ToElement(); }
+    osgEarth::TiXmlElement* Element() const	{ return ToElement(); }
 	/**	@deprecated use ToText()
 		Return the handle as a TiXmlText. This may return null.
 	*/
-	TiXmlText* Text() const			{ return ToText(); }
+    osgEarth::TiXmlText* Text() const			{ return ToText(); }
 	/** @deprecated use ToUnknown()
 		Return the handle as a TiXmlUnknown. This may return null.
 	*/
-	TiXmlUnknown* Unknown() const	{ return ToUnknown(); }
+    osgEarth::TiXmlUnknown* Unknown() const	{ return ToUnknown(); }
 
 private:
-	TiXmlNode* node;
+    osgEarth::TiXmlNode* node;
 };
 
 
@@ -1727,22 +1741,22 @@ private:
 	fprintf( stdout, "%s", printer.CStr() );
 	@endverbatim
 */
-class TiXmlPrinter : public TiXmlVisitor
+class OSGEARTH_EXPORT TiXmlPrinter : public osgEarth::TiXmlVisitor
 {
 public:
 	TiXmlPrinter() : depth( 0 ), simpleTextPrint( false ),
 					 buffer(), indent( "    " ), lineBreak( "\n" ) {}
 
-	virtual bool VisitEnter( const TiXmlDocument& doc );
-	virtual bool VisitExit( const TiXmlDocument& doc );
+    virtual bool VisitEnter( const osgEarth::TiXmlDocument& doc );
+    virtual bool VisitExit( const osgEarth::TiXmlDocument& doc );
 
-	virtual bool VisitEnter( const TiXmlElement& element, const TiXmlAttribute* firstAttribute );
-	virtual bool VisitExit( const TiXmlElement& element );
+    virtual bool VisitEnter( const osgEarth::TiXmlElement& element, const osgEarth::TiXmlAttribute* firstAttribute );
+    virtual bool VisitExit( const osgEarth::TiXmlElement& element );
 
-	virtual bool Visit( const TiXmlDeclaration& declaration );
-	virtual bool Visit( const TiXmlText& text );
-	virtual bool Visit( const TiXmlComment& comment );
-	virtual bool Visit( const TiXmlUnknown& unknown );
+    virtual bool Visit( const osgEarth::TiXmlDeclaration& declaration );
+    virtual bool Visit( const osgEarth::TiXmlText& text );
+    virtual bool Visit( const osgEarth::TiXmlComment& comment );
+    virtual bool Visit( const osgEarth::TiXmlUnknown& unknown );
 
 	/** Set the indent characters for printing. By default 4 spaces
 		but tab (\t) is also useful, or null/empty string for no indentation.
diff --git a/src/osgEarth/tinyxmlerror.cpp b/src/osgEarth/tinyxmlerror.cpp
index d66f6ff..af6b3d6 100644
--- a/src/osgEarth/tinyxmlerror.cpp
+++ b/src/osgEarth/tinyxmlerror.cpp
@@ -30,6 +30,7 @@ distribution.
 //
 // It also cleans up the code a bit.
 //
+using namespace osgEarth;
 
 const char* TiXmlBase::errorString[ TIXML_ERROR_STRING_COUNT ] =
 {
diff --git a/src/osgEarth/tinyxmlparser.cpp b/src/osgEarth/tinyxmlparser.cpp
index 666a4f7..0164df8 100644
--- a/src/osgEarth/tinyxmlparser.cpp
+++ b/src/osgEarth/tinyxmlparser.cpp
@@ -37,6 +37,8 @@ distribution.
 #	endif
 #endif
 
+using namespace osgEarth;
+
 // Note tha "PutString" hardcodes the same list. This
 // is less flexible than it appears. Changing the entries
 // or order will break putstring.	
@@ -170,13 +172,13 @@ void TiXmlBase::ConvertUTF32ToUTF8( unsigned long input, char* output, int* leng
 
 class TiXmlParsingData
 {
+public:
 	friend class TiXmlDocument;
-  public:
-	void Stamp( const char* now, TiXmlEncoding encoding );
+
+    void Stamp( const char* now, TiXmlEncoding encoding );
 
 	const TiXmlCursor& Cursor()	{ return cursor; }
 
-  private:
 	// Only used by the document!
 	TiXmlParsingData( const char* start, int _tabsize, int row, int col )
 	{
@@ -187,8 +189,8 @@ class TiXmlParsingData
 		cursor.col = col;
 	}
 
-	TiXmlCursor		cursor;
-	const char*		stamp;
+    TiXmlCursor		cursor;
+    const char*		stamp;
 	int				tabsize;
 };
 
diff --git a/src/osgEarthAnnotation/AnnotationEditing.cpp b/src/osgEarthAnnotation/AnnotationEditing.cpp
index 476379a..10f1b8f 100644
--- a/src/osgEarthAnnotation/AnnotationEditing.cpp
+++ b/src/osgEarthAnnotation/AnnotationEditing.cpp
@@ -62,7 +62,7 @@ GeoPositionNodeEditor::GeoPositionNodeEditor(GeoPositionNode* node):
 _node( node )
 {
     _dragger  = new SphereDragger( _node->getMapNode());  
-    _dragger->addPositionChangedCallback(new DraggerCallback(_node, this) );        
+    _dragger->addPositionChangedCallback(new DraggerCallback(_node.get(), this) );        
     addChild(_dragger);
     updateDraggers();
 }
diff --git a/src/osgEarthAnnotation/AnnotationExtension b/src/osgEarthAnnotation/AnnotationExtension
deleted file mode 100644
index 6ecc7ca..0000000
--- a/src/osgEarthAnnotation/AnnotationExtension
+++ /dev/null
@@ -1,61 +0,0 @@
-/* -*-c++-*- */
-/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2016 Pelican Mapping
- * http://osgearth.org
- *
- * osgEarth is free software; you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>
- */
-#ifndef OSGEARTH_ANNOTATION_EXTENSION
-#define OSGEARTH_ANNOTATION_EXTENSION 1
-
-#include "Common"
-#include <osgEarth/Extension>
-#include <osgEarth/MapNode>
-
-namespace osgEarth { namespace Annotation
-{
-    using namespace osgEarth;    
-
-    /**
-     * Extension for loading the splatting effect on demand.
-     */
-    class OSGEARTHANNO_EXPORT AnnotationExtension : public Extension,
-                                                    public ExtensionInterface<MapNode>,
-                                                    public ConfigOptions
-    {
-    public:
-        META_osgEarth_Extension(AnnotationExtension);
-
-        AnnotationExtension(const ConfigOptions& options);
-
-    public: // Extension
-
-        void setDBOptions(const osgDB::Options* readOptions);
-
-        const ConfigOptions& getConfigOptions() const { return *this; }
-
-
-    public: // ExtensionInterface<MapNode>
-
-        bool connect(MapNode* mapNode);
-
-        bool disconnect(MapNode* mapNode);
-
-    private:
-        osg::ref_ptr<const osgDB::Options> _dbo;
-    };
-
-} } // namespace osgEarth::Annotations
-
-#endif // OSGEARTH_ANNOTATION_EXTENSION
diff --git a/src/osgEarthAnnotation/AnnotationExtension.cpp b/src/osgEarthAnnotation/AnnotationExtension.cpp
deleted file mode 100644
index 5b7be4e..0000000
--- a/src/osgEarthAnnotation/AnnotationExtension.cpp
+++ /dev/null
@@ -1,91 +0,0 @@
-/* -*-c++-*- */
-/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2016 Pelican Mapping
- * http://osgearth.org
- *
- * osgEarth is free software; you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>
- */
-#include "AnnotationExtension"
-#include <osgEarthAnnotation/AnnotationRegistry>
-#include <osgEarth/Extension>
-
-#include <osgEarth/MapNode>
-#include <osgDB/FileNameUtils>
-#include <osgDB/ReaderWriter>
-
-using namespace osgEarth;
-using namespace osgEarth::Annotation;
-
-#define LC "[AnnotationExtension] "
-
-//.........................................................................
-
-
-AnnotationExtension::AnnotationExtension(const ConfigOptions& co) :
-ConfigOptions( co )
-{
-    //nop
-}
-
-void
-AnnotationExtension::setDBOptions(const osgDB::Options* dbOptions)
-{
-    _dbo = dbOptions;
-}
-
-bool
-AnnotationExtension::connect(MapNode* mapNode)
-{
-    if ( !mapNode )
-    {
-        OE_WARN << LC << "Illegal: MapNode cannot be null." << std::endl;
-        return false;
-    }
-
-    // decode the Config.
-    osg::Group* annotations = 0L;
-
-    AnnotationRegistry::instance()->create( mapNode, getConfig(), _dbo.get(), annotations );
-
-    if ( annotations )
-    {
-        if ( annotations )
-            annotations->setName("osgEarth::AnnotationExtension");
-
-        mapNode->addChild( annotations );
-    }
-
-    return true;
-}
-
-bool
-AnnotationExtension::disconnect(MapNode* mapNode)
-{
-    if ( mapNode )
-    {
-        for(unsigned i=0; i<mapNode->getNumChildren(); ++i)
-        {
-            if ( mapNode->getChild(i)->getName() == "osgEarth::AnnotationExtension")
-            {
-                mapNode->removeChild(i);
-                break;
-            }
-        }
-    }
-
-    return true;
-}
-
-REGISTER_OSGEARTH_EXTENSION(osgearth_annotations, AnnotationExtension);
-//REGISTER_OSGEARTH_EXTENSION(osgearth_annotation,  AnnotationExtension);
diff --git a/src/osgEarthAnnotation/AnnotationLayer b/src/osgEarthAnnotation/AnnotationLayer
new file mode 100644
index 0000000..18ff036
--- /dev/null
+++ b/src/osgEarthAnnotation/AnnotationLayer
@@ -0,0 +1,98 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+ * Copyright 2016 Pelican Mapping
+ * http://osgearth.org
+ *
+ * osgEarth is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+#ifndef OSGEARTH_ANNOTATION_ANNOTATION_LAYER
+#define OSGEARTH_ANNOTATION_ANNOTATION_LAYER
+
+#include <osgEarth/VisibleLayer>
+#include <osgEarthAnnotation/Common>
+
+namespace osgEarth { namespace Annotation
+{
+    using namespace osgEarth;
+
+    /**
+     * Configuration options for the geodetic graticule.
+     */
+    class AnnotationLayerOptions : public VisibleLayerOptions
+    {
+    public:
+        AnnotationLayerOptions(const ConfigOptions& conf =ConfigOptions()) : VisibleLayerOptions(conf) {
+            fromConfig(_conf);
+        }
+
+    public:
+        virtual Config getConfig() const {
+            Config conf = VisibleLayerOptions::getConfig();
+            conf.key() = "annotations";
+            return conf;
+        }
+
+        virtual void fromConfig(const Config& conf) {
+            //nop
+        }
+
+    protected:
+
+        void mergeConfig(const Config& conf) {
+            VisibleLayerOptions::mergeConfig(conf);
+            fromConfig(conf);
+        }
+    };
+
+    /**
+     * Layer that holds annotation nodes.
+     *
+     * Use with the options structure is intended for loading from an earth file.
+     * To use from the API, you can just call getOrCreateNode(), cast to a Group,
+     * and add your annotations there.
+     */
+    class OSGEARTHANNO_EXPORT AnnotationLayer : public VisibleLayer
+    {
+    public:
+        META_Layer(osgEarthAnnotation, AnnotationLayer, AnnotationLayerOptions);
+
+        //! Construct a default layer
+        AnnotationLayer();
+
+        //! Construct a layer with custom options
+        AnnotationLayer(const AnnotationLayerOptions& options);
+
+        //! Call to refresh after setting an option
+        void dirty();
+
+    public: // Layer
+        
+        virtual osg::Node* getOrCreateNode();
+
+        virtual void init();
+
+    protected:
+
+        /** dtor */
+        virtual ~AnnotationLayer() { }        
+
+    private:
+
+        osg::ref_ptr<osg::Group> _root;
+
+        void deserialize();
+    };  
+} } // namespace osgEarth::Annotation
+
+#endif // OSGEARTH_ANNOTATION_ANNOTATION_LAYER
diff --git a/src/osgEarthAnnotation/AnnotationLayer.cpp b/src/osgEarthAnnotation/AnnotationLayer.cpp
new file mode 100644
index 0000000..b3afdba
--- /dev/null
+++ b/src/osgEarthAnnotation/AnnotationLayer.cpp
@@ -0,0 +1,74 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+ * Copyright 2016 Pelican Mapping
+ * http://osgearth.org
+ *
+ * osgEarth is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+#include <osgEarthAnnotation/AnnotationLayer>
+#include <osgEarthAnnotation/AnnotationRegistry>
+
+using namespace osgEarth;
+using namespace osgEarth::Annotation;
+
+REGISTER_OSGEARTH_LAYER(annotations, AnnotationLayer);
+
+
+AnnotationLayer::AnnotationLayer() :
+VisibleLayer(&_optionsConcrete),
+_options(&_optionsConcrete)
+{
+    init();
+}
+
+AnnotationLayer::AnnotationLayer(const AnnotationLayerOptions& options) :
+VisibleLayer(&_optionsConcrete),
+_options(&_optionsConcrete),
+_optionsConcrete(options)
+{
+    init();
+}
+
+void
+AnnotationLayer::init()
+{
+    VisibleLayer::init();
+}
+
+osg::Node*
+AnnotationLayer::getOrCreateNode()
+{
+    if (_root.valid() == false)
+    {
+        _root = new osg::Group();
+        deserialize();
+    }
+
+    return _root.get();
+}
+
+void
+AnnotationLayer::deserialize()
+{
+    // reset:
+    _root->removeChildren(0, _root->getNumChildren());
+
+    // deserialize from the options:
+    osg::Group* group = 0L;
+    AnnotationRegistry::instance()->create(0L, options().getConfig(), getReadOptions(), group);
+    if (group)
+    {
+        _root->addChild(group);
+    }
+}
diff --git a/src/osgEarthAnnotation/AnnotationNode b/src/osgEarthAnnotation/AnnotationNode
index 800f123..205aab0 100644
--- a/src/osgEarthAnnotation/AnnotationNode
+++ b/src/osgEarthAnnotation/AnnotationNode
@@ -72,6 +72,8 @@ namespace osgEarth { namespace Annotation
     public:
 
         const std::string& getName() const { return osg::Group::getName(); }
+
+        void traverse(osg::NodeVisitor& nv);
         
         /**
          * Priority of the item. A negative Priority means "never occlude me".
@@ -140,6 +142,9 @@ namespace osgEarth { namespace Annotation
         // hidden copy ctor
         AnnotationNode(const AnnotationNode& rhs, const osg::CopyOp& op=osg::CopyOp::DEEP_COPY_ALL) : osg::Group(rhs, op) { }
 
+        // override to prevent render-order symbology from taking effect.
+        virtual bool supportsRenderBinDetails() const { return true; }
+
     protected:
         
         osg::ref_ptr<HorizonCullCallback> _horizonCuller;
@@ -148,6 +153,7 @@ namespace osgEarth { namespace Annotation
             
         osg::observer_ptr<MapNode> _mapNode;
         static Style s_emptyStyle;
+        bool _mapNodeRequired;
 
     public:
 
diff --git a/src/osgEarthAnnotation/AnnotationNode.cpp b/src/osgEarthAnnotation/AnnotationNode.cpp
index 096f338..7473742 100644
--- a/src/osgEarthAnnotation/AnnotationNode.cpp
+++ b/src/osgEarthAnnotation/AnnotationNode.cpp
@@ -26,6 +26,12 @@
 
 #include <osgEarth/DepthOffset>
 #include <osgEarth/MapNode>
+#include <osgEarth/NodeUtils>
+#include <osgEarth/ShaderUtils>
+#include <osgEarth/Lighting>
+
+#include <osg/PolygonOffset>
+#include <osg/Depth>
 
 using namespace osgEarth;
 using namespace osgEarth::Annotation;
@@ -48,6 +54,9 @@ _priority   ( 0.0f )
     
     _horizonCuller = new HorizonCullCallback();
     this->addCullCallback( _horizonCuller.get() );
+
+    _mapNodeRequired = true;
+    ADJUST_UPDATE_TRAV_COUNT(this, +1);
 }
 
 AnnotationNode::AnnotationNode(const Config& conf) :
@@ -62,6 +71,9 @@ _priority   ( 0.0f )
     this->addCullCallback( _horizonCuller.get() );
 
     this->setName( conf.value("name") );
+
+    _mapNodeRequired = true;
+    ADJUST_UPDATE_TRAV_COUNT(this, +1);
 }
 
 AnnotationNode::~AnnotationNode()
@@ -70,6 +82,33 @@ AnnotationNode::~AnnotationNode()
 }
 
 void
+AnnotationNode::traverse(osg::NodeVisitor& nv)
+{
+    if (nv.getVisitorType() == nv.UPDATE_VISITOR)
+    {
+        // MapNode auto discovery.
+        if (_mapNodeRequired)
+        {
+            if (getMapNode() == 0L)
+            {
+                MapNode* mapNode = osgEarth::findInNodePath<MapNode>(nv);
+                if (mapNode)
+                {
+                    setMapNode(mapNode);
+                }
+            }
+
+            if (getMapNode() != 0L)
+            {
+                _mapNodeRequired = false;
+                ADJUST_UPDATE_TRAV_COUNT(this, -1);
+            }
+        }
+    }
+    osg::Group::traverse(nv);
+}
+
+void
 AnnotationNode::setLightingIfNotSet( bool lighting )
 {
     osg::StateSet* ss = this->getOrCreateStateSet();
@@ -171,9 +210,12 @@ AnnotationNode::applyRenderSymbology(const Style& style)
 
         if ( render->lighting().isSet() )
         {
-            getOrCreateStateSet()->setMode(
-                GL_LIGHTING,
+            getOrCreateStateSet()->setDefine(
+                OE_LIGHTING_DEFINE,
                 (render->lighting() == true? osg::StateAttribute::ON : osg::StateAttribute::OFF) | osg::StateAttribute::OVERRIDE );
+            //getOrCreateStateSet()->setMode(
+            //    GL_LIGHTING,
+            //    (render->lighting() == true? osg::StateAttribute::ON : osg::StateAttribute::OFF) | osg::StateAttribute::OVERRIDE );
         }
 
         if ( render->depthOffset().isSet() )
@@ -189,7 +231,7 @@ AnnotationNode::applyRenderSymbology(const Style& style)
                 (render->backfaceCulling() == true? osg::StateAttribute::ON : osg::StateAttribute::OFF) | osg::StateAttribute::OVERRIDE );
         }
 
-#ifndef OSG_GLES2_AVAILABLE
+#if !( defined(OSG_GLES2_AVAILABLE) || defined(OSG_GLES3_AVAILABLE) || defined(OSG_GL3_AVAILABLE) )
         if ( render->clipPlane().isSet() )
         {
             GLenum mode = GL_CLIP_PLANE0 + render->clipPlane().value();
@@ -197,7 +239,7 @@ AnnotationNode::applyRenderSymbology(const Style& style)
         }
 #endif
 
-        if ( render->order().isSet() || render->renderBin().isSet() )
+        if ( supportsRenderBinDetails() && (render->order().isSet() || render->renderBin().isSet()) )
         {
             osg::StateSet* ss = getOrCreateStateSet();
             int binNumber = render->order().isSet() ? (int)render->order()->eval() : ss->getBinNumber();
@@ -218,5 +260,14 @@ AnnotationNode::applyRenderSymbology(const Style& style)
             osg::StateSet* ss = getOrCreateStateSet();
             ss->setRenderingHint( ss->TRANSPARENT_BIN );
         }
+        
+        if (render->decal() == true)
+        {
+            getOrCreateStateSet()->setAttributeAndModes(
+                new osg::PolygonOffset(-1,-1), 1);
+
+            getOrCreateStateSet()->setAttributeAndModes(
+                new osg::Depth(osg::Depth::LEQUAL, 0, 1, false));
+        }
     }
 }
diff --git a/src/osgEarthAnnotation/AnnotationRegistry b/src/osgEarthAnnotation/AnnotationRegistry
index cae1e08..00b4045 100644
--- a/src/osgEarthAnnotation/AnnotationRegistry
+++ b/src/osgEarthAnnotation/AnnotationRegistry
@@ -78,7 +78,12 @@ namespace osgEarth { namespace Annotation
 
     // Macro used to register new annotation types.
 #define OSGEARTH_REGISTER_ANNOTATION( KEY, CLASSNAME ) \
+    extern "C" void osgearth_annotation_##KEY(void) {} \
     static AnnotationRegistrationProxy< CLASSNAME > s_osgEarthAnnotationRegistrationProxy##KEY( #KEY )
+    
+#define USE_OSGEARTH_ANNOTATION( KEY ) \
+    extern "C" void osgearth_annotation_##KEY(void); \
+    static osgDB::PluginFunctionProxy proxy_osgearth_annotation_##KEY(osgearth_annotation_##KEY);
 
 
     //--------------------------------------------------------------------
diff --git a/src/osgEarthAnnotation/AnnotationUtils.cpp b/src/osgEarthAnnotation/AnnotationUtils.cpp
index 805e9a5..addaa7e 100755
--- a/src/osgEarthAnnotation/AnnotationUtils.cpp
+++ b/src/osgEarthAnnotation/AnnotationUtils.cpp
@@ -87,6 +87,20 @@ AnnotationUtils::convertTextSymbolEncoding (const TextSymbol::Encoding encoding)
     return text_encoding;
 }
 
+namespace
+{
+    static int nextPowerOf2(int x)
+    {
+        --x;
+        x |= x >> 1;
+        x |= x >> 2;
+        x |= x >> 4;
+        x |= x >> 8;
+        x |= x >> 16;
+        return x+1;
+    }
+}
+
 osg::Drawable*
 AnnotationUtils::createTextDrawable(const std::string& text,
                                     const TextSymbol*  symbol,
@@ -184,19 +198,19 @@ AnnotationUtils::createTextDrawable(const std::string& text,
 
     t->setAutoRotateToScreen(false);
 
-#if OSG_MIN_VERSION_REQUIRED(3,4,0)
     t->setCullingActive(false);
-#endif
 
     t->setCharacterSizeMode( osgText::Text::OBJECT_COORDS );
-    float size = symbol && symbol->size().isSet() ? (float)(symbol->size()->eval()) : 16.0f;
-    t->setCharacterSize( size );
+    
+    float size = symbol && symbol->size().isSet() ? (float)(symbol->size()->eval()) : 16.0f;    
+
+    t->setCharacterSize( size * Registry::instance()->getDevicePixelRatio() );
     t->setColor( symbol && symbol->fill().isSet() ? symbol->fill()->color() : Color::White );
 
-    osgText::Font* font = 0L;
+    osg::ref_ptr<osgText::Font> font;
     if ( symbol && symbol->font().isSet() )
     {
-        font = osgText::readFontFile( *symbol->font() );
+        font = osgText::readRefFontFile( *symbol->font() );
     }
     if ( !font )
         font = Registry::instance()->getDefaultFont();
@@ -205,12 +219,21 @@ AnnotationUtils::createTextDrawable(const std::string& text,
     {
         t->setFont( font );
 
+        
+#if OSG_VERSION_LESS_THAN(3,5,8)
         // mitigates mipmapping issues that cause rendering artifacts for some fonts/placement
         font->setGlyphImageMargin( 2 );
+#endif
+
+        // OSG 3.4.1+ adds a program, so we remove it since we're using VPs.
+        t->setStateSet(0L);
     }
 
-    t->setFontResolution( size, size );
-    t->setBackdropOffset( (float)t->getFontWidth() / 256.0f, (float)t->getFontHeight() / 256.0f );
+    float resFactor = 2.0f;
+    int res = nextPowerOf2((int)(size*resFactor));
+    t->setFontResolution(res, res);
+    float offsetFactor = 1.0f / (resFactor*256.0f);
+    t->setBackdropOffset( (float)t->getFontWidth() * offsetFactor, (float)t->getFontHeight() * offsetFactor );
 
     if ( symbol && symbol->halo().isSet() )
     {
@@ -278,8 +301,8 @@ AnnotationUtils::createImageGeometry(osg::Image*       image,
     geom->setUseVertexBufferObjects(true);
     geom->setStateSet(dstate);
 
-    float s = scale * image->s();
-    float t = scale * image->t();
+    float s = Registry::instance()->getDevicePixelRatio() * scale * image->s();
+    float t = Registry::instance()->getDevicePixelRatio() * scale * image->t();
 
     float x0 = (float)pixelOffset.x() - s/2.0;
     float y0 = (float)pixelOffset.y() - t/2.0;
@@ -313,7 +336,8 @@ AnnotationUtils::createImageGeometry(osg::Image*       image,
     geom->setColorArray(colors);
     geom->setColorBinding(osg::Geometry::BIND_OVERALL);
 
-    geom->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::QUADS,0,4));
+    GLushort indices[] = {0,1,2,0,2,3};
+    geom->addPrimitiveSet( new osg::DrawElementsUShort( GL_TRIANGLES, 6, indices ) );
 
     return geom;
 }
@@ -819,31 +843,11 @@ AnnotationUtils::installOverlayParent(osg::Node* node, const Style& style)
     // GPU-clamped geometry
     else if ( ap.gpuClamping )
     {
-        ClampableNode* clampable = new ClampableNode( 0L );
+        ClampableNode* clampable = new ClampableNode();
         clampable->addChild( node );
         node = clampable;
-
-        const RenderSymbol* render = style.get<RenderSymbol>();
-        if ( render && render->depthOffset().isSet() )
-        {
-            clampable->setDepthOffsetOptions( *render->depthOffset() );
-        }
     }
 
-#if 0 // TODO -- constuct a callback instead.
-
-    // scenegraph-clamped geometry
-    else if ( ap.sceneClamping )
-    {
-        // save for later when we need to reclamp the mesh on the CPU
-        _altitude = style.get<AltitudeSymbol>();
-
-        // activate the terrain callback:
-        setCPUAutoClamping( true );
-    }
-
-#endif
-
     return node;
 }
 
diff --git a/src/osgEarthAnnotation/BboxDrawable.cpp b/src/osgEarthAnnotation/BboxDrawable.cpp
index 88cc073..003f415 100644
--- a/src/osgEarthAnnotation/BboxDrawable.cpp
+++ b/src/osgEarthAnnotation/BboxDrawable.cpp
@@ -75,7 +75,5 @@ osg::Geometry()
     getOrCreateStateSet()->addUniform( s_isTextUniform.get() );
 
     // Disable culling since this bounding box will eventually be drawn in screen space.
-#if OSG_MIN_VERSION_REQUIRED(3,4,0)
     setCullingActive(false);
-#endif
 }
diff --git a/src/osgEarthAnnotation/CMakeLists.txt b/src/osgEarthAnnotation/CMakeLists.txt
index 667b922..47f39cf 100644
--- a/src/osgEarthAnnotation/CMakeLists.txt
+++ b/src/osgEarthAnnotation/CMakeLists.txt
@@ -8,18 +8,17 @@ set(LIB_NAME osgEarthAnnotation)
 
 set(HEADER_PATH ${OSGEARTH_SOURCE_DIR}/include/${LIB_NAME})
 
-set(LIB_PUBLIC_HEADERS  
+set(LIB_PUBLIC_HEADERS
     AnnotationSettings
     AnnotationEditing
-    AnnotationExtension
     AnnotationData
+    AnnotationLayer
     AnnotationNode
     AnnotationRegistry
     AnnotationUtils
 	BboxDrawable
     CircleNode
     Common
-    Decoration
     Draggers
     EllipseNode
     Export
@@ -28,28 +27,25 @@ set(LIB_PUBLIC_HEADERS
     GeoPositionNode
     GeoPositionNodeAutoScaler
     LocalGeometryNode
-    HighlightDecoration
     ImageOverlay
     ImageOverlayEditor
     LabelNode
     ModelNode
     PlaceNode
     RectangleNode
-    ScaleDecoration
     TrackNode
 )
 
 set(LIB_COMMON_FILES
     AnnotationEditing.cpp
-    AnnotationExtension.cpp
     AnnotationSettings.cpp
     AnnotationData.cpp
+    AnnotationLayer.cpp
     AnnotationNode.cpp
     AnnotationRegistry.cpp
     AnnotationUtils.cpp
 	BboxDrawable.cpp
     CircleNode.cpp
-    Decoration.cpp
     Draggers.cpp
     EllipseNode.cpp
     FeatureNode.cpp
@@ -57,7 +53,6 @@ set(LIB_COMMON_FILES
     GeoPositionNode.cpp
     GeoPositionNodeAutoScaler.cpp
     LocalGeometryNode.cpp
-    HighlightDecoration.cpp
     ImageOverlay.cpp
     ImageOverlayEditor.cpp
     LabelNode.cpp
@@ -69,7 +64,7 @@ set(LIB_COMMON_FILES
 
 
 ADD_LIBRARY( ${LIB_NAME} ${OSGEARTH_USER_DEFINED_DYNAMIC_OR_STATIC}
-    ${LIB_PUBLIC_HEADERS}  
+    ${LIB_PUBLIC_HEADERS}
     ${LIB_COMMON_FILES}
 )
 
diff --git a/src/osgEarthAnnotation/Common b/src/osgEarthAnnotation/Common
index bfb920c..bb9acbe 100644
--- a/src/osgEarthAnnotation/Common
+++ b/src/osgEarthAnnotation/Common
@@ -24,7 +24,10 @@
 #include <osgEarthAnnotation/Export>
 
 // common utilities
-namespace osgEarth { namespace Annotation {
+namespace osgEarth { 
+    
+    //! Annotations
+    namespace Annotation {
     
 } } // namespace osgEarth::Annotation
 
diff --git a/src/osgEarthAnnotation/Decoration b/src/osgEarthAnnotation/Decoration
deleted file mode 100644
index 5df1d5e..0000000
--- a/src/osgEarthAnnotation/Decoration
+++ /dev/null
@@ -1,120 +0,0 @@
-/* -*-c++-*- */
-/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2016 Pelican Mapping
-* http://osgearth.org
-*
-* osgEarth is free software; you can redistribute it and/or modify
-* it under the terms of the GNU Lesser General Public License as published by
-* the Free Software Foundation; either version 2 of the License, or
-* (at your option) any later version.
-*
-* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
-* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
-* IN THE SOFTWARE.
-*
-* You should have received a copy of the GNU Lesser General Public License
-* along with this program.  If not, see <http://www.gnu.org/licenses/>
-*/
-#ifndef OSGEARTH_ANNOTATION_DECORATION_H
-#define OSGEARTH_ANNOTATION_DECORATION_H 1
-
-#include <osgEarthAnnotation/Common>
-#include <osg/NodeVisitor>
-
-namespace osgEarth { namespace Annotation
-{	
-    using namespace osgEarth;
-
-#if 0
-    //-----------------------------------------------------------------------
-
-    class OSGEARTHANNO_EXPORT Decoration : public osg::Referenced
-    {
-    public:
-        Decoration() { }
-        virtual ~Decoration() { }
-
-        virtual bool isShareable() const { return false; }
-        virtual Decoration* clone() const =0;
-        Decoration* copyOrClone() { return isShareable() ? this : clone(); }
-
-    public:
-        virtual bool apply(class AnnotationNode& node, bool enable);
-        virtual bool apply(class GeoPositionNode& node, bool enable);
-    };
-
-    //-----------------------------------------------------------------------
-
-    /**
-     * Simple visitor that installs a decoration on all the 
-     * AnnotationNode's in a graph.
-     */
-    class OSGEARTHANNO_EXPORT DecorationInstaller : public osg::NodeVisitor
-    {
-    public:
-        struct Callback : public osg::Referenced
-        {
-            virtual void operator()( AnnotationNode* node ) =0;
-        };
-
-    public:
-        /**
-         * Constructor - for applying a single decorator to all the Annotations
-         * found in the visited graph.
-         */
-        DecorationInstaller( const std::string& name, Decoration* tech )
-            : osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN),
-              _tech(tech), _name(name) { }
-
-        /**
-         * Constructor - for using a callback to assign a decorator to each
-         * individual Annotation.
-         */
-        DecorationInstaller( const std::string& name, Callback* callback )
-            : osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN),
-              _callback(callback), _name(name) { }
-
-
-        virtual ~DecorationInstaller() { }
-
-    public:
-        virtual void apply(osg::Node& node);
-
-    public:
-        osg::ref_ptr<Decoration> _tech;
-        osg::ref_ptr<Callback>   _callback;
-        std::string              _name;
-    };
-
-    //-----------------------------------------------------------------------
-
-    /** 
-     * A decorator that injects a node below the annotation's
-     * transform. For example, you could install an Effect node, or a node
-     * that activates a shader.
-     */
-    class OSGEARTHANNO_EXPORT InjectionDecoration : public Decoration
-    {
-    public:
-        InjectionDecoration( osg::Group* group =0L );
-        virtual ~InjectionDecoration() { }
-
-        virtual Decoration* clone() const { return new InjectionDecoration(osg::clone(_injectionGroup.get(), osg::CopyOp::DEEP_COPY_ALL)); }
-
-        virtual bool apply(class AnnotationNode& node, bool enable);
-
-    protected:
-        osg::ref_ptr<osg::Group> _injectionGroup;
-
-        virtual bool apply(osg::Group* attachPoint, bool enable);
-    };
-
-#endif
-
-} } // namespace osgEarth::Annotation
-
-#endif //OSGEARTH_ANNOTATION_DECORATION_H
diff --git a/src/osgEarthAnnotation/Decoration.cpp b/src/osgEarthAnnotation/Decoration.cpp
deleted file mode 100644
index 451e538..0000000
--- a/src/osgEarthAnnotation/Decoration.cpp
+++ /dev/null
@@ -1,106 +0,0 @@
-/* -*-c++-*- */
-/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2016 Pelican Mapping
-* http://osgearth.org
-*
-* osgEarth is free software; you can redistribute it and/or modify
-* it under the terms of the GNU Lesser General Public License as published by
-* the Free Software Foundation; either version 2 of the License, or
-* (at your option) any later version.
-*
-* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
-* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
-* IN THE SOFTWARE.
-*
-* You should have received a copy of the GNU Lesser General Public License
-* along with this program.  If not, see <http://www.gnu.org/licenses/>
-*/
-
-#include <osgEarthAnnotation/Decoration>
-
-#include <osgEarthAnnotation/AnnotationUtils>
-#include <osgEarthAnnotation/AnnotationNode>
-#include <osgEarthAnnotation/GeoPositionNode>
-#include <osgEarthAnnotation/LabelNode>
-#include <osgEarthAnnotation/PlaceNode>
-#include <osgEarthAnnotation/TrackNode>
-
-using namespace osgEarth::Annotation;
-#if 0
-//---------------------------------------------------------------------------
-
-void
-DecorationInstaller::apply(osg::Node& node)
-{
-    if ( dynamic_cast<AnnotationNode*>(&node) )
-    {
-        if ( _tech.valid() )
-            static_cast<AnnotationNode*>(&node)->installDecoration( _name, _tech );
-        else if ( _callback.valid() )
-            _callback->operator()( static_cast<AnnotationNode*>(&node) );
-    }
-    traverse(node);
-}
-
-//---------------------------------------------------------------------------
-
-bool
-Decoration::apply(class AnnotationNode& node, bool enable)
-{
-    return false;
-}
-
-bool
-Decoration::apply(class GeoPositionNode& node, bool enable)
-{
-    return apply(static_cast<AnnotationNode&>(node), enable);
-}
-
-//---------------------------------------------------------------------------
-
-InjectionDecoration::InjectionDecoration( osg::Group* group ) :
-_injectionGroup( group )
-{
-    if ( !_injectionGroup.valid() )
-        _injectionGroup = new osg::Group();
-}
-
-bool
-InjectionDecoration::apply(AnnotationNode& node, bool enable)
-{
-    bool success = apply( node.getChildAttachPoint(), enable );
-    return success ? true : Decoration::apply(node, enable);
-}
-
-bool
-InjectionDecoration::apply(osg::Group* ap, bool enable)
-{
-    if ( _injectionGroup.valid() && ap )
-    {
-        if ( enable )
-        {
-            for( unsigned i=0; i<ap->getNumChildren(); ++i )
-            {
-                _injectionGroup->addChild( ap->getChild(i) );
-            }
-            ap->removeChildren(0, ap->getNumChildren() );
-            ap->addChild( _injectionGroup.get() );
-        }
-        else // if ( !enable)
-        {
-            for( unsigned i=0; i<_injectionGroup->getNumChildren(); ++i )
-            {
-                ap->addChild( _injectionGroup->getChild(i) );
-            }
-            ap->removeChild(0, 1);
-            _injectionGroup->removeChildren(0, _injectionGroup->getNumChildren());
-        }
-        return true;
-    }
-    return false;
-}
-#endif
\ No newline at end of file
diff --git a/src/osgEarthAnnotation/FeatureEditing.cpp b/src/osgEarthAnnotation/FeatureEditing.cpp
index 5e9db15..2359107 100644
--- a/src/osgEarthAnnotation/FeatureEditing.cpp
+++ b/src/osgEarthAnnotation/FeatureEditing.cpp
@@ -62,7 +62,7 @@ AddPointHandler::addPoint( float x, float y, osgViewer::View* view )
         GeoPoint mapPoint;
         mapPoint.fromWorld( mapNode->getMapSRS(), world );
 
-        Feature* feature = _featureNode->getFeatures().front();
+        Feature* feature = _featureNode->getFeatures().front().get();
 
         if ( feature )            
         {
@@ -125,7 +125,7 @@ public:
 
       virtual void onPositionChanged(const Dragger* sender, const osgEarth::GeoPoint& position)
       {
-          Feature* feature = _featureNode->getFeatures().front();
+          Feature* feature = _featureNode->getFeatures().front().get();
           (*feature->getGeometry())[_point] =  osg::Vec3d(position.x(), position.y(), 0);
           _featureNode->init();
       }
@@ -200,7 +200,7 @@ FeatureEditor::init()
 {
     removeChildren( 0, getNumChildren() );
 
-    Feature* feature = _featureNode->getFeatures().front();
+    Feature* feature = _featureNode->getFeatures().front().get();
     //Create a dragger for each point
     for (unsigned int i = 0; i < feature->getGeometry()->size(); i++)
     {
diff --git a/src/osgEarthAnnotation/FeatureNode b/src/osgEarthAnnotation/FeatureNode
index 22b2f1b..e0603c9 100644
--- a/src/osgEarthAnnotation/FeatureNode
+++ b/src/osgEarthAnnotation/FeatureNode
@@ -25,7 +25,7 @@
 #include <osgEarthAnnotation/AnnotationNode>
 #include <osgEarth/MapNode>
 #include <osgEarth/GeometryClamper>
-#include <osgEarthSymbology/Style>
+#include <osgEarthSymbology/StyleSheet>
 #include <osgEarthFeatures/Feature>
 #include <osgEarthFeatures/GeometryCompiler>
 #include <osg/Polytope>
@@ -47,27 +47,23 @@ namespace osgEarth { namespace Annotation
         META_AnnotationNode(osgEarthAnnotation, FeatureNode);
 
         /**
-         * Constructs a new FeatureNode from a single Feature
+         * Construct a new FeatureNode from a single Feature.
          */
         FeatureNode( 
-            MapNode* mapNode, 
             Feature* feature,
             const Style& style = Style(),
             const GeometryCompilerOptions& options = GeometryCompilerOptions(),
             StyleSheet* styleSheet = 0);
 
         /**
-         * Constructs a new FeatureNode from a FeatureList
+         * Constuct a new FeatureNode from a list of features.
          */
-        FeatureNode( 
-            MapNode* mapNode, 
-            FeatureList& features,
+        FeatureNode(
+            const FeatureList& features,
             const Style& style = Style(),
             const GeometryCompilerOptions& options = GeometryCompilerOptions(), 
             StyleSheet* styleSheet = 0);
 
-        virtual ~FeatureNode() { }
-
          /**
          * Gets the list of features
          */
@@ -93,7 +89,6 @@ namespace osgEarth { namespace Annotation
          */
         void setStyleSheet(StyleSheet* styleSheet);
 
-
         /**
          * Call init to force a rebuild of this FeatureNode.  If you modify the features in the features list or add/remove features
          * call this function to rebuild the node.
@@ -101,6 +96,34 @@ namespace osgEarth { namespace Annotation
         void init();
         void dirty() { init(); }
 
+    public: // older constructors that take a MapNode
+
+        /**
+         * Constructs a new FeatureNode from a single Feature and a MapNode.
+         * You can use this variant if you the FeatureNode will not be a
+         * descendant of the MapNode.
+         * NOTE: candidate for deprecation
+         */
+        FeatureNode( 
+            MapNode* mapNode, 
+            Feature* feature,
+            const Style& style = Style(),
+            const GeometryCompilerOptions& options = GeometryCompilerOptions(),
+            StyleSheet* styleSheet = 0);
+
+        /**
+         * Constructs a new FeatureNode from a FeatureList and a MapNode.
+         * You can use this variant if you the FeatureNode will not be a
+         * descendant of the MapNode.
+         * NOTE: candidate for deprecation
+         */
+        FeatureNode( 
+            MapNode* mapNode, 
+            const FeatureList& features,
+            const Style& style = Style(),
+            const GeometryCompilerOptions& options = GeometryCompilerOptions(), 
+            StyleSheet* styleSheet = 0);
+
     public: // AnnotationNode
 
         /**
@@ -115,6 +138,10 @@ namespace osgEarth { namespace Annotation
          */
         virtual void setStyle(const Style& style);
 
+    public: // osg::Node
+
+        virtual void traverse(osg::NodeVisitor&);
+
     public: // MapNodeObserver
 
         virtual void setMapNode( MapNode* mapNode );
@@ -125,6 +152,9 @@ namespace osgEarth { namespace Annotation
         virtual Config getConfig() const;
 
     protected:
+
+        virtual ~FeatureNode() { }
+
         FeatureList                  _features;
         GeometryCompilerOptions      _options;
         osg::Group*                  _attachPoint;
@@ -135,6 +165,7 @@ namespace osgEarth { namespace Annotation
 
         typedef TerrainCallbackAdapter<FeatureNode> ClampCallback;
         osg::ref_ptr<ClampCallback> _clampCallback;
+        bool _clampDirty;
 
         osg::ref_ptr< osg::Node >    _compiled;
 
@@ -143,7 +174,7 @@ namespace osgEarth { namespace Annotation
         FeatureNode() { }
         FeatureNode(const FeatureNode& rhs, const osg::CopyOp& op) { }
         
-        void clamp(const Terrain* terrain, osg::Node* patch);
+        void clamp(osg::Node* graph, const Terrain* terrain);
 
         void build();
 
@@ -151,7 +182,7 @@ namespace osgEarth { namespace Annotation
 
         void onTileAdded(
             const TileKey&          key, 
-            osg::Node*              tile, 
+            osg::Node*              graph, 
             TerrainCallbackContext& context);
     };
 
diff --git a/src/osgEarthAnnotation/FeatureNode.cpp b/src/osgEarthAnnotation/FeatureNode.cpp
index 5ff3e5b..88421be 100644
--- a/src/osgEarthAnnotation/FeatureNode.cpp
+++ b/src/osgEarthAnnotation/FeatureNode.cpp
@@ -26,6 +26,7 @@
 
 #include <osgEarthFeatures/GeometryCompiler>
 #include <osgEarthFeatures/GeometryUtils>
+#include <osgEarthFeatures/FilterContext>
 
 #include <osgEarthSymbology/AltitudeSymbol>
 
@@ -49,6 +50,41 @@ using namespace osgEarth::Annotation;
 using namespace osgEarth::Features;
 using namespace osgEarth::Symbology;
 
+FeatureNode::FeatureNode(Feature* feature,
+                         const Style& in_style,
+                         const GeometryCompilerOptions& options,
+                         StyleSheet* styleSheet) :
+AnnotationNode(),
+_options           ( options ),
+_needsRebuild      ( true ),
+_styleSheet        ( styleSheet ),
+_clampDirty        (false)
+{
+    _features.push_back( feature );
+
+    Style style = in_style;
+    if (style.empty() && feature->style().isSet())
+    {
+        style = *feature->style();
+    }
+
+    setStyle( style );
+}
+
+FeatureNode::FeatureNode(const FeatureList& features,
+                         const Style& style,
+                         const GeometryCompilerOptions& options,
+                         StyleSheet* styleSheet):
+AnnotationNode(),
+_options        ( options ),
+_needsRebuild   ( true ),
+_styleSheet     ( styleSheet ),
+_clampDirty     ( false )
+{
+    _features.insert( _features.end(), features.begin(), features.end() );
+    setStyle( style );
+}
+
 FeatureNode::FeatureNode(MapNode* mapNode,
                          Feature* feature,
                          const Style& in_style,
@@ -57,7 +93,8 @@ FeatureNode::FeatureNode(MapNode* mapNode,
 AnnotationNode(),
 _options           ( options ),
 _needsRebuild      ( true ),
-_styleSheet        ( styleSheet )
+_styleSheet        ( styleSheet ),
+_clampDirty        (false)
 {
     _features.push_back( feature );
 
@@ -73,14 +110,15 @@ _styleSheet        ( styleSheet )
 }
 
 FeatureNode::FeatureNode(MapNode* mapNode,
-                         FeatureList& features,
+                         const FeatureList& features,
                          const Style& style,
                          const GeometryCompilerOptions& options,
                          StyleSheet* styleSheet):
 AnnotationNode(),
 _options        ( options ),
 _needsRebuild   ( true ),
-_styleSheet     ( styleSheet )
+_styleSheet     ( styleSheet ),
+_clampDirty     ( false )
 {
     _features.insert( _features.end(), features.begin(), features.end() );
     FeatureNode::setMapNode( mapNode );
@@ -190,15 +228,9 @@ FeatureNode::build()
         // GPU-clamped geometry
         else if ( ap.gpuClamping )
         {
-            ClampableNode* clampable = new ClampableNode( getMapNode() );
+            ClampableNode* clampable = new ClampableNode();
             clampable->addChild( _attachPoint );
             this->addChild( clampable );
-
-            const RenderSymbol* render = style.get<RenderSymbol>();
-            if ( render && render->depthOffset().isSet() )
-            {
-                clampable->setDepthOffsetOptions( *render->depthOffset() );
-            }
         }
 
         else
@@ -208,15 +240,17 @@ FeatureNode::build()
             // set default lighting based on whether we are extruding:
             setLightingIfNotSet( style.has<ExtrusionSymbol>() );
 
-            applyRenderSymbology( style );
+            //applyRenderSymbology( style );
         }
 
+        applyRenderSymbology(style);
+
         if ( getMapNode()->getTerrain() )
         {
             if ( ap.sceneClamping )
             {
                 getMapNode()->getTerrain()->addTerrainCallback( _clampCallback.get() );
-                clamp( getMapNode()->getTerrain(), getMapNode()->getTerrain()->getGraph() );
+                clamp( getMapNode()->getTerrain()->getGraph(), getMapNode()->getTerrain() );
             }
             else
             {
@@ -259,7 +293,7 @@ FeatureNode::setStyle(const Style& style)
 
 StyleSheet* FeatureNode::getStyleSheet() const
 {
-    return _styleSheet;
+    return _styleSheet.get();
 }
 
 void FeatureNode::setStyleSheet(StyleSheet* styleSheet)
@@ -271,7 +305,7 @@ Feature* FeatureNode::getFeature()
 {
     if (_features.size() == 1)
     {
-        return _features.front();
+        return _features.front().get();
     }
     return 0;
 }
@@ -296,33 +330,76 @@ void FeatureNode::init()
 // This will be called by AnnotationNode when a new terrain tile comes in.
 void
 FeatureNode::onTileAdded(const TileKey&          key,
-                         osg::Node*              tile,
+                         osg::Node*              graph,
                          TerrainCallbackContext& context)
 {
-    if ( !tile || _featurePolytope.contains( tile->getBound() ) )
+    if (!_clampDirty)
     {
-        clamp( context.getTerrain(), tile );
+        bool needsClamp;
+
+        if (key.valid())
+        {
+            osg::Polytope tope;
+            key.getExtent().createPolytope(tope);
+            needsClamp = tope.contains(this->getBound());
+        }
+        else
+        {
+            // without a valid tilekey we don't know the extent of the change,
+            // so clamping is required.
+            needsClamp = true;
+        }
+
+        if (needsClamp)
+        {
+            _clampDirty = true;
+            ADJUST_UPDATE_TRAV_COUNT(this, +1);
+            //clamp(graph, context.getTerrain());
+        }
     }
 }
 
 void
-FeatureNode::clamp(const Terrain* terrain, osg::Node* patch)
+FeatureNode::clamp(osg::Node* graph, const Terrain* terrain)
 {
-    if ( terrain && patch )
+    if ( terrain && graph )
     {
         const AltitudeSymbol* alt = getStyle().get<AltitudeSymbol>();
+        if (alt && alt->technique() != alt->TECHNIQUE_SCENE)
+            return;
+
         bool relative = alt && alt->clamping() == alt->CLAMP_RELATIVE_TO_TERRAIN && alt->technique() == alt->TECHNIQUE_SCENE;
+        float offset = alt ? alt->verticalOffset()->eval() : 0.0f;
 
         GeometryClamper clamper;
-        clamper.setTerrainPatch( patch );
+        clamper.setTerrainPatch( graph );
         clamper.setTerrainSRS( terrain->getSRS() );
         clamper.setPreserveZ( relative );
+        clamper.setOffset( offset );
 
         this->accept( clamper );
-        this->dirtyBound();
     }
 }
 
+void
+FeatureNode::traverse(osg::NodeVisitor& nv)
+{
+    if (nv.getVisitorType() == nv.UPDATE_VISITOR && _clampDirty)
+    {
+        if (getMapNode())
+        {
+            osg::ref_ptr<Terrain> terrain = getMapNode()->getTerrain();
+            if (terrain.valid())
+                clamp(terrain->getGraph(), terrain.get());
+
+            ADJUST_UPDATE_TRAV_COUNT(this, -1);
+            _clampDirty = false;
+        }
+    }
+    AnnotationNode::traverse(nv);
+}
+
+
 //-------------------------------------------------------------------
 
 OSGEARTH_REGISTER_ANNOTATION( feature, osgEarth::Annotation::FeatureNode );
@@ -331,7 +408,8 @@ OSGEARTH_REGISTER_ANNOTATION( feature, osgEarth::Annotation::FeatureNode );
 FeatureNode::FeatureNode(MapNode*              mapNode,
                          const Config&         conf,
                          const osgDB::Options* dbOptions ) :
-AnnotationNode(conf)
+AnnotationNode(conf),
+_clampDirty(false)
 {
     osg::ref_ptr<Geometry> geom;
     if ( conf.hasChild("geometry") )
@@ -339,13 +417,13 @@ AnnotationNode(conf)
         Config geomconf = conf.child("geometry");
         geom = GeometryUtils::geometryFromWKT( geomconf.value() );
         if ( !geom.valid() )
-            OE_WARN << LC << "Config is missing required 'geometry' element" << std::endl;
+            OE_WARN << LC << "Config (" << conf.value("name") << ") is missing valid 'geometry' element" << std::endl;
     }
 
     osg::ref_ptr<const SpatialReference> srs;
     srs = SpatialReference::create( conf.value("srs"), conf.value("vdatum") );
     if ( !srs.valid() )
-        OE_WARN << LC << "Config is missing required 'srs' element" << std::endl;
+        OE_WARN << LC << "Config is missing valid 'srs' element" << std::endl;
 
     optional<GeoInterpolation> geoInterp;
 
diff --git a/src/osgEarthAnnotation/GeoPositionNode b/src/osgEarthAnnotation/GeoPositionNode
index ee178da..8884295 100644
--- a/src/osgEarthAnnotation/GeoPositionNode
+++ b/src/osgEarthAnnotation/GeoPositionNode
@@ -58,9 +58,15 @@ namespace osgEarth { namespace Annotation
          */
         GeoPositionNode(MapNode* mapNode, const GeoPoint& position);
 
-        /** The anchor position */
-        virtual void setPosition(const GeoPoint& pos) { _geoxform->setPosition( pos ); dirty(); }
-        const GeoPoint& getPosition() const           { return _geoxform->getPosition(); }
+        /**
+         * The anchor position of this node.
+         * If the annotation also has a style that contains an AltitudeSymbol,
+         * any clamping properties in the symbol will take precedence over
+         * the altitude mode in the GeoPoint you pass into this call.
+         * @param pos New geoposition
+         */
+        virtual void setPosition(const GeoPoint& pos);
+        const GeoPoint& getPosition() const { return _geoxform->getPosition(); }
 
         /** Local XYZ offset */
         virtual void setLocalOffset(const osg::Vec3f& pos) { _paxform->setPosition(pos); dirty(); }
diff --git a/src/osgEarthAnnotation/GeoPositionNode.cpp b/src/osgEarthAnnotation/GeoPositionNode.cpp
index eda0c61..b3e67cd 100644
--- a/src/osgEarthAnnotation/GeoPositionNode.cpp
+++ b/src/osgEarthAnnotation/GeoPositionNode.cpp
@@ -90,9 +90,10 @@ _horizonCullingRequested( DEFAULT_HORIZON_CULLING )
 
 void
 GeoPositionNode::init()
-{    
+{
+    this->removeChildren(0, this->getNumChildren());
+
     _geoxform = new GeoTransform();
-    _geoxform->setAutoRecomputeHeights( true );
     this->addChild( _geoxform );
 
     _paxform = new osg::PositionAttitudeTransform();
@@ -169,6 +170,33 @@ GeoPositionNode::applyStyle(const Style& style)
     AnnotationNode::applyStyle( style );
 }
 
+void
+GeoPositionNode::setPosition(const GeoPoint& pos)
+{
+    GeoPoint pos2 = pos;
+
+    // The altitude symbol, if there is one, overrides the incoming GeoPoint:
+    const AltitudeSymbol* alt = getStyle().get<AltitudeSymbol>();
+    if (alt)
+    {
+        if (alt->clamping() == alt->CLAMP_TO_TERRAIN)
+        {
+            pos2.z() = 0;
+            pos2.altitudeMode() = ALTMODE_RELATIVE;
+        }
+        else if (alt->clamping() == alt->CLAMP_RELATIVE_TO_TERRAIN)
+        {
+            pos2.altitudeMode() = ALTMODE_RELATIVE;
+        }
+        else if (alt->clamping() == alt->CLAMP_NONE)
+        {
+            pos2.altitudeMode() = ALTMODE_ABSOLUTE;
+        }
+    }
+    _geoxform->setPosition( pos2 );
+    dirty();
+}
+
 bool
 GeoPositionNode::getOcclusionCulling() const
 {
@@ -217,8 +245,9 @@ void GeoPositionNode::setOcclusionCullingMaxAltitude( double occlusionCullingMax
 
 
 GeoPositionNode::GeoPositionNode(MapNode* mapNode, const Config& conf) :
-AnnotationNode          ( conf ),
-_horizonCullingRequested( true )
+AnnotationNode            ( conf ),
+_occlusionCullingRequested( DEFAULT_OCCLUSION_CULLING ),
+_horizonCullingRequested  ( DEFAULT_HORIZON_CULLING )
 {
     init();
     GeoPositionNode::setMapNode( mapNode );
@@ -229,6 +258,10 @@ void
 GeoPositionNode::setConfig(const Config& conf)
 {
     //AnnotationNode::setConfig(conf);
+    if (conf.hasValue("name"))
+    {
+        setName(conf.value("name"));
+    }
 
     if ( conf.hasChild( "position" ) )
     {
diff --git a/src/osgEarthAnnotation/GeoPositionNodeAutoScaler.cpp b/src/osgEarthAnnotation/GeoPositionNodeAutoScaler.cpp
index 4d1085b..2d44358 100644
--- a/src/osgEarthAnnotation/GeoPositionNodeAutoScaler.cpp
+++ b/src/osgEarthAnnotation/GeoPositionNodeAutoScaler.cpp
@@ -43,11 +43,54 @@ GeoPositionNodeAutoScaler::operator()(osg::Node* node, osg::NodeVisitor* nv)
 {
     GeoPositionNode* geo = static_cast<GeoPositionNode*>(node);
     osgUtil::CullVisitor* cs = static_cast<osgUtil::CullVisitor*>(nv);
-    double size = 1.0/cs->pixelSize( node->getBound().center(), 0.5f );
-	if (size < _minScale)
-		size = _minScale;
-	else if (size>_maxScale)
-		size = _maxScale;
-    geo->getPositionAttitudeTransform()->setScale( osg::componentMultiply(_baseScale, osg::Vec3d(size,size,size)) );
+
+    osg::Camera* cam = cs->getCurrentCamera();
+
+    // If this is an RTT camera, we need to use it's "parent"
+    // to calculate the proper scale factor.
+    if (cam->isRenderToTextureCamera() &&
+        cam->getView() &&
+        cam->getView()->getCamera() &&
+        cam->getView()->getCamera() != cam)
+    {
+        cam = cam->getView()->getCamera();
+    }
+
+    if (cam->getViewport())
+    {
+        // Reset the scale so we get a proper bound
+        geo->getPositionAttitudeTransform()->setScale(_baseScale);
+        const osg::BoundingSphere& bs = node->getBound();
+
+        // transform centroid to VIEW space:
+        osg::Vec3d centerView = bs.center() * cam->getViewMatrix();
+
+        // Set X coordinate to the radius so we can use the resulting CLIP
+        // distance to calculate meters per pixel:
+        centerView.x() = bs.radius();
+
+        // transform the CLIP space:
+        osg::Vec3d centerClip = centerView * cam->getProjectionMatrix();
+        
+        // caluclate meters per pixel:
+        double mpp = (centerClip.x()*0.5) * cam->getViewport()->width();
+
+        // and the resulting scale we need to auto-scale.
+        double scale = bs.radius() / mpp;
+
+        if (scale < _minScale)
+            scale = _minScale;
+        else if (scale>_maxScale)
+            scale = _maxScale;
+
+        geo->getPositionAttitudeTransform()->setScale(
+            osg::componentMultiply(_baseScale, osg::Vec3d(scale, scale, scale)));
+    }
+
+    if (node->getCullingActive() == false)
+    {
+        node->setCullingActive(true);
+    }
+
     traverse(node, nv);
 }
diff --git a/src/osgEarthAnnotation/HighlightDecoration b/src/osgEarthAnnotation/HighlightDecoration
deleted file mode 100644
index 90a540a..0000000
--- a/src/osgEarthAnnotation/HighlightDecoration
+++ /dev/null
@@ -1,62 +0,0 @@
-/* -*-c++-*- */
-/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2016 Pelican Mapping
-* http://osgearth.org
-*
-* osgEarth is free software; you can redistribute it and/or modify
-* it under the terms of the GNU Lesser General Public License as published by
-* the Free Software Foundation; either version 2 of the License, or
-* (at your option) any later version.
-*
-* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
-* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
-* IN THE SOFTWARE.
-*
-* You should have received a copy of the GNU Lesser General Public License
-* along with this program.  If not, see <http://www.gnu.org/licenses/>
-*/
-#ifndef OSGEARTH_ANNOTATION_HIGHLIGHT_DECORATION_H
-#define OSGEARTH_ANNOTATION_HIGHLIGHT_DECORATION_H 1
-
-#include <osgEarthAnnotation/Decoration>
-#include <osg/Uniform>
-
-namespace osgEarth { namespace Annotation
-{	
-    using namespace osgEarth;
-
-#if 0
-    /**
-     * Decoration technique that highlights the geometry.
-     */
-    class OSGEARTHANNO_EXPORT HighlightDecoration : public Decoration
-    {
-    public:
-        HighlightDecoration(const osg::Vec4f& color =osg::Vec4f(1,1,0,0.5));
-
-        virtual ~HighlightDecoration() { }
-
-        virtual Decoration* clone() const { return new HighlightDecoration(_color); }
-
-        /** Hightlight color (mixes with alpha) */
-        void setColor(const osg::Vec4f& color);
-        const osg::Vec4f& getColor() const { return _color; }
-        
-    public: // Decoration interface
-
-        virtual bool apply(class AnnotationNode& node, bool enable);
-
-    private:
-        bool                         _supported;
-        osg::Vec4f                   _color;
-        osg::ref_ptr<osg::Uniform>   _colorUniform;
-    };
-#endif
-
-} } // namespace osgEarth::Annotation
-
-#endif //OSGEARTH_ANNOTATION_HIGHLIGHT_DECORATION_H
diff --git a/src/osgEarthAnnotation/HighlightDecoration.cpp b/src/osgEarthAnnotation/HighlightDecoration.cpp
deleted file mode 100644
index 48742b3..0000000
--- a/src/osgEarthAnnotation/HighlightDecoration.cpp
+++ /dev/null
@@ -1,95 +0,0 @@
-/* -*-c++-*- */
-/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2016 Pelican Mapping
-* http://osgearth.org
-*
-* osgEarth is free software; you can redistribute it and/or modify
-* it under the terms of the GNU Lesser General Public License as published by
-* the Free Software Foundation; either version 2 of the License, or
-* (at your option) any later version.
-*
-* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
-* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
-* IN THE SOFTWARE.
-*
-* You should have received a copy of the GNU Lesser General Public License
-* along with this program.  If not, see <http://www.gnu.org/licenses/>
-*/
-
-#if 0
-#include <osgEarthAnnotation/HighlightDecoration>
-#include <osgEarthAnnotation/AnnotationNode>
-#include <osgEarthAnnotation/AnnotationUtils>
-#include <osgEarth/VirtualProgram>
-#include <osgEarth/Registry>
-#include <osgEarth/Capabilities>
-
-#undef  LC
-#define LC "[HighlightDecoration] "
-
-using namespace osgEarth::Annotation;
-
-#define FRAG_FUNCTION "oe_anno_highlight_frag"
-
-namespace
-{
-    const char* fragSource =
-        "#version " GLSL_VERSION_STR "\n"
-        "uniform vec4 oe_anno_highlight_color; \n"
-        "void " FRAG_FUNCTION "(inout vec4 color) {\n"
-        "    color.rgb = mix(color.rgb, oe_anno_highlight_color.rgb, oe_anno_highlight_color.a); \n"
-        "}\n";
-}
-
-
-HighlightDecoration::HighlightDecoration(const osg::Vec4f& color) :
-Decoration(),
-_color    (color)
-{
-    _supported = Registry::capabilities().supportsGLSL();
-    if ( _supported )
-    {
-        _colorUniform = new osg::Uniform(osg::Uniform::FLOAT_VEC4, "oe_anno_highlight_color");
-        _colorUniform->set(_color);
-    }
-}
-
-void
-HighlightDecoration::setColor(const osg::Vec4f& color)
-{
-    _color = color;
-    if ( _colorUniform.valid() )
-    {
-        _colorUniform->set(_color);
-    }
-}
-
-bool
-HighlightDecoration::apply(AnnotationNode& node, bool enable)
-{
-    if ( _supported )
-    {
-        osg::StateSet* ss = node.getOrCreateStateSet();
-        if ( enable )
-        {
-            VirtualProgram* vp = VirtualProgram::getOrCreate( ss );
-            if ( vp->getPolyShader(FRAG_FUNCTION) == 0L )
-            {
-                vp->setFunction(FRAG_FUNCTION, fragSource, ShaderComp::LOCATION_FRAGMENT_COLORING, 0.6f);
-                ss->addUniform( _colorUniform.get() );
-            }
-            _colorUniform->set(_color);
-        }
-        else
-        {
-            // sets alpha=0 to disable highlighting
-            _colorUniform->set(osg::Vec4f(1,1,1,0));
-        }
-    }
-    return _supported;
-}
-#endif
diff --git a/src/osgEarthAnnotation/ImageOverlay b/src/osgEarthAnnotation/ImageOverlay
index 3001d6a..7818841 100644
--- a/src/osgEarthAnnotation/ImageOverlay
+++ b/src/osgEarthAnnotation/ImageOverlay
@@ -25,6 +25,8 @@
 #include <osgEarth/URI>
 #include <osgEarth/Terrain>
 
+#include <osgEarthFeatures/Feature>
+
 #include <osg/Group>
 #include <osg/Geometry>
 #include <osg/Image>
@@ -145,7 +147,7 @@ namespace osgEarth { namespace Annotation
         // callback from Terrain whsen new data arrives
         void onTileAdded(
             const TileKey&          key, 
-            osg::Node*              tile, 
+            osg::Node*              graph, 
             TerrainCallbackContext& context);
         
     private:
@@ -157,6 +159,8 @@ namespace osgEarth { namespace Annotation
 
         void updateFilters();
 
+        osg::Node* createNode(osgEarth::Features::Feature* feature, bool split);
+
 
         osg::Vec2d _lowerLeft;
         osg::Vec2d _lowerRight;
@@ -166,9 +170,8 @@ namespace osgEarth { namespace Annotation
         osg::ref_ptr< osg::Image > _image;
         bool _dirty;
         OpenThreads::Mutex _mutex;
-        osg::Geode* _geode;
-        osg::MatrixTransform* _transform;
-        osg::Geometry* _geometry;
+
+        osg::Group* _root;        
         osg::Texture* _texture;
         Distance _geometryResolution;
 
@@ -179,10 +182,13 @@ namespace osgEarth { namespace Annotation
         optional<float> _alpha;
         optional<osg::Texture::FilterMode> _minFilter;
         optional<osg::Texture::FilterMode> _magFilter;
+        optional<bool> _draped;
+
+        bool _updateScheduled;
 
         osg::ref_ptr< TerrainCallbackAdapter<ImageOverlay> > _clampCallback;
         
-        void clamp(const Terrain* terrain, osg::Node* patch);
+        void clamp(osg::Node* patch, const Terrain* terrain);
 
         ImageOverlay() { }
         ImageOverlay(const ImageOverlay&, const osg::CopyOp&) { }
diff --git a/src/osgEarthAnnotation/ImageOverlay.cpp b/src/osgEarthAnnotation/ImageOverlay.cpp
index f845e47..ca036de 100644
--- a/src/osgEarthAnnotation/ImageOverlay.cpp
+++ b/src/osgEarthAnnotation/ImageOverlay.cpp
@@ -24,7 +24,6 @@
 #include <osgEarthSymbology/MeshSubdivider>
 #include <osgEarthFeatures/GeometryUtils>
 #include <osgEarth/GeometryClamper>
-#include <osgEarthFeatures/Feature>
 #include <osgEarth/MapNode>
 #include <osgEarth/NodeUtils>
 #include <osgEarth/ImageUtils>
@@ -54,7 +53,7 @@ namespace
     void clampLatitude(osg::Vec2d& l)
     {
         l.y() = osg::clampBetween( l.y(), -90.0, 90.0);
-    }
+    }    
 
     static Distance default_geometryResolution(5.0, Units::DEGREES);
 }
@@ -74,7 +73,8 @@ _alpha        (1.0f),
 _minFilter    (osg::Texture::LINEAR_MIPMAP_LINEAR),
 _magFilter    (osg::Texture::LINEAR),
 _texture      (0),
-_geometryResolution(default_geometryResolution)
+_geometryResolution(default_geometryResolution),
+_draped(true)
 {
     conf.getIfSet( "url",   _imageURI );
     if ( _imageURI.isSet() )
@@ -118,6 +118,8 @@ _geometryResolution(default_geometryResolution)
     conf.getIfSet("min_filter","NEAREST_MIPMAP_LINEAR", _minFilter,osg::Texture::NEAREST_MIPMAP_LINEAR);
     conf.getIfSet("min_filter","NEAREST_MIPMAP_NEAREST",_minFilter,osg::Texture::NEAREST_MIPMAP_NEAREST);
 
+    conf.getIfSet("draped", _draped);
+
     if (conf.hasValue("geometry_resolution"))
     {
         float value; Units units;
@@ -159,18 +161,20 @@ ImageOverlay::getConfig() const
     conf.add( geomConf );
 
     //Save the filter settings
-	conf.updateIfSet("mag_filter","LINEAR",                _magFilter,osg::Texture::LINEAR);
-    conf.updateIfSet("mag_filter","LINEAR_MIPMAP_LINEAR",  _magFilter,osg::Texture::LINEAR_MIPMAP_LINEAR);
-    conf.updateIfSet("mag_filter","LINEAR_MIPMAP_NEAREST", _magFilter,osg::Texture::LINEAR_MIPMAP_NEAREST);
-    conf.updateIfSet("mag_filter","NEAREST",               _magFilter,osg::Texture::NEAREST);
-    conf.updateIfSet("mag_filter","NEAREST_MIPMAP_LINEAR", _magFilter,osg::Texture::NEAREST_MIPMAP_LINEAR);
-    conf.updateIfSet("mag_filter","NEAREST_MIPMAP_NEAREST",_magFilter,osg::Texture::NEAREST_MIPMAP_NEAREST);
-    conf.updateIfSet("min_filter","LINEAR",                _minFilter,osg::Texture::LINEAR);
-    conf.updateIfSet("min_filter","LINEAR_MIPMAP_LINEAR",  _minFilter,osg::Texture::LINEAR_MIPMAP_LINEAR);
-    conf.updateIfSet("min_filter","LINEAR_MIPMAP_NEAREST", _minFilter,osg::Texture::LINEAR_MIPMAP_NEAREST);
-    conf.updateIfSet("min_filter","NEAREST",               _minFilter,osg::Texture::NEAREST);
-    conf.updateIfSet("min_filter","NEAREST_MIPMAP_LINEAR", _minFilter,osg::Texture::NEAREST_MIPMAP_LINEAR);
-    conf.updateIfSet("min_filter","NEAREST_MIPMAP_NEAREST",_minFilter,osg::Texture::NEAREST_MIPMAP_NEAREST);
+	conf.set("mag_filter","LINEAR",                _magFilter,osg::Texture::LINEAR);
+    conf.set("mag_filter","LINEAR_MIPMAP_LINEAR",  _magFilter,osg::Texture::LINEAR_MIPMAP_LINEAR);
+    conf.set("mag_filter","LINEAR_MIPMAP_NEAREST", _magFilter,osg::Texture::LINEAR_MIPMAP_NEAREST);
+    conf.set("mag_filter","NEAREST",               _magFilter,osg::Texture::NEAREST);
+    conf.set("mag_filter","NEAREST_MIPMAP_LINEAR", _magFilter,osg::Texture::NEAREST_MIPMAP_LINEAR);
+    conf.set("mag_filter","NEAREST_MIPMAP_NEAREST",_magFilter,osg::Texture::NEAREST_MIPMAP_NEAREST);
+    conf.set("min_filter","LINEAR",                _minFilter,osg::Texture::LINEAR);
+    conf.set("min_filter","LINEAR_MIPMAP_LINEAR",  _minFilter,osg::Texture::LINEAR_MIPMAP_LINEAR);
+    conf.set("min_filter","LINEAR_MIPMAP_NEAREST", _minFilter,osg::Texture::LINEAR_MIPMAP_NEAREST);
+    conf.set("min_filter","NEAREST",               _minFilter,osg::Texture::NEAREST);
+    conf.set("min_filter","NEAREST_MIPMAP_LINEAR", _minFilter,osg::Texture::NEAREST_MIPMAP_LINEAR);
+    conf.set("min_filter","NEAREST_MIPMAP_NEAREST",_minFilter,osg::Texture::NEAREST_MIPMAP_NEAREST);
+
+    conf.set("draped", _draped);
 
     if (_geometryResolution != default_geometryResolution)
     {
@@ -204,20 +208,20 @@ _geometryResolution(default_geometryResolution)
 void
 ImageOverlay::postCTOR()
 {
-    _geode = new osg::Geode;
+    _updateScheduled = false;
 
-    _transform = new osg::MatrixTransform;
-    _transform->addChild( _geode );
+    _root = new osg::Group;
 
     // place the geometry under a drapeable node so it will project onto the terrain    
-    DrapeableNode* d = new DrapeableNode(); // getMapNode() );
+    DrapeableNode* d = new DrapeableNode();
+    d->setDrapingEnabled(*_draped);
     addChild( d );
 
-    d->addChild( _transform );
+    d->addChild( _root );
 
     init();
 
-    ADJUST_UPDATE_TRAV_COUNT( this, 1 );
+    ADJUST_EVENT_TRAV_COUNT(this, 1);
 }
 
 void
@@ -225,7 +229,10 @@ ImageOverlay::init()
 {
     OpenThreads::ScopedLock< OpenThreads::Mutex > lock(_mutex);
 
-    _geode->removeDrawables(0, _geode->getNumDrawables() );
+    if (_root->getNumChildren() > 0)
+    {
+        _root->removeChildren(0, _root->getNumChildren());
+    }
 
     if ( !_clampCallback.valid() )
     {
@@ -233,10 +240,7 @@ ImageOverlay::init()
     }
 
     if ( getMapNode() )
-    {
-        osg::Geometry* geometry = new osg::Geometry();
-        geometry->setUseVertexBufferObjects(true);
-
+    {                
         const SpatialReference* mapSRS = getMapNode()->getMapSRS();
 
         // calculate a bounding polytope in world space (for mesh clamping):
@@ -249,73 +253,21 @@ ImageOverlay::init()
         
         f->getWorldBoundingPolytope( getMapNode()->getMapSRS(), _boundingPolytope );
 
-        // next, convert to world coords and create the geometry:
-        osg::Vec3Array* verts = new osg::Vec3Array();
-        verts->reserve(4);
-        osg::Vec3d anchor;
-        for( Geometry::iterator i = g->begin(); i != g->end(); ++i )
-        {        
-            osg::Vec3d map, world;        
-            f->getSRS()->transform( *i, mapSRS, map);
-            mapSRS->transformToWorld( map, world );
-            if (i == g->begin())
-            {
-                anchor = world;
-            }
-            verts->push_back( world - anchor );
+        FeatureList features;
+        if (!mapSRS->isGeographic())        
+        {
+            f->splitAcrossDateLine(features);
         }
-        
-        _transform->setMatrix( osg::Matrixd::translate( anchor ) );
-
-
-
-        geometry->setVertexArray( verts );
-        if ( verts->getVertexBufferObject() )
-            verts->getVertexBufferObject()->setUsage(GL_STATIC_DRAW_ARB);
-
-        osg::Vec4Array* colors = new osg::Vec4Array(1);
-        (*colors)[0] = osg::Vec4(1,1,1,*_alpha);
-
-        geometry->setColorArray( colors );
-        geometry->setColorBinding( osg::Geometry::BIND_OVERALL );
-
-         GLushort tris[6] = { 0, 1, 2,
-                            0, 2, 3
-                          };        
-        geometry->addPrimitiveSet(new osg::DrawElementsUShort( GL_TRIANGLES, 6, tris ) );
-
-        bool flip = false;
-        if (_image.valid())
+        else
         {
-            //Create the texture
-            _texture = new osg::Texture2D(_image.get());     
-            _texture->setWrap(_texture->WRAP_S, _texture->CLAMP_TO_EDGE);
-            _texture->setWrap(_texture->WRAP_T, _texture->CLAMP_TO_EDGE);
-            _texture->setResizeNonPowerOfTwoHint(false);
-            updateFilters();
-            _geode->getOrCreateStateSet()->setTextureAttributeAndModes(0, _texture, osg::StateAttribute::ON);    
-            flip = _image->getOrigin()==osg::Image::TOP_LEFT;
+            features.push_back( f );
         }
 
-        osg::Vec2Array* texcoords = new osg::Vec2Array(4);
-        (*texcoords)[0].set(0.0f,flip ? 1.0 : 0.0f);
-        (*texcoords)[1].set(1.0f,flip ? 1.0 : 0.0f);
-        (*texcoords)[2].set(1.0f,flip ? 0.0 : 1.0f);
-        (*texcoords)[3].set(0.0f,flip ? 0.0 : 1.0f);
-        geometry->setTexCoordArray(0, texcoords);
-
-         
-        //Only run the MeshSubdivider on geocentric maps
-        if (getMapNode()->getMap()->isGeocentric())
+        for (FeatureList::iterator itr = features.begin(); itr != features.end(); ++itr)
         {
-            MeshSubdivider ms(osg::Matrixd::inverse(_transform->getMatrix()), _transform->getMatrix());
-            ms.run(*geometry, _geometryResolution.as(Units::RADIANS), GEOINTERP_RHUMB_LINE);
+            _root->addChild(createNode(itr->get(), features.size() > 1));
         }
 
-        _geode->addDrawable( geometry );
-
-        _geometry = geometry;
-
         _dirty = false;
         
         // Set the annotation up for auto-clamping. We always need to auto-clamp a draped image
@@ -325,16 +277,9 @@ ImageOverlay::init()
         style.getOrCreate<AltitudeSymbol>()->clamping() = AltitudeSymbol::CLAMP_RELATIVE_TO_TERRAIN;
         applyStyle( style );
         setLightingIfNotSet( false );
-        //clampMesh( getMapNode()->getTerrain()->getGraph() );
-
-        if ( Registry::capabilities().supportsGLSL() )
-        {
-            //OE_WARN << LC << "ShaderGen RUNNING" << std::endl;
-            Registry::shaderGenerator().run( _geode, "osgEarth.ImageOverlay" );
-        }
 
         getMapNode()->getTerrain()->addTerrainCallback( _clampCallback.get() );
-        clamp( getMapNode()->getTerrain(), getMapNode()->getTerrain()->getGraph() );
+        clamp( getMapNode()->getTerrain()->getGraph(), getMapNode()->getTerrain() );
     }
 }
 
@@ -357,6 +302,7 @@ ImageOverlay::getDraped() const
 void
 ImageOverlay::setDraped( bool draped )
 {
+    _draped = draped;
     static_cast< DrapeableNode *>( getChild(0))->setDrapingEnabled( draped );
 }
 
@@ -401,6 +347,121 @@ ImageOverlay::setMagFilter( osg::Texture::FilterMode filter )
     updateFilters();
 }
 
+osg::Node* ImageOverlay::createNode(Feature* feature, bool split)
+{    
+    const SpatialReference* mapSRS = getMapNode()->getMapSRS();
+
+    osg::MatrixTransform* transform = new osg::MatrixTransform;
+    
+    osg::Geode* geode = new osg::Geode;
+    // Disable depth test
+    geode->getOrCreateStateSet()->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF);
+    transform->addChild(geode);
+
+    osg::Geometry* geometry = new osg::Geometry();     
+    geometry->setUseVertexBufferObjects(true);
+    geode->addDrawable( geometry );
+
+    // next, convert to world coords and create the geometry:
+    osg::Vec3Array* verts = new osg::Vec3Array();
+    verts->reserve(4);
+    osg::Vec3d anchor;
+    for( Geometry::iterator i = feature->getGeometry()->begin(); i != feature->getGeometry()->end(); ++i )
+    {        
+        osg::Vec3d map, world;        
+        feature->getSRS()->transform( *i, mapSRS, map);
+        mapSRS->transformToWorld( map, world );
+        if (i == feature->getGeometry()->begin())
+        {
+            anchor = world;
+        }
+        verts->push_back( world - anchor );
+    }    
+
+    transform->setMatrix( osg::Matrixd::translate( anchor ) );
+
+    geometry->setVertexArray( verts );
+    if ( verts->getVertexBufferObject() )
+        verts->getVertexBufferObject()->setUsage(GL_STATIC_DRAW_ARB);
+
+    osg::Vec4Array* colors = new osg::Vec4Array(1);
+    (*colors)[0] = osg::Vec4(1,1,1,*_alpha);
+
+    geometry->setColorArray( colors );
+    geometry->setColorBinding( osg::Geometry::BIND_OVERALL );
+
+    GLushort tris[6] = { 0, 1, 2,
+        0, 2, 3
+    };        
+    geometry->addPrimitiveSet(new osg::DrawElementsUShort( GL_TRIANGLES, 6, tris ) );
+
+    bool flip = false;
+    if (_image.valid())
+    {
+        //Create the texture
+        _texture = new osg::Texture2D(_image.get());     
+        _texture->setWrap(_texture->WRAP_S, _texture->CLAMP_TO_EDGE);
+        _texture->setWrap(_texture->WRAP_T, _texture->CLAMP_TO_EDGE);
+        _texture->setResizeNonPowerOfTwoHint(false);
+        updateFilters();
+        geode->getOrCreateStateSet()->setTextureAttributeAndModes(0, _texture, osg::StateAttribute::ON);    
+        flip = _image->getOrigin()==osg::Image::TOP_LEFT;
+    }
+
+    osg::Vec2Array* texcoords = new osg::Vec2Array(4);
+
+
+
+    if (split)
+    {
+        // If the feature has been split across the antimerdian we have to figure out new texture coordinates, we can't just just use the corners.
+        // This code is limited in that it only works with rectangular images though, so overlays that are non axis aligned and split across the antimerdian could look wrong
+        double width = _upperRight.x() - _lowerLeft.x();
+        double height = _upperRight.y() - _lowerLeft.y();
+
+        for (unsigned int i = 0; i < feature->getGeometry()->size(); ++i)
+        {
+            osg::Vec3d v = (*feature->getGeometry())[i];
+
+            if (v.x() < _lowerLeft.x())
+            {
+                v.x() += 360.0;
+            }
+            if (v.x() > _upperRight.x())
+            {
+                v.x() -= 360.0;
+            }
+
+            float s = (v.x() - _lowerLeft.x()) / width;
+            float t = (v.y() - _lowerLeft.y()) / height;
+            (*texcoords)[i].set(s, flip ? 1.0f - t : t);
+        }
+    }
+    else
+    {
+        (*texcoords)[0].set(0.0f, flip ? 1.0 : 0.0f);
+        (*texcoords)[1].set(1.0f, flip ? 1.0 : 0.0f);
+        (*texcoords)[2].set(1.0f, flip ? 0.0 : 1.0f);
+        (*texcoords)[3].set(0.0f, flip ? 0.0 : 1.0f);
+    }
+    geometry->setTexCoordArray(0, texcoords);    
+
+    //Only run the MeshSubdivider on geocentric maps
+    if (getMapNode()->getMap()->isGeocentric())
+    {
+        MeshSubdivider ms(osg::Matrixd::inverse(transform->getMatrix()), transform->getMatrix());
+        ms.run(*geometry, _geometryResolution.as(Units::RADIANS), GEOINTERP_RHUMB_LINE);
+    } 
+
+    if ( Registry::capabilities().supportsGLSL() )
+    {
+        //OE_WARN << LC << "ShaderGen RUNNING" << std::endl;
+        Registry::shaderGenerator().run( geode, "osgEarth.ImageOverlay" );
+    }
+
+    return transform;
+}
+
 void
 ImageOverlay::updateFilters()
 {
@@ -409,7 +470,7 @@ ImageOverlay::updateFilters()
         _texture->setFilter(osg::Texture::MAG_FILTER, *_magFilter);
 
         
-        if (ImageUtils::isPowerOfTwo( _image ) && !(!_image->isMipmap() && ImageUtils::isCompressed(_image)))
+        if (ImageUtils::isPowerOfTwo( _image.get() ) && !(!_image->isMipmap() && ImageUtils::isCompressed(_image.get())))
         {
             _texture->setFilter(osg::Texture::MIN_FILTER, *_minFilter);
         }
@@ -702,11 +763,30 @@ ImageOverlay::setControlPoint(ControlPoint controlPoint, double lon_deg, double
 
 void
 ImageOverlay::traverse(osg::NodeVisitor &nv)
-{     
-    if (nv.getVisitorType() == osg::NodeVisitor::UPDATE_VISITOR && _dirty)
+{
+    if (nv.getVisitorType() == nv.EVENT_VISITOR)
     {
-        init();        
+        if (_dirty == true && _updateScheduled == false)
+        {
+            _updateScheduled = true;
+            ADJUST_UPDATE_TRAV_COUNT(this, +1);
+        }
     }
+
+    else if (nv.getVisitorType() == nv.UPDATE_VISITOR)
+    {
+        if (_dirty)
+        {
+            init();
+        }
+
+        if (_updateScheduled)
+        {
+            _updateScheduled = false;
+            ADJUST_UPDATE_TRAV_COUNT(this, -1);
+        }
+    }
+
     AnnotationNode::traverse(nv);
 }
 
@@ -741,12 +821,12 @@ ImageOverlay::removeCallback( ImageOverlayCallback* cb )
 }
 
 void
-ImageOverlay::clamp(const Terrain* terrain, osg::Node* patch)
+ImageOverlay::clamp(osg::Node* graph, const Terrain* terrain)
 {
-    if ( terrain && patch )
+    if ( terrain && graph )
     {
         GeometryClamper clamper;
-        clamper.setTerrainPatch( patch );
+        clamper.setTerrainPatch( graph );
         clamper.setTerrainSRS( terrain->getSRS() );
 
         this->accept( clamper );
@@ -756,11 +836,12 @@ ImageOverlay::clamp(const Terrain* terrain, osg::Node* patch)
 
 void
 ImageOverlay::onTileAdded(const TileKey&          key, 
-                          osg::Node*              tile, 
+                          osg::Node*              graph, 
                           TerrainCallbackContext& context)
 {
-    if ( tile == 0L || _boundingPolytope.contains(tile->getBound()) )
+    if ( graph == 0L || !key.valid() || _boundingPolytope.contains(graph->getBound()) )
     {
-        clamp( context.getTerrain(), tile );
+        clamp( graph, context.getTerrain() );
     }
 }
+
diff --git a/src/osgEarthAnnotation/LabelNode b/src/osgEarthAnnotation/LabelNode
index 2058ffb..e7483f1 100644
--- a/src/osgEarthAnnotation/LabelNode
+++ b/src/osgEarthAnnotation/LabelNode
@@ -125,6 +125,10 @@ namespace osgEarth { namespace Annotation
 
         virtual void dirty();
 
+    protected: // AnnotationNode override
+        
+        virtual bool supportsRenderBinDetails() const { return false; }
+
     protected:
         void init(const Style& style);
 
diff --git a/src/osgEarthAnnotation/LabelNode.cpp b/src/osgEarthAnnotation/LabelNode.cpp
index 9615510..982e705 100644
--- a/src/osgEarthAnnotation/LabelNode.cpp
+++ b/src/osgEarthAnnotation/LabelNode.cpp
@@ -31,6 +31,7 @@
 #include <osgEarth/GeoMath>
 #include <osgEarth/Utils>
 #include <osgEarth/ScreenSpaceLayout>
+#include <osgEarth/Lighting>
 #include <osgText/Text>
 #include <osg/Depth>
 #include <osgUtil/IntersectionVisitor>
@@ -50,12 +51,13 @@ LabelNode::LabelNode(MapNode*            mapNode,
                      const std::string&  text,
                      const Style&        style ) :
 
-GeoPositionNode( mapNode, position ),
+GeoPositionNode( mapNode ),
 _text             ( text ),
 _labelRotationRad ( 0. ),
 _followFixedCourse( false )
 {
     init( style );
+    setPosition( position );
 }
 
 LabelNode::LabelNode(MapNode*            mapNode,
@@ -63,7 +65,7 @@ LabelNode::LabelNode(MapNode*            mapNode,
                      const std::string&  text,
                      const TextSymbol*   symbol ) :
 
-GeoPositionNode( mapNode, position ),
+GeoPositionNode( mapNode ),
 _text             ( text ),
 _labelRotationRad ( 0. ),
 _followFixedCourse( false )
@@ -71,6 +73,7 @@ _followFixedCourse( false )
     Style style;
     style.add( const_cast<TextSymbol*>(symbol) );
     init( style );
+    setPosition( position );
 }
 
 LabelNode::LabelNode(const std::string&  text,
@@ -86,11 +89,12 @@ _followFixedCourse( false )
 LabelNode::LabelNode(MapNode*            mapNode,
                      const GeoPoint&     position,
                      const Style&        style ) :
-GeoPositionNode   ( mapNode, position ),
+GeoPositionNode   ( mapNode ),
 _labelRotationRad ( 0. ),
 _followFixedCourse( false )
 {
     init( style );
+    setPosition( position );
 }
 
 LabelNode::LabelNode(MapNode*            mapNode,
@@ -113,7 +117,12 @@ _followFixedCourse(false)
 void
 LabelNode::init( const Style& style )
 {
-    ScreenSpaceLayout::activate( this->getOrCreateStateSet() );
+    osg::StateSet* ss = this->getOrCreateStateSet();
+
+    ScreenSpaceLayout::activate(ss);
+    
+    // Disable lighting for place nodes by default
+    ss->setDefine(OE_LIGHTING_DEFINE, osg::StateAttribute::OFF);
 
     _geode = new osg::Geode();
 
@@ -138,21 +147,25 @@ LabelNode::setText( const std::string& text )
         return;
     }
 
-    osgText::Text* d = dynamic_cast<osgText::Text*>(_geode->getDrawable(0));
-    if ( d )
+    for (unsigned int i=0; i < _geode->getNumDrawables(); i++)
     {
-        const TextSymbol* symbol = _style.get<TextSymbol>();
-
-        osgText::String::Encoding textEncoding = osgText::String::ENCODING_UNDEFINED;
-        if (symbol && symbol->encoding().isSet())
+        osgText::Text* d = dynamic_cast<osgText::Text*>(_geode->getDrawable(i));
+        if ( d )
         {
-            textEncoding = AnnotationUtils::convertTextSymbolEncoding(symbol->encoding().value());
-        }
+            const TextSymbol* symbol = _style.get<TextSymbol>();
+
+            osgText::String::Encoding textEncoding = osgText::String::ENCODING_UNDEFINED;
+            if (symbol && symbol->encoding().isSet())
+            {
+                textEncoding = AnnotationUtils::convertTextSymbolEncoding(symbol->encoding().value());
+            }
 
-        d->setText(text, textEncoding);
+            d->setText(text, textEncoding);
 
-        d->dirtyDisplayList();
-        _text = text;
+            d->dirtyDisplayList();
+            _text = text;
+            return;
+        }
     }
 }
 
@@ -171,22 +184,25 @@ LabelNode::setStyle( const Style& style )
 
     const TextSymbol* symbol = _style.get<TextSymbol>();
 
-    if ( _text.empty() )
-        _text = symbol->content()->eval();
-
-    if ( symbol && symbol->onScreenRotation().isSet() )
+    if (symbol)
     {
-        _labelRotationRad = osg::DegreesToRadians(symbol->onScreenRotation()->eval());
-    }
+        if ( _text.empty() )
+            _text = symbol->content()->eval();
 
-    // In case of a label must follow a course on map, we project a point from the position
-    // with the given bearing. Then during culling phase we compute both points on the screen
-    // and then we can deduce the screen rotation
-    // may be optimized...
-    else if ( symbol && symbol->geographicCourse().isSet() )
-    {
-        _followFixedCourse = true;
-        _labelRotationRad = osg::DegreesToRadians ( symbol->geographicCourse()->eval() );
+        if ( symbol->onScreenRotation().isSet() )
+        {
+            _labelRotationRad = osg::DegreesToRadians(symbol->onScreenRotation()->eval());
+        }
+
+        // In case of a label must follow a course on map, we project a point from the position
+        // with the given bearing. Then during culling phase we compute both points on the screen
+        // and then we can deduce the screen rotation
+        // may be optimized...
+        else if ( symbol->geographicCourse().isSet() )
+        {
+            _followFixedCourse = true;
+            _labelRotationRad = osg::DegreesToRadians ( symbol->geographicCourse()->eval() );
+        }
     }
 
     osg::Drawable* text = AnnotationUtils::createTextDrawable( _text, symbol, osg::Vec3(0,0,0) );
@@ -314,6 +330,8 @@ _followFixedCourse( false )
     conf.getIfSet   ( "text",  _text );
 
     init( *style );
+
+    setPosition(getPosition());
 }
 
 Config
diff --git a/src/osgEarthAnnotation/LocalGeometryNode b/src/osgEarthAnnotation/LocalGeometryNode
index 60cb44e..3043d1d 100644
--- a/src/osgEarthAnnotation/LocalGeometryNode
+++ b/src/osgEarthAnnotation/LocalGeometryNode
@@ -88,7 +88,7 @@ namespace osgEarth { namespace Annotation
         /**
          * Gets the external node attached to this annotation (if applicable)
          */
-        osg::Node* getNode() { return _node; }
+        osg::Node* getNode() { return _node.get(); }
 
         /**
          * Sets an external node to attach to this annotation
@@ -97,6 +97,8 @@ namespace osgEarth { namespace Annotation
 
     public:
 
+        virtual void setLocalOffset(const osg::Vec3f& pos);
+
         virtual void setMapNode(MapNode*);
 
     public:
@@ -113,7 +115,9 @@ namespace osgEarth { namespace Annotation
 
     public: // osg::Node
 
-        virtual osg::BoundingSphere computeBound() const;
+        //virtual osg::BoundingSphere computeBound() const;
+
+        virtual void traverse(osg::NodeVisitor&);
 
     protected: // GeoPositionNode
 
@@ -125,11 +129,12 @@ namespace osgEarth { namespace Annotation
         Style                        _style;
         osg::ref_ptr<osg::Node>      _node;
         osg::ref_ptr<Geometry>       _geom;
+        bool                         _clampDirty;
         
         typedef TerrainCallbackAdapter<LocalGeometryNode> ClampCallback;
         osg::ref_ptr<ClampCallback> _clampCallback;
         bool _clampRelative;
-        mutable osg::Polytope _boundingPT;
+        //mutable osg::Polytope _boundingPT;
 
         void initNode();
         void initGeometry(const osgDB::Options*);
@@ -140,11 +145,11 @@ namespace osgEarth { namespace Annotation
     public:
         void onTileAdded(
             const TileKey&          key, 
-            osg::Node*              tile, 
+            osg::Node*              graph, 
             TerrainCallbackContext& context);
 
-        virtual void clampToScene(
-            osg::Node*     tile,
+        virtual void clamp(
+            osg::Node*     graph,
             const Terrain* terrain);
     };
 
diff --git a/src/osgEarthAnnotation/LocalGeometryNode.cpp b/src/osgEarthAnnotation/LocalGeometryNode.cpp
index d5bbe17..b874a20 100644
--- a/src/osgEarthAnnotation/LocalGeometryNode.cpp
+++ b/src/osgEarthAnnotation/LocalGeometryNode.cpp
@@ -25,8 +25,10 @@
 #include <osgEarthAnnotation/AnnotationUtils>
 #include <osgEarthFeatures/GeometryCompiler>
 #include <osgEarthFeatures/GeometryUtils>
+#include <osgEarthFeatures/FilterContext>
 #include <osgEarth/GeometryClamper>
 #include <osgEarth/Utils>
+#include <osgEarth/NodeUtils>
 
 #define LC "[GeometryNode] "
 
@@ -37,14 +39,16 @@ using namespace osgEarth::Features;
 
 LocalGeometryNode::LocalGeometryNode() :
 GeoPositionNode(),
-_clampRelative(false)
+_clampRelative(false),
+_clampDirty(false)
 {
     //nop - unused
 }
 
 LocalGeometryNode::LocalGeometryNode(MapNode* mapNode) :
 GeoPositionNode(),
-_clampRelative(false)
+_clampRelative(false),
+_clampDirty(false)
 {
     LocalGeometryNode::setMapNode( mapNode );
     init( 0L );
@@ -56,7 +60,8 @@ LocalGeometryNode::LocalGeometryNode(MapNode*     mapNode,
 GeoPositionNode(),
 _geom    ( geom ),
 _style   ( style ),
-_clampRelative(false)
+_clampRelative(false),
+_clampDirty(false)
 {
     LocalGeometryNode::setMapNode( mapNode );
     init( 0L );
@@ -69,13 +74,21 @@ LocalGeometryNode::LocalGeometryNode(MapNode*     mapNode,
 GeoPositionNode(),
 _node    ( node ),
 _style   ( style ),
-_clampRelative(false)
+_clampRelative(false),
+_clampDirty(false)
 {
     LocalGeometryNode::setMapNode( mapNode );
     init( 0L );
 }
 
 void
+LocalGeometryNode::setLocalOffset(const osg::Vec3f& pos)
+{
+    GeoPositionNode::setLocalOffset(pos);
+    dirty();
+}
+
+void
 LocalGeometryNode::setMapNode(MapNode* mapNode)
 {
     if ( mapNode != getMapNode() )
@@ -115,7 +128,7 @@ LocalGeometryNode::initGeometry(const osgDB::Options* dbOptions)
             session = new Session(getMapNode()->getMap(), 0L, 0L, dbOptions);
         
         GeometryCompiler gc;
-        osg::ref_ptr<osg::Node> node = gc.compile( _geom.get(), getStyle(), FilterContext(session) );
+        osg::ref_ptr<osg::Node> node = gc.compile( _geom.get(), getStyle(), FilterContext(session.get()) );
         if ( node.valid() )
         {
             node = AnnotationUtils::installOverlayParent( node.get(), getStyle() );
@@ -213,35 +226,81 @@ LocalGeometryNode::applyAltitudeSymbology(const Style& style)
                 }
             }
         }
+        dirty();
     }
 }
 
 void
 LocalGeometryNode::onTileAdded(const TileKey&          key, 
-                               osg::Node*              patch, 
+                               osg::Node*              graph, 
                                TerrainCallbackContext& context)
 {
-    // if key and data intersect then
-    if ( _boundingPT.contains(patch->getBound()) )
-    {    
-        clampToScene( patch, context.getTerrain() );
-        this->dirtyBound();
+    // If we are already set to clamp, ignore this
+    if (_clampDirty)
+        return;
+
+    bool needsClamp;
+
+    // This was faster, but less precise and resulted in a lot of unnecessary clamp attempts:
+    //if ( _boundingPT.contains(patch->getBound()) )
+
+    // Does the tile key's polytope intersect the world bounds or this object?
+    // (taking getParent(0) gives the world-tranformed bounds vs. local bounds)
+    if (key.valid())
+    {
+        osg::Polytope tope;
+        key.getExtent().createPolytope(tope);
+        needsClamp = tope.contains(this->getParent(0)->getBound());
+    }
+    else
+    {
+        // with no key, must clamp no matter what
+        needsClamp = true;
+    }
+
+    if (needsClamp)
+    {
+        //clamp(graph, context.getTerrain());
+        _clampDirty = true;
+        ADJUST_UPDATE_TRAV_COUNT(this, +1);
+        OE_DEBUG << LC << "LGN: clamp requested b/c of key " << key.str() << std::endl;
     }
 }
 
 void
-LocalGeometryNode::clampToScene(osg::Node* patch, const Terrain* terrain)
+LocalGeometryNode::clamp(osg::Node* graph, const Terrain* terrain)
 {
-    GeometryClamper clamper;
+    if (terrain && graph)
+    {
+        GeometryClamper clamper;
+
+        clamper.setTerrainPatch( graph );
+        clamper.setTerrainSRS( terrain ? terrain->getSRS() : 0L );
+        clamper.setPreserveZ( _clampRelative );
+        //clamper.setOffset( getPosition().alt() );
+
+        this->accept( clamper );
+
+        OE_DEBUG << LC << "LGN: clamped.\n";
+    }
+}
 
-    clamper.setTerrainPatch( patch );
-    clamper.setTerrainSRS( terrain ? terrain->getSRS() : 0L );
-    clamper.setPreserveZ( _clampRelative );
-    clamper.setOffset( getPosition().alt() );
+void
+LocalGeometryNode::traverse(osg::NodeVisitor& nv)
+{
+    if (nv.getVisitorType() == nv.UPDATE_VISITOR && _clampDirty)
+    {
+        osg::ref_ptr<Terrain> terrain = getGeoTransform()->getTerrain();
+        if (terrain.valid())
+            clamp(terrain->getGraph(), terrain.get());
 
-    this->accept( clamper );
+        ADJUST_UPDATE_TRAV_COUNT(this, -1);
+        _clampDirty = false;
+    }
+    GeoPositionNode::traverse(nv);
 }
 
+#if 0
 osg::BoundingSphere
 LocalGeometryNode::computeBound() const
 {
@@ -288,6 +347,7 @@ LocalGeometryNode::computeBound() const
 
     return bs;
 }
+#endif
 
 void
 LocalGeometryNode::dirty()
@@ -297,7 +357,7 @@ LocalGeometryNode::dirty()
     // re-clamp the geometry if necessary.
     if ( _clampCallback.valid() && getMapNode() )
     {
-        clampToScene( getMapNode()->getTerrain()->getGraph(), getMapNode()->getTerrain() );
+        clamp( getMapNode()->getTerrain()->getGraph(), getMapNode()->getTerrain() );
     }
 }
 
@@ -310,7 +370,8 @@ LocalGeometryNode::LocalGeometryNode(MapNode*              mapNode,
                                      const Config&         conf,
                                      const osgDB::Options* dbOptions) :
 GeoPositionNode( mapNode, conf ),
-_clampRelative(false)
+_clampRelative(false),
+_clampDirty(false)
 {
     if ( conf.hasChild("geometry") )
     {
diff --git a/src/osgEarthAnnotation/ModelNode.cpp b/src/osgEarthAnnotation/ModelNode.cpp
index 8e00a4a..244a24b 100644
--- a/src/osgEarthAnnotation/ModelNode.cpp
+++ b/src/osgEarthAnnotation/ModelNode.cpp
@@ -26,11 +26,11 @@
 #include <osgEarthAnnotation/GeoPositionNodeAutoScaler>
 #include <osgEarthSymbology/Style>
 #include <osgEarthSymbology/InstanceSymbol>
-#include <osgEarth/AutoScale>
 #include <osgEarth/Registry>
 #include <osgEarth/Capabilities>
 #include <osgEarth/ShaderGenerator>
 #include <osgEarth/VirtualProgram>
+#include <osgEarth/NodeUtils>
 
 #define LC "[ModelNode] "
 
@@ -45,7 +45,7 @@ using namespace osgEarth::Symbology;
 ModelNode::ModelNode(MapNode*              mapNode,
                      const Style&          style,
                      const osgDB::Options* dbOptions ) :
-GeoPositionNode    ( mapNode, GeoPoint() ),
+GeoPositionNode( mapNode ),
 _style       ( style ),
 _dbOptions   ( dbOptions )
 {
@@ -58,6 +58,7 @@ ModelNode::setStyle(const Style& style)
 {
     _style = style;
     init();
+    setPosition(getPosition());
 }
 
 void
@@ -142,6 +143,7 @@ ModelNode::init()
                 // auto scaling?
                 if ( sym->autoScale() == true )
                 {
+                    this->setCullingActive(false);
                     this->addCullCallback( new GeoPositionNodeAutoScaler( osg::Vec3d(1,1,1), sym->minAutoScale().value(), sym->maxAutoScale().value() ));
                 } 
 
@@ -194,6 +196,7 @@ _dbOptions   ( dbOptions )
         _style.getOrCreate<ModelSymbol>()->url() = StringExpression(uri);
 
     init();
+    setPosition(getPosition());
 }
 
 Config
diff --git a/src/osgEarthAnnotation/PlaceNode b/src/osgEarthAnnotation/PlaceNode
index 016d450..bf434a8 100644
--- a/src/osgEarthAnnotation/PlaceNode
+++ b/src/osgEarthAnnotation/PlaceNode
@@ -120,6 +120,10 @@ namespace osgEarth { namespace Annotation
 
         virtual void dirty();
 
+    protected: // AnnotationNode override
+        
+        virtual bool supportsRenderBinDetails() const { return false; }
+
     protected:
 
         virtual ~PlaceNode() { }
diff --git a/src/osgEarthAnnotation/PlaceNode.cpp b/src/osgEarthAnnotation/PlaceNode.cpp
index ee4c5c1..a4f32dc 100644
--- a/src/osgEarthAnnotation/PlaceNode.cpp
+++ b/src/osgEarthAnnotation/PlaceNode.cpp
@@ -31,6 +31,8 @@
 #include <osgEarth/ShaderGenerator>
 #include <osgEarth/GeoMath>
 #include <osgEarth/ScreenSpaceLayout>
+#include <osgEarth/NodeUtils>
+#include <osgEarth/Lighting>
 
 #include <osg/Depth>
 #include <osgText/Text>
@@ -43,6 +45,7 @@ using namespace osgEarth::Features;
 using namespace osgEarth::Symbology;
 
 PlaceNode::PlaceNode() :
+_geode            ( 0L ),
 _labelRotationRad ( 0. ),
 _followFixedCourse( false )
 {
@@ -55,7 +58,7 @@ PlaceNode::PlaceNode(MapNode*           mapNode,
                      const std::string& text,
                      const Style&       style ) :
 
-GeoPositionNode( mapNode, position ),
+GeoPositionNode( mapNode ),
 _image   ( image ),
 _text    ( text ),
 _style   ( style ),
@@ -64,6 +67,7 @@ _labelRotationRad ( 0. ),
 _followFixedCourse( false )
 {
     init();
+    setPosition(position);
 }
 
 PlaceNode::PlaceNode(MapNode*           mapNode,
@@ -71,7 +75,7 @@ PlaceNode::PlaceNode(MapNode*           mapNode,
                      const std::string& text,
                      const Style&       style ) :
 
-GeoPositionNode( mapNode, position ),
+GeoPositionNode( mapNode ),
 _text    ( text ),
 _style   ( style ),
 _geode            ( 0L ),
@@ -79,25 +83,34 @@ _labelRotationRad ( 0. ),
 _followFixedCourse( false )
 {
     init();
+    setPosition(position);
 }
 
 PlaceNode::PlaceNode(MapNode*              mapNode,
                      const GeoPoint&       position,
                      const Style&          style,
                      const osgDB::Options* dbOptions ) :
-GeoPositionNode ( mapNode, position ),
+GeoPositionNode ( mapNode ),
 _style    ( style ),
 _dbOptions        ( dbOptions ),
+_geode            ( 0L ),
 _labelRotationRad ( 0. ),
 _followFixedCourse( false )
 {
     init();
+    setPosition(position);
 }
 
 void
 PlaceNode::init()
 {
-    ScreenSpaceLayout::activate( this->getOrCreateStateSet() );
+    osg::StateSet* ss = this->getOrCreateStateSet();
+
+    // Draw place nodes in screen space.
+    ScreenSpaceLayout::activate(ss);
+
+    // Disable lighting for place nodes by default
+    ss->setDefine(OE_LIGHTING_DEFINE, osg::StateAttribute::OFF);
 
     osgEarth::clearChildren( getPositionAttitudeTransform() );
 
@@ -361,7 +374,7 @@ PlaceNode::updateLayoutData()
 void
 PlaceNode::setText( const std::string& text )
 {
-    if ( !_dynamic )
+    if ( !_dynamic && !_geode )
     {
         OE_WARN << LC << "Illegal state: cannot change a LabelNode that is not dynamic" << std::endl;
         return;
@@ -369,20 +382,23 @@ PlaceNode::setText( const std::string& text )
 
     _text = text;
 
-    for(unsigned i=0; i<_geode->getNumDrawables(); ++i)
+    if (_geode)
     {
-        osgText::Text* d = dynamic_cast<osgText::Text*>( _geode->getDrawable(i) );
-        if ( d )
+        for(unsigned i=0; i<_geode->getNumDrawables(); ++i)
         {
-			TextSymbol* symbol =  _style.getOrCreate<TextSymbol>();
-			osgText::String::Encoding text_encoding = osgText::String::ENCODING_UNDEFINED;
-			if ( symbol && symbol->encoding().isSet() )
-			{
-				text_encoding = AnnotationUtils::convertTextSymbolEncoding(symbol->encoding().value());
-			}
-
-            d->setText( text, text_encoding );
-            break;
+            osgText::Text* d = dynamic_cast<osgText::Text*>( _geode->getDrawable(i) );
+            if ( d )
+            {
+			    TextSymbol* symbol =  _style.getOrCreate<TextSymbol>();
+			    osgText::String::Encoding text_encoding = osgText::String::ENCODING_UNDEFINED;
+			    if ( symbol && symbol->encoding().isSet() )
+			    {
+				    text_encoding = AnnotationUtils::convertTextSymbolEncoding(symbol->encoding().value());
+			    }
+
+                d->setText( text, text_encoding );
+                break;
+            }
         }
     }
 }
@@ -411,10 +427,13 @@ PlaceNode::setDynamic( bool value )
 {
     GeoPositionNode::setDynamic( value );
     
-    for(unsigned i=0; i<_geode->getNumDrawables(); ++i)
+    if (_geode)
     {
-        _geode->getDrawable(i)->setDataVariance( 
-            value ? osg::Object::DYNAMIC : osg::Object::STATIC );
+        for(unsigned i=0; i<_geode->getNumDrawables(); ++i)
+        {
+            _geode->getDrawable(i)->setDataVariance( 
+                value ? osg::Object::DYNAMIC : osg::Object::STATIC );
+        }
     }
 }
 
@@ -444,6 +463,7 @@ _followFixedCourse( false )
     }
 
     init();
+    setPosition(getPosition());
 }
 
 void
diff --git a/src/osgEarthAnnotation/RectangleNode.cpp b/src/osgEarthAnnotation/RectangleNode.cpp
index db095aa..63f823b 100644
--- a/src/osgEarthAnnotation/RectangleNode.cpp
+++ b/src/osgEarthAnnotation/RectangleNode.cpp
@@ -24,10 +24,12 @@
 #include <osgEarthAnnotation/AnnotationRegistry>
 #include <osgEarthAnnotation/AnnotationUtils>
 #include <osgEarthFeatures/GeometryCompiler>
+#include <osgEarthFeatures/FilterContext>
 #include <osgEarthSymbology/GeometryFactory>
 #include <osgEarthSymbology/ExtrusionSymbol>
 #include <osgEarth/MapNode>
 #include <osgEarth/DrapeableNode>
+#include <osgEarth/NodeUtils>
 #include <osg/MatrixTransform>
 
 using namespace osgEarth;
@@ -342,7 +344,7 @@ RectangleNode::rebuild()
     if ( geom )
     {
         GeometryCompiler compiler;
-        osg::ref_ptr<osg::Node> node = compiler.compile( geom, _style, FilterContext(0L) );
+        osg::ref_ptr<osg::Node> node = compiler.compile( geom, _style, FilterContext() );
         if ( node )
         {
             node = AnnotationUtils::installOverlayParent( node.get(), _style );
diff --git a/src/osgEarthAnnotation/ScaleDecoration b/src/osgEarthAnnotation/ScaleDecoration
deleted file mode 100644
index 839169f..0000000
--- a/src/osgEarthAnnotation/ScaleDecoration
+++ /dev/null
@@ -1,54 +0,0 @@
-/* -*-c++-*- */
-/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2016 Pelican Mapping
-* http://osgearth.org
-*
-* osgEarth is free software; you can redistribute it and/or modify
-* it under the terms of the GNU Lesser General Public License as published by
-* the Free Software Foundation; either version 2 of the License, or
-* (at your option) any later version.
-*
-* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
-* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
-* IN THE SOFTWARE.
-*
-* You should have received a copy of the GNU Lesser General Public License
-* along with this program.  If not, see <http://www.gnu.org/licenses/>
-*/
-#ifndef OSGEARTH_ANNOTATION_SCALE_DECORATION_H
-#define OSGEARTH_ANNOTATION_SCALE_DECORATION_H 1
-
-#include <osgEarthAnnotation/Decoration>
-#include <osg/NodeVisitor>
-
-namespace osgEarth { namespace Annotation
-{	
-    using namespace osgEarth;
-
-#if 0
-    /**
-     * A decoration technique that scales the annotation.
-     */
-    class /*OSGEARTHANNO_EXPORT*/ ScaleDecoration : public InjectionDecoration
-    {
-    public:
-        ScaleDecoration(float factor =1.1f) :
-            InjectionDecoration(new osg::MatrixTransform(osg::Matrix::scale(factor,factor,factor))),
-            _factor(factor) { }
-
-        virtual ~ScaleDecoration() { }
-
-        virtual Decoration* clone() const { return new ScaleDecoration(_factor); }
-
-    protected:
-        float _factor;
-    };
-#endif
-
-} } // namespace osgEarth::Annotation
-
-#endif //OSGEARTH_ANNOTATION_DECORATION_H
diff --git a/src/osgEarthAnnotation/TrackNode b/src/osgEarthAnnotation/TrackNode
index a40f6fd..2573253 100644
--- a/src/osgEarthAnnotation/TrackNode
+++ b/src/osgEarthAnnotation/TrackNode
@@ -151,6 +151,10 @@ namespace osgEarth { namespace Annotation
 
         void init( const TrackNodeFieldSchema& schema );
 
+    protected: // AnnotationNode override
+        
+        virtual bool supportsRenderBinDetails() const { return false; }
+
     private:
         // required by META_Node, but this object is not cloneable
         TrackNode() { }
diff --git a/src/osgEarthAnnotation/TrackNode.cpp b/src/osgEarthAnnotation/TrackNode.cpp
index 3a021c9..6814c09 100644
--- a/src/osgEarthAnnotation/TrackNode.cpp
+++ b/src/osgEarthAnnotation/TrackNode.cpp
@@ -26,6 +26,8 @@
 #include <osgEarth/Registry>
 #include <osgEarth/ShaderGenerator>
 #include <osgEarth/ScreenSpaceLayout>
+#include <osgEarth/NodeUtils>
+#include <osgEarth/Lighting>
 #include <osg/Depth>
 #include <osgText/Text>
 
@@ -67,8 +69,13 @@ _style      ( style )
 void
 TrackNode::init( const TrackNodeFieldSchema& schema )
 {
-    // tracknodes draw in screen space at their geoposition.
-    ScreenSpaceLayout::activate( this->getOrCreateStateSet() );
+    osg::StateSet* ss = this->getOrCreateStateSet();
+
+    ScreenSpaceLayout::activate(ss);
+    
+    // Disable lighting for place nodes by default
+    ss->setDefine(OE_LIGHTING_DEFINE, osg::StateAttribute::OFF);
+
 
     osgEarth::clearChildren( getPositionAttitudeTransform() );
 
diff --git a/src/osgEarthDrivers/CMakeLists.txt b/src/osgEarthDrivers/CMakeLists.txt
index 85fa291..2337f7c 100644
--- a/src/osgEarthDrivers/CMakeLists.txt
+++ b/src/osgEarthDrivers/CMakeLists.txt
@@ -27,15 +27,56 @@ SET(TARGET_COMMON_LIBRARIES
 # Folder name for plugins
 SET(OSGEARTH_PLUGINS_FOLDER Plugins)
 
-############################################################
-#
-#  NodeKit/Psudo loader plugins
-#
+add_subdirectory(agglite)
+add_subdirectory(arcgis)
+add_subdirectory(bing)
+add_subdirectory(bumpmap)
+add_subdirectory(cache_filesystem)
+add_subdirectory(cache_leveldb)
+add_subdirectory(cache_rocksdb)
+add_subdirectory(colorramp)
+add_subdirectory(debug)
+add_subdirectory(detail)
+add_subdirectory(earth)
+add_subdirectory(engine_mp)
+add_subdirectory(engine_rex)
+add_subdirectory(fastdxt)
+add_subdirectory(featurefilter_intersect)
+add_subdirectory(featurefilter_join)
+add_subdirectory(feature_elevation)
+add_subdirectory(feature_mapnikvectortiles)
+add_subdirectory(feature_ogr)
+add_subdirectory(feature_raster)
+add_subdirectory(feature_tfs)
+add_subdirectory(feature_wfs)
+add_subdirectory(feature_xyz)
+add_subdirectory(gdal)
+add_subdirectory(kml)
+add_subdirectory(label_annotation)
+add_subdirectory(mapinspector)
+add_subdirectory(mask_feature)
+add_subdirectory(mbtiles)
+add_subdirectory(model_feature_geom)
+add_subdirectory(model_simple)
+add_subdirectory(monitor)
+add_subdirectory(ocean_simple)
+add_subdirectory(ocean_triton)
+add_subdirectory(osg)
+add_subdirectory(script_engine_duktape)
+add_subdirectory(skyview)
+add_subdirectory(sky_gl)
+add_subdirectory(sky_silverlining)
+add_subdirectory(sky_simple)
+add_subdirectory(template)
+add_subdirectory(terrainshader)
+add_subdirectory(tileindex)
+add_subdirectory(tms)
+add_subdirectory(vdatum_egm2008)
+add_subdirectory(vdatum_egm84)
+add_subdirectory(vdatum_egm96)
+add_subdirectory(viewpoints)
+add_subdirectory(vpb)
+add_subdirectory(wcs)
+add_subdirectory(wms)
+add_subdirectory(xyz)
 
-
-SUBDIRLIST(PLUGIN_DIRS ${CMAKE_CURRENT_SOURCE_DIR})
-
-FOREACH(subdir ${PLUGIN_DIRS})
-    # MESSAGE("Adding driver ${subdir}")
-    ADD_SUBDIRECTORY(${subdir})
-ENDFOREACH()
diff --git a/src/osgEarthDrivers/agglite/AGGLiteOptions b/src/osgEarthDrivers/agglite/AGGLiteOptions
index 882d053..89d6269 100644
--- a/src/osgEarthDrivers/agglite/AGGLiteOptions
+++ b/src/osgEarthDrivers/agglite/AGGLiteOptions
@@ -62,8 +62,8 @@ namespace osgEarth { namespace Drivers
     public:
         Config getConfig() const {
             Config conf = FeatureTileSourceOptions::getConfig();
-            conf.updateIfSet("optimize_line_sampling", _optimizeLineSampling);
-            conf.updateIfSet("gamma", _gamma );
+            conf.set("optimize_line_sampling", _optimizeLineSampling);
+            conf.set("gamma", _gamma );
             return conf;
         }
 
diff --git a/src/osgEarthDrivers/agglite/AGGLiteRasterizerTileSource.cpp b/src/osgEarthDrivers/agglite/AGGLiteRasterizerTileSource.cpp
index 7566849..f366afc 100644
--- a/src/osgEarthDrivers/agglite/AGGLiteRasterizerTileSource.cpp
+++ b/src/osgEarthDrivers/agglite/AGGLiteRasterizerTileSource.cpp
@@ -21,6 +21,7 @@
 #include <osgEarthFeatures/ResampleFilter>
 #include <osgEarthFeatures/TransformFilter>
 #include <osgEarthFeatures/BufferFilter>
+#include <osgEarthFeatures/FilterContext>
 #include <osgEarthSymbology/Style>
 //TODO: replace this with GeometryRasterizer
 #include <osgEarthSymbology/AGG.h>
@@ -139,7 +140,6 @@ public:
         // clear the buffer.
         if ( _options.coverage() == true )
         {
-            // For coverage data, FLT_MAX = no data.
             agg::renderer<span_coverage32, float32> ren(rbuf);
             ren.clear( float32(NO_DATA_VALUE) );
         }
@@ -322,12 +322,31 @@ public:
         // extend just outside the actual extents so we don't get edge artifacts:
         GeoExtent cropExtent = GeoExtent(imageExtent);
         cropExtent.scale(1.1, 1.1);
+        double cropXMin, cropYMin, cropXMax, cropYMax;
+        cropExtent.getBounds(cropXMin, cropYMin, cropXMax, cropYMax);
+
+        // GEOS crop won't abide by weird extents, so if we're in geographic space
+        // we must clamp the scaled extent back to a legal range.
+        if (cropExtent.crossesAntimeridian())
+        {
+            osg::Vec3d centroid = imageExtent.getCentroid();
+            if (centroid.x() < 0.0) // tile is east of antimeridian
+            {
+                cropXMin = -180.0;
+                cropXMax = cropExtent.east();
+            }
+            else
+            {
+                cropXMin = cropExtent.west();
+                cropXMax = 180.0;
+            }
+        }
 
         osg::ref_ptr<Symbology::Polygon> cropPoly = new Symbology::Polygon( 4 );
-        cropPoly->push_back( osg::Vec3d( cropExtent.xMin(), cropExtent.yMin(), 0 ));
-        cropPoly->push_back( osg::Vec3d( cropExtent.xMax(), cropExtent.yMin(), 0 ));
-        cropPoly->push_back( osg::Vec3d( cropExtent.xMax(), cropExtent.yMax(), 0 ));
-        cropPoly->push_back( osg::Vec3d( cropExtent.xMin(), cropExtent.yMax(), 0 ));
+        cropPoly->push_back( osg::Vec3d(cropXMin, cropYMin, 0) );
+        cropPoly->push_back( osg::Vec3d(cropXMax, cropYMin, 0) );
+        cropPoly->push_back( osg::Vec3d(cropXMax, cropYMax, 0) );
+        cropPoly->push_back( osg::Vec3d(cropXMin, cropYMax, 0) );
 
         // If there's a coverage symbol, make a copy of the expressions so we can evaluate them
         optional<NumericExpression> covValue;
diff --git a/src/osgEarthDrivers/arcgis/ArcGISOptions b/src/osgEarthDrivers/arcgis/ArcGISOptions
index d407319..bf36656 100644
--- a/src/osgEarthDrivers/arcgis/ArcGISOptions
+++ b/src/osgEarthDrivers/arcgis/ArcGISOptions
@@ -59,10 +59,10 @@ namespace osgEarth { namespace Drivers
     public:
         Config getConfig() const {
             Config conf = TileSourceOptions::getConfig();
-            conf.updateIfSet("url", _url );
-            conf.updateIfSet("token", _token );
-            conf.updateIfSet("format", _format );
-            conf.updateIfSet("layers", _layers );
+            conf.set("url", _url );
+            conf.set("token", _token );
+            conf.set("format", _format );
+            conf.set("layers", _layers );
             return conf;
         }
 
diff --git a/src/osgEarthDrivers/arcgis_map_cache/CMakeLists.txt b/src/osgEarthDrivers/arcgis_map_cache/CMakeLists.txt
deleted file mode 100644
index 6f08883..0000000
--- a/src/osgEarthDrivers/arcgis_map_cache/CMakeLists.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-SET(TARGET_SRC ReaderWriterArcGISMapCache.cpp)
-SETUP_PLUGIN(osgearth_arcgis_map_cache)
-
diff --git a/src/osgEarthDrivers/arcgis_map_cache/ReaderWriterArcGISMapCache.cpp b/src/osgEarthDrivers/arcgis_map_cache/ReaderWriterArcGISMapCache.cpp
deleted file mode 100644
index 696ee6b..0000000
--- a/src/osgEarthDrivers/arcgis_map_cache/ReaderWriterArcGISMapCache.cpp
+++ /dev/null
@@ -1,147 +0,0 @@
-/* -*-c++-*- */
-/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2016 Pelican Mapping
- * http://osgearth.org
- *
- * osgEarth is free software; you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>
- */
-
-#include <osgEarth/TileSource>
-#include <osgEarth/Registry>
-#include <osgEarth/URI>
-
-#include <osg/Notify>
-#include <osgDB/FileNameUtils>
-#include <osgDB/FileUtils>
-#include <osgDB/Registry>
-#include <osgDB/ReadFile>
-#include <osgDB/WriteFile>
-
-#include <sstream>
-#include <iomanip>
-
-using namespace osgEarth;
-
-#define PROPERTY_URL        "url"
-#define PROPERTY_MAP        "map"
-#define PROPERTY_LAYER      "layer"
-#define PROPERTY_FORMAT     "format"
-
-class AGSMapCacheSource : public TileSource
-{
-public:
-    AGSMapCacheSource( const TileSourceOptions& options ) :
-      TileSource( options )
-    {
-        const Config& conf = options.getConfig();
-
-        // this is the AGS virtual directory pointing to the map cache
-        _url = conf.value( PROPERTY_URL );
-
-        // the name of the map service cache
-        _map = conf.value( PROPERTY_MAP );
-
-        // the layer, or null to use the fused "_alllayers" cache
-        _layer = conf.value( PROPERTY_LAYER );
-
-        // the image format (defaults to "png")
-        // TODO: read this from the XML tile schema file
-        _format = conf.value( PROPERTY_FORMAT );
-
-        // validate dataset
-        if ( _layer.empty() )
-            _layer = "_alllayers"; // default to the AGS "fused view"
-
-        if ( _format.empty() )
-            _format = "png";
-    }
-
-    Status initialize( const osgDB::Options* dbOptions )
-    {
-        _dbOptions = Registry::instance()->cloneOrCreateOptions( dbOptions );        
-
-        //Set the profile to global geodetic.
-        setProfile(osgEarth::Registry::instance()->getGlobalGeodeticProfile());
-
-        return STATUS_OK;
-    }
-
-    // override
-    osg::Image* createImage( const TileKey& key, ProgressCallback* progress)
-    {
-        std::stringstream buf;
-
-        int level = key.getLevelOfDetail()-1;
-
-        unsigned int tile_x, tile_y;
-        key.getTileXY( tile_x, tile_y );
-
-        std::string bufStr = Stringify()
-            << _url << "/" << _map 
-            << "/Layers/" << _layer
-            << "/L" << std::hex << std::setw(2) << std::setfill('0') << level
-            << "/R" << std::hex << std::setw(8) << std::setfill('0') << tile_y
-            << "/C" << std::hex << std::setw(8) << std::setfill('0') << tile_x << "." << _format;
-        
-        osg::ref_ptr<osg::Image> image;
-
-        return URI(bufStr).getImage( _dbOptions.get(), progress );
-    }
-
-    // override
-    osg::HeightField* createHeightField( const TileKey& key, ProgressCallback* progress)
-    {
-        //TODO
-        return NULL;
-    }
-
-    // override
-    virtual std::string getExtension()  const 
-    {
-        return _format;
-    }
-
-private:
-    std::string _url;
-    std::string _map;
-    std::string _layer;
-    std::string _format;
-    osg::ref_ptr<osgDB::Options> _dbOptions;
-};
-
-
-class ReaderWriterAGSMapCache : public TileSourceDriver
-{
-    public:
-        ReaderWriterAGSMapCache()
-        {
-            supportsExtension( "osgearth_arcgis_map_cache", "ArcGIS Server Map Service Cache" );
-        }
-
-        virtual const char* className() const
-        {
-            return "ArcGIS Server Map Service Cache Imagery ReaderWriter";
-        }
-
-        virtual ReadResult readObject(const std::string& file_name, const Options* options) const
-        {
-            if ( !acceptsExtension(osgDB::getLowerCaseFileExtension( file_name )))
-                return ReadResult::FILE_NOT_HANDLED;
-
-            return new AGSMapCacheSource( getTileSourceOptions(options) );
-        }
-};
-
-REGISTER_OSGPLUGIN(osgearth_arcgis_map_cache, ReaderWriterAGSMapCache)
-
diff --git a/src/osgEarthDrivers/bing/BingOptions b/src/osgEarthDrivers/bing/BingOptions
index 83fcea5..b6821fa 100644
--- a/src/osgEarthDrivers/bing/BingOptions
+++ b/src/osgEarthDrivers/bing/BingOptions
@@ -84,9 +84,9 @@ namespace osgEarth { namespace Drivers
     public:
         Config getConfig() const {
             Config conf = TileSourceOptions::getConfig();
-            conf.updateIfSet("key",                  _apiKey);
-            conf.updateIfSet("imagery_set",          _imagerySet );
-            conf.updateIfSet("imagery_metadata_api", _imageryMetadataAPI );
+            conf.set("key",                  _apiKey);
+            conf.set("imagery_set",          _imagerySet );
+            conf.set("imagery_metadata_api", _imageryMetadataAPI );
             return conf;
         }
 
diff --git a/src/osgEarthDrivers/bing/BingTileSource.cpp b/src/osgEarthDrivers/bing/BingTileSource.cpp
index 95f9911..1e6899a 100644
--- a/src/osgEarthDrivers/bing/BingTileSource.cpp
+++ b/src/osgEarthDrivers/bing/BingTileSource.cpp
@@ -123,7 +123,7 @@ public:
      */
     osg::Image* createImage( const TileKey& key, ProgressCallback* progress )
     {
-        osg::Image* image = 0L;
+        osg::ref_ptr<osg::Image> image;
 
         if (_debugDirect)
         {
@@ -173,7 +173,7 @@ public:
                     OE_DEBUG << LC << "API calls = " << c << std::endl;
             
                 // fetch it:
-                ReadResult metadataResult = URI(request).readString(_readOptions, progress);
+                ReadResult metadataResult = URI(request).readString(_readOptions.get(), progress);
 
                 if ( metadataResult.failed() )
                 {
@@ -229,7 +229,7 @@ public:
 
             // request the actual tile
             //OE_INFO << "key = " << key.str() << ", URL = " << location->value() << std::endl;
-            image = osgDB::readImageFile( location.full() );
+            image = osgDB::readRefImageFile( location.full() );
         }
 
         if ( image &&  _geom.valid() )
@@ -238,10 +238,10 @@ public:
             rasterizer.draw( _geom.get(), osg::Vec4(1,1,1,1) );
             osg::ref_ptr<osg::Image> overlay = rasterizer.finalize();
             ImageUtils::PixelVisitor<AlphaBlend> blend;
-            blend.accept( overlay.get(), image );
+            blend.accept( overlay.get(), image.get() );
         }
 
-        return image;
+        return image.release();
     }
 
 private:
diff --git a/src/osgEarthDrivers/bumpmap/BumpMap.frag.common.glsl b/src/osgEarthDrivers/bumpmap/BumpMap.frag.common.glsl
index 797734f..342dfb7 100644
--- a/src/osgEarthDrivers/bumpmap/BumpMap.frag.common.glsl
+++ b/src/osgEarthDrivers/bumpmap/BumpMap.frag.common.glsl
@@ -8,7 +8,7 @@ in vec2 oe_normalMapCoords;
 
 float oe_bumpmap_getSlope()
 {
-    vec4 encodedNormal = texture2D(oe_nmap_normalTex, oe_normalMapCoords);
+    vec4 encodedNormal = texture(oe_nmap_normalTex, oe_normalMapCoords);
     vec3 normalTangent = normalize(encodedNormal.xyz*2.0-1.0);
     return clamp((1.0-normalTangent.z)/0.8, 0.0, 1.0);
 }
diff --git a/src/osgEarthDrivers/bumpmap/BumpMap.frag.progressive.glsl b/src/osgEarthDrivers/bumpmap/BumpMap.frag.progressive.glsl
index a3d2ee5..edfac1c 100644
--- a/src/osgEarthDrivers/bumpmap/BumpMap.frag.progressive.glsl
+++ b/src/osgEarthDrivers/bumpmap/BumpMap.frag.progressive.glsl
@@ -1,4 +1,5 @@
 #version $GLSL_VERSION_STR
+$GLSL_DEFAULT_PRECISION_FLOAT
 
 #pragma vp_entryPoint oe_bumpmap_fragment
 #pragma vp_location   fragment_coloring
@@ -6,6 +7,15 @@
 
 #pragma include BumpMap.frag.common.glsl
 
+#pragma import_defines(OE_IS_SHADOW_CAMERA, OE_IS_PICK_CAMERA)
+
+#if defined(OE_IS_SHADOW_CAMERA) || defined(OE_IS_PICK_CAMERA)
+
+//nop
+void oe_bumpmap_fragment(inout vec4 color) { }
+
+#else
+
 uniform sampler2D oe_bumpmap_tex;
 uniform float     oe_bumpmap_intensity;
 uniform int       oe_bumpmap_octaves;
@@ -42,7 +52,7 @@ void oe_bumpmap_fragment(inout vec4 color)
         float fadeIn = 1.0;
         if ( range <= limit && limit < oe_bumpmap_maxRange )
             fadeIn = clamp((lastRange-limit)/(lastRange-range), 0.0, 1.0);
-        bump += (texture2D(oe_bumpmap_tex, oe_bumpmap_coords*scale).xyz*2.0-1.0)*amplitude*fadeIn;
+        bump += (texture(oe_bumpmap_tex, oe_bumpmap_coords*scale).xyz*2.0-1.0)*amplitude*fadeIn;
         if ( range <= limit )
             break;
         lastRange = range;
@@ -58,3 +68,5 @@ void oe_bumpmap_fragment(inout vec4 color)
 	// permute the normal with the bump.
 	vp_Normal = normalize(vp_Normal + bump*oe_bumpmap_intensity*slope);
 }
+
+#endif
\ No newline at end of file
diff --git a/src/osgEarthDrivers/bumpmap/BumpMap.frag.simple.glsl b/src/osgEarthDrivers/bumpmap/BumpMap.frag.simple.glsl
index a160378..b29530b 100644
--- a/src/osgEarthDrivers/bumpmap/BumpMap.frag.simple.glsl
+++ b/src/osgEarthDrivers/bumpmap/BumpMap.frag.simple.glsl
@@ -1,4 +1,5 @@
 #version $GLSL_VERSION_STR
+$GLSL_DEFAULT_PRECISION_FLOAT
 
 #pragma vp_entryPoint oe_bumpmap_fragment
 #pragma vp_location   fragment_coloring
@@ -6,6 +7,15 @@
 
 #pragma include BumpMap.frag.common.glsl
 
+#pragma import_defines(OE_IS_SHADOW_CAMERA, OE_IS_PICK_CAMERA)
+
+#if defined(OE_IS_SHADOW_CAMERA) || defined(OE_IS_PICK_CAMERA)
+
+//nop
+void oe_bumpmap_fragment(inout vec4 color) { }
+
+#else
+
 in vec3 vp_Normal;
 in vec2 oe_bumpmap_coords;
 flat in mat3 oe_bumpmap_normalMatrix;
@@ -19,7 +29,7 @@ uniform float     oe_bumpmap_slopeFactor;
 void oe_bumpmap_fragment(inout vec4 color)
 {
 	// sample the bump map
-    vec3 bump = oe_bumpmap_normalMatrix * normalize(texture2D(oe_bumpmap_tex, oe_bumpmap_coords).xyz*2.0-1.0);
+    vec3 bump = oe_bumpmap_normalMatrix * normalize(texture(oe_bumpmap_tex, oe_bumpmap_coords).xyz*2.0-1.0);
     
     // calculate slope from normal:
     float slope = clamp( (1.0-dot(oe_UpVectorView, vp_Normal))*oe_bumpmap_slopeFactor, 0.0, 1.0);
@@ -27,3 +37,5 @@ void oe_bumpmap_fragment(inout vec4 color)
 	// permute the normal with the bump.
 	vp_Normal = normalize(vp_Normal + bump*oe_bumpmap_intensity*slope);
 }
+
+#endif
\ No newline at end of file
diff --git a/src/osgEarthDrivers/bumpmap/BumpMap.vert.view.glsl b/src/osgEarthDrivers/bumpmap/BumpMap.vert.view.glsl
index 0528203..8733cd3 100644
--- a/src/osgEarthDrivers/bumpmap/BumpMap.vert.view.glsl
+++ b/src/osgEarthDrivers/bumpmap/BumpMap.vert.view.glsl
@@ -1,8 +1,16 @@
 #version $GLSL_VERSION_STR
+$GLSL_DEFAULT_PRECISION_FLOAT
 
 #pragma vp_entryPoint oe_bumpmap_vertexView
 #pragma vp_location   vertex_view
 #pragma vp_order      0.5
+#pragma import_defines(OE_IS_SHADOW_CAMERA, OE_IS_PICK_CAMERA)
+
+#if defined(OE_IS_SHADOW_CAMERA) || defined(OE_IS_PICK_CAMERA)
+
+void oe_bumpmap_vertexView(inout vec4 vertexView) { } 
+
+#else
 
 uniform vec4 oe_tile_key;
 uniform float oe_bumpmap_scale;
@@ -50,3 +58,5 @@ void oe_bumpmap_vertexView(inout vec4 vertexView)
     // propagate normal matrix to fragment stage
     oe_bumpmap_normalMatrix = gl_NormalMatrix;
 }
+
+#endif
diff --git a/src/osgEarthDrivers/bumpmap/BumpMapOptions b/src/osgEarthDrivers/bumpmap/BumpMapOptions
index 312cf58..045b7b5 100644
--- a/src/osgEarthDrivers/bumpmap/BumpMapOptions
+++ b/src/osgEarthDrivers/bumpmap/BumpMapOptions
@@ -79,12 +79,12 @@ namespace osgEarth { namespace BumpMap
     public:
         Config getConfig() const {
             Config conf = DriverConfigOptions::getConfig();
-            conf.updateIfSet("image",     _imageURI);
-            conf.updateIfSet("intensity", _intensity);
-            conf.updateIfSet("scale",     _scale);
-            conf.updateIfSet("octaves",   _octaves);
-            conf.updateIfSet("max_range", _maxRange);
-            conf.updateIfSet("base_lod",  _baseLOD);
+            conf.set("image",     _imageURI);
+            conf.set("intensity", _intensity);
+            conf.set("scale",     _scale);
+            conf.set("octaves",   _octaves);
+            conf.set("max_range", _maxRange);
+            conf.set("base_lod",  _baseLOD);
             return conf;
         }
 
diff --git a/src/osgEarthDrivers/bumpmap/BumpMapTerrainEffect.cpp b/src/osgEarthDrivers/bumpmap/BumpMapTerrainEffect.cpp
index f34130d..c883fbd 100644
--- a/src/osgEarthDrivers/bumpmap/BumpMapTerrainEffect.cpp
+++ b/src/osgEarthDrivers/bumpmap/BumpMapTerrainEffect.cpp
@@ -26,7 +26,6 @@
 #include <osgEarth/Capabilities>
 #include <osgEarth/VirtualProgram>
 #include <osgEarth/TerrainEngineNode>
-#include <osgEarth/TerrainTileNode>
 
 #include "BumpMapShaders"
 
diff --git a/src/osgEarthDrivers/cache_filesystem/FileSystemCache.cpp b/src/osgEarthDrivers/cache_filesystem/FileSystemCache.cpp
index 18b1c68..a8e0839 100644
--- a/src/osgEarthDrivers/cache_filesystem/FileSystemCache.cpp
+++ b/src/osgEarthDrivers/cache_filesystem/FileSystemCache.cpp
@@ -122,9 +122,10 @@ namespace
         bool                              _binPathExists;
         std::string                       _metaPath;       // full path to the bin's metadata file
         std::string                       _binPath;        // full path to the bin's root folder
+        std::string                       _compressorName;
         osg::ref_ptr<osgDB::ReaderWriter> _rw;
         osg::ref_ptr<osgDB::Options>      _zlibOptions;
-        mutable Threading::Mutex          _mutex;
+        mutable Threading::ReadWriteMutex _mutex;
     };
 
     void writeMeta( const std::string& fullPath, const Config& meta )
@@ -298,12 +299,21 @@ namespace
 
         _rw = osgDB::Registry::instance()->getReaderWriterForExtension(OSG_FORMAT);
 
+        _zlibOptions = Registry::instance()->cloneOrCreateOptions();
+
 #ifdef OSG_COMPRESS
 #ifdef OSGEARTH_HAVE_ZLIB
-        _zlibOptions = Registry::instance()->cloneOrCreateOptions();
         _zlibOptions->setPluginStringData("Compressor", "zlib");
+        _compressorName = "zlib";
 #endif        
 #endif
+        if (::getenv(OSGEARTH_ENV_DEFAULT_COMPRESSOR) != 0L){
+           _compressorName = ::getenv(OSGEARTH_ENV_DEFAULT_COMPRESSOR);
+        }
+        if (_compressorName.length() > 0){
+           _zlibOptions->setPluginStringData("Compressor", _compressorName);
+        }
+     
     }
 
     const osgDB::Options*
@@ -320,7 +330,9 @@ namespace
         else
         {
             osgDB::Options* merged = Registry::cloneOrCreateOptions(dbo);
-            merged->setPluginStringData("Compressor", "zlib");
+            if (_compressorName.length()){
+               merged->setPluginStringData("Compressor", _compressorName);
+            }
             return merged;
         }
     }
@@ -344,7 +356,7 @@ namespace
 
         osgDB::ReaderWriter::ReadResult r;
         {
-            ScopedMutexLock lock(_mutex);
+            ScopedReadLock lock(_mutex);
 
             r = _rw->readImage( path, dbo.get() );
             if ( !r.success() )
@@ -381,7 +393,7 @@ namespace
 
         osgDB::ReaderWriter::ReadResult r;
         {
-            ScopedMutexLock lock(_mutex);
+            ScopedReadLock lock(_mutex);
 
             r = _rw->readObject( path, dbo.get() );
             if ( !r.success() )
@@ -430,7 +442,7 @@ namespace
         bool objWriteOK = false;
         {
             // prevent cache contention:
-            ScopedMutexLock lock(_mutex);
+            ScopedWriteLock lock(_mutex);
 
             // make a home for it..
             if ( !osgDB::fileExists( osgDB::getFilePath(fileURI.full()) ) )
@@ -499,7 +511,7 @@ namespace
         URI fileURI( getHashedKey(key), _metaPath );
         std::string path( fileURI.full() + OSG_EXT );
 
-        ScopedMutexLock lock(_mutex);
+        ScopedWriteLock lock(_mutex);
         return ::unlink( path.c_str() ) == 0;
     }
 
@@ -510,7 +522,7 @@ namespace
         URI fileURI( getHashedKey(key), _metaPath );
         std::string path( fileURI.full() + OSG_EXT );
 
-        ScopedMutexLock lock(_mutex);
+        ScopedWriteLock lock(_mutex);
         return osgEarth::touchFile( path );
     }
 
@@ -519,8 +531,6 @@ namespace
     {
         if ( !binValidForReading() ) return false;
 
-        ScopedMutexLock lock(_mutex);
-
         bool allOK = true;
         osgDB::DirectoryContents dc = osgDB::getDirectoryContents( dir );
 
@@ -563,7 +573,7 @@ namespace
         if ( !binValidForReading() )
             return false;
 
-        ScopedMutexLock lock(_mutex);
+        ScopedWriteLock lock(_mutex);
         std::string binDir = osgDB::getFilePath( _metaPath );
         return purgeDirectory( binDir );
     }
@@ -573,7 +583,7 @@ namespace
     {
         if ( !binValidForReading() ) return Config();
         
-        ScopedMutexLock lock(_mutex);
+        ScopedReadLock lock(_mutex);
 
         Config conf;
         conf.fromJSON( URI(_metaPath).getString(_zlibOptions.get()) );
@@ -586,7 +596,7 @@ namespace
     {
         if ( !binValidForWriting() ) return false;
         
-        ScopedMutexLock lock(_mutex);
+        ScopedWriteLock lock(_mutex);
 
         std::fstream output( _metaPath.c_str(), std::ios_base::out );
         if ( output.is_open() )
diff --git a/src/osgEarthDrivers/cache_rocksdb/CMakeLists.txt b/src/osgEarthDrivers/cache_rocksdb/CMakeLists.txt
index a626c8f..e3dd9ed 100644
--- a/src/osgEarthDrivers/cache_rocksdb/CMakeLists.txt
+++ b/src/osgEarthDrivers/cache_rocksdb/CMakeLists.txt
@@ -1,5 +1,7 @@
 IF(ROCKSDB_FOUND)
 
+set (CMAKE_CXX_STANDARD 11)
+
 INCLUDE_DIRECTORIES( ${ROCKSDB_INCLUDE_DIR} )
 
 SET(TARGET_H
diff --git a/src/osgEarthDrivers/colorramp/CMakeLists.txt b/src/osgEarthDrivers/colorramp/CMakeLists.txt
index 9a59bbb..a70f630 100644
--- a/src/osgEarthDrivers/colorramp/CMakeLists.txt
+++ b/src/osgEarthDrivers/colorramp/CMakeLists.txt
@@ -9,7 +9,7 @@ SETUP_PLUGIN(osgearth_colorramp)
 
 
 # to install public driver includes:
-SET(LIB_NAME osg)
+SET(LIB_NAME colorramp)
 SET(LIB_PUBLIC_HEADERS ColorRampOptions)
 INCLUDE(ModuleInstallOsgEarthDriverIncludes OPTIONAL)
 
diff --git a/src/osgEarthDrivers/colorramp/ColorRampOptions b/src/osgEarthDrivers/colorramp/ColorRampOptions
index 7bdd6cd..8a2f35c 100644
--- a/src/osgEarthDrivers/colorramp/ColorRampOptions
+++ b/src/osgEarthDrivers/colorramp/ColorRampOptions
@@ -51,8 +51,8 @@ namespace osgEarth { namespace Drivers
     public:
         Config getConfig() const {
             Config conf = TileSourceOptions::getConfig();
-            conf.updateObjIfSet("elevation", _elevationLayerOptions );
-            conf.updateIfSet("ramp", _ramp);
+            conf.setObj("elevation", _elevationLayerOptions );
+            conf.set("ramp", _ramp);
             return conf;
         }
 
diff --git a/src/osgEarthDrivers/debug/DebugOptions b/src/osgEarthDrivers/debug/DebugOptions
index f8684bd..b5026c0 100644
--- a/src/osgEarthDrivers/debug/DebugOptions
+++ b/src/osgEarthDrivers/debug/DebugOptions
@@ -50,8 +50,8 @@ namespace osgEarth { namespace Drivers
     public:
         Config getConfig() const {
             Config conf = TileSourceOptions::getConfig();
-            conf.updateIfSet( "color", _colorCode );
-            conf.updateIfSet( "inverty", _invertY );
+            conf.set( "color", _colorCode );
+            conf.set( "invert_y", _invertY );
             return conf;
         }
 
@@ -64,7 +64,7 @@ namespace osgEarth { namespace Drivers
     private:
         void fromConfig( const Config& conf ) {
             conf.getIfSet( "color", _colorCode );
-            conf.getIfSet( "inverty", _invertY);
+            conf.getIfSet( "invert_y", _invertY);
         }
 
         optional<std::string> _colorCode;
diff --git a/src/osgEarthDrivers/debug/DebugTileSource.cpp b/src/osgEarthDrivers/debug/DebugTileSource.cpp
index e0d40a3..57dc960 100644
--- a/src/osgEarthDrivers/debug/DebugTileSource.cpp
+++ b/src/osgEarthDrivers/debug/DebugTileSource.cpp
@@ -109,18 +109,28 @@ public:
         {
             buf << key.str();
         }        
+
+        double r = key.getExtent().computeBoundingGeoCircle().getRadius();
+        buf << "\nr = " << (int)r << "m";
         
         std::string text;
         text = buf.str();
 
         unsigned x = 10, y = 10;
 
-        osgText::FontResolution resolution(32, 32);
+        int res = 32;
+        osgText::FontResolution resolution(res, res);
         for( unsigned i=0; i<text.length(); ++i )
         {
-            osgText::Glyph* glyph = _font->getGlyph( resolution, text.at(i) );
-            copySubImageAndColorize( glyph, image, x, y, _color );
-            x += glyph->s() + 1;
+            if (text[i] == '\n') {
+                y += res + 10;
+                x = 10;
+            }
+            else {            
+                osgText::Glyph* glyph = _font->getGlyph( resolution, text[i] );
+                copySubImageAndColorize( glyph, image, x, y, _color );
+                x += glyph->s() + 1;
+            }
         }
 
         return image;
diff --git a/src/osgEarthDrivers/detail/Detail.frag.glsl b/src/osgEarthDrivers/detail/Detail.frag.glsl
index 106aa5e..4bb1f15 100644
--- a/src/osgEarthDrivers/detail/Detail.frag.glsl
+++ b/src/osgEarthDrivers/detail/Detail.frag.glsl
@@ -1,4 +1,6 @@
 #version $GLSL_VERSION_STR
+$GLSL_DEFAULT_PRECISION_FLOAT
+
 #pragma vp_entryPoint oe_detail_fragment
 #pragma vp_location   fragment_coloring
 #pragma vp_order      1
@@ -12,4 +14,4 @@ void oe_detail_fragment(inout vec4 color)
 {
     vec4 texel = texture(oe_detail_tex, detailCoords);
     color.rgb = mix(color.rgb, texel.rgb, oe_detail_alpha * detailIntensity);
-}                
\ No newline at end of file
+}                
diff --git a/src/osgEarthDrivers/detail/Detail.vert.view.glsl b/src/osgEarthDrivers/detail/Detail.vert.view.glsl
index af76960..1afa7c8 100644
--- a/src/osgEarthDrivers/detail/Detail.vert.view.glsl
+++ b/src/osgEarthDrivers/detail/Detail.vert.view.glsl
@@ -1,4 +1,5 @@
 #version $GLSL_VERSION_STR
+$GLSL_DEFAULT_PRECISION_FLOAT
 
 #pragma vp_entryPoint oe_detail_vertexView
 #pragma vp_location   vertex_view
@@ -17,4 +18,4 @@ void oe_detail_vertexView(inout vec4 VertexView)
 {
   detailCoords = oe_terrain_scaleCoordsToRefLOD(oe_layer_tilec.st, int(oe_detail_lod));
   detailIntensity = clamp((oe_detail_maxRange - (-VertexView.z))/oe_detail_attenDist, 0.0, 1.0);
-}
\ No newline at end of file
+}
diff --git a/src/osgEarthDrivers/detail/DetailOptions b/src/osgEarthDrivers/detail/DetailOptions
index 7904ad1..ceb7339 100644
--- a/src/osgEarthDrivers/detail/DetailOptions
+++ b/src/osgEarthDrivers/detail/DetailOptions
@@ -65,11 +65,11 @@ namespace osgEarth { namespace Detail
     public:
         Config getConfig() const {
             Config conf = DriverConfigOptions::getConfig();
-            conf.updateIfSet("image", _imageURI);
-            conf.updateIfSet("lod", _lod);
-            conf.updateIfSet("alpha", _alpha);
-            conf.updateIfSet("max_range", _maxRange);
-            conf.updateIfSet("attenuation", _attenDist);
+            conf.set("image", _imageURI);
+            conf.set("lod", _lod);
+            conf.set("alpha", _alpha);
+            conf.set("max_range", _maxRange);
+            conf.set("attenuation", _attenDist);
             return conf;
         }
 
diff --git a/src/osgEarthDrivers/detail/DetailTerrainEffect.cpp b/src/osgEarthDrivers/detail/DetailTerrainEffect.cpp
index e7c41b8..e979835 100644
--- a/src/osgEarthDrivers/detail/DetailTerrainEffect.cpp
+++ b/src/osgEarthDrivers/detail/DetailTerrainEffect.cpp
@@ -26,7 +26,7 @@
 #include <osgEarth/Capabilities>
 #include <osgEarth/TerrainEngineNode>
 #include <osgEarth/ImageUtils>
-#include <osgEarthUtil/SimplexNoise>
+#include <osgEarth/SimplexNoise>
 
 #define LC "[Detail] "
 
diff --git a/src/osgEarthDrivers/earth/EarthFileSerializer1.cpp b/src/osgEarthDrivers/earth/EarthFileSerializer1.cpp
index 2b98192..37f7323 100644
--- a/src/osgEarthDrivers/earth/EarthFileSerializer1.cpp
+++ b/src/osgEarthDrivers/earth/EarthFileSerializer1.cpp
@@ -24,6 +24,7 @@ using namespace osgEarth;
 MapNode*
 EarthFileSerializer1::deserialize( const Config& conf, const std::string& referenceURI ) const
 {
+#if 0
     // piece together a MapOptions, TerrainOptions, and MapNodeOptions:
     Config mapOptionsConf;
     mapOptionsConf.setReferrer( conf.referrer() );
@@ -132,7 +133,7 @@ EarthFileSerializer1::deserialize( const Config& conf, const std::string& refere
         layerOpt.name() = layerDriverConf.value( "name" );
         layerOpt.driver() = ModelSourceOptions( layerDriverConf );
 
-        map->addModelLayer( new ModelLayer(layerOpt) );
+        map->addLayer( new ModelLayer(layerOpt) );
         //map->addModelLayer( new ModelLayer( i->value("name"), ModelSourceOptions(*i) ) );
     }
 
@@ -146,9 +147,11 @@ EarthFileSerializer1::deserialize( const Config& conf, const std::string& refere
         options.name() = maskLayerConf.value( "name" );
         options.driver() = MaskSourceOptions(options);
 
-        map->addTerrainMaskLayer( new MaskLayer(options) );
+        map->addLayer( new MaskLayer(options) );
     }
 
     return new MapNode( map, mapNodeOptions );
+#endif
+    return 0L;
 }
 
diff --git a/src/osgEarthDrivers/earth/EarthFileSerializer2.cpp b/src/osgEarthDrivers/earth/EarthFileSerializer2.cpp
index 8e78184..3c6ea01 100644
--- a/src/osgEarthDrivers/earth/EarthFileSerializer2.cpp
+++ b/src/osgEarthDrivers/earth/EarthFileSerializer2.cpp
@@ -22,6 +22,7 @@
 #include <osgEarth/Extension>
 #include <osgEarth/StringUtils>
 #include <osgEarth/FileUtils>
+#include <osgEarth/URI>
 #include <osgDB/FileUtils>
 #include <osgDB/FileNameUtils>
 #include <stdio.h>
@@ -83,17 +84,20 @@ namespace
 		return std::find_first_of(it, end, PATH_SEPARATORS, PATH_SEPARATORS+PATH_SEPARATORS_LEN);
 	}
 
+    // Config tags that we handle specially (versus just letting the plugin mechanism
+    // take take of them)
     bool isReservedWord(const std::string& k)
     {
         return
             k == "options" ||
-            k == "image" ||
+            //k == "image" ||
             k == "elevation" ||
             k == "heightfield" ||
-            k == "model" ||
-            k == "mask" ||
+            //k == "model" ||
+            //k == "mask" ||
             k == "external" ||
-            k == "extensions";
+            k == "extensions" ||
+            k == "libraries";
     }
 
     /**
@@ -314,50 +318,8 @@ namespace
 
 namespace
 {
-    void addImageLayer(const Config& conf, Map* map)
-    {
-        ImageLayerOptions options( conf );
-        options.name() = conf.value("name");
-        ImageLayer* layer = new ImageLayer(options);
-        map->addImageLayer(layer);
-        if (layer->getStatus().isError())
-            OE_WARN << LC << "Layer \"" << layer->getName() << "\" : " << layer->getStatus().toString() << std::endl;
-    }
-
-    void addElevationLayer(const Config& conf, Map* map)
-    {
-        ElevationLayerOptions options( conf );
-        options.name() = conf.value( "name" );
-        ElevationLayer* layer = new ElevationLayer(options);
-        map->addElevationLayer(layer);
-        if (layer->getStatus().isError())
-            OE_WARN << LC << "Layer \"" << layer->getName() << "\" : " << layer->getStatus().toString() << std::endl;
-    }
-
-    void addModelLayer(const Config& conf, Map* map)
-    {
-        ModelLayerOptions options( conf );
-        options.name() = conf.value( "name" );
-        options.driver() = ModelSourceOptions( conf );
-        ModelLayer* layer = new ModelLayer(options);
-        map->addModelLayer(layer);
-        if (layer->getStatus().isError())
-            OE_WARN << LC << "Layer \"" << layer->getName() << "\" : " << layer->getStatus().toString() << std::endl;
-    }
-
-    void addMaskLayer(const Config& conf, Map* map)
-    {
-        MaskLayerOptions options(conf);
-        options.name() = conf.value( "name" );
-        options.driver() = MaskSourceOptions(options);
-        MaskLayer* layer = new MaskLayer(options);
-        map->addTerrainMaskLayer(layer);
-        if (layer->getStatus().isError())
-            OE_WARN << LC << "Layer \"" << layer->getName() << "\" : " << layer->getStatus().toString() << std::endl;
-    }
-
     // support for "special" extension names (convenience and backwards compat)
-    Extension* createSpecialExtension(const Config& conf, MapNode* mapNode)
+    Extension* createSpecialExtension(const Config& conf)
     {
         // special support for the default sky extension:
         if (conf.key() == "sky" && !conf.hasValue("driver"))
@@ -369,7 +331,18 @@ namespace
         return 0L;
     }
 
-    void addExtension(const Config& conf, MapNode* mapNode)
+    bool addLayer(const Config& conf, Map* map)
+    {
+        std::string name = conf.key();
+        Layer* layer = Layer::create(name, conf);
+        if (layer)
+        {
+            map->addLayer(layer);
+        }
+        return layer != 0L;
+    }
+
+    Extension* loadExtension(const Config& conf)
     {
         std::string name = conf.key();
         Extension* extension = Extension::create( conf.key(), conf );
@@ -379,17 +352,27 @@ namespace
             extension = Extension::create(name, conf);
 
             if (!extension)
-                extension = createSpecialExtension(conf, mapNode);
+                extension = createSpecialExtension(conf);
         }
 
-        if (extension)
+        if (!extension)
         {
-            mapNode->addExtension(extension);
-        }
-        else
-        {            
             OE_INFO << LC << "Failed to find an extension for \"" << name << "\"\n";
         }
+
+        return extension;
+    }
+
+    void reportErrors(const Map* map)
+    {
+        for (unsigned i = 0; i < map->getNumLayers(); ++i)
+        {
+            const Layer* layer = map->getLayerAt(i);
+            if (layer->getStatus().isError())
+            {
+                OE_WARN << LC << layer->getTypeName() << " \"" << layer->getName() << "\" : " << layer->getStatus().toString() << std::endl;
+            }
+        }
     }
 }
 
@@ -420,23 +403,32 @@ EarthFileSerializer2::deserialize( const Config& conf, const std::string& referr
         mapOptions.mergeConfig( legacy );
     }
 
-    Map* map = new Map( mapOptions );
-
-    // Yes, MapOptions and MapNodeOptions share the same Config node. Weird but true.
-    MapNodeOptions mapNodeOptions( conf.child( "options" ) );
+    osg::ref_ptr< Map > map = new Map( mapOptions );
 
-    // Create a map node.
-    osg::ref_ptr<MapNode> mapNode = new MapNode( map, mapNodeOptions );
+    // Start a batch update of the map:
+    map->beginUpdate();
 
     // Read all the elevation layers in FIRST so other layers can access them for things like clamping.
+    // TODO: revisit this since we should really be listening for elevation data changes and
+    // re-clamping based on that..
     for(ConfigSet::const_iterator i = conf.children().begin(); i != conf.children().end(); ++i)
     {
-        if ( i->key() == "elevation" || i->key() == "heightfield" )
+        // for backwards compatibility:
+        if (i->key() == "heightfield")
+        {
+            Config temp = *i;
+            temp.key() = "elevation";
+            addLayer(temp, map.get());
+        }
+
+        else if ( i->key() == "elevation" ) // || i->key() == "heightfield" )
         {
-            addElevationLayer( *i, map );
+            addLayer(*i, map.get());
         }
     }
 
+    Config externalConfig;
+    std::vector<osg::ref_ptr<Extension> > extensions;
 
     // Read the layers in LAST (otherwise they will not benefit from the cache/profile configuration)
     for(ConfigSet::const_iterator i = conf.children().begin(); i != conf.children().end(); ++i)
@@ -446,12 +438,13 @@ EarthFileSerializer2::deserialize( const Config& conf, const std::string& referr
             // nop - handled earlier
         }
 
+#if 0
         else if ( i->key() == "image" )
         {
             addImageLayer( *i, map );
         }
 
-        else if ( i->key() == "model" )
+        else */if ( i->key() == "model" )
         {
             addModelLayer( *i, map );
         }
@@ -460,22 +453,60 @@ EarthFileSerializer2::deserialize( const Config& conf, const std::string& referr
         {
             addMaskLayer( *i, map );
         }
+#endif
 
         else if ( i->key() == "external" || i->key() == "extensions" )
         {
-            mapNode->externalConfig() = *i;
+            externalConfig = *i;
+            
             for(ConfigSet::const_iterator e = i->children().begin(); e != i->children().end(); ++e)
             {
-                addExtension( *e, mapNode.get() );
+                Extension* extension = loadExtension(*e);
+                if (extension)
+                    extensions.push_back(extension);
+                //addExtension( *e, mapNode.get() );
             }
         }
 
         else if ( !isReservedWord(i->key()) ) // plugins/extensions.
         {
-            addExtension( *i, mapNode.get() );
+            // try to add as a plugin Layer first:
+            bool addedLayer = addLayer(*i, map.get()); 
+
+            // failing that, try to load as an extension:
+            if ( !addedLayer )
+            {
+                Extension* extension = loadExtension(*i);
+                if (extension)
+                    extensions.push_back(extension);
+            }
         }
     }
 
+    // Complete the batch update of the map
+    map->endUpdate();
+
+    // If any errors occurred, report them now.
+    reportErrors(map.get());
+
+    // Yes, MapOptions and MapNodeOptions share the same Config node. Weird but true.
+    MapNodeOptions mapNodeOptions( conf.child("options") );
+
+    // Create a map node.
+    osg::ref_ptr<MapNode> mapNode = new MapNode( map.get(), mapNodeOptions );
+
+    // Apply the external conf if there is one.
+    if (!externalConfig.empty())
+    {
+        mapNode->externalConfig() = externalConfig;
+    }
+
+    // Install the extensions
+    for (unsigned i = 0; i < extensions.size(); ++i)
+    {
+        mapNode->addExtension(extensions[i].get());
+    }
+
     // return the topmost parent of the mapnode. It's possible that
     // an extension added parents!
     osg::Node* top = mapNode.release();
@@ -491,74 +522,19 @@ Config
 EarthFileSerializer2::serialize(const MapNode* input, const std::string& referrer) const
 {
     Config mapConf("map");
-    mapConf.set("version", "2");
-
-    if ( !input || !input->getMap() )
-        return mapConf; 
-
-    const Map* map = input->getMap();
-    MapFrame mapf( map, Map::ENTIRE_MODEL );
-
-    // the map and node options:
-    Config optionsConf = map->getInitialMapOptions().getConfig();
-    optionsConf.merge( input->getMapNodeOptions().getConfig() );
-    mapConf.add( "options", optionsConf );
-
-    // the layers
-    for( ImageLayerVector::const_iterator i = mapf.imageLayers().begin(); i != mapf.imageLayers().end(); ++i )
-    {
-        ImageLayer* layer = i->get();
-        //Config layerConf = layer->getInitialOptions().getConfig();
-        Config layerConf = layer->getImageLayerOptions().getConfig();
-        layerConf.set("name", layer->getName());
-        layerConf.set("driver", layer->getInitialOptions().driver()->getDriver());        
-        mapConf.add( "image", layerConf );
-    }
-
-    for( ElevationLayerVector::const_iterator i = mapf.elevationLayers().begin(); i != mapf.elevationLayers().end(); ++i )
-    {
-        ElevationLayer* layer = i->get();
-        //Config layerConf = layer->getInitialOptions().getConfig();
-        Config layerConf = layer->getElevationLayerOptions().getConfig();
-        layerConf.set("name", layer->getName());
-        layerConf.set("driver", layer->getInitialOptions().driver()->getDriver());        
-        mapConf.add( "elevation", layerConf );
-    }
-
-    for( ModelLayerVector::const_iterator i = mapf.modelLayers().begin(); i != mapf.modelLayers().end(); ++i )
+    
+    if (input && input->getMap())
     {
-        ModelLayer* layer = i->get();
-        Config layerConf = layer->getModelLayerOptions().getConfig();
-        layerConf.set("name", layer->getName());
-        layerConf.set("driver", layer->getModelLayerOptions().driver()->getDriver());
-        mapConf.add( "model", layerConf );
-    }
+        mapConf = input->getConfig();
 
-    typedef std::vector< osg::ref_ptr<Extension> > Extensions;
-    for(Extensions::const_iterator i = input->getExtensions().begin(); i != input->getExtensions().end(); ++i)
-    {
-        Extension* e = i->get();
-        Config conf = e->getConfigOptions().getConfig();
-        if ( !conf.key().empty() )
+        // Re-write pathnames in the Config so they are relative to the new referrer.
+        if ( _rewritePaths && !referrer.empty() )
         {
-            mapConf.add( conf );
+            RewritePaths rewritePaths( referrer );
+            rewritePaths.setRewriteAbsolutePaths( _rewriteAbsolutePaths );
+            rewritePaths.apply( mapConf );
         }
     }
 
-    Config ext = input->externalConfig();
-    if ( !ext.empty() )
-    {
-        ext.key() = "external";
-        mapConf.add( ext );
-    }
-
-    // Re-write pathnames in the Config so they are relative to the new referrer.
-    if ( _rewritePaths && !referrer.empty() )
-    {
-        RewritePaths rewritePaths( referrer );
-        rewritePaths.setRewriteAbsolutePaths( _rewriteAbsolutePaths );
-        rewritePaths.apply( mapConf );
-    }
-
     return mapConf;
 }
diff --git a/src/osgEarthDrivers/engine_byo/BYOTerrainEngineDriver.cpp b/src/osgEarthDrivers/engine_byo/BYOTerrainEngineDriver.cpp
deleted file mode 100644
index 8356a91..0000000
--- a/src/osgEarthDrivers/engine_byo/BYOTerrainEngineDriver.cpp
+++ /dev/null
@@ -1,66 +0,0 @@
-/* -*-c++-*- */
-/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2016 Pelican Mapping
- * http://osgearth.org
- *
- * osgEarth is free software; you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>
- */
-#include "BYOTerrainEngineNode"
-#include "BYOTerrainEngineOptions"
-#include <osgDB/FileNameUtils>
-
-#define LC "[engine_byo driver] "
-
-using namespace osgEarth::Drivers;
-using namespace osgEarth_engine_byo;
-
-/**
- * osgEarth driver for the "Bring Your Own" terrain engine.
- */
-class osgEarth_BYOTerrainEngineDriver : public osgDB::ReaderWriter
-{
-public:
-    osgEarth_BYOTerrainEngineDriver() {}
-
-    virtual const char* className() const
-    {
-        return "osgEarth BYO Terrain Engine";
-    }
-
-    virtual bool acceptsExtension(const std::string& extension) const
-    {
-        return
-            osgDB::equalCaseInsensitive( extension, "osgearth_engine_byo" );
-    }
-
-    virtual ReadResult readObject(const std::string& uri, const Options* options) const
-    {
-        if ( "osgearth_engine_byo" == osgDB::getFileExtension( uri ) )
-        {
-            OE_INFO << LC << "Activated!" << std::endl;
-            return ReadResult( new BYOTerrainEngineNode() );
-        }
-        else
-        {
-            return ReadResult::FILE_NOT_HANDLED;
-        }
-    }    
-
-    virtual ReadResult readNode(const std::string& uri, const Options* options) const
-    {
-        return readObject(uri, options);
-    }
-};
-
-REGISTER_OSGPLUGIN(osgearth_engine_byo, osgEarth_BYOTerrainEngineDriver)
diff --git a/src/osgEarthDrivers/engine_byo/BYOTerrainEngineNode b/src/osgEarthDrivers/engine_byo/BYOTerrainEngineNode
deleted file mode 100644
index b31433c..0000000
--- a/src/osgEarthDrivers/engine_byo/BYOTerrainEngineNode
+++ /dev/null
@@ -1,57 +0,0 @@
-/* -*-c++-*- */
-/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2016 Pelican Mapping
- * http://osgearth.org
- *
- * osgEarth is free software; you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>
- */
-#ifndef OSGEARTH_ENGINE_BYO_ENGINE_NODE_H
-#define OSGEARTH_ENGINE_BYO_ENGINE_NODE_H 1
-
-#include <osgEarth/TerrainEngineNode>
-#include "BYOTerrainEngineOptions"
-
-using namespace osgEarth;
-
-namespace osgEarth_engine_byo
-{
-    class BYOTerrainEngineNode : public TerrainEngineNode
-    {
-    public:
-        BYOTerrainEngineNode();
-        META_Node(osgEarth, BYOTerrainEngineNode);
-        virtual ~BYOTerrainEngineNode();
-
-    public: // TerrainEngineNode
-
-        UID getUID() const { return _uid; }
-
-        // for standalone tile creation outside of a terrain
-        osg::Node* createTile(const TileKey& key) { return 0L; }
-
-    public: // internal TerrainEngineNode
-
-        virtual void preInitialize( const Map* map, const TerrainOptions& options );
-        virtual const TerrainOptions& getTerrainOptions() const { return _terrainOptions; }
-
-    private:
-        osgEarth::Drivers::BYOTerrainEngineOptions _terrainOptions;
-        BYOTerrainEngineNode( const BYOTerrainEngineNode& rhs, const osg::CopyOp& op =osg::CopyOp::DEEP_COPY_ALL ) { }
-
-        UID _uid;
-    };
-
-} // namespace osgEarth_engine_byo
-
-#endif // OSGEARTH_ENGINE_BYO_ENGINE_NODE_H
diff --git a/src/osgEarthDrivers/engine_byo/BYOTerrainEngineNode.cpp b/src/osgEarthDrivers/engine_byo/BYOTerrainEngineNode.cpp
deleted file mode 100644
index ba213b6..0000000
--- a/src/osgEarthDrivers/engine_byo/BYOTerrainEngineNode.cpp
+++ /dev/null
@@ -1,81 +0,0 @@
-/* -*-c++-*- */
-/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2016 Pelican Mapping
-* http://osgearth.org
-*
-* osgEarth is free software; you can redistribute it and/or modify
-* it under the terms of the GNU Lesser General Public License as published by
-* the Free Software Foundation; either version 2 of the License, or
-* (at your option) any later version.
-*
-* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
-* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
-* IN THE SOFTWARE.
-*
-* You should have received a copy of the GNU Lesser General Public License
-* along with this program.  If not, see <http://www.gnu.org/licenses/>
-*/
-#include "BYOTerrainEngineNode"
-#include "BYOTerrainEngineOptions"
-#include <osgEarth/ShaderGenerator>
-#include <osgEarth/Registry>
-
-#define LC "[BYOTerrainEngineNode] "
-
-using namespace osgEarth_engine_byo;
-using namespace osgEarth;
-using namespace osgEarth::Drivers;
-
-//------------------------------------------------------------------------
-
-BYOTerrainEngineNode::BYOTerrainEngineNode() :
-TerrainEngineNode( )
-{
-    _uid = Registry::instance()->createUID();
-}
-
-BYOTerrainEngineNode::~BYOTerrainEngineNode()
-{
-    //nop
-}
-
-void
-BYOTerrainEngineNode::preInitialize( const Map* map, const TerrainOptions& options )
-{
-    TerrainEngineNode::preInitialize( map, options );
-    BYOTerrainEngineOptions myoptions(options);
-    if ( myoptions.getNode() )
-    {
-        this->addChild( myoptions.getNode() );
-    }
-    else if ( myoptions.url().isSet() )
-    {
-        OE_INFO << LC << "Loading terrain from " << myoptions.url()->full() << std::endl;
-
-        osg::Node* node = myoptions.url()->getNode();
-        if ( node )
-        {
-            if ( myoptions.shaderPolicy() == SHADERPOLICY_GENERATE )
-            {
-                osg::ref_ptr<StateSetCache> cache = new StateSetCache();
-                
-                Registry::shaderGenerator().run(
-                    node,
-                    "osgEarth.BYOTerrainEngine",
-                    cache.get() );
-            }
-            else if ( myoptions.shaderPolicy() == SHADERPOLICY_DISABLE )
-            {
-                node->getOrCreateStateSet()->setAttributeAndModes(
-                    new osg::Program(),
-                    osg::StateAttribute::OFF | osg::StateAttribute::OVERRIDE );
-            }
-
-            this->addChild( node );
-        }
-    }
-}
diff --git a/src/osgEarthDrivers/engine_byo/BYOTerrainEngineOptions b/src/osgEarthDrivers/engine_byo/BYOTerrainEngineOptions
deleted file mode 100644
index 73ab11e..0000000
--- a/src/osgEarthDrivers/engine_byo/BYOTerrainEngineOptions
+++ /dev/null
@@ -1,91 +0,0 @@
-/* -*-c++-*- */
-/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2016 Pelican Mapping
- * http://osgearth.org
- *
- * osgEarth is free software; you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>
- */
-#ifndef OSGEARTH_ENGINE_BYO_OPTIONS
-#define OSGEARTH_ENGINE_BYO_OPTIONS 1
-
-#include <osgEarth/Common>
-#include <osgEarth/ShaderUtils>
-#include <osgEarth/TerrainOptions>
-#include <osgEarth/URI>
-
-namespace osgEarth { namespace Drivers
-{
-    using namespace osgEarth;
-
-    /**
-     * Options for configuring the Bring Your Own Engine driver.
-     */
-    class BYOTerrainEngineOptions : public TerrainOptions // NO EXPORT (header-only)
-    {
-    public:
-        BYOTerrainEngineOptions( const ConfigOptions& options =ConfigOptions() ) 
-            : TerrainOptions( options ),
-              _shaderPolicy ( SHADERPOLICY_GENERATE )
-        {
-            setDriver( "byo" );
-            fromConfig( _conf );
-        }
-
-        /** dtor */
-        virtual ~BYOTerrainEngineOptions() { }
-
-    public:
-        optional<URI>& url() { return _uri; }
-        const optional<URI>& url() const { return _uri; }
-
-        optional<ShaderPolicy>& shaderPolicy() { return _shaderPolicy; }
-        const optional<ShaderPolicy>& shaderPolicy() const { return _shaderPolicy; }
-
-    public: // non-serializable
-        void setNode(osg::Node* node);
-        osg::Node* getNode() { return _node.get(); }
-
-    protected:
-        virtual Config getConfig() const {
-            Config conf = TerrainOptions::getConfig();
-            conf.updateIfSet( "url", _uri );
-            conf.updateIfSet( "shader_policy", "disable",  _shaderPolicy, SHADERPOLICY_DISABLE );
-            conf.updateIfSet( "shader_policy", "inherit",  _shaderPolicy, SHADERPOLICY_INHERIT );
-            conf.updateIfSet( "shader_policy", "generate", _shaderPolicy, SHADERPOLICY_GENERATE );
-            conf.updateNonSerializable( "node", _node.get() );
-            return conf;
-        }
-
-        virtual void mergeConfig( const Config& conf ) {
-            TerrainOptions::mergeConfig( conf );
-            fromConfig( conf );
-        }
-
-    private:
-        void fromConfig( const Config& conf ) {
-            conf.getIfSet( "url", _uri );
-            conf.getIfSet( "shader_policy", "disable",  _shaderPolicy, SHADERPOLICY_DISABLE );
-            conf.getIfSet( "shader_policy", "inherit",  _shaderPolicy, SHADERPOLICY_INHERIT );
-            conf.getIfSet( "shader_policy", "generate", _shaderPolicy, SHADERPOLICY_GENERATE );
-            _node = conf.getNonSerializable<osg::Node>( "node" );
-        }
-
-        optional<URI>           _uri;
-        optional<ShaderPolicy>  _shaderPolicy;
-        osg::ref_ptr<osg::Node> _node;
-    };
-
-} } // namespace osgEarth::Drivers
-
-#endif // OSGEARTH_ENGINE_MP_OPTIONS
diff --git a/src/osgEarthDrivers/engine_byo/CMakeLists.txt b/src/osgEarthDrivers/engine_byo/CMakeLists.txt
deleted file mode 100644
index 1af02a3..0000000
--- a/src/osgEarthDrivers/engine_byo/CMakeLists.txt
+++ /dev/null
@@ -1,20 +0,0 @@
-
-SET(TARGET_COMMON_LIBRARIES ${TARGET_COMMON_LIBRARIES} osgEarthSymbology)
-
-SET(TARGET_SRC
-    BYOTerrainEngineNode.cpp
-    BYOTerrainEngineDriver.cpp
-)
-
-SET(TARGET_H
-    Common
-    BYOTerrainEngineNode
-    BYOTerrainEngineOptions
-)
-
-SETUP_PLUGIN(osgearth_engine_byo)
-
-# to install public driver includes:
-SET(LIB_NAME engine_byo)
-SET(LIB_PUBLIC_HEADERS ${TARGET_H})
-INCLUDE(ModuleInstallOsgEarthDriverIncludes OPTIONAL)
diff --git a/src/osgEarthDrivers/engine_byo/Common b/src/osgEarthDrivers/engine_byo/Common
deleted file mode 100644
index f3431fb..0000000
--- a/src/osgEarthDrivers/engine_byo/Common
+++ /dev/null
@@ -1,24 +0,0 @@
-/* -*-c++-*- */
-/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2016 Pelican Mapping
- * http://osgearth.org
- *
- * osgEarth is free software; you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>
- */
-#ifndef OSGEARTH_ENGINE_BYO_COMMON_H
-#define OSGEARTH_ENGINE_BYO_COMMON_H 1
-
-#include <osgEarth/Common>
-
-#endif // OSGEARTH_ENGINE_BYO_COMMON_H
diff --git a/src/osgEarthDrivers/engine_mp/HeightFieldCache.cpp b/src/osgEarthDrivers/engine_mp/HeightFieldCache.cpp
index 8cb9e36..3a902e1 100644
--- a/src/osgEarthDrivers/engine_mp/HeightFieldCache.cpp
+++ b/src/osgEarthDrivers/engine_mp/HeightFieldCache.cpp
@@ -95,7 +95,7 @@ HeightFieldCache::getOrCreateHeightField(const MapFrame&                 frame,
         // MSL=0 reference heightfield instead.
         if ( !out_hf.valid() )
         {
-            out_hf = HeightFieldUtils::createReferenceHeightField( key.getExtent(), _tileSize, _tileSize );
+            out_hf = HeightFieldUtils::createReferenceHeightField( key.getExtent(), _tileSize, _tileSize, 0u );
         }
 
         // Next, populate it with data from the Map. The map will overwrite our starting
diff --git a/src/osgEarthDrivers/engine_mp/MPEngine.NormalMap.frag.glsl b/src/osgEarthDrivers/engine_mp/MPEngine.NormalMap.frag.glsl
index 632e742..2160a04 100644
--- a/src/osgEarthDrivers/engine_mp/MPEngine.NormalMap.frag.glsl
+++ b/src/osgEarthDrivers/engine_mp/MPEngine.NormalMap.frag.glsl
@@ -1,4 +1,5 @@
 #version $GLSL_VERSION_STR
+$GLSL_DEFAULT_PRECISION_FLOAT
 
 #pragma vp_entryPoint oe_mp_NormalMap_fragment
 #pragma vp_location   fragment_coloring
@@ -14,7 +15,7 @@ void oe_mp_NormalMap_fragment(inout vec4 color)
 {
     //const vec3 B = vec3(0,1,0);
 
-    vec4 encodedNormal = texture2D(oe_tile_normalTex, oe_normalMapCoords);
+    vec4 encodedNormal = texture(oe_tile_normalTex, oe_normalMapCoords);
     vec3 normal        = normalize(encodedNormal.xyz*2.0-1.0);
 
     //vp_Normal = normalize(oe_mp_NormalMap_TBN * normalTangent);
diff --git a/src/osgEarthDrivers/engine_mp/MPEngine.NormalMap.vert.glsl b/src/osgEarthDrivers/engine_mp/MPEngine.NormalMap.vert.glsl
index 9339346..c7afe67 100644
--- a/src/osgEarthDrivers/engine_mp/MPEngine.NormalMap.vert.glsl
+++ b/src/osgEarthDrivers/engine_mp/MPEngine.NormalMap.vert.glsl
@@ -1,4 +1,5 @@
 #version $GLSL_VERSION_STR
+$GLSL_DEFAULT_PRECISION_FLOAT
 
 #pragma vp_entryPoint oe_mp_NormalMap_vertex
 #pragma vp_location   vertex_view
diff --git a/src/osgEarthDrivers/engine_mp/MPEngine.frag.glsl b/src/osgEarthDrivers/engine_mp/MPEngine.frag.glsl
index 0e29fb0..fb31340 100644
--- a/src/osgEarthDrivers/engine_mp/MPEngine.frag.glsl
+++ b/src/osgEarthDrivers/engine_mp/MPEngine.frag.glsl
@@ -6,7 +6,8 @@ $GLSL_DEFAULT_PRECISION_FLOAT
 #pragma vp_order      0.5
 #pragma vp_define     MP_USE_BLENDING
 
-uniform bool oe_isPickCamera;
+#pragma import_defines(OE_IS_PICK_CAMERA, OE_IS_SHADOW_CAMERA, OE_TERRAIN_CAST_SHADOWS)
+
 uniform vec4 oe_terrain_color;
 uniform sampler2D oe_layer_tex;
 uniform int oe_layer_uid;
@@ -18,10 +19,19 @@ in float oe_layer_rangeOpacity;
 
 void oe_mp_apply_coloring(inout vec4 color)
 {
+#if defined(OE_IS_SHADOW_CAMERA) && !defined(OE_TERRAIN_CAST_SHADOWS)
+    discard;
+    return;
+#endif
+
+#ifdef OE_IS_PICK_CAMERA
+    color = vec4(0);
+#else
+
     color = oe_terrain_color.a >= 0.0 ? oe_terrain_color : color;
 
     float applyImagery = oe_layer_uid >= 0 ? 1.0 : 0.0;
-    vec4 texel = mix(color, texture2D(oe_layer_tex, oe_layer_texc.st), applyImagery);
+    vec4 texel = mix(color, texture(oe_layer_tex, oe_layer_texc.st), applyImagery);
     texel.a = mix(texel.a, texel.a*oe_layer_opacity*oe_layer_rangeOpacity, applyImagery);
 
 #ifdef MP_USE_BLENDING
@@ -31,8 +41,5 @@ void oe_mp_apply_coloring(inout vec4 color)
     color = texel;
 #endif
 
-    // disable primary coloring for pick cameras. Necessary to support picking of
-    // draped geometry.
-    float pick = oe_isPickCamera ? 1.0 : 0.0;
-    color = mix(color, vec4(0), pick);
+#endif // OE_IS_PICK_CAMERA
 }
diff --git a/src/osgEarthDrivers/engine_mp/MPGeometry b/src/osgEarthDrivers/engine_mp/MPGeometry
index d6da05f..0318b82 100644
--- a/src/osgEarthDrivers/engine_mp/MPGeometry
+++ b/src/osgEarthDrivers/engine_mp/MPGeometry
@@ -152,6 +152,11 @@ namespace osgEarth { namespace Drivers { namespace MPTerrainEngine
         osg::BoundingBox computeBound() const;
 #endif
 
+    protected:
+#if OSG_MIN_VERSION_REQUIRED(3,5,6)
+        virtual osg::VertexArrayState* createVertexArrayState(osg::RenderInfo& renderInfo) const;
+#endif
+
     public:
         META_Object(osgEarth, MPGeometry);
         MPGeometry();
diff --git a/src/osgEarthDrivers/engine_mp/MPGeometry.cpp b/src/osgEarthDrivers/engine_mp/MPGeometry.cpp
index b6ccfc6..c3fef83 100644
--- a/src/osgEarthDrivers/engine_mp/MPGeometry.cpp
+++ b/src/osgEarthDrivers/engine_mp/MPGeometry.cpp
@@ -144,9 +144,12 @@ MPGeometry::renderPrimitiveSets(osg::State& state,
             // This should only happen is the layer ordering changes;
             // If layers are added or removed, the Tile gets rebuilt and
             // the point is moot.
+            ImageLayerVector layers;
+            _frame.getLayers(layers);
+
             std::vector<Layer> reordered;
-            const ImageLayerVector& layers = _frame.imageLayers();
             reordered.reserve( layers.size() );
+
             for( ImageLayerVector::const_iterator i = layers.begin(); i != layers.end(); ++i )
             {
                 std::vector<Layer>::iterator j = std::find( _layers.begin(), _layers.end(), i->get()->getUID() );
@@ -162,21 +165,13 @@ MPGeometry::renderPrimitiveSets(osg::State& state,
     // access the GL extensions interface for the current GC:
     const osg::Program::PerContextProgram* pcp = 0L;
 
-#if OSG_MIN_VERSION_REQUIRED(3,3,3)
 	osg::ref_ptr<osg::GLExtensions> ext;
-#else
-    osg::ref_ptr<osg::GL2Extensions> ext;
-#endif
     unsigned contextID;
 
     if (_supportsGLSL)
     {
         contextID = state.getContextID();
-#if OSG_MIN_VERSION_REQUIRED(3,3,3)
 		ext = osg::GLExtensions::Get(contextID, true);
-#else
-		ext = osg::GL2Extensions::Get( contextID, true );
-#endif
         pcp = state.getLastAppliedProgramObject();
     }
 
@@ -232,23 +227,13 @@ MPGeometry::renderPrimitiveSets(osg::State& state,
         state.setTexCoordPointer( _imageUnit+1, _tileCoords.get() );
     }
 
-#ifndef OSG_GLES2_AVAILABLE
+#if !( defined(OSG_GLES2_AVAILABLE) || defined(OSG_GLES3_AVAILABLE) || defined(OSG_GL3_AVAILABLE) )
     if ( renderColor )
     {
         // emit a default terrain color since we're not binding a color array:
         glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
     }
-#endif
-
-    // activate the elevation texture if there is one. Same for all layers.
-    //if ( _elevTex.valid() )
-    //{
-    //    state.setActiveTextureUnit( 2 );
-    //    state.setTexCoordPointer( 1, _tileCoords.get() ); // necessary?? since we do it above
-    //    _elevTex->apply( state );
-    //    // todo: probably need an elev texture matrix as well. -gw
-    //}
-    
+#endif    
 
     // track the active image unit.
     int activeImageUnit = -1;
@@ -384,13 +369,13 @@ MPGeometry::renderPrimitiveSets(osg::State& state,
                         // assign the min range
                         if ( minRangeLocation >= 0 )
                         {
-                            ext->glUniform1f( minRangeLocation, layer._imageLayer->getImageLayerOptions().minVisibleRange().get() );
+                            ext->glUniform1f( minRangeLocation, layer._imageLayer->options().minVisibleRange().get() );
                         }
 
                         // assign the max range
                         if ( maxRangeLocation >= 0 )
                         {
-                            ext->glUniform1f( maxRangeLocation, layer._imageLayer->getImageLayerOptions().maxVisibleRange().get() );
+                            ext->glUniform1f( maxRangeLocation, layer._imageLayer->options().maxVisibleRange().get() );
                         }
                     }
 
@@ -579,6 +564,19 @@ MPGeometry::compileGLObjects( osg::RenderInfo& renderInfo ) const
     osg::Geometry::compileGLObjects( renderInfo );
 }
 
+#if OSG_MIN_VERSION_REQUIRED(3,5,6)
+osg::VertexArrayState*
+MPGeometry::createVertexArrayState(osg::RenderInfo& renderInfo) const
+{
+    osg::VertexArrayState* vas = osg::Geometry::createVertexArrayState(renderInfo);
+    
+    // make sure we have array dispatchers for the multipass coords
+    vas->assignTexCoordArrayDispatcher(_texCoordList.size() + 2);
+
+    return vas;
+}
+#endif
+
 
 void 
 MPGeometry::drawImplementation(osg::RenderInfo& renderInfo) const
@@ -594,61 +592,44 @@ MPGeometry::drawImplementation(osg::RenderInfo& renderInfo) const
 
     bool hasVertexAttributes = !_vertexAttribList.empty();
 
-    osg::ArrayDispatchers& arrayDispatchers = state.getArrayDispatchers();
-
-    arrayDispatchers.reset();
-    arrayDispatchers.setUseVertexAttribAlias(state.getUseVertexAttributeAliasing());
-
-
-    //Remove?
-#if OSG_VERSION_LESS_THAN(3,1,8)
-    arrayDispatchers.setUseGLBeginEndAdapter(false);
-#endif
-
-#if OSG_MIN_VERSION_REQUIRED(3,1,8)
-    arrayDispatchers.activateNormalArray(_normalArray.get());
+#if OSG_VERSION_LESS_THAN(3,5,6)
+    osg::ArrayDispatchers& dispatchers = state.getArrayDispatchers();
 #else
-    arrayDispatchers.activateNormalArray(_normalData.binding, _normalData.array.get(), _normalData.indices.get());
+    osg::AttributeDispatchers& dispatchers = state.getAttributeDispatchers();
 #endif
-    
+
+    dispatchers.reset();
+    dispatchers.setUseVertexAttribAlias(state.getUseVertexAttributeAliasing());
+    dispatchers.activateNormalArray(_normalArray.get());   
 
     if (hasVertexAttributes)
     {
         for(unsigned int unit=0;unit<_vertexAttribList.size();++unit)
         {
-#if OSG_MIN_VERSION_REQUIRED(3,1,8)
-            arrayDispatchers.activateVertexAttribArray(unit, _vertexAttribList[unit].get());
-#else
-            arrayDispatchers.activateVertexAttribArray(_vertexAttribList[unit].binding, unit, _vertexAttribList[unit].array.get(), _vertexAttribList[unit].indices.get());
-#endif             
+            dispatchers.activateVertexAttribArray(unit, _vertexAttribList[unit].get());
         }
     }
 
     // dispatch any attributes that are bound overall
-    arrayDispatchers.dispatch(BIND_OVERALL,0);
+#if OSG_VERSION_LESS_THAN(3,5,6)
+    dispatchers.dispatch(BIND_OVERALL,0);
+#else
+    dispatchers.dispatch(0);
+#endif
     state.lazyDisablingOfVertexAttributes();
 
 
     // set up arrays
-#if OSG_MIN_VERSION_REQUIRED( 3, 1, 8 )
     if( _vertexArray.valid() )
         state.setVertexPointer(_vertexArray.get());
 
     if (_normalArray.valid() && _normalArray->getBinding()==osg::Array::BIND_PER_VERTEX)
         state.setNormalPointer(_normalArray.get());
-#else
-    if( _vertexData.array.valid() )
-        state.setVertexPointer(_vertexData.array.get());
-
-    if (_normalData.binding==BIND_PER_VERTEX && _normalData.array.valid())
-        state.setNormalPointer(_normalData.array.get());
-#endif
 
     if( hasVertexAttributes )
     {
         for(unsigned int index = 0; index < _vertexAttribList.size(); ++index )
         {
-#if OSG_MIN_VERSION_REQUIRED( 3, 1, 8)
             const Array* array = _vertexAttribList[index].get();
             if (array && array->getBinding()==osg::Array::BIND_PER_VERTEX)
             {
@@ -664,14 +645,6 @@ MPGeometry::drawImplementation(osg::RenderInfo& renderInfo) const
                     state.setVertexAttribPointer( index, array );
                 }
             }
-#else            
-            const osg::Array* array = _vertexAttribList[index].array.get();
-            const AttributeBinding ab = _vertexAttribList[index].binding;
-            if( ab == BIND_PER_VERTEX && array )
-            {
-                state.setVertexAttribPointer( index, array, _vertexAttribList[index].normalize );
-            }
-#endif
         }
     }
 
@@ -681,8 +654,13 @@ MPGeometry::drawImplementation(osg::RenderInfo& renderInfo) const
     renderPrimitiveSets(state, renderColor, true);
 
     // unbind the VBO's if any are used.
-    state.unbindVertexBufferObject();
-    state.unbindElementBufferObject();
+#if OSG_MIN_VERSION_REQUIRED(3,5,6)
+    if (!state.useVertexArrayObject(_useVertexArrayObject) || state.getCurrentVertexArrayState()->getRequiresSetArrays())
+#endif
+    {
+        state.unbindVertexBufferObject();
+        state.unbindElementBufferObject();
+    }
 }
 
 void
diff --git a/src/osgEarthDrivers/engine_mp/MPTerrainEngineDriver.cpp b/src/osgEarthDrivers/engine_mp/MPTerrainEngineDriver.cpp
index d2cd67e..b8e754a 100644
--- a/src/osgEarthDrivers/engine_mp/MPTerrainEngineDriver.cpp
+++ b/src/osgEarthDrivers/engine_mp/MPTerrainEngineDriver.cpp
@@ -71,7 +71,6 @@ namespace osgEarth { namespace Drivers { namespace MPTerrainEngine
                 }
                 else
                 {
-                    MPTerrainEngineOptions terrainOpts;
                     OE_INFO << LC << "Activated!" << std::endl;
                     return ReadResult( new MPTerrainEngineNode() );
                 }
@@ -226,9 +225,10 @@ namespace osgEarth { namespace Drivers { namespace MPTerrainEngine
                     else
                     {   
                         // notify the Terrain interface of a new tile
-                        osg::Timer_t start = osg::Timer::instance()->tick();
-                        engineNode->getTerrain()->notifyTileAdded(key, node.get());
-                        osg::Timer_t end = osg::Timer::instance()->tick();
+                        // moved this to TileNodeRegistry::add
+                        //osg::Timer_t start = osg::Timer::instance()->tick();
+                        //engineNode->getTerrain()->notifyTileAdded(key, node.get());
+                        //osg::Timer_t end = osg::Timer::instance()->tick();
                     }
                     
                     return ReadResult( node.get(), ReadResult::FILE_LOADED );
diff --git a/src/osgEarthDrivers/engine_mp/MPTerrainEngineNode b/src/osgEarthDrivers/engine_mp/MPTerrainEngineNode
index 65e13ec..f22d939 100644
--- a/src/osgEarthDrivers/engine_mp/MPTerrainEngineNode
+++ b/src/osgEarthDrivers/engine_mp/MPTerrainEngineNode
@@ -20,7 +20,7 @@
 #define OSGEARTH_DRIVERS_MP_TERRAIN_ENGINE_ENGINE_NODE_H 1
 
 #include <osgEarth/TerrainEngineNode>
-#include <osgEarth/TextureCompositor>
+#include <osgEarth/TerrainResources>
 #include <osgEarth/Map>
 #include <osgEarth/Revisioning>
 #include <osgEarth/ThreadingUtils>
@@ -79,14 +79,13 @@ namespace osgEarth { namespace Drivers { namespace MPTerrainEngine
 
     public: // internal TerrainEngineNode
 
-        virtual void preInitialize( const Map* map, const TerrainOptions& options );
-        virtual void postInitialize( const Map* map, const TerrainOptions& options );
-        virtual const TerrainOptions& getTerrainOptions() const { return _terrainOptions; }
-        virtual osg::BoundingSphere computeBound() const;
+        void setMap(const Map* map, const TerrainOptions& options);
+        const TerrainOptions& getTerrainOptions() const { return _terrainOptions; }
 
     public: // osg::Node
 
         void traverse(osg::NodeVisitor& nv);
+        osg::BoundingSphere computeBound() const;
 
     public: // MapCallback adapter functions
 
@@ -100,12 +99,12 @@ namespace osgEarth { namespace Drivers { namespace MPTerrainEngine
         static void getEngineByUID( UID uid, osg::ref_ptr<MPTerrainEngineNode>& output );
 
     public:
-        class ElevationChangedCallback : public ElevationLayerCallback
+        class ElevationChangedCallback : public VisibleLayerCallback
         {
         public:
             ElevationChangedCallback( MPTerrainEngineNode* terrain );
 
-           virtual void onVisibleChanged( TerrainLayer* layer );
+            void onVisibleChanged(VisibleLayer* layer);
 
             MPTerrainEngineNode* _terrain;
             friend class MPTerrainEngineNode;
@@ -133,7 +132,7 @@ namespace osgEarth { namespace Drivers { namespace MPTerrainEngine
         void toggleElevationLayer( ElevationLayer* layer );
 
         void moveImageLayer( unsigned int oldIndex, unsigned int newIndex );
-        void moveElevationLayer( unsigned int oldIndex, unsigned int newIndex );
+        void moveElevationLayer( ElevationLayer* layer );
         
         void updateState(); 
 
diff --git a/src/osgEarthDrivers/engine_mp/MPTerrainEngineNode.cpp b/src/osgEarthDrivers/engine_mp/MPTerrainEngineNode.cpp
index 84c2789..1d02be4 100644
--- a/src/osgEarthDrivers/engine_mp/MPTerrainEngineNode.cpp
+++ b/src/osgEarthDrivers/engine_mp/MPTerrainEngineNode.cpp
@@ -55,9 +55,6 @@ using namespace osgEarth::Drivers::MPTerrainEngine;
 using namespace osgEarth;
 
 
-// TODO: bins don't work with SSDK. No idea why. Disable until further notice.
-//#define USE_RENDER_BINS 1
-
 //------------------------------------------------------------------------
 
 namespace
@@ -124,54 +121,6 @@ namespace
         }
     };
 
-#if 0
-    class NormalTexInstaller : public TerrainEngine::NodeCallback
-    {
-    public:
-        NormalTexInstaller(int unit) : _unit(unit) { }
-        
-    public: // TileNodeCallback
-        void operator()(const TileKey& key, osg::Node* node)
-        {
-            TileNode* tile = osgEarth::findTopMostNodeOfType<TileNode>(node);
-            if ( !tile )
-            {
-                OE_WARN << LC << "No tile " << key.str() << "\n";
-                return;
-            }
-
-            if ( !tile->getTileModel() )
-            {
-                OE_WARN << LC << "No tile model available for " << key.str() << "\n";
-                return;
-            }
-            
-            osg::StateSet* ss = node->getOrCreateStateSet();
-            osg::Texture* tex = tile->getTileModel()->getNormalTexture();
-            if ( tex )
-            {
-                ss->setTextureAttribute(_unit, tex);
-            }
-
-            osg::RefMatrixf* mat = tile->getModel()->getNormalTextureMatrix();
-            osg::Matrixf fmat;
-            if ( mat )
-            {
-                fmat = osg::Matrixf(*mat);
-            }
-            else
-            {
-                // special marker indicating that there's no valid normal texture.
-                fmat(0,0) = 0.0f;
-            }
-
-            ss->addUniform(new osg::Uniform("oe_tile_normalTexMatrix", fmat) );
-        }
-
-    private:
-        int _unit;
-    };
-#endif
 }
 
 //---------------------------------------------------------------------------
@@ -223,7 +172,7 @@ _terrain( terrain )
 }
 
 void
-MPTerrainEngineNode::ElevationChangedCallback::onVisibleChanged( TerrainLayer* layer )
+MPTerrainEngineNode::ElevationChangedCallback::onVisibleChanged(VisibleLayer* layer)
 {
     _terrain->refresh(true); // true => force a dirty
 }
@@ -246,23 +195,6 @@ _stateUpdateRequired  ( false )
     // unique ID for this engine:
     _uid = Registry::instance()->createUID();
 
-#ifdef USE_RENDER_BINS
-    // Register our render bins protos.
-    {
-        // Mutex because addRenderBinPrototype isn't thread-safe.
-        Threading::ScopedMutexLock lock(_renderBinMutex);
-
-        // generate uniquely named render bin prototypes for this engine:
-        _terrainRenderBinPrototype = new TerrainBin();
-        _terrainRenderBinPrototype->setName( Stringify() << "oe.TerrainBin." << _uid );
-        osgUtil::RenderBin::addRenderBinPrototype( _terrainRenderBinPrototype->getName(), _terrainRenderBinPrototype.get() );
-
-        _payloadRenderBinPrototype = new PayloadBin();
-        _payloadRenderBinPrototype->setName( Stringify() << "oe.PayloadBin." << _uid );
-        osgUtil::RenderBin::addRenderBinPrototype( _payloadRenderBinPrototype->getName(), _payloadRenderBinPrototype.get() );
-    }
-#endif
-
     // install an elevation callback so we can update elevation data
     _elevationCallback = new ElevationChangedCallback( this );
 
@@ -277,11 +209,6 @@ _stateUpdateRequired  ( false )
 
 MPTerrainEngineNode::~MPTerrainEngineNode()
 {
-#ifdef USE_RENDER_BINS
-    osgUtil::RenderBin::removeRenderBinPrototype( _terrainRenderBinPrototype.get() );
-    osgUtil::RenderBin::removeRenderBinPrototype( _payloadRenderBinPrototype.get() );
-#endif
-
     if ( _update_mapf )
     {
         delete _update_mapf;
@@ -292,7 +219,8 @@ bool
 MPTerrainEngineNode::includeShaderLibrary(VirtualProgram* vp)
 {
     static const char* libVS =
-        "#version 330\n"
+        "#version " GLSL_VERSION_STR "\n"
+        GLSL_DEFAULT_PRECISION_FLOAT "\n"
         "#pragma vp_name MP Terrain SDK (VS)\n"
 
         "in vec4 oe_terrain_attr; \n"
@@ -333,7 +261,8 @@ MPTerrainEngineNode::includeShaderLibrary(VirtualProgram* vp)
         "} \n";
 
     static const char* libFS =
-        "#version 330\n"
+        "#version " GLSL_VERSION_STR "\n"
+        GLSL_DEFAULT_PRECISION_FLOAT "\n"
         "#pragma vp_name MP Terrain SDK (FS)\n"
 
         "uniform vec4 oe_tile_key; \n"
@@ -396,21 +325,15 @@ MPTerrainEngineNode::includeShaderLibrary(VirtualProgram* vp)
 }
 
 void
-MPTerrainEngineNode::preInitialize( const Map* map, const TerrainOptions& options )
-{
-    TerrainEngineNode::preInitialize( map, options );
-    //nop.
-}
-
-void
-MPTerrainEngineNode::postInitialize( const Map* map, const TerrainOptions& options )
+MPTerrainEngineNode::setMap(const Map* map, const TerrainOptions& options)
 {
-    TerrainEngineNode::postInitialize( map, options );
+    // First invoke the base class:
+    TerrainEngineNode::setMap(map, options);
 
     // Initialize the map frames. We need one for the update thread and one for the
     // cull thread. Someday we can detect whether these are actually the same thread
     // (depends on the viewer's threading mode).
-    _update_mapf = new MapFrame( map, Map::ENTIRE_MODEL );
+    _update_mapf = new MapFrame( map );
 
     // merge in the custom options:
     _terrainOptions.merge( options );
@@ -419,7 +342,7 @@ MPTerrainEngineNode::postInitialize( const Map* map, const TerrainOptions& optio
     // if requested in the options. Revision tracking lets the registry notify all
     // live tiles of the current map revision so they can inrementally update
     // themselves if necessary.
-    _liveTiles = new TileNodeRegistry("live");
+    _liveTiles = new TileNodeRegistry("live", this->getTerrain());
     _liveTiles->setRevisioningEnabled( _terrainOptions.incrementalUpdate() == true );
     _liveTiles->setMapRevision( _update_mapf->getRevision() );
 
@@ -465,12 +388,12 @@ MPTerrainEngineNode::postInitialize( const Map* map, const TerrainOptions& optio
     _batchUpdateInProgress = true;
 
     ElevationLayerVector elevationLayers;
-    map->getElevationLayers( elevationLayers );
+    map->getLayers( elevationLayers );
     for( ElevationLayerVector::const_iterator i = elevationLayers.begin(); i != elevationLayers.end(); ++i )
         addElevationLayer( i->get() );
 
     ImageLayerVector imageLayers;
-    map->getImageLayers( imageLayers );
+    map->getLayers( imageLayers );
     for( ImageLayerVector::iterator i = imageLayers.begin(); i != imageLayers.end(); ++i )
         addImageLayer( i->get() );
 
@@ -557,11 +480,7 @@ MPTerrainEngineNode::onMapInfoEstablished( const MapInfo& mapInfo )
 osg::StateSet*
 MPTerrainEngineNode::getTerrainStateSet()
 {
-#ifdef USE_RENDER_BINS
-    return _terrainRenderBinPrototype->getStateSet();
-#else
     return _terrain ? _terrain->getOrCreateStateSet() : 0L;
-#endif
 }
 
 namespace
@@ -619,13 +538,8 @@ MPTerrainEngineNode::dirtyTerrain()
     // Clear out the tile registry:
     _liveTiles->releaseAll(_releaser.get());
 
-
-#ifdef USE_RENDER_BINS
-    _terrain->getOrCreateStateSet()->setRenderBinDetails( 0, _terrainRenderBinPrototype->getName() );
-    _terrain->getOrCreateStateSet()->setNestRenderBins(false);
-#else
+    // minimizes depth overdraw
     _terrain->getOrCreateStateSet()->setRenderBinDetails(0, "SORT_FRONT_TO_BACK");
-#endif
 
     this->addChild( _terrain );
     // Build the first level of the terrain.
@@ -639,11 +553,11 @@ MPTerrainEngineNode::dirtyTerrain()
         _update_mapf->getProfile()->getAllKeysAtLOD( *_terrainOptions.firstLOD(), keys );
 
         // create a root node for each root tile key.
-        OE_INFO << LC << "Creating " << keys.size() << " root keys.." << std::endl;
+        OE_DEBUG << LC << "Creating " << keys.size() << " root keys.." << std::endl;
+
+        osg::Group* root = new osg::Group;
+        _terrain->addChild( root );        
 
-        TilePagedLOD* root = new TilePagedLOD( _uid, _liveTiles, _releaser.get() );
-        root->setRangeFactor(_terrainOptions.minTileRangeFactor().get());
-        _terrain->addChild( root );
 
         osg::ref_ptr<osgDB::Options> dbOptions = Registry::instance()->cloneOrCreateOptions();
 
@@ -657,9 +571,6 @@ MPTerrainEngineNode::dirtyTerrain()
             if ( node.valid() )
             {
                 root->addChild( node.get() );
-                root->setRange( child++, 0.0f, FLT_MAX );
-                root->setCenter( node->getBound().center() );
-                root->setNumChildrenThatCannotBeExpired( child );
             }
             else
             {
@@ -729,10 +640,16 @@ MPTerrainEngineNode::getKeyNodeFactory()
         bool optimizeTriangleOrientation = 
             getMap()->getMapOptions().elevationInterpolation() != INTERP_TRIANGULATE;
 
+        MaskLayerVector maskLayers;
+        _update_mapf->getLayers(maskLayers);
+
+        ModelLayerVector modelLayers;
+        _update_mapf->getLayers(modelLayers);
+
         // A compiler specific to this thread:
         TileModelCompiler* compiler = new TileModelCompiler(
-            _update_mapf->terrainMaskLayers(),
-            _update_mapf->modelLayers(),
+            maskLayers,
+            modelLayers,
             _primaryUnit,
             optimizeTriangleOrientation,
             _terrainOptions );
@@ -801,7 +718,7 @@ MPTerrainEngineNode::createTile( const TileKey& key )
 
     // Request a heightfield from the map, falling back on lower resolution tiles
     int tileSize = _terrainOptions.tileSize().get();    
-    osg::ref_ptr<osg::HeightField> hf = HeightFieldUtils::createReferenceHeightField( key.getExtent(), tileSize, tileSize );
+    osg::ref_ptr<osg::HeightField> hf = HeightFieldUtils::createReferenceHeightField( key.getExtent(), tileSize, tileSize, 0u );
 
     TileKey sampleKey = key;
     bool populated = false;
@@ -826,20 +743,26 @@ MPTerrainEngineNode::createTile( const TileKey& key )
     {
         // We have no heightfield so just create a reference heightfield.
         int tileSize = _terrainOptions.tileSize().get();
-        hf = HeightFieldUtils::createReferenceHeightField( key.getExtent(), tileSize, tileSize );
+        hf = HeightFieldUtils::createReferenceHeightField( key.getExtent(), tileSize, tileSize, 0u );
         sampleKey = key;
     }
 
     model->_elevationData = TileModel::ElevationData(
-        hf,
+        hf.get(),
         GeoLocator::createForKey( sampleKey, mapInfo ),
         false );        
 
     bool optimizeTriangleOrientation = getMap()->getMapOptions().elevationInterpolation() != INTERP_TRIANGULATE;
 
+    MaskLayerVector maskLayers;
+    _update_mapf->getLayers(maskLayers);
+
+    ModelLayerVector modelLayers;
+    _update_mapf->getLayers(modelLayers);
+
     osg::ref_ptr<TileModelCompiler> compiler = new TileModelCompiler(
-        _update_mapf->terrainMaskLayers(),
-        _update_mapf->modelLayers(),
+        maskLayers,
+        modelLayers,
         _primaryUnit,
         optimizeTriangleOrientation,
         _terrainOptions );
@@ -881,30 +804,31 @@ MPTerrainEngineNode::onMapModelChanged( const MapModelChange& change )
             // then apply the actual change:
             switch( change.getAction() )
             {
-            case MapModelChange::ADD_IMAGE_LAYER:
-                addImageLayer( change.getImageLayer() );
-                break;
-            case MapModelChange::REMOVE_IMAGE_LAYER:
-                removeImageLayer( change.getImageLayer() );
-                break;
-            case MapModelChange::ADD_ELEVATION_LAYER:
-                addElevationLayer( change.getElevationLayer() );
+            case MapModelChange::ADD_LAYER:
+                if (change.getImageLayer())
+                    addImageLayer(change.getImageLayer());
+                else if (change.getElevationLayer())
+                    addElevationLayer(change.getElevationLayer());
                 break;
-            case MapModelChange::REMOVE_ELEVATION_LAYER:
-                removeElevationLayer( change.getElevationLayer() );
-                break;
-            case MapModelChange::MOVE_IMAGE_LAYER:
-                moveImageLayer( change.getFirstIndex(), change.getSecondIndex() );
+
+            case MapModelChange::REMOVE_LAYER:
+                if (change.getImageLayer())
+                    removeImageLayer(change.getImageLayer());
+                else if (change.getElevationLayer())
+                    removeElevationLayer(change.getElevationLayer());
                 break;
-            case MapModelChange::MOVE_ELEVATION_LAYER:
-                moveElevationLayer( change.getFirstIndex(), change.getSecondIndex() );
+
+            case MapModelChange::MOVE_LAYER:
+                if (change.getImageLayer())
+                    moveImageLayer(change.getFirstIndex(), change.getSecondIndex());
+                else if (change.getElevationLayer())
+                    moveElevationLayer(change.getElevationLayer());
                 break;
+
             case MapModelChange::TOGGLE_ELEVATION_LAYER:
                 toggleElevationLayer( change.getElevationLayer() );
                 break;
-            case MapModelChange::ADD_MODEL_LAYER:
-            case MapModelChange::REMOVE_MODEL_LAYER:
-            case MapModelChange::MOVE_MODEL_LAYER:
+
             default: 
                 break;
             }
@@ -988,7 +912,11 @@ MPTerrainEngineNode::addElevationLayer( ElevationLayer* layer )
 
     layer->addCallback( _elevationCallback.get() );
 
-    refresh();
+    // only need to refresh the terrain if the layer is visible
+    if (layer->getVisible())
+    {
+        refresh();
+    }
 }
 
 void
@@ -999,13 +927,21 @@ MPTerrainEngineNode::removeElevationLayer( ElevationLayer* layerRemoved )
 
     layerRemoved->removeCallback( _elevationCallback.get() );
 
-    refresh();
+    // only need to refresh the terrain if the layer was visible
+    if (layerRemoved->getVisible())
+    {
+        refresh();
+    }
 }
 
 void
-MPTerrainEngineNode::moveElevationLayer( unsigned int oldIndex, unsigned int newIndex )
-{
-    refresh();
+MPTerrainEngineNode::moveElevationLayer(ElevationLayer* layer)
+{    
+    // only need to refresh the terrain if the layer is visible
+    if (layer->getVisible())
+    {
+        refresh();
+    }
 }
 
 void
@@ -1054,7 +990,7 @@ MPTerrainEngineNode::updateState()
 
             package.replace( "$MP_PRIMARY_UNIT",   Stringify() << _primaryUnit );
             package.replace( "$MP_SECONDARY_UNIT", Stringify() << (_secondaryUnit>=0?_secondaryUnit:0) );
-
+                
             package.define( "MP_USE_BLENDING", (_terrainOptions.enableBlending() == true) );
 
             package.load( vp, package.EngineVertexModel );
@@ -1073,6 +1009,12 @@ MPTerrainEngineNode::updateState()
             Color terrainColor = _terrainOptions.color().getOrUse( Color(1,1,1,-1) );
             terrainStateSet->addUniform(new osg::Uniform("oe_terrain_color", terrainColor));
 
+            // shadowing?
+            if (_terrainOptions.castShadows() == true)
+            {
+                terrainStateSet->setDefine("OE_TERRAIN_CAST_SHADOWS");
+            }
+
             if ( _update_mapf )
             {
                 // assemble color filter code snippets.
@@ -1094,11 +1036,14 @@ MPTerrainEngineNode::updateState()
                     const char* I = "    ";
 
                     // second, install the per-layer color filter functions AND shared layer bindings.
+                    ImageLayerVector imageLayers;
+                    _update_mapf->getLayers(imageLayers);
+
                     bool ifStarted = false;
-                    int numImageLayers = _update_mapf->imageLayers().size();
+                    int numImageLayers = imageLayers.size();
                     for( int i=0; i<numImageLayers; ++i )
                     {
-                        ImageLayer* layer = _update_mapf->getImageLayerAt(i);
+                        ImageLayer* layer = imageLayers[i].get();
                         if ( layer->getEnabled() )
                         {
                             // install Color Filter function calls:
@@ -1182,7 +1127,7 @@ MPTerrainEngineNode::updateState()
             // default min/max range uniforms. (max < min means ranges are disabled)
             terrainStateSet->addUniform( new osg::Uniform("oe_layer_minRange", 0.0f) );
             terrainStateSet->addUniform( new osg::Uniform("oe_layer_maxRange", FLT_MAX) );
-            terrainStateSet->addUniform( new osg::Uniform("oe_layer_attenuationRange", _terrainOptions.attentuationDistance().get()) );
+            terrainStateSet->addUniform( new osg::Uniform("oe_layer_attenuationRange", _terrainOptions.attenuationDistance().get()) );
             
             terrainStateSet->getOrCreateUniform(
                 "oe_min_tile_range_factor",
@@ -1195,10 +1140,13 @@ MPTerrainEngineNode::updateState()
             // assign the uniforms for each shared layer.
             if ( _update_mapf )
             {
-                int numImageLayers = _update_mapf->imageLayers().size();
+                ImageLayerVector imageLayers;
+                _update_mapf->getLayers(imageLayers);
+
+                int numImageLayers = imageLayers.size();
                 for( int i=0; i<numImageLayers; ++i )
                 {
-                    ImageLayer* layer = _update_mapf->getImageLayerAt(i);
+                    ImageLayer* layer = imageLayers[i].get();
                     if ( layer->getEnabled() && layer->isShared() )
                     {
                         terrainStateSet->addUniform( new osg::Uniform(
diff --git a/src/osgEarthDrivers/engine_mp/MPTerrainEngineOptions b/src/osgEarthDrivers/engine_mp/MPTerrainEngineOptions
index 540de85..42d3f7f 100644
--- a/src/osgEarthDrivers/engine_mp/MPTerrainEngineOptions
+++ b/src/osgEarthDrivers/engine_mp/MPTerrainEngineOptions
@@ -22,6 +22,7 @@
 #include <osgEarth/Common>
 #include <osgEarth/TerrainOptions>
 #include <osgEarthSymbology/Color>
+#include <osg/LOD>
 
 namespace osgEarth { namespace Drivers { namespace MPTerrainEngine
 {
@@ -37,14 +38,12 @@ namespace osgEarth { namespace Drivers { namespace MPTerrainEngine
         MPTerrainEngineOptions( const ConfigOptions& options =ConfigOptions() ) : TerrainOptions( options ),
             _skirtRatio        ( 0.05 ),
             _quickRelease      ( true ),
-            _normalizeEdges    ( false ),
-            _rangeMode         ( osg::LOD::DISTANCE_FROM_EYE_POINT ),
-            _tilePixelSize     ( 256 ),
+            _normalizeEdges    ( true ),
             _color             ( Color::White ),
             _incrementalUpdate ( false ),
             _smoothing         ( false ),
             _normalMaps        ( false ),
-            _adaptivePolarRangeFactor( false )
+            _adaptivePolarRangeFactor( true )
          {
             setDriver( "mp" );
             fromConfig( _conf );
@@ -68,14 +67,6 @@ namespace osgEarth { namespace Drivers { namespace MPTerrainEngine
         optional<bool>& normalizeEdges() { return _normalizeEdges; }
         const optional<bool>& normalizeEdges() const { return _normalizeEdges; }
 
-        /** Mode to use when calculating LOD switching distances */
-        optional<osg::LOD::RangeMode>& rangeMode() { return _rangeMode;}
-        const optional<osg::LOD::RangeMode>& rangeMode() const { return _rangeMode;}
-
-        /** The size of the tile, in pixels, when using rangeMode = PIXEL_SIZE_ON_SCREEN */
-        optional<float>& tilePixelSize() { return _tilePixelSize; }
-        const optional<float>& tilePixelSize() const { return _tilePixelSize; }
-
         /** The color of the globe surface where no images are applied */
         optional<Color>& color() { return _color; }
         const optional<Color>& color() const { return _color; }
@@ -110,17 +101,14 @@ namespace osgEarth { namespace Drivers { namespace MPTerrainEngine
     protected:
         virtual Config getConfig() const {
             Config conf = TerrainOptions::getConfig();
-            conf.updateIfSet( "skirt_ratio", _skirtRatio );
-            conf.updateIfSet( "quick_release_gl_objects", _quickRelease );
-            conf.updateIfSet( "normalize_edges", _normalizeEdges);
-            conf.updateIfSet( "tile_pixel_size", _tilePixelSize );
-            conf.updateIfSet( "range_mode", "PIXEL_SIZE_ON_SCREEN", _rangeMode, osg::LOD::PIXEL_SIZE_ON_SCREEN );
-            conf.updateIfSet( "range_mode", "DISTANCE_FROM_EYE_POINT", _rangeMode, osg::LOD::DISTANCE_FROM_EYE_POINT);
-            conf.updateIfSet( "color", _color );
-            conf.updateIfSet( "incremental_update", _incrementalUpdate );
-            conf.updateIfSet( "elevation_smoothing", _smoothing );
-            conf.updateIfSet( "normal_maps", _normalMaps );
-            conf.updateIfSet( "adaptive_polar_range_factor", _adaptivePolarRangeFactor);
+            conf.set( "skirt_ratio", _skirtRatio );
+            conf.set( "quick_release_gl_objects", _quickRelease );
+            conf.set( "normalize_edges", _normalizeEdges);
+            conf.set( "color", _color );
+            conf.set( "incremental_update", _incrementalUpdate );
+            conf.set( "elevation_smoothing", _smoothing );
+            conf.set( "normal_maps", _normalMaps );
+            conf.set( "adaptive_polar_range_factor", _adaptivePolarRangeFactor);
 
             return conf;
         }
@@ -135,10 +123,6 @@ namespace osgEarth { namespace Drivers { namespace MPTerrainEngine
             conf.getIfSet( "skirt_ratio", _skirtRatio );
             conf.getIfSet( "quick_release_gl_objects", _quickRelease );
             conf.getIfSet( "normalize_edges", _normalizeEdges );
-            conf.getIfSet( "tile_pixel_size", _tilePixelSize );
-
-            conf.getIfSet( "range_mode", "PIXEL_SIZE_ON_SCREEN", _rangeMode, osg::LOD::PIXEL_SIZE_ON_SCREEN );
-            conf.getIfSet( "range_mode", "DISTANCE_FROM_EYE_POINT", _rangeMode, osg::LOD::DISTANCE_FROM_EYE_POINT);
             conf.getIfSet( "color", _color );
             conf.getIfSet( "incremental_update", _incrementalUpdate );
             conf.getIfSet( "elevation_smoothing", _smoothing );
@@ -150,8 +134,6 @@ namespace osgEarth { namespace Drivers { namespace MPTerrainEngine
         optional<bool>                _quickRelease;
         optional<float>               _lodFallOff;
         optional<bool>                _normalizeEdges;
-        optional<osg::LOD::RangeMode> _rangeMode;
-        optional<float>               _tilePixelSize;
         optional<Color>               _color;
         optional<bool>                _incrementalUpdate;
         optional<bool>                _smoothing;
diff --git a/src/osgEarthDrivers/engine_mp/SingleKeyNodeFactory.cpp b/src/osgEarthDrivers/engine_mp/SingleKeyNodeFactory.cpp
index 25bf2bf..81fe16b 100644
--- a/src/osgEarthDrivers/engine_mp/SingleKeyNodeFactory.cpp
+++ b/src/osgEarthDrivers/engine_mp/SingleKeyNodeFactory.cpp
@@ -33,6 +33,8 @@
 
 #include <osgUtil/CullVisitor>
 
+#include <osg/LOD>
+
 using namespace osgEarth::Drivers::MPTerrainEngine;
 using namespace osgEarth;
 
@@ -100,7 +102,7 @@ SingleKeyNodeFactory::createTile(TileModel*        model,
     }
 #else
     // compile the model into a node:
-    TileNode* tileNode = _modelCompiler->compile(model, _frame, progress);
+    osg::ref_ptr<TileNode> tileNode = _modelCompiler->compile(model, _frame, progress);
 #endif
 
     // see if this tile might have children.
@@ -115,16 +117,16 @@ SingleKeyNodeFactory::createTile(TileModel*        model,
         osg::BoundingSphere bs = tileNode->getBound();
         TilePagedLOD* plod = new TilePagedLOD( _engine->getUID(), _liveTiles.get(), _releaser.get() );
         plod->setCenter  ( bs.center() );
-        plod->addChild   ( tileNode );
+        plod->addChild   ( tileNode.get() );
         plod->setFileName( 1, Stringify() << tileNode->getKey().str() << "." << _engine->getUID() << ".osgearth_engine_mp_tile" );
         
         double rangeFactor = _options.minTileRangeFactor().get();
-        if (_options.adaptivePolarRangeFactor() == true)
-        {
-            double lat = model->_tileKey.getExtent().yMin() < 0 ? -model->_tileKey.getExtent().yMax() : model->_tileKey.getExtent().yMin();
-            double latRad = osg::DegreesToRadians(lat);
-            rangeFactor -= (rangeFactor - 1.0)*sin(latRad)*sin(latRad);
-        }
+        //if (_options.adaptivePolarRangeFactor() == true)
+        //{
+        //    double lat = model->_tileKey.getExtent().yMin() < 0 ? -model->_tileKey.getExtent().yMax() : model->_tileKey.getExtent().yMin();
+        //    double latRad = osg::DegreesToRadians(lat);
+        //    rangeFactor -= (rangeFactor - 1.0)*sin(latRad)*sin(latRad);
+        //}
         plod->setRangeFactor(rangeFactor);
 
         // Setup expiration.
@@ -143,32 +145,36 @@ SingleKeyNodeFactory::createTile(TileModel*        model,
             //Compute the min range based on the 2D size of the tile
             GeoExtent extent = model->_tileKey.getExtent();
             double radius = 0.0;
-            
-#if 0
-            // Test code to use the equitorial radius so that all of the tiles at the same level
-            // have the same range.  This will make the poles page in more appropriately.
-            if (_frame.getMapInfo().isGeocentric())
+      
+            GeoPoint lowerLeft(extent.getSRS(), extent.xMin(), extent.yMin(), 0.0, ALTMODE_ABSOLUTE);
+            GeoPoint upperRight(extent.getSRS(), extent.xMax(), extent.yMax(), 0.0, ALTMODE_ABSOLUTE);
+            osg::Vec3d ll, ur;
+            lowerLeft.toWorld( ll );
+            upperRight.toWorld( ur );
+            double radiusDiag = (ur - ll).length() / 2.0;
+
+            if (_options.adaptivePolarRangeFactor() == true )
             {
-                GeoExtent equatorialExtent(
-                extent.getSRS(),
-                extent.west(),
-                -extent.height()/2.0,
-                extent.east(),
-                extent.height()/2.0 );
-                radius = equatorialExtent.getBoundingGeoCircle().getRadius();
+                GeoPoint left(extent.getSRS(), extent.xMin(), extent.yMin()+extent.height()*0.5, 0.0, ALTMODE_ABSOLUTE);
+                GeoPoint right(extent.getSRS(), extent.xMax(), extent.yMin()+extent.height()*0.5, 0.0, ALTMODE_ABSOLUTE);
+                osg::Vec3d l, r;
+                left.toWorld(l);
+                right.toWorld(r);
+                double radiusHoriz = 1.4142 * (r - l).length() / 2.0;
+                
+                double lat = model->_tileKey.getExtent().yMin() < 0 ? -model->_tileKey.getExtent().yMax() : model->_tileKey.getExtent().yMin();
+                double latRad = osg::DegreesToRadians(lat);
+
+                // mix between diagonal radius and horizontal radius based on latitude
+                double t = cos(latRad);
+                t = 1.0-(1.0-t)*(1.0-t);    // decelerate t to weight the mix in favor of equator (diag radius)
+                radius = t*radiusDiag + (1.0-t)*radiusHoriz;
             }
             else
-#endif
             {
-                GeoPoint lowerLeft(extent.getSRS(), extent.xMin(), extent.yMin(), 0.0, ALTMODE_ABSOLUTE);
-                GeoPoint upperRight(extent.getSRS(), extent.xMax(), extent.yMax(), 0.0, ALTMODE_ABSOLUTE);
-                osg::Vec3d ll, ur;
-                lowerLeft.toWorld( ll );
-                upperRight.toWorld( ur );
-                radius = (ur - ll).length() / 2.0;
+                radius = radiusDiag;
             }
           
-            //float minRange = (float)(radius * _options.minTileRangeFactor().value());
             float minRange = radius;
 
             plod->setRange( 0, minRange, FLT_MAX );
@@ -217,7 +223,7 @@ SingleKeyNodeFactory::createTile(TileModel*        model,
     }
     else
     {
-        result = tileNode;
+        result = tileNode.release();
     }
 
     return result;
diff --git a/src/osgEarthDrivers/engine_mp/TileModel.cpp b/src/osgEarthDrivers/engine_mp/TileModel.cpp
index 5cb12d4..c8ecdfc 100644
--- a/src/osgEarthDrivers/engine_mp/TileModel.cpp
+++ b/src/osgEarthDrivers/engine_mp/TileModel.cpp
@@ -159,8 +159,8 @@ _order       ( order ),
 _locator     ( locator ),
 _fallbackData( fallbackData )
 {
-    osg::Texture::FilterMode minFilter = layer->getImageLayerOptions().minFilter().get();
-    osg::Texture::FilterMode magFilter = layer->getImageLayerOptions().magFilter().get();
+    osg::Texture::FilterMode minFilter = layer->options().minFilter().get();
+    osg::Texture::FilterMode magFilter = layer->options().magFilter().get();
 
     if (image->r() <= 1)
     {
diff --git a/src/osgEarthDrivers/engine_mp/TileModelCompiler b/src/osgEarthDrivers/engine_mp/TileModelCompiler
index 5267831..9473b62 100644
--- a/src/osgEarthDrivers/engine_mp/TileModelCompiler
+++ b/src/osgEarthDrivers/engine_mp/TileModelCompiler
@@ -30,6 +30,8 @@
 #include <osgEarth/Map>
 #include <osgEarth/Locators>
 #include <osgEarth/Progress>
+#include <osgEarth/MaskLayer>
+#include <osgEarth/ModelLayer>
 
 #include <osg/Node>
 #include <osg/StateSet>
@@ -96,8 +98,8 @@ namespace osgEarth { namespace Drivers { namespace MPTerrainEngine
             ProgressCallback* progress);
 
     protected:
-        const MaskLayerVector&                    _maskLayers;
-        const ModelLayerVector&                   _modelLayers;
+        MaskLayerVector                           _maskLayers;
+        ModelLayerVector                          _modelLayers;
         int                                       _textureImageUnit;
         bool                                      _optimizeTriOrientation;
         const MPTerrainEngineOptions&             _options;
diff --git a/src/osgEarthDrivers/engine_mp/TileModelCompiler.cpp b/src/osgEarthDrivers/engine_mp/TileModelCompiler.cpp
index da9a94c..6253399 100644
--- a/src/osgEarthDrivers/engine_mp/TileModelCompiler.cpp
+++ b/src/osgEarthDrivers/engine_mp/TileModelCompiler.cpp
@@ -260,7 +260,7 @@ namespace
             {
                 osg::ref_ptr<MPGeometry> stitchGeom = new MPGeometry( d.model->_tileKey, d.frame, d.textureImageUnit );
                 stitchGeom->setName("stitchGeom");
-                d.maskRecords.push_back( MaskRecord(boundary, min_ndc, max_ndc, stitchGeom) );
+                d.maskRecords.push_back( MaskRecord(boundary, min_ndc, max_ndc, stitchGeom.get()) );
             }
         }
     }
@@ -354,26 +354,26 @@ namespace
         // allocate and assign vertices
         d.surfaceVerts = new osg::Vec3Array();
         d.surfaceVerts->reserve( d.numVerticesInSurface );
-        d.surface->setVertexArray( d.surfaceVerts );
+        d.surface->setVertexArray( d.surfaceVerts.get() );
 
         // allocate and assign normals
         d.normals = new osg::Vec3Array();
         d.normals->reserve( d.numVerticesInSurface );
-        d.surface->setNormalArray( d.normals );
+        d.surface->setNormalArray( d.normals.get() );
         d.surface->setNormalBinding( osg::Geometry::BIND_PER_VERTEX );
 
         // vertex attribution
         // for each vertex, a vec4 containing a unit extrusion vector in [0..2] and the raw elevation in [3]
         d.surfaceAttribs = new osg::Vec4Array();
         d.surfaceAttribs->reserve( d.numVerticesInSurface );
-        d.surface->setVertexAttribArray( osg::Drawable::ATTRIBUTE_6, d.surfaceAttribs );
+        d.surface->setVertexAttribArray( osg::Drawable::ATTRIBUTE_6, d.surfaceAttribs.get() );
         d.surface->setVertexAttribBinding( osg::Drawable::ATTRIBUTE_6, osg::Geometry::BIND_PER_VERTEX );
         d.surface->setVertexAttribNormalize( osg::Drawable::ATTRIBUTE_6, false );
 
         // for each vertex, index 0 holds the interpolated elevation from the lower lod (for morphing)
         d.surfaceAttribs2 = new osg::Vec4Array();
         d.surfaceAttribs2->reserve( d.numVerticesInSurface );
-        d.surface->setVertexAttribArray( osg::Drawable::ATTRIBUTE_7, d.surfaceAttribs2 );
+        d.surface->setVertexAttribArray( osg::Drawable::ATTRIBUTE_7, d.surfaceAttribs2.get() );
         d.surface->setVertexAttribBinding( osg::Drawable::ATTRIBUTE_7, osg::Geometry::BIND_PER_VERTEX );
         d.surface->setVertexAttribNormalize( osg::Drawable::ATTRIBUTE_7, false );
         
@@ -425,6 +425,9 @@ namespace
 
 #else // not USE_TEXCOORD_CACHE
         d.renderTileCoords = new osg::Vec2Array();
+#if OSG_MIN_VERSION_REQUIRED(3,5,6)
+        if(osg::DisplaySettings::instance()->getVertexBufferHint() == osg::DisplaySettings::VERTEX_ARRAY_OBJECT) d.renderTileCoords->setVertexBufferObject( new osg::VertexBufferObject() );
+#endif
         d.renderTileCoords->reserve( d.numVerticesInSurface );
         d.ownsTileCoords = true;
 #endif
@@ -470,6 +473,9 @@ namespace
 
 #else // not USE_TEXCOORD_CACHE
                     r._texCoords = new osg::Vec2Array();
+#if OSG_MIN_VERSION_REQUIRED(3,5,6)
+                    if(osg::DisplaySettings::instance()->getVertexBufferHint() == osg::DisplaySettings::VERTEX_ARRAY_OBJECT) r._texCoords->setVertexBufferObject( new osg::VertexBufferObject() );
+#endif
                     r._texCoords->reserve( d.numVerticesInSurface );
                     r._ownsTexCoords = true;
 #endif
@@ -581,7 +587,7 @@ namespace
 
                 if ( hf )
                 {
-                    validValue = d.model->_elevationData.getHeight( ndc, d.model->_tileLocator, heightValue, INTERP_TRIANGULATE );
+                    validValue = d.model->_elevationData.getHeight( ndc, d.model->_tileLocator.get(), heightValue, INTERP_TRIANGULATE );
                 }
 
                 ndc.z() = heightValue * d.heightScale + d.heightOffset;
@@ -1055,10 +1061,10 @@ namespace
 
             osg::ref_ptr<osg::Vec3Array> stitch_verts = new osg::Vec3Array();
             stitch_verts->reserve(trig->getInputPointArray()->size());
-            stitch_geom->setVertexArray(stitch_verts);
+            stitch_geom->setVertexArray(stitch_verts.get());
 
             osg::ref_ptr<osg::Vec3Array> stitch_norms = new osg::Vec3Array(trig->getInputPointArray()->size());
-            stitch_geom->setNormalArray( stitch_norms );
+            stitch_geom->setNormalArray( stitch_norms.get() );
             stitch_geom->setNormalBinding( osg::Geometry::BIND_PER_VERTEX );
 
 
@@ -1066,14 +1072,14 @@ namespace
             // for each vertex, a vec4 containing a unit extrusion vector in [0..2] and the raw elevation in [3]
             osg::ref_ptr<osg::Vec4Array> surfaceAttribs = new osg::Vec4Array();
             surfaceAttribs->reserve( trig->getInputPointArray()->size() );
-            stitch_geom->setVertexAttribArray( osg::Drawable::ATTRIBUTE_6, surfaceAttribs );
+            stitch_geom->setVertexAttribArray( osg::Drawable::ATTRIBUTE_6, surfaceAttribs.get() );
             stitch_geom->setVertexAttribBinding( osg::Drawable::ATTRIBUTE_6, osg::Geometry::BIND_PER_VERTEX );
             stitch_geom->setVertexAttribNormalize( osg::Drawable::ATTRIBUTE_6, false );
 
             // for each vertex, index 0 holds the interpolated elevation from the lower lod (for morphing)
             osg::ref_ptr<osg::Vec4Array> surfaceAttribs2 = new osg::Vec4Array();
             surfaceAttribs2->reserve( trig->getInputPointArray()->size() );
-            stitch_geom->setVertexAttribArray( osg::Drawable::ATTRIBUTE_7, surfaceAttribs2 );
+            stitch_geom->setVertexAttribArray( osg::Drawable::ATTRIBUTE_7, surfaceAttribs2.get() );
             stitch_geom->setVertexAttribBinding( osg::Drawable::ATTRIBUTE_7, osg::Geometry::BIND_PER_VERTEX );
             stitch_geom->setVertexAttribNormalize( osg::Drawable::ATTRIBUTE_7, false );
 
@@ -1120,7 +1126,7 @@ namespace
                 // Calculate and store the "old height", i.e the height value from
                 // the parent LOD.
                 float heightValue = 0.0;
-                d.model->_elevationData.getHeight( local_zero, d.model->_tileLocator, heightValue, INTERP_TRIANGULATE );
+                d.model->_elevationData.getHeight( local_zero, d.model->_tileLocator.get(), heightValue, INTERP_TRIANGULATE );
 
                 float     oldHeightValue = heightValue;
                 osg::Vec3 oldNormal;
@@ -1177,7 +1183,7 @@ namespace
             osg::ref_ptr<osg::DrawElementsUInt> tris = trig->getTriangles();
             if ( tris && tris->getNumIndices() >= 3 )
             {
-                stitch_geom->addPrimitiveSet(tris);
+                stitch_geom->addPrimitiveSet(tris.get());
             }
 
             // Finally, add it to the geode.
@@ -1969,7 +1975,7 @@ namespace
         // in the case of full-masking, this will be empty
         if ( elements->getNumIndices() > 0 )
         {
-            d.surface->insertPrimitiveSet(0, elements); // because we always want this first.
+            d.surface->insertPrimitiveSet(0, elements.get()); // because we always want this first.
         }
     }
 
@@ -1987,6 +1993,13 @@ namespace
         if ( d.renderTileCoords.valid() )
             d.surface->_tileCoords = d.renderTileCoords;
 
+        // install the tile coordinates in the geometry.
+        if ( d.surface->_tileCoords.valid() )
+        {
+            int index = d.surface->getTexCoordArrayList().size();
+            d.surface->setTexCoordArray( index, d.surface->_tileCoords.get() );
+        }
+
         // install the render data for each layer:
         for( RenderLayerVector::const_iterator r = d.renderLayers.begin(); r != d.renderLayers.end(); ++r )
         {
@@ -2059,13 +2072,6 @@ namespace
             }
         }
 
-        // install the tile coordinates in the geometry.
-        if ( d.surface->_tileCoords.valid() )
-        {
-            int index = d.surface->getTexCoordArrayList().size();
-            d.surface->setTexCoordArray( index, d.surface->_tileCoords.get() );
-        }
-
         // elevation texture.
         d.surface->_elevTex = d.model->_elevationTexture.get();
     }
@@ -2243,8 +2249,13 @@ TileModelCompiler::compile(TileModel*        model,
     // camera matricies when DRAW overlaps the next frame's CULL. Please see my comments
     // in DrapingTechnique.cpp for more information.
     // NOTE: cannot set this until optimizations (above) are complete
+
+    // Using VAOs on MPGeometry doesn't work if the data variance is set to DYNAMIC,
+    // so for now, don't run with for VAO testing
+//#ifndef USE_VAO
     SetDataVarianceVisitor sdv( osg::Object::DYNAMIC );
     tile->accept( sdv );
+//#endif
 
     osg::ComputeBoundsVisitor cbv;
     d.surfaceGeode->accept(cbv);
diff --git a/src/osgEarthDrivers/engine_mp/TileModelFactory.cpp b/src/osgEarthDrivers/engine_mp/TileModelFactory.cpp
index c279ad3..b42df98 100644
--- a/src/osgEarthDrivers/engine_mp/TileModelFactory.cpp
+++ b/src/osgEarthDrivers/engine_mp/TileModelFactory.cpp
@@ -84,19 +84,20 @@ namespace
             const Profile* layerProfile = _layer->getProfile();
 
             //Only try to get data from the source if it actually intersects the key extent
-            bool hasDataInExtent = true;
-            if (tileSource && layerProfile)
-            {
-                GeoExtent ext = _key.getExtent();
-                if (!layerProfile->getSRS()->isEquivalentTo( ext.getSRS()))
-                {
-                    ext = layerProfile->clampAndTransformExtent( ext );
-                }
-                hasDataInExtent = tileSource->hasDataInExtent( ext );
-            }
+            bool hasDataInExtent = _layer->mayHaveDataInExtent(_key.getExtent());
+            //bool hasDataInExtent = true;
+            //if (tileSource && layerProfile)
+            //{
+            //    GeoExtent ext = _key.getExtent();
+            //    if (!layerProfile->getSRS()->isEquivalentTo( ext.getSRS()))
+            //    {
+            //        ext = layerProfile->clampAndTransformExtent( ext );
+            //    }
+            //    hasDataInExtent = tileSource->hasDataInExtent( ext );
+            //}
             
             // fetch the image from the layer.
-            if (hasDataInExtent && _layer->isKeyInRange(_key))
+            if (hasDataInExtent && _layer->isKeyInLegalRange(_key))
             {
                 if ( useMercatorFastPath )
                 {
@@ -275,7 +276,7 @@ TileModelFactory::buildElevation(const TileKey&    key,
     if (_meshHFCache->getOrCreateHeightField(frame, key, parentHF.get(), hf, isFallback, SAMPLE_FIRST_VALID, interp, progress))
     {
         model->_elevationData = TileModel::ElevationData(
-            hf,
+            hf.get(),
             GeoLocator::createForKey( key, mapInfo ),
             isFallback );
 
@@ -392,7 +393,7 @@ TileModelFactory::buildNormalMap(const TileKey&    key,
             else
             {
                 model->_normalData = TileModel::NormalData(
-                    hf,
+                    hf.get(),
                     GeoLocator::createForKey( key, mapInfo ),
                     isFallback );
 
@@ -405,10 +406,10 @@ TileModelFactory::buildNormalMap(const TileKey&    key,
     {
         // empty HF must be at least 2x2 for normal texture gen to work
         hf = HeightFieldUtils::createReferenceHeightField(
-            key.getExtent(), EMPTY_NORMAL_MAP_SIZE, EMPTY_NORMAL_MAP_SIZE, true );
+            key.getExtent(), EMPTY_NORMAL_MAP_SIZE, EMPTY_NORMAL_MAP_SIZE, 0u, true );
 
         model->_normalData = TileModel::NormalData(
-            hf,
+            hf.get(),
             GeoLocator::createForKey( key, mapInfo ),
             false );
 
@@ -446,11 +447,15 @@ TileModelFactory::createTileModel(const TileKey&           key,
     // Fetch the image data and make color layers.
     unsigned index = 0;
     unsigned order = 0;
-    for( ImageLayerVector::const_iterator i = frame.imageLayers().begin(); i != frame.imageLayers().end(); ++i )
+
+    ImageLayerVector imageLayers;
+    frame.getLayers(imageLayers);
+
+    for( ImageLayerVector::const_iterator i = imageLayers.begin(); i != imageLayers.end(); ++i )
     {
         ImageLayer* layer = i->get();
 
-        if ( layer->getEnabled() && layer->isKeyInRange(key) )
+        if ( layer->getEnabled() && layer->isKeyInLegalRange(key) )
         {
             BuildColorData build;
             build.init( key, layer, order, frame.getMapInfo(), _terrainOptions, _liveTiles.get(), model.get() );
@@ -495,7 +500,7 @@ TileModelFactory::createTileModel(const TileKey&           key,
     // as fallback data of course)
     if ( !model->_elevationData.getHeightField() )
     {
-        osg::HeightField* hf = HeightFieldUtils::createReferenceHeightField( key.getExtent(), 15, 15 );
+        osg::HeightField* hf = HeightFieldUtils::createReferenceHeightField( key.getExtent(), 15, 15, 0u );
         model->_elevationData = TileModel::ElevationData(
             hf,
             GeoLocator::createForKey(key, frame.getMapInfo()),
diff --git a/src/osgEarthDrivers/engine_mp/TileNode b/src/osgEarthDrivers/engine_mp/TileNode
index 4a5febb..0fd2883 100644
--- a/src/osgEarthDrivers/engine_mp/TileNode
+++ b/src/osgEarthDrivers/engine_mp/TileNode
@@ -33,8 +33,7 @@ namespace osgEarth { namespace Drivers { namespace MPTerrainEngine
      * a TileModel (and corresponds to one TileKey). The matrixtransform
      * localizes the TileNode within the terrain.
      */
-    class TileNode : public osg::MatrixTransform,
-                     public osgEarth::TerrainTileNode
+    class TileNode : public osg::MatrixTransform                     
     {
     public: // osgEarth::TerrainTileNode
         
@@ -105,8 +104,7 @@ namespace osgEarth { namespace Drivers { namespace MPTerrainEngine
 
     public: // TileNodeContainer
 
-        TileNode* getTileNode() { return this; }
-
+        TileNode* getTileNode() { return this; }    
 
     public: // OVERRIDES
 
diff --git a/src/osgEarthDrivers/engine_mp/TileNode.cpp b/src/osgEarthDrivers/engine_mp/TileNode.cpp
index 22fe872..465eb5c 100644
--- a/src/osgEarthDrivers/engine_mp/TileNode.cpp
+++ b/src/osgEarthDrivers/engine_mp/TileNode.cpp
@@ -21,6 +21,8 @@
 */
 #include "TileNode"
 
+#include "TilePagedLOD"
+
 #include <osg/ClusterCullingCallback>
 #include <osg/NodeCallback>
 #include <osg/NodeVisitor>
@@ -47,6 +49,10 @@ _lastTraversalFrame( 0 ),
 _dirty             ( false ),
 _outOfDate         ( false )
 {
+    if (!key.valid())
+    {
+        OE_NOTICE << "TileNode constructor not valid...." << std::endl;
+    }
     this->setName( key.str() );
     this->setMatrix( matrix );
 
@@ -269,4 +275,4 @@ TileNode::notifyOfArrival(TileNode* that)
         // or column that changed.
         thisImage->dirty();
     }
-}
+}
\ No newline at end of file
diff --git a/src/osgEarthDrivers/engine_mp/TileNodeRegistry b/src/osgEarthDrivers/engine_mp/TileNodeRegistry
index 37e22d7..bd44535 100644
--- a/src/osgEarthDrivers/engine_mp/TileNodeRegistry
+++ b/src/osgEarthDrivers/engine_mp/TileNodeRegistry
@@ -27,6 +27,7 @@
 #include <osgEarth/Revisioning>
 #include <osgEarth/ThreadingUtils>
 #include <osgEarth/ResourceReleaser>
+#include <osgEarth/Terrain>
 #include <OpenThreads/Atomic>
 #include <map>
 #include <set>
@@ -57,7 +58,7 @@ namespace osgEarth { namespace Drivers { namespace MPTerrainEngine
         };
 
     public:
-        TileNodeRegistry( const std::string& name );
+        TileNodeRegistry(const std::string& name, Terrain* terrainIF);
 
         /* Enabled revisioning on TileNodes, to support incremental update. */
         void setRevisioningEnabled(bool value);
@@ -127,6 +128,7 @@ namespace osgEarth { namespace Drivers { namespace MPTerrainEngine
         TileNodeMap                       _tiles;
         OpenThreads::Atomic               _frameNumber;
         mutable Threading::Mutex          _tilesMutex;
+        osg::observer_ptr<Terrain>        _terrain;
 
         typedef std::set<TileKey> TileKeySet;
         typedef std::map<TileKey, TileKeySet> Notifications;
diff --git a/src/osgEarthDrivers/engine_mp/TileNodeRegistry.cpp b/src/osgEarthDrivers/engine_mp/TileNodeRegistry.cpp
index a1654cf..f4efbe6 100644
--- a/src/osgEarthDrivers/engine_mp/TileNodeRegistry.cpp
+++ b/src/osgEarthDrivers/engine_mp/TileNodeRegistry.cpp
@@ -32,10 +32,11 @@ using namespace osgEarth;
 
 //----------------------------------------------------------------------------
 
-TileNodeRegistry::TileNodeRegistry(const std::string& name) :
+TileNodeRegistry::TileNodeRegistry(const std::string& name, Terrain* terrain) :
 _name              ( name ),
 _revisioningEnabled( false ),
-_frameNumber       ( 0u )
+_frameNumber       ( 0u ),
+_terrain( terrain )
 {
     //nop
 }
@@ -102,6 +103,12 @@ TileNodeRegistry::add( TileNode* tile )
 {
     if ( tile )
     {
+        osg::ref_ptr<Terrain> terrain;
+        if (_terrain.lock(terrain))
+        {
+            terrain->notifyTileAdded(tile->getKey(), tile);
+        }
+
         Threading::ScopedMutexLock exclusive( _tilesMutex );
         _tiles[ tile->getKey() ] = tile;
         if ( _revisioningEnabled )
diff --git a/src/osgEarthDrivers/engine_mp/TilePagedLOD b/src/osgEarthDrivers/engine_mp/TilePagedLOD
index 52e5479..492de40 100644
--- a/src/osgEarthDrivers/engine_mp/TilePagedLOD
+++ b/src/osgEarthDrivers/engine_mp/TilePagedLOD
@@ -37,7 +37,8 @@ namespace osgEarth { namespace Drivers { namespace MPTerrainEngine
      * TilePagedLOD is an extension to osg::PagedLOD that supports the tile
      * registry and does LTP bbox culling on subtiles.
      */
-    class TilePagedLOD : public osg::PagedLOD
+    class TilePagedLOD : public osg::PagedLOD,
+                         public osgEarth::TerrainTileNode
     {
     public:
         TilePagedLOD(
@@ -57,6 +58,7 @@ namespace osgEarth { namespace Drivers { namespace MPTerrainEngine
 
         /** The tilenode in this group */
         TileNode* getTileNode();
+        const TileNode* getTileNode() const;
         void setTileNode(TileNode* tilenode);
 
         osgDB::Options* getOrCreateDBOptions();
@@ -77,6 +79,19 @@ namespace osgEarth { namespace Drivers { namespace MPTerrainEngine
         /** override to manage the tile node registries. */
         bool removeExpiredChildren(double expiryTime, unsigned expiryFrame, osg::NodeList& removedChildren);
 
+    public:  // TerrainTileNode        
+        
+        virtual double getMinimumExpirationTime() const;
+        virtual void setMinimumExpirationTime(double minExpiryTime);
+        
+        virtual unsigned int getMinimumExpirationFrames() const;
+        virtual void setMinimumExpirationFrames(unsigned int minExpiryFrames);
+
+        virtual void loadChildren();
+
+        const TileKey& getKey() const;
+
+
     protected:
         virtual ~TilePagedLOD();
 
diff --git a/src/osgEarthDrivers/engine_mp/TilePagedLOD.cpp b/src/osgEarthDrivers/engine_mp/TilePagedLOD.cpp
index 3aeb724..e1008c9 100644
--- a/src/osgEarthDrivers/engine_mp/TilePagedLOD.cpp
+++ b/src/osgEarthDrivers/engine_mp/TilePagedLOD.cpp
@@ -161,6 +161,12 @@ TilePagedLOD::getTileNode()
     return _children.size() > 0 ? static_cast<TileNode*>(_children[0].get()) : 0L;
 }
 
+const TileNode*
+TilePagedLOD::getTileNode() const
+{
+    return _children.size() > 0 ? static_cast<TileNode*>(_children[0].get()) : 0L;
+}
+
 void
 TilePagedLOD::setTileNode(TileNode* tilenode)
 {
@@ -190,6 +196,8 @@ TilePagedLOD::addChild(osg::Node* node)
             return true;
         }
 
+        bool value = osg::PagedLOD::addChild( node );
+
         // If it's a TileNode, this is the simple first addition of the 
         // static TileNode child (not from the pager).
         TileNode* tilenode = dynamic_cast<TileNode*>( node );
@@ -198,7 +206,7 @@ TilePagedLOD::addChild(osg::Node* node)
             _live->add( tilenode );
         }
 
-        return osg::PagedLOD::addChild( node );
+        return value;
     }
 
     return false;
@@ -437,3 +445,60 @@ TilePagedLOD::removeExpiredChildren(double         expiryTime,
     }
     return false;
 }
+
+const TileKey& TilePagedLOD::getKey() const
+{
+    if (!getTileNode())
+    {
+        return TileKey::INVALID;
+    }
+    return getTileNode()->getKey();
+}
+
+double TilePagedLOD::getMinimumExpirationTime() const
+{
+    return PagedLOD::getMinimumExpiryTime(1);    
+}
+
+void TilePagedLOD::setMinimumExpirationTime(double minExpiryTime)
+{
+    PagedLOD::setMinimumExpiryTime(1, minExpiryTime);
+}
+
+unsigned int TilePagedLOD::getMinimumExpirationFrames() const
+{
+    return PagedLOD::getMinimumExpiryFrames(1);    
+}
+
+void TilePagedLOD::setMinimumExpirationFrames(unsigned int minExpiryFrames)
+{
+    PagedLOD::setMinimumExpiryFrames(1, minExpiryFrames);
+}
+
+
+void TilePagedLOD::loadChildren()
+{
+    TileKey key = getKey();
+
+    // Load all filenames that aren't currently loaded in the PagedLOD
+
+    if (getNumChildren() < getNumFileNames())
+    {
+        for (unsigned int i = 0; i < getNumFileNames(); i++)
+        {
+            std::string filename = getFileName(i);    
+            if (!filename.empty() && i >= getNumChildren())
+            {
+                osg::ref_ptr< osg::Node > node = osgDB::readRefNodeFile(filename);
+                if (!node.valid())
+                {
+                    break;
+                }
+                addChild(node.get());
+            }
+        }
+    }
+}
+
+
+
diff --git a/src/osgEarthDrivers/engine_rex/CMakeLists.txt b/src/osgEarthDrivers/engine_rex/CMakeLists.txt
index 5a40c99..8a6107a 100644
--- a/src/osgEarthDrivers/engine_rex/CMakeLists.txt
+++ b/src/osgEarthDrivers/engine_rex/CMakeLists.txt
@@ -1,8 +1,9 @@
 
-SET(TARGET_COMMON_LIBRARIES ${TARGET_COMMON_LIBRARIES} osgEarthSymbology)
+SET(TARGET_COMMON_LIBRARIES ${TARGET_COMMON_LIBRARIES} osgEarthSymbology osgEarthUtil)
 
 set(TARGET_GLSL
     RexEngine.vert.glsl
+    RexEngine.elevation.glsl
     RexEngine.vert.view.glsl
     RexEngine.tcs.glsl
     RexEngine.tes.glsl
@@ -24,14 +25,18 @@ configure_shaders(
     ${TARGET_GLSL} )
 
 SET(TARGET_SRC
+    DrawState.cpp
+    DrawTileCommand.cpp
     GeometryPool.cpp
     RexTerrainEngineNode.cpp
     RexTerrainEngineDriver.cpp
+    LayerDrawable.cpp
     LoadTileData.cpp
     MaskGenerator.cpp
-    MPTexture.cpp
 	SelectionInfo.cpp
     SurfaceNode.cpp
+    TerrainCuller.cpp
+    TerrainRenderData.cpp
 	TileDrawable.cpp
     EngineContext.cpp
     TileNode.cpp
@@ -43,16 +48,21 @@ SET(TARGET_SRC
 
 SET(TARGET_H
     Common
+    DrawState
+    DrawTileCommand
     GeometryPool
     Shaders
     RexTerrainEngineNode
     RexTerrainEngineOptions
+    LayerDrawable
     LoadTileData
     MaskGenerator
-    MPTexture
     RenderBindings
     SurfaceNode
+    TerrainCuller
+    TerrainRenderData
 	TileDrawable
+    TileRenderModel
     EngineContext
     TileNode
     TileNodeRegistry
diff --git a/src/osgEarthDrivers/engine_rex/DrawState b/src/osgEarthDrivers/engine_rex/DrawState
new file mode 100644
index 0000000..94f179e
--- /dev/null
+++ b/src/osgEarthDrivers/engine_rex/DrawState
@@ -0,0 +1,152 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+ * Copyright 2008-2014 Pelican Mapping
+ * http://osgearth.org
+ *
+ * osgEarth is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+#ifndef OSGEARTH_REX_TERRAIN_DRAW_STATE_H
+#define OSGEARTH_REX_TERRAIN_DRAW_STATE_H 1
+
+#include "RenderBindings"
+
+#include <osg/RenderInfo>
+#include <osg/GLExtensions>
+#include <osg/StateSet>
+#include <osg/Program>
+
+#include <vector>
+
+using namespace osgEarth;
+
+namespace osgEarth { namespace Drivers { namespace RexTerrainEngine
+{
+    /**
+     * Tracks the state of a single sampler through the draw process,
+     * to prevent redundant OpenGL texture binding and matrix uniform sets.
+     */
+    struct SamplerState
+    {
+        SamplerState() : _matrixUL(-1) { }
+        optional<osg::Texture*> _texture;    // Texture currently bound
+        optional<osg::Matrixf> _matrix;      // Matrix that is currently set
+        GLint _matrixUL;                     // Matrix uniform location
+
+        void clear() {
+            _texture.clear();
+            _matrix.clear();
+        }
+
+        void clearUniformData() {
+            _matrix.clear();
+            _matrixUL = -1;
+        }
+    };
+
+    /**
+     * Tracks the state of all samplers used in render a tile,
+     * to prevent redundant OpenGL binds.
+     */
+    struct TileSamplerState
+    {
+        std::vector<SamplerState> _samplers;
+
+        void clear() {
+            for (unsigned i = 0; i<_samplers.size(); ++i)
+                _samplers[i].clear();
+        }
+
+        void clearUniformData() {
+            for (unsigned i = 0; i<_samplers.size(); ++i)
+                _samplers[i].clearUniformData();
+        }
+    };
+
+    struct PerContextDrawState
+    {
+        // uniform locations
+        GLint _tileKeyUL;
+        GLint _parentTextureExistsUL;
+        GLint _layerUidUL;
+        GLint _layerOpacityUL;
+        GLint _layerOrderUL;
+        GLint _layerMinRangeUL;
+        GLint _layerMaxRangeUL;
+        GLint _elevTexelCoeffUL;
+        GLint _morphConstantsUL;
+
+        optional<int>        _layerOrder;
+        optional<osg::Vec2f> _elevTexelCoeff;
+        optional<osg::Vec2f> _morphConstants;
+        optional<bool>       _parentTextureExists;
+
+        const osg::Program::PerContextProgram* _pcp;
+
+        osg::ref_ptr<osg::GLExtensions> _ext;
+
+        TileSamplerState _samplerState;
+
+        PerContextDrawState() :
+            _tileKeyUL(-1),
+            _parentTextureExistsUL(-1),
+            _layerUidUL(-1),
+            _layerOpacityUL(-1),
+            _layerOrderUL(-1),
+            _layerMinRangeUL(-1),
+            _layerMaxRangeUL(-1),
+            _elevTexelCoeffUL(-1),
+            _morphConstantsUL(-1),
+            _ext(0L),
+            _pcp(0L)
+        {
+            //nop
+        }
+
+        // Ensures that the Uniform Locations in the DrawState correspond to
+        // the currently applied program object.
+        void refresh(osg::RenderInfo& ri, const RenderBindings* bindings);
+
+        // Clears all saved state.
+        void clear();
+    };
+
+    /**
+     * Tracks the state of terrain drawing settings in a single frame,
+     * to prevent redundant OpenGL calls.
+     */
+    struct DrawState : public osg::Referenced
+    {
+        unsigned _frame;
+
+        const RenderBindings* _bindings;
+
+        osg::BoundingSphere _bs;
+        osg::BoundingBox    _box;
+
+        osg::buffered_object<PerContextDrawState> _pcds;
+
+        DrawState() :
+            _frame(0u),
+            _bindings(0L)
+        {
+            //nop
+            _pcds.resize(64);
+        }
+
+        PerContextDrawState& getPCDS(unsigned contextID) { return _pcds[contextID]; }
+    };
+
+} } } // namespace 
+
+#endif // OSGEARTH_REX_TERRAIN_DRAW_STATE_H
diff --git a/src/osgEarthDrivers/engine_rex/DrawState.cpp b/src/osgEarthDrivers/engine_rex/DrawState.cpp
new file mode 100644
index 0000000..b410231
--- /dev/null
+++ b/src/osgEarthDrivers/engine_rex/DrawState.cpp
@@ -0,0 +1,79 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+ * Copyright 2008-2014 Pelican Mapping
+ * http://osgearth.org
+ *
+ * osgEarth is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+#include "DrawState"
+
+using namespace osgEarth::Drivers::RexTerrainEngine;
+
+#undef  LC
+#define LC "[DrawState] "
+
+void
+PerContextDrawState::refresh(osg::RenderInfo& ri, const RenderBindings* bindings)
+{
+    // Establish a GL Extensions handle:
+    if (!_ext.valid())
+    {
+        _ext = osg::GLExtensions::Get(ri.getContextID(), true);
+    }
+
+    // Size the sampler states property:
+    if (_samplerState._samplers.size() < bindings->size())
+    {
+        _samplerState._samplers.resize(bindings->size());
+    }
+
+    const osg::Program::PerContextProgram* pcp = ri.getState()->getLastAppliedProgramObject();
+    if (pcp && (pcp != _pcp))
+    {
+        // Reset all sampler matrix states since their uniform locations are going to change.
+        _layerOrder.clear();
+        _elevTexelCoeff.clear();
+        _morphConstants.clear();
+        _parentTextureExists.clear();
+        _samplerState.clear();
+
+        // for each sampler binding, initialize its state tracking structure 
+        // and resolve its matrix uniform location:
+        for (unsigned i = 0; i < bindings->size(); ++i)
+        {
+            const SamplerBinding& binding = (*bindings)[i];
+            _samplerState._samplers[i]._matrixUL = pcp->getUniformLocation(osg::Uniform::getNameID(binding.matrixName()));
+        }
+
+        // resolve all the other uniform locations:
+        _tileKeyUL = pcp->getUniformLocation(osg::Uniform::getNameID("oe_tile_key"));
+        _elevTexelCoeffUL = pcp->getUniformLocation(osg::Uniform::getNameID("oe_tile_elevTexelCoeff"));
+        _parentTextureExistsUL = pcp->getUniformLocation(osg::Uniform::getNameID("oe_layer_texParentExists"));
+        _layerUidUL = pcp->getUniformLocation(osg::Uniform::getNameID("oe_layer_uid"));
+        _layerOpacityUL = pcp->getUniformLocation(osg::Uniform::getNameID("oe_layer_opacity"));
+        _layerOrderUL = pcp->getUniformLocation(osg::Uniform::getNameID("oe_layer_order"));
+        _layerMinRangeUL = pcp->getUniformLocation(osg::Uniform::getNameID("oe_layer_minRange"));
+        _layerMaxRangeUL = pcp->getUniformLocation(osg::Uniform::getNameID("oe_layer_maxRange"));
+        _morphConstantsUL = pcp->getUniformLocation(osg::Uniform::getNameID("oe_tile_morph"));
+    }
+
+    _pcp = pcp;
+}
+
+void
+PerContextDrawState::clear()
+{
+    _samplerState.clear();
+    _pcp = 0L;
+}
diff --git a/src/osgEarthDrivers/engine_rex/DrawTileCommand b/src/osgEarthDrivers/engine_rex/DrawTileCommand
new file mode 100644
index 0000000..bb6d1be
--- /dev/null
+++ b/src/osgEarthDrivers/engine_rex/DrawTileCommand
@@ -0,0 +1,114 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+ * Copyright 2008-2014 Pelican Mapping
+ * http://osgearth.org
+ *
+ * osgEarth is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+#ifndef OSGEARTH_REX_TERRAIN_DRAW_TILE_COMMAND_H
+#define OSGEARTH_REX_TERRAIN_DRAW_TILE_COMMAND_H 1
+
+#include "TileRenderModel"
+#include "DrawState"
+#include "GeometryPool"
+#include <osgEarth/PatchLayer>
+#include <osgEarth/TileKey>
+#include <osg/Matrix>
+#include <osg/Geometry>
+#include <list>
+
+using namespace osgEarth;
+
+namespace osgEarth { namespace Drivers { namespace RexTerrainEngine
+{
+    /**
+     * All data necessary to draw a single terrain tile.
+     */
+    struct DrawTileCommand
+    {
+        // ModelView matrix to apply before rendering this tile
+        //osg::Matrixf _modelViewMatrix;
+        osg::ref_ptr<const osg::RefMatrix> _modelViewMatrix;
+
+        // Samplers that are shared between all rendering passes
+        const Samplers* _sharedSamplers;
+
+        // Samplers specific to one rendering pass
+        const Samplers* _colorSamplers;
+
+        // Tile geometry, if present
+        osg::ref_ptr<SharedGeometry> _geom;
+
+        // Tile key
+        const TileKey* _key;
+
+        // Tile key value to push to uniform just before drawing
+        osg::Vec4f _keyValue;
+
+        // Elevation texel sampling coefficients (so we sample elevation
+        // data on center rather than on edge) - We might be able to move
+        // this to the Layer level as long as the elevation texture size
+        // doesn't change
+        osg::Vec2f _elevTexelCoeff;
+
+        // Coefficient used for tile vertex morphing
+        osg::Vec2f _morphConstants;
+
+        // Custom draw callback to call instead of rendering _geom
+        PatchLayer::DrawCallback* _drawCallback;
+
+        // When drawing _geom, whether to render as GL_PATCHES 
+        // instead of GL_TRIANGLES (for patch layers)
+        bool _drawPatch;
+
+        // Distance from camera to center of tile
+        float _range;
+
+        // Tile draw order
+        int _order;
+
+
+        void draw(osg::RenderInfo& ri, DrawState& ds, osg::Referenced* layerData) const;
+
+        // Less than operator will ensure that tiles are sorted high-to-low LOD
+        // (to minimize Z overdraw) and then grouped by shared geometry
+        // (to minimize buffer binds). Both of these make a significant performance
+        // difference based on benchmarking.
+        bool operator < (const DrawTileCommand& rhs) const
+        {
+            if (_key->getLOD() > rhs._key->getLOD()) return true;
+            if (_key->getLOD() < rhs._key->getLOD()) return false;
+            return _geom < rhs._geom;
+        }
+
+        DrawTileCommand() :
+            _sharedSamplers(0L),
+            _colorSamplers(0L),
+            _geom(0L),
+            _elevTexelCoeff(1.0f, 0.0f),
+            _drawCallback(0L),
+            _drawPatch(false),
+            _range(0.0f),
+            _order(0) { }
+    };
+
+    /**
+     * Ordered list of tile drawing commands.
+     * List is faster than vector here (benchmarked)
+     */
+    typedef std::list<DrawTileCommand> DrawTileCommands;
+
+} } } // namespace 
+
+#endif // OSGEARTH_REX_TERRAIN_DRAW_TILE_COMMAND_H
diff --git a/src/osgEarthDrivers/engine_rex/DrawTileCommand.cpp b/src/osgEarthDrivers/engine_rex/DrawTileCommand.cpp
new file mode 100644
index 0000000..ca740a2
--- /dev/null
+++ b/src/osgEarthDrivers/engine_rex/DrawTileCommand.cpp
@@ -0,0 +1,155 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+ * Copyright 2008-2014 Pelican Mapping
+ * http://osgearth.org
+ *
+ * osgEarth is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+#include "DrawTileCommand"
+
+using namespace osgEarth::Drivers::RexTerrainEngine;
+
+#undef  LC
+#define LC "[DrawTileCommand] "
+
+void
+DrawTileCommand::draw(osg::RenderInfo& ri, DrawState& dsMaster, osg::Referenced* layerData) const
+{
+    PerContextDrawState& ds = dsMaster.getPCDS(ri.getContextID());
+
+    osg::State& state = *ri.getState();
+
+    //OE_INFO << LC << "      TILE: " << _geom << std::endl;
+        
+    // Tile key encoding, if the uniform is required.
+    if (ds._tileKeyUL >= 0 )
+    {
+        ds._ext->glUniform4fv(ds._tileKeyUL, 1, _keyValue.ptr());
+    }
+
+    if (ds._layerOrderUL >= 0 && !ds._layerOrder.isSetTo(_order))
+    {
+        ds._ext->glUniform1i(ds._layerOrderUL, (GLint)_order);
+        ds._layerOrder = _order;
+    }
+
+    // Elevation coefficients (can probably be terrain-wide)
+    if (ds._elevTexelCoeffUL >= 0 && !ds._elevTexelCoeff.isSetTo(_elevTexelCoeff))
+    {
+        ds._ext->glUniform2fv(ds._elevTexelCoeffUL, 1, _elevTexelCoeff.ptr());
+        ds._elevTexelCoeff = _elevTexelCoeff;
+    }
+
+    // Morphing constants for this LOD
+    if (ds._morphConstantsUL >= 0 && !ds._morphConstants.isSetTo(_morphConstants))
+    {
+        ds._ext->glUniform2fv(ds._morphConstantsUL, 1, _morphConstants.ptr());
+        ds._morphConstants = _morphConstants;
+    }
+
+    // MVM for this tile:
+    state.applyModelViewMatrix(_modelViewMatrix.get());
+    
+    // MVM uniforms for GL3 core:
+    if (state.getUseModelViewAndProjectionUniforms())
+    {
+        state.applyModelViewAndProjectionUniformsIfRequired();
+    }
+
+    // Apply samplers for this tile draw:
+    unsigned s = 0;
+
+    if (_colorSamplers)
+    {
+        for (s = 0; s <= SamplerBinding::COLOR_PARENT; ++s)
+        {
+            const Sampler& sampler = (*_colorSamplers)[s];
+            SamplerState& samplerState = ds._samplerState._samplers[s];
+
+            if (sampler._texture.valid() && !samplerState._texture.isSetTo(sampler._texture.get()))
+            {
+                state.setActiveTextureUnit((*dsMaster._bindings)[s].unit());
+                sampler._texture->apply(state);
+                samplerState._texture = sampler._texture.get();
+            }
+
+            if (samplerState._matrixUL >= 0 && !samplerState._matrix.isSetTo(sampler._matrix))
+            {
+                ds._ext->glUniformMatrix4fv(samplerState._matrixUL, 1, GL_FALSE, sampler._matrix.ptr());
+                samplerState._matrix = sampler._matrix;
+            }
+
+            // Need a special uniform for color parents.
+            if (s == SamplerBinding::COLOR_PARENT)
+            {
+                if (ds._parentTextureExistsUL >= 0 && !ds._parentTextureExists.isSetTo(sampler._texture.get() != 0L))
+                {
+                    ds._ext->glUniform1f(ds._parentTextureExistsUL, sampler._texture.valid() ? 1.0f : 0.0f);
+                    ds._parentTextureExists = sampler._texture.valid();
+                }
+            }
+        }
+    }
+
+    if (_sharedSamplers)
+    {
+        for (; s < _sharedSamplers->size(); ++s)
+        {
+            const Sampler& sampler = (*_sharedSamplers)[s];
+            SamplerState& samplerState = ds._samplerState._samplers[s];
+
+            if (sampler._texture.valid() && !samplerState._texture.isSetTo(sampler._texture.get()))
+            {
+                state.setActiveTextureUnit((*dsMaster._bindings)[s].unit());
+                sampler._texture->apply(state);
+                samplerState._texture = sampler._texture.get();
+            }
+
+            if (samplerState._matrixUL >= 0 && !samplerState._matrix.isSetTo(sampler._matrix))
+            {
+                ds._ext->glUniformMatrix4fv(samplerState._matrixUL, 1, GL_FALSE, sampler._matrix.ptr());
+                samplerState._matrix = sampler._matrix;
+            }
+        }
+    }
+
+    if (_drawCallback)
+    {
+        PatchLayer::DrawContext dc;
+
+        //TODO: might not need any of this. review. -gw
+        dc.colorTexture = _colorSamplers? (*_colorSamplers)[SamplerBinding::COLOR]._texture.get() : 0L;
+        if (_sharedSamplers)
+        {
+            dc.elevationTexture = (*_sharedSamplers)[SamplerBinding::ELEVATION]._texture.get();
+            dc.normalTexture    = (*_sharedSamplers)[SamplerBinding::NORMAL]._texture.get();
+            dc.coverageTexture  = (*_sharedSamplers)[SamplerBinding::COVERAGE]._texture.get();
+        }
+        dc.key = _key;
+        dc.range = _range;
+        _drawCallback->draw(ri, dc, layerData);
+
+        // evaluate this.
+        ds._samplerState.clear();
+    }
+
+    else
+    // If there's a geometry, draw it now:
+    if (_geom.valid())
+    {
+        GLenum ptype = _drawPatch ? GL_PATCHES : GL_TRIANGLES;
+
+        _geom->render(ptype, ri);
+    }    
+}
diff --git a/src/osgEarthDrivers/engine_rex/EngineContext b/src/osgEarthDrivers/engine_rex/EngineContext
index 02a2e4d..5916318 100644
--- a/src/osgEarthDrivers/engine_rex/EngineContext
+++ b/src/osgEarthDrivers/engine_rex/EngineContext
@@ -27,11 +27,13 @@
 #include "TileNodeRegistry"
 #include "RexTerrainEngineOptions"
 #include "RenderBindings"
+#include "TileDrawable"
 
 #include <osgEarth/TerrainTileModel>
 #include <osgEarth/MapFrame>
 #include <osgEarth/Progress>
 #include <osgEarth/TerrainEngineNode>
+#include <osgEarth/TileRasterizer>
 
 #include <osgUtil/CullVisitor>
 
@@ -56,11 +58,12 @@ namespace osgEarth { namespace Drivers { namespace RexTerrainEngine
             GeometryPool*                       geometryPool,
             Loader*                             loader,
             Unloader*                           unloader,
+            TileRasterizer*                     rasterizer,
             TileNodeRegistry*                   liveTiles,
             const RenderBindings&               renderBindings,
             const RexTerrainEngineOptions&      options,
             const SelectionInfo&                selectionInfo,
-            TilePatchCallbacks&                 tilePatchCallbacks);
+            ModifyBoundingBoxCallback*          modifyBBoxCallback);
         
         Loader* getLoader() const { return _loader; }
 
@@ -70,11 +73,15 @@ namespace osgEarth { namespace Drivers { namespace RexTerrainEngine
 
         GeometryPool* getGeometryPool() const { return _geometryPool; }
 
-        const MapFrame& getMapFrame();
+        // Raw pointer to the Map. Only call this from the MAIN thread. DO NOT access this
+        // from a pager thread. Instead, create a MapFrame from it in the MAIN thread and
+        // use THAT from the pager thread.
+        const Map* getMap() const;
 
-        TerrainEngineNode* getEngine() const { return _terrainEngine; }
+        // only call this variant when it's safe to do so (update, cull).
+        TerrainEngineNode* getEngine() const { return _terrainEngine.get(); }
 
-        TileNodeRegistry* liveTiles() const { return _liveTiles.get(); }
+        TileNodeRegistry* liveTiles() const { return _liveTiles; }
 
         const SelectionInfo& getSelectionInfo() const { return _selectionInfo; }
 
@@ -90,44 +97,36 @@ namespace osgEarth { namespace Drivers { namespace RexTerrainEngine
 
         bool maxLiveTilesExceeded() const;
 
-        osg::Uniform* getOrCreateMatrixUniform(const std::string& name, const osg::Matrixf& m);
+        double getExpirationRange2() const { return _expirationRange2; }
 
-        void unloadChildrenOf(const TileNode*);
+        ModifyBoundingBoxCallback* getModifyBBoxCallback() const { return _bboxCB; }
 
-        double getExpirationRange2() const { return _expirationRange2; }
+        bool getUseTextureBorder() const { return false; }
 
-        void invokeTilePatchCallbacks(
-            osgUtil::CullVisitor* cv,
-            const TileKey&        tileKey,
-            osg::StateSet*        tileStateSet,
-            osg::Node*            tilePatch);
-        
-        osg::ref_ptr<osg::StateSet>           _surfaceSS;
+        TileRasterizer* getTileRasterizer() const { return _tileRasterizer; }
 
     protected:
 
         virtual ~EngineContext() { }
 
     public:
-
-        MapFrame                              _frame;
-        osg::ref_ptr<TileNodeRegistry>        _liveTiles;
+        
+        osg::observer_ptr<TerrainEngineNode>  _terrainEngine;
+        int                                   _mainThreadId;
+        const Map*                            _map;
+        TileNodeRegistry*                     _liveTiles;
         const RexTerrainEngineOptions&        _options;
         const RenderBindings&                 _renderBindings;
-        TerrainEngineNode*                    _terrainEngine;
         GeometryPool*                         _geometryPool;
         Loader*                               _loader;
         Unloader*                             _unloader;
+        TileRasterizer*                       _tileRasterizer;
         const SelectionInfo&                  _selectionInfo;
         osg::Timer_t                          _tick;
         int                                   _tilesLastCull;
-        osg::ref_ptr<ProgressCallback>        _progress;
-        TilePatchCallbacks&                   _tilePatchCallbacks;        
-        std::vector<TileKey>                  _tilesWithChildrenToUnload;
+        osg::ref_ptr<ProgressCallback>        _progress;    
         double                                _expirationRange2;
-
-        typedef std::map<osg::Vec4f, osg::ref_ptr<osg::Uniform> > MatrixUniformMap;
-        MatrixUniformMap _matrixUniforms;
+        ModifyBoundingBoxCallback*            _bboxCB;
     };
 
 } } } // namespace osgEarth::Drivers::RexTerrainEngine
diff --git a/src/osgEarthDrivers/engine_rex/EngineContext.cpp b/src/osgEarthDrivers/engine_rex/EngineContext.cpp
index 9238dfb..79f0dfa 100644
--- a/src/osgEarthDrivers/engine_rex/EngineContext.cpp
+++ b/src/osgEarthDrivers/engine_rex/EngineContext.cpp
@@ -37,49 +37,49 @@ EngineContext::EngineContext(const Map*                     map,
                              GeometryPool*                  geometryPool,
                              Loader*                        loader,
                              Unloader*                      unloader,
+                             TileRasterizer*                tileRasterizer,
                              TileNodeRegistry*              liveTiles,
                              const RenderBindings&          renderBindings,
                              const RexTerrainEngineOptions& options,
                              const SelectionInfo&           selectionInfo,
-                             TilePatchCallbacks&            tilePatchCallbacks) :
-_frame         ( map ),
+                             ModifyBoundingBoxCallback*     bboxCB) :
+_map           ( map ),
 _terrainEngine ( terrainEngine ),
 _geometryPool  ( geometryPool ),
 _loader        ( loader ),
 _unloader      ( unloader ),
+_tileRasterizer( tileRasterizer ),
 _liveTiles     ( liveTiles ),
 _renderBindings( renderBindings ),
 _options       ( options ),
 _selectionInfo ( selectionInfo ),
-_tilePatchCallbacks( tilePatchCallbacks ),
+_bboxCB        ( bboxCB ),
 _tick(0),
 _tilesLastCull(0)
 {
     _expirationRange2 = _options.expirationRange().get() * _options.expirationRange().get();
+    _mainThreadId = Threading::getCurrentThreadId();
 }
 
-const MapFrame& EngineContext::getMapFrame()
+const Map*
+EngineContext::getMap() const
 {
-    if (_frame.needsSync())
-        _frame.sync();
-
-    return _frame;
-}
-
-void
-EngineContext::unloadChildrenOf(const TileNode* tile)
-{
-   _tilesWithChildrenToUnload.push_back( tile->getTileKey() );
-   OE_INFO << LC << "Unload children of: " << tile->getTileKey().str() << "\n";
+#if 0
+    // debugging check
+    if (Threading::getCurrentThreadId() != _mainThreadId)
+    {
+        OE_WARN << LC << "Illegal - do not call getMap from outside the main thread\n";
+    }
+#endif
+    return _map;
 }
 
 void
 EngineContext::startCull(osgUtil::CullVisitor* cv)
 {
+#ifdef PROFILE
     _tick = osg::Timer::instance()->tick();
     _tilesLastCull = _liveTiles->size();
-
-#ifdef PROFILE
     _progress = new ProgressCallback();
 #endif
 }
@@ -133,7 +133,7 @@ namespace
                     const TileNode* tile = tiles.at(f%s);
                     if (tile->areSubTilesDormant(_stamp))
                     {
-                        _keys.push_back(tile->getTileKey());
+                        _keys.push_back(tile->getKey());
                     }
                 }
                 break;
@@ -144,7 +144,7 @@ namespace
                     for(unsigned i=0; i<4; ++i) {
                         const TileNode* tile = tiles.at((f+i)%s);
                         if ( tile->areSubTilesDormant(_stamp) )
-                            _keys.push_back( tile->getTileKey() );
+                            _keys.push_back( tile->getKey() );
                     }
                 }
             }
@@ -174,23 +174,24 @@ EngineContext::endCull(osgUtil::CullVisitor* cv)
                     std::setprecision(2) << 100.0*i->second/totalCull << "%)" << std::endl;
             }
         }
-    }  
+    } 
 
 #if 0 // render bin printout
     Config c = CullDebugger().dumpRenderBin(cv->getCurrentRenderBin());
     OE_NOTICE << c.toJSON(true) << std::endl << std::endl;
 #endif
 
-    Scanner scanner(_tilesWithChildrenToUnload, cv->getFrameStamp());
+    // Scan for tiles that need to be unloaded.
+    std::vector<TileKey> tilesWithChildrenToUnload;
+    Scanner scanner(tilesWithChildrenToUnload, cv->getFrameStamp());
     _liveTiles->run( scanner );
 
-    if ( !_tilesWithChildrenToUnload.empty() )
+    if ( !tilesWithChildrenToUnload.empty() )
     {        
-        getUnloader()->unloadChildren( _tilesWithChildrenToUnload );
-        _tilesWithChildrenToUnload.clear();
+        getUnloader()->unloadChildren( tilesWithChildrenToUnload );
     }
 
-    Registry::instance()->startActivity("REX live tiles", Stringify()<<_liveTiles->size());
+    //Registry::instance()->startActivity("REX live tiles", Stringify()<<_liveTiles->size());
 }
 
 bool
@@ -198,35 +199,3 @@ EngineContext::maxLiveTilesExceeded() const
 {
     return _liveTiles->size() > _options.expirationThreshold().get();
 }
-
-osg::Uniform*
-EngineContext::getOrCreateMatrixUniform(const std::string& name, const osg::Matrixf& m)
-{
-    // Unique key for this uniform include the scale, the x/y bias, and the name ID.
-    osg::Vec4f key(m(0,0),m(3,0),m(3,1),(float)osg::Uniform::getNameID(name));
-
-    MatrixUniformMap::iterator i = _matrixUniforms.find(key);
-    if ( i != _matrixUniforms.end() )
-    {
-        return i->second.get();
-    }
-    
-    osg::Uniform* u = new osg::Uniform(name.c_str(), m);
-    _matrixUniforms[key] = u;
-
-    return u;
-}
-
-void
-EngineContext::invokeTilePatchCallbacks(osgUtil::CullVisitor* cv,
-                                        const TileKey&        tileKey,
-                                        osg::StateSet*        tileStateSet,
-                                        osg::Node*            tilePatch)
-{
-    for(TilePatchCallbacks::iterator i = _tilePatchCallbacks.begin();
-        i != _tilePatchCallbacks.end();
-        ++i)
-    {
-        i->get()->cull(cv, tileKey, tileStateSet, tilePatch);
-    }
-}
diff --git a/src/osgEarthDrivers/engine_rex/GeometryPool b/src/osgEarthDrivers/engine_rex/GeometryPool
index 22196b4..fb9fc4d 100644
--- a/src/osgEarthDrivers/engine_rex/GeometryPool
+++ b/src/osgEarthDrivers/engine_rex/GeometryPool
@@ -28,10 +28,95 @@
 #include <osgEarth/ResourceReleaser>
 #include <osg/Geometry>
 
+#if OSG_MIN_VERSION_REQUIRED(3,5,6)
+#define SUPPORTS_VAO 1
+#endif
+
 namespace osgEarth { namespace Drivers { namespace RexTerrainEngine
 {
     using namespace osgEarth;
 
+    // Adapted from osgTerrain shared geometry class.
+    class /*internal*/ SharedGeometry : public osg::Drawable
+    {
+    public:
+        SharedGeometry();
+
+        SharedGeometry(const SharedGeometry&, const osg::CopyOp& copyop = osg::CopyOp::SHALLOW_COPY);
+
+        META_Node(osgEarthRex, SharedGeometry);
+
+        void setVertexArray(osg::Array* array) { _vertexArray = array; }
+        osg::Array* getVertexArray() { return _vertexArray.get(); }
+        const osg::Array* getVertexArray() const  { return _vertexArray.get(); }
+
+        void setNormalArray(osg::Array* array) { _normalArray = array; }
+        osg::Array* getNormalArray() { return _normalArray.get(); }
+        const osg::Array* getNormalArray() const { return _normalArray.get(); }
+
+        void setTexCoordArray(osg::Array* array) { _texcoordArray = array; }
+        osg::Array* getTexCoordArray() { return _texcoordArray.get(); }
+        const osg::Array* getTexCoordArray() const { return _texcoordArray.get(); }
+
+        void setNeighborArray(osg::Array* array) { _neighborArray = array; }
+        osg::Array* getNeighborArray() { return _neighborArray.get(); }
+        const osg::Array* getNeighborArray() const { return _neighborArray.get(); }
+
+        void setDrawElements(osg::DrawElements* array) { _drawElements = array; }
+        osg::DrawElements* getDrawElements() { return _drawElements.get(); }
+        const osg::DrawElements* getDrawElements() const { return _drawElements.get(); }
+
+        void setMaskElements(osg::DrawElements* array) { _maskElements = array; }
+        osg::DrawElements* getMaskElements() { return _maskElements.get(); }
+        const osg::DrawElements* getMaskElements() const { return _maskElements.get(); }
+
+#ifdef SUPPORTS_VAO
+        osg::VertexArrayState* createVertexArrayState(osg::RenderInfo& renderInfo) const;
+#endif
+
+        void compileGLObjects(osg::RenderInfo& renderInfo) const;
+
+        void render(GLenum primitiveType, osg::RenderInfo& renderInfo) const;
+
+        void resizeGLObjectBuffers(unsigned int maxSize);
+        void releaseGLObjects(osg::State* state) const;
+        
+        virtual bool supports(const osg::Drawable::AttributeFunctor&) const { return true; }
+        virtual void accept(osg::Drawable::AttributeFunctor&);
+
+        virtual bool supports(const osg::Drawable::ConstAttributeFunctor&) const { return true; }
+        virtual void accept(osg::Drawable::ConstAttributeFunctor&) const;
+
+        virtual bool supports(const osg::PrimitiveFunctor&) const { return true; }
+        virtual void accept(osg::PrimitiveFunctor&) const;
+
+        virtual bool supports(const osg::PrimitiveIndexFunctor&) const { return true; }
+        virtual void accept(osg::PrimitiveIndexFunctor&) const;
+
+    public:
+
+        // internal function for testing
+        void drawImplementation(osg::RenderInfo& ri) const { render(GL_TRIANGLES, ri); }
+
+        // convert to a "real" geometry object
+        osg::Geometry* makeOsgGeometry();
+
+        // whether this geometry contains anything
+        bool empty() const;
+
+    protected:
+
+        virtual ~SharedGeometry();
+
+        osg::ref_ptr<osg::Array>        _vertexArray;
+        osg::ref_ptr<osg::Array>        _normalArray;
+        osg::ref_ptr<osg::Array>        _colorArray;
+        osg::ref_ptr<osg::Array>        _texcoordArray;
+        osg::ref_ptr<osg::Array>        _neighborArray;
+        osg::ref_ptr<osg::DrawElements> _drawElements;
+        osg::ref_ptr<osg::DrawElements> _maskElements;
+    };
+
     /**
      * Pool of terrain tile geometries.
      *
@@ -62,7 +147,7 @@ namespace osgEarth { namespace Drivers { namespace RexTerrainEngine
         {
             GeometryKey() :
                 lod(-1),
-                yMin(0.0),
+                tileY(0),
                 patch(false),
                 size(0u)
                 {
@@ -72,20 +157,21 @@ namespace osgEarth { namespace Drivers { namespace RexTerrainEngine
             {
                 if (lod < rhs.lod) return true;
                 if (lod > rhs.lod) return false;
-                if (yMin < rhs.yMin) return true;
-                if (yMin > rhs.yMin) return false;
+                if (tileY < rhs.tileY) return true;
+                if (tileY > rhs.tileY) return false;
                 if (size < rhs.size) return true;
                 if (size > rhs.size) return false;
-                return patch == false && rhs.patch == true;
+                if (patch == false && rhs.patch == true) return true;
+                return false;
             }
 
             int      lod;
-            double   yMin;
+            int      tileY;
             bool     patch;
             unsigned size;
         };
 
-        typedef std::map<GeometryKey, osg::ref_ptr<osg::Geometry> > GeometryMap;
+        typedef std::map<GeometryKey, osg::ref_ptr<SharedGeometry> > GeometryMap;
 
         /**
          * Gets the Geometry associated with a tile key, creating a new one if
@@ -94,13 +180,25 @@ namespace osgEarth { namespace Drivers { namespace RexTerrainEngine
         void getPooledGeometry(
             const TileKey&               tileKey,
             const MapInfo&               mapInfo,
-            osg::ref_ptr<osg::Geometry>& out,
-            MaskGenerator*               maskSet=0L);
+            unsigned                     tileSize,
+            MaskGenerator*               maskSet,
+            osg::ref_ptr<SharedGeometry>& out);
 
         /**
          * The number of elements (incides) in the terrain skirt, if applicable
          */
-        int getNumSkirtElements() const;
+        int getNumSkirtElements(unsigned tileSize) const;
+
+        /**
+         * Are we doing pooling?
+         */
+        bool isEnabled() const { return _enabled; }
+
+        /**
+         * Clear and reset the pool.
+         */
+        void clear();
+
 
     public: // osg::Node
 
@@ -112,7 +210,7 @@ namespace osgEarth { namespace Drivers { namespace RexTerrainEngine
 
         mutable Threading::Mutex       _geometryMapMutex;
         GeometryMap                    _geometryMap;
-        unsigned                       _tileSize;
+        //unsigned                       _tileSize;
         const RexTerrainEngineOptions& _options; 
         osg::ref_ptr<ResourceReleaser> _releaser;
 
@@ -124,9 +222,10 @@ namespace osgEarth { namespace Drivers { namespace RexTerrainEngine
             const MapInfo& mapInfo,
             GeometryKey&   out) const;
 
-        osg::Geometry* createGeometry(
+        SharedGeometry* createGeometry(
             const TileKey& tileKey,
             const MapInfo& mapInfo,
+            unsigned       tileSize,
             MaskGenerator* maskSet ) const;
 
         bool _enabled;
diff --git a/src/osgEarthDrivers/engine_rex/GeometryPool.cpp b/src/osgEarthDrivers/engine_rex/GeometryPool.cpp
index 9f5978c..43422cc 100644
--- a/src/osgEarthDrivers/engine_rex/GeometryPool.cpp
+++ b/src/osgEarthDrivers/engine_rex/GeometryPool.cpp
@@ -18,10 +18,13 @@
 */
 #include "GeometryPool"
 #include <osgEarth/Locators>
+#include <osgEarth/NodeUtils>
+#include <osgEarthUtil/TopologyGraph>
 #include <osg/Point>
 #include <cstdlib> // for getenv
 
 using namespace osgEarth;
+using namespace osgEarth::Util;
 using namespace osgEarth::Drivers::RexTerrainEngine;
 
 #define LC "[GeometryPool] "
@@ -31,6 +34,17 @@ using namespace osgEarth::Drivers::RexTerrainEngine;
 /// JB:  Disabled to fix issues with ATI.
 //#define SHARE_TEX_COORDS 1
 
+//struct DebugGeometry : public osg::Geometry {
+//    void compileGLObjects(osg::RenderInfo& renderInfo) const {
+//        OE_WARN << "Compiling GL Objects: " << this << std::endl;
+//        osg::Geometry::compileGLObjects(renderInfo);
+//    }
+//    void releaseGLObjects(osg::State* state) const {
+//        OE_WARN << "Releasing GL Objects: " << this << std::endl;
+//        osg::Geometry::releaseGLObjects(state);
+//    }
+//};
+
 
 GeometryPool::GeometryPool(const RexTerrainEngineOptions& options) :
 _options ( options ),
@@ -40,7 +54,7 @@ _debug   ( false )
     // sign up for the update traversal so we can prune unused pool objects.
     setNumChildrenRequiringUpdateTraversal(1u);
 
-    _tileSize = _options.tileSize().get();
+    //_tileSize = _options.tileSize().get();
 
     // activate debugging mode
     if ( getenv("OSGEARTH_DEBUG_REX_GEOMETRY_POOL") != 0L )
@@ -62,14 +76,15 @@ _debug   ( false )
 }
 
 void
-GeometryPool::getPooledGeometry(const TileKey&               tileKey,
-                                const MapInfo&               mapInfo,
-                                osg::ref_ptr<osg::Geometry>& out,
-                                MaskGenerator*               maskSet)
+GeometryPool::getPooledGeometry(const TileKey&                tileKey,
+                                const MapInfo&                mapInfo,
+                                unsigned                      tileSize,
+                                MaskGenerator*                maskSet,
+                                osg::ref_ptr<SharedGeometry>& out)
 {
     // convert to a unique-geometry key:
     GeometryKey geomKey;
-    createKeyForTileKey( tileKey, _tileSize, mapInfo, geomKey );
+    createKeyForTileKey( tileKey, tileSize, mapInfo, geomKey );
 
     if ( _enabled )
     {
@@ -87,10 +102,12 @@ GeometryPool::getPooledGeometry(const TileKey&               tileKey,
         else
         {
             // Not found. Create it.
-            out = createGeometry( tileKey, mapInfo, maskSet );
+            out = createGeometry( tileKey, mapInfo, tileSize, maskSet );
 
-            if (!masking)
+            if (!masking && out.valid())
+            {
                 _geometryMap[ geomKey ] = out.get();
+            }
 
             if ( _debug )
             {
@@ -101,25 +118,25 @@ GeometryPool::getPooledGeometry(const TileKey&               tileKey,
 
     else
     {
-        out = createGeometry( tileKey, mapInfo, maskSet );
+        out = createGeometry( tileKey, mapInfo, tileSize, maskSet );
     }
 }
 
 void
 GeometryPool::createKeyForTileKey(const TileKey&             tileKey,
-                                  unsigned                   size,
+                                  unsigned                   tileSize,
                                   const MapInfo&             mapInfo,
                                   GeometryPool::GeometryKey& out) const
 {
     out.lod  = tileKey.getLOD();
-    out.yMin = mapInfo.isGeocentric()? tileKey.getExtent().yMin() : 0.0;
-    out.size = size;
+    out.tileY = mapInfo.isGeocentric()? tileKey.getTileY() : 0;
+    out.size = tileSize;
 }
 
 int
-GeometryPool::getNumSkirtElements() const
+GeometryPool::getNumSkirtElements(unsigned tileSize) const
 {
-    return _options.heightFieldSkirtRatio().get() > 0.0 ? (_tileSize-1) * 4 * 6 : 0;
+    return _options.heightFieldSkirtRatio().get() > 0.0 ? (tileSize-1) * 4 * 6 : 0;
 }
 
 namespace
@@ -158,9 +175,20 @@ namespace
     } \
 }
 
-osg::Geometry*
+#define addMaskSkirtTriangles(INDEX0, INDEX1) \
+{ \
+    primSet->addElement((INDEX0));   \
+    primSet->addElement((INDEX0)+1); \
+    primSet->addElement((INDEX1));   \
+    primSet->addElement((INDEX1));   \
+    primSet->addElement((INDEX0)+1); \
+    primSet->addElement((INDEX1)+1); \
+}
+
+SharedGeometry*
 GeometryPool::createGeometry(const TileKey& tileKey,
                              const MapInfo& mapInfo,
+                             unsigned       tileSize,
                              MaskGenerator* maskSet) const
 {    
     // Establish a local reference frame for the tile:
@@ -176,46 +204,54 @@ GeometryPool::createGeometry(const TileKey& tileKey,
     // Attempt to calculate the number of verts in the surface geometry.
     bool createSkirt = _options.heightFieldSkirtRatio() > 0.0f;
 
-    unsigned numVertsInSurface    = (_tileSize*_tileSize);
-    unsigned numVertsInSkirt      = createSkirt ? _tileSize*4u - 4u : 0;
+    unsigned numVertsInSurface    = (tileSize*tileSize);
+    unsigned numVertsInSkirt      = createSkirt ? tileSize*4u - 4u : 0;
     unsigned numVerts             = numVertsInSurface + numVertsInSkirt;    
-    unsigned numIndiciesInSurface = (_tileSize-1) * (_tileSize-1) * 6;
-    unsigned numIncidesInSkirt    = getNumSkirtElements();
+    unsigned numIndiciesInSurface = (tileSize-1) * (tileSize-1) * 6;
+    unsigned numIncidesInSkirt    = getNumSkirtElements(tileSize);
     
+    // TODO: reconsider this ... 
     GLenum mode = (_options.gpuTessellation() == true) ? GL_PATCHES : GL_TRIANGLES;
 
-    // Pre-allocate enough space for all triangles.
-    osg::DrawElements* primSet = new osg::DrawElementsUShort(mode);
-
-    primSet->reserveElements(numIndiciesInSurface + numIncidesInSkirt);
-
     osg::BoundingSphere tileBound;
 
     // the geometry:
-    osg::Geometry* geom = new osg::Geometry();
+    osg::ref_ptr<SharedGeometry> geom = new SharedGeometry();
     geom->setUseVertexBufferObjects(true);
-    geom->setUseDisplayList(false);
+    //geom->setUseDisplayList(false);
+
+    osg::ref_ptr<osg::VertexBufferObject> vbo = new osg::VertexBufferObject();
 
-    geom->addPrimitiveSet( primSet );
+    // Pre-allocate enough space for all triangles.
+    osg::DrawElements* primSet = new osg::DrawElementsUShort(mode);
+    primSet->setElementBufferObject(new osg::ElementBufferObject());
+    primSet->reserveElements(numIndiciesInSurface + numIncidesInSkirt);
+    geom->setDrawElements(primSet);
 
     // the vertex locations:
     osg::Vec3Array* verts = new osg::Vec3Array();
+    verts->setVertexBufferObject(vbo.get());
     verts->reserve( numVerts );
+    verts->setBinding(verts->BIND_PER_VERTEX);
     geom->setVertexArray( verts );
 
     // the surface normals (i.e. extrusion vectors)
     osg::Vec3Array* normals = new osg::Vec3Array();
+    normals->setVertexBufferObject(vbo.get());
     normals->reserve( numVerts );
+    normals->setBinding(normals->BIND_PER_VERTEX);
     geom->setNormalArray( normals );
-    geom->setNormalBinding( geom->BIND_PER_VERTEX );
-
+    
     osg::Vec3Array* neighbors = 0L;
     if ( _options.morphTerrain() == true )
     {
         // neighbor positions (for morphing)
         neighbors = new osg::Vec3Array();
+        neighbors->setBinding(neighbors->BIND_PER_VERTEX);
+        neighbors->setVertexBufferObject(vbo.get());
         neighbors->reserve( numVerts );
-        geom->setTexCoordArray( 1, neighbors );
+        geom->setNeighborArray(neighbors);
+        //geom->setTexCoordArray( 1, neighbors );
     }
 
     // tex coord is [0..1] across the tile. The 3rd dimension tracks whether the
@@ -232,24 +268,26 @@ GeometryPool::createGeometry(const TileKey& tileKey,
 #else
     bool populateTexCoords = true;
     osg::Vec3Array* texCoords = new osg::Vec3Array();
+    texCoords->setBinding(texCoords->BIND_PER_VERTEX);
+    texCoords->setVertexBufferObject(vbo.get());
     texCoords->reserve( numVerts );
 #endif
 
-    geom->setTexCoordArray( 0, texCoords );
+    geom->setTexCoordArray(texCoords);
     
-    float delta = 1.0/(_tileSize-1);
+    float delta = 1.0/(tileSize-1);
     osg::Vec3d tdelta(delta,0,0);
     tdelta.normalize();
     osg::Vec3d vZero(0,0,0);
 
     osg::ref_ptr<GeoLocator> locator = GeoLocator::createForKey( tileKey, mapInfo );
 
-    for(unsigned row=0; row<_tileSize; ++row)
+    for(unsigned row=0; row<tileSize; ++row)
     {
-        float ny = (float)row/(float)(_tileSize-1);
-        for(unsigned col=0; col<_tileSize; ++col)
+        float ny = (float)row/(float)(tileSize-1);
+        for(unsigned col=0; col<tileSize; ++col)
         {
-            float nx = (float)col/(float)(_tileSize-1);
+            float nx = (float)col/(float)(tileSize-1);
 
             osg::Vec3d model;
             locator->unitToModel(osg::Vec3d(nx, ny, 0.0f), model);
@@ -273,7 +311,7 @@ GeometryPool::createGeometry(const TileKey& tileKey,
             // neighbor:
             if ( neighbors )
             {
-                osg::Vec3d modelNeighborLTP = (*verts)[verts->size() - getMorphNeighborIndexOffset(col, row, _tileSize)];
+                osg::Vec3d modelNeighborLTP = (*verts)[verts->size() - getMorphNeighborIndexOffset(col, row, tileSize)];
                 neighbors->push_back(modelNeighborLTP);
             }
         }
@@ -284,21 +322,21 @@ GeometryPool::createGeometry(const TileKey& tileKey,
     // TODO: do we really need this??
     bool swapOrientation = !locator->orientationOpenGL();
 
-    for(unsigned j=0; j<_tileSize-1; ++j)
+    for(unsigned j=0; j<tileSize-1; ++j)
     {
-        for(unsigned i=0; i<_tileSize-1; ++i)
+        for(unsigned i=0; i<tileSize-1; ++i)
         {
             int i00;
             int i01;
             if (swapOrientation)
             {
-                i01 = j*_tileSize + i;
-                i00 = i01+_tileSize;
+                i01 = j*tileSize + i;
+                i00 = i01+tileSize;
             }
             else
             {
-                i00 = j*_tileSize + i;
-                i01 = i00+_tileSize;
+                i00 = j*tileSize + i;
+                i01 = i00+tileSize;
             }
 
             int i10 = i00+1;
@@ -331,7 +369,70 @@ GeometryPool::createGeometry(const TileKey& tileKey,
         }
     }
 
-    if ( createSkirt )
+    // create mask geometry
+    bool skirtCreated = false;
+
+    if (maskSet)
+    {
+        int s = verts->size();
+        osg::ref_ptr<osg::DrawElementsUInt> maskPrim = maskSet->createMaskPrimitives(mapInfo, verts, texCoords, normals, neighbors);
+        if (maskPrim && maskPrim->size() > 0)
+        {
+            maskPrim->setElementBufferObject(primSet->getElementBufferObject());
+            geom->setMaskElements(maskPrim.get());
+
+            if (createSkirt)
+            {
+                // Skirts for masking geometries are complicated. There are two parts.
+                // The first part is the "perimeter" of the tile, i.e the outer edge of the 
+                // tessellation. This code will detect that outer boundary and create skrits
+                // for it.
+                // The second part (NYI) detects the actual inner boundary ("patch geometry")
+                // that patches the tile tessellation to the masking boundary. TDB.
+                TopologyGraph topo;
+                BuildTopologyVisitor visitor(topo);
+                visitor.apply(geom.get(), verts); 
+
+                if (topo._verts.empty() == false)
+                {
+                    TopologyGraph::IndexVector boundary;
+                    topo.createBoundary(boundary);
+                
+                    double height = tileBound.radius() * _options.heightFieldSkirtRatio().get();
+                    unsigned skirtIndex = verts->size();
+
+                    unsigned matches = 0;
+                    for (TopologyGraph::IndexVector::const_iterator i = boundary.begin(); i != boundary.end(); ++i)
+                    {
+                        int k;
+                        for (k = 0; k<skirtIndex; ++k)
+                        {
+                            if ((*verts)[k].x() == (*i)->x() && (*verts)[k].y() == (*i)->y())
+                            {
+                                addSkirtDataForIndex(k, height);
+                                matches++;
+                                break;
+                            }
+                        }
+                    }
+
+                    if (matches != boundary.size()) {
+                        OE_WARN << LC << "matches != boundary size" << std::endl;
+                    }
+
+                    int n;
+                    for (n = skirtIndex; n<(int)verts->size()-2; n+=2)
+                        addMaskSkirtTriangles(n, n+2);
+
+                    addMaskSkirtTriangles(n, skirtIndex);
+
+                    skirtCreated = true;
+                }
+            }
+        }
+    }
+
+    if ( createSkirt && !skirtCreated )
     {
         // SKIRTS:
         // calculate the skirt extrusion height
@@ -340,17 +441,17 @@ GeometryPool::createGeometry(const TileKey& tileKey,
         unsigned skirtIndex = verts->size();
 
         // first, create all the skirt verts, normals, and texcoords.
-        for(int c=0; c<(int)_tileSize-1; ++c)
+        for(int c=0; c<(int)tileSize-1; ++c)
             addSkirtDataForIndex( c, height ); //top
 
-        for(int r=0; r<(int)_tileSize-1; ++r)
-            addSkirtDataForIndex( r*_tileSize+(_tileSize-1), height ); //right
+        for(int r=0; r<(int)tileSize-1; ++r)
+            addSkirtDataForIndex( r*tileSize+(tileSize-1), height ); //right
     
-        for(int c=_tileSize-1; c>=0; --c)
-            addSkirtDataForIndex( (_tileSize-1)*_tileSize+c, height ); //bottom
+        for(int c=tileSize-1; c>=0; --c)
+            addSkirtDataForIndex( (tileSize-1)*tileSize+c, height ); //bottom
 
-        for(int r=_tileSize-1; r>=0; --r)
-            addSkirtDataForIndex( r*_tileSize, height ); //left
+        for(int r=tileSize-1; r>=0; --r)
+            addSkirtDataForIndex( r*tileSize, height ); //left
     
         // then create the elements indices:
         int i;
@@ -360,15 +461,7 @@ GeometryPool::createGeometry(const TileKey& tileKey,
         addSkirtTriangles( i, skirtIndex );
     }
 
-    // create mask geometry
-    if (maskSet)
-    {
-        osg::ref_ptr<osg::DrawElementsUInt> maskPrim = maskSet->createMaskPrimitives(mapInfo, verts, texCoords, normals, neighbors);
-        if (maskPrim)
-            geom->addPrimitiveSet( maskPrim );
-    }
-
-    return geom;
+    return geom.release();
 }
 
 
@@ -403,12 +496,19 @@ GeometryPool::traverse(osg::NodeVisitor& nv)
                 {
                     keys.push_back(i->first);
                     objects.push_back(i->second.get());
+                    
+                    //OE_INFO << "Releasing: " << i->second.get() << std::endl;
                 }
             }
             for (std::vector<GeometryKey>::iterator key = keys.begin(); key != keys.end(); ++key)
             {
+                if (_geometryMap[*key]->referenceCount() != 2) // one for the map, and one for the local objects list
+                    OE_WARN << LC << "Erasing key geom with refcount <> 2" << std::endl;
+
                 _geometryMap.erase(*key);
             }
+
+            //OE_WARN << "Released " << keys.size() << ", pool = " << _geometryMap.size() << std::endl;
         }
 
         if (!objects.empty())
@@ -419,3 +519,332 @@ GeometryPool::traverse(osg::NodeVisitor& nv)
 
     osg::Group::traverse(nv);
 }
+
+
+void
+GeometryPool::clear()
+{
+    if (!_releaser.valid() || !_enabled)
+        return;
+
+    ResourceReleaser::ObjectList objects;
+
+    // collect all objects in a thread safe manner
+    {
+        Threading::ScopedMutexLock exclusive( _geometryMapMutex );
+
+        for (GeometryMap::iterator i = _geometryMap.begin(); i != _geometryMap.end(); ++i)
+        {
+            //if (i->second.get()->referenceCount() == 1)
+            {
+                objects.push_back(i->second.get());
+            }
+        }
+
+        _geometryMap.clear();
+
+        if (!objects.empty())
+        {
+            OE_INFO << LC << "Cleared " << objects.size() << " objects from the geometry pool\n";
+        }
+    }
+
+    // submit to the releaser.
+    if (!objects.empty())
+    {
+        _releaser->push(objects);
+    }
+}
+
+//.........................................................................
+// Code mostly adapted from osgTerrain SharedGeometry.
+
+SharedGeometry::SharedGeometry()
+{
+    setSupportsDisplayList(false);
+    _supportsVertexBufferObjects = true;
+}
+
+SharedGeometry::SharedGeometry(const SharedGeometry& rhs,const osg::CopyOp& copyop):
+    osg::Drawable(rhs, copyop),
+    _vertexArray(rhs._vertexArray),
+    _normalArray(rhs._normalArray),
+    _texcoordArray(rhs._texcoordArray),
+    _neighborArray(rhs._neighborArray),
+    _drawElements(rhs._drawElements),
+    _maskElements(rhs._maskElements)
+{
+    //nop
+}
+
+SharedGeometry::~SharedGeometry()
+{
+    //nop
+}
+
+bool 
+SharedGeometry::empty() const
+{
+    return
+        (_drawElements.valid() == false || _drawElements->getNumIndices() == 0) &&
+        (_maskElements.valid() == false || _maskElements->getNumIndices() == 0);
+}
+
+
+#ifdef SUPPORTS_VAO
+osg::VertexArrayState* SharedGeometry::createVertexArrayState(osg::RenderInfo& renderInfo) const
+{
+    osg::State& state = *renderInfo.getState();
+
+    osg::VertexArrayState* vas = new osg::VertexArrayState(&state);
+
+    if (_vertexArray.valid()) vas->assignVertexArrayDispatcher();
+    if (_normalArray.valid()) vas->assignNormalArrayDispatcher();
+    unsigned texUnits = 0;
+    if (_neighborArray.valid())
+    {
+        texUnits = 2;
+    }
+    else if (_texcoordArray.valid())
+    {
+        texUnits = 1;
+    }
+    if (texUnits)
+        vas->assignTexCoordArrayDispatcher(texUnits);
+    if (state.useVertexArrayObject(_useVertexArrayObject))
+    {
+        vas->generateVertexArrayObject();
+    }
+
+    return vas;
+}
+#endif
+
+void SharedGeometry::compileGLObjects(osg::RenderInfo& renderInfo) const
+{
+    if (!_vertexArray)
+        return;
+
+    if (_vertexArray->getVertexBufferObject())
+    {
+        osg::State& state = *renderInfo.getState();
+        unsigned int contextID = state.getContextID();
+        osg::GLExtensions* extensions = state.get<osg::GLExtensions>();
+        if (!extensions) return;
+
+        osg::BufferObject* vbo = _vertexArray->getVertexBufferObject();
+        osg::GLBufferObject* vbo_glBufferObject = vbo->getOrCreateGLBufferObject(contextID);
+        if (vbo_glBufferObject && vbo_glBufferObject->isDirty())
+        {
+            // OSG_NOTICE<<"Compile buffer "<<glBufferObject<<std::endl;
+            vbo_glBufferObject->compileBuffer();
+            extensions->glBindBuffer(GL_ARRAY_BUFFER_ARB,0);
+        }
+
+        osg::BufferObject* ebo = _drawElements->getElementBufferObject();
+        osg::GLBufferObject* ebo_glBufferObject = ebo->getOrCreateGLBufferObject(contextID);
+        if (ebo_glBufferObject && vbo_glBufferObject->isDirty())
+        {
+            // OSG_NOTICE<<"Compile buffer "<<glBufferObject<<std::endl;
+            ebo_glBufferObject->compileBuffer();
+            extensions->glBindBuffer(GL_ELEMENT_ARRAY_BUFFER_ARB,0);
+        }
+
+#ifdef SUPPORTS_VAO
+        if (state.useVertexArrayObject(_useVertexArrayObject))
+        {
+            osg::VertexArrayState* vas = 0;
+
+            _vertexArrayStateList[contextID] = vas = createVertexArrayState(renderInfo);
+
+            osg::State::SetCurrentVertexArrayStateProxy setVASProxy(state, vas);
+
+#if OSG_MIN_VERSION_REQUIRED(3,5,6)
+            state.bindVertexArrayObject(vas);
+#else
+            vas->bindVertexArrayObject();
+#endif
+
+            if (vbo_glBufferObject) vas->bindVertexBufferObject(vbo_glBufferObject);
+            if (ebo_glBufferObject) vas->bindElementBufferObject(ebo_glBufferObject);
+        }
+#endif
+    }
+    else
+    {
+        Drawable::compileGLObjects(renderInfo);
+    }
+}
+
+void SharedGeometry::resizeGLObjectBuffers(unsigned int maxSize)
+{
+    Drawable::resizeGLObjectBuffers(maxSize);
+
+    osg::BufferObject* vbo = _vertexArray->getVertexBufferObject();
+    if (vbo) vbo->resizeGLObjectBuffers(maxSize);
+
+    osg::BufferObject* ebo = _drawElements->getElementBufferObject();
+    if (ebo) ebo->resizeGLObjectBuffers(maxSize);
+}
+
+void SharedGeometry::releaseGLObjects(osg::State* state) const
+{
+    Drawable::releaseGLObjects(state);
+
+    osg::BufferObject* vbo = _vertexArray->getVertexBufferObject();
+    if (vbo) vbo->releaseGLObjects(state);
+
+    osg::BufferObject* ebo = _drawElements->getElementBufferObject();
+    if (ebo) ebo->releaseGLObjects(state);
+}
+
+// called from DrawTileCommand
+void SharedGeometry::render(GLenum primitiveType, osg::RenderInfo& renderInfo) const
+{
+    osg::State& state = *renderInfo.getState();
+    
+#if OSG_VERSION_LESS_THAN(3,5,6)
+    osg::ArrayDispatchers& dispatchers = state.getArrayDispatchers();
+#else
+    osg::AttributeDispatchers& dispatchers = state.getAttributeDispatchers();
+#endif
+
+    dispatchers.reset();
+    dispatchers.setUseVertexAttribAlias(state.getUseVertexAttributeAliasing());
+    dispatchers.activateNormalArray(_normalArray.get());
+
+#ifdef SUPPORTS_VAO
+    osg::VertexArrayState* vas = state.getCurrentVertexArrayState();
+    if (!state.useVertexArrayObject(_useVertexArrayObject) || vas->getRequiresSetArrays())
+    {
+        // OSG_NOTICE<<"   sending vertex arrays vas->getRequiresSetArrays()="<<vas->getRequiresSetArrays()<<std::endl;
+
+        vas->lazyDisablingOfVertexAttributes();
+
+        // set up arrays
+        if( _vertexArray.valid() )
+            vas->setVertexArray(state, _vertexArray.get());
+
+        if (_normalArray.valid() && _normalArray->getBinding()==osg::Array::BIND_PER_VERTEX)
+            vas->setNormalArray(state, _normalArray.get());
+
+        if (_colorArray.valid() && _colorArray->getBinding()==osg::Array::BIND_PER_VERTEX)
+            vas->setColorArray(state, _colorArray.get());
+
+        if (_texcoordArray.valid() && _texcoordArray->getBinding()==osg::Array::BIND_PER_VERTEX)
+            vas->setTexCoordArray(state, 0, _texcoordArray.get());
+
+        if (_neighborArray.valid() && _neighborArray->getBinding()==osg::Array::BIND_PER_VERTEX)
+            vas->setTexCoordArray(state, 1, _neighborArray.get());
+
+        vas->applyDisablingOfVertexAttributes(state);
+    }
+    else
+#endif
+
+    {
+        state.lazyDisablingOfVertexAttributes();
+
+        if( _vertexArray.valid() )
+            state.setVertexPointer(_vertexArray.get());
+
+        if (_normalArray.valid())
+            state.setNormalPointer(_normalArray.get());
+
+        if (_texcoordArray.valid())
+            state.setTexCoordPointer(0, _texcoordArray.get());
+
+        if (_neighborArray.valid())
+            state.setTexCoordPointer(1, _neighborArray.get());
+
+        state.applyDisablingOfVertexAttributes();
+    }
+
+
+#ifdef SUPPORTS_VAO
+    bool request_bind_unbind = !state.useVertexArrayObject(_useVertexArrayObject) || state.getCurrentVertexArrayState()->getRequiresSetArrays();
+#else
+    bool request_bind_unbind = true;
+#endif
+
+    osg::GLBufferObject* ebo = _drawElements->getOrCreateGLBufferObject(state.getContextID());
+
+    if (ebo)
+    {
+        /*if (request_bind_unbind)*/ state.bindElementBufferObject(ebo);
+
+        glDrawElements(primitiveType, _drawElements->getNumIndices(), _drawElements->getDataType(), (const GLvoid *)(ebo->getOffset(_drawElements->getBufferIndex())));
+
+        if (_maskElements.valid())
+        {
+            glDrawElements(primitiveType, _maskElements->getNumIndices(), _maskElements->getDataType(), (const GLvoid *)(ebo->getOffset(_maskElements->getBufferIndex())));
+        }
+
+        /*if (request_bind_unbind)*/ state.unbindElementBufferObject();
+    }
+    else
+    {
+        glDrawElements(primitiveType, _drawElements->getNumIndices(), _drawElements->getDataType(), _drawElements->getDataPointer());
+
+        if (_maskElements.valid())
+        {
+            glDrawElements(primitiveType, _maskElements->getNumIndices(), _maskElements->getDataType(), _maskElements->getDataPointer());
+        }
+    }
+
+    // unbind the VBO's if any are used.
+    if (request_bind_unbind)
+    {
+        state.unbindVertexBufferObject();
+    }
+}
+
+void SharedGeometry::accept(osg::Drawable::AttributeFunctor& af)
+{
+    osg::AttributeFunctorArrayVisitor afav(af);
+
+    afav.applyArray(VERTICES,_vertexArray.get());
+    afav.applyArray(NORMALS, _normalArray.get());
+    afav.applyArray(TEXTURE_COORDS_0,_texcoordArray.get());
+    afav.applyArray(TEXTURE_COORDS_1,_neighborArray.get());
+}
+
+void SharedGeometry::accept(osg::Drawable::ConstAttributeFunctor& af) const
+{
+    osg::ConstAttributeFunctorArrayVisitor afav(af);
+
+    afav.applyArray(VERTICES,_vertexArray.get());
+    afav.applyArray(NORMALS, _normalArray.get());
+    afav.applyArray(TEXTURE_COORDS_0,_texcoordArray.get());
+    afav.applyArray(TEXTURE_COORDS_1,_neighborArray.get());
+}
+
+void SharedGeometry::accept(osg::PrimitiveFunctor& pf) const
+{
+    pf.setVertexArray(_vertexArray->getNumElements(),static_cast<const osg::Vec3*>(_vertexArray->getDataPointer()));
+    _drawElements->accept(pf);
+}
+
+void SharedGeometry::accept(osg::PrimitiveIndexFunctor& pif) const
+{
+    pif.setVertexArray(_vertexArray->getNumElements(),static_cast<const osg::Vec3*>(_vertexArray->getDataPointer()));
+    _drawElements->accept(pif);
+}
+
+osg::Geometry*
+SharedGeometry::makeOsgGeometry()
+{
+    osg::Geometry* geom = new osg::Geometry();
+    geom->setUseVertexBufferObjects(true);
+    geom->setUseDisplayList(false);
+
+    geom->setVertexArray(getVertexArray());
+    geom->setNormalArray(getNormalArray());
+    geom->setTexCoordArray(0, getTexCoordArray());
+    if (getDrawElements())
+        geom->addPrimitiveSet(getDrawElements());
+    if (getMaskElements())
+        geom->addPrimitiveSet(getMaskElements());
+
+    return geom;
+}
diff --git a/src/osgEarthDrivers/engine_rex/LayerDrawable b/src/osgEarthDrivers/engine_rex/LayerDrawable
new file mode 100644
index 0000000..85f1c46
--- /dev/null
+++ b/src/osgEarthDrivers/engine_rex/LayerDrawable
@@ -0,0 +1,87 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+ * Copyright 2008-2014 Pelican Mapping
+ * http://osgearth.org
+ *
+ * osgEarth is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+#ifndef OSGEARTH_REX_TERRAIN_LAYER_DRAWABLE_H
+#define OSGEARTH_REX_TERRAIN_LAYER_DRAWABLE_H 1
+
+#include "DrawTileCommand"
+#include "DrawState"
+
+#include <osgEarth/ImageLayer>
+#include <vector>
+
+using namespace osgEarth;
+
+namespace osgEarth { namespace Drivers { namespace RexTerrainEngine
+{
+    /**
+     * Drawable for single "Layer" i.e. rendering pass. 
+     * It is important that LayerDrawables be rendered in the order in which
+     * they appear. Since all LayerDrawables share a common bounds, this 
+     * should happen automatically, but let's keep an eye out for trouble.
+     */
+    class LayerDrawable : public osg::Drawable
+    {
+    public:
+        LayerDrawable();
+
+        // The (sorted) list of tiles to render for this layer
+        DrawTileCommands  _tiles;
+
+        // Determines whether to use the default surface shader program
+        Layer::RenderType _renderType;
+
+        // Pointer back to the actual Map layer, if there is one
+        const Layer* _layer;
+
+        // If _layer is a VisibleLayer, this will be set as well, otherwise NULL
+        const VisibleLayer* _visibleLayer;
+
+        // If _layer is an ImageLayer, this will be set as well, otherwise NULL
+        const ImageLayer* _imageLayer;
+
+        // Layer render order, which is pushed into a Uniform at render time.
+        // This value is assigned at cull time by RexTerrainEngineNode.
+        int _order;
+
+        // The last layer to render will have this flag set, which will
+        // prompt the render to dirty the osg::State to prevent corruption.
+        // This flag is set at cull time by RexTerrainEngineNode.
+        bool _clearOsgState;
+
+        // Reference the terrain-wide state
+        osg::ref_ptr<DrawState> _drawState;
+        
+
+    public: // osg::Drawable
+        
+        void drawImplementation(osg::RenderInfo& ri) const;
+
+        // All LayerDrawables share the common terrain bounds.
+        osg::BoundingSphere computeBound() const { return _drawState->_bs; }
+        osg::BoundingBox computeBoundingBox() const { return _drawState->_box; }
+    };
+
+
+    // Straight list of LayerDrawables.
+    typedef std::vector< osg::ref_ptr<LayerDrawable> > LayerDrawableList;
+    typedef std::map<UID, osg::ref_ptr<LayerDrawable> > LayerDrawableMap;
+
+} } } // namespace 
+
+#endif // OSGEARTH_REX_TERRAIN_LAYER_DRAWABLE_H
diff --git a/src/osgEarthDrivers/engine_rex/LayerDrawable.cpp b/src/osgEarthDrivers/engine_rex/LayerDrawable.cpp
new file mode 100644
index 0000000..a776b01
--- /dev/null
+++ b/src/osgEarthDrivers/engine_rex/LayerDrawable.cpp
@@ -0,0 +1,96 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+ * Copyright 2008-2014 Pelican Mapping
+ * http://osgearth.org
+ *
+ * osgEarth is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+#include "LayerDrawable"
+
+using namespace osgEarth::Drivers::RexTerrainEngine;
+
+#undef  LC
+#define LC "[LayerDrawable] "
+
+
+LayerDrawable::LayerDrawable() :
+_renderType(Layer::RENDERTYPE_TILE),
+_order(0),
+_layer(0L),
+_clearOsgState(false)
+{
+    setDataVariance(DYNAMIC);
+    setUseDisplayList(false);
+    setUseVertexBufferObjects(true);
+}
+
+void
+LayerDrawable::drawImplementation(osg::RenderInfo& ri) const
+{
+    //OE_INFO << LC << (_layer ? _layer->getName() : "[empty]") << " tiles=" << _tiles.size() << std::endl;
+
+    // Get this context's state values:
+    PerContextDrawState& ds = _drawState->getPCDS(ri.getContextID());
+
+    ds.refresh(ri, _drawState->_bindings);
+
+    if (_layer)
+    {
+        if (ds._layerUidUL >= 0)
+            ds._ext->glUniform1i(ds._layerUidUL,      (GLint)_layer->getUID());
+        if (ds._layerOpacityUL >= 0 && _visibleLayer)
+            ds._ext->glUniform1f(ds._layerOpacityUL,  (GLfloat)_visibleLayer->getOpacity());
+        if (ds._layerMinRangeUL >= 0 && _imageLayer)
+            ds._ext->glUniform1f(ds._layerMinRangeUL, (GLfloat)_imageLayer->getMinVisibleRange());
+        if (ds._layerMaxRangeUL >= 0 && _imageLayer)
+            ds._ext->glUniform1f(ds._layerMaxRangeUL, (GLfloat)_imageLayer->getMaxVisibleRange());
+    }
+    else
+    {
+        if (ds._layerUidUL >= 0)
+            ds._ext->glUniform1i(ds._layerUidUL,      (GLint)-1);
+        if (ds._layerOpacityUL >= 0)
+            ds._ext->glUniform1f(ds._layerOpacityUL,  (GLfloat)1.0f);
+        if (ds._layerMinRangeUL >= 0)
+            ds._ext->glUniform1f(ds._layerMinRangeUL, (GLfloat)0.0f);
+        if (ds._layerMaxRangeUL >= 0)
+            ds._ext->glUniform1f(ds._layerMaxRangeUL, (GLfloat)FLT_MAX);
+    }
+
+    for (DrawTileCommands::const_iterator tile = _tiles.begin(); tile != _tiles.end(); ++tile)
+    {
+        tile->draw(ri, *_drawState, 0L);
+    }
+
+    // If set, dirty all OSG state to prevent any leakage - this is sometimes
+    // necessary when doing custom OpenGL within a Drawable.
+    if (_clearOsgState)
+    {
+        // Necessary because tile->draw() applies texture attributes without informing the State:
+        ri.getState()->dirtyAllAttributes();
+
+        // NOTE: this is a NOOP in OSG 3.5.x, but not in 3.4.x ... Later we will need to
+        // revisit whether to call disableAllVertexArrays() in 3.5.x instead.
+        ri.getState()->dirtyAllVertexArrays();
+        
+        // unbind local buffers when finished.
+        ds._ext->glBindBuffer(GL_ARRAY_BUFFER_ARB,0);
+        ds._ext->glBindBuffer(GL_ELEMENT_ARRAY_BUFFER_ARB,0);
+
+        // gw: no need to do this, in fact it will cause positional attributes
+        // (light clip planes and lights) to immediately be reapplied under the
+        // current MVM, which will by definition be wrong!)
+        //ri.getState()->apply();
+    }
+}
diff --git a/src/osgEarthDrivers/engine_rex/LoadTileData b/src/osgEarthDrivers/engine_rex/LoadTileData
index e02a316..a3e8702 100644
--- a/src/osgEarthDrivers/engine_rex/LoadTileData
+++ b/src/osgEarthDrivers/engine_rex/LoadTileData
@@ -24,6 +24,8 @@
 #include "RenderBindings"
 #include "TileNode"
 #include "EngineContext"
+#include "TileRenderModel"
+#include <osgEarth/Progress>
 
 namespace osgEarth { namespace Drivers { namespace RexTerrainEngine
 {    
@@ -32,6 +34,10 @@ namespace osgEarth { namespace Drivers { namespace RexTerrainEngine
     public:
         LoadTileData(TileNode* tilenode, EngineContext* factory);
 
+        CreateTileModelFilter& filter() { return _filter; }
+
+        void setEnableCancelation(bool value) { _enableCancel = value; }
+
     public: // Loader::Request
 
         /** Fetches the data for the tile node. */
@@ -40,10 +46,21 @@ namespace osgEarth { namespace Drivers { namespace RexTerrainEngine
         /** Applies the fetched data to the tile node (scene-graph safe) */
         void apply(const osg::FrameStamp*);
 
+    public: // ProgressCallback
+
+        bool isCanceled();
+
     protected:
-        osg::observer_ptr<TileNode>    _tilenode;
-        EngineContext*              _context;
-        osg::ref_ptr<TerrainTileModel> _model;
+        osg::observer_ptr<TileNode> _tilenode;
+        osg::observer_ptr<TerrainEngineNode> _engine;
+        EngineContext* _context;
+        osg::ref_ptr<TerrainTileModel> _dataModel;
+        TileRenderModel _renderModel;
+        CreateTileModelFilter _filter;
+        MapFrame _mapFrame;
+        bool _enableCancel;
+
+        virtual ~LoadTileData() { }
     };
 
 } } } // namespace osgEarth::Drivers::RexTerrainEngine
diff --git a/src/osgEarthDrivers/engine_rex/LoadTileData.cpp b/src/osgEarthDrivers/engine_rex/LoadTileData.cpp
index fe3aace..548daa6 100644
--- a/src/osgEarthDrivers/engine_rex/LoadTileData.cpp
+++ b/src/osgEarthDrivers/engine_rex/LoadTileData.cpp
@@ -17,10 +17,9 @@
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
 */
 #include "LoadTileData"
-#include "MPTexture"
+#include "SurfaceNode"
 #include <osgEarth/TerrainEngineNode>
 #include <osgEarth/Terrain>
-#include <osgEarth/Registry>
 #include <osg/NodeVisitor>
 
 using namespace osgEarth::Drivers::RexTerrainEngine;
@@ -28,55 +27,24 @@ using namespace osgEarth;
 
 #define LC "[LoadTileData] "
 
-namespace
-{
-    // Visitor that recalculates the sampler inheritance matrices in a graph.
-    struct UpdateInheritance : public osg::NodeVisitor
-    {
-        UpdateInheritance(EngineContext* context, Loader::Request::ChangeSet& changeSet)
-            : _context(context),
-              _changeSet(changeSet)
-        {
-            setTraversalMode( TRAVERSE_ALL_CHILDREN );
-        }
-
-        void apply(osg::Group& node)
-        {
-            TileNode* tilenode = dynamic_cast<TileNode*>(&node);
-            if ( tilenode )
-            {
-                if ( tilenode->inheritState( _context ) )
-                {
-                    // return true = changes occurred.
-                    _changeSet.push_back( tilenode );
-                }
-            }
-
-            traverse(node);
-        }
-
-        Loader::Request::ChangeSet& _changeSet;
-        EngineContext*              _context;
-    };
-}
-
-//............................................................................
-
 
 LoadTileData::LoadTileData(TileNode* tilenode, EngineContext* context) :
 _tilenode(tilenode),
-_context(context)
+_context(context),
+_enableCancel(true)
 {
-    //nop
+    this->setTileKey(tilenode->getKey());
+    _mapFrame.setMap(context->getMap());
+    _engine = context->getEngine();
 }
 
 namespace
 {
-    void applyDefaultUnRefPolicy(osg::Texture* tex)
-    {
-        const optional<bool>& unRefPolicy = Registry::instance()->unRefImageDataAfterApply();
-        tex->setUnRefImageDataAfterApply( unRefPolicy.get() );
-    }
+    struct MyProgress : public ProgressCallback {
+        LoadTileData* _req;
+        MyProgress(LoadTileData* req) : _req(req) {}
+        bool isCanceled() { return _req->isIdle(); }
+    };
 }
 
 
@@ -84,137 +52,40 @@ namespace
 void
 LoadTileData::invoke()
 {
-    osg::ref_ptr<TileNode> tilenode;
-    if ( _tilenode.lock(tilenode) )
-    {
-        osg::ref_ptr<ProgressCallback> progress; // = new ProgressCallback();
+    if (!_mapFrame.isValid())
+        return;
 
-        // Assemble all the components necessary to display this tile
-        _model = _context->getEngine()->createTileModel(
-            _context->getMapFrame(),
-            tilenode->getTileKey(),
-            progress ); // progress
+    // we're in a pager thread, so must lock safe pointers
+    // (don't access _context from here!)
 
-        // Prep the stateset for merging (and for GL pre-compile).
-        if ( _model.valid() )
-        {
-#if 0
-            if ( progress.valid() )
-            {
-                int count = (int)progress->stats("http_get_count");
-                if ( count > 0 )
-                {
-                    double t = (progress->stats("http_get_time")*1000.0);
-                    OE_NOTICE << LC << tilenode->getTileKey().str()
-                        << " : http_get_time = " << t << " ms, "
-                        << " : http_get_count = " << count << ", avg = " << (t/count) << std::endl;
-                }
-            }
-#endif
-
-            const RenderBindings& bindings = _context->getRenderBindings();
-
-            osg::StateSet* stateSet = getStateSet();
-
-            // Insert all the color layers into a new MPTexture state attribute,
-            // which exists to facilitate GL pre-compilation.
-            if ( _model->colorLayers().size() > 0 )
-            {
-                const SamplerBinding* colorBinding = SamplerBinding::findUsage(bindings, SamplerBinding::COLOR);
-                if ( colorBinding )
-                {
-                    osg::ref_ptr<MPTexture> mptex = new MPTexture();
-
-                    for(TerrainTileImageLayerModelVector::iterator i = _model->colorLayers().begin();
-                        i != _model->colorLayers().end();
-                        ++i)
-                    {
-                        TerrainTileImageLayerModel* layerModel = i->get();
-                        if ( layerModel && layerModel->getTexture() )
-                        {
-                            applyDefaultUnRefPolicy( layerModel->getTexture() );
-                            mptex->setLayer( layerModel->getImageLayer(), layerModel->getTexture(), layerModel->getOrder() );
-                        }
-                    }
-
-                    if ( !mptex->getPasses().empty() )
-                    {
-                        stateSet->setTextureAttribute(
-                            colorBinding->unit(),
-                            mptex );
-                    }
-                }
-            }
-
-            // Insert the elevation texture and an identity matrix:
-            if ( _model->elevationModel().valid() && _model->elevationModel()->getTexture())
-            {
-                const SamplerBinding* binding = SamplerBinding::findUsage(bindings, SamplerBinding::ELEVATION);
-                if ( binding )
-                {                
-                    applyDefaultUnRefPolicy( _model->elevationModel()->getTexture() );
-
-                    stateSet->setTextureAttribute(
-                        binding->unit(),
-                        _model->elevationModel()->getTexture() );
-
-                    stateSet->removeUniform(binding->matrixName());
-
-                    stateSet->addUniform( _context->getOrCreateMatrixUniform(
-                        binding->matrixName(),
-                        osg::Matrixf::identity() ) );    
-                }
-            }
-            
-            // Insert the normal texture and an identity matrix:
-            if ( _model->normalModel().valid() && _model->normalModel()->getTexture() )
-            {
-                const SamplerBinding* binding = SamplerBinding::findUsage(bindings, SamplerBinding::NORMAL);
-                if ( binding )
-                {
-                    //TODO: if we subload the normal texture later on, we will need to change unref to false.
-                    applyDefaultUnRefPolicy( _model->normalModel()->getTexture() );
-
-                    stateSet->setTextureAttribute(
-                        binding->unit(),
-                        _model->normalModel()->getTexture() );
-
-                    stateSet->removeUniform(binding->matrixName());
-
-                    stateSet->addUniform( _context->getOrCreateMatrixUniform(
-                        binding->matrixName(),
-                        osg::Matrixf::identity() ) );
-                }
-            }
-
-            // Process any shared image layers, each of which should have its
-            // own sampler binding point
-            for(TerrainTileImageLayerModelVector::iterator i = _model->sharedLayers().begin();
-                i != _model->sharedLayers().end();
-                ++i)
-            {
-                TerrainTileImageLayerModel* layerModel = i->get();
-                if ( layerModel->getTexture() )
-                {
-                    const SamplerBinding* binding = SamplerBinding::findUID(bindings, layerModel->getImageLayer()->getUID());
-                    if ( binding )
-                    {
-                        applyDefaultUnRefPolicy( layerModel->getTexture() );
-
-                        stateSet->setTextureAttribute(
-                            binding->unit(),
-                            layerModel->getTexture() );
+    osg::ref_ptr<TileNode> tilenode;
+    if (!_tilenode.lock(tilenode))
+        return;
+
+    osg::ref_ptr<TerrainEngineNode> engine;
+    if (!_engine.lock(engine))
+        return;
+
+    // ensure the map frame is up to date:
+    if (_mapFrame.needsSync())
+        _mapFrame.sync();
+
+    // Only use a progress callback is cancelation is enabled.    
+    osg::ref_ptr<ProgressCallback> progress = _enableCancel ? new MyProgress(this) : 0L;
+
+    // Assemble all the components necessary to display this tile
+    _dataModel = engine->createTileModel(
+        _mapFrame,
+        tilenode->getKey(),           
+        _filter,
+        progress.get() );
+}
 
-                        stateSet->removeUniform(binding->matrixName());
 
-                        stateSet->addUniform( _context->getOrCreateMatrixUniform(
-                            binding->matrixName(),
-                            osg::Matrixf::identity() ) );
-                    }
-                }
-            }
-        }
-    }
+bool
+LoadTileData::isCanceled()
+{
+    return isIdle();
 }
 
 
@@ -222,47 +93,51 @@ LoadTileData::invoke()
 void
 LoadTileData::apply(const osg::FrameStamp* stamp)
 {
-    if ( _model.valid() )
+    // ensure we got an actual datamodel:
+    if (_dataModel.valid())
     {
-        osg::ref_ptr<TileNode> tilenode;
-        if ( _tilenode.lock(tilenode) )
+        // ensure it's in sync with the map revision (not out of date):
+        if (_dataModel->getRevision() == _context->getMap()->getDataModelRevision())
         {
-            const RenderBindings& bindings      = _context->getRenderBindings();
-            const SelectionInfo&  selectionInfo = _context->getSelectionInfo();
-            const MapInfo&        mapInfo       = _context->getMapFrame().getMapInfo();
-
-            const SamplerBinding* color = SamplerBinding::findUsage(bindings, SamplerBinding::COLOR);
-
-            // Find the mptexture, and then remove it since it was only in the state set for ICO compilation.
-            osg::ref_ptr<MPTexture> mptex = dynamic_cast<MPTexture*>(
-                getStateSet()->getTextureAttribute( color->unit(), osg::StateAttribute::TEXTURE) );
-
-            if ( mptex.valid() )
+            // ensure the tile node hasn't expired:
+            osg::ref_ptr<TileNode> tilenode;
+            if ( _tilenode.lock(tilenode) )
             {
-                getStateSet()->removeTextureAttribute( color->unit(), mptex.get() );
-            }
-
-            // Merge our prepped stateset into the live one.
-            tilenode->mergeStateSet( getStateSet(), mptex.get(), bindings);
+                const RenderBindings& bindings = _context->getRenderBindings();
 
-            // Update existing inheritance matrices as necessary.
-            UpdateInheritance update( _context, getChangeSet() );
-            tilenode->accept( update );
+                // Merge the new data into the tile.
+                tilenode->merge(_dataModel.get(), bindings);
 
-            // Mark as complete. TODO: per-data requests will do something different.
-            tilenode->setDirty( false );
+                // Mark as complete. TODO: per-data requests will do something different.
+                tilenode->setDirty( false );
 
-            // Notify listeners that we've added a tile.
-            _context->getEngine()->getTerrain()->notifyTileAdded( _key, tilenode );
+#if 0 // gw - moved the notifications to TileNode.
 
-            OE_DEBUG << LC << "apply " << _model->getKey().str() << "\n";
+                // Notify listeners that we've added a tile. The patch must be in world space
+                // (include a transform). Only need to fire onTileAdded if there's real elevation data...right?
+                if (_dataModel->elevationModel().valid())
+                {
+                    // Notify the terrain of the new tile. The "graph" needs to be
+                    // the entire terrain graph since REX can load tiles out of order.
+                    _context->getEngine()->getTerrain()->notifyTileAdded(
+                        _dataModel->getKey(),
+                        _context->getEngine()->getTerrain()->getGraph() );
+                }
+#endif
 
-            // Delete the model immediately
-            _model = 0L;
+                OE_DEBUG << LC << "apply " << _dataModel->getKey().str() << "\n";
+            }
+            else
+            {
+                OE_DEBUG << LC << "LoadTileData failed; TileNode disappeared\n";
+            }
         }
         else
         {
-            OE_DEBUG << LC << "LoadTileData failed; TileNode disappeared\n";
+            OE_INFO << LC << "apply " << _dataModel->getKey().str() << " ignored b/c it is out of date\n";
         }
+
+        // Delete the model immediately
+        _dataModel = 0L;
     }
 }
diff --git a/src/osgEarthDrivers/engine_rex/Loader b/src/osgEarthDrivers/engine_rex/Loader
index be16849..08d0ffc 100644
--- a/src/osgEarthDrivers/engine_rex/Loader
+++ b/src/osgEarthDrivers/engine_rex/Loader
@@ -32,15 +32,14 @@
 #include <set>
 
 namespace osgEarth {
-    class TerrainEngine;
+    class TerrainEngineNode;
 }
 
 namespace osgEarth { namespace Drivers { namespace RexTerrainEngine
 {
-    class TileNodeRegistry; // for UnloaderGroup
-
-
-    /** Interface for a utility to satifies loading requests. */
+    /**
+     * Interface for a utility to satisty Tile loading requests.
+     */
     class Loader
     {
     public:
@@ -136,6 +135,9 @@ namespace osgEarth { namespace Drivers { namespace RexTerrainEngine
     };
 
 
+    /**
+     * A Loader encapsulated in a Group Node.
+     */
     class LoaderGroup : public osg::Group, public Loader
     {
     public: // Loader
@@ -160,15 +162,27 @@ namespace osgEarth { namespace Drivers { namespace RexTerrainEngine
     };
 
 
-    /** Loader that uses the OSG database pager to run requests in the background. */
+    /**
+     * Loader that uses the OSG database pager to run requests in the background.
+     */
     class PagerLoader : public LoaderGroup
     {
     public:
-        PagerLoader(TerrainEngine* engine); //UID engineUID);
+        PagerLoader(TerrainEngineNode* engine);
+
+        /** Tell the loader the maximum LOD so it can properly scale the priorities. */
+        void setNumLODs(unsigned num);
 
         /** Sets the maximum number of requests to merge per frame. 0=infinity */
         void setMergesPerFrame(int);
 
+        /** Sets a priority offset for an LOD. The units are LODs. For example, setting the
+            offset for LOD 10 to +3 will give it the priority of an LOD 13 request. */
+        void setLODPriorityOffset(unsigned lod, float offset);
+
+        /** Set the priority scale for an LOD. */
+        void setLODPriorityScale(unsigned lod, float scale);
+
     public: // Loader
 
         /** Asks the loader to begin or continue loading something.
@@ -209,13 +223,16 @@ namespace osgEarth { namespace Drivers { namespace RexTerrainEngine
         //typedef std::set<RefRequest, SortRequest> MergeQueue;
         typedef std::multiset<RefRequest, SortRequest> MergeQueue;
 
-        UID              _engineUID;
+        //UID              _engineUID;
         osg::NodePath    _myNodePath;
         Requests         _requests;
         MergeQueue       _mergeQueue;  
         osg::Timer_t     _checkpoint;
         int              _mergesPerFrame;
         unsigned         _frameNumber;
+        unsigned         _numLODs;
+        float            _priorityScales[64];
+        float            _priorityOffsets[64];
 
         osg::ref_ptr<osgDB::Options> _dboptions;
         mutable Threading::Mutex     _requestsMutex;
diff --git a/src/osgEarthDrivers/engine_rex/Loader.cpp b/src/osgEarthDrivers/engine_rex/Loader.cpp
index 2064786..9e92588 100644
--- a/src/osgEarthDrivers/engine_rex/Loader.cpp
+++ b/src/osgEarthDrivers/engine_rex/Loader.cpp
@@ -20,6 +20,10 @@
 #include "RexTerrainEngineNode"
 
 #include <osgEarth/Registry>
+#include <osgEarth/Utils>
+#include <osgEarth/NodeUtils>
+#include <osgEarth/Metrics>
+
 #include <osgDB/FileNameUtils>
 #include <osgDB/FileUtils>
 #include <osgDB/Registry>
@@ -113,28 +117,31 @@ namespace
         {
             Location result = REMOTE_FILE;
 
-            osgEarth::UID requestUID, engineUID;
+            if (dboptions)
+            {
+                osgEarth::UID requestUID;
 
-            sscanf(filename.c_str(), "%d.%d", &requestUID, &engineUID);
+                const RexTerrainEngineNode* engine = dynamic_cast<const RexTerrainEngineNode*>(
+                    osg::getUserObject(dboptions, "osgEarth.RexTerrainEngineNode"));
 
-            osg::ref_ptr<RexTerrainEngineNode> engine;
-            RexTerrainEngineNode::getEngineByUID( (UID)engineUID, engine );
+                sscanf(filename.c_str(), "%u", &requestUID);
 
-            if ( engine.valid() )
-            {
-                PagerLoader* loader = dynamic_cast<PagerLoader*>( engine->getLoader() );
-                if ( loader )
+                if ( engine )
                 {
-                    TileKey key = loader->getTileKeyForRequest(requestUID);
-
-                    MapFrame frame(engine->getMap());
-                    if ( frame.isCached(key) )
+                    PagerLoader* loader = dynamic_cast<PagerLoader*>( engine->getLoader() );
+                    if ( loader )
                     {
-                        result = LOCAL_FILE;
+                        TileKey key = loader->getTileKeyForRequest(requestUID);
+
+                        MapFrame frame(engine->getMap());
+                        if ( frame.isCached(key) )
+                        {
+                            result = LOCAL_FILE;
+                        }
                     }
-                }
 
-                //OE_NOTICE << "key=" << key.str() << " : " << (result==LOCAL_FILE?"local":"remote") << "\n";
+                    //OE_NOTICE << "key=" << key.str() << " : " << (result==LOCAL_FILE?"local":"remote") << "\n";
+                }
             }
 
             return result;
@@ -173,16 +180,31 @@ namespace
 }
 
 
-PagerLoader::PagerLoader(TerrainEngine* engine) :
-_engineUID     ( engine->getUID() ),
+PagerLoader::PagerLoader(TerrainEngineNode* engine) :
 _checkpoint    ( (osg::Timer_t)0 ),
 _mergesPerFrame( 0 ),
-_frameNumber   ( 0 )
+_frameNumber   ( 0 ),
+_numLODs       ( 20u )
 {
     _myNodePath.push_back( this );
 
     _dboptions = new osgDB::Options();
     _dboptions->setFileLocationCallback( new FileLocationCallback() );
+
+    OptionsData<PagerLoader>::set(_dboptions.get(), "osgEarth.PagerLoader", this);
+
+    // initialize the LOD priority scales and offsets
+    for (unsigned i = 0; i < 64; ++i)
+    {
+        _priorityScales[i] = 1.0f;
+        _priorityOffsets[i] = 0.0f;
+    }
+}
+
+void
+PagerLoader::setNumLODs(unsigned lods)
+{
+    _numLODs = std::max(lods, 1u);
 }
 
 void
@@ -190,13 +212,28 @@ PagerLoader::setMergesPerFrame(int value)
 {
     _mergesPerFrame = std::max(value, 0);
     this->setNumChildrenRequiringUpdateTraversal( 1 );
+    OE_INFO << LC << "Merges per frame = " << _mergesPerFrame << std::endl;
+    
+}
+
+void
+PagerLoader::setLODPriorityScale(unsigned lod, float priorityScale)
+{
+    if (lod < 64)
+        _priorityScales[lod] = priorityScale;
+}
+
+void
+PagerLoader::setLODPriorityOffset(unsigned lod, float offset)
+{
+    if (lod < 64)
+        _priorityOffsets[lod] = offset;
 }
 
 bool
 PagerLoader::load(Loader::Request* request, float priority, osg::NodeVisitor& nv)
 {
     // check that the request is not already completed but unmerged:
-    //if ( request && !request->isMerging() && nv.getDatabaseRequestHandler() )
     if ( request && !request->isMerging() && !request->isFinished() && nv.getDatabaseRequestHandler() )
     {
         //OE_INFO << LC << "load (" << request->getTileKey().str() << ")" << std::endl;
@@ -218,8 +255,10 @@ PagerLoader::load(Loader::Request* request, float priority, osg::NodeVisitor& nv
             // remember the last tick at which this request was submitted
             request->_lastTick = osg::Timer::instance()->tick();
 
-            // update the priority
-            request->_priority = priority;
+            // update the priority, scale and bias it, and then normalize it to [0..1] range.
+            unsigned lod = request->getTileKey().getLOD();
+            float p = priority * _priorityScales[lod] + _priorityOffsets[lod];            
+            request->_priority = p / (float)(_numLODs+1);
 
             // timestamp it
             request->setFrameNumber( fn );
@@ -233,17 +272,13 @@ PagerLoader::load(Loader::Request* request, float priority, osg::NodeVisitor& nv
         request->unlock();
 
         char filename[64];
-        sprintf(filename, "%u.%u.osgearth_rex_loader", request->_uid, _engineUID);
-        //std::string filename = Stringify() << request->_uid << "." << _engineUID << ".osgearth_rex_loader";
-
-        // scale from LOD to 0..1 range, more or less
-        // TODO: need to balance this with normal PagedLOD priority setup
-        //float scaledPriority = priority / 20.0f;
+        //sprintf(filename, "%u.%u.osgearth_rex_loader", request->_uid, _engineUID);
+        sprintf(filename, "%u.osgearth_rex_loader", request->_uid);
 
         nv.getDatabaseRequestHandler()->requestNodeFile(
             filename,
             _myNodePath,
-            priority,
+            request->_priority,
             nv.getFrameStamp(),
             request->_internalHandle,
             _dboptions.get() );
@@ -278,24 +313,31 @@ PagerLoader::traverse(osg::NodeVisitor& nv)
             setFrameStamp(nv.getFrameStamp());
         }
 
-        int count;
-        for(count=0; count < _mergesPerFrame && !_mergeQueue.empty(); ++count)
+        // process pending merges.
         {
-            Request* req = _mergeQueue.begin()->get();
-            if ( req && req->_lastTick >= _checkpoint )
+            METRIC_BEGIN("loader.merge");
+            int count;
+            for(count=0; count < _mergesPerFrame && !_mergeQueue.empty(); ++count)
             {
-                OE_START_TIMER(req_apply);
-                req->apply( getFrameStamp() );
-                double s = OE_STOP_TIMER(req_apply);
+                Request* req = _mergeQueue.begin()->get();
+                if ( req && req->_lastTick >= _checkpoint )
+                {
+                    OE_START_TIMER(req_apply);
+                    req->apply( getFrameStamp() );
+                    double s = OE_STOP_TIMER(req_apply);
 
-                req->setState(Request::FINISHED);
-            }
+                    req->setState(Request::FINISHED);
+                }
 
-            _mergeQueue.erase( _mergeQueue.begin() );
+                _mergeQueue.erase( _mergeQueue.begin() );
+            }
+            METRIC_END("loader.merge");
         }
 
         // cull finished requests.
         {
+            METRIC_SCOPED("loader.cull");
+
             Threading::ScopedMutexLock lock( _requestsMutex );
 
             unsigned fn = 0;
@@ -306,7 +348,9 @@ PagerLoader::traverse(osg::NodeVisitor& nv)
             for(Requests::iterator i = _requests.begin(); i != _requests.end(); )
             {
                 Request* req = i->second.get();
+                const unsigned frameDiff = fn - req->getLastFrameSubmitted();
 
+                // Deal with completed requests:
                 if ( req->isFinished() )
                 {
                     //OE_INFO << LC << req->getName() << "(" << i->second->getUID() << ") finished." << std::endl; 
@@ -316,9 +360,20 @@ PagerLoader::traverse(osg::NodeVisitor& nv)
                     _requests.erase( i++ );
                 }
 
-                else if ( !req->isMerging() && (fn - req->getLastFrameSubmitted() > 2) )
+                // Discard requests that are no longer required:
+                else if ( !req->isMerging() && frameDiff > 2 )
                 {
-                    //OE_INFO << LC << req->getName() << "(" << i->second->getUID() << ") died waiting after " << fn-req->getLastFrameSubmitted() << " frames" << std::endl; 
+                    //OE_INFO << LC << req->getName() << "(" << i->second->getUID() << ") died waiting after " << frameDiff << " frames" << std::endl; 
+                    req->setState( Request::IDLE );
+                    if ( REPORT_ACTIVITY )
+                        Registry::instance()->endActivity( req->getName() );
+                    _requests.erase( i++ );
+                }
+
+                // Prevent a request from getting stuck in the merge queue:
+                else if ( req->isMerging() && frameDiff > 1800 )
+                {
+                    //OE_INFO << LC << req->getName() << "(" << i->second->getUID() << ") died waiting " << frameDiff << " frames to merge" << std::endl; 
                     req->setState( Request::IDLE );
                     if ( REPORT_ACTIVITY )
                         Registry::instance()->endActivity( req->getName() );
@@ -455,20 +510,14 @@ namespace osgEarth { namespace Drivers { namespace RexTerrainEngine
             {
                 // parse the tile key and engine ID:
                 std::string requestdef = osgDB::getNameLessExtension(uri);
-                unsigned requestUID, engineUID;
-                sscanf(requestdef.c_str(), "%u.%u", &requestUID, &engineUID);
+                unsigned requestUID;
+                sscanf(requestdef.c_str(), "%u", &requestUID);
 
-                // find the appropriate engine:
-                osg::ref_ptr<RexTerrainEngineNode> engineNode;
-                RexTerrainEngineNode::getEngineByUID( (UID)engineUID, engineNode );
-                if ( engineNode.valid() )
+                osg::ref_ptr<PagerLoader> loader;
+                if (OptionsData<PagerLoader>::lock(dboptions, "osgEarth.PagerLoader", loader))
                 {
-                    PagerLoader* loader = dynamic_cast<PagerLoader*>(engineNode->getLoader());
-                    if ( loader )
-                    {
-                        Loader::Request* req = loader->invokeAndRelease( requestUID );
-                        return new RequestResultNode(req);
-                    }
+                    Loader::Request* req = loader->invokeAndRelease( requestUID );
+                    return new RequestResultNode(req);
                 }
                 return ReadResult::FILE_NOT_FOUND;
             }
diff --git a/src/osgEarthDrivers/engine_rex/MPTexture b/src/osgEarthDrivers/engine_rex/MPTexture
deleted file mode 100644
index 111b361..0000000
--- a/src/osgEarthDrivers/engine_rex/MPTexture
+++ /dev/null
@@ -1,106 +0,0 @@
-/* -*-c++-*- */
-/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
-* http://osgearth.org
-*
-* osgEarth is free software; you can redistribute it and/or modify
-* it under the terms of the GNU Lesser General Public License as published by
-* the Free Software Foundation; either version 2 of the License, or
-* (at your option) any later version.
-*
-* This program is distributed in the hope that it will be useful,
-* but WITHOUT ANY WARRANTY; without even the implied warranty of
-* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-* GNU Lesser General Public License for more details.
-*
-* You should have received a copy of the GNU Lesser General Public License
-* along with this program.  If not, see <http://www.gnu.org/licenses/>
-*/
-#ifndef OSGEARTH_REX_MPTEXTURE
-#define OSGEARTH_REX_MPTEXTURE 1
-
-#include "Common"
-#include <osg/Texture2D>
-#include <osgEarth/ImageLayer>
-
-using namespace osgEarth;
-
-namespace osgEarth { namespace Drivers { namespace RexTerrainEngine
-{
-    /**
-     * MPTexture is a multipass texture attribute that holds one or more Texture2D
-     * objects each with associated texture matrix.
-     *
-     * We don't actually store it in the TileNode's stateSet. The reason it is a 
-     * StateAttribute (and subclassed from Texture2D) is so that OSG's GL object
-     * pre-compile visitors will find it and process it.
-     */
-    class MPTexture : public osg::Texture2D
-    {
-    public:
-        /** A single color layer pass. */
-        struct Pass
-        {
-            Pass() : _ownsTexture(false), _order(0) { }
-
-            Pass(const Pass& rhs) {
-                _layer = rhs._layer.get();
-                _order = rhs._order;
-                _texture = rhs._texture.get();
-                _textureMatrix = rhs._textureMatrix;
-                _ownsTexture = false; //rhs._ownsTexture;
-                _parentTexture = rhs._parentTexture.get();
-                _parentTextureMatrix = rhs._parentTextureMatrix;
-            }
-
-            osg::ref_ptr<const ImageLayer> _layer;          // source image layer
-            int                            _order;          // rendering order
-            osg::ref_ptr<osg::Texture>     _texture;        // texture object
-            osg::Matrixf                   _textureMatrix;  // scale bias matrix for inherited textures.
-            bool                           _ownsTexture;    // whether this key/pass owns this texture.
-
-            osg::ref_ptr<osg::Texture>     _parentTexture;
-            osg::Matrixf                   _parentTextureMatrix;
-        };
-
-        typedef std::vector<Pass> Passes;
-
-    public:
-        /** ctor */
-        MPTexture();
-
-        /** Sets or adds an image layer and associated texture object as a pass. */
-        void setLayer(const ImageLayer* layer, osg::Texture* tex, int order);
-
-        /** Passes to be drawn for this object. */
-        const Passes& getPasses() const { return _passes; }
-
-        /** Merge the contents of "rhs" into this object. */
-        void merge(MPTexture* rhs);
-
-        /** Inherit pass information from another MPTexture with scale/bias information. */
-        void inheritState(MPTexture* parent, const osg::Matrixf& scaleBias);
-
-    public: // osg::Texture
-
-        // override an nullify apply; functionality happens manually in TileDrawable
-        void apply(osg::State& state) const { }
-
-        // support GL pre-compilation, etc.
-        void compileGLObjects(osg::State& state) const;
-        void resizeGLObjectBuffers(unsigned int maxSize);
-        void releaseGLObjects(osg::State* state=0) const;
-
-    protected:
-        virtual ~MPTexture() { }
-
-        Passes _passes;
-        
-    private:
-        /** copy ctor - disabled */
-        MPTexture(const MPTexture& rhs) { }
-    };
-
-} } } // namespace osgEarth::Drivers::RexTerrainEngine
-
-#endif // OSGEARTH_REX_MPTEXTURE
diff --git a/src/osgEarthDrivers/engine_rex/MPTexture.cpp b/src/osgEarthDrivers/engine_rex/MPTexture.cpp
deleted file mode 100644
index b378e9d..0000000
--- a/src/osgEarthDrivers/engine_rex/MPTexture.cpp
+++ /dev/null
@@ -1,171 +0,0 @@
-/* -*-c++-*- */
-/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
-* http://osgearth.org
-*
-* osgEarth is free software; you can redistribute it and/or modify
-* it under the terms of the GNU Lesser General Public License as published by
-* the Free Software Foundation; either version 2 of the License, or
-* (at your option) any later version.
-*
-* This program is distributed in the hope that it will be useful,
-* but WITHOUT ANY WARRANTY; without even the implied warranty of
-* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-* GNU Lesser General Public License for more details.
-*
-* You should have received a copy of the GNU Lesser General Public License
-* along with this program.  If not, see <http://www.gnu.org/licenses/>
-*/
-#include "MPTexture"
-
-using namespace osgEarth::Drivers::RexTerrainEngine;
-using namespace osgEarth;
-
-#define LC "[MPTexture] "
-
-MPTexture::MPTexture() :
-osg::Texture2D()
-{
-    //nop
-}
-
-void
-MPTexture::setLayer(const ImageLayer* layer, osg::Texture* tex, int order)
-{
-    Passes::iterator insertionPoint = _passes.end();
-
-    // If the layer already exists as a pass, update it
-    for(Passes::iterator pass = _passes.begin(); pass != _passes.end(); ++pass)
-    {
-        if ( pass->_layer.get() == layer )
-        {
-            pass->_texture = tex;
-            pass->_ownsTexture = true;
-            pass->_textureMatrix = osg::Matrixf::identity();
-            return;
-        }
-        else if ( order < pass->_order )
-        {
-            insertionPoint = pass;
-        }            
-    }
-
-    // Layer didn't already exist; add it (in the proper order)
-    Pass& pass = *_passes.insert( insertionPoint, Pass() );
-    pass._layer   = layer;
-    pass._texture = tex;
-    pass._parentTexture = tex;
-    pass._order   = order;
-    pass._ownsTexture = true;
-}
-
-void
-MPTexture::merge(MPTexture* rhs)
-{
-    if ( rhs )
-    {
-        for(Passes::const_iterator pass = rhs->getPasses().begin(); pass != rhs->getPasses().end(); ++pass)
-        {
-            setLayer( pass->_layer.get(), pass->_texture.get(), pass->_order );
-        }
-    }
-}
-
-void
-MPTexture::inheritState(MPTexture* parent, const osg::Matrixf& scaleBias)
-{
-    if ( parent )
-    {
-        // First time around, copy the parent's data in whole, and then scale/bias
-        // the texture matrix to the appropriate quadrant
-        if ( _passes.empty() )
-        {
-            // copy it:
-            _passes = parent->getPasses();
-
-            for(Passes::iterator pass = _passes.begin(); pass != _passes.end(); ++pass)
-            {
-                // scale and bias the texture matrix; then the new parent texture just
-                // points to the main texture until the main texture gets replaced later.
-                pass->_textureMatrix.preMult( scaleBias );
-                pass->_parentTexture = pass->_texture.get();
-                pass->_parentTextureMatrix = pass->_textureMatrix;
-                pass->_ownsTexture = false;
-            }
-        }
-
-        // If this object already has data, recalculate the sub-matrixing on all
-        // existing textures and parent-textures:
-        else
-        {
-            for(Passes::const_iterator parentPass = parent->getPasses().begin(); parentPass != parent->getPasses().end(); ++parentPass)
-            {
-                for(Passes::iterator pass = _passes.begin(); pass != _passes.end(); ++pass)
-                {
-                    if ( parentPass->_layer.get() == pass->_layer.get())
-                    {
-                        // If the texture matrix is non-identity, that means we are inheriting from another tile.
-                        // In this case, re-copy from the parent (in case it changed) and recalculate the
-                        // texture matrix.
-                        if ( !pass->_textureMatrix.isIdentity() )
-                        {
-                            pass->_texture = parentPass->_texture.get();
-                            pass->_textureMatrix = parentPass->_textureMatrix;
-                            pass->_textureMatrix.preMult( scaleBias );
-                        }
-                        
-                        // Update the parent texture always.
-                        if ( parentPass->_texture.valid() )
-                        {
-                            pass->_parentTexture = parentPass->_texture.get();
-                            pass->_parentTextureMatrix = parentPass->_textureMatrix;
-                            pass->_parentTextureMatrix.preMult( scaleBias );
-                        }
-                        else
-                        {
-                            pass->_parentTexture = pass->_texture.get();
-                            pass->_parentTextureMatrix = pass->_textureMatrix;
-                        }
-                    }
-                }
-            }
-        }
-    }
-}
-
-void 
-MPTexture::compileGLObjects(osg::State& state) const
-{
-    for(Passes::const_iterator pass = _passes.begin(); pass != _passes.end(); ++pass)
-    {
-        if ( pass->_texture.valid() ) //&& pass->_ownsTexture )
-        {
-            pass->_texture->apply(state);
-        }
-    }
-}
-
-void
-MPTexture::resizeGLObjectBuffers(unsigned int maxSize)
-{
-    for(Passes::const_iterator pass = _passes.begin(); pass != _passes.end(); ++pass)
-    {
-        if ( pass->_texture.valid() && pass->_ownsTexture )
-        {
-            pass->_texture->resizeGLObjectBuffers(maxSize);
-        }
-    }
-}
-
-void
-MPTexture::releaseGLObjects(osg::State* state) const
-{
-    for(Passes::const_iterator pass = _passes.begin(); pass != _passes.end(); ++pass)
-    {
-        // check the refcount since textures are shared across MPTexture instances.
-        if ( pass->_texture.valid() && pass->_texture->referenceCount() == 1 )
-        {
-            pass->_texture->releaseGLObjects(state);
-        }
-    }
-}
diff --git a/src/osgEarthDrivers/engine_rex/MaskGenerator b/src/osgEarthDrivers/engine_rex/MaskGenerator
index fad96a8..1c6925d 100644
--- a/src/osgEarthDrivers/engine_rex/MaskGenerator
+++ b/src/osgEarthDrivers/engine_rex/MaskGenerator
@@ -56,7 +56,7 @@ namespace osgEarth { namespace Drivers { namespace RexTerrainEngine
     class MaskGenerator : public osg::Referenced
     {
     public:
-        MaskGenerator(const TileKey& key, unsigned tileSize, const MapFrame& mapFrame);
+        MaskGenerator(const TileKey& key, unsigned tileSize, const Map* map);
 
 
         bool hasMasks() const
@@ -83,12 +83,13 @@ namespace osgEarth { namespace Drivers { namespace RexTerrainEngine
             return false;
         }
 
+        //! Gets the LL and UR corners of the "patch rectangle" in NDC space
         void getMinMax(osg::Vec3d& min, osg::Vec3d& max);
 
         osg::DrawElementsUInt* createMaskPrimitives(const MapInfo& mapInfo, osg::Vec3Array* verts, osg::Vec3Array* texCoords,  osg::Vec3Array* normals, osg::Vec3Array* neighbors);
 
     protected:
-        void setupMaskRecord(const MapFrame& mapFrame, osg::Vec3dArray* boundary);
+        void setupMaskRecord(const MapInfo& mapInfo, osg::Vec3dArray* boundary);
 
     protected:
         const TileKey    _key;
diff --git a/src/osgEarthDrivers/engine_rex/MaskGenerator.cpp b/src/osgEarthDrivers/engine_rex/MaskGenerator.cpp
index 51ff8c5..b6db165 100644
--- a/src/osgEarthDrivers/engine_rex/MaskGenerator.cpp
+++ b/src/osgEarthDrivers/engine_rex/MaskGenerator.cpp
@@ -18,6 +18,7 @@
 */
 #include "MaskGenerator"
 
+#include <osgEarth/MaskLayer>
 #include <osgEarth/Locators>
 #include <osgEarthSymbology/Geometry>
 
@@ -33,10 +34,12 @@ using namespace osgEarth::Symbology;
 #define MATCH_TOLERANCE 0.000001
 
 
-MaskGenerator::MaskGenerator(const TileKey& key, unsigned tileSize, const MapFrame& mapFrame) :
+MaskGenerator::MaskGenerator(const TileKey& key, unsigned tileSize, const Map* map) :
 _key( key ), _tileSize(tileSize)
 {
-    const osgEarth::MaskLayerVector maskLayers = mapFrame.terrainMaskLayers();
+    MapFrame frame(map);
+    MaskLayerVector maskLayers;
+    frame.getLayers(maskLayers);
     for(MaskLayerVector::const_iterator it = maskLayers.begin();
         it != maskLayers.end(); 
         ++it)
@@ -44,15 +47,15 @@ _key( key ), _tileSize(tileSize)
         MaskLayer* layer = it->get();
         if ( layer->getMinLevel() <= key.getLevelOfDetail() )
         {
-            setupMaskRecord( mapFrame, layer->getOrCreateMaskBoundary( 1.0, key.getExtent().getSRS(), (ProgressCallback*)0L ) );
+            setupMaskRecord(frame.getMapInfo(), layer->getOrCreateMaskBoundary( 1.0, key.getExtent().getSRS(), (ProgressCallback*)0L ) );
         }
     }
 }
 
 void
-MaskGenerator::setupMaskRecord(const MapFrame& mapFrame, osg::Vec3dArray* boundary)
+MaskGenerator::setupMaskRecord(const MapInfo& mapInfo, osg::Vec3dArray* boundary)
 {
-    osg::ref_ptr<osgEarth::GeoLocator> geoLocator = GeoLocator::createForKey(_key, mapFrame.getMapInfo());
+    osg::ref_ptr<osgEarth::GeoLocator> geoLocator = GeoLocator::createForKey(_key, mapInfo);
     if (geoLocator->getCoordinateSystemType() == GeoLocator::GEOCENTRIC)
         geoLocator = geoLocator->getGeographicFromGeocentric();
 
@@ -176,31 +179,32 @@ MaskGenerator::createMaskPrimitives(const MapInfo& mapInfo, osg::Vec3Array* vert
         int num_i = max_i - min_i + 1;
         int num_j = max_j - min_j + 1;
 
-        osg::ref_ptr<Polygon> maskSkirtPoly = new Polygon();
-        maskSkirtPoly->resize(num_i * 2 + num_j * 2 - 4);
+        // The "patch polygon" is the region that stitches the normal tile geometry to the mask boundary.
+        osg::ref_ptr<Polygon> patchPoly = new Polygon();
+        patchPoly->resize(num_i * 2 + num_j * 2 - 4);
 
         for (int i = 0; i < num_i; i++)
         {
             {
                 osg::Vec3d ndc( ((double)(i + min_i))/(double)(_tileSize-1), ((double)min_j)/(double)(_tileSize-1), 0.0);
-                (*maskSkirtPoly)[i] = ndc;
+                (*patchPoly)[i] = ndc;
             }
 
             {
                 osg::Vec3d ndc( ((double)(i + min_i))/(double)(_tileSize-1), ((double)max_j)/(double)(_tileSize-1), 0.0);
-                (*maskSkirtPoly)[i + (2 * num_i + num_j - 3) - 2 * i] = ndc;
+                (*patchPoly)[i + (2 * num_i + num_j - 3) - 2 * i] = ndc;
             }
         }
         for (int j = 0; j < num_j - 2; j++)
         {
             {
                 osg::Vec3d ndc( ((double)max_i)/(double)(_tileSize-1), ((double)(min_j + j + 1))/(double)(_tileSize-1), 0.0);
-                (*maskSkirtPoly)[j + num_i] = ndc;
+                (*patchPoly)[j + num_i] = ndc;
             }
 
             {
                 osg::Vec3d ndc( ((double)min_i)/(double)(_tileSize-1), ((double)(min_j + j + 1))/(double)(_tileSize-1), 0.0);
-                (*maskSkirtPoly)[j + (2 * num_i + 2 * num_j - 5) - 2 * j] = ndc;
+                (*patchPoly)[j + (2 * num_i + 2 * num_j - 5) - 2 * j] = ndc;
             }
         }
 
@@ -238,7 +242,7 @@ MaskGenerator::createMaskPrimitives(const MapInfo& mapInfo, osg::Vec3Array* vert
 
             //Crop the mask to the stitching poly (for case where mask crosses tile edge)
             osg::ref_ptr<Geometry> maskCrop;
-            maskPoly->crop(maskSkirtPoly.get(), maskCrop);
+            maskPoly->crop(patchPoly.get(), maskCrop);
 
             GeometryIterator i( maskCrop.get(), false );
             while( i.hasMore() )
@@ -261,8 +265,8 @@ MaskGenerator::createMaskPrimitives(const MapInfo& mapInfo, osg::Vec3Array* vert
             {
                 int zSet = 0;
 
-                //Look for verts that belong to the original mask skirt polygon
-                for (Polygon::iterator mit = maskSkirtPoly->begin(); mit != maskSkirtPoly->end(); ++mit)
+                //Look for verts that belong to the original mask patch polygon
+                for (Polygon::iterator mit = patchPoly->begin(); mit != patchPoly->end(); ++mit)
                 {
                     if (osg::absolute((*mit).x() - (*it).x()) < MATCH_TOLERANCE && osg::absolute((*mit).y() - (*it).y()) < MATCH_TOLERANCE)
                     {
@@ -477,6 +481,11 @@ MaskGenerator::createMaskPrimitives(const MapInfo& mapInfo, osg::Vec3Array* vert
 void
 MaskGenerator::getMinMax(osg::Vec3d& min, osg::Vec3d& max)
 {
+#if 1
+    min = _ndcMin;
+    max = _ndcMax;
+
+#else
     if (_maskRecords.size() > 0)
     {
         min.x() = _maskRecords[0]._ndcMin.x();
@@ -498,12 +507,13 @@ MaskGenerator::getMinMax(osg::Vec3d& min, osg::Vec3d& max)
             if (it->_ndcMax.z() > max.z()) max.z() = it->_ndcMax.z();
         }
     }
+#endif
 }
 
 float
 MaskGenerator::getMarker(float nx, float ny) const
 {
-    float marker = 1.0f; // 1.0 == does not contain
+    float marker = MASK_MARKER_NORMAL;
 
     if (_maskRecords.size() > 0)
     {
diff --git a/src/osgEarthDrivers/engine_rex/RenderBindings b/src/osgEarthDrivers/engine_rex/RenderBindings
index 6b13d48..22b9054 100644
--- a/src/osgEarthDrivers/engine_rex/RenderBindings
+++ b/src/osgEarthDrivers/engine_rex/RenderBindings
@@ -22,20 +22,28 @@
 #include "Common"
 #include <osgEarth/Common>
 #include <osgEarth/optional>
+#include <osgEarth/Containers>
+#include <osg/Texture>
+#include <osg/Matrix>
 #include <vector>
+#include <map>
 
 namespace osgEarth { namespace Drivers { namespace RexTerrainEngine
 {
+    /**
+     * Defines the usage information for a single texture sampler.
+     */
     class SamplerBinding
     {
     public:
         enum Usage
         {
-            COLOR,
-            ELEVATION,
-            NORMAL,
-            MATERIAL,
-            COLOR_PARENT
+            COLOR         = 0,
+            COLOR_PARENT  = 1,
+            ELEVATION     = 2,
+            NORMAL        = 3,
+            COVERAGE      = 4,
+            SHARED        = 5   // non-core shared layers start at this index
         };
 
     public:
@@ -62,6 +70,7 @@ namespace osgEarth { namespace Drivers { namespace RexTerrainEngine
         const std::string& matrixName() const { return _matrixName; }
 
     public:
+        /** True if this binding is bound to a teture image unit and therefore active. */
         bool isActive() const { return _unit >= 0; }
 
     private:
@@ -70,27 +79,15 @@ namespace osgEarth { namespace Drivers { namespace RexTerrainEngine
         int             _unit;
         std::string     _samplerName;
         std::string     _matrixName;
-
-    public:
-        static const SamplerBinding* findUsage(const std::vector<SamplerBinding>& v, Usage usage) {
-            for(int i=0; i<v.size(); ++i) {
-                if ( v[i].usage().isSetTo(usage) )
-                    return &v[i];
-            }
-            return 0L;
-        }
-        
-        static const SamplerBinding* findUID(const std::vector<SamplerBinding>& v, UID uid) {
-            for(int i=0; i<v.size(); ++i) {
-                if ( v[i].sourceUID().isSetTo(uid) )
-                    return &v[i];
-            }
-            return 0L;
-        }
     };
 
-    typedef std::vector<SamplerBinding> RenderBindings;
-
+    /**
+     * Array of render bindings. This array is always indexed by the 
+     * SamplerBinding::USAGE itself. So for example, RenderBindings[0] is always
+     * the COLOR usage, [2] is the ELEVATION usage, etc. Shared layer bindings
+     * (i.e., custom samplers) start at index SHARED and go up from there.
+     */
+    typedef AutoArray<SamplerBinding> RenderBindings;
 
 } } } // namespace osgEarth::Drivers::RexTerrainEngine
 
diff --git a/src/osgEarthDrivers/engine_rex/RexEngine.Morphing.vert.glsl b/src/osgEarthDrivers/engine_rex/RexEngine.Morphing.vert.glsl
index a2f5b77..272ca73 100644
--- a/src/osgEarthDrivers/engine_rex/RexEngine.Morphing.vert.glsl
+++ b/src/osgEarthDrivers/engine_rex/RexEngine.Morphing.vert.glsl
@@ -1,10 +1,13 @@
 #version $GLSL_VERSION_STR
+$GLSL_DEFAULT_PRECISION_FLOAT
 
 #pragma vp_name       REX Engine - Morphing
 #pragma vp_entryPoint oe_rexEngine_morph
 #pragma vp_location   vertex_model
 #pragma vp_order      0.5
-#pragma vp_define     OE_REX_VERTEX_MORPHING
+
+#pragma import_defines(OE_TERRAIN_MORPH_GEOMETRY, OE_TERRAIN_RENDER_ELEVATION)
+
 
 // stage
 vec3 vp_Normal; // up vector
@@ -14,11 +17,8 @@ vec4 oe_layer_tilec;
 
 out float oe_rex_morphFactor;
 
-uniform sampler2D oe_tile_elevationTex;
-uniform mat4      oe_tile_elevationTexMatrix;
-uniform vec2	  oe_tile_morph;
-uniform float     oe_tile_size;
-uniform vec4	  oe_tile_key;
+uniform vec2  oe_tile_morph;
+uniform float oe_tile_size;
 
 // SDK functions:
 float oe_terrain_getElevation(in vec2 uv);
@@ -26,7 +26,7 @@ float oe_terrain_getElevation(in vec2 uv);
 // Vertex Markers:
 #define MASK_MARKER_DISCARD  0.0
 #define MASK_MARKER_NORMAL   1.0
-#define MASK_MARKER_SKIRT    2.0
+#define MASK_MARKER_PATCH    2.0
 #define MASK_MARKER_BOUNDARY 3.0
 
 
@@ -52,10 +52,10 @@ float oe_rex_ComputeMorphFactor(in vec4 position, in vec3 up)
     // assume with no morphing)
 	vec4 wouldBePosition = position;
 
-	#ifdef OE_REX_VERTEX_MORPHING
+#ifdef OE_TERRAIN_RENDER_ELEVATION
         float elev = oe_terrain_getElevation( oe_layer_tilec.st );
 		wouldBePosition.xyz += up*elev;
-	#endif
+#endif
 
     vec4 wouldBePositionView = gl_ModelViewMatrix * wouldBePosition;
     
@@ -74,7 +74,7 @@ void oe_rexEngine_morph(inout vec4 vertexModel)
     {
         oe_rex_morphFactor = oe_rex_ComputeMorphFactor(vertexModel, vp_Normal);    
 
-#ifdef OE_REX_VERTEX_MORPHING
+#ifdef OE_TERRAIN_MORPH_GEOMETRY
         vec3 neighborVertexModel = gl_MultiTexCoord1.xyz;
         oe_rex_MorphVertex(vertexModel.xyz, oe_layer_tilec.st, neighborVertexModel.xyz);
 #endif
@@ -84,4 +84,3 @@ void oe_rexEngine_morph(inout vec4 vertexModel)
         oe_rex_morphFactor = 0.0;
     }
 }
-
diff --git a/src/osgEarthDrivers/engine_rex/RexEngine.NormalMap.frag.glsl b/src/osgEarthDrivers/engine_rex/RexEngine.NormalMap.frag.glsl
index 70dadfe..4bd800b 100644
--- a/src/osgEarthDrivers/engine_rex/RexEngine.NormalMap.frag.glsl
+++ b/src/osgEarthDrivers/engine_rex/RexEngine.NormalMap.frag.glsl
@@ -1,9 +1,12 @@
 #version $GLSL_VERSION_STR
+$GLSL_DEFAULT_PRECISION_FLOAT
 
 #pragma vp_entryPoint oe_normalMapFragment
 #pragma vp_location   fragment_coloring
 #pragma vp_order      0.2
 
+#pragma import_defines(OE_TERRAIN_RENDER_NORMAL_MAP, OE_DEBUG_NORMALS)
+
 // import terrain SDK
 vec4 oe_terrain_getNormalAndCurvature(in vec2);
 
@@ -16,22 +19,25 @@ in vec3 oe_normalMapBinormal;
 
 void oe_normalMapFragment(inout vec4 color)
 {
+#ifndef OE_TERRAIN_RENDER_NORMAL_MAP
+    return;
+#endif
+
     vec4 encodedNormal = oe_terrain_getNormalAndCurvature(oe_normalMapCoords);
-    //vec4 encodedNormal = texture2D(oe_tile_normalTex, oe_normalMapCoords);
     vec3 normal = normalize(encodedNormal.xyz*2.0-1.0);
 
     vec3 tangent = normalize(cross(oe_normalMapBinormal, oe_UpVectorView));
     vp_Normal = normalize( mat3(tangent, oe_normalMapBinormal, oe_UpVectorView) * normal );
 
-    // visualize curvature gradient:
-    //color.rgb = vec3(0,0,0);
-    //color.r = (encodedNormal.a+1.0)/2.0;
-    //color.b = 1.0-color.r;
-
     // visualize curvature quantized:
-    //if(encodedNormal.a >= 0.4) color.r = 1.0;
-    //if(encodedNormal.a <= -0.4) color.b = 1.0;
+    //color.rgba = vec4(0.0,0,1);
+    //float curvature = 2.0*encodedNormal.w - 1.0;
+    //if (curvature > 0.0) color.r = curvature;
+    //if (curvature < 0.0) color.b = -curvature;
+    //color.a = 1.0;
     
+#ifdef OE_DEBUG_NORMALS
     // visualize normals:
-    //color.rgb = encodedNormal.xyz;
+    color.rgb = encodedNormal.xyz;
+#endif
 }
diff --git a/src/osgEarthDrivers/engine_rex/RexEngine.NormalMap.vert.glsl b/src/osgEarthDrivers/engine_rex/RexEngine.NormalMap.vert.glsl
index 95552c3..54fc492 100644
--- a/src/osgEarthDrivers/engine_rex/RexEngine.NormalMap.vert.glsl
+++ b/src/osgEarthDrivers/engine_rex/RexEngine.NormalMap.vert.glsl
@@ -1,10 +1,14 @@
 #version $GLSL_VERSION_STR
+$GLSL_DEFAULT_PRECISION_FLOAT
 
 #pragma vp_entryPoint oe_normalMapVertex
 #pragma vp_location   vertex_view
 #pragma vp_order      0.5
 
+#pragma import_defines(OE_TERRAIN_RENDER_NORMAL_MAP)
+
 uniform mat4 oe_tile_normalTexMatrix;
+uniform vec2 oe_tile_elevTexelCoeff;
 
 // stage globals
 vec4 oe_layer_tilec;
@@ -14,8 +18,17 @@ out vec3 oe_normalMapBinormal;
 
 void oe_normalMapVertex(inout vec4 unused)
 {
+#ifndef OE_TERRAIN_RENDER_NORMAL_MAP
+    return;
+#endif
+
     // calculate the sampling coordinates for the normal texture
-    oe_normalMapCoords = (oe_tile_normalTexMatrix * oe_layer_tilec).st;
+//    oe_normalMapCoords = (oe_tile_normalTexMatrix * oe_layer_tilec).st;
+    
+    oe_normalMapCoords = oe_layer_tilec.st
+        * oe_tile_elevTexelCoeff.x * oe_tile_normalTexMatrix[0][0]
+        + oe_tile_elevTexelCoeff.x * oe_tile_normalTexMatrix[3].st
+        + oe_tile_elevTexelCoeff.y;
 
     // send the bi-normal to the fragment shader
     oe_normalMapBinormal = gl_NormalMatrix * vec3(0,1,0);
diff --git a/src/osgEarthDrivers/engine_rex/RexEngine.SDK.vert.glsl b/src/osgEarthDrivers/engine_rex/RexEngine.SDK.vert.glsl
index 751c90e..b969706 100644
--- a/src/osgEarthDrivers/engine_rex/RexEngine.SDK.vert.glsl
+++ b/src/osgEarthDrivers/engine_rex/RexEngine.SDK.vert.glsl
@@ -1,4 +1,6 @@
-#version 330
+#version $GLSL_VERSION_STR
+$GLSL_DEFAULT_PRECISION_FLOAT
+
 #pragma vp_name Rex Terrain SDK
 
 /**
@@ -23,6 +25,20 @@ vec4 oe_layer_tilec;
 /**
  * Sample the elevation data at a UV tile coordinate.
  */
+float oe_terrain_getElevationUnscaled(in vec2 uv)
+{
+    // Texel-level scale and bias allow us to sample the elevation texture
+    // on texel center instead of edge.
+    vec2 elevc = uv
+        * oe_tile_elevTexelCoeff.x     // scale
+        + oe_tile_elevTexelCoeff.y;
+
+    return texture(oe_tile_elevationTex, elevc).r;
+}
+
+/**
+ * Sample the elevation data at a UV tile coordinate.
+ */
 float oe_terrain_getElevation(in vec2 uv)
 {
     // Texel-level scale and bias allow us to sample the elevation texture
@@ -30,7 +46,7 @@ float oe_terrain_getElevation(in vec2 uv)
     vec2 elevc = uv
         * oe_tile_elevTexelCoeff.x * oe_tile_elevationTexMatrix[0][0]     // scale
         + oe_tile_elevTexelCoeff.x * oe_tile_elevationTexMatrix[3].st     // bias
-        + oe_tile_elevTexelCoeff.y;                                      
+        + oe_tile_elevTexelCoeff.y;
 
     return texture(oe_tile_elevationTex, elevc).r;
 }
@@ -53,7 +69,11 @@ vec4 oe_terrain_getNormalAndCurvature(in vec2 uv_scaledBiased)
 
 vec4 oe_terrain_getNormalAndCurvature()
 {
-    vec2 uv_scaledBiased = oe_layer_tilec.st * oe_tile_normalTexMatrix[0][0] + oe_tile_normalTexMatrix[3].st;
+    vec2 uv_scaledBiased = oe_layer_tilec.st
+        * oe_tile_elevTexelCoeff.x * oe_tile_normalTexMatrix[0][0]
+        + oe_tile_elevTexelCoeff.x * oe_tile_normalTexMatrix[3].st
+        + oe_tile_elevTexelCoeff.y;
+
     return texture(oe_tile_normalTex, uv_scaledBiased);
 }
 
diff --git a/src/osgEarthDrivers/engine_rex/RexEngine.elevation.glsl b/src/osgEarthDrivers/engine_rex/RexEngine.elevation.glsl
new file mode 100644
index 0000000..4d8d0e1
--- /dev/null
+++ b/src/osgEarthDrivers/engine_rex/RexEngine.elevation.glsl
@@ -0,0 +1,33 @@
+#version $GLSL_VERSION_STR
+
+#pragma vp_name       REX Engine - Elevation
+#pragma vp_entryPoint oe_rexEngine_elevation
+#pragma vp_location   vertex_model
+#pragma vp_order      0.7
+
+#pragma import_defines(OE_TERRAIN_RENDER_ELEVATION)
+
+// Vertex Markers:
+#define MASK_MARKER_DISCARD  0.0
+#define MASK_MARKER_NORMAL   1.0
+#define MASK_MARKER_SKIRT    2.0
+#define MASK_MARKER_BOUNDARY 3.0
+
+// stage
+vec3 vp_Normal; // up vector
+vec4 oe_layer_texc;
+vec4 oe_layer_tilec;
+
+// SDK functions:
+float oe_terrain_getElevation(in vec2 uv);
+
+void oe_rexEngine_elevation(inout vec4 vertexModel)
+{    
+#ifdef OE_TERRAIN_RENDER_ELEVATION
+    float elev = 
+        oe_layer_tilec.z == MASK_MARKER_BOUNDARY || oe_layer_tilec.z == MASK_MARKER_DISCARD ? 0.0f
+        : oe_terrain_getElevation( oe_layer_tilec.st );
+
+    vertexModel.xyz += normalize(vp_Normal) * elev;
+#endif
+}
diff --git a/src/osgEarthDrivers/engine_rex/RexEngine.frag.glsl b/src/osgEarthDrivers/engine_rex/RexEngine.frag.glsl
index e6071c8..d555777 100644
--- a/src/osgEarthDrivers/engine_rex/RexEngine.frag.glsl
+++ b/src/osgEarthDrivers/engine_rex/RexEngine.frag.glsl
@@ -1,19 +1,19 @@
-#version 330
+#version $GLSL_VERSION_STR
+$GLSL_DEFAULT_PRECISION_FLOAT
 
 #pragma vp_name       REX Engine - Fragment
 #pragma vp_entryPoint oe_rexEngine_frag
 #pragma vp_location   fragment_coloring
 #pragma vp_order      0.5
-#pragma vp_define     OE_REX_GL_BLENDING
-#pragma vp_define     OE_REX_MORPH_IMAGERY
 
-uniform bool      oe_isPickCamera;
+#pragma import_defines(OE_TERRAIN_RENDER_IMAGERY, OE_TERRAIN_MORPH_IMAGERY, OE_TERRAIN_BLEND_IMAGERY, OE_TERRAIN_CAST_SHADOWS, OE_IS_PICK_CAMERA, OE_IS_SHADOW_CAMERA)
+
 uniform sampler2D oe_layer_tex;
 uniform int       oe_layer_uid;
 uniform int       oe_layer_order;
 uniform float     oe_layer_opacity;
 
-#ifdef OE_REX_MORPH_IMAGERY
+#ifdef OE_TERRAIN_MORPH_IMAGERY
 uniform sampler2D oe_layer_texParent;
 uniform float oe_layer_texParentExists;
 in vec4 oe_layer_texcParent;
@@ -21,16 +21,29 @@ in float oe_rex_morphFactor;
 #endif
 
 in vec4 oe_layer_texc;
+in vec4 oe_layer_tilec;
 
 in float oe_layer_rangeOpacity;
 
 void oe_rexEngine_frag(inout vec4 color)
 {
-    float applyImagery = oe_layer_uid >= 0 ? 1.0 : 0.0;
-    
+#if defined(OE_IS_SHADOW_CAMERA) && !defined(OE_TERRAIN_CAST_SHADOWS)
+    discard;
+    return;
+#endif
+
+#ifdef OE_IS_PICK_CAMERA
+    color = vec4(0);
+#else
+
+#ifndef OE_TERRAIN_RENDER_IMAGERY
+    return;
+#endif
+
+    float isImageLayer = oe_layer_uid >= 0 ? 1.0 : 0.0;
 	vec4 texelSelf = texture(oe_layer_tex, oe_layer_texc.st);
 
-#ifdef OE_REX_MORPH_IMAGERY
+#ifdef OE_TERRAIN_MORPH_IMAGERY
 
     // sample the parent texture:
 	vec4 texelParent = texture(oe_layer_texParent, oe_layer_texcParent.st);
@@ -43,28 +56,26 @@ void oe_rexEngine_frag(inout vec4 color)
 	vec4 texel = mix(texelSelf, texelParent, oe_rex_morphFactor);
 
     // Decide whether to use the texel or the incoming color:
-	texel = mix(color, texel, applyImagery);
+	texel = mix(color, texel, isImageLayer);
 
 #else
 
     // No morphing, just use the incoming color or texture:
-    vec4 texel = mix(color, texelSelf, applyImagery);
+    vec4 texel = mix(color, texelSelf, isImageLayer);
 
 #endif
 
     // Integrate layer opacity into the texture:
-    texel.a = mix(texel.a, texel.a*oe_layer_opacity*oe_layer_rangeOpacity, applyImagery);
-
-    float firstLayer = (applyImagery == 1.0 && oe_layer_order == 0) ? 1.0 : 0.0;
-
-#ifdef OE_REX_GL_BLENDING
+    //texel.a = mix(texel.a, texel.a*oe_layer_rangeOpacity, isImageLayer);
+    texel.a = mix(texel.a, texel.a*oe_layer_opacity*oe_layer_rangeOpacity, isImageLayer);
     
-    // Blend RGB with the incoming color:
-    //color.rgb = texel.rgb*texel.a + color.rgb*(1.0-texel.a);
+#ifdef OE_TERRAIN_BLEND_IMAGERY
+
+    float isFirstImageLayer = (isImageLayer == 1.0 && oe_layer_order == 0) ? 1.0 : 0.0;
 
-    // If this is a first image layer, use the max alpha; otherwise just leave it
-    // to GL blending
-    if (firstLayer == 1.0) {
+    // If this is a first image layer, blend with the incoming terrian color.
+    // Otherwise, apply directly and let GL blending do the rest.
+    if (isFirstImageLayer == 1.0) {
         color.rgb = texel.rgb*texel.a + color.rgb*(1.0-texel.a);
         color.a = max(color.a, texel.a);
     }
@@ -75,10 +86,7 @@ void oe_rexEngine_frag(inout vec4 color)
     // No blending? The output is just the texel value.
     color = texel;
 
-#endif
+#endif // OE_TERRAIN_BLEND_IMAGERY
 
-    // disable primary coloring for pick cameras. Necessary to support picking of
-    // draped geometry.
-    float pick = oe_isPickCamera ? 1.0 : 0.0;
-    color = mix(color, vec4(0), pick);
+#endif // OE_IS_PICK_CAMERA
 }
diff --git a/src/osgEarthDrivers/engine_rex/RexEngine.gs.glsl b/src/osgEarthDrivers/engine_rex/RexEngine.gs.glsl
index c0de328..4e82f38 100644
--- a/src/osgEarthDrivers/engine_rex/RexEngine.gs.glsl
+++ b/src/osgEarthDrivers/engine_rex/RexEngine.gs.glsl
@@ -1,4 +1,5 @@
 #version $GLSL_VERSION_STR
+$GLSL_DEFAULT_PRECISION_FLOAT
 
 
 #if 0 // currently unused - triangle discard implemented on CPU instead
diff --git a/src/osgEarthDrivers/engine_rex/RexEngine.tcs.glsl b/src/osgEarthDrivers/engine_rex/RexEngine.tcs.glsl
index 4ee0135..a9779c1 100644
--- a/src/osgEarthDrivers/engine_rex/RexEngine.tcs.glsl
+++ b/src/osgEarthDrivers/engine_rex/RexEngine.tcs.glsl
@@ -1,3 +1,4 @@
 #version $GLSL_VERSION_STR
+$GLSL_DEFAULT_PRECISION_FLOAT
 
 }
diff --git a/src/osgEarthDrivers/engine_rex/RexEngine.vert.glsl b/src/osgEarthDrivers/engine_rex/RexEngine.vert.glsl
index 1ad78ab..d9feca2 100644
--- a/src/osgEarthDrivers/engine_rex/RexEngine.vert.glsl
+++ b/src/osgEarthDrivers/engine_rex/RexEngine.vert.glsl
@@ -1,4 +1,5 @@
 #version $GLSL_VERSION_STR
+$GLSL_DEFAULT_PRECISION_FLOAT
 
 #pragma vp_name       REX Engine - Vertex
 #pragma vp_entryPoint oe_rexEngine_vert
@@ -28,7 +29,7 @@ void oe_rexEngine_vert(inout vec4 vertexModel)
 	
     // "up" vector at this vertex in view space, which we will later
     // need in order to elevate the terrain
-	oe_UpVectorView = normalize(gl_NormalMatrix*vp_Normal);
+    oe_UpVectorView = normalize(gl_NormalMatrix*vp_Normal);
 
     // initialize:
     oe_rex_morphFactor = 0.0;
diff --git a/src/osgEarthDrivers/engine_rex/RexEngine.vert.view.glsl b/src/osgEarthDrivers/engine_rex/RexEngine.vert.view.glsl
index dff05f0..07f48fd 100644
--- a/src/osgEarthDrivers/engine_rex/RexEngine.vert.view.glsl
+++ b/src/osgEarthDrivers/engine_rex/RexEngine.vert.view.glsl
@@ -1,4 +1,5 @@
 #version $GLSL_VERSION_STR
+$GLSL_DEFAULT_PRECISION_FLOAT
 
 #pragma vp_name       REX Engine - Vertex/View
 #pragma vp_entryPoint oe_rex_elevateVertexAndSetTexCoords
@@ -7,9 +8,9 @@
 
 // Stage globals
 vec4 oe_layer_tilec;
+vec3 oe_UpVectorView;
 vec4 oe_layer_texc;
 vec4 oe_layer_texcParent;
-vec3 oe_UpVectorView;
 
 vec4 vp_Color;
 
@@ -20,36 +21,16 @@ uniform float oe_layer_minRange;
 uniform float oe_layer_maxRange;
 uniform float oe_layer_attenuationRange;
 
-// SDK functions:
-float oe_terrain_getElevation(in vec2 uv);
-
 out float oe_layer_rangeOpacity;
 
-// Vertex Markers:
-#define MASK_MARKER_DISCARD  0.0
-#define MASK_MARKER_NORMAL   1.0
-#define MASK_MARKER_SKIRT    2.0
-#define MASK_MARKER_BOUNDARY 3.0
+// SDK functions:
+float oe_terrain_getElevation(in vec2 uv);
 
 void oe_rex_elevateVertexAndSetTexCoords(inout vec4 vertexView)
 {
-    float elev = 
-        oe_layer_tilec.z == MASK_MARKER_BOUNDARY || oe_layer_tilec.z == MASK_MARKER_DISCARD ? 0.0f
-        : oe_terrain_getElevation( oe_layer_tilec.st );
-
-    vertexView.xyz += oe_UpVectorView * elev;
-
-#if 0
     // calculate the texture coordinates:
     oe_layer_texc       = oe_layer_texMatrix       * oe_layer_tilec;
 	oe_layer_texcParent = oe_layer_texParentMatrix * oe_layer_tilec;
-#else
-    // faster (MAD)
-	oe_layer_texc.xy	   = oe_layer_tilec.xy*oe_layer_texMatrix[0][0] + oe_layer_texMatrix[3].xy;
-    oe_layer_texc.zw       = oe_layer_tilec.zw;
-    oe_layer_texcParent.xy = oe_layer_tilec.xy*oe_layer_texParentMatrix[0][0] + oe_layer_texParentMatrix[3].xy;
-    oe_layer_texcParent.zw = oe_layer_tilec.zw;
-#endif
 
    float range = max(-vertexView.z, 0.0);
 
diff --git a/src/osgEarthDrivers/engine_rex/RexTerrainEngineNode b/src/osgEarthDrivers/engine_rex/RexTerrainEngineNode
index e5cd0fe..b2c4669 100644
--- a/src/osgEarthDrivers/engine_rex/RexTerrainEngineNode
+++ b/src/osgEarthDrivers/engine_rex/RexTerrainEngineNode
@@ -20,12 +20,13 @@
 #define OSGEARTH_DRIVERS_REX_TERRAIN_ENGINE_ENGINE_NODE_H 1
 
 #include <osgEarth/TerrainEngineNode>
-#include <osgEarth/TextureCompositor>
+#include <osgEarth/TerrainResources>
 #include <osgEarth/Map>
 #include <osgEarth/Revisioning>
 #include <osgEarth/ThreadingUtils>
 #include <osgEarth/Containers>
 #include <osgEarth/ResourceReleaser>
+#include <osgEarth/TileRasterizer>
 
 #include "RexTerrainEngineOptions"
 #include "EngineContext"
@@ -35,18 +36,23 @@
 #include "Loader"
 #include "Unloader"
 #include "SelectionInfo"
+#include "SurfaceNode"
+#include "TileDrawable"
+#include "TerrainCuller"
 
 #include <osg/Geode>
 #include <osg/NodeCallback>
 #include <osg/Uniform>
 #include <osg/Timer>
 
+#include <list>
+#include <map>
+#include <vector>
+
 using namespace osgEarth;
 
 namespace osgEarth { namespace Drivers { namespace RexTerrainEngine
 {
-    class SelectionInfo;
-
     class RexTerrainEngineNode : public TerrainEngineNode
     {
     public:
@@ -58,6 +64,12 @@ namespace osgEarth { namespace Drivers { namespace RexTerrainEngine
 
         // for standalone tile creation outside of a terrain
         osg::Node* createTile(const TileKey& key);
+
+        // Create a standalone tile from a tile model (experimental)
+        osg::Node* createTile(
+            const TerrainTileModel* model,
+            int createTileFlags,
+            unsigned referenceLOD);
         
         // when incremental update is enabled, forces regeneration of tiles
         // in the given region.
@@ -71,40 +83,22 @@ namespace osgEarth { namespace Drivers { namespace RexTerrainEngine
 
     public: // internal TerrainEngineNode
 
-        virtual void preInitialize( const Map* map, const TerrainOptions& options );
-        virtual void postInitialize( const Map* map, const TerrainOptions& options );
-        virtual const TerrainOptions& getTerrainOptions() const { return _terrainOptions; }
-        virtual osg::BoundingSphere computeBound() const;
+        void setMap(const Map* map, const TerrainOptions& options);
+        const TerrainOptions& getTerrainOptions() const { return _terrainOptions; }
+        UID getUID() const { return _uid; }
 
     public: // osg::Node
 
         void traverse(osg::NodeVisitor& nv);
 
+        osg::BoundingSphere computeBound() const;
+
     public: // MapCallback adapter functions
 
-        void onMapInfoEstablished( const MapInfo& mapInfo ); // not virtual!
         void onMapModelChanged( const MapModelChange& change ); // not virtual!
 
-        UID getUID() const;
-
-    public: // statics    
-        static void registerEngine( RexTerrainEngineNode* engineNode );
-        static void getEngineByUID( UID uid, osg::ref_ptr<RexTerrainEngineNode>& output );
-
         /** Access to the asychronous data loader */
-        Loader* getLoader() { return _loader.get(); }
-
-    public:
-        class ElevationChangedCallback : public ElevationLayerCallback
-        {
-        public:
-            ElevationChangedCallback( RexTerrainEngineNode* terrain );
-
-           virtual void onVisibleChanged( TerrainLayer* layer );
-
-            RexTerrainEngineNode* _terrain;
-            friend class RexTerrainEngineNode;
-        };
+        Loader* getLoader() const { return _loader.get(); }
 
     protected:
         // override from TerrainEngineNode
@@ -123,47 +117,46 @@ namespace osgEarth { namespace Drivers { namespace RexTerrainEngine
         // Reloads all the tiles in the terrain due to a data model change
         void refresh(bool force =false);
 
-        void addImageLayer( ImageLayer* layer );
+        // Various methods that trigger when the Map model changes.
+        void addLayer(Layer* layer);
         void addElevationLayer( ElevationLayer* layer );
-
+        void addTileLayer( Layer* layer );
         void removeImageLayer( ImageLayer* layerRemoved );
         void removeElevationLayer( ElevationLayer* layerRemoved );
         void toggleElevationLayer( ElevationLayer* layer );
-
-        void moveImageLayer( unsigned int oldIndex, unsigned int newIndex );
-        void moveElevationLayer( unsigned int oldIndex, unsigned int newIndex );
+        void moveElevationLayer( ElevationLayer* layerMoved );
         
+        //! refresh the statesets of the terrain and the imagelayer tile surface
         void updateState(); 
 
+        //! one-time allocation of render units for the terrain
         void setupRenderBindings();
+        
+        //! Adds a Layer to the cachedLayerExtents vector.
+        void cacheLayerExtentInMapSRS(Layer* layer); 
 
-
-
-        /**
-         * Utility function to compute the heightfield sample size required to match the vertices at the highest level of detail in rex
-         * if a tile key was requested at the given level of detail.
-         */
+        //! computes the heightfield sample size required to match the vertices at the highest
+        //! level of detail in rex if a tile key was requested at the given level of detail.
         unsigned int computeSampleSize(unsigned int levelOfDetail);
 
-
     private:
         RexTerrainEngineOptions _terrainOptions;
 
-        UID                _uid;
-        bool               _batchUpdateInProgress;
-        bool               _refreshRequired;
-        bool               _stateUpdateRequired;
+        UID _uid;
+        bool _batchUpdateInProgress;
+        bool _refreshRequired;
+        bool _stateUpdateRequired;
+        MapFrame _mapFrame;
 
-        osg::ref_ptr<ElevationChangedCallback> _elevationCallback;
+        LayerExtentVector _cachedLayerExtents;          // extents of each layer, in the Map's SRS. UID = vector index (for speed)
 
-        MapFrame* _update_mapf; // map frame for the main/update traversal thread
 
         // node registry is shared across all threads.
         osg::ref_ptr<TileNodeRegistry> _liveTiles;      // tiles in the scene graph.
         osg::ref_ptr<ResourceReleaser> _releaser;
-
-        PerThread< osg::ref_ptr<EngineContext> > _perThreadTileGroupFactories;
-        EngineContext* getEngineContext();
+     
+        EngineContext* getEngineContext() const { return _engineContext.get(); }
+        osg::ref_ptr< EngineContext > _engineContext;
 
         osg::Timer _timer;
         unsigned   _tileCount;
@@ -172,19 +165,19 @@ namespace osgEarth { namespace Drivers { namespace RexTerrainEngine
         osg::ref_ptr<GeometryPool> _geometryPool;
         osg::ref_ptr<LoaderGroup>  _loader;
         osg::ref_ptr<UnloaderGroup> _unloader;
+        TileRasterizer* _rasterizer;
         
         osg::ref_ptr<osg::Group> _terrain;
 
-        Threading::Mutex _renderBinMutex;
-        osg::ref_ptr<osgUtil::RenderBin> _surfaceRenderBinPrototype;
+        bool _renderModelUpdateRequired;
 
         RexTerrainEngineNode( const RexTerrainEngineNode& rhs, const osg::CopyOp& op =osg::CopyOp::DEEP_COPY_ALL ) { }
 
         SelectionInfo _selectionInfo;
-        void buildSelectionInfo(void);
-        void destroySelectionInfo(void);
 
-        osg::ref_ptr<osg::StateSet> _surfaceSS;
+        osg::ref_ptr<osg::StateSet> _imageLayerStateSet;
+
+        osg::ref_ptr<ModifyBoundingBoxCallback> _modifyBBoxCallback;
     };
 
 } } } // namespace osgEarth::Drivers::RexTerrainEngine
diff --git a/src/osgEarthDrivers/engine_rex/RexTerrainEngineNode.cpp b/src/osgEarthDrivers/engine_rex/RexTerrainEngineNode.cpp
index b8832ac..14923d9 100644
--- a/src/osgEarthDrivers/engine_rex/RexTerrainEngineNode.cpp
+++ b/src/osgEarthDrivers/engine_rex/RexTerrainEngineNode.cpp
@@ -19,29 +19,24 @@
 #include "RexTerrainEngineNode"
 #include "Shaders"
 #include "SelectionInfo"
+#include "TerrainCuller"
+#include "GeometryPool"
 
-#include <osgEarth/HeightFieldUtils>
 #include <osgEarth/ImageUtils>
 #include <osgEarth/Registry>
 #include <osgEarth/Capabilities>
 #include <osgEarth/VirtualProgram>
-#include <osgEarth/ShaderFactory>
 #include <osgEarth/MapModelChange>
 #include <osgEarth/Progress>
 #include <osgEarth/ShaderLoader>
 #include <osgEarth/Utils>
 #include <osgEarth/ObjectIndex>
-#include <osgEarth/TraversalData>
 
 #include <osg/Version>
 #include <osg/BlendFunc>
 #include <osg/Depth>
-#include <osgUtil/RenderBin>
-
-#if OSG_VERSION_GREATER_OR_EQUAL(3,1,8)
-#   define HAVE_OSG_PATCH_PARAMETER
-#   include <osg/PatchParameter>
-#endif
+#include <osg/CullFace>
+#include <osg/ValueObject>
 
 #include <cstdlib> // for getenv
 
@@ -50,6 +45,8 @@
 using namespace osgEarth::Drivers::RexTerrainEngine;
 using namespace osgEarth;
 
+#define DEFAULT_MAX_LOD 19u
+
 //------------------------------------------------------------------------
 
 namespace
@@ -60,72 +57,88 @@ namespace
         RexTerrainEngineNodeMapCallbackProxy(RexTerrainEngineNode* node) : _node(node) { }
         osg::observer_ptr<RexTerrainEngineNode> _node;
 
-        void onMapInfoEstablished( const MapInfo& mapInfo ) {
-            osg::ref_ptr<RexTerrainEngineNode> node;
-            if ( _node.lock(node) )
-                node->onMapInfoEstablished( mapInfo );
-        }
-
         void onMapModelChanged( const MapModelChange& change ) {
             osg::ref_ptr<RexTerrainEngineNode> node;
             if ( _node.lock(node) )
                 node->onMapModelChanged( change );
         }
     };
-}
+    
 
-//---------------------------------------------------------------------------
+    /**
+     * Run this visitor whenever you remove a layer, so that each
+     * TileNode can update its render model and get rid of passes
+     * that no longer exist.
+     */
+    struct UpdateRenderModels : public osg::NodeVisitor
+    {
+        const MapFrame& _frame;
+        const RenderBindings& _bindings;
+        unsigned _count;
+        bool _reload;
+        std::set<UID> _layersToLoad;
 
-static Threading::ReadWriteMutex s_engineNodeCacheMutex;
-//Caches the engines that have been created
-typedef std::map<UID, osg::observer_ptr<RexTerrainEngineNode> > EngineNodeCache;
+        UpdateRenderModels(const MapFrame& frame, RenderBindings& bindings) : _frame(frame), _bindings(bindings), _count(0u), _reload(false)
+        {
+            setTraversalMode(TRAVERSE_ALL_CHILDREN);
+            setNodeMaskOverride(~0);
+        }
 
-static
-EngineNodeCache& getEngineNodeCache()
-{
-    static EngineNodeCache s_cache;
-    return s_cache;
-}
+        std::set<UID>& layersToLoad()
+        {
+            return _layersToLoad;
+        }
 
-void
-RexTerrainEngineNode::registerEngine(RexTerrainEngineNode* engineNode)
-{
-    Threading::ScopedWriteLock exclusiveLock( s_engineNodeCacheMutex );
-    getEngineNodeCache()[engineNode->_uid] = engineNode;
-    OE_DEBUG << LC << "Registered engine " << engineNode->_uid << std::endl;
-}
+        void setReloadData(bool value)
+        {
+            _reload = value;
+        }
 
-// since this method is called in a database pager thread, we use a ref_ptr output
-// parameter to avoid the engine node being destructed between the time we 
-// return it and the time it's accessed; this could happen if the user removed the
-// MapNode from the scene during paging.
-void
-RexTerrainEngineNode::getEngineByUID( UID uid, osg::ref_ptr<RexTerrainEngineNode>& output )
-{
-    Threading::ScopedReadLock sharedLock( s_engineNodeCacheMutex );
-    EngineNodeCache::const_iterator k = getEngineNodeCache().find( uid );
-    if (k != getEngineNodeCache().end())
-        output = k->second.get();
-}
+        void apply(osg::Node& node)
+        {
+            TileNode* tileNode = dynamic_cast<TileNode*>(&node);
+            if (tileNode)
+            {
+                apply(*tileNode);
+            }
+            traverse(node);
+        }
 
-UID
-RexTerrainEngineNode::getUID() const
-{
-    return _uid;
-}
+        void apply(TileNode& tileNode)
+        {
+            TileRenderModel& model = tileNode.renderModel();
+            for (int p = 0; p < model._passes.size(); ++p)
+            {
+                RenderingPass& pass = model._passes[p];
 
-//------------------------------------------------------------------------
+                // if the map doesn't contain a layer with a matching UID,
+                // it's gone so remove it from the render model.
+                if (!_frame.containsLayer(pass.sourceUID()))
+                {
+                    model._passes.erase(model._passes.begin()+p);
+                    --p;
+                    _count++;
+                }
+            }
 
-RexTerrainEngineNode::ElevationChangedCallback::ElevationChangedCallback( RexTerrainEngineNode* terrain ):
-_terrain( terrain )
-{
-    //nop
-}
+            // For shared samplers we need to refresh the list if one of them
+            // goes inactive (as is the case when removing a shared layer)
+            tileNode.refreshSharedSamplers(_bindings);
+
+            // todo. Might be better to use a Revision here though.
+            if (_reload)
+            {
+                tileNode.setDirty(true);
+            }
+
+            if (!_layersToLoad.empty())
+            {
+                tileNode.newLayers().insert(_layersToLoad.begin(), _layersToLoad.end());
+                tileNode.setDirty(true);
+            }
+        }
+    };
 
-void
-RexTerrainEngineNode::ElevationChangedCallback::onVisibleChanged( TerrainLayer* layer )
-{
-    _terrain->refresh(true); // true => force a dirty
 }
 
 //------------------------------------------------------------------------
@@ -133,13 +146,15 @@ RexTerrainEngineNode::ElevationChangedCallback::onVisibleChanged( TerrainLayer*
 RexTerrainEngineNode::RexTerrainEngineNode() :
 TerrainEngineNode     ( ),
 _terrain              ( 0L ),
-_update_mapf          ( 0L ),
 _tileCount            ( 0 ),
 _tileCreationTime     ( 0.0 ),
 _batchUpdateInProgress( false ),
 _refreshRequired      ( false ),
 _stateUpdateRequired  ( false )
 {
+    // Necessary for pager object data
+    this->setName("osgEarth.RexTerrainEngineNode");
+
     // unique ID for this engine:
     _uid = Registry::instance()->createUID();
 
@@ -147,52 +162,53 @@ _stateUpdateRequired  ( false )
     _requireElevationTextures = true;
 
     // install an elevation callback so we can update elevation data
-    _elevationCallback = new ElevationChangedCallback( this );
+    //_elevationCallback = new ElevationChangedCallback( this );
 
     // static shaders.
     if ( Registry::capabilities().supportsGLSL() )
     {
         osg::StateSet* stateset = getOrCreateStateSet();
+        stateset->setName("RexTerrainEngineNode");
         VirtualProgram* vp = VirtualProgram::getOrCreate(stateset);
+        vp->setName("RexTerrainEngineNode");
+        vp->setIsAbstract(true);    // cannot run by itself, requires additional children
         Shaders package;
         package.load(vp, package.SDK);
     }
 
-    _surfaceSS = new osg::StateSet();
+    // TODO: replace with a "renderer" object that can return statesets
+    // for different layer types, or something.
+    _imageLayerStateSet = new osg::StateSet();
+    _imageLayerStateSet->setName("Surface");
+
+    _terrain = new osg::Group();
+    addChild(_terrain.get());
 }
 
 RexTerrainEngineNode::~RexTerrainEngineNode()
 {
-    if ( _update_mapf )
-    {
-        delete _update_mapf;
-    }
-    destroySelectionInfo();
+    OE_DEBUG << LC << "~RexTerrainEngineNode\n";
 }
 
 void
-RexTerrainEngineNode::preInitialize( const Map* map, const TerrainOptions& options )
+RexTerrainEngineNode::setMap(const Map* map, const TerrainOptions& options)
 {
-    // Force the mercator fast path off, since REX does not support it yet.
-    TerrainOptions myOptions = options;
-    myOptions.enableMercatorFastPath() = false;
+    if (!map) return;
 
-    TerrainEngineNode::preInitialize( map, myOptions );
-}
+    // Invoke the base class first:
+    TerrainEngineNode::setMap(map, options);
 
-void
-RexTerrainEngineNode::postInitialize( const Map* map, const TerrainOptions& options )
-{
     // Force the mercator fast path off, since REX does not support it yet.
     TerrainOptions myOptions = options;
     myOptions.enableMercatorFastPath() = false;
 
-    TerrainEngineNode::postInitialize( map, myOptions );
-
     // Initialize the map frames. We need one for the update thread and one for the
     // cull thread. Someday we can detect whether these are actually the same thread
     // (depends on the viewer's threading mode).
-    _update_mapf = new MapFrame( map, Map::ENTIRE_MODEL );
+    _mapFrame.setMap(map);
+
+    // A callback for overriding bounding boxes for tiles
+    _modifyBBoxCallback = new ModifyBoundingBoxCallback(_mapFrame);
 
     // merge in the custom options:
     _terrainOptions.merge( myOptions );
@@ -209,6 +225,11 @@ RexTerrainEngineNode::postInitialize( const Map* map, const TerrainOptions& opti
         _terrainOptions.morphTerrain() = false;
     }
 
+    if (_terrainOptions.rangeMode() == osg::LOD::PIXEL_SIZE_ON_SCREEN)
+    {
+        OE_INFO << LC << "Range mode = pixel size; pixel tile size = " << _terrainOptions.tilePixelSize().get() << std::endl;
+    }
+
     // if the envvar for tile expiration is set, overide the options setting
     const char* val = ::getenv("OSGEARTH_EXPIRATION_THRESHOLD");
     if ( val )
@@ -222,20 +243,30 @@ RexTerrainEngineNode::postInitialize( const Map* map, const TerrainOptions& opti
     if ( hiresFirst )
     {
         _terrainOptions.highResolutionFirst() = true;
+        OE_INFO << LC << "High Res First option set to true by env var\n";
     }
 
+    // Check for normals debugging.
+    if (::getenv("OSGEARTH_DEBUG_NORMALS"))
+        getOrCreateStateSet()->setDefine("OE_DEBUG_NORMALS");
+    else
+        if (getStateSet()) getStateSet()->removeDefine("OE_DEBUG_NORMALS");
+
     // check for normal map generation (required for lighting).
     if ( _terrainOptions.normalMaps() == true )
     {
         this->_requireNormalTextures = true;
     }
 
+    // ensure we get full coverage at the first LOD.
+    this->_requireFullDataAtFirstLOD = true;
+
     // A shared registry for tile nodes in the scene graph. Enable revision tracking
     // if requested in the options. Revision tracking lets the registry notify all
     // live tiles of the current map revision so they can inrementally update
     // themselves if necessary.
     _liveTiles = new TileNodeRegistry("live");
-    _liveTiles->setMapRevision( _update_mapf->getRevision() );
+    _liveTiles->setMapRevision( _mapFrame.getRevision() );
 
     // A resource releaser that will call releaseGLObjects() on expired objects.
     _releaser = new ResourceReleaser();
@@ -248,7 +279,15 @@ RexTerrainEngineNode::postInitialize( const Map* map, const TerrainOptions& opti
 
     // Make a tile loader
     PagerLoader* loader = new PagerLoader( this );
+    loader->setNumLODs(_terrainOptions.maxLOD().getOrUse(DEFAULT_MAX_LOD));
     loader->setMergesPerFrame( _terrainOptions.mergesPerFrame().get() );
+    for (std::vector<RexTerrainEngineOptions::LODOptions>::const_iterator i = _terrainOptions.lods().begin(); i != _terrainOptions.lods().end(); ++i) {
+        if (i->_lod.isSet()) {
+            loader->setLODPriorityScale(i->_lod.get(), i->_priorityScale.getOrUse(1.0f));
+            loader->setLODPriorityOffset(i->_lod.get(), i->_priorityOffset.getOrUse(0.0f));
+        }
+    }
+
     _loader = loader;
     this->addChild( _loader.get() );
 
@@ -257,14 +296,13 @@ RexTerrainEngineNode::postInitialize( const Map* map, const TerrainOptions& opti
     _unloader->setThreshold( _terrainOptions.expirationThreshold().get() );
     _unloader->setReleaser(_releaser.get());
     this->addChild( _unloader.get() );
-    
-    // handle an already-established map profile:
-    MapInfo mapInfo( map );
-    if ( _update_mapf->getProfile() )
-    {
-        // NOTE: this will initialize the map with the startup layers
-        onMapInfoEstablished( mapInfo );
-    }
+
+    // Tile rasterizer in case we need one
+    _rasterizer = new TileRasterizer();
+    this->addChild( _rasterizer );
+
+    // Initialize the core render bindings.
+    setupRenderBindings();
 
     // install a layer callback for processing further map actions:
     map->addMapCallback( new RexTerrainEngineNodeMapCallbackProxy(this) );
@@ -272,23 +310,48 @@ RexTerrainEngineNode::postInitialize( const Map* map, const TerrainOptions& opti
     // Prime with existing layers:
     _batchUpdateInProgress = true;
 
-    ElevationLayerVector elevationLayers;
-    map->getElevationLayers( elevationLayers );
-    for( ElevationLayerVector::const_iterator i = elevationLayers.begin(); i != elevationLayers.end(); ++i )
-        addElevationLayer( i->get() );
+    LayerVector layers;
+    map->getLayers(layers);
+    for (LayerVector::const_iterator i = layers.begin(); i != layers.end(); ++i)
+        addLayer(i->get());
 
-    ImageLayerVector imageLayers;
-    map->getImageLayers( imageLayers );
-    for( ImageLayerVector::iterator i = imageLayers.begin(); i != imageLayers.end(); ++i )
-        addImageLayer( i->get() );
+    //ElevationLayerVector elevationLayers;
+    //map->getLayers( elevationLayers );
+    //for( ElevationLayerVector::const_iterator i = elevationLayers.begin(); i != elevationLayers.end(); ++i )
+    //    addElevationLayer( i->get() );
+
+    //ImageLayerVector imageLayers;
+    //map->getLayers( imageLayers );
+    //for( ImageLayerVector::iterator i = imageLayers.begin(); i != imageLayers.end(); ++i )
+    //    addTileLayer( i->get() );
 
     _batchUpdateInProgress = false;
+    
+    // Establish a new engine context
+    _engineContext = new EngineContext(
+        getMap(),
+        this, // engine
+        _geometryPool.get(),
+        _loader.get(),
+        _unloader.get(),
+        _rasterizer,
+        _liveTiles.get(),
+        _renderBindings,
+        _terrainOptions,
+        _selectionInfo,
+        _modifyBBoxCallback.get());
 
-    // set up the initial shaders
-    updateState();
+    // Calculate the LOD morphing parameters:
+    unsigned maxLOD = _terrainOptions.maxLOD().getOrUse(DEFAULT_MAX_LOD);
+
+    _selectionInfo.initialize(
+        0u, // always zero, not the terrain options firstLOD
+        std::min( _terrainOptions.maxLOD().get(), maxLOD ),
+        _mapFrame.getMapInfo().getProfile(),        
+        _terrainOptions.minTileRangeFactor().get() );
 
-    // register this instance to the osgDB plugin can find it.
-    registerEngine( this );
+    // set up the initial graph
+    refresh();
 
     // now that we have a map, set up to recompute the bounds
     dirtyBound();
@@ -334,142 +397,129 @@ RexTerrainEngineNode::refresh(bool forceDirty)
     }
 }
 
-void
-RexTerrainEngineNode::onMapInfoEstablished( const MapInfo& mapInfo )
-{
-    dirtyTerrain();
-}
-
 osg::StateSet*
 RexTerrainEngineNode::getSurfaceStateSet()
 {
-    return _surfaceSS.get();
+    return _imageLayerStateSet.get();
 }
 
 void
 RexTerrainEngineNode::setupRenderBindings()
 {
-    _renderBindings.push_back( SamplerBinding() );
-    SamplerBinding& color = _renderBindings.back();
+    // Release any pre-existing bindings:
+    for (unsigned i = 0; i < _renderBindings.size(); ++i)
+    {
+        SamplerBinding& b = _renderBindings[i];
+        if (b.isActive())
+        {
+            getResources()->releaseTextureImageUnit(b.unit());
+        }
+    }
+    _renderBindings.clear();
+
+    // "SHARED" is the start of shared layers, so we always want the bindings
+    // vector to be at least that size.
+    _renderBindings.resize(SamplerBinding::SHARED);
+
+    SamplerBinding& color = _renderBindings[SamplerBinding::COLOR];
     color.usage()       = SamplerBinding::COLOR;
     color.samplerName() = "oe_layer_tex";
     color.matrixName()  = "oe_layer_texMatrix";
-    this->getResources()->reserveTextureImageUnit( color.unit(), "Terrain Color" );
-
-    _renderBindings.push_back( SamplerBinding() );
-    SamplerBinding& elevation = _renderBindings.back();
+    getResources()->reserveTextureImageUnit( color.unit(), "Terrain Color" );
+    
+    SamplerBinding& elevation = _renderBindings[SamplerBinding::ELEVATION];
     elevation.usage()       = SamplerBinding::ELEVATION;
     elevation.samplerName() = "oe_tile_elevationTex";
     elevation.matrixName()  = "oe_tile_elevationTexMatrix";
-    this->getResources()->reserveTextureImageUnit( elevation.unit(), "Terrain Elevation" );
-
-    _renderBindings.push_back( SamplerBinding() );
-    SamplerBinding& normal = _renderBindings.back();
+    if (this->elevationTexturesRequired())
+        getResources()->reserveTextureImageUnit( elevation.unit(), "Terrain Elevation" );
+   
+    SamplerBinding& normal = _renderBindings[SamplerBinding::NORMAL];
     normal.usage()       = SamplerBinding::NORMAL;
     normal.samplerName() = "oe_tile_normalTex";
     normal.matrixName()  = "oe_tile_normalTexMatrix";
-    this->getResources()->reserveTextureImageUnit( normal.unit(), "Terrain Normals" );
-
-    _renderBindings.push_back( SamplerBinding() );
-    SamplerBinding& colorParent = _renderBindings.back();
+    if (this->normalTexturesRequired())
+        getResources()->reserveTextureImageUnit( normal.unit(), "Terrain Normals" );
+    
+    SamplerBinding& colorParent = _renderBindings[SamplerBinding::COLOR_PARENT];
     colorParent.usage()       = SamplerBinding::COLOR_PARENT;
     colorParent.samplerName() = "oe_layer_texParent";
     colorParent.matrixName()  = "oe_layer_texParentMatrix";
-    this->getResources()->reserveTextureImageUnit( colorParent.unit(), "Terrain Color (Parent)" );
-}
-
-void RexTerrainEngineNode::destroySelectionInfo()
-{
-    //if (_selectionInfo)
-    //{
-    //    delete _selectionInfo; _selectionInfo = 0;
-    //}
-}
-
-void RexTerrainEngineNode::buildSelectionInfo()
-{
-//    _selectionInfo = new SelectionInfo;
+    if (this->parentTexturesRequired())
+        getResources()->reserveTextureImageUnit(colorParent.unit(), "Terrain Color (Parent)");
+
+    // Apply a default, empty texture to each render binding.
+    OE_DEBUG << LC << "Render Bindings:\n";
+    osg::StateSet* terrainSS = _terrain->getOrCreateStateSet();
+    osg::ref_ptr<osg::Texture> tex = new osg::Texture2D(ImageUtils::createEmptyImage(1, 1));
+    for (unsigned i = 0; i < _renderBindings.size(); ++i)
+    {
+        SamplerBinding& b = _renderBindings[i];
+        if (b.isActive())
+        {
+            terrainSS->addUniform(new osg::Uniform(b.samplerName().c_str(), b.unit()));
+            terrainSS->setTextureAttribute(b.unit(), tex.get());
+            OE_DEBUG << LC << " > Bound \"" << b.samplerName() << "\" to unit " << b.unit() << "\n";
+        }
+    }
 }
 
 void
 RexTerrainEngineNode::dirtyTerrain()
 {
-    //TODO: scrub the geometry pool?
+    _terrain->removeChildren(0, _terrain->getNumChildren());
 
     // clear the loader:
     _loader->clear();
 
-    if ( _terrain )
-    {
-        this->removeChild( _terrain );
-    }
-
-    // New terrain
-    _terrain = new osg::Group();
-    this->addChild( _terrain );
-
-    // are we LOD blending?
-    bool setupParentData = 
-        _terrainOptions.morphImagery() == true || // gw: redundant?
-        this->parentTexturesRequired();
-    
-    // reserve GPU unit for the main color texture:
-    if ( _renderBindings.empty() )
-    {
-        setupRenderBindings();
-    }
-
-    // Calculate the LOD morphing parameters:
-    double averageRadius = 0.5*(
-        _update_mapf->getMapInfo().getSRS()->getEllipsoid()->getRadiusEquator() +
-        _update_mapf->getMapInfo().getSRS()->getEllipsoid()->getRadiusPolar());
-
-    double farLOD = 
-        _terrainOptions.minTileRangeFactor().get() *
-        3.214 * averageRadius;
-
-    _selectionInfo.initialize(
-        0u, // always zero, not the terrain options firstLOD
-        std::min( _terrainOptions.maxLOD().get(), 19u ),
-        _terrainOptions.tileSize().get(),
-        farLOD );
-
     // clear out the tile registry:
     if ( _liveTiles.valid() )
     {
         _liveTiles->releaseAll(_releaser.get());
     }
-
-    // Factory to create the root keys:
-    EngineContext* context = getEngineContext();
+    
+    // scrub the geometry pool:
+    _geometryPool->clear();
 
     // Build the first level of the terrain.
     // Collect the tile keys comprising the root tiles of the terrain.
     std::vector<TileKey> keys;
-    _update_mapf->getProfile()->getAllKeysAtLOD( *_terrainOptions.firstLOD(), keys );
+    _mapFrame.getProfile()->getAllKeysAtLOD( *_terrainOptions.firstLOD(), keys );
 
     // create a root node for each root tile key.
-    OE_INFO << LC << "Creating " << keys.size() << " root keys.." << std::endl;
+    OE_DEBUG << LC << "Creating " << keys.size() << " root keys." << std::endl;
+
+    // We need to take a self-ref here to ensure that the TileNode's data loader
+    // can use its observer_ptr back to the terrain engine.
+    this->ref();
 
-    unsigned child = 0;
     for( unsigned i=0; i<keys.size(); ++i )
     {
         TileNode* tileNode = new TileNode();
-        if (context->getOptions().minExpiryFrames().isSet())
+
+        if (_terrainOptions.minExpiryFrames().isSet())
         {
-            tileNode->setMinimumExpiryFrames( *context->getOptions().minExpiryFrames() );
+            tileNode->setMinimumExpirationFrames(_terrainOptions.minExpiryFrames().get());
         }
-        if (context->getOptions().minExpiryTime().isSet())
+        if (_terrainOptions.minExpiryTime().isSet())
         {         
-            tileNode->setMinimumExpiryTime( *context->getOptions().minExpiryTime() );
+            tileNode->setMinimumExpirationTime(_terrainOptions.minExpiryTime().get());
         }
                 
         // Next, build the surface geometry for the node.
-        tileNode->create( keys[i], context );
+        tileNode->create( keys[i], 0L, _engineContext.get() );
 
+        // Add it to the scene graph
         _terrain->addChild( tileNode );
+
+        // And load the tile's data synchronously (only for root tiles)
+        tileNode->loadSync();
     }
 
+    // release the self-ref.
+    this->unref_nodelete();
+
+    // Set up the state sets.
     updateState();
 
     // Call the base class
@@ -502,11 +552,21 @@ RexTerrainEngineNode::dirtyState()
     updateState();
 }
 
-
 void
 RexTerrainEngineNode::traverse(osg::NodeVisitor& nv)
 {
-    if ( nv.getVisitorType() == nv.CULL_VISITOR )
+    if (nv.getVisitorType() == nv.UPDATE_VISITOR)
+    {
+        if (_renderModelUpdateRequired)
+        {
+            UpdateRenderModels visitor(_mapFrame, _renderBindings);
+            _terrain->accept(visitor);
+            _renderModelUpdateRequired = false;
+        }
+        TerrainEngineNode::traverse( nv );
+    }
+    
+    else if ( nv.getVisitorType() == nv.CULL_VISITOR )
     {
         // Inform the registry of the current frame so that Tiles have access
         // to the information.
@@ -514,34 +574,133 @@ RexTerrainEngineNode::traverse(osg::NodeVisitor& nv)
         {
             _liveTiles->setTraversalFrame( nv.getFrameStamp()->getFrameNumber() );
         }
-    }
-
-#if 0
-    static int c = 0;
-    if ( ++c % 60 == 0 )
-    {
-        OE_NOTICE << LC << "Live = " << _liveTiles->size() << ", Dead = " << _deadTiles->size() << std::endl;
-        _liveTiles->run( CheckForOrphans() );
-    }
-#endif
-    
-    if ( nv.getVisitorType() == nv.CULL_VISITOR && _loader.valid() ) // ensures that postInitialize has run
-    {
-        VisitorData::store(nv, ENGINE_CONTEXT_TAG, this->getEngineContext());
-        // Pass the tile creation context to the traversal.
-        //osg::ref_ptr<osg::Referenced> data = nv.getUserData();
-        //nv.setUserData( this->getEngineContext() );
 
         osgUtil::CullVisitor* cv = static_cast<osgUtil::CullVisitor*>(&nv);
 
-        this->getEngineContext()->_surfaceSS = _surfaceSS.get();
+        // Marks the start of the cull pass
+        getEngineContext()->startCull( cv );
+        
+        // Initialize a new culler
+        TerrainCuller culler(cv, this->getEngineContext());
 
-        this->getEngineContext()->startCull( cv );
-        TerrainEngineNode::traverse( nv );
+        // Prepare the culler with the set of renderable layers:
+        culler.setup(_mapFrame, _cachedLayerExtents, this->getEngineContext()->getRenderBindings());
+
+        // Assemble the terrain drawables:
+        _terrain->accept(culler);
+
+        // If we're using geometry pooling, optimize the drawable for shared state
+        // by sorting the draw commands
+        if (getEngineContext()->getGeometryPool()->isEnabled())
+        {
+            culler._terrain.sortDrawCommands();
+        }
+
+        // The common stateset for the terrain group:
+        cv->pushStateSet(_terrain->getOrCreateStateSet());
+
+        // Push all the layers to draw on to the cull visitor in the order in which
+        // they appear in the map.
+        LayerDrawable* lastLayer = 0L;
+        unsigned order = 0;
+        bool surfaceStateSetPushed = false;
+        int layersDrawn = 0;
+
+        osg::State::StateSetStack stateSetStack;
+
+        for(LayerDrawableList::iterator i = culler._terrain.layers().begin();
+            i != culler._terrain.layers().end();
+            ++i)
+        {
+            // Note: Cannot save lastLayer here because its _tiles may be empty, which can lead to a crash later
+            if (!i->get()->_tiles.empty())
+            {
+                lastLayer = i->get();
+                lastLayer->_order = -1;
+
+                // if this is a RENDERTYPE_TILE, we need to activate the default surface state set.
+                if (lastLayer->_renderType == Layer::RENDERTYPE_TILE)
+                {
+                    lastLayer->_order = order++;
+
+                    if (!surfaceStateSetPushed)
+                        cv->pushStateSet(getSurfaceStateSet());
+                    surfaceStateSetPushed = true;
+                }
+                else if (surfaceStateSetPushed)
+                {
+                    cv->popStateSet();
+                    surfaceStateSetPushed = false;
+                }                    
+
+                //OE_INFO << "   Apply: " << (lastLayer->_layer ? lastLayer->_layer->getName() : "-1") << "; tiles=" << lastLayer->_tiles.size() << std::endl;
+                //buf << (lastLayer->_layer ? lastLayer->_layer->getName() : "none") << " (" << lastLayer->_tiles.size() << ")\n";
+
+                if (lastLayer->_layer)
+                {
+                    stateSetStack.clear();
+
+                    if (lastLayer->_layer->cull(cv, stateSetStack))
+                    {
+                        for (unsigned j = 0; j<stateSetStack.size(); ++j)
+                            cv->pushStateSet(stateSetStack[j]);
+
+                        cv->apply(*lastLayer);
+
+                        for (unsigned j = 0; j<stateSetStack.size(); ++j)
+                            cv->popStateSet();
+                    }
+                }
+                else
+                {
+                    cv->apply(*lastLayer);
+                }
+
+                ++layersDrawn;
+            }
+
+            //buf << (lastLayer->_layer ? lastLayer->_layer->getName() : "none") << " (" << lastLayer->_tiles.size() << ")\n";
+        }
+
+        // Uncomment this to see how many layers were drawn
+        //Registry::instance()->startActivity("Layers", Stringify()<<layersDrawn);
+
+        // The last layer to render must clear up the OSG state,
+        // otherwise it will be corrupt and can lead to crashing.
+        if (lastLayer)
+        {
+            lastLayer->_clearOsgState = true;
+        }
+                
+        if (surfaceStateSetPushed)
+        {
+            cv->popStateSet();
+            surfaceStateSetPushed = false;
+        }
+
+        // pop the common terrain state set
+        cv->popStateSet();
+        
+        // marks the end of the cull pass
         this->getEngineContext()->endCull( cv );
 
-        //if ( data.valid() )
-        //    nv.setUserData( data.get() );
+        // If the culler found any orphaned data, we need to update the render model
+        // during the next update cycle.
+        if (culler._orphanedPassesDetected > 0u)
+        {
+            _renderModelUpdateRequired = true;
+            OE_INFO << LC << "Detected " << culler._orphanedPassesDetected << " orphaned rendering passes\n";
+        }
+
+        // we don't call this b/c we don't want _terrain
+        //TerrainEngineNode::traverse(nv);
+
+        // traverse all the other children (geometry pool, loader/unloader, etc.)
+        _geometryPool->accept(nv);
+        _loader->accept(nv);
+        _unloader->accept(nv);
+        _releaser->accept(nv);
+        _rasterizer->accept(nv);
     }
 
     else
@@ -550,30 +709,6 @@ RexTerrainEngineNode::traverse(osg::NodeVisitor& nv)
     }
 }
 
-
-EngineContext*
-RexTerrainEngineNode::getEngineContext()
-{
-    osg::ref_ptr<EngineContext>& context = _perThreadTileGroupFactories.get(); // thread-safe get
-    if ( !context.valid() )
-    {
-        // initialize a key node factory.
-        context = new EngineContext(
-            getMap(),
-            this, // engine
-            _geometryPool.get(),
-            _loader.get(),
-            _unloader.get(),
-            _liveTiles.get(),
-            _renderBindings,
-            _terrainOptions,
-            _selectionInfo,
-            _tilePatchCallbacks);
-    }
-
-    return context.get();
-}
-
 unsigned int
 RexTerrainEngineNode::computeSampleSize(unsigned int levelOfDetail)
 {    
@@ -653,6 +788,188 @@ osg::Node* renderHeightField(const GeoHeightField& geoHF)
     return mt;
 }
 
+osg::Node*
+RexTerrainEngineNode::createTile(const TerrainTileModel* model,
+                                 int flags,
+                                 unsigned referenceLOD)
+{
+    if (model == 0L)
+    {
+        OE_WARN << LC << "Illegal: createTile(NULL)" << std::endl;
+        return 0L;
+    }
+
+    // Dimension of each tile in vertices
+    unsigned tileSize = getEngineContext()->getOptions().tileSize().get();
+
+    optional<bool> hasMasks(false);
+    
+    // Trivial rejection test for masking geometry. Check at the top level and
+    // if there's not mask there, there's no mask at the reference LOD either.
+    osg::ref_ptr<MaskGenerator> maskGenerator = new MaskGenerator(
+        model->getKey(),
+        tileSize,
+        getEngineContext()->getMap()
+    );
+
+    bool includeTilesWithMasks = (flags & CREATE_TILE_INCLUDE_TILES_WITH_MASKS) != 0;
+    bool includeTilesWithoutMasks = (flags & CREATE_TILE_INCLUDE_TILES_WITHOUT_MASKS) != 0;
+    
+    if (maskGenerator->hasMasks() == false && includeTilesWithoutMasks == false)
+        return 0L;
+
+    // If the ref LOD is higher than the key LOD, build a list of tile keys at the 
+    // ref LOD that match up with the main tile key in the model.
+    std::vector<TileKey> keys;
+    if (referenceLOD > model->getKey().getLOD())
+    {
+        unsigned span = 1 << (referenceLOD - model->getKey().getLOD());
+        unsigned x0 = model->getKey().getTileX() * span;
+        unsigned y0 = model->getKey().getTileY() * span;
+
+        keys.reserve(span*span);
+
+        for (unsigned x=x0; x<x0+span; ++x)
+        {
+            for (unsigned y=y0; y<y0+span; ++y)
+            {
+                keys.push_back(TileKey(referenceLOD, x, y, model->getKey().getProfile()));
+            }
+        }
+    }
+    else
+    {
+        // no reference LOD? Just use the model's key.
+        keys.push_back(model->getKey());
+    }
+
+    // group to hold all the tiles
+    osg::Group* group = new osg::Group();
+
+    maskGenerator = 0L;
+    
+    for (std::vector<TileKey>::const_iterator key = keys.begin(); key != keys.end(); ++key)
+    {
+        // Mask generator creates geometry from masking boundaries when they exist.
+        maskGenerator = new MaskGenerator(
+            *key,
+            tileSize,
+            getEngineContext()->getMap());
+
+        if (maskGenerator->hasMasks() == true && includeTilesWithMasks == false)
+            continue;
+
+        if (maskGenerator->hasMasks() == false && includeTilesWithoutMasks == false)
+            continue;
+
+        osg::ref_ptr<SharedGeometry> sharedGeom;
+
+        getEngineContext()->getGeometryPool()->getPooledGeometry(
+            *key,
+            _mapFrame.getMapInfo(),
+            tileSize,
+            maskGenerator.get(),
+            sharedGeom);
+
+        osg::ref_ptr<osg::Drawable> drawable = sharedGeom.get();
+
+        osg::UserDataContainer* udc = drawable->getOrCreateUserDataContainer();
+        udc->setUserValue("tile_key", key->str());
+
+        if (sharedGeom.valid())
+        {
+            osg::ref_ptr<osg::Geometry> geom = sharedGeom->makeOsgGeometry();
+            drawable = geom.get();
+            drawable->setUserDataContainer(udc);
+
+            if (!sharedGeom->empty())
+            {
+                // Burn elevation data into the vertex list
+                if (model->elevationModel().valid())
+                {
+                    // Clone the vertex array since it's shared and we're going to alter it
+                    geom->setVertexArray(osg::clone(geom->getVertexArray()));
+
+                    // Apply the elevation model to the verts, noting that the texture coordinate
+                    // runs [0..1] across the tile and the normal is the up vector at each vertex.
+                    osg::Vec3Array* verts = dynamic_cast<osg::Vec3Array*>(geom->getVertexArray());
+                    osg::Vec3Array* ups = dynamic_cast<osg::Vec3Array*>(geom->getNormalArray());
+                    osg::Vec3Array* tileCoords = dynamic_cast<osg::Vec3Array*>(geom->getTexCoordArray(0));
+
+                    const osg::HeightField* hf = model->elevationModel()->getHeightField();
+                    const osg::RefMatrixf* hfmatrix = model->elevationModel()->getMatrix();
+
+                    // Tile coords must be transformed into the local tile's space
+                    // for elevation grid lookup:
+                    osg::Matrix scaleBias;
+                    key->getExtent().createScaleBias(model->getKey().getExtent(), scaleBias);
+
+                    // Apply elevation to each vertex.
+                    for (unsigned i = 0; i < verts->size(); ++i)
+                    {
+                        osg::Vec3& vert = (*verts)[i];
+                        osg::Vec3& up = (*ups)[i];
+                        osg::Vec3& tileCoord = (*tileCoords)[i];
+
+                        // Skip verts on a masking boundary since their elevations are hard-wired.
+                        if (tileCoord.z() != MASK_MARKER_BOUNDARY)
+                        {
+                            osg::Vec3d n = osg::Vec3d(tileCoord.x(), tileCoord.y(), 0);
+                            n = n * scaleBias;
+                            if (hfmatrix) n = n * (*hfmatrix);
+
+                            float z = HeightFieldUtils::getHeightAtNormalizedLocation(hf, n.x(), n.y());
+                            if (z != NO_DATA_VALUE)
+                            {
+                                vert += up*z;
+                            }
+                        }
+                    }
+                }
+
+                // Encode the masking extents into a user data object
+                if (maskGenerator->hasMasks())
+                {
+                    // Find the NDC coords of the masking patch geometry:
+                    osg::Vec3d maskMin, maskMax;
+                    maskGenerator->getMinMax(maskMin, maskMax);
+
+                    // Clamp to the tile's extent
+                    maskMin.x() = osg::clampBetween(maskMin.x(), 0.0, 1.0);
+                    maskMin.y() = osg::clampBetween(maskMin.y(), 0.0, 1.0);
+                    maskMax.x() = osg::clampBetween(maskMax.x(), 0.0, 1.0);
+                    maskMax.y() = osg::clampBetween(maskMax.y(), 0.0, 1.0);
+
+                    const GeoExtent& e = key->getExtent();
+                    osg::Vec2d tkMin(e.xMin() + maskMin.x()*e.width(), e.yMin() + maskMin.y()*e.height());
+                    osg::Vec2d tkMax(e.xMin() + maskMax.x()*e.width(), e.yMin() + maskMax.y()*e.height());
+
+                    udc->setUserValue("mask_patch_min", tkMin);
+                    udc->setUserValue("mask_patch_max", tkMax);
+
+                    //OE_INFO << LC << "key " << key->str()
+                    //    << " ext=" << e.toString() << "\n"
+                    //    << " min=" << tkMin.x() << "," << tkMin.y()
+                    //    << "; max=" << tkMax.x() << "," << tkMax.y() << std::endl;
+                }
+            }
+    
+            // Establish a local reference frame for the tile:
+            GeoPoint centroid;
+            key->getExtent().getCentroid(centroid);
+
+            osg::Matrix local2world;
+            centroid.createLocalToWorld(local2world);
+
+            osg::MatrixTransform* xform = new osg::MatrixTransform(local2world);
+            xform->addChild(drawable.get());
+
+            group->addChild(xform);
+        }
+    }
+
+    return group;
+}
 
 osg::Node*
 RexTerrainEngineNode::createTile( const TileKey& key )
@@ -665,14 +982,14 @@ RexTerrainEngineNode::createTile( const TileKey& key )
 
     // ALWAYS use 257x257 b/c that is what rex always uses.
     osg::ref_ptr< osg::HeightField > out_hf = HeightFieldUtils::createReferenceHeightField(
-            key.getExtent(), 257, 257, true );
+            key.getExtent(), 257, 257, 0u, true );
 
     sampleKey = key;
 
     bool populated = false;
     while (!populated)
     {
-        populated = _update_mapf->populateHeightField(
+        populated = _mapFrame.populateHeightField(
             out_hf,
             sampleKey,
             true, // convertToHAE
@@ -694,7 +1011,7 @@ RexTerrainEngineNode::createTile( const TileKey& key )
     if (!populated)
     {
         // We have no heightfield so just create a reference heightfield.
-        out_hf = HeightFieldUtils::createReferenceHeightField( key.getExtent(), 257, 257);
+        out_hf = HeightFieldUtils::createReferenceHeightField( key.getExtent(), 257, 257, 0u);
         sampleKey = key;
     }
 #endif
@@ -733,9 +1050,10 @@ RexTerrainEngineNode::onMapModelChanged( const MapModelChange& change )
     else
     {
         // update the thread-safe map model copy:
-        if ( _update_mapf->sync() )
+        if ( _mapFrame.sync() )
         {
-            _liveTiles->setMapRevision( _update_mapf->getRevision() );
+            _liveTiles->setMapRevision( _mapFrame.getRevision() );
+            OE_INFO << LC << "MapFrame synced to new revision: " << _mapFrame.getRevision() << std::endl;
         }
 
         // dispatch the change handler
@@ -744,30 +1062,26 @@ RexTerrainEngineNode::onMapModelChanged( const MapModelChange& change )
             // then apply the actual change:
             switch( change.getAction() )
             {
-            case MapModelChange::ADD_IMAGE_LAYER:
-                addImageLayer( change.getImageLayer() );
-                break;
-            case MapModelChange::REMOVE_IMAGE_LAYER:
-                removeImageLayer( change.getImageLayer() );
+            case MapModelChange::ADD_LAYER:
+                addLayer(change.getLayer());
                 break;
-            case MapModelChange::ADD_ELEVATION_LAYER:
-                addElevationLayer( change.getElevationLayer() );
-                break;
-            case MapModelChange::REMOVE_ELEVATION_LAYER:
-                removeElevationLayer( change.getElevationLayer() );
-                break;
-            case MapModelChange::MOVE_IMAGE_LAYER:
-                moveImageLayer( change.getFirstIndex(), change.getSecondIndex() );
+
+            case MapModelChange::REMOVE_LAYER:
+                if (change.getImageLayer())
+                    removeImageLayer( change.getImageLayer() );
+                else if (change.getElevationLayer())
+                    removeElevationLayer(change.getElevationLayer());
                 break;
-            case MapModelChange::MOVE_ELEVATION_LAYER:
-                moveElevationLayer( change.getFirstIndex(), change.getSecondIndex() );
+
+            case MapModelChange::MOVE_LAYER:
+                if (change.getElevationLayer())
+                    moveElevationLayer(change.getElevationLayer());
                 break;
+
             case MapModelChange::TOGGLE_ELEVATION_LAYER:
                 toggleElevationLayer( change.getElevationLayer() );
                 break;
-            case MapModelChange::ADD_MODEL_LAYER:
-            case MapModelChange::REMOVE_MODEL_LAYER:
-            case MapModelChange::MOVE_MODEL_LAYER:
+
             default: 
                 break;
             }
@@ -775,52 +1089,134 @@ RexTerrainEngineNode::onMapModelChanged( const MapModelChange& change )
     }
 }
 
+void
+RexTerrainEngineNode::cacheLayerExtentInMapSRS(Layer* layer)
+{
+    if (layer->getUID() + 1 > _cachedLayerExtents.size())
+    {
+        _cachedLayerExtents.resize(layer->getUID()+1);
+    }
+
+    // Store the layer's extent in the map's SRS:
+    LayerExtent& le = _cachedLayerExtents[layer->getUID()];
+    le._extent = layer->getExtent().transform(_mapFrame.getMapInfo().getSRS());
+    le._computed = true;
+}
+
+void
+RexTerrainEngineNode::addLayer(Layer* layer)
+{
+    if (layer)
+    {
+        if (layer->getEnabled())
+        {
+            if (layer->getRenderType() == Layer::RENDERTYPE_TILE)
+                addTileLayer(layer);
+            else if (dynamic_cast<ElevationLayer*>(layer))
+                addElevationLayer(dynamic_cast<ElevationLayer*>(layer));
+        }
+
+        cacheLayerExtentInMapSRS(layer);
+    }
+}
 
 void
-RexTerrainEngineNode::addImageLayer( ImageLayer* layerAdded )
+RexTerrainEngineNode::addTileLayer(Layer* tileLayer)
 {
-    if ( layerAdded && layerAdded->getEnabled() )
+    if ( tileLayer && tileLayer->getEnabled() )
     {
-        // for a shared layer, allocate a shared image unit if necessary.
-        if ( layerAdded->isShared() )
+        ImageLayer* imageLayer = dynamic_cast<ImageLayer*>(tileLayer);
+        if (imageLayer)
         {
-            optional<int>& unit = layerAdded->shareImageUnit();
-            if ( !unit.isSet() )
+            // for a shared layer, allocate a shared image unit if necessary.
+            if ( imageLayer->isShared() )
             {
-                int temp;
-                if ( getResources()->reserveTextureImageUnit(temp) )
+                if (!imageLayer->shareImageUnit().isSet())
                 {
-                    layerAdded->shareImageUnit() = temp;
-                    OE_INFO << LC << "Image unit " << temp << " assigned to shared layer " << layerAdded->getName() << std::endl;
+                    int temp;
+                    if ( getResources()->reserveTextureImageUnit(temp, imageLayer->getName().c_str()) )
+                    {
+                        imageLayer->shareImageUnit() = temp;
+                        //OE_INFO << LC << "Image unit " << temp << " assigned to shared layer " << imageLayer->getName() << std::endl;
+                    }
+                    else
+                    {
+                        OE_WARN << LC << "Insufficient GPU image units to share layer " << imageLayer->getName() << std::endl;
+                    }
                 }
-                else
+
+                // Build a sampler binding for the shared layer.
+                if ( imageLayer->shareImageUnit().isSet() )
                 {
-                    OE_WARN << LC << "Insufficient GPU image units to share layer " << layerAdded->getName() << std::endl;
+                    // Find the next empty SHARED slot:
+                    unsigned newIndex = SamplerBinding::SHARED;
+                    while (_renderBindings[newIndex].isActive())
+                        ++newIndex;
+
+                    // Put the new binding there:
+                    SamplerBinding& newBinding = _renderBindings[newIndex];
+                    newBinding.usage()       = SamplerBinding::SHARED;
+                    newBinding.sourceUID()   = imageLayer->getUID();
+                    newBinding.unit()        = imageLayer->shareImageUnit().get();
+                    newBinding.samplerName() = imageLayer->shareTexUniformName().get();
+                    newBinding.matrixName()  = imageLayer->shareTexMatUniformName().get();
+
+                    OE_INFO << LC 
+                        << "Shared Layer \"" << imageLayer->getName() << "\" : sampler=\"" << newBinding.samplerName() << "\", "
+                        << "matrix=\"" << newBinding.matrixName() << "\", "
+                        << "unit=" << newBinding.unit() << "\n";
+
+                    // Install an empty texture for this binding at the top of the graph, so that 
+                    // a texture is always defined even when the data source supplies no real data.
+                    if (newBinding.isActive())
+                    {
+                        osg::StateSet* terrainSS = _terrain->getOrCreateStateSet();
+                        osg::ref_ptr<osg::Texture> tex = new osg::Texture2D(ImageUtils::createEmptyImage(1,1));
+                        terrainSS->addUniform(new osg::Uniform(newBinding.samplerName().c_str(), newBinding.unit()));
+                        terrainSS->setTextureAttribute(newBinding.unit(), tex.get(), 1);
+                        OE_INFO << LC << "Bound shared sampler " << newBinding.samplerName() << " to unit " << newBinding.unit() << std::endl;
+                    }
                 }
             }
 
-            // Build a sampler binding for the layer.
-            if ( unit.isSet() )
-            {
-                _renderBindings.push_back( SamplerBinding() );
-                SamplerBinding& binding = _renderBindings.back();
+            // For an image layer, attach the default fragment shader:
+            Shaders shaders;
+            osg::StateSet* stateSet = imageLayer->getOrCreateStateSet();
+            VirtualProgram* vp = VirtualProgram::getOrCreate(stateSet);
+            shaders.load(vp, shaders.ENGINE_FRAG);
+        }
 
-                //binding.usage()     = binding.MATERIAL; // ?... not COLOR at least
-                binding.sourceUID() = layerAdded->getUID();
-                binding.unit()      = unit.get();
+        else
+        {
+            // non-image tile layer. Keep track of these..
+        }
 
-                binding.samplerName() = layerAdded->shareTexUniformName().get();
-                binding.matrixName()  = layerAdded->shareTexMatUniformName().get();
+        if (_terrain)
+        {
+            // Update the existing render models, and trigger a data reload.
+            // Later we can limit the reload to an update of only the new data.
+            UpdateRenderModels updateModels(_mapFrame, _renderBindings);
 
-                OE_INFO << LC 
-                    << " .. Sampler=\"" << binding.samplerName() << "\", "
-                    << "Matrix=\"" << binding.matrixName() << ", "
-                    << "unit=" << binding.unit() << "\n";                
-            }
+#if 0
+            // This uses the loaddata filter approach which will only request
+            // data for one layer. It mostly works but not 100%; see hires-insets
+            // as an example. Removing the world layer and re-adding it while
+            // zoomed in doesn't result in all tiles reloading. Possibly a
+            // synchronization issue.
+            ImageLayerVector imageLayers;
+            _mapFrame.getLayers(imageLayers);
+
+            if (imageLayers.size() == 1)
+                updateModels.setReloadData(true);
+            else
+                updateModels.layersToLoad().insert(tileLayer->getUID());
+#else
+            updateModels.setReloadData(true);
+#endif
+
+            _terrain->accept(updateModels);
         }
     }
-
-    refresh();
 }
 
 
@@ -838,17 +1234,34 @@ RexTerrainEngineNode::removeImageLayer( ImageLayer* layerRemoved )
                 layerRemoved->shareImageUnit().unset();
             }
 
-            //TODO: remove the sampler/matrix uniforms
+            // Remove from RenderBindings (mark as unused)
+            for (unsigned i = 0; i < _renderBindings.size(); ++i)
+            {
+                SamplerBinding& binding = _renderBindings[i];
+                if (binding.isActive() && binding.sourceUID() == layerRemoved->getUID())
+                {
+                    OE_INFO << LC << "Binding (" << binding.samplerName() << " unit " << binding.unit() << ") cleared\n";
+                    binding.usage().clear();
+                    binding.unit() = -1;
+
+                    // Request an update to reset the shared sampler in the scene graph
+                    _renderModelUpdateRequired = true;
+                }
+            }
         }
     }
 
-    refresh();
-}
+    if (_terrain)
+    {
+        // Run the update visitor, which will clean out any rendering passes
+        // associated with the layer we just removed. This would happen 
+        // automatically during cull/update anyway, but it's more efficient
+        // to do it all at once.
+        UpdateRenderModels updater(_mapFrame, _renderBindings);
+        _terrain->accept(updater);
+    }
 
-void
-RexTerrainEngineNode::moveImageLayer( unsigned int oldIndex, unsigned int newIndex )
-{
-    updateState();
+    //OE_INFO << LC << " Updated " << updater._count << " tiles\n";
 }
 
 void
@@ -857,9 +1270,13 @@ RexTerrainEngineNode::addElevationLayer( ElevationLayer* layer )
     if ( layer == 0L || layer->getEnabled() == false )
         return;
 
-    layer->addCallback( _elevationCallback.get() );
+    //layer->addCallback( _elevationCallback.get() );
 
-    refresh();
+    // only need to refresh is the elevation layer is visible.
+    if (layer->getVisible())
+    {
+        refresh();
+    }
 }
 
 void
@@ -868,15 +1285,26 @@ RexTerrainEngineNode::removeElevationLayer( ElevationLayer* layerRemoved )
     if ( layerRemoved->getEnabled() == false )
         return;
 
-    layerRemoved->removeCallback( _elevationCallback.get() );
+    //layerRemoved->removeCallback( _elevationCallback.get() );
 
-    refresh();
+    // only need to refresh is the elevation layer is visible.
+    if (layerRemoved->getVisible())
+    {
+        refresh();
+    }
 }
 
 void
-RexTerrainEngineNode::moveElevationLayer( unsigned int oldIndex, unsigned int newIndex )
+RexTerrainEngineNode::moveElevationLayer(ElevationLayer* layerMoved)
 {
-    refresh();
+    if ( layerMoved->getEnabled() == false )
+        return;
+
+    // only need to refresh is the elevation layer is visible.
+    if (layerMoved->getVisible())
+    {
+        refresh();
+    }
 }
 
 void
@@ -896,14 +1324,19 @@ RexTerrainEngineNode::updateState()
     else
     {
         osg::StateSet* terrainStateSet   = _terrain->getOrCreateStateSet();   // everything
-        osg::StateSet* surfaceStateSet   = getSurfaceStateSet();    // just the surface
+        terrainStateSet->setName("Terrain Group");
+
+        osg::StateSet* surfaceStateSet = getSurfaceStateSet();    // just the surface
         
-        terrainStateSet->setRenderBinDetails(0, "SORT_FRONT_TO_BACK");
+        //terrainStateSet->setRenderBinDetails(0, "SORT_FRONT_TO_BACK");
         
         // required for multipass tile rendering to work
         surfaceStateSet->setAttributeAndModes(
             new osg::Depth(osg::Depth::LEQUAL, 0, 1, true) );
 
+        surfaceStateSet->setAttributeAndModes(
+            new osg::CullFace(), osg::StateAttribute::ON);
+
         // activate standard mix blending.
         terrainStateSet->setAttributeAndModes( 
             new osg::BlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA),
@@ -928,11 +1361,7 @@ RexTerrainEngineNode::updateState()
             
             surfaceStateSet->addUniform(new osg::Uniform("oe_terrain_color", _terrainOptions.color().get()));
 
-            bool useBlending = _terrainOptions.enableBlending().get();
-            package.define("OE_REX_GL_BLENDING", useBlending);
-
-            bool morphImagery = _terrainOptions.morphImagery().get();
-            package.define("OE_REX_MORPH_IMAGERY", morphImagery);
+            surfaceStateSet->setDefine("OE_TERRAIN_RENDER_IMAGERY");
 
             // Funtions that affect only the terrain surface:
             VirtualProgram* surfaceVP = VirtualProgram::getOrCreate(surfaceStateSet);
@@ -940,21 +1369,48 @@ RexTerrainEngineNode::updateState()
 
             // Functions that affect the terrain surface only:
             package.load(surfaceVP, package.ENGINE_VERT_VIEW);
-            package.load(surfaceVP, package.ENGINE_FRAG);
+            package.load(surfaceVP, package.ENGINE_ELEVATION_MODEL);
+            //package.load(surfaceVP, package.ENGINE_FRAG);
+
+            // Elevation?
+            if (this->elevationTexturesRequired())
+            {
+                surfaceStateSet->setDefine("OE_TERRAIN_RENDER_ELEVATION");
+            }
 
             // Normal mapping shaders:
             if ( this->normalTexturesRequired() )
             {
                 package.load(surfaceVP, package.NORMAL_MAP_VERT);
                 package.load(surfaceVP, package.NORMAL_MAP_FRAG);
+                surfaceStateSet->setDefine("OE_TERRAIN_RENDER_NORMAL_MAP");
+            }
+
+            if (_terrainOptions.enableBlending() == true)
+            {
+                surfaceStateSet->setDefine("OE_TERRAIN_BLEND_IMAGERY");
             }
 
             // Morphing?
             if (_terrainOptions.morphTerrain() == true ||
                 _terrainOptions.morphImagery() == true)
             {
-                package.define("OE_REX_VERTEX_MORPHING", (_terrainOptions.morphTerrain() == true));
                 package.load(surfaceVP, package.MORPHING_VERT);
+
+                if (_terrainOptions.morphImagery() == true)
+                {
+                    surfaceStateSet->setDefine("OE_TERRAIN_MORPH_IMAGERY");
+                }
+                if (_terrainOptions.morphTerrain() == true)
+                {
+                    surfaceStateSet->setDefine("OE_TERRAIN_MORPH_GEOMETRY");
+                }
+            }
+
+            // Shadowing?
+            if (_terrainOptions.castShadows() == true)
+            {
+                surfaceStateSet->setDefine("OE_TERRAIN_CAST_SHADOWS");
             }
 
             // assemble color filter code snippets.
@@ -977,10 +1433,12 @@ RexTerrainEngineNode::updateState()
 
                 // second, install the per-layer color filter functions AND shared layer bindings.
                 bool ifStarted = false;
-                int numImageLayers = _update_mapf->imageLayers().size();
-                for( int i=0; i<numImageLayers; ++i )
+                ImageLayerVector imageLayers;
+                _mapFrame.getLayers(imageLayers);
+
+                for( int i=0; i<imageLayers.size(); ++i )
                 {
-                    ImageLayer* layer = _update_mapf->getImageLayerAt(i);
+                    ImageLayer* layer = imageLayers[i].get();
                     if ( layer->getEnabled() )
                     {
                         // install Color Filter function calls:
@@ -1021,21 +1479,22 @@ RexTerrainEngineNode::updateState()
                 }
             }
 
+#if 0
             // Apply uniforms for sampler bindings:
             OE_DEBUG << LC << "Render Bindings:\n";
-            for(RenderBindings::const_iterator b = _renderBindings.begin(); b != _renderBindings.end(); ++b)
+            osg::ref_ptr<osg::Texture> tex = new osg::Texture2D(ImageUtils::createEmptyImage(1,1));
+            for (unsigned i = 0; i < _renderBindings.size(); ++i)
             {
-                osg::Image* empty = ImageUtils::createEmptyImage(1,1);
-                osg::ref_ptr<osg::Texture2D> tex = new osg::Texture2D(empty);
-
-                if ( b->isActive() )
+                SamplerBinding& b = _renderBindings[i];
+                if (b.isActive())
                 {
-                    terrainStateSet->addUniform( new osg::Uniform(b->samplerName().c_str(), b->unit()) );
-                    OE_DEBUG << LC << " > Bound \"" << b->samplerName() << "\" to unit " << b->unit() << "\n";
-                    terrainStateSet->setTextureAttribute(b->unit(), tex.get());
+                    osg::Uniform* u = new osg::Uniform(b.samplerName().c_str(), b.unit());
+                    terrainStateSet->addUniform( u );
+                    OE_DEBUG << LC << " > Bound \"" << b.samplerName() << "\" to unit " << b.unit() << "\n";
+                    terrainStateSet->setTextureAttribute(b.unit(), tex.get());
                 }
-
             }
+#endif
 
             // uniform that controls per-layer opacity
             terrainStateSet->addUniform( new osg::Uniform("oe_layer_opacity", 1.0f) );
@@ -1052,7 +1511,7 @@ RexTerrainEngineNode::updateState()
             // default min/max range uniforms. (max < min means ranges are disabled)
             terrainStateSet->addUniform( new osg::Uniform("oe_layer_minRange", 0.0f) );
             terrainStateSet->addUniform( new osg::Uniform("oe_layer_maxRange", -1.0f) );
-            terrainStateSet->addUniform( new osg::Uniform("oe_layer_attenuationRange", _terrainOptions.attentuationDistance().get()) );
+            terrainStateSet->addUniform( new osg::Uniform("oe_layer_attenuationRange", _terrainOptions.attenuationDistance().get()) );
             
             terrainStateSet->getOrCreateUniform(
                 "oe_min_tile_range_factor",
diff --git a/src/osgEarthDrivers/engine_rex/RexTerrainEngineOptions b/src/osgEarthDrivers/engine_rex/RexTerrainEngineOptions
index 20c135f..9477f26 100644
--- a/src/osgEarthDrivers/engine_rex/RexTerrainEngineOptions
+++ b/src/osgEarthDrivers/engine_rex/RexTerrainEngineOptions
@@ -38,11 +38,11 @@ namespace osgEarth { namespace Drivers { namespace RexTerrainEngine
         RexTerrainEngineOptions( const ConfigOptions& options =ConfigOptions() ) : TerrainOptions( options ),
             _skirtRatio             ( 0.0f ),
             _color                  ( Color::White ),
-            //_quickRelease           ( false ),
             _expirationThreshold    ( 300 ),
             _progressive            ( false ),
             _highResolutionFirst    ( true ),
             _normalMaps             ( true ),
+            _normalizeEdges         ( false ),
             _morphTerrain           ( true ),
             _morphImagery           ( true ),
             _mergesPerFrame         ( 20 ),
@@ -59,6 +59,13 @@ namespace osgEarth { namespace Drivers { namespace RexTerrainEngine
         virtual ~RexTerrainEngineOptions() { }
 
     public:
+        struct LODOptions {
+            optional<unsigned> _lod;
+            optional<float> _priorityScale;
+            optional<float> _priorityOffset;
+        };
+
+    public:
         /** Ratio of terrain tile skirt height to tile radius */
         optional<float>& heightFieldSkirtRatio() { return _skirtRatio; }
         const optional<float>& heightFieldSkirtRatio() const { return _skirtRatio; }
@@ -67,13 +74,6 @@ namespace osgEarth { namespace Drivers { namespace RexTerrainEngine
         optional<Color>& color() { return _color; }
         const optional<Color>& color() const { return _color; }
 
-#if 0
-        /** Whether to run a post-render process that releases GL objects as quickly as
-          * possible, freeing up memory faster */
-        optional<bool>& quickReleaseGLObjects() { return _quickRelease; }
-        const optional<bool>& quickReleaseGLObjects() const { return _quickRelease; }
-#endif
-
         /** Minimum distance at which tiles will page out of memory once loaded */
         optional<float>& expirationRange() { return _expirationRange; }
         const optional<float>& expirationRange() const { return _expirationRange; }
@@ -94,6 +94,11 @@ namespace osgEarth { namespace Drivers { namespace RexTerrainEngine
         optional<bool>& normalMaps() { return _normalMaps; }
         const optional<bool>& normalMaps() const { return _normalMaps; }
 
+        /** Whether to average normal vectors on tile boundaries. Doing so reduces the
+         *  the appearance of seams when using lighting, but requires extra CPU work. */
+        optional<bool>& normalizeEdges() { return _normalizeEdges; }
+        const optional<bool>& normalizeEdges() const { return _normalizeEdges; }
+
         /** Whether to morph terrain data between terrain tile LODs. */
         optional<bool>& morphTerrain() { return _morphTerrain; }
         const optional<bool>& morphTerrain() const { return _morphTerrain; }
@@ -106,25 +111,42 @@ namespace osgEarth { namespace Drivers { namespace RexTerrainEngine
         optional<int>& mergesPerFrame() { return _mergesPerFrame; }
         const optional<int>& mergesPerFrame() const { return _mergesPerFrame; }
 
+        /** Options for specific LODs */
+        std::vector<LODOptions>& lods() { return _lods; }
+        const std::vector<LODOptions>& lods() const { return _lods; }
 
-    protected:
+    public:
         virtual Config getConfig() const {
             Config conf = TerrainOptions::getConfig();
-            conf.updateIfSet( "skirt_ratio", _skirtRatio );
-            conf.updateIfSet( "color", _color );
-            conf.updateIfSet( "quick_release_gl_objects", _quickRelease );
-            conf.updateIfSet( "expiration_range", _expirationRange );
-            conf.updateIfSet( "expiration_threshold", _expirationThreshold );
-            conf.updateIfSet( "progressive", _progressive );
-            conf.updateIfSet( "high_resolution_first", _highResolutionFirst );
-            conf.updateIfSet( "normal_maps", _normalMaps );
-            conf.updateIfSet( "morph_terrain", _morphTerrain );
-            conf.updateIfSet( "morph_imagery", _morphImagery );
-            conf.updateIfSet( "merges_per_frame", _mergesPerFrame );
+            conf.set( "skirt_ratio", _skirtRatio );
+            conf.set( "color", _color );
+            conf.set( "quick_release_gl_objects", _quickRelease );
+            conf.set( "expiration_range", _expirationRange );
+            conf.set( "expiration_threshold", _expirationThreshold );
+            conf.set( "progressive", _progressive );
+            conf.set( "high_resolution_first", _highResolutionFirst );
+            conf.set( "normal_maps", _normalMaps );
+            conf.set( "normalize_edges", _normalizeEdges);
+            conf.set( "morph_terrain", _morphTerrain );
+            conf.set( "morph_imagery", _morphImagery );
+            conf.set( "merges_per_frame", _mergesPerFrame );
+
+            if (!_lods.empty()) {
+                Config lodsConf("lods");
+                for (unsigned i = 0; i < _lods.size(); ++i) {
+                    Config lodConf("lod");
+                    lodConf.addIfSet("lod", _lods[i]._lod);
+                    lodConf.addIfSet("priority_scale", _lods[i]._priorityScale);
+                    lodConf.addIfSet("priority_offset", _lods[i]._priorityOffset);
+                    lodsConf.add(lodConf);
+                }
+                conf.add(lodsConf);
+            }
 
             return conf;
         }
 
+    protected:
         virtual void mergeConfig( const Config& conf ) {
             TerrainOptions::mergeConfig( conf );
             fromConfig( conf );
@@ -140,9 +162,24 @@ namespace osgEarth { namespace Drivers { namespace RexTerrainEngine
             conf.getIfSet( "progressive", _progressive );
             conf.getIfSet( "high_resolution_first", _highResolutionFirst );
             conf.getIfSet( "normal_maps", _normalMaps );
+            conf.getIfSet( "normalize_edges", _normalizeEdges);
             conf.getIfSet( "morph_terrain", _morphTerrain );
             conf.getIfSet( "morph_imagery", _morphImagery );
             conf.getIfSet( "merges_per_frame", _mergesPerFrame );
+
+            const Config* lods = conf.child_ptr("lods");
+            if (lods) {
+                for (ConfigSet::const_iterator i = lods->children().begin(); i != lods->children().end(); ++i) {
+                    const Config& lodConf = *i;
+                    if (!lodConf.empty()) {
+                        _lods.push_back(LODOptions());
+                        LODOptions& lod = _lods.back();
+                        lodConf.getIfSet("lod", lod._lod);
+                        lodConf.getIfSet("priority_scale", lod._priorityScale);
+                        lodConf.getIfSet("priority_offset", lod._priorityOffset);
+                    }
+                }
+            }
         }
 
         optional<float>    _skirtRatio;
@@ -153,9 +190,11 @@ namespace osgEarth { namespace Drivers { namespace RexTerrainEngine
         optional<bool>     _progressive;
         optional<bool>     _highResolutionFirst;
         optional<bool>     _normalMaps;
+        optional<bool>     _normalizeEdges;
         optional<bool>     _morphTerrain;
         optional<bool>     _morphImagery;
         optional<int>      _mergesPerFrame;
+        std::vector<LODOptions> _lods;
     };
 
 } } } // namespace osgEarth::Drivers::RexTerrainEngine
diff --git a/src/osgEarthDrivers/engine_rex/SelectionInfo b/src/osgEarthDrivers/engine_rex/SelectionInfo
index 431f86e..e726a2f 100644
--- a/src/osgEarthDrivers/engine_rex/SelectionInfo
+++ b/src/osgEarthDrivers/engine_rex/SelectionInfo
@@ -20,6 +20,7 @@
 #define OSGEARTH_DRIVERS_REX_TERRAIN_ENGINE_SELECTION_INFO 1
 
 #include "Common"
+#include <osgEarth/Profile>
 #include <vector>
 
 
@@ -32,27 +33,27 @@ namespace osgEarth { namespace Drivers { namespace RexTerrainEngine
         double _fMorphStart;
         double _fMorphEnd;
     };
+
+    /**
+     * SelectionInfo is a data structure that holds the LOD distance switching 
+     * information for the terrain, to support paging and LOD morphing. 
+     * This is calculated once when the terrain is first created.
+     */
     class SelectionInfo
     {
     public:
         SelectionInfo() : _numLods(0), _uiFirstLOD(0) { }
 
         bool initialized(void) const;
-        void initialize(unsigned uiFirstLOD, unsigned uiMaxLod, unsigned uiTileSize, double fLodFar);
-
-        unsigned numLods(void) const;
-
-        // Dimensions of the skeleton grid in X
-        unsigned gridDimX(void) const;
+        void initialize(unsigned uiFirstLod, unsigned uiMaxLod, const Profile* profile, double mtrf);
 
-        // Dimensions of the skeleton grid in Y
-        unsigned gridDimY(void) const;
+        unsigned getNumLODs(void) const { return _numLods; }
 
         // Morph start ratio
-        static double   morphStartRatio(void);
+        static double getMorphStartRatio(void) { return _fMorphStartRatio; }
 
         // LOD at which morphing should start
-        static unsigned lodForMorphing(bool isProjected);
+        static unsigned getLODForMorphing(bool isProjected);
 
         VisParameters visParameters(unsigned lod) const;
     private:
@@ -60,7 +61,6 @@ namespace osgEarth { namespace Drivers { namespace RexTerrainEngine
         std::pair<unsigned, unsigned>   _uiGridDimensions; 
         std::vector<VisParameters>      _vecVisParams;
         unsigned                        _uiFirstLOD;
-
         static const double             _fMorphStartRatio; 
 
         // This is the lod at which morphing should start (used by shader)
@@ -71,7 +71,7 @@ namespace osgEarth { namespace Drivers { namespace RexTerrainEngine
         // hence VisParameters) are not generated
         static const double             _fLodLowerBound;
     };
-}
-}
-}
+
+} } } // namespace
+
 #endif
diff --git a/src/osgEarthDrivers/engine_rex/SelectionInfo.cpp b/src/osgEarthDrivers/engine_rex/SelectionInfo.cpp
index 76560ce..1b52899 100644
--- a/src/osgEarthDrivers/engine_rex/SelectionInfo.cpp
+++ b/src/osgEarthDrivers/engine_rex/SelectionInfo.cpp
@@ -1,7 +1,27 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+* Copyright 2008-2014 Pelican Mapping
+* http://osgearth.org
+*
+* osgEarth is free software; you can redistribute it and/or modify
+* it under the terms of the GNU Lesser General Public License as published by
+* the Free Software Foundation; either version 2 of the License, or
+* (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+* GNU Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public License
+* along with this program.  If not, see <http://www.gnu.org/licenses/>
+*/
 #include "SelectionInfo"
 #include "SurfaceNode"
 #include "RexTerrainEngineOptions"
 
+#include <osgEarth/Profile>
+
 using namespace osgEarth::Drivers::RexTerrainEngine;
 using namespace osgEarth;
 
@@ -11,31 +31,11 @@ const unsigned SelectionInfo::_uiLODForMorphingRoundEarth = 0;
 const double   SelectionInfo::_fLodLowerBound   = 12.0;
 const double   SelectionInfo::_fMorphStartRatio = 0.66;
 
-unsigned SelectionInfo::lodForMorphing(bool isProjected)
+unsigned SelectionInfo::getLODForMorphing(bool isProjected)
 {
     return (isProjected) ? 0 : _uiLODForMorphingRoundEarth;
 }
 
-double SelectionInfo::morphStartRatio(void) 
-{
-    return _fMorphStartRatio;
-}
-
-unsigned SelectionInfo::numLods(void) const
-{
-    return _numLods;
-}
-
-unsigned SelectionInfo::gridDimX(void) const
-{
-    return _uiGridDimensions.first;
-}
-
-unsigned SelectionInfo::gridDimY(void) const
-{
-    return _uiGridDimensions.second;
-}
-
 VisParameters SelectionInfo::visParameters(unsigned lod) const
 {
     if (lod-_uiFirstLOD>=_vecVisParams.size())
@@ -52,69 +52,45 @@ bool SelectionInfo::initialized(void) const
     return _vecVisParams.size()>0;
 }
 
-void SelectionInfo::initialize(unsigned uiFirstLOD, unsigned uiMaxLod, unsigned uiTileSize, double fLodFar)
+void SelectionInfo::initialize(unsigned uiFirstLod, unsigned uiMaxLod, const Profile* profile, double mtrf)
 {
     if (initialized())
     {
         OE_INFO << LC <<"Error: Selection Information already initialized"<<std::endl;
         return;
     }
-
-    if (fLodFar<0)
-    {
-        OE_INFO << LC <<"Error: Invalid fLodFar hint"<<std::endl;
-        return;
-    }
-    if (uiFirstLOD>uiMaxLod)
+    if (uiFirstLod>uiMaxLod)
     {
         OE_INFO << LC <<"Error: Inconsistent First and Max LODs"<<std::endl;
         return;
     }
-    _uiGridDimensions.first  = uiTileSize;
-    _uiGridDimensions.second = uiTileSize;
 
-    _uiFirstLOD = uiFirstLOD;
+    _uiFirstLOD = uiFirstLod;
 
     double fLodNear = 0;
     float fRatio = 1.0;
 
-    unsigned currLOD = _uiFirstLOD;
-    while(currLOD<=uiMaxLod)
-    {
-        double fVisibility = fLodNear + fRatio*(fLodFar-fLodNear);
-        if (fVisibility<_fLodLowerBound)
-        {
-            break;
-        }
-        fRatio*= 0.5;
-        ++currLOD;
-    }
+    _numLods = uiMaxLod+1u; // - uiFirstLod;
 
-    _numLods = currLOD-_uiFirstLOD;
-
-    fLodNear = 0;
-    fRatio = 1.0;
     _vecVisParams.resize(_numLods);
-    for( int i = 0; i < (int)_numLods; ++i )
+
+    for (unsigned lod = 0; lod <= uiMaxLod; ++lod)
     {
-        _vecVisParams[i]._visibilityRange = fLodNear + fRatio*(fLodFar-fLodNear);
-        _vecVisParams[i]._visibilityRange2 = _vecVisParams[i]._visibilityRange * _vecVisParams[i]._visibilityRange;
-        fRatio *= 0.5;
-    }
+        TileKey key(lod, 0, 0, profile);
+        GeoExtent e = key.getExtent();
+        GeoCircle c = e.computeBoundingGeoCircle();
+        double range = c.getRadius() * mtrf * 2.0;
 
+        _vecVisParams[lod]._visibilityRange = range;
+        _vecVisParams[lod]._visibilityRange2 = range*range;
+    }
+    
+    fLodNear = 0;
     double fPrevPos = fLodNear;
     for (int i=(int)(_numLods-1); i>=0; --i)
     {
         _vecVisParams[i]._fMorphEnd   = _vecVisParams[i]._visibilityRange;
         _vecVisParams[i]._fMorphStart = fPrevPos + (_vecVisParams[i]._fMorphEnd - fPrevPos) * _fMorphStartRatio;
-
         fPrevPos = _vecVisParams[i]._fMorphStart;
     }
-    for( int i = 0; i < (int)_numLods; ++i ) 
-    {
-        OE_DEBUG << "LOD[" << i+_uiFirstLOD<<"] = "<<_vecVisParams[i]._visibilityRange
-                 <<" Start: "<<_vecVisParams[i]._fMorphStart
-                 <<" End  : "<<_vecVisParams[i]._fMorphEnd
-                 <<std::endl;
-    }
-}
\ No newline at end of file
+}
diff --git a/src/osgEarthDrivers/engine_rex/Shaders b/src/osgEarthDrivers/engine_rex/Shaders
index 07d38bc..4543ae3 100644
--- a/src/osgEarthDrivers/engine_rex/Shaders
+++ b/src/osgEarthDrivers/engine_rex/Shaders
@@ -28,6 +28,7 @@ namespace osgEarth { namespace Drivers { namespace RexTerrainEngine
         std::string
             ENGINE_VERT_MODEL,
             ENGINE_VERT_VIEW,
+            ENGINE_ELEVATION_MODEL,
             ENGINE_FRAG,
             ENGINE_GEOM,
             NORMAL_MAP_VERT,
diff --git a/src/osgEarthDrivers/engine_rex/Shaders.cpp.in b/src/osgEarthDrivers/engine_rex/Shaders.cpp.in
index ad3fa04..658d896 100644
--- a/src/osgEarthDrivers/engine_rex/Shaders.cpp.in
+++ b/src/osgEarthDrivers/engine_rex/Shaders.cpp.in
@@ -9,6 +9,9 @@ Shaders::Shaders()
     ENGINE_VERT_MODEL = "RexEngine.vert.glsl";
     _sources[ENGINE_VERT_MODEL] = "@RexEngine.vert.glsl@";
 
+	ENGINE_ELEVATION_MODEL = "RexEngine.elevation.glsl";
+    _sources[ENGINE_ELEVATION_MODEL] = "@RexEngine.elevation.glsl@";
+
     MORPHING_VERT = "RexEngine.Morphing.vert.glsl";
     _sources[MORPHING_VERT] = "@RexEngine.Morphing.vert.glsl@";
 
diff --git a/src/osgEarthDrivers/engine_rex/SurfaceNode b/src/osgEarthDrivers/engine_rex/SurfaceNode
index 0f53209..fa762c5 100644
--- a/src/osgEarthDrivers/engine_rex/SurfaceNode
+++ b/src/osgEarthDrivers/engine_rex/SurfaceNode
@@ -23,6 +23,7 @@
 #include "GeometryPool"
 #include "RenderBindings"
 #include "TileDrawable"
+#include "TileRenderModel"
 
 #include <osgEarth/MapInfo>
 #include <osgEarth/Horizon>
@@ -53,7 +54,8 @@ namespace osgEarth { namespace Drivers { namespace RexTerrainEngine
 
 
     /**
-     * SurfaceNode holds the geometry of the terrain surface.
+     * SurfaceNode holds the geometry and transform information
+     * for one terrain tile surface.
      */
     class SurfaceNode : public osg::MatrixTransform
     {
@@ -72,8 +74,8 @@ namespace osgEarth { namespace Drivers { namespace RexTerrainEngine
         
         TileDrawable* getDrawable() const { return _drawable.get(); }
 
-        inline bool isVisible(osgUtil::CullVisitor* cv) const {
-            return _horizonCuller.isVisible(cv->getViewPointLocal());
+        inline bool isVisibleFrom(const osg::Vec3& viewpoint) const {
+            return _horizonCuller.isVisible(viewpoint);
         }
         
         // A box can have 4 children. 
@@ -91,15 +93,14 @@ namespace osgEarth { namespace Drivers { namespace RexTerrainEngine
 
         void setDebugText(const std::string& strText);
 
+        osg::BoundingSphere computeBound() const;
+
     protected:
         virtual ~SurfaceNode() { }
 
-        TileKey                    _tileKey;
-        osg::BoundingBox           _bbox;
-        osg::ref_ptr<TileDrawable> _drawable;
-        osg::ref_ptr<osg::Geode>   _surfaceGeode;
-
-        osg::ref_ptr<osg::Geode>    _debugGeode;
+        TileKey                     _tileKey;
+        osg::ref_ptr<TileDrawable>  _drawable;
+        osg::ref_ptr<osg::Group>    _debugGeode;
         osg::ref_ptr<osgText::Text> _debugText;
         static const bool           _enableDebugNodes;
         
@@ -115,8 +116,6 @@ namespace osgEarth { namespace Drivers { namespace RexTerrainEngine
 
         typedef VectorPoints (ChildrenCorners) [4];
         ChildrenCorners _childrenCorners;
-
-        //osg::ref_ptr<osg::RefMatrix> _matrix;
     };
 
 } } } // namespace osgEarth::Drivers::RexTerrainEngine
diff --git a/src/osgEarthDrivers/engine_rex/SurfaceNode.cpp b/src/osgEarthDrivers/engine_rex/SurfaceNode.cpp
index b2c4591..9382b32 100644
--- a/src/osgEarthDrivers/engine_rex/SurfaceNode.cpp
+++ b/src/osgEarthDrivers/engine_rex/SurfaceNode.cpp
@@ -45,9 +45,9 @@ using namespace osgEarth;
 
 namespace
 {    
-    osg::Geode* makeBBox(const osg::BoundingBox& bbox, const TileKey& key)
+    osg::Group* makeBBox(const osg::BoundingBox& bbox, const TileKey& key)
     {        
-        osg::Geode* geode = new osg::Geode();
+        osg::Group* geode = new osg::Group();
         std::string sizeStr = "(empty)";
         float zpos = 0.0f;
 
@@ -91,7 +91,7 @@ namespace
             geom->setColorArray(c);
             geom->setColorBinding(geom->BIND_OVERALL);
 
-            geode->addDrawable(geom);
+            geode->addChild(geom);
 
             sizeStr = Stringify() << key.str() << "\nmax="<<bbox.zMax()<<"\nmin="<<bbox.zMin()<<"\n";
             zpos = bbox.zMax();
@@ -109,7 +109,7 @@ namespace
         textDrawable->setBackdropType(textDrawable->OUTLINE);
         textDrawable->setPosition(osg::Vec3(0,0,zpos));
         textDrawable->setAutoRotateToScreen(true);
-        geode->addDrawable(textDrawable);
+        geode->addChild(textDrawable);
 
         geode->getOrCreateStateSet()->setAttributeAndModes(new osg::Program(),0);
         geode->getOrCreateStateSet()->setMode(GL_LIGHTING,0);
@@ -118,7 +118,7 @@ namespace
         return geode;
     }
 
-    osg::Geode* makeSphere(const osg::BoundingSphere& bs)
+    osg::Drawable* makeSphere(const osg::BoundingSphere& bs)
     {
         osg::Geometry* geom = new osg::Geometry();
         geom->setUseVertexBufferObjects(true);
@@ -166,10 +166,7 @@ namespace
         geom->setColorArray(c);
         geom->setColorBinding(osg::Geometry::BIND_OVERALL);
 
-        osg::Geode* geode = new osg::Geode();
-        geode->addDrawable(geom);
-
-        return geode;
+        return geom;
     }
 }
 
@@ -188,9 +185,6 @@ HorizonTileCuller::set(const SpatialReference* srs,
     if (_horizon.valid())
     {
         _horizon->setEllipsoid(*srs->getEllipsoid());
-        //_radiusPolar = srs->getEllipsoid()->getRadiusPolar();
-        //_radiusEquator = srs->getEllipsoid()->getRadiusEquator();
-        //_local2world = local2world;
 
         // Adjust the horizon ellipsoid based on the minimum Z value of the tile;
         // necessary because a tile that's below the ellipsoid (ocean floor, e.g.)
@@ -208,8 +202,6 @@ HorizonTileCuller::set(const SpatialReference* srs,
         {
             _points[i] = bbox.corner(4+i) * local2world;
         }
-
-        //_bs.set(bbox.center() * _local2world, bbox.radius());
     }
 }
 
@@ -243,11 +235,8 @@ SurfaceNode::SurfaceNode(const TileKey&        tilekey,
 
     _drawable = drawable;
 
-    _surfaceGeode = new osg::Geode();
-    _surfaceGeode->addDrawable( drawable );
-    
     // Create the final node.
-    addChild( _surfaceGeode.get() );
+    addChild(_drawable.get());
 
     // Establish a local reference frame for the tile:
     GeoPoint centroid;
@@ -261,6 +250,19 @@ SurfaceNode::SurfaceNode(const TileKey&        tilekey,
     setElevationRaster( 0L, osg::Matrixf::identity() );
 }
 
+osg::BoundingSphere
+SurfaceNode::computeBound() const
+{
+    osg::Matrix l2w;
+    computeLocalToWorldMatrix(l2w, 0L);
+    osg::BoundingSphere bs;
+    osg::BoundingBox box = _drawable->getBoundingBox();
+    for (unsigned i=0; i<8; ++i)
+        bs.expandBy(box.corner(i)*l2w);
+
+    return bs;
+}
+
 void
 SurfaceNode::setElevationRaster(const osg::Image*   raster,
                                 const osg::Matrixf& scaleBias)
@@ -383,7 +385,6 @@ SurfaceNode::addDebugNode(const osg::BoundingBox& box)
 {
     _debugText = 0;
     _debugGeode = makeBBox(box, _tileKey);
-    //_debugGeode = makeSphere(this->getBound());
     addChild( _debugGeode.get() );
 }
 
diff --git a/src/osgEarthDrivers/engine_rex/TerrainCuller b/src/osgEarthDrivers/engine_rex/TerrainCuller
new file mode 100644
index 0000000..ebacc2f
--- /dev/null
+++ b/src/osgEarthDrivers/engine_rex/TerrainCuller
@@ -0,0 +1,94 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+ * Copyright 2008-2014 Pelican Mapping
+ * http://osgearth.org
+ *
+ * osgEarth is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+#ifndef OSGEARTH_REX_TERRAIN_CULLER_H
+#define OSGEARTH_REX_TERRAIN_CULLER_H 1
+
+#include "EngineContext"
+#include "TerrainRenderData"
+
+#include <osgEarth/MapFrame>
+
+#include <osg/NodeVisitor>
+#include <osgUtil/CullVisitor>
+
+
+using namespace osgEarth;
+
+namespace osgEarth { namespace Drivers { namespace RexTerrainEngine
+{
+    struct LayerExtent
+    {
+        LayerExtent() : _computed(false) { }
+        bool _computed;
+        GeoExtent _extent;
+    };
+    typedef std::vector<LayerExtent> LayerExtentVector;
+
+    /**
+     * Node visitor responsible for assembling a TerrainRenderData that 
+     * contains all the information necessary to render the terrain.
+     */
+    class TerrainCuller : public osg::NodeVisitor, public osg::CullStack
+    {
+    public:
+        const MapFrame* _frame;
+        TerrainRenderData _terrain;
+        EngineContext* _context;
+        osg::Camera* _camera;
+        TileNode* _currentTileNode;
+        unsigned _currentTileDrawCommands;
+        DrawTileCommand* _firstTileDrawCommandForTile;
+        unsigned _orphanedPassesDetected;
+        osgUtil::CullVisitor* _cv;
+        LayerExtentVector* _layerExtents;
+
+    public:
+        /** A new terrain culler */
+        TerrainCuller(osgUtil::CullVisitor* cullVisitor, EngineContext* context);
+
+        /** Initialize the culler with a map and a set of render bindings. */
+        void setup(const MapFrame& frame, LayerExtentVector& layerExtents, const RenderBindings& bindings);
+
+        /** The active camera */
+        osg::Camera* getCamera() { return _camera; }
+
+        /** Access to terrain engine resources */
+        EngineContext* getEngineContext() { return _context; }
+
+        /** The CullVIsitor that parents this culler. */
+        osgUtil::CullVisitor& getParent() { return *_cv; }
+
+    public: // osg::NodeVisitor
+        void apply(osg::Node& node);
+        
+        float getDistanceToViewPoint(const osg::Vec3& pos, bool withLODScale) const;
+
+    private:
+
+        DrawTileCommand* addDrawCommand(
+            UID sourceUID, 
+            const TileRenderModel* model, 
+            const RenderingPass* pass, 
+            TileNode* node);
+
+    };
+
+} } } // namespace 
+
+#endif // OSGEARTH_REX_TERRAIN_CULLER_H
diff --git a/src/osgEarthDrivers/engine_rex/TerrainCuller.cpp b/src/osgEarthDrivers/engine_rex/TerrainCuller.cpp
new file mode 100644
index 0000000..f00d14a
--- /dev/null
+++ b/src/osgEarthDrivers/engine_rex/TerrainCuller.cpp
@@ -0,0 +1,274 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+ * Copyright 2008-2014 Pelican Mapping
+ * http://osgearth.org
+ *
+ * osgEarth is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+#include "TerrainCuller"
+#include "TileNode"
+#include "SurfaceNode"
+
+#define LC "[TerrainCuller] "
+
+using namespace osgEarth::Drivers::RexTerrainEngine;
+
+
+TerrainCuller::TerrainCuller(osgUtil::CullVisitor* cullVisitor, EngineContext* context) :
+_frame(0L),
+_camera(0L),
+_currentTileNode(0L),
+_orphanedPassesDetected(0u),
+_cv(cullVisitor),
+_context(context)
+{
+    setVisitorType(CULL_VISITOR);
+    setTraversalMode(TRAVERSE_ALL_CHILDREN);
+    setCullingMode(cullVisitor->getCullingMode());
+
+    setFrameStamp(new osg::FrameStamp(*_cv->getFrameStamp()));
+    setDatabaseRequestHandler(_cv->getDatabaseRequestHandler());
+    pushReferenceViewPoint(_cv->getReferenceViewPoint());
+    pushViewport(_cv->getViewport());
+    pushProjectionMatrix(_cv->getProjectionMatrix());
+    pushModelViewMatrix(_cv->getModelViewMatrix(), _cv->getCurrentCamera()->getReferenceFrame());
+    _camera = _cv->getCurrentCamera();
+}
+
+void
+TerrainCuller::setup(const MapFrame& frame, LayerExtentVector& layerExtents, const RenderBindings& bindings)
+{
+    unsigned frameNum = getFrameStamp() ? getFrameStamp()->getFrameNumber() : 0u;
+    _layerExtents = &layerExtents;
+    _terrain.setup(frame, bindings, frameNum, _cv);
+}
+
+float
+TerrainCuller::getDistanceToViewPoint(const osg::Vec3& pos, bool withLODScale) const
+{
+    if (withLODScale) return (pos-getViewPointLocal()).length()*getLODScale();
+    else return (pos-getViewPointLocal()).length();
+}
+
+DrawTileCommand*
+TerrainCuller::addDrawCommand(UID uid, const TileRenderModel* model, const RenderingPass* pass, TileNode* tileNode)
+{
+    SurfaceNode* surface = tileNode->getSurfaceNode();
+
+    const RenderBindings& bindings = _context->getRenderBindings();
+
+    // skip layers that are not visible:
+    if (pass && 
+        pass->visibleLayer() && 
+        pass->visibleLayer()->getVisible() == false)
+    {
+        //OE_DEBUG << LC << "Skipping " << pass->visibleLayer()->getName() << " because it's not visible." << std::endl;
+        return 0L;
+    }
+
+    // add a new Draw command to the appropriate layer
+    osg::ref_ptr<LayerDrawable> drawable = _terrain.layer(uid);
+    if (drawable.valid())
+    {
+        // Cull based on the layer extent.
+        if (drawable->_layer)
+        {
+            const LayerExtent& le = (*_layerExtents)[drawable->_layer->getUID()];
+            if (le._computed && 
+                le._extent.isValid() &&
+                le._extent.intersects(tileNode->getKey().getExtent()) == false)
+            {
+                // culled out!
+                //OE_DEBUG << LC << "Skippping " << drawable->_layer->getName() 
+                //    << " key " << tileNode->getKey().str()
+                //    << " because it was culled by extent." << std::endl;
+                return 0L;
+            }
+        }
+
+        drawable->_tiles.push_back(DrawTileCommand());
+        DrawTileCommand* tile = &drawable->_tiles.back();
+
+        // install everything we need in the Draw Command:
+        tile->_colorSamplers = pass ? &(pass->samplers()) : 0L;
+        tile->_sharedSamplers = &model->_sharedSamplers;
+        tile->_modelViewMatrix = this->getModelViewMatrix();
+        tile->_keyValue = tileNode->getTileKeyValue();
+        tile->_geom = surface->getDrawable()->_geom.get();
+        tile->_morphConstants = tileNode->getMorphConstants();
+        tile->_key = &tileNode->getKey();
+        //tile->_order = (int)orderInTile;
+        tile->_order = drawable->_order; // layer order in map tile.
+
+        osg::Vec3 c = surface->getBound().center() * surface->getInverseMatrix();
+        tile->_range = getDistanceToViewPoint(c, true);
+
+        const osg::Image* elevRaster = tileNode->getElevationRaster();
+        if (elevRaster)
+        {
+            float bias = _context->getUseTextureBorder() ? 1.5 : 0.5;
+
+            // Compute an elevation texture sampling scale/bias so we sample elevation data on center
+            // instead of on edge (as we do with color, etc.)
+            //
+            // This starts out as:
+            //   scale = (size-1)/size : this shrinks the sample area by one texel since we're sampling on center
+            //   bias = 0.5/size : this shifts the sample area over 1/2 texel to the center.
+            //
+            // But, since we also have a 1-texel border, we need to further reduce the scale by 2 texels to
+            // remove the border, and shift an extra texel over as well. Giving us this:
+            float size = (float)elevRaster->s();
+            tile->_elevTexelCoeff.set((size - (2.0*bias)) / size, bias / size);
+        }
+
+        return tile;
+    }
+    else if (pass)
+    {
+        // The pass exists but it's layer is not in the render data draw list.
+        // This means that the layer is no longer in the map. Detect and record
+        // this information so we can run a cleanup visitor later on.
+        ++_orphanedPassesDetected;
+    }
+    
+    return 0L;
+}
+
+void
+TerrainCuller::apply(osg::Node& node)
+{
+    // push the node's state.
+    osg::StateSet* node_state = node.getStateSet();
+
+    TileNode* tileNode = dynamic_cast<TileNode*>(&node);
+    if (tileNode)
+    {
+        _currentTileNode = tileNode;
+
+        // reset the pointer to the first DrawTileCommand. We keep track of this so
+        // we can set it's "order" member to zero at the end, so the rendering engine
+        // knows to blend it with the terrain geometry color.
+        _firstTileDrawCommandForTile = 0L;
+
+        //_currentTileDrawCommands = 0u;
+        
+        if (!_terrain.patchLayers().empty())
+        {
+            // todo: check for patch/virtual
+            const RenderBindings& bindings = _context->getRenderBindings();
+            TileRenderModel& renderModel = _currentTileNode->renderModel();
+
+            bool pushedMatrix = false;
+            
+            for (PatchLayerVector::const_iterator i = _terrain.patchLayers().begin(); i != _terrain.patchLayers().end(); ++i)
+            {
+                PatchLayer* layer = i->get();
+                if (layer->getAcceptCallback() == 0L ||
+                    layer->getAcceptCallback()->acceptKey(_currentTileNode->getKey()))
+                {
+                    // Push this tile's matrix if we haven't already done so:
+                    if (!pushedMatrix)
+                    {
+                        SurfaceNode* surface = tileNode->getSurfaceNode();
+
+                        // push the surface matrix:
+                        osg::Matrix mvm = *getModelViewMatrix();
+                        surface->computeLocalToWorldMatrix(mvm, this);
+                        pushModelViewMatrix(createOrReuseMatrix(mvm), surface->getReferenceFrame());
+                        pushedMatrix = true;
+                    }
+
+                    // Add the draw command:
+                    DrawTileCommand* cmd = addDrawCommand(layer->getUID(), &renderModel, 0L, tileNode);
+                    if (cmd)
+                    {
+                        cmd->_drawPatch = true;
+                        cmd->_drawCallback = layer->getDrawCallback();
+                    }
+                }
+            }
+
+            if (pushedMatrix)
+            {
+                popModelViewMatrix();
+            }
+        }
+    }
+
+    else
+    {
+        SurfaceNode* surface = dynamic_cast<SurfaceNode*>(&node);
+        if (surface)
+        {
+            TileRenderModel& renderModel = _currentTileNode->renderModel();
+
+            // push the surface matrix:
+            osg::Matrix mvm = *getModelViewMatrix();
+            surface->computeLocalToWorldMatrix(mvm, this);
+            pushModelViewMatrix(createOrReuseMatrix(mvm), surface->getReferenceFrame());
+
+            int order = 0;
+
+            // First go through any legit rendering pass data in the Tile and
+            // and add a DrawCommand for each.
+            for (unsigned p = 0; p < renderModel._passes.size(); ++p)
+            {
+                const RenderingPass& pass = renderModel._passes[p];
+                DrawTileCommand* cmd = addDrawCommand(pass.sourceUID(), &renderModel, &pass, _currentTileNode);
+                if (cmd)
+                {
+                    if (_firstTileDrawCommandForTile == 0L)
+                    {
+                        _firstTileDrawCommandForTile = cmd;
+                    }
+                    else if (cmd->_order < _firstTileDrawCommandForTile->_order)
+                    {
+                        //_firstTileDrawCommandForTile->_order = 1;
+                        _firstTileDrawCommandForTile = cmd;
+                    }
+                }
+            }
+
+            // If the culler added no draw commands for this tile... we still need
+            // to draw something or else there will be a hole! So draw a blank tile.
+            // UID = -1 is the special UID code for a blank.
+            if (_firstTileDrawCommandForTile == 0L)
+            {
+                //OE_INFO << LC << "Adding blank render for tile " << _currentTileNode->getKey().str() << std::endl;
+                DrawTileCommand* cmd = addDrawCommand(-1, &renderModel, 0L, _currentTileNode);
+                if (cmd)
+                    cmd->_order = 0;
+            }
+
+            // Set the layer order of the first draw command for this tile to zero,
+            // to support proper terrain blending.
+            if (_firstTileDrawCommandForTile)
+            {
+                _firstTileDrawCommandForTile->_order = 0;
+            }
+                
+            // pop the matrix from the cull stack
+            popModelViewMatrix();
+
+            // update our bounds
+            _terrain._drawState->_bs.expandBy(surface->getBound());
+            _terrain._drawState->_box.expandBy(_terrain._drawState->_bs);
+        }
+    }
+
+    // Handle any CullCallbacks and traverse.
+    osg::Callback* cullCallback = node.getCullCallback();
+    if (cullCallback) cullCallback->run(&node, this);
+    else traverse(node);
+}
diff --git a/src/osgEarthDrivers/engine_rex/TerrainRenderData b/src/osgEarthDrivers/engine_rex/TerrainRenderData
new file mode 100644
index 0000000..1eda2ff
--- /dev/null
+++ b/src/osgEarthDrivers/engine_rex/TerrainRenderData
@@ -0,0 +1,75 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+ * Copyright 2008-2014 Pelican Mapping
+ * http://osgearth.org
+ *
+ * osgEarth is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+#ifndef OSGEARTH_REX_TERRAIN_DRAWABLE_H
+#define OSGEARTH_REX_TERRAIN_DRAWABLE_H 1
+
+#include "RenderBindings"
+#include "DrawState"
+#include "LayerDrawable"
+
+#include <osgEarth/MapFrame>
+#include <osg/StateSet>
+
+using namespace osgEarth;
+
+namespace osgEarth { namespace Drivers { namespace RexTerrainEngine
+{
+    /**
+     * Main data structure assembled by the TerrainCuller that contains
+     * everything necessary to render one frame of the terrain.
+     */
+    class TerrainRenderData
+    {
+    public:
+        TerrainRenderData() :
+            _bindings(0L) { }
+
+        /** Set up the map layers before culling the terrain */
+        void setup(const MapFrame& frame, const RenderBindings& bindings, unsigned frameNum, osgUtil::CullVisitor* cv);
+
+        /** Optimize for best state sharing (when using geometry pooling) */
+        void sortDrawCommands();
+
+        /** Add a Drawable for a layer. Add these in the order you wish to render them. */
+        LayerDrawable* addLayerDrawable(const Layer*, const MapFrame& frame);
+
+        /** Layers to draw */
+        LayerDrawableList& layers() { return _layerList; }
+        const LayerDrawableList& layers() const { return _layerList; }
+
+        /** Look up a LayerDrawable by its source layer UID. */
+        osg::ref_ptr<LayerDrawable>& layer(UID uid) { return _layerMap[uid]; }
+
+        // Draw state shared by all layers during one frame.
+        osg::ref_ptr<DrawState> _drawState;
+
+        // Layers of type RENDERTYPE_PATCH
+        PatchLayerVector& patchLayers() { return _patchLayers; }
+        
+    private:
+
+        LayerDrawableList     _layerList;
+        LayerDrawableMap      _layerMap;
+        const RenderBindings* _bindings;
+        PatchLayerVector      _patchLayers;
+    };
+
+} } } // namespace 
+
+#endif // OSGEARTH_REX_TERRAIN_DRAWABLE_H
diff --git a/src/osgEarthDrivers/engine_rex/TerrainRenderData.cpp b/src/osgEarthDrivers/engine_rex/TerrainRenderData.cpp
new file mode 100644
index 0000000..3015f8b
--- /dev/null
+++ b/src/osgEarthDrivers/engine_rex/TerrainRenderData.cpp
@@ -0,0 +1,131 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+ * Copyright 2008-2014 Pelican Mapping
+ * http://osgearth.org
+ *
+ * osgEarth is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+#include "TerrainRenderData"
+#include "TileNode"
+#include "SurfaceNode"
+
+using namespace osgEarth::Drivers::RexTerrainEngine;
+
+#undef  LC
+#define LC "[TerrainRenderData] "
+
+
+void
+TerrainRenderData::sortDrawCommands()
+{
+    for (LayerDrawableList::iterator i = _layerList.begin(); i != _layerList.end(); ++i)
+    {
+        i->get()->_tiles.sort();
+    }
+}
+
+void
+TerrainRenderData::setup(const MapFrame& frame,
+                         const RenderBindings& bindings,
+                         unsigned frameNum,
+                         osgUtil::CullVisitor* cv)
+{
+    _bindings = &bindings;
+
+    // Create a new State object to track sampler and uniform settings
+    _drawState = new DrawState();
+    _drawState->_frame = frameNum;
+    _drawState->_bindings = &bindings;
+
+    // Make a drawable for each rendering pass (i.e. each render-able map layer).
+    for(LayerVector::const_iterator i = frame.layers().begin();
+        i != frame.layers().end();
+        ++i)
+    {
+        Layer* layer = i->get();
+        if (layer->getEnabled())
+        {
+            if (layer->getRenderType() == Layer::RENDERTYPE_TILE ||
+                layer->getRenderType() == Layer::RENDERTYPE_PATCH)
+            {
+                bool render = true;
+
+                // If this is an image layer, check the enabled/visible states.
+                VisibleLayer* visLayer = dynamic_cast<VisibleLayer*>(layer);
+                if (visLayer)
+                {
+                    render = visLayer->getVisible();
+                }
+
+                if (render)
+                {
+                    if (layer->getRenderType() == Layer::RENDERTYPE_PATCH)
+                    {
+                        PatchLayer* patchLayer = static_cast<PatchLayer*>(layer); // asumption!
+
+                        if (patchLayer->getAcceptCallback() != 0L &&
+                            patchLayer->getAcceptCallback()->acceptLayer(*cv, cv->getCurrentCamera()))
+                        {
+                            patchLayers().push_back(dynamic_cast<PatchLayer*>(layer));
+                            addLayerDrawable(layer, frame);
+                        }
+                    }
+                    else
+                    {
+                        addLayerDrawable(layer, frame);
+                    }
+                }
+            }
+        }
+    }
+
+    // Include a "blank" layer for missing data.
+    LayerDrawable* blank = addLayerDrawable(0L, frame);
+    blank->getOrCreateStateSet()->setDefine("OE_TERRAIN_RENDER_IMAGERY", osg::StateAttribute::OFF);
+}
+
+namespace
+{
+    struct DebugCallback : public osg::Drawable::DrawCallback
+    {
+        std::string _s;
+        DebugCallback(const std::string& s) : _s(s) { }
+        void drawImplementation(osg::RenderInfo& ri, const osg::Drawable* d) const {
+            OE_WARN << "  Drawing Layer: " << _s << std::endl;
+            d->drawImplementation(ri);
+        }
+
+    };
+}
+
+LayerDrawable*
+TerrainRenderData::addLayerDrawable(const Layer* layer, const MapFrame& frame)
+{
+    UID uid = layer ? layer->getUID() : -1;
+    LayerDrawable* ld = new LayerDrawable();
+    _layerList.push_back(ld);
+    _layerMap[uid] = ld;
+    ld->_layer = layer;
+    ld->_visibleLayer = dynamic_cast<const VisibleLayer*>(layer);
+    ld->_imageLayer = dynamic_cast<const ImageLayer*>(layer);
+    ld->_order = _layerList.size() - 1;
+    ld->_drawState = _drawState.get();
+    if (layer)
+    {
+        ld->setStateSet(layer->getStateSet());
+        ld->_renderType = layer->getRenderType();
+    }
+    //ld->setDrawCallback(new DebugCallback(layer ? layer->getName() : "[blank]"));
+    return ld;
+}
diff --git a/src/osgEarthDrivers/engine_rex/TileDrawable b/src/osgEarthDrivers/engine_rex/TileDrawable
index e4d8543..7c6cecb 100644
--- a/src/osgEarthDrivers/engine_rex/TileDrawable
+++ b/src/osgEarthDrivers/engine_rex/TileDrawable
@@ -20,122 +20,100 @@
 #define OSGEARTH_DRIVERS_REX_TERRAIN_ENGINE_TILE_DRAWABLE 1
 
 #include "Common"
-#include "TileNode"
-#include "TileNodeRegistry"
-#include "RenderBindings"
-#include "MPTexture"
-
+#include "TileRenderModel"
+#include "GeometryPool"
 #include <osg/Geometry>
-#include <osg/buffered_value>
-#include <osgEarth/Map>
+#include <osg/Image>
+#include <osg/Matrixf>
+#include <osgEarth/TileKey>
 #include <osgEarth/MapFrame>
 
 using namespace osgEarth;
 
 namespace osgEarth { namespace Drivers { namespace RexTerrainEngine
 {
+    struct ModifyBoundingBoxCallback : public osg::Referenced
+    {
+        ModifyBoundingBoxCallback(const MapFrame& frame) : _frame(frame) { }
+
+        mutable Threading::Mutex _mutex;
+        mutable MapFrame _frame;
+
+        void operator()(const TileKey& key, osg::BoundingBox& bbox) const
+        {
+            if (_frame.needsSync())
+            {
+                Threading::ScopedMutexLock lock(_mutex);
+                _frame.sync();
+            }
+
+            for (LayerVector::const_iterator layer = _frame.layers().begin();
+                layer != _frame.layers().end();
+                ++layer)
+            {
+                if (layer->valid())
+                {
+                    layer->get()->modifyTileBoundingBox(key, bbox);
+                }
+            }
+        }
+    };
+
     /**
-     * A Geometry that will draw its primitive sets multiple time,
-     * once for each configured "layer". For rendering multipass
-     * data from a single cull.
+     * TileDrawable is an osg::Drawable that represents an individual terrain tile
+     * for the purposes of scene graph operations (like intersections, bounds
+     * computation, statistics, etc.)
+     * 
+     * NOTE: TileDrawable does not actually render anything!
+     * The TerrainRenderData object does all the rendering of tiles.
+     *
+     * Instead, it exposes various osg::Drawable Functors for traversing
+     * the terrain's geometry. It also hold a pointer to the tile's elevation
+     * raster so it can properly reflect the elevation data in the texture.
      */
     class TileDrawable : public osg::Drawable
     {
     public:
-        // uniform name IDs.
-        unsigned _uidUniformNameID;
-        unsigned _orderUniformNameID;
-        unsigned _opacityUniformNameID;
-        unsigned _texMatrixUniformNameID;
-        unsigned _texMatrixParentUniformNameID;
-        unsigned _texParentExistsUniformNameID;
-        unsigned _minRangeUniformNameID;
-        unsigned _maxRangeUniformNameID;
-
-        // Data stored for each graphics context:
-        struct PerContextData {
-            PerContextData() : birthTime(-1.0f), lastFrame(0) { }
-            float    birthTime;
-            unsigned lastFrame;
-        };
-        mutable osg::buffered_object<PerContextData> _pcd;
-
-        mutable osg::Vec4f _tileMorphCValue;
-        mutable osg::Vec4f _tileGridDimsValue;
-        mutable osg::Vec4f _tileExtentsValue;
-        RenderBindings _bindings;
-
-        bool _supportsGLSL;
-
-        // underlying geometry, possible shared between this tile and other.
-        osg::ref_ptr<osg::Geometry> _geom;
+        // underlying geometry, possibly shared between this tile and other.
+        osg::ref_ptr<SharedGeometry> _geom;
 
         // tile dimensions
         int _tileSize;
 
-        bool _drawPatch;
-
-        osg::ref_ptr<MPTexture> _mptex;
-
-        int _textureImageUnit;
-        int _textureParentImageUnit;
-
         const TileKey _key;
 
         osg::ref_ptr<const osg::Image> _elevationRaster;
         osg::Matrixf                   _elevationScaleBias;
 
-        int _skirtSize;
+        // cached 3D mesh of the terrain tile (derived from the elevation raster)
+        osg::Vec3f* _mesh;
+        GLuint* _meshIndices;
 
-        float* _heightCache;
+        ModifyBoundingBoxCallback* _bboxCB;
 
     public:
         
         // construct a new TileDrawable that fronts an osg::Geometry
         TileDrawable(
-            const TileKey&        key,
-            const RenderBindings& bindings,
-            osg::Geometry*        geometry,
-            int                   tileSize,
-            int                   skirtSize);
-
-        // whether to draw this as a multi-image geometry or as a tesselation patch.
-        void setDrawAsPatches(bool value) { _drawPatch = value; }
-
-        /** Sets the multipass texture to use when rendering geometry. */
-        void setMPTexture(MPTexture* tex) { _mptex = tex; }
+            const TileKey& key,
+            SharedGeometry* geometry,
+            int            tileSize);
 
     public:
 
-        void drawVertexArraysImplementation(osg::RenderInfo& renderInfo) const;
-
-        void drawPrimitivesImplementation(osg::RenderInfo& renderInfo) const;
-
-        void drawPatches(osg::RenderInfo& renderInfo) const;
-
-        void drawSurface(osg::RenderInfo& renderInfo, bool renderColor) const;
-
-        // validate the geometry is OK.
-        void validate();
-
         // Sets the elevation raster for this tile
         void setElevationRaster(const osg::Image* image, const osg::Matrixf& scaleBias);
-        const osg::Image* getElevationRaster() const;
-        const osg::Matrixf& getElevationMatrix() const;
 
-    public: // osg::Geometry overrides
+        const osg::Image* getElevationRaster() const {
+            return _elevationRaster.get();
+        }
 
-        // override so we can properly release the GL buffer objects
-        // that are not tracked by the Geometry itself but rather are
-        // stored in the LayerRenderData.
-        void releaseGLObjects(osg::State* state) const;
-        void resizeGLObjectBuffers(unsigned maxSize);
-        void compileGLObjects(osg::RenderInfo& renderInfo) const;
+        const osg::Matrixf& getElevationMatrix() const {
+            return _elevationScaleBias;
+        }
 
-        // this is copied mostly from osg::Geometry, but we've removed 
-        // all the code that handles non-fastPath (don't need it) and
-        // called into our custom renderPrimitiveSets method.
-        void drawImplementation(osg::RenderInfo& renderInfo) const;
+        // Set the render model so we can properly calculate bounding boxes
+        void setModifyBBoxCallback(ModifyBoundingBoxCallback* bboxCB) { _bboxCB = bboxCB; }
 
     public: // osg::Drawable overrides
 
@@ -153,6 +131,9 @@ namespace osgEarth { namespace Drivers { namespace RexTerrainEngine
         /** indexed functor is NOT supported since we need to apply elevation dynamically */
         bool supports(const osg::PrimitiveIndexFunctor& f) const { return false; }
 
+        osg::BoundingSphere computeBound() const;
+        osg::BoundingBox computeBoundingBox() const;
+
     public:
         META_Object(osgEarth, TileDrawable);
         TileDrawable() : osg::Drawable(){}
diff --git a/src/osgEarthDrivers/engine_rex/TileDrawable.cpp b/src/osgEarthDrivers/engine_rex/TileDrawable.cpp
index 9fd63f5..ee2b303 100644
--- a/src/osgEarthDrivers/engine_rex/TileDrawable.cpp
+++ b/src/osgEarthDrivers/engine_rex/TileDrawable.cpp
@@ -17,10 +17,8 @@
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
 */
 #include "TileDrawable"
-#include "MPTexture"
 
 #include <osg/Version>
-#include <osgUtil/MeshOptimizers>
 #include <iterator>
 #include <osgEarth/Registry>
 #include <osgEarth/Capabilities>
@@ -33,275 +31,45 @@ using namespace osgEarth;
 #define LC "[TileDrawable] "
 
 
-static Threading::Mutex _profMutex;
-static unsigned s_frame = 0;
-static unsigned s_draws = 0;
-static unsigned s_functors = 0;
-
-
-TileDrawable::TileDrawable(const TileKey&        key,
-                           const RenderBindings& bindings,
-                           osg::Geometry*        geometry,
-                           int                   tileSize,
-                           int                   skirtSize) :
+TileDrawable::TileDrawable(const TileKey& key,
+                           SharedGeometry* geometry,
+                           int            tileSize) :
 osg::Drawable( ),
 _key         ( key ),
-_bindings    ( bindings ),
 _geom        ( geometry ),
-_tileSize    ( tileSize ),
-_drawPatch   ( false ),
-_skirtSize   ( skirtSize )
-{
-    this->setDataVariance( DYNAMIC );
-
-    if (_geom.valid())
-        _geom->setDataVariance( DYNAMIC );
-
-    this->setName( key.str() );
-
-    setUseVertexBufferObjects( true );
-    setUseDisplayList( false );
-
-    _supportsGLSL = Registry::capabilities().supportsGLSL();
-
-    // establish uniform name IDs.
-    _uidUniformNameID             = osg::Uniform::getNameID( "oe_layer_uid" );
-    _orderUniformNameID           = osg::Uniform::getNameID( "oe_layer_order" );
-    _opacityUniformNameID         = osg::Uniform::getNameID( "oe_layer_opacity" );
-    _texMatrixUniformNameID       = osg::Uniform::getNameID( "oe_layer_texMatrix" );
-    _texMatrixParentUniformNameID = osg::Uniform::getNameID( "oe_layer_texParentMatrix" );
-    _texParentExistsUniformNameID = osg::Uniform::getNameID( "oe_layer_texParentExists" );
-    _minRangeUniformNameID        = osg::Uniform::getNameID( "oe_layer_minRange" );
-    _maxRangeUniformNameID        = osg::Uniform::getNameID( "oe_layer_maxRange" );
-
-    _textureImageUnit       = SamplerBinding::findUsage(bindings, SamplerBinding::COLOR)->unit();
-    _textureParentImageUnit = SamplerBinding::findUsage(bindings, SamplerBinding::COLOR_PARENT)->unit();
+_tileSize    ( tileSize )
+{   
+    // a mesh to materialize the heightfield for functors
+    _mesh = new osg::Vec3f[ tileSize*tileSize ];
     
-    int tileSize2 = tileSize*tileSize;
-    _heightCache = new float[ tileSize2 ];
-    for(int i=0; i<tileSize2; ++i)
-        _heightCache[i] = 0.0f;    
-}
-
-TileDrawable::~TileDrawable()
-{
-    delete [] _heightCache;
-}
-
-void
-TileDrawable::drawPrimitivesImplementation(osg::RenderInfo& renderInfo) const
-{
-    if ( _drawPatch )
-    {
-        drawPatches( renderInfo );
-    }
-    else
-    {
-        const osg::Camera* camera = renderInfo.getCurrentCamera();
-
-        bool renderColor =
-            //(camera->getRenderOrder() != osg::Camera::PRE_RENDER) ||
-            ((camera->getClearMask() & GL_COLOR_BUFFER_BIT) != 0L);
-
-        drawSurface( renderInfo, renderColor );
-    }
-}
-
-
-void
-TileDrawable::drawPatches(osg::RenderInfo& renderInfo) const
-{
-    if  (!_geom.valid() || _geom->getNumPrimitiveSets() < 1 )
-        return;
-
-    osg::State& state = *renderInfo.getState(); 
+    // allocate and prepopulate mesh index array. 
+    // TODO: This is the same for all tiles (of the same tilesize)
+    // so perhaps in the future we can just share it.
+    _meshIndices = new GLuint[ (tileSize-1)*(tileSize-1)*6 ];
     
-    const osg::DrawElementsUShort* de = static_cast<osg::DrawElementsUShort*>(_geom->getPrimitiveSet(0));
-    osg::GLBufferObject* ebo = de->getOrCreateGLBufferObject(state.getContextID());
-    state.bindElementBufferObject(ebo);
-    if (ebo)
-        glDrawElements(GL_PATCHES, de->size()-_skirtSize, GL_UNSIGNED_SHORT, (const GLvoid *)(ebo->getOffset(de->getBufferIndex())));
-    else
-        glDrawElements(GL_PATCHES, de->size()-_skirtSize, GL_UNSIGNED_SHORT, &de->front());
-}
-
-
-void
-TileDrawable::drawSurface(osg::RenderInfo& renderInfo, bool renderColor) const
-{
-    unsigned layersDrawn = 0;
-
-    osg::State& state = *renderInfo.getState();    
-
-    // access the GL extensions interface for the current GC:
-    const osg::Program::PerContextProgram* pcp = 0L;
-
-#if OSG_MIN_VERSION_REQUIRED(3,3,3)
-	osg::ref_ptr<osg::GLExtensions> ext;
-#else
-    osg::ref_ptr<osg::GL2Extensions> ext;
-#endif
-    unsigned contextID;
-
-    if (_supportsGLSL)
-    {
-        contextID = state.getContextID();
-#if OSG_MIN_VERSION_REQUIRED(3,3,3)
-		ext = osg::GLExtensions::Get(contextID, true);
-#else   
-		ext = osg::GL2Extensions::Get( contextID, true );
-#endif
-        pcp = state.getLastAppliedProgramObject();
-    }
-
-    // safely latch
-    if ( !_geom.valid() || _geom->getNumPrimitiveSets() < 1 )
-        return;
-
-    // cannot store these in the object since there could be multiple GCs (and multiple
-    // PerContextPrograms) at large
-    GLint opacityLocation            = -1;
-    GLint uidLocation                = -1;
-    GLint orderLocation              = -1;
-    GLint texMatrixLocation          = -1;
-    GLint texMatrixParentLocation    = -1;
-    GLint texParentExistsLocation    = -1;
-    GLint minRangeLocation           = -1;
-    GLint maxRangeLocation           = -1;
-
-    // The PCP can change (especially in a VirtualProgram environment). So we do need to
-    // requery the uni locations each time unfortunately. TODO: explore optimizations.
-    if ( pcp )
-    {
-        opacityLocation             = pcp->getUniformLocation( _opacityUniformNameID );
-        uidLocation                 = pcp->getUniformLocation( _uidUniformNameID );
-        orderLocation               = pcp->getUniformLocation( _orderUniformNameID );
-        texMatrixLocation           = pcp->getUniformLocation( _texMatrixUniformNameID );
-        texMatrixParentLocation     = pcp->getUniformLocation( _texMatrixParentUniformNameID );
-        texParentExistsLocation     = pcp->getUniformLocation( _texParentExistsUniformNameID );
-        minRangeLocation            = pcp->getUniformLocation( _minRangeUniformNameID );
-        maxRangeLocation            = pcp->getUniformLocation( _maxRangeUniformNameID );
-    }
-
-    float prevOpacity = -1.0f;
-    if ( renderColor && _mptex.valid() && !_mptex->getPasses().empty() )
+    GLuint* k = &_meshIndices[0];
+    for(int t=0; t<_tileSize-1; ++t)
     {
-        float prevOpacity = -1.0f;
-
-        // in FFP mode, we need to enable the GL mode for texturing:
-        if ( !pcp )
-            state.applyMode(GL_TEXTURE_2D, true);
-
-        optional<bool> texParentExists_lastValue;
-
-        for(MPTexture::Passes::const_iterator p = _mptex->getPasses().begin();
-            p != _mptex->getPasses().end();
-            ++p)
+        for(int s=0; s<_tileSize-1; ++s)
         {
-            const MPTexture::Pass& pass = *p;
-
-            if ( pass._layer->getVisible() && pass._layer->getOpacity() > 0.1 )
-            {
-                // Apply the texture.
-                state.setActiveTextureUnit( _textureImageUnit );
-                const osg::StateAttribute* lastTex = state.getLastAppliedTextureAttribute(_textureImageUnit, osg::StateAttribute::TEXTURE);
-                if ( lastTex != pass._texture.get() )
-                    pass._texture->apply( state );
-
-                // Apply the texture matrix.
-                ext->glUniformMatrix4fv( texMatrixLocation, 1, GL_FALSE, pass._textureMatrix.ptr() );
-
-                bool texParentExists = pass._parentTexture.valid();
-                if ( texParentExists )
-                {
-                    // Apply the parent texture.
-                    state.setActiveTextureUnit( _textureParentImageUnit );
-                    const osg::StateAttribute* lastTex = state.getLastAppliedTextureAttribute(_textureParentImageUnit, osg::StateAttribute::TEXTURE);
-                    if ( lastTex != pass._parentTexture.get() )
-                        pass._parentTexture->apply( state );
-
-                    // Apply the parent texture matrix.
-                    ext->glUniformMatrix4fv( texMatrixParentLocation, 1, GL_FALSE, pass._parentTextureMatrix.ptr() );
-                }
-
-                if ( !texParentExists_lastValue.isSetTo(texParentExists) )
-                {
-                    texParentExists_lastValue = texParentExists;
-                    ext->glUniform1f( texParentExistsLocation, texParentExists? 1.0f : 0.0f );
-                }
-
-                // Order uniform (TODO: evaluate whether we still need this)
-                if ( orderLocation >= 0 )
-                {
-                    ext->glUniform1i( orderLocation, (GLint)layersDrawn );
-                }
-
-                // assign the layer UID:
-                if ( uidLocation >= 0 )
-                {
-                    ext->glUniform1i( uidLocation, (GLint)pass._layer->getUID() );
-                }
-
-                // apply opacity:
-                if ( opacityLocation >= 0 )
-                {
-                    float opacity = pass._layer->getOpacity();
-                    if ( opacity != prevOpacity )
-                    {
-                        ext->glUniform1f( opacityLocation, (GLfloat)opacity );
-                        prevOpacity = opacity;
-                    }
-                }         
-
-                // Apply the min/max range
-                float minRange = pass._layer->getImageLayerOptions().minVisibleRange().getOrUse(0.0);
-                float maxRange = pass._layer->getImageLayerOptions().maxVisibleRange().getOrUse(-1.0);
-                ext->glUniform1f( minRangeLocation, minRange );
-                ext->glUniform1f( maxRangeLocation, maxRange );
-
-                for (unsigned i=0; i < _geom->getNumPrimitiveSets(); i++)
-                    _geom->getPrimitiveSet(i)->draw(state, true);
-
-                ++layersDrawn;
-            }
-        }
-    }
-
-    // No mptex or no layers in the mptex? Draw simple.
-    if ( layersDrawn == 0 )
-    {
-        if ( opacityLocation >= 0 )
-            ext->glUniform1f( opacityLocation, (GLfloat)1.0f );
-
-        if ( uidLocation >= 0 )
-            ext->glUniform1i( uidLocation, (GLint)-1 );
-
-        if ( orderLocation >= 0 )
-            ext->glUniform1i( orderLocation, (GLint)0 );
+            int i00 = t*_tileSize + s;
+            int i10 = i00 + 1;
+            int i01 = i00 + _tileSize;
+            int i11 = i01 + 1;
 
-        if ( renderColor )
-        {
-            for (unsigned i=0; i < _geom->getNumPrimitiveSets(); i++)
-            {
-                _geom->getPrimitiveSet(i)->draw(state, true);
-            }
-        }
-        else
-        {
-            // draw the surface w/o the skirt:
-            const osg::DrawElementsUShort* de = static_cast<osg::DrawElementsUShort*>(_geom->getPrimitiveSet(0));
-            osg::GLBufferObject* ebo = de->getOrCreateGLBufferObject(state.getContextID());
-            state.bindElementBufferObject(ebo);
-            glDrawElements(GL_TRIANGLES, de->size()-_skirtSize, GL_UNSIGNED_SHORT, (const GLvoid *)(ebo->getOffset(de->getBufferIndex())));
-        
-            // draw the remaining primsets normally
-            for (unsigned i=1; i < _geom->getNumPrimitiveSets(); i++)
-            {
-                _geom->getPrimitiveSet(i)->draw(state, true);
-            }
+            *k++ = i00; *k++ = i10; *k++ = i01;
+            *k++ = i01; *k++ = i10; *k++ = i11;
         }
     }
+    
+    // builds the initial mesh.
+    setElevationRaster(0L, osg::Matrixf::identity());
+}
 
+TileDrawable::~TileDrawable()
+{
+    delete [] _meshIndices;
+    delete [] _mesh;
 }
 
 void
@@ -316,10 +84,11 @@ TileDrawable::setElevationRaster(const osg::Image*   image,
     {
         OE_WARN << "("<<_key.str()<<") precision error\n";
     }
+    
+    const osg::Vec3Array& verts = *static_cast<osg::Vec3Array*>(_geom->getVertexArray());
 
     if ( _elevationRaster.valid() )
     {
-        const osg::Vec3Array& verts   = *static_cast<osg::Vec3Array*>(_geom->getVertexArray());
         const osg::Vec3Array& normals = *static_cast<osg::Vec3Array*>(_geom->getNormalArray());
 
         //OE_INFO << LC << _key.str() << " - rebuilding height cache" << std::endl;
@@ -347,343 +116,52 @@ TileDrawable::setElevationRaster(const osg::Image*   image,
             {
                 float u = (float)s / (float)(_tileSize-1);
                 u = u*scaleU + biasU;
-                _heightCache[t*_tileSize+s] = elevation(u, v).r();
-            }
-        }
-    }
-
-    dirtyBound();
-}
-
-const osg::Image*
-TileDrawable::getElevationRaster() const
-{
-    return _elevationRaster.get();
-}
 
-const osg::Matrixf&
-TileDrawable::getElevationMatrix() const
-{
-    return _elevationScaleBias;
-}
-
-// Functor supplies triangles to things like IntersectionVisitor, ComputeBoundsVisitor, etc.
-void
-TileDrawable::accept(osg::PrimitiveFunctor& f) const
-{
-    const osg::Vec3Array& verts   = *static_cast<osg::Vec3Array*>(_geom->getVertexArray());
-    const osg::Vec3Array& normals = *static_cast<osg::Vec3Array*>(_geom->getNormalArray());
-        
-#if 1 // triangles (OSG-stats-friendly)
-
-    //TODO: improve by caching the entire Vec3f, not just the height.
-
-    f.begin(GL_TRIANGLES);
-    for(int t=0; t<_tileSize-1; ++t)
-    {
-        for(int s=0; s<_tileSize-1; ++s)
-        {
-            int i00 = t*_tileSize + s;
-            int i10 = i00 + 1;
-            int i01 = i00 + _tileSize;
-            int i11 = i01 + 1;
-            
-            osg::Vec3d v01 = verts[i01] + normals[i01] * _heightCache[i01];
-            osg::Vec3d v10 = verts[i10] + normals[i10] * _heightCache[i10];
-
-            f.vertex( verts[i00] + normals[i00] * _heightCache[i00] );
-            f.vertex( v01 );
-            f.vertex( v10 );
-            
-            f.vertex( v10 );
-            f.vertex( v01 );
-            f.vertex( verts[i11] + normals[i11] * _heightCache[i11] );
+                unsigned index = t*_tileSize+s;
+                _mesh[index] = verts[index] + normals[index] * elevation(u, v).r();
+            }
         }
     }
-    f.end();
-
-#else
-    // triangle-strips (faster? but not stats-friendly; will cause the OSG stats
-    // to report _tileSize-1 primitive sets per TileDrawable even though there
-    // is only one.
 
-    for(int t=0; t<_tileSize-1; ++t)
+    else
     {
-        f.begin( GL_TRIANGLE_STRIP );
-
-        for(int s=0; s<_tileSize; ++s)
+        for (int i = 0; i < _tileSize*_tileSize; ++i)
         {
-            int i = t*_tileSize + s;
-            f.vertex( verts[i] + normals[i] * _heightCache[i] );
-
-            i += _tileSize;
-            f.vertex( verts[i] + normals[i] * _heightCache[i] );
+            _mesh[i] = verts[i];
         }
-
-        f.end();
-    }
-
-#endif
-}
-
-void 
-TileDrawable::releaseGLObjects(osg::State* state) const
-{
-    osg::Drawable::releaseGLObjects( state );
-
-    if ( _geom.valid() )
-    {
-        _geom->releaseGLObjects( state );
-    }
-}
-
-
-void
-TileDrawable::resizeGLObjectBuffers(unsigned maxSize)
-{
-    osg::Drawable::resizeGLObjectBuffers( maxSize );
-
-    if ( _geom.valid() )
-    {
-        _geom->resizeGLObjectBuffers( maxSize );
     }
 
-    if ( _pcd.size() < maxSize )
-    {
-        _pcd.resize(maxSize);
-    }
-}
-
-
-void 
-TileDrawable::compileGLObjects(osg::RenderInfo& renderInfo) const
-{
-    osg::Drawable::compileGLObjects( renderInfo );
-
-    if ( _geom.valid() )
-    {
-        _geom->compileGLObjects( renderInfo );
-    }
-
-    // unbind the BufferObjects
-    //extensions->glBindBuffer(GL_ARRAY_BUFFER_ARB,0);
-    //extensions->glBindBuffer(GL_ELEMENT_ARRAY_BUFFER_ARB,0);
+    dirtyBound();    
 }
 
-
-#if OSG_VERSION_GREATER_OR_EQUAL(3,1,8)
-#   define GET_ARRAY(a) (a)
-#else
-#   define GET_ARRAY(a) (a).array
-#endif
-
-
+// Functor supplies triangles to things like IntersectionVisitor, ComputeBoundsVisitor, etc.
 void
-TileDrawable::drawImplementation(osg::RenderInfo& renderInfo) const
+TileDrawable::accept(osg::PrimitiveFunctor& f) const
 {
-    State& state = *renderInfo.getState();
-    //bool checkForGLErrors = state.getCheckForGLErrors() == osg::State::ONCE_PER_ATTRIBUTE;
-    //if ( checkForGLErrors ) state.checkGLErrors("start of TileDrawable::drawImplementation()");
-
-#if 0
-    const osg::FrameStamp* fs = state.getFrameStamp();
-    if ( fs )
-    {
-        Threading::ScopedMutexLock lock(_profMutex);
-        if ( s_frame != fs->getFrameNumber() )
-        {
-            OE_NOTICE << "Frame " << s_frame << " : draws = " << s_draws << ", functors = " << s_functors << std::endl;
-            s_draws = 0;
-            s_functors = 0;
-            s_frame = fs->getFrameNumber();
-        }
-        s_draws++;
-    }
-#endif
-
-
-#if OSG_MIN_VERSION_REQUIRED(3,3,1)
-    _geom->drawVertexArraysImplementation( renderInfo );
-#else
-    drawVertexArraysImplementation( renderInfo );
-#endif
-
-    drawPrimitivesImplementation( renderInfo );
-
-    //if ( checkForGLErrors ) state.checkGLErrors("end of TileDrawable::drawImplementation()");
-    
-    // unbind the VBO's if any are used.
-    state.unbindVertexBufferObject();
-    state.unbindElementBufferObject();
+    f.setVertexArray(_tileSize*_tileSize, _mesh);
+    f.drawElements(GL_TRIANGLES, (_tileSize - 1)*(_tileSize-1)*6, _meshIndices);
 }
 
-
-#if OSG_MIN_VERSION_REQUIRED(3,1,8)
-void
-TileDrawable::drawVertexArraysImplementation(osg::RenderInfo& renderInfo) const
+osg::BoundingSphere
+TileDrawable::computeBound() const
 {
-    if ( !_geom.valid() )
-        return;
-
-    State& state = *renderInfo.getState();
-
-    bool handleVertexAttributes = !_geom->getVertexAttribArrayList().empty();
-
-    ArrayDispatchers& arrayDispatchers = state.getArrayDispatchers();
-
-    arrayDispatchers.reset();
-    arrayDispatchers.setUseVertexAttribAlias(state.getUseVertexAttributeAliasing());
-
-    arrayDispatchers.activateNormalArray(_geom->getNormalArray());
-    //arrayDispatchers.activateColorArray(_geom->getColorArray());
-    //arrayDispatchers.activateSecondaryColorArray(_geom->getSecondaryColorArray());
-    //arrayDispatchers.activateFogCoordArray(_geom->getFogCoordArray());
-
-    if (handleVertexAttributes)
-    {
-        for(unsigned int unit=0;unit<_geom->getVertexAttribArrayList().size();++unit)
-        {
-            arrayDispatchers.activateVertexAttribArray(unit, _geom->getVertexAttribArray(unit));
-        }
-    }
-
-    // dispatch any attributes that are bound overall
-    arrayDispatchers.dispatch(osg::Array::BIND_OVERALL,0);
-
-    state.lazyDisablingOfVertexAttributes();
-
-    // set up arrays
-    if( _geom->getVertexArray() )
-        state.setVertexPointer(_geom->getVertexArray());
-
-    if (_geom->getNormalArray() && _geom->getNormalArray()->getBinding()==osg::Array::BIND_PER_VERTEX)
-        state.setNormalPointer(_geom->getNormalArray());
-
-    //if (_geom->getColorArray() && _geom->getColorArray()->getBinding()==osg::Array::BIND_PER_VERTEX)
-    //    state.setColorPointer(_geom->getColorArray());
-
-    //if (_geom->getSecondaryColorArray() && _geom->getSecondaryColorArray()->getBinding()==osg::Array::BIND_PER_VERTEX)
-    //    state.setSecondaryColorPointer(_geom->getSecondaryColorArray());
-
-    //if (_geom->getFogCoordArray() && _geom->getFogCoordArray()->getBinding()==osg::Array::BIND_PER_VERTEX)
-    //    state.setFogCoordPointer(_geom->getFogCoordArray());
-
-    for(unsigned int unit=0;unit<_geom->getTexCoordArrayList().size();++unit)
-    {
-        const Array* array = _geom->getTexCoordArray(unit);
-        if (array)
-        {
-            state.setTexCoordPointer(unit,array);
-        }
-    }
-
-    if ( handleVertexAttributes )
-    {
-        for(unsigned int index = 0; index < _geom->getVertexAttribArrayList().size(); ++index)
-        {
-            const Array* array = _geom->getVertexAttribArray(index);
-            if (array && array->getBinding()==osg::Array::BIND_PER_VERTEX)
-            {
-                if (array->getPreserveDataType())
-                {
-                    GLenum dataType = array->getDataType();
-                    if (dataType==GL_FLOAT) state.setVertexAttribPointer( index, array );
-                    else if (dataType==GL_DOUBLE) state.setVertexAttribLPointer( index, array );
-                    else state.setVertexAttribIPointer( index, array );
-                }
-                else
-                {
-                    state.setVertexAttribPointer( index, array );
-                }
-            }
-        }
-    }
-    
-    state.applyDisablingOfVertexAttributes();
+    return osg::BoundingSphere(getBoundingBox());
 }
 
-#else
-
-void
-TileDrawable::drawVertexArraysImplementation(osg::RenderInfo& renderInfo) const
+osg::BoundingBox
+TileDrawable::computeBoundingBox() const
 {
-    State& state = *renderInfo.getState();
-    bool handleVertexAttributes = !_geom->getVertexAttribArrayList().empty();
-    //bool handleVertexAttributes = !_vertexAttribList.empty();
-
-    ArrayDispatchers& arrayDispatchers = state.getArrayDispatchers();
-
-    arrayDispatchers.reset();
-    arrayDispatchers.setUseVertexAttribAlias(state.getUseVertexAttributeAliasing());
-    arrayDispatchers.setUseGLBeginEndAdapter(false);
+    osg::BoundingBox box;
 
-    arrayDispatchers.activateNormalArray(_geom->getNormalBinding(), _geom->getNormalArray(), _geom->getNormalIndices());
-    arrayDispatchers.activateColorArray(_geom->getColorBinding(), _geom->getColorArray(), _geom->getColorIndices());
-    arrayDispatchers.activateSecondaryColorArray(_geom->getSecondaryColorBinding(), _geom->getSecondaryColorArray(), _geom->getSecondaryColorIndices());
-    arrayDispatchers.activateFogCoordArray(_geom->getFogCoordBinding(), _geom->getFogCoordArray(), _geom->getFogCoordIndices());
-
-    if (handleVertexAttributes)
+    for(unsigned i=0; i<_tileSize*_tileSize; ++i)
     {
-        for(unsigned int unit=0;unit < _geom->getVertexAttribArrayList().size();++unit)
-        {
-            const osg::Geometry::ArrayData& val = _geom->getVertexAttribArrayList()[unit];
-            arrayDispatchers.activateVertexAttribArray(val.binding, unit, val.array.get(), val.indices.get());
-        }
+        box.expandBy(_mesh[i]);
     }
 
-    // dispatch any attributes that are bound overall
-    arrayDispatchers.dispatch(_geom->BIND_OVERALL, 0);
-
-    state.lazyDisablingOfVertexAttributes();
-
-    // set up arrays
-    if( _geom->getVertexArray() )
-        state.setVertexPointer(_geom->getVertexArray()); //_vertexData.array.get());
-
-    if (_geom->getNormalBinding()==_geom->BIND_PER_VERTEX && _geom->getNormalArray())
-        state.setNormalPointer(_geom->getNormalArray());
-
-    if (_geom->getColorBinding()==_geom->BIND_PER_VERTEX && _geom->getColorArray())
-        state.setColorPointer(_geom->getColorArray());
-
-    if (_geom->getSecondaryColorBinding()==_geom->BIND_PER_VERTEX && _geom->getSecondaryColorArray())
-        state.setSecondaryColorPointer(_geom->getSecondaryColorArray());
-
-    if (_geom->getFogCoordBinding()==_geom->BIND_PER_VERTEX && _geom->getFogCoordArray())
-        state.setFogCoordPointer(_geom->getFogCoordArray());
-    
-    for(unsigned int unit=0;unit<_geom->getTexCoordArrayList().size();++unit)
+    if (_bboxCB)
     {
-        const Array* array = _geom->getTexCoordArray(unit);
-        if (array)
-        {
-            state.setTexCoordPointer(unit,array);
-        }
-    }
-    
-    if ( handleVertexAttributes )
-    {
-        for(unsigned int index = 0; index < _geom->getVertexAttribArrayList().size(); ++index)
-        {
-            const osg::Array* array = _geom->getVertexAttribArray(index);
-            if ( array && _geom->getVertexAttribBinding(index) == _geom->BIND_PER_VERTEX )
-            {
-                if (array->getPreserveDataType())
-                {
-                    GLenum dataType = array->getDataType();
-                    if (dataType==GL_FLOAT) state.setVertexAttribPointer( index, array, GL_FALSE );
-                    else if (dataType==GL_DOUBLE) state.setVertexAttribLPointer( index, array );
-                    else state.setVertexAttribIPointer( index, array );
-                }
-                else
-                {
-                    state.setVertexAttribPointer( index, array, GL_FALSE );
-                }
-            }
-        }
+        (*_bboxCB)(_key, box);
     }
 
-    state.applyDisablingOfVertexAttributes();
+    return box;
 }
-
-#endif
diff --git a/src/osgEarthDrivers/engine_rex/TileNode b/src/osgEarthDrivers/engine_rex/TileNode
index 26efb51..7816c77 100644
--- a/src/osgEarthDrivers/engine_rex/TileNode
+++ b/src/osgEarthDrivers/engine_rex/TileNode
@@ -23,11 +23,12 @@
 #include "RenderBindings"
 #include "Loader"
 #include "MaskGenerator"
-#include "MPTexture"
-#include "ProxySurfaceNode"
+#include "TileRenderModel"
 
-#include <OpenThreads/Atomic>
+#include <osgEarth/TerrainTileModel>
 #include <osgEarth/TerrainTileNode>
+
+#include <OpenThreads/Atomic>
 #include <vector>
 
 namespace osg {
@@ -39,23 +40,25 @@ namespace osgUtil {
 
 namespace osgEarth { namespace Drivers { namespace RexTerrainEngine
 {
+    class LoadTileData;
     class EngineContext;
     class SurfaceNode;
-    class ProxySurfaceNode;
     class SelectionInfo;
+    class TerrainCuller;
 
     /**
      * TileNode represents a single tile. TileNode has 5 children:
      * one SurfaceNode that renders the actual tile content under a MatrixTransform;
      * and four TileNodes representing the LOD+1 quadtree tiles under this tile.
      */
-    class TileNode : public osg::Group
+    class TileNode : public osg::Group,
+                     public osgEarth::TerrainTileNode
     {
     public:
         TileNode();
 
         /** TileKey of the key representing the data in this node. */
-        const TileKey& getTileKey() const { return _key; }
+        const TileKey& getKey() const { return _key; }
 
         /** Sets the map revision that this tile should be using. */
         void setMapRevision(Revision rev) { } // nyi - check for need
@@ -64,10 +67,7 @@ namespace osgEarth { namespace Drivers { namespace RexTerrainEngine
         void setDirty(bool value);
 
         /** Creates the geometry and state for this tilenode. */
-        void create(const TileKey& key, EngineContext* context);
-
-        /** Whether this node has enough data to traverse. */
-        bool isReadyToTraverse() const;
+        void create(const TileKey& key, TileNode* parent, EngineContext* context);
 
         /** Whether the tile is expired; i.e. has not been visited in some time. */
         bool isDormant(const osg::FrameStamp*) const;
@@ -78,26 +78,14 @@ namespace osgEarth { namespace Drivers { namespace RexTerrainEngine
         /** Removed any sub tiles from the scene graph. Please call from a safe thread only (update) */
         void removeSubTiles();
 
-        /** Whether the tile drawables are visible */
-        bool isVisible(osg::CullStack* cs) const;
-
-        /** Load (or continue loading) content for the tiles in this quad. */
-        void load(osg::NodeVisitor& nv);
-
         /** Notifies this tile that another tile has come into existence. */
-        void notifyOfArrival(TileNode* that) { } // nyi - todo
-
-        /** Set inheritance matrices as necessary. Return true is changed were made. */
-        bool inheritState(EngineContext* context);
+        void notifyOfArrival(TileNode* that);
 
         /** Returns the tile's parent; convenience function */
         TileNode* getParentTile() { return dynamic_cast<TileNode*>(getParent(0)); }
-        
-        /** merge in new stuff from a state set. */
-        void mergeStateSet(osg::StateSet* stateSet, MPTexture* mptex, const RenderBindings& bindings);
 
-        /** Access to the multipass texture attribute */
-        MPTexture* getMPTexture() const { return _mptex.get(); }
+        /** Returns the SurfaceNode under this node. */
+        SurfaceNode* getSurfaceNode() { return _surface.get(); }
 
         /** Elevation data for this node along with its scale/bias matrix; needed for bounding box */
         void setElevationRaster(const osg::Image* image, const osg::Matrixf& matrix);
@@ -108,13 +96,30 @@ namespace osgEarth { namespace Drivers { namespace RexTerrainEngine
         TileNode* getSubTile(unsigned i) { return static_cast<TileNode*>(_children[i].get()); }
         const TileNode* getSubTile(unsigned i) const { return static_cast<TileNode*>(_children[i].get()); }
 
-        double getMinimumExpiryTime() const { return _minExpiryTime; }
-        void setMinimumExpiryTime(double minExpiryTime) { _minExpiryTime = minExpiryTime; }
+        virtual double getMinimumExpirationTime() const { return _minExpiryTime; }
+        virtual void setMinimumExpirationTime(double minExpiryTime) { _minExpiryTime = minExpiryTime; }
         
-        unsigned int getMinimumExpiryFrames() const { return _minExpiryFrames; }
-        void setMinimumExpiryFrames(unsigned int minExpiryFrames) { _minExpiryFrames = minExpiryFrames; }
+        virtual unsigned int getMinimumExpirationFrames() const { return _minExpiryFrames; }
+        virtual void setMinimumExpirationFrames(unsigned int minExpiryFrames) { _minExpiryFrames = minExpiryFrames; }
+
+        virtual void loadChildren();
 
+        /** Merge new Tile model data into this tile's rendering data model. */
+        void merge(const TerrainTileModel* dataModel, const RenderBindings& bindings);
 
+        /** Access the rendering model for this tile */
+        TileRenderModel& renderModel() { return _renderModel; }
+
+        const osg::Vec4f& getTileKeyValue() const { return _tileKeyValue; }
+
+        const osg::Vec2f& getMorphConstants() const { return _morphConstants; }
+
+        void loadSync();
+
+        std::set<UID>& newLayers() { return _newLayers; }
+
+        void refreshSharedSamplers(const RenderBindings& bindings);
+        
     public: // osg::Node
 
         osg::BoundingSphere computeBound() const;
@@ -127,41 +132,50 @@ namespace osgEarth { namespace Drivers { namespace RexTerrainEngine
         TileKey                            _key;
         osg::ref_ptr<SurfaceNode>          _surface;
         osg::ref_ptr<SurfaceNode>          _patch;
-        osg::ref_ptr<Loader::Request>      _loadRequest;
-        osg::ref_ptr<Loader::Request>      _expireRequest;
+        osg::ref_ptr<LoadTileData>         _loadRequest;
+        osg::ref_ptr<EngineContext>        _context;
         Threading::Mutex                   _mutex;
         bool                               _dirty;
         OpenThreads::Atomic                _lastTraversalFrame;
         double                             _lastTraversalTime;
         OpenThreads::Atomic                _lastAcceptSurfaceFrame;
         unsigned                           _count;
-        osg::ref_ptr<MPTexture>            _mptex;
-        osg::ref_ptr<osg::StateSet>        _payloadStateSet;
         bool                               _childrenReady;
         unsigned int                       _minExpiryFrames;
         double                             _minExpiryTime;
+        mutable osg::Vec4f                 _tileKeyValue;
+        osg::Vec2f                         _morphConstants;
+        TileRenderModel                    _renderModel;
+        std::set<UID>                      _newLayers;
+        bool                               _empty;
+        bool                               _isRootTile;
+
+        osg::observer_ptr<TileNode> _eastNeighbor;
+        osg::observer_ptr<TileNode> _southNeighbor;
+        bool _stitchNormalMap;
 
     private:
 
+        void updateNormalMap();
+
         void createChildren(EngineContext* context);
 
         /** Returns false if the Surface node fails visiblity test */
-        bool cull(osgUtil::CullVisitor* cv);
+        bool cull(TerrainCuller*);
 
-        bool accept_cull(osgUtil::CullVisitor* cv);
+        bool accept_cull(TerrainCuller*);
 
-        bool cull_stealth(osgUtil::CullVisitor* cv);
+        bool cull_stealth(TerrainCuller*);
 
-        bool accept_cull_stealth(osgUtil::CullVisitor* cv);
+        bool accept_cull_stealth(TerrainCuller*);
 
-        // returns true if any surface geometry was added (visible).
-        bool acceptSurface(osgUtil::CullVisitor* nv, EngineContext*);
+        bool shouldSubDivide(TerrainCuller*, const SelectionInfo&);
 
-        bool shouldSubDivide(osgUtil::CullVisitor* cv, const SelectionInfo& selectionInfo);
-
-        void createPayloadStateSet(EngineContext*);
+        /** Load (or continue loading) content for the tiles in this quad. */
+        void load(TerrainCuller*);
 
-        void updateTileUniforms(const SelectionInfo& selectionInfo);
+        /** Ensure that inherited data from the parent node is up to date. */
+        void refreshInheritedData(TileNode* parent, const RenderBindings& bindings);
 
         osg::ref_ptr<osg::Uniform> _tileKeyUniform;
         osg::ref_ptr<osg::Uniform> _tileMorphUniform;
diff --git a/src/osgEarthDrivers/engine_rex/TileNode.cpp b/src/osgEarthDrivers/engine_rex/TileNode.cpp
index 23aaae9..58585b0 100644
--- a/src/osgEarthDrivers/engine_rex/TileNode.cpp
+++ b/src/osgEarthDrivers/engine_rex/TileNode.cpp
@@ -24,6 +24,8 @@
 #include "LoadTileData"
 #include "SelectionInfo"
 #include "ElevationTextureUtils"
+#include "TerrainCuller"
+#include "RexTerrainEngineNode"
 
 #include <osgEarth/CullingUtils>
 #include <osgEarth/ImageUtils>
@@ -68,76 +70,153 @@ _childrenReady( false ),
 _minExpiryTime( 0.0 ),
 _minExpiryFrames( 0 ),
 _lastTraversalTime(0.0),
-_lastTraversalFrame(0.0)
+_lastTraversalFrame(0.0),
+_count(0),
+_stitchNormalMap(false),
+_empty(false),              // an "empty" node exists but has no geometry or children.,
+_isRootTile(false)
 {
-    osg::StateSet* stateSet = getOrCreateStateSet();
-
-    // The StateSet must have a dynamic data variance since we plan to alter it
-    // as new data becomes available.
-    stateSet->setDataVariance(osg::Object::DYNAMIC);
-
-    _count = 0;
+    //nop
 }
 
 void
-TileNode::create(const TileKey& key, EngineContext* context)
+TileNode::create(const TileKey& key, TileNode* parent, EngineContext* context)
 {
     if (!context)
         return;
 
+    _context = context;
+
     _key = key;
 
-    // Create mask records
-    osg::ref_ptr<MaskGenerator> masks = new MaskGenerator(key, context->getOptions().tileSize().get(), context->getMapFrame());
+    // Mask generator creates geometry from masking boundaries when they exist.
+    osg::ref_ptr<MaskGenerator> masks = new MaskGenerator(
+        key, 
+        context->getOptions().tileSize().get(),
+        context->getMap());
+
+    MapInfo mapInfo(context->getMap());
 
     // Get a shared geometry from the pool that corresponds to this tile key:
-    osg::ref_ptr<osg::Geometry> geom;
-    context->getGeometryPool()->getPooledGeometry(key, context->getMapFrame().getMapInfo(), geom, masks.get());
+    osg::ref_ptr<SharedGeometry> geom;
+    context->getGeometryPool()->getPooledGeometry(
+        key,
+        mapInfo,         
+        context->getOptions().tileSize().get(),
+        masks.get(), 
+        geom);
 
+    // If we donget an empty, that most likely means the tile was completely
+    // contained by a masking boundary. Mark as empty and we are done.
+    if (geom->empty())
+    {
+        OE_DEBUG << LC << "Tile " << _key.str() << " is empty.\n";
+        _empty = true;
+        return;
+    }
 
     // Create the drawable for the terrain surface:
     TileDrawable* surfaceDrawable = new TileDrawable(
         key, 
-        context->getRenderBindings(), 
         geom.get(),
-        context->getOptions().tileSize().get(),
-        context->getGeometryPool()->getNumSkirtElements() );
+        context->getOptions().tileSize().get() );
 
-    surfaceDrawable->setDrawAsPatches(false);
+    // Give the tile Drawable access to the render model so it can properly
+    // calculate its bounding box and sphere.
+    surfaceDrawable->setModifyBBoxCallback(context->getModifyBBoxCallback());
 
     // Create the node to house the tile drawable:
     _surface = new SurfaceNode(
         key,
-        context->getMapFrame().getMapInfo(),
+        mapInfo,
         context->getRenderBindings(),
         surfaceDrawable );
-
-    // Create a drawable for "patch" geometry, which is rendered as GL patches, not triangles.
-    // Patch geometry can be used to place land cover or render other tile-specific data.
-    TileDrawable* patchDrawable = new TileDrawable(
-        key, 
-        context->getRenderBindings(),
-        geom.get(),
-        context->getOptions().tileSize().get(),
-        context->getGeometryPool()->getNumSkirtElements() );
     
-    patchDrawable->setDrawAsPatches(true);
+    // create a data load request for this new tile:
+    _loadRequest = new LoadTileData( this, context );
+    _loadRequest->setName( _key.str() );
+    _loadRequest->setTileKey( _key );
 
-    // And a node to house that as well:
-    _patch = new SurfaceNode(
-        key,
-        context->getMapFrame().getMapInfo(),
-        context->getRenderBindings(),
-        patchDrawable );
+    // whether the stitch together normal maps for adjacent tiles.
+    _stitchNormalMap = context->_options.normalizeEdges() == true;
+
+    // Encode the tile key in a uniform. Note! The X and Y components are presented
+    // modulo 2^16 form so they don't overrun single-precision space.
+    unsigned tw, th;
+    _key.getProfile()->getNumTiles(_key.getLOD(), tw, th);
+
+    const double m = 65536; //pow(2.0, 16.0);
+
+    double x = (double)_key.getTileX();
+    double y = (double)(th - _key.getTileY()-1);
+
+    _tileKeyValue.set(
+        (float)fmod(x, m),
+        (float)fmod(y, m),
+        (float)_key.getLOD(),
+        -1.0f);
 
     // initialize all the per-tile uniforms the shaders will need:
-    createPayloadStateSet(context);
+    float start = (float)context->getSelectionInfo().visParameters(_key.getLOD())._fMorphStart;
+    float end   = (float)context->getSelectionInfo().visParameters(_key.getLOD())._fMorphEnd;
+    float one_by_end_minus_start = end - start;
+    one_by_end_minus_start = 1.0f/one_by_end_minus_start;
+    _morphConstants.set( end * one_by_end_minus_start, one_by_end_minus_start );
+
+    // Initialize the data model by copying the parent's rendering data
+    // and scale/biasing the matrices.
+    if (parent)
+    {
+        unsigned quadrant = getKey().getQuadrant();
+
+        const RenderBindings& bindings = context->getRenderBindings();
 
-    updateTileUniforms(context->getSelectionInfo());
+        bool setElevation = false;
 
-    // Set up a data container for multipass layer rendering:
-    _mptex = new MPTexture();
-    surfaceDrawable->setMPTexture( _mptex.get() );
+        for (unsigned p = 0; p < parent->_renderModel._passes.size(); ++p)
+        {
+            const RenderingPass& parentPass = parent->_renderModel._passes[p];
+
+            // Copy the parent pass:
+            _renderModel._passes.push_back(parentPass);
+            RenderingPass& myPass = _renderModel._passes.back();
+
+            // Scale/bias each matrix for this key quadrant.
+            Samplers& samplers = myPass.samplers();
+            for (unsigned s = 0; s < samplers.size(); ++s)
+            {
+                samplers[s]._matrix.preMult(scaleBias[quadrant]);
+            }
+
+            // Are we using image blending? If so, initialize the color_parent 
+            // to the color texture.
+            if (bindings[SamplerBinding::COLOR_PARENT].isActive())
+            {
+                samplers[SamplerBinding::COLOR_PARENT] = samplers[SamplerBinding::COLOR];
+            }
+        }
+
+        // Copy the parent's shared samplers and scale+bias each matrix to the new quadrant:
+        _renderModel._sharedSamplers = parent->_renderModel._sharedSamplers;
+
+        for (unsigned s = 0; s<_renderModel._sharedSamplers.size(); ++s)
+        {
+            Sampler& sampler = _renderModel._sharedSamplers[s];
+            sampler._matrix.preMult(scaleBias[quadrant]);
+        }
+
+        // Use the elevation sampler to initialize the elevation raster
+        // (used for primitive functors, intersection, etc.)
+        if (!setElevation && bindings[SamplerBinding::ELEVATION].isActive())
+        {
+            const Sampler& elevation = _renderModel._sharedSamplers[SamplerBinding::ELEVATION];
+            if (elevation._texture.valid())
+            {
+                setElevationRaster(elevation._texture->getImage(0), elevation._matrix);
+                setElevation = true;
+            }
+        }
+    }
 
     // need to recompute the bounds after adding payload:
     dirtyBound();
@@ -147,12 +226,23 @@ TileNode::create(const TileKey& key, EngineContext* context)
 
     // register me.
     context->liveTiles()->add( this );
+
+    // tell the world.
+    OE_DEBUG << LC << "notify (create) key " << getKey().str() << std::endl;
+    context->getEngine()->getTerrain()->notifyTileAdded(getKey(), this);
 }
 
 osg::BoundingSphere
 TileNode::computeBound() const
 {
-    return _surface.valid() ? _surface->computeBound() : osg::BoundingSphere();
+    osg::BoundingSphere bs;
+    if (_surface.valid())
+    {
+        bs = _surface->getBound();
+        const osg::BoundingBox bbox = _surface->getAlignedBoundingBox();
+        _tileKeyValue.a() = std::max( (bbox.xMax()-bbox.xMin()), (bbox.yMax()-bbox.yMin()) );
+    }    
+    return bs;
 }
 
 bool
@@ -170,11 +260,19 @@ TileNode::isDormant(const osg::FrameStamp* fs) const
 void
 TileNode::setElevationRaster(const osg::Image* image, const osg::Matrixf& matrix)
 {
-    if ( _surface.valid() )
-        _surface->setElevationRaster( image, matrix );
+    if (image == 0L)
+    {
+        OE_WARN << LC << "TileNode::setElevationRaster: image is NULL!\n";
+    }
 
-    if ( _patch.valid() )
-        _patch->setElevationRaster( image, matrix );
+    if (image != getElevationRaster() || matrix != getElevationMatrix())
+    {
+        if ( _surface.valid() )
+            _surface->setElevationRaster( image, matrix );
+
+        if ( _patch.valid() )
+            _patch->setElevationRaster( image, matrix );
+    }
 }
 
 const osg::Image*
@@ -190,141 +288,89 @@ TileNode::getElevationMatrix() const
 }
 
 void
-TileNode::createPayloadStateSet(EngineContext* context)
-{
-    _payloadStateSet = new osg::StateSet();
-
-    // Install the tile key uniform.
-    _tileKeyUniform = new osg::Uniform("oe_tile_key", osg::Vec4f(0,0,0,0));
-    _payloadStateSet->addUniform( _tileKeyUniform.get() );
-
-    _tileMorphUniform = new osg::Uniform("oe_tile_morph", osg::Vec2f(0,0));
-    _payloadStateSet->addUniform( _tileMorphUniform.get() );
-}
-
-void
-TileNode::updateTileUniforms(const SelectionInfo& selectionInfo)
-{
-    //assert(_surface.valid());
-    // update the tile key uniform
-    const osg::BoundingBox& bbox = _surface->getAlignedBoundingBox();
-    float width = std::max( (bbox.xMax()-bbox.xMin()), (bbox.yMax()-bbox.yMin()) );
-
-
-    // Encode the tile key in a uniform. Note! The X and Y components are presented
-    // modulo 2^16 form so they don't overrun single-precision space.
-    unsigned tw, th;
-    _key.getProfile()->getNumTiles(_key.getLOD(), tw, th);
-
-    const double m = pow(2.0, 16.0);
-
-    double x = (double)_key.getTileX();
-    double y = (double)(th - _key.getTileY()-1);
-
-    _tileKeyUniform->set(osg::Vec4f(
-        (float)fmod(x, m),
-        (float)fmod(y, m),
-        (float)_key.getLOD(),
-        width));
-
-    // update the morph constants
-
-    float start = (float)selectionInfo.visParameters(_key.getLOD())._fMorphStart;
-    float end   = (float)selectionInfo.visParameters(_key.getLOD())._fMorphEnd;
-
-    float one_by_end_minus_start = end - start;
-    one_by_end_minus_start = 1.0f/one_by_end_minus_start;
-    osg::Vec2f morphConstants( end * one_by_end_minus_start, one_by_end_minus_start );
-    _tileMorphUniform->set( morphConstants );
-
-    const osg::Image* er = getElevationRaster();
-    if ( er )
-    {
-        // pre-calculate texel-sampling scale and bias coefficients that allow us to sample
-        // elevation textures on texel-center instead of edge:
-        float size = (float)er->s();
-        osg::Vec2f elevTexelOffsets( (size-1.0f)/size, 0.5/size );
-        getOrCreateStateSet()->getOrCreateUniform("oe_tile_elevTexelCoeff", osg::Uniform::FLOAT_VEC2)->set(elevTexelOffsets);
-    }
-}
-
-bool
-TileNode::isReadyToTraverse() const
-{
-    // Later, we might replace this if the tile isn't immediately created at cull time.
-    return true;
-}
-
-void
 TileNode::setDirty(bool value)
 {
     _dirty = value;
+    
+    if (_dirty == false && !_newLayers.empty())
+    {
+        _loadRequest->filter().clear();
+        _loadRequest->filter().layers() = _newLayers;
+        _newLayers.clear();
+        _dirty = true;
+    }
 }
 
 void
 TileNode::releaseGLObjects(osg::State* state) const
 {
-    OE_DEBUG << LC << "Tile " << _key.str() << " : Release GL objects\n";
+    //OE_WARN << LC << "Tile " << _key.str() << " : Release GL objects\n";
 
-    if ( getStateSet() )
-        getStateSet()->releaseGLObjects(state);
-    if ( _payloadStateSet.valid() )
-        _payloadStateSet->releaseGLObjects(state);
     if ( _surface.valid() )
         _surface->releaseGLObjects(state);
+
     if ( _patch.valid() )
         _patch->releaseGLObjects(state);
-    if ( _mptex.valid() )
-        _mptex->releaseGLObjects(state);
+
+    _renderModel.releaseGLObjects(state);
 
     osg::Group::releaseGLObjects(state);
 }
 
 bool
-TileNode::shouldSubDivide(osgUtil::CullVisitor* cv, const SelectionInfo& selectionInfo)
-{
+TileNode::shouldSubDivide(TerrainCuller* culler, const SelectionInfo& selectionInfo)
+{    
     unsigned currLOD = _key.getLOD();
-    if (currLOD < selectionInfo.numLods() && currLOD != selectionInfo.numLods()-1)
+
+    EngineContext* context = culler->getEngineContext();
+
+    if (context->getOptions().rangeMode() == osg::LOD::PIXEL_SIZE_ON_SCREEN)
     {
-        return _surface->anyChildBoxIntersectsSphere(
-            cv->getViewPointLocal(), 
-            (float)selectionInfo.visParameters(currLOD+1)._visibilityRange2,
-            cv->getLODScale());
+        float pixelSize = -1.0;
+        if (context->getEngine()->getComputeRangeCallback())
+        {
+            pixelSize = (*context->getEngine()->getComputeRangeCallback())(this, *culler->_cv);
+        }    
+        if (pixelSize <= 0.0)
+        {
+            pixelSize = culler->clampedPixelSize(getBound());
+        }
+        return (pixelSize > context->getOptions().tilePixelSize().get() * 4);
     }
+    else
+    {
+        float range = (float)selectionInfo.visParameters(currLOD+1)._visibilityRange2;
+        if (currLOD < selectionInfo.getNumLODs() && currLOD != selectionInfo.getNumLODs()-1)
+        {
+            return _surface->anyChildBoxIntersectsSphere(
+                culler->getViewPointLocal(), 
+                range,
+                culler->getLODScale());
+        }
+    }                 
     return false;
 }
 
-
 bool
-TileNode::isVisible(osg::CullStack* stack) const
-{
-#ifdef VISIBILITY_PRECHECK
-    return _surface->isVisible( stack );
-#else
-    return true;
-#endif
-}
-
-bool
-TileNode::cull_stealth(osgUtil::CullVisitor* cv)
+TileNode::cull_stealth(TerrainCuller* culler)
 {
     bool visible = false;
 
-    EngineContext* context = VisitorData::fetch<EngineContext>(*cv, ENGINE_CONTEXT_TAG);
+    EngineContext* context = culler->getEngineContext();
 
     // Shows all culled tiles, good for testing culling
-    unsigned frame = cv->getFrameStamp()->getFrameNumber();
+    unsigned frame = culler->getFrameStamp()->getFrameNumber();
 
     if ( frame - _lastAcceptSurfaceFrame < 2u )
     {
-        acceptSurface( cv, context );
+        _surface->accept( *culler );
     }
 
     else if ( _childrenReady )
     {
         for(int i=0; i<4; ++i)
         {
-            getSubTile(i)->accept_cull_stealth( cv );
+            getSubTile(i)->accept( *culler );
         }
     }
 
@@ -332,19 +378,18 @@ TileNode::cull_stealth(osgUtil::CullVisitor* cv)
 }
 
 bool
-TileNode::cull(osgUtil::CullVisitor* cv)
+TileNode::cull(TerrainCuller* culler)
 {
-    EngineContext* context = VisitorData::fetch<EngineContext>(*cv, ENGINE_CONTEXT_TAG);
-    const SelectionInfo& selectionInfo = context->getSelectionInfo();
+    EngineContext* context = culler->getEngineContext();
 
     // Horizon check the surface first:
-    if ( !_surface->isVisible(cv) )
+    if (!_surface->isVisibleFrom(culler->getViewPointLocal()))
     {
         return false;
     }
     
     // determine whether we can and should subdivide to a higher resolution:
-    bool childrenInRange = shouldSubDivide(cv, selectionInfo);
+    bool childrenInRange = shouldSubDivide(culler, context->getSelectionInfo());
 
     // whether it is OK to create child TileNodes is necessary.
     bool canCreateChildren = childrenInRange;
@@ -363,7 +408,7 @@ TileNode::cull(osgUtil::CullVisitor* cv)
     
     // If this is an inherit-viewpoint camera, we don't need it to invoke subdivision
     // because we want only the tiles loaded by the true viewpoint.
-    const osg::Camera* cam = cv->getCurrentCamera();
+    const osg::Camera* cam = culler->getCamera();
     if ( cam && cam->getReferenceFrame() == osg::Camera::ABSOLUTE_RF_INHERIT_VIEWPOINT )
     {
         canCreateChildren = false;
@@ -398,12 +443,8 @@ TileNode::cull(osgUtil::CullVisitor* cv)
         {
             for(int i=0; i<4; ++i)
             {
-                getSubTile(i)->accept_cull(cv);
+                getSubTile(i)->accept(*culler);
             }
-
-            // if we traversed all children, but they all return "not visible",
-            // that means it's a horizon-culled tile anyway and we don't need
-            // to add any drawables.
         }
 
         // If we don't traverse the children, traverse this node's payload.
@@ -422,61 +463,33 @@ TileNode::cull(osgUtil::CullVisitor* cv)
     // accept this surface if necessary.
     if ( canAcceptSurface )
     {
-        acceptSurface( cv, context );
-        _lastAcceptSurfaceFrame.exchange( cv->getFrameStamp()->getFrameNumber() );
+        _surface->accept( *culler );
+        _lastAcceptSurfaceFrame.exchange( culler->getFrameStamp()->getFrameNumber() );
     }
 
-       
-    // Run any patch callbacks.
-    context->invokeTilePatchCallbacks( cv, getTileKey(), _payloadStateSet.get(), _patch.get() );
-
     // If this tile is marked dirty, try loading data.
     if ( _dirty && canLoadData )
     {
-        load( *cv );
+        load( culler );
     }
 
     return true;
 }
 
 bool
-TileNode::acceptSurface(osgUtil::CullVisitor* cv, EngineContext* context)
-{
-    OE_START_TIMER(acceptSurface);
-
-    // The reason we push the top-leel surface SS for every node is because
-    // of the patch callbacks. Instead of doing this we need a way to put
-    // patch traversals in their own top-level bin...
-
-    cv->pushStateSet( context->_surfaceSS.get() );
-    cv->pushStateSet( _payloadStateSet.get() );
-    _surface->accept( *cv );
-    cv->popStateSet();
-    cv->popStateSet();
-
-    REPORT("TileNode::acceptSurface", acceptSurface);
-
-    return true; //visible;
-}
-
-bool
-TileNode::accept_cull(osgUtil::CullVisitor* cv)
+TileNode::accept_cull(TerrainCuller* culler)
 {
     bool visible = false;
     
-    if (cv)
+    if (culler)
     {
         // update the timestamp so this tile doesn't become dormant.
-        _lastTraversalFrame.exchange( cv->getFrameStamp()->getFrameNumber() );
-        _lastTraversalTime = cv->getFrameStamp()->getReferenceTime();
+        _lastTraversalFrame.exchange( culler->getFrameStamp()->getFrameNumber() );
+        _lastTraversalTime = culler->getFrameStamp()->getReferenceTime();
 
-        if ( !cv->isCulled(*this) )
+        if ( !culler->isCulled(*this) )
         {
-            cv->pushStateSet( getStateSet() );
-
-            visible = cull( cv );
-
-            cv->popStateSet();
+            visible = cull( culler );
         }
     }
 
@@ -484,17 +497,13 @@ TileNode::accept_cull(osgUtil::CullVisitor* cv)
 }
 
 bool
-TileNode::accept_cull_stealth(osgUtil::CullVisitor* cv)
+TileNode::accept_cull_stealth(TerrainCuller* culler)
 {
     bool visible = false;
     
-    if (cv)
+    if (culler)
     {
-        cv->pushStateSet( getStateSet() );
-
-        visible = cull_stealth( cv );
-
-        cv->popStateSet();
+        visible = cull_stealth( culler );
     }
 
     return visible;
@@ -506,15 +515,18 @@ TileNode::traverse(osg::NodeVisitor& nv)
     // Cull only:
     if ( nv.getVisitorType() == nv.CULL_VISITOR )
     {
-        osgUtil::CullVisitor* cv = dynamic_cast<osgUtil::CullVisitor*>(&nv);
-
-        if (VisitorData::isSet(nv, "osgEarth.Stealth"))
+        if (_empty == false)
         {
-            accept_cull_stealth( cv );
-        }
-        else
-        {
-            accept_cull( cv );
+            TerrainCuller* culler = dynamic_cast<TerrainCuller*>(&nv);
+        
+            if (VisitorData::isSet(culler->getParent(), "osgEarth.Stealth"))
+            {
+                accept_cull_stealth( culler );
+            }
+            else
+            {
+                accept_cull( culler );
+            }
         }
     }
 
@@ -532,8 +544,7 @@ TileNode::traverse(osg::NodeVisitor& nv)
         }
 
         // Otherwise traverse the surface.
-        // TODO: in what situations should we traverse the landcover as well? GL compile?
-        else 
+        else if (_surface.valid())
         {
             _surface->accept( nv );
         }
@@ -544,6 +555,7 @@ void
 TileNode::createChildren(EngineContext* context)
 {
     // NOTE: Ensure that _mutex is locked before calling this fucntion!
+    //OE_WARN << "Creating children for " << _key.str() << std::endl;
 
     // Create the four child nodes.
     for(unsigned quadrant=0; quadrant<4; ++quadrant)
@@ -551,178 +563,417 @@ TileNode::createChildren(EngineContext* context)
         TileNode* node = new TileNode();
         if (context->getOptions().minExpiryFrames().isSet())
         {
-            node->setMinimumExpiryFrames( *context->getOptions().minExpiryFrames() );
+            node->setMinimumExpirationFrames( *context->getOptions().minExpiryFrames() );
         }
         if (context->getOptions().minExpiryTime().isSet())
         {         
-            node->setMinimumExpiryTime( *context->getOptions().minExpiryTime() );
+            node->setMinimumExpirationTime( *context->getOptions().minExpiryTime() );
         }
 
         // Build the surface geometry:
-        node->create( getTileKey().createChildKey(quadrant), context );
+        node->create( getKey().createChildKey(quadrant), this, context );
 
         // Add to the scene graph.
         addChild( node );
-
-        // Inherit the samplers with new scale/bias information.
-        node->inheritState( context );
     }
 }
 
-bool
-TileNode::inheritState(EngineContext* context)
+void
+TileNode::merge(const TerrainTileModel* model, const RenderBindings& bindings)
 {
-    // Find the parent node. It will only be null if this is a "first LOD" tile.
-    TileNode* parent = getNumParents() > 0 ? dynamic_cast<TileNode*>(getParent(0)) : 0L;
-
-    bool changesMade = false;
-
-    // which quadrant is this tile in?
-    unsigned quadrant = getTileKey().getQuadrant();
+    bool newElevationData = false;
 
-    // default inheritance of the elevation data for bounding purposes:
-    osg::ref_ptr<const osg::Image> elevRaster;
-    osg::Matrixf                   elevMatrix;
-    if ( parent )
+#if 1
+    const SamplerBinding& color = bindings[SamplerBinding::COLOR];
+    if (color.isActive())
     {
-        elevRaster = parent->getElevationRaster();
-        elevMatrix = parent->getElevationMatrix();
-        elevMatrix.preMult( scaleBias[quadrant] );
-    }
-
-    // Find all the sampler matrix uniforms and scale/bias them to the current quadrant.
-    // This will inherit textures and use the proper sub-quadrant until new data arrives (later).
-    for( RenderBindings::const_iterator binding = context->getRenderBindings().begin(); binding != context->getRenderBindings().end(); ++binding )
-    {
-        if ( binding->usage().isSetTo(binding->COLOR) )
+        for(TerrainTileColorLayerModelVector::const_iterator i = model->colorLayers().begin();
+            i != model->colorLayers().end();
+            ++i)
         {
-            if ( parent && parent->getStateSet() )
+            TerrainTileImageLayerModel* model = dynamic_cast<TerrainTileImageLayerModel*>(i->get());
+            if (model)
             {
-                MPTexture* parentMPTex = parent->getMPTexture();
-                _mptex->inheritState( parentMPTex, scaleBias[quadrant] );
-                changesMade = true;
+                if (model->getTexture())
+                {
+                    RenderingPass* pass = _renderModel.getPass(model->getImageLayer()->getUID());
+                    if (!pass)
+                    {
+                        pass = &_renderModel.addPass();
+                        pass->setLayer(model->getLayer());
+
+                        // This is a new pass that just showed up at this LOD
+                        // Since it just arrived at this LOD, make the parent the same as the color.
+                        if (bindings[SamplerBinding::COLOR_PARENT].isActive())
+                        {
+                            pass->samplers()[SamplerBinding::COLOR_PARENT]._texture = model->getTexture();
+                            pass->samplers()[SamplerBinding::COLOR_PARENT]._matrix.makeIdentity();
+                        }
+                    }
+                    pass->samplers()[SamplerBinding::COLOR]._texture = model->getTexture();
+                    pass->samplers()[SamplerBinding::COLOR]._matrix = *model->getMatrix();
+
+                    // Handle an RTT image layer:
+                    if (model->getImageLayer() && model->getImageLayer()->createTextureSupported())
+                    {
+                        // Check the texture's userdata for a Node. If there is one there,
+                        // render it to the texture using the Tile Rasterizer service.
+                        // TODO: consider hanging on to this texture and not applying it to
+                        // the live tile until the RTT is complete. (Prevents unsightly flashing)
+                        GeoNode* rttNode = dynamic_cast<GeoNode*>(model->getTexture()->getUserData());
+                        if (rttNode)
+                        {
+                            _context->getTileRasterizer()->push(rttNode->_node.get(), model->getTexture(), rttNode->_extent);
+                        }
+                    }
+                }
             }
-        }
 
-        else if ( binding->usage().isSetTo(binding->COLOR_PARENT) )
-        {
-            //nop -- this is handled as part of the COLOR binding
+            else // non-image color layer (like splatting, e.g.)
+            {
+                TerrainTileColorLayerModel* model = i->get();
+                if (model && model->getLayer())
+                {
+                    RenderingPass* pass = _renderModel.getPass(model->getLayer()->getUID());
+                    if (!pass)
+                    {
+                        pass = &_renderModel.addPass();
+                        pass->setLayer(model->getLayer());
+                    }
+                }
+            }
         }
-
-        else
+    }
+#else
+    // Add color passes:
+    const SamplerBinding& color = bindings[SamplerBinding::COLOR];
+    if (color.isActive())
+    {
+        for (TerrainTileImageLayerModelVector::const_iterator i = model->colorLayers().begin();
+            i != model->colorLayers().end();
+            ++i)
         {
-            osg::StateAttribute* sa = getStateSet()->getTextureAttribute(binding->unit(), osg::StateAttribute::TEXTURE);        
-
-            // If the attribute isn't present, that means we are inheriting it from above.
-            // So construct a new scale/bias matrix.
-            if ( sa == 0L )
+            TerrainTileImageLayerModel* layer = i->get();
+            if (layer && layer->getTexture())
             {
-                osg::Matrixf matrix;
+                RenderingPass* pass = _renderModel.getPass(layer->getImageLayer()->getUID());
+                if (!pass)
+                {
+                    pass = &_renderModel.addPass();
+                    pass->_layer = layer->getImageLayer();
+                    pass->_imageLayer = layer->getImageLayer();
+                    pass->_sourceUID = layer->getImageLayer()->getUID();
+
+                    // This is a new pass that just showed up at this LOD
+                    // Since it just arrived at this LOD, make the parent the same as the color.
+                    if (bindings[SamplerBinding::COLOR_PARENT].isActive())
+                    {
+                        pass->_samplers[SamplerBinding::COLOR_PARENT]._texture = layer->getTexture();
+                        pass->_samplers[SamplerBinding::COLOR_PARENT]._matrix.makeIdentity();
+                    }
+                }
+                pass->_samplers[SamplerBinding::COLOR]._texture = layer->getTexture();
+                //pass->_samplers[SamplerBinding::COLOR]._matrix.makeIdentity();
+                pass->_samplers[SamplerBinding::COLOR]._matrix = *layer->getMatrix();
 
-                // Find the parent's matrix and scale/bias it to this quadrant:
-                if ( parent && parent->getStateSet() )
+                // Handle an RTT image layer:
+                if (layer->getImageLayer() && layer->getImageLayer()->createTextureSupported())
                 {
-                    const osg::Uniform* matrixUniform = parent->getStateSet()->getUniform( binding->matrixName() );
-                    if ( matrixUniform )
+                    // Check the texture's userdata for a Node. If there is one there,
+                    // render it to the texture using the Tile Rasterizer service.
+                    // TODO: consider hanging on to this texture and not applying it to
+                    // the live tile until the RTT is complete. (Prevents unsightly flashing)
+                    GeoNode* rttNode = dynamic_cast<GeoNode*>(layer->getTexture()->getUserData());
+                    if (rttNode)
                     {
-                        matrixUniform->get( matrix );
-                        matrix.preMult( scaleBias[quadrant] );
+                        _context->getTileRasterizer()->push(rttNode->_node.get(), layer->getTexture(), rttNode->_extent);
                     }
                 }
+            }
+        }
+    }
+#endif
 
-                // Add a new uniform with the scale/bias'd matrix:
-                osg::StateSet* stateSet = getOrCreateStateSet();
-                stateSet->removeUniform( binding->matrixName() );
-                stateSet->addUniform( context->getOrCreateMatrixUniform(binding->matrixName(), matrix) );
-                changesMade = true;
+    // Elevation:
+    const SamplerBinding& elevation = bindings[SamplerBinding::ELEVATION];
+    if (elevation.isActive() && model->elevationModel().valid() && model->elevationModel()->getTexture())
+    {
+        osg::Texture* tex = model->elevationModel()->getTexture();
+
+        // always keep the elevation image around because we use it for bounding box computation:
+        tex->setUnRefImageDataAfterApply(false);
+
+        _renderModel._sharedSamplers[SamplerBinding::ELEVATION]._texture = tex;
+        _renderModel._sharedSamplers[SamplerBinding::ELEVATION]._matrix.makeIdentity();
+
+        setElevationRaster(tex->getImage(0), osg::Matrixf::identity());
+
+        newElevationData = true;
+    } 
+
+    // Normals:
+    const SamplerBinding& normals = bindings[SamplerBinding::NORMAL];
+    if (normals.isActive() && model->normalModel().valid() && model->normalModel()->getTexture())
+    {
+        osg::Texture* tex = model->normalModel()->getTexture();
+        // keep the normal map around because we might update it later in "ping"
+        tex->setUnRefImageDataAfterApply(false);
+
+        _renderModel._sharedSamplers[SamplerBinding::NORMAL]._texture = tex;
+        _renderModel._sharedSamplers[SamplerBinding::NORMAL]._matrix.makeIdentity();
+
+        updateNormalMap();
+    }
+
+    // Other Shared Layers:
+    for (unsigned i = 0; i < model->sharedLayers().size(); ++i)
+    {
+        TerrainTileImageLayerModel* layerModel = model->sharedLayers()[i].get();
+        if (layerModel->getTexture())
+        {
+            // locate the shared binding corresponding to this layer:
+            UID uid = layerModel->getImageLayer()->getUID();
+            unsigned bindingIndex = INT_MAX;
+            for(unsigned i=SamplerBinding::SHARED; i<bindings.size() && bindingIndex==INT_MAX; ++i) {
+                if (bindings[i].isActive() && bindings[i].sourceUID().isSetTo(uid)) {
+                    bindingIndex = i;
+                }                   
             }
 
-            // If this is elevation data, record the new raster so we can apply it to the node.
-            else if ( binding->usage().isSetTo(binding->ELEVATION) )
+            if (bindingIndex < INT_MAX)
             {
-                osg::Texture* t = static_cast<osg::Texture*>(sa);
-                elevRaster = t->getImage(0);
-                elevMatrix = osg::Matrixf::identity();
+                osg::Texture* tex = layerModel->getTexture();
+                _renderModel._sharedSamplers[bindingIndex]._texture = tex;
+                _renderModel._sharedSamplers[bindingIndex]._matrix.makeIdentity();
             }
         }
     }
 
-    // If we found one, communicate it to the node and its children.
-    if (elevRaster.valid())
+    // Patch Layers
+    for (unsigned i = 0; i < model->patchLayers().size(); ++i)
     {
-        if (elevRaster.get() != getElevationRaster() || elevMatrix != getElevationMatrix() )
-        {
-            setElevationRaster( elevRaster.get(), elevMatrix );
-            changesMade = true;
-        }
+        TerrainTilePatchLayerModel* layerModel = model->patchLayers()[i].get();
     }
 
-    // finally, update the uniforms for terrain morphing
-    updateTileUniforms( context->getSelectionInfo() );
-
-    if ( !changesMade )
+    if (_childrenReady)
     {
-        OE_INFO << LC << _key.str() << ", good, no changes :)\n";
+        getSubTile(0)->refreshInheritedData(this, bindings);
+        getSubTile(1)->refreshInheritedData(this, bindings);
+        getSubTile(2)->refreshInheritedData(this, bindings);
+        getSubTile(3)->refreshInheritedData(this, bindings);
     }
-    else
+
+    if (newElevationData)
     {
-        dirtyBound();
+        OE_DEBUG << LC << "notify (merge) key " << getKey().str() << std::endl;
+        _context->getEngine()->getTerrain()->notifyTileAdded(getKey(), this);
     }
+}
 
-    return changesMade;
+void TileNode::loadChildren()
+{
+    _mutex.lock();
+
+    if ( !_childrenReady )
+    {        
+        // Create the children
+        createChildren( _context.get() );        
+        _childrenReady = true;        
+        int numChildren = getNumChildren();
+        if ( numChildren > 0 )
+        {
+            for(int i=0; i<numChildren; ++i)
+            {
+                TileNode* child = getSubTile(i);
+                if (child)
+                {
+                    // Load the children's data.
+                    child->loadSync();
+                }
+            }
+        }
+    }
+    _mutex.unlock();    
 }
 
 void
-TileNode::mergeStateSet(osg::StateSet* stateSet, MPTexture* mptex, const RenderBindings& bindings)
-{
-    _mptex->merge( mptex );    
-    getStateSet()->merge(*stateSet);
+TileNode::refreshSharedSamplers(const RenderBindings& bindings)
+{    
+    for (unsigned i = 0; i < _renderModel._sharedSamplers.size(); ++i)
+    {
+        if (bindings[i].isActive() == false)
+        {
+            _renderModel._sharedSamplers[i]._texture = 0L;
+        }
+    }
 }
 
 void
-TileNode::load(osg::NodeVisitor& nv)
+TileNode::refreshInheritedData(TileNode* parent, const RenderBindings& bindings)
 {
-    // Access the context:
-    EngineContext* context = VisitorData::fetch<EngineContext>(nv, ENGINE_CONTEXT_TAG);
+    // Run through this tile's rendering data and re-inherit textures and matrixes
+    // from the parent. When a TileNode gets new data (via a call to merge), any
+    // children of that tile that are inheriting textures or matrixes need to 
+    // refresh to inherit that new data. In turn, those tile's children then need
+    // to update as well. This method does that.
 
-    // Create a new load request on demand:
-    if ( !_loadRequest.valid() )
+    // which quadrant is this tile in?
+    unsigned quadrant = getKey().getQuadrant();
+
+    // Count the number of inherited samplers so we know when to stop. If none of the
+    // samplers in this tile inherit from the parent, there is no need to continue
+    // down the Tile tree.
+    unsigned changes = 0;
+
+    RenderingPasses& parentPasses = parent->_renderModel._passes;
+
+    for (unsigned p = 0; p<parentPasses.size(); ++p)
     {
-        Threading::ScopedMutexLock lock(_mutex);
-        if ( !_loadRequest.valid() )
+        const RenderingPass& parentPass = parentPasses[p];
+
+        RenderingPass* myPass = _renderModel.getPass(parentPass.sourceUID());
+
+        // Inherit the samplers for this pass.
+        if (myPass)
         {
-            _loadRequest = new LoadTileData( this, context );
-            _loadRequest->setName( _key.str() );
-            _loadRequest->setTileKey( _key );
+            Samplers& samplers = myPass->samplers();
+            for (unsigned s = 0; s < samplers.size(); ++s)
+            {
+                Sampler& mySampler = samplers[s];
+                
+                // the color-parent gets special treatment, since it is not included
+                // in the TileModel (rather it is always derived here).
+                if (s == SamplerBinding::COLOR_PARENT && bindings[SamplerBinding::COLOR_PARENT].isActive())
+                {
+                    const Samplers& parentSamplers = parentPass.samplers();
+                    const Sampler& parentSampler = parentSamplers[SamplerBinding::COLOR];
+                    osg::Matrixf newMatrix = parentSampler._matrix;
+                    newMatrix.preMult(scaleBias[quadrant]);
+
+                    // Did something change?
+                    if (mySampler._texture.get() != parentSampler._texture.get() ||
+                        mySampler._matrix != newMatrix)
+                    {
+                        if (parentSampler._texture.valid())
+                        {
+                            // set the parent-color texture to the parent's color texture
+                            // and scale/bias the matrix.
+                            mySampler._texture = parentSampler._texture.get();
+                            mySampler._matrix = newMatrix;
+                        }
+                        else
+                        {
+                            // parent has no color texture? Then set our parent-color
+                            // equal to our normal color texture.
+                            mySampler._texture = samplers[SamplerBinding::COLOR]._texture.get();
+                            mySampler._matrix = samplers[SamplerBinding::COLOR]._matrix;
+                        }
+                        ++changes;
+                    }
+                }
+
+                // all other samplers just need to inherit from their parent 
+                // and scale/bias their texture matrix.
+                else if (!mySampler._texture.valid() || !mySampler._matrix.isIdentity())
+                {
+                    const Sampler& parentSampler = parentPass.samplers()[s];
+                    mySampler._texture = parentSampler._texture.get();
+                    mySampler._matrix = parentSampler._matrix;
+                    mySampler._matrix.preMult(scaleBias[quadrant]);
+                    ++changes;
+                }
+            }
+        }
+        else
+        {
+            // Pass exists in the parent node, but not in this node, so add it now.
+            myPass = &_renderModel.addPass();
+            *myPass = parentPass;
+
+            for (unsigned s = 0; s < myPass->samplers().size(); ++s)
+            {
+                Sampler& sampler = myPass->samplers()[s];
+                sampler._matrix.preMult(scaleBias[quadrant]);
+            }
+            ++changes;
         }
     }
 
-    
-    // Construct the load PRIORITY: 0=lowest, 1=highest.
-    
-    const SelectionInfo& si = context->getSelectionInfo();
-    int lod     = getTileKey().getLOD();
-    int numLods = si.numLods();
+    // Handle all the shared samples (elevation, normal, etc.)
+    const Samplers& parentSharedSamplers = parent->_renderModel._sharedSamplers;
+    Samplers& mySharedSamplers = _renderModel._sharedSamplers;
+    for (unsigned s = 0; s<mySharedSamplers.size(); ++s)
+    {        
+        Sampler& mySampler = mySharedSamplers[s];
+        if (!mySampler._texture.valid() || !mySampler._matrix.isIdentity())
+        {
+            const Sampler& parentSampler = parentSharedSamplers[s];
+            mySampler._texture = parentSampler._texture.get();
+            mySampler._matrix = parentSampler._matrix;
+            mySampler._matrix.preMult(scaleBias[quadrant]);
+            ++changes;
+
+            // Update the local elevation raster cache (for culling and intersection testing).
+            if (s == SamplerBinding::ELEVATION && mySampler._texture.valid())
+            {
+                this->setElevationRaster(mySampler._texture->getImage(0), mySampler._matrix);
+            }
+        }
+    }
+
+    if (changes > 0)
+    {
+        dirtyBound(); // only for elev/patch changes maybe?
+
+        if (_childrenReady)
+        {
+            getSubTile(0)->refreshInheritedData(this, bindings);
+            getSubTile(1)->refreshInheritedData(this, bindings);
+            getSubTile(2)->refreshInheritedData(this, bindings);
+            getSubTile(3)->refreshInheritedData(this, bindings);
+        }
+    }
+    else
+    {
+        //OE_INFO << LC << _key.str() << ": refreshInheritedData, stopped short.\n";
+    }
+}
+
+void
+TileNode::load(TerrainCuller* culler)
+{    
+    const SelectionInfo& si = _context->getSelectionInfo();
+    int lod     = getKey().getLOD();
+    int numLods = si.getNumLODs();
     
     // LOD priority is in the range [0..numLods]
     float lodPriority = (float)lod;
-    if ( context->getOptions().highResolutionFirst() == false )
+    if ( _context->getOptions().highResolutionFirst() == false )
         lodPriority = (float)(numLods - lod);
 
-    float distance = nv.getDistanceToViewPoint(getBound().center(), true);
+    float distance = culler->getDistanceToViewPoint(getBound().center(), true);
 
     // dist priority uis in the range [0..1]
     float distPriority = 1.0 - distance/si.visParameters(0)._visibilityRange;
 
-    // add thenm together, and you get tiles sorted first by lodPriority (because of
-    // the biggest range), and second by distance.
+    // add them together, and you get tiles sorted first by lodPriority
+    // (because of the biggest range), and second by distance.
     float priority = lodPriority + distPriority;
 
     // normalize the composite priority to [0..1].
-    priority /= (float)(numLods+1);
+    //priority /= (float)(numLods+1); // GW: moved this to the PagerLoader.
 
     // Submit to the loader.
-    context->getLoader()->load( _loadRequest.get(), priority, nv );
+    _context->getLoader()->load( _loadRequest.get(), priority, *culler );
+}
+
+void
+TileNode::loadSync()
+{
+    osg::ref_ptr<LoadTileData> loadTileData = new LoadTileData(this, _context.get());
+    loadTileData->setEnableCancelation(false);
+    loadTileData->invoke();
+    loadTileData->apply(0L);
 }
 
 bool
@@ -742,3 +993,90 @@ TileNode::removeSubTiles()
     _childrenReady = false;
     this->removeChildren(0, this->getNumChildren());
 }
+
+
+void
+TileNode::notifyOfArrival(TileNode* that)
+{
+    if (_key.createNeighborKey(1, 0) == that->getKey())
+        _eastNeighbor = that;
+
+    if (_key.createNeighborKey(0, 1) == that->getKey())
+        _southNeighbor = that;
+
+    updateNormalMap();
+}
+
+void
+TileNode::updateNormalMap()
+{
+    if ( !_stitchNormalMap )
+        return;
+
+    Sampler& thisNormalMap = _renderModel._sharedSamplers[SamplerBinding::NORMAL];
+    if (!thisNormalMap._texture.valid() || !thisNormalMap._matrix.isIdentity() || !thisNormalMap._texture->getImage(0))
+        return;
+
+    if (!_eastNeighbor.valid() || !_southNeighbor.valid())
+        return;
+
+    osg::ref_ptr<TileNode> east;
+    if (_eastNeighbor.lock(east))
+    {
+        const Sampler& thatNormalMap = east->_renderModel._sharedSamplers[SamplerBinding::NORMAL];
+        if (!thatNormalMap._texture.valid() || !thatNormalMap._matrix.isIdentity() || !thatNormalMap._texture->getImage(0))
+            return;
+
+        osg::Image* thisImage = thisNormalMap._texture->getImage(0);
+        osg::Image* thatImage = thatNormalMap._texture->getImage(0);
+
+        int width = thisImage->s();
+        int height = thisImage->t();
+        if ( width != thatImage->s() || height != thatImage->t() )
+            return;
+
+        // Just copy the neighbor's edge normals over to our texture.
+        // Averaging them would be more accurate, but then we'd have to
+        // re-generate each texture multiple times instead of just once.
+        // Besides, there's almost no visual difference anyway.
+        ImageUtils::PixelReader readThat(thatImage);
+        ImageUtils::PixelWriter writeThis(thisImage);
+        
+        for (int t=0; t<height; ++t)
+        {
+            writeThis(readThat(0, t), width-1, t);
+        }
+
+        thisImage->dirty();
+    }
+
+    osg::ref_ptr<TileNode> south;
+    if (_southNeighbor.lock(south))
+    {
+        const Sampler& thatNormalMap = south->_renderModel._sharedSamplers[SamplerBinding::NORMAL];
+        if (!thatNormalMap._texture.valid() || !thatNormalMap._matrix.isIdentity() || !thatNormalMap._texture->getImage(0))
+            return;
+
+        osg::Image* thisImage = thisNormalMap._texture->getImage(0);
+        osg::Image* thatImage = thatNormalMap._texture->getImage(0);
+
+        int width = thisImage->s();
+        int height = thisImage->t();
+        if ( width != thatImage->s() || height != thatImage->t() )
+            return;
+
+        // Just copy the neighbor's edge normals over to our texture.
+        // Averaging them would be more accurate, but then we'd have to
+        // re-generate each texture multiple times instead of just once.
+        // Besides, there's almost no visual difference anyway.
+        ImageUtils::PixelReader readThat(thatImage);
+        ImageUtils::PixelWriter writeThis(thisImage);
+
+        for (int s=0; s<width; ++s)
+            writeThis(readThat(s, height-1), s, 0);
+
+        thisImage->dirty();
+    }
+
+    //OE_INFO << LC << _key.str() << " : updated normal map.\n";
+}
\ No newline at end of file
diff --git a/src/osgEarthDrivers/engine_rex/TileNodeRegistry b/src/osgEarthDrivers/engine_rex/TileNodeRegistry
index 62e8344..bebc477 100644
--- a/src/osgEarthDrivers/engine_rex/TileNodeRegistry
+++ b/src/osgEarthDrivers/engine_rex/TileNodeRegistry
@@ -203,7 +203,7 @@ namespace osgEarth { namespace Drivers { namespace RexTerrainEngine
         /** Tells the registry to listen for the TileNode for the specific key
             to arrive, and upon its arrival, notifies the waiter. After notifying
             the waiter, it removes the listen request. */
-        void listenFor(const TileKey& keyToWaitFor, TileNode* waiter);
+        //void listenFor(const TileKey& keyToWaitFor, TileNode* waiter);
 
         /** Take an arbitrary node from the registry. */
         TileNode* takeAny();
@@ -232,6 +232,14 @@ namespace osgEarth { namespace Drivers { namespace RexTerrainEngine
             that node is not NULL */
         void addSafely(TileNode* node);
         void removeSafely(const TileKey& key);
+
+        /** Tells the registry to listen for the TileNode for the specific key
+            to arrive, and upon its arrival, notifies the waiter. After notifying
+            the waiter, it removes the listen request. (assumes lock held) */
+        void startListeningFor(const TileKey& keyToWaitFor, TileNode* waiter);
+
+        /** Removes a listen request set by startListeningFor (assumes lock held) */
+        void stopListeningFor(const TileKey& keyToWairFor, TileNode* waiter);
     };
 
 } } } // namespace osgEarth::Drivers::MPTerrainEngine
diff --git a/src/osgEarthDrivers/engine_rex/TileNodeRegistry.cpp b/src/osgEarthDrivers/engine_rex/TileNodeRegistry.cpp
index 0e6d122..4cbe7b6 100644
--- a/src/osgEarthDrivers/engine_rex/TileNodeRegistry.cpp
+++ b/src/osgEarthDrivers/engine_rex/TileNodeRegistry.cpp
@@ -18,6 +18,8 @@
 */
 #include "TileNodeRegistry"
 
+#include <osgEarth/Metrics>
+
 using namespace osgEarth::Drivers::RexTerrainEngine;
 using namespace osgEarth;
 
@@ -98,13 +100,17 @@ TileNodeRegistry::setDirty(const GeoExtent& extent,
 void
 TileNodeRegistry::addSafely(TileNode* tile)
 {
-    _tiles.insert( tile->getTileKey(), tile );
+    _tiles.insert( tile->getKey(), tile );
     //_tiles[ tile->getTileKey() ] = tile;
     if ( _revisioningEnabled )
         tile->setMapRevision( _maprev );
+    
+    // Start waiting on our neighbors
+    startListeningFor(tile->getKey().createNeighborKey(1, 0), tile);
+    startListeningFor(tile->getKey().createNeighborKey(0, 1), tile);
 
     // check for tiles that are waiting on this tile, and notify them!
-    TileKeyOneToMany::iterator notifier = _notifiers.find( tile->getTileKey() );
+    TileKeyOneToMany::iterator notifier = _notifiers.find( tile->getKey() );
     if ( notifier != _notifiers.end() )
     {
         TileKeySet& listeners = notifier->second;
@@ -120,26 +126,39 @@ TileNodeRegistry::addSafely(TileNode* tile)
         _notifiers.erase( notifier );
     }
 
-    OE_TEST << LC << _name 
+    OE_DEBUG << LC << _name 
         << ": tiles=" << _tiles.size()
         << ", notifiers=" << _notifiers.size()
         << std::endl;
+
+    Metrics::counter("RexStats", "Tiles", _tiles.size());
 }
 
 void
 TileNodeRegistry::removeSafely(const TileKey& key)
 {
-    _tiles.erase( key );
-
-    for(TileKeyOneToMany::iterator i = _notifiers.begin(); i != _notifiers.end(); )
+    TileNode* tile = _tiles.find(key);
+    if (tile)
     {
-        i->second.erase( key );
+        // remove neighbor listeners:
+        stopListeningFor(key.createNeighborKey(1, 0), tile);
+        stopListeningFor(key.createNeighborKey(0, 1), tile);
+
+        // remove the tile.
+        _tiles.erase( key );
 
-        if ( i->second.size() == 0 )
-            _notifiers.erase( i++ ); // http://stackoverflow.com/a/8234813/4218920
-        else
-            ++i;
+        Metrics::counter("RexStats", "Tiles", _tiles.size());
     }
+
+    //for(TileKeyOneToMany::iterator i = _notifiers.begin(); i != _notifiers.end(); )
+    //{
+    //    i->second.erase( key );
+
+    //    if ( i->second.size() == 0 )
+    //        _notifiers.erase( i++ ); // http://stackoverflow.com/a/8234813/4218920
+    //    else
+    //        ++i;
+    //}
 }
 
 void
@@ -173,7 +192,7 @@ TileNodeRegistry::remove( TileNode* tile )
     if ( tile )
     {
         Threading::ScopedWriteLock exclusive( _tilesMutex );
-        removeSafely( tile->getTileKey() );
+        removeSafely( tile->getKey() );
     }
 }
   
@@ -229,6 +248,7 @@ TileNodeRegistry::empty() const
     return _tiles.empty();
 }
 
+#if 0
 void
 TileNodeRegistry::listenFor(const TileKey& tileToWaitFor, TileNode* waiter)
 {
@@ -248,13 +268,55 @@ TileNodeRegistry::listenFor(const TileKey& tileToWaitFor, TileNode* waiter)
         _notifiers[tileToWaitFor].insert( waiter->getTileKey() );
     }
 }
+#endif
+
+void
+TileNodeRegistry::startListeningFor(const TileKey& tileToWaitFor, TileNode* waiter)
+{
+    //Threading::ScopedMutexLock lock( _tilesMutex );
+    // ASSUME EXCLUSIVE LOCK
+    TileNode* tile = _tiles.find( tileToWaitFor );
+    if ( tile )
+    {
+        OE_DEBUG << LC << waiter->getKey().str() << " listened for " << tileToWaitFor.str()
+            << ", but it was already in the repo.\n";
+
+        waiter->notifyOfArrival( tile );
+    }
+    else
+    {
+        OE_DEBUG << LC << waiter->getKey().str() << " listened for " << tileToWaitFor.str() << ".\n";
+        //_notifications[tileToWaitFor].push_back( waiter->getKey() );
+        _notifiers[tileToWaitFor].insert( waiter->getKey() );
+    }
+}
+
+void
+TileNodeRegistry::stopListeningFor(const TileKey& tileToWaitFor, TileNode* waiter)
+{
+    //Threading::ScopedMutexLock lock( _tilesMutex );
+    // ASSUME EXCLUSIVE LOCK
+
+    TileKeyOneToMany::iterator i = _notifiers.find(tileToWaitFor);
+    if (i != _notifiers.end())
+    {
+        // remove the waiter from this set:
+        i->second.erase(waiter->getKey());
+
+        // if the set is now empty, remove the set entirely
+        if (i->second.empty())
+        {
+            _notifiers.erase(i);
+        }
+    }
+}
         
 TileNode*
 TileNodeRegistry::takeAny()
 {
     Threading::ScopedWriteLock exclusive( _tilesMutex );
     osg::ref_ptr<TileNode> tile = _tiles.begin()->second.tile.get();
-    removeSafely( tile->getTileKey() );
+    removeSafely( tile->getKey() );
     return tile.release();
 }
 
@@ -272,6 +334,8 @@ TileNodeRegistry::releaseAll(ResourceReleaser* releaser)
 
         _tiles.clear();
         _notifiers.clear();
+
+        Metrics::counter("RexStats", "Tiles", _tiles.size());
     }
 
     releaser->push(objects);
diff --git a/src/osgEarthDrivers/engine_rex/TileRenderModel b/src/osgEarthDrivers/engine_rex/TileRenderModel
new file mode 100644
index 0000000..8cf9799
--- /dev/null
+++ b/src/osgEarthDrivers/engine_rex/TileRenderModel
@@ -0,0 +1,171 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+ * Copyright 2008-2014 Pelican Mapping
+ * http://osgearth.org
+ *
+ * osgEarth is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+#ifndef OSGEARTH_REX_TILE_RENDER_MODEL
+#define OSGEARTH_REX_TILE_RENDER_MODEL 1
+
+#include "Common"
+#include "RenderBindings"
+#include <osgEarth/Common>
+#include <osgEarth/Containers> // for AutoArray
+#include <osgEarth/Layer>
+#include <osgEarth/ImageLayer>
+#include <osgEarth/PatchLayer>
+#include <osg/Texture>
+#include <osg/Matrix>
+#include <vector>
+
+namespace osgEarth { namespace Drivers { namespace RexTerrainEngine
+{
+    /**
+     * A single texture and its matrix. This corresponds to a 
+     * single SamplerBinding (above).
+     */
+    struct Sampler
+    {
+        osg::ref_ptr<osg::Texture> _texture;
+        osg::Matrixf _matrix;
+    };
+    typedef AutoArray<Sampler> Samplers;
+
+    /**
+     * Samplers (one per RenderBinding) specific to one rendering pass of a tile.
+     * Typically this is just the color and color parent samplers.
+     */
+    struct RenderingPass
+    {
+    public:
+        RenderingPass() :
+            _sourceUID(-1),
+            _samplers(SamplerBinding::COLOR_PARENT+1),
+            _visibleLayer(0L)
+            { }
+        
+        UID sourceUID() const { return _sourceUID; }
+        Samplers& samplers() { return _samplers; }
+        const Samplers& samplers() const  { return _samplers; }
+        const Layer* layer() const { return _layer.get(); }
+        const VisibleLayer* visibleLayer() const { return _visibleLayer; }
+
+        void releaseGLObjects(osg::State* state) const
+        {
+            for (unsigned s = 0; s<_samplers.size(); ++s)
+                if (_samplers[s]._texture.valid() && _samplers[s]._matrix.isIdentity())
+                    _samplers[s]._texture->releaseGLObjects(state);
+        }
+
+        void resizeGLObjectBuffers(unsigned size)
+        {
+            for (unsigned s = 0; s<_samplers.size(); ++s)
+                if (_samplers[s]._texture.valid() && _samplers[s]._matrix.isIdentity())
+                    _samplers[s]._texture->resizeGLObjectBuffers(size);
+        }
+
+        void setLayer(const Layer* layer) {
+            _layer = layer;
+            if (layer) {
+                _visibleLayer = dynamic_cast<const VisibleLayer*>(layer);
+                _sourceUID = layer->getUID();
+            }
+        }
+
+    private:
+        /** UID of the layer responsible for this rendering pass (usually an ImageLayer) */
+        UID _sourceUID;
+
+        /** Samplers specific to this rendering pass (COLOR, COLOR_PARENT) */
+        Samplers _samplers;
+
+        /** Layer respsonible for this rendering pass */
+        osg::ref_ptr<const Layer> _layer;
+
+        /** VisibleLayer responsible for this rendering pass (is _layer is a VisibleLayer) */
+        const VisibleLayer* _visibleLayer;
+
+    };
+
+    /**
+     * Unordered collection of rendering passes.
+     */
+    typedef std::vector<RenderingPass> RenderingPasses;
+
+    /**
+     * Everything necessary to render a single terrain tile.
+     * REX renders the terrain in multiple passes, one pass for each visible layer.
+     */
+    struct TileRenderModel
+    {
+        /** Samplers that are bound for every rendering pass (elevation, normal map, etc.) */
+        Samplers _sharedSamplers;
+
+        /** Samplers bound for each visible layer (color) */
+        RenderingPasses _passes;
+
+        /** Add a new rendering pass to the end of the list. */
+        RenderingPass& addPass()
+        {
+            _passes.resize(_passes.size()+1);
+            return _passes.back();
+        }
+
+        /** Look up a rendering pass by the corresponding layer ID */
+        const RenderingPass* getPass(UID uid) const
+        {
+            for (unsigned i = 0; i < _passes.size(); ++i) {
+                if (_passes[i].sourceUID() == uid)
+                    return &_passes[i];
+            }
+            return 0L;
+        }
+
+        /** Look up a rendering pass by the corresponding layer ID */
+        RenderingPass* getPass(UID uid)
+        {
+            for (unsigned i = 0; i < _passes.size(); ++i) {
+                if (_passes[i].sourceUID() == uid)
+                    return &_passes[i];
+            }
+            return 0L;
+        }
+
+        /** Deallocate GPU objects associated with this model */
+        void releaseGLObjects(osg::State* state) const
+        {
+            for (unsigned s = 0; s<_sharedSamplers.size(); ++s)
+                if (_sharedSamplers[s]._texture.valid() && _sharedSamplers[s]._matrix.isIdentity())
+                    _sharedSamplers[s]._texture->releaseGLObjects(state);
+
+            for (unsigned p = 0; p<_passes.size(); ++p)
+                _passes[p].releaseGLObjects(state);
+        }
+
+        /** Resize GL buffers associated with thie model */
+        void resizeGLObjectBuffers(unsigned size)
+        {
+            for (unsigned s = 0; s<_sharedSamplers.size(); ++s)
+                if (_sharedSamplers[s]._texture.valid() && _sharedSamplers[s]._matrix.isIdentity())
+                    _sharedSamplers[s]._texture->resizeGLObjectBuffers(size);
+
+            for (unsigned p = 0; p<_passes.size(); ++p)
+                _passes[p].resizeGLObjectBuffers(size);
+        }
+    };
+
+} } } // namespace osgEarth::Drivers::RexTerrainEngine
+
+#endif // OSGEARTH_REX_TILE_RENDER_MODEL
diff --git a/src/osgEarthDrivers/engine_rex/Unloader b/src/osgEarthDrivers/engine_rex/Unloader
index 919d8f8..9756439 100644
--- a/src/osgEarthDrivers/engine_rex/Unloader
+++ b/src/osgEarthDrivers/engine_rex/Unloader
@@ -33,14 +33,20 @@ namespace osgEarth { namespace Drivers { namespace RexTerrainEngine
     class TileNodeRegistry; // for UnloaderGroup
 
 
-    /** Pure interface class for a tile unloader. */
+    /**
+     * Pure interface class for a tile unloader. The Tile Unloader is 
+     * responsible for removing unused tiles from the scene graph and
+     * releasing any GL resources associated with them.
+     */
     class Unloader
     {
     public:
         virtual void unloadChildren(const std::vector<TileKey>& keys) =0;
     };
 
-    /** Group-based tile unloader. */
+    /**
+     * Group-based tile unloader.
+     */
     class UnloaderGroup : public osg::Group, public Unloader
     {
     public:
@@ -61,7 +67,7 @@ namespace osgEarth { namespace Drivers { namespace RexTerrainEngine
 
     protected:
         int                            _threshold;
-        std::vector<TileKey>           _parentKeys;
+        std::set<TileKey>              _parentKeys;
         TileNodeRegistry*              _tiles;
         osg::ref_ptr<ResourceReleaser> _releaser;
         mutable Threading::Mutex       _mutex;
diff --git a/src/osgEarthDrivers/engine_rex/Unloader.cpp b/src/osgEarthDrivers/engine_rex/Unloader.cpp
index 32b9863..7813b86 100644
--- a/src/osgEarthDrivers/engine_rex/Unloader.cpp
+++ b/src/osgEarthDrivers/engine_rex/Unloader.cpp
@@ -20,6 +20,8 @@
 #include "TileNode"
 #include "TileNodeRegistry"
 
+#include <osgEarth/Metrics>
+
 using namespace osgEarth::Drivers::RexTerrainEngine;
 
 
@@ -77,7 +79,7 @@ UnloaderGroup::unloadChildren(const std::vector<TileKey>& keys)
 {
     _mutex.lock();
     for(std::vector<TileKey>::const_iterator i = keys.begin(); i != keys.end(); ++i)
-        _parentKeys.push_back( *i );
+        _parentKeys.insert(*i);
     _mutex.unlock();
 }
 
@@ -85,12 +87,14 @@ void
 UnloaderGroup::traverse(osg::NodeVisitor& nv)
 {
     if ( nv.getVisitorType() == nv.UPDATE_VISITOR )
-    {
+    {        
         if ( _parentKeys.size() > _threshold )
         {
+            ScopedMetric m("Unloader expire");
+
             unsigned unloaded=0, notFound=0, notDormant=0;
             Threading::ScopedMutexLock lock( _mutex );
-            for(std::vector<TileKey>::const_iterator parentKey = _parentKeys.begin(); parentKey != _parentKeys.end(); ++parentKey)
+            for(std::set<TileKey>::const_iterator parentKey = _parentKeys.begin(); parentKey != _parentKeys.end(); ++parentKey)
             {
                 osg::ref_ptr<TileNode> parentNode;
                 if ( _tiles->get(*parentKey, parentNode) )
@@ -115,7 +119,7 @@ UnloaderGroup::traverse(osg::NodeVisitor& nv)
                 else notFound++;
             }
 
-            //OE_NOTICE << LC << "Total=" << _parentKeys.size() << "; threshold=" << _threshold << "; unloaded=" << unloaded << "; notDormant=" << notDormant << "; notFound=" << notFound << "; live=" << _live->size() << "\n";
+            OE_DEBUG << LC << "Total=" << _parentKeys.size() << "; threshold=" << _threshold << "; unloaded=" << unloaded << "; notDormant=" << notDormant << "; notFound=" << notFound << "\n";
             _parentKeys.clear();
         }
     }
diff --git a/src/osgEarthDrivers/fastdxt/CMakeLists.txt b/src/osgEarthDrivers/fastdxt/CMakeLists.txt
index ce231bd..c90e8a4 100644
--- a/src/osgEarthDrivers/fastdxt/CMakeLists.txt
+++ b/src/osgEarthDrivers/fastdxt/CMakeLists.txt
@@ -1,9 +1,14 @@
-OPTION(ENABLE_FASTDXT "Set to ON to build optional FastDXT image compressor." OFF)
-IF(ENABLE_FASTDXT)
+OPTION(OSGEARTH_ENABLE_FASTDXT "Set to ON to build optional FastDXT image compressor." ON)
+IF(OSGEARTH_ENABLE_FASTDXT)
 
 OPTION(CURL_IS_STATIC "on if curl is a static lib " ON)
 MARK_AS_ADVANCED(CURL_IS_STATIC)
 
+IF(MINGW)
+    # required by gcc 7.2.0
+    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -msse4.1")
+ENDIF(MINGW)
+
 IF(WIN32)
     IF(MSVC)
         SET(CMAKE_SHARED_LINKER_FLAGS_DEBUG "${CMAKE_SHARED_LINKER_FLAGS_DEBUG} /NODEFAULTLIB:MSVCRT")
@@ -38,4 +43,4 @@ ENDIF()
 
 SETUP_PLUGIN(fastdxt)
 
-ENDIF(ENABLE_FASTDXT)
+ENDIF(OSGEARTH_ENABLE_FASTDXT)
diff --git a/src/osgEarthDrivers/fastdxt/FastDXTImageProcessor.cpp b/src/osgEarthDrivers/fastdxt/FastDXTImageProcessor.cpp
index 2398610..f254d03 100644
--- a/src/osgEarthDrivers/fastdxt/FastDXTImageProcessor.cpp
+++ b/src/osgEarthDrivers/fastdxt/FastDXTImageProcessor.cpp
@@ -47,7 +47,7 @@ public:
             osg::Timer_t start = osg::Timer::instance()->tick();
             rgba = osgEarth::ImageUtils::convertToRGBA8( &image );
             osg::Timer_t end = osg::Timer::instance()->tick();
-            OE_INFO << "conversion to rgba took" << osg::Timer::instance()->delta_m(start, end) << std::endl;
+            OE_DEBUG << "conversion to rgba took" << osg::Timer::instance()->delta_m(start, end) << std::endl;
             sourceImage = rgba.get();
         }
 
@@ -58,12 +58,12 @@ public:
         case osg::Texture::USE_S3TC_DXT1_COMPRESSION:
             format = FORMAT_DXT1;
             pixelFormat = GL_COMPRESSED_RGB_S3TC_DXT1_EXT;
-            OE_INFO << "FastDXT using dxt1 format" << std::endl;
+            OE_DEBUG << "FastDXT using dxt1 format" << std::endl;
             break;
         case osg::Texture::USE_S3TC_DXT5_COMPRESSION:
             format = FORMAT_DXT5;
             pixelFormat = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;
-            OE_INFO << "FastDXT dxt5 format" << std::endl;
+            OE_DEBUG << "FastDXT dxt5 format" << std::endl;
             break;
         default:
             OSG_WARN << "Unhandled compressed format" << compressedFormat << std::endl;
@@ -85,7 +85,7 @@ public:
         osg::Timer_t start = osg::Timer::instance()->tick();
         int outputBytes = CompressDXT(in, out, sourceImage->s(), sourceImage->t(), format);
         osg::Timer_t end = osg::Timer::instance()->tick();
-        OE_INFO << "compression took" << osg::Timer::instance()->delta_m(start, end) << std::endl;
+        OE_DEBUG << "compression took" << osg::Timer::instance()->delta_m(start, end) << std::endl;
 
         //Allocate and copy over the output data to the correct size array.
         unsigned char* data = (unsigned char*)malloc(outputBytes);
diff --git a/src/osgEarthDrivers/fastdxt/util.cpp b/src/osgEarthDrivers/fastdxt/util.cpp
index 9a53069..f2a01f7 100644
--- a/src/osgEarthDrivers/fastdxt/util.cpp
+++ b/src/osgEarthDrivers/fastdxt/util.cpp
@@ -101,7 +101,7 @@ double aTime()
 
 
 
-void aLog(char* format,...)
+void aLog(const char* format,...)
 {
 	va_list vl;
 	char line[2048];
@@ -118,7 +118,7 @@ void aLog(char* format,...)
 	fflush(stderr);
 }
 
-void aError(char* format,...)
+void aError(const char* format,...)
 {
 	va_list vl;
 	char line[2048];
diff --git a/src/osgEarthDrivers/fastdxt/util.h b/src/osgEarthDrivers/fastdxt/util.h
index 5b5ded1..5cae4bb 100644
--- a/src/osgEarthDrivers/fastdxt/util.h
+++ b/src/osgEarthDrivers/fastdxt/util.h
@@ -54,8 +54,8 @@ void   aInitialize();
 
 double aTime();
 
-void   aLog(char* format,...);
-void aError(char* format,...);
+void   aLog(const char* format,...);
+void aError(const char* format,...);
 
 void* aAlloc(size_t const n);
 void aFree(void* const p);
diff --git a/src/osgEarthDrivers/feature_elevation/FeatureElevationOptions b/src/osgEarthDrivers/feature_elevation/FeatureElevationOptions
index 0f2ade8..fad848d 100644
--- a/src/osgEarthDrivers/feature_elevation/FeatureElevationOptions
+++ b/src/osgEarthDrivers/feature_elevation/FeatureElevationOptions
@@ -63,9 +63,9 @@ namespace osgEarth { namespace Drivers
         Config getConfig() const
         {
             Config conf = TileSourceOptions::getConfig();
-            conf.updateIfSet( "attr", _attr );
-            conf.updateObjIfSet( "features", _featureOptions );
-            conf.updateIfSet( "offset", _offset );
+            conf.set( "attr", _attr );
+            conf.setObj( "features", _featureOptions );
+            conf.set( "offset", _offset );
 
             return conf;
         }
diff --git a/src/osgEarthDrivers/feature_elevation/ReaderWriterFeatureElevation.cpp b/src/osgEarthDrivers/feature_elevation/ReaderWriterFeatureElevation.cpp
index a0cc694..ee53323 100644
--- a/src/osgEarthDrivers/feature_elevation/ReaderWriterFeatureElevation.cpp
+++ b/src/osgEarthDrivers/feature_elevation/ReaderWriterFeatureElevation.cpp
@@ -26,8 +26,10 @@
 #include <osgEarth/ImageUtils>
 #include <osgEarth/URI>
 #include <osgEarth/HeightFieldUtils>
+#include <osgEarth/Progress>
 
 #include <osgEarthFeatures/TransformFilter>
+#include <osgEarthFeatures/FeatureCursor>
 
 #include <osgDB/FileNameUtils>
 #include <osgDB/FileUtils>
@@ -49,6 +51,7 @@ using namespace osgEarth;
 using namespace osgEarth::Drivers;
 
 #include <osgEarth/Profiler>
+#include <osgEarth/Metrics>
 
 
 class FeatureElevationTileSource : public TileSource
@@ -112,6 +115,12 @@ public:
 
 				setProfile( profile );
 			}
+
+            DataExtent de(_extents);
+                //_features->getFeatureProfile()->getFirstLevel(),
+                //_features->getFeatureProfile()->getMaxLevel()));
+
+            getDataExtents().push_back(de);
         }
 
         return STATUS_OK;
@@ -133,10 +142,13 @@ public:
             return NULL;
         }
 
-        int tileSize = _options.tileSize().value();        
+        METRIC_SCOPED("fe.chf");
+
+        int tileSize = getPixelsPerTile(); //_options.tileSize().value();        
 
 	    if (intersects(key))
         {
+            METRIC_BEGIN("fe.chf.query_features");
             //Get the extents of the tile
             double xmin, ymin, xmax, ymax;
             key.getExtent().getBounds(xmin, ymin, xmax, ymax);
@@ -150,7 +162,7 @@ public:
             // assemble a spatial query. It helps if your features have a spatial index.
             Query query;
             query.bounds() = extentInFeatureSRS.bounds();
-
+            
 		    FeatureList featureList;
             osg::ref_ptr<FeatureCursor> cursor = _features->createFeatureCursor(query);
             while ( cursor.valid() && cursor->hasMore() )
@@ -159,13 +171,19 @@ public:
                 if ( f && f->getGeometry() )
                     featureList.push_back(f);
             }
+            METRIC_END("fe.chf.query_features");
 
             // We now have a feature list in feature SRS.
 
             bool transformRequired = !keySRS->isHorizEquivalentTo(featureSRS);
 		    
-			if (!featureList.empty())
+            if (!featureList.empty())
 			{
+                METRIC_SCOPED("fe.chf.iterate_grid");
+
+                if (progress && progress->isCanceled())
+                    return 0L;
+
                 //Only allocate the heightfield if we actually intersect any features.
                 osg::ref_ptr<osg::HeightField> hf = new osg::HeightField;
                 hf->allocate(tileSize, tileSize);
@@ -183,9 +201,14 @@ public:
 						double geoY = ymin + (dy * (double)r);
 
 						float h = NO_DATA_VALUE;
+                        
+                        METRIC_SCOPED("fe.chf.iterate_features");
 
 						for (FeatureList::iterator f = featureList.begin(); f != featureList.end(); ++f)
 						{
+                            if (progress && progress->isCanceled())
+                                return 0L;
+
 							osgEarth::Symbology::Polygon* boundary = dynamic_cast<osgEarth::Symbology::Polygon*>((*f)->getGeometry());
 
 							if (!boundary)
diff --git a/src/osgEarthDrivers/feature_mapnikvectortiles/FeatureSourceMVT.cpp b/src/osgEarthDrivers/feature_mapnikvectortiles/FeatureSourceMVT.cpp
index eeb0f80..b30227b 100644
--- a/src/osgEarthDrivers/feature_mapnikvectortiles/FeatureSourceMVT.cpp
+++ b/src/osgEarthDrivers/feature_mapnikvectortiles/FeatureSourceMVT.cpp
@@ -21,6 +21,7 @@
 #include <osgEarth/Registry>
 #include <osgEarth/FileUtils>
 #include <osgEarth/GeoData>
+#include <osgEarthFeatures/FeatureCursor>
 #include <osgEarthFeatures/FeatureSource>
 #include <osgEarthFeatures/MVT>
 #include <osgEarthFeatures/Filter>
@@ -270,7 +271,8 @@ private:
         }
 
 
-        result->setFirstLevel(_minLevel);
+        // Use the max level for now as the min level.
+        result->setFirstLevel(_maxLevel);
         result->setMaxLevel(_maxLevel);
         result->setProfile(profile);
         result->geoInterp() = osgEarth::GEOINTERP_GREAT_CIRCLE;
diff --git a/src/osgEarthDrivers/feature_mapnikvectortiles/MVTFeatureOptions b/src/osgEarthDrivers/feature_mapnikvectortiles/MVTFeatureOptions
index 357086c..8df5e4d 100644
--- a/src/osgEarthDrivers/feature_mapnikvectortiles/MVTFeatureOptions
+++ b/src/osgEarthDrivers/feature_mapnikvectortiles/MVTFeatureOptions
@@ -51,7 +51,7 @@ namespace osgEarth { namespace Drivers
     public:
         Config getConfig() const {
             Config conf = FeatureSourceOptions::getConfig();
-            conf.updateIfSet( "url", _url ); 
+            conf.set( "url", _url ); 
             return conf;
         }
 
diff --git a/src/osgEarthDrivers/feature_ogr/FeatureCursorOGR b/src/osgEarthDrivers/feature_ogr/FeatureCursorOGR
index 5def968..c8835d4 100644
--- a/src/osgEarthDrivers/feature_ogr/FeatureCursorOGR
+++ b/src/osgEarthDrivers/feature_ogr/FeatureCursorOGR
@@ -19,7 +19,7 @@
 #ifndef OSGEARTHFEATURES_FEATURE_CURSOR_OGR
 #define OSGEARTHFEATURES_FEATURE_CURSOR_OGR 1
 
-#include <osgEarthFeatures/Feature>
+#include <osgEarthFeatures/FeatureCursor>
 #include <osgEarthFeatures/FeatureSource>
 #include <osgEarthFeatures/Filter>
 #include <osgEarthSymbology/Query>
@@ -47,12 +47,12 @@ public:
      *      The the query from which this cursor was created.
      */
     FeatureCursorOGR(
-        OGRLayerH                dsHandle,
-        OGRLayerH                layerHandle,
-        const FeatureSource*     source,
-        const FeatureProfile*    profile,
-        const Symbology::Query&  query,
-        const FeatureFilterList& filters );
+        OGRLayerH                 dsHandle,
+        OGRLayerH                 layerHandle,
+        const FeatureSource*      source,
+        const FeatureProfile*     profile,
+        const Symbology::Query&   query,
+        const FeatureFilterChain* filters );
 
 public: // FeatureCursor
 
@@ -74,7 +74,7 @@ private:
     osg::ref_ptr<const FeatureProfile>  _profile;
     std::queue< osg::ref_ptr<Feature> > _queue;
     osg::ref_ptr<Feature>               _lastFeatureReturned;
-    const FeatureFilterList&            _filters;
+    osg::ref_ptr<const FeatureFilterChain> _filters;
     bool                                _resultSetEndReached;
 
 private:
diff --git a/src/osgEarthDrivers/feature_ogr/FeatureCursorOGR.cpp b/src/osgEarthDrivers/feature_ogr/FeatureCursorOGR.cpp
index 30e93e5..09bef1c 100644
--- a/src/osgEarthDrivers/feature_ogr/FeatureCursorOGR.cpp
+++ b/src/osgEarthDrivers/feature_ogr/FeatureCursorOGR.cpp
@@ -19,6 +19,7 @@
 #include "FeatureCursorOGR"
 #include <osgEarthFeatures/OgrUtils>
 #include <osgEarthFeatures/Feature>
+#include <osgEarthFeatures/FilterContext>
 #include <osgEarth/Registry>
 #include <osg/Math>
 #include <algorithm>
@@ -74,7 +75,7 @@ FeatureCursorOGR::FeatureCursorOGR(OGRDataSourceH              dsHandle,
                                    const FeatureSource*        source,
                                    const FeatureProfile*       profile,
                                    const Symbology::Query&     query,
-                                   const FeatureFilterList&    filters) :
+                                   const FeatureFilterChain*   filters) :
 _source           ( source ),
 _dsHandle         ( dsHandle ),
 _layerHandle      ( layerHandle ),
@@ -243,11 +244,27 @@ FeatureCursorOGR::readChunk()
             {
                 osg::ref_ptr<Feature> feature = OgrUtils::createFeature( handle, _profile.get() );
 
-                if (feature.valid() &&
-                    !_source->isBlacklisted( feature->getFID() ) &&
-                    validateGeometry( feature->getGeometry() ))
+                if (feature.valid())
                 {
-                    filterList.push_back( feature.release() );
+                    if (!_source->isBlacklisted(feature->getFID()))
+                    {
+                        if (validateGeometry( feature->getGeometry() ))
+                        {
+                            filterList.push_back( feature.release() );
+                        }
+                        else
+                        {
+                            OE_DEBUG << LC << "Invalid geometry found at feature " << feature->getFID() << std::endl;
+                        }
+                    }
+                    else
+                    {
+                        OE_DEBUG << LC << "Blacklisted feature " << feature->getFID() << " skipped" << std::endl;
+                    }
+                }
+                else
+                {
+                    OE_DEBUG << LC << "Skipping NULL feature" << std::endl;
                 }
                 OGR_F_Destroy( handle );
             }
@@ -258,7 +275,7 @@ FeatureCursorOGR::readChunk()
         }
 
         // preprocess the features using the filter list:
-        if ( !_filters.empty() )
+        if ( _filters.valid() && !_filters->empty() )
         {
             FilterContext cx;
             cx.setProfile( _profile.get() );
@@ -271,7 +288,7 @@ FeatureCursorOGR::readChunk()
                 cx.extent() = _profile->getExtent();
             }
 
-            for( FeatureFilterList::const_iterator i = _filters.begin(); i != _filters.end(); ++i )
+            for( FeatureFilterChain::const_iterator i = _filters->begin(); i != _filters->end(); ++i )
             {
                 FeatureFilter* filter = i->get();
                 cx = filter->push( filterList, cx );
diff --git a/src/osgEarthDrivers/feature_ogr/FeatureSourceOGR.cpp b/src/osgEarthDrivers/feature_ogr/FeatureSourceOGR.cpp
index a52c405..2c51b70 100644
--- a/src/osgEarthDrivers/feature_ogr/FeatureSourceOGR.cpp
+++ b/src/osgEarthDrivers/feature_ogr/FeatureSourceOGR.cpp
@@ -213,7 +213,7 @@ public:
                 if (!srHandle)
                     return Status::Error(Status::ResourceUnavailable, Stringify() << "No spatial reference found in \"" << _source << "\"");
 
-                osg::ref_ptr<SpatialReference> srs = SpatialReference::createFromHandle( srHandle, false );
+                osg::ref_ptr<SpatialReference> srs = SpatialReference::createFromHandle(srHandle);
                 if (!srs.valid())
                     return Status::Error(Status::ResourceUnavailable, Stringify() << "Unrecognized SRS found in \"" << _source << "\"");
 
diff --git a/src/osgEarthDrivers/feature_ogr/OGRFeatureOptions b/src/osgEarthDrivers/feature_ogr/OGRFeatureOptions
index b3fbd37..d130a48 100644
--- a/src/osgEarthDrivers/feature_ogr/OGRFeatureOptions
+++ b/src/osgEarthDrivers/feature_ogr/OGRFeatureOptions
@@ -69,14 +69,14 @@ namespace osgEarth { namespace Drivers
     public:
         Config getConfig() const {
             Config conf = FeatureSourceOptions::getConfig();
-            conf.updateIfSet( "url", _url );
-            conf.updateIfSet( "connection", _connection );
-            conf.updateIfSet( "ogr_driver", _ogrDriver );
-            conf.updateIfSet( "build_spatial_index", _buildSpatialIndex );
-            conf.updateIfSet( "force_rebuild_spatial_index", _forceRebuildSpatialIndex );
-            conf.updateIfSet( "geometry", _geometryConf );    
-            conf.updateIfSet( "geometry_url", _geometryUrl );
-            conf.updateIfSet( "layer", _layer );
+            conf.set( "url", _url );
+            conf.set( "connection", _connection );
+            conf.set( "ogr_driver", _ogrDriver );
+            conf.set( "build_spatial_index", _buildSpatialIndex );
+            conf.set( "force_rebuild_spatial_index", _forceRebuildSpatialIndex );
+            conf.set( "geometry", _geometryConf );    
+            conf.set( "geometry_url", _geometryUrl );
+            conf.set( "layer", _layer );
             conf.updateNonSerializable( "OGRFeatureOptions::geometry", _geometry.get() );
             return conf;
         }
diff --git a/src/osgEarthDrivers/feature_raster/FeatureSourceRaster.cpp b/src/osgEarthDrivers/feature_raster/FeatureSourceRaster.cpp
index 93d7753..ec735dd 100644
--- a/src/osgEarthDrivers/feature_raster/FeatureSourceRaster.cpp
+++ b/src/osgEarthDrivers/feature_raster/FeatureSourceRaster.cpp
@@ -18,11 +18,15 @@
  */
 #include "RasterFeatureOptions"
 
-#include <osgDB/FileNameUtils>
-#include <osgDB/FileUtils>
 #include <osgEarth/Registry>
 #include <osgEarth/ImageUtils>
+#include <osgEarth/ImageLayer>
+
 #include <osgEarthFeatures/FeatureSource>
+#include <osgEarthFeatures/FeatureCursor>
+
+#include <osgDB/FileNameUtils>
+#include <osgDB/FileUtils>
 
 #define LC "[Raster FeatureSource] "
 
@@ -66,8 +70,10 @@ public:
         return new FeatureListCursor( features );
 #else
 
-        osg::ref_ptr< osgEarth::ImageLayer > layer = query.getMap()->getImageLayerByName(*_options.layer());
-        if (layer.valid())
+        MapFrame mapf = query.getMap();
+        osgEarth::ImageLayer* layer = mapf.getLayerByName<osgEarth::ImageLayer>(_options.layer().get());
+        //osg::ref_ptr< osgEarth::ImageLayer > layer = query.getMap()->getLayerByName<ImageLayer>(*_options.layer());
+        if (layer)//.valid())
         {
             GeoImage image = layer->createImage( key );
          
diff --git a/src/osgEarthDrivers/feature_raster/RasterFeatureOptions b/src/osgEarthDrivers/feature_raster/RasterFeatureOptions
index a60e64a..e0a32e2 100644
--- a/src/osgEarthDrivers/feature_raster/RasterFeatureOptions
+++ b/src/osgEarthDrivers/feature_raster/RasterFeatureOptions
@@ -59,9 +59,9 @@ namespace osgEarth { namespace Drivers
     public:
         Config getConfig() const {
             Config conf = FeatureSourceOptions::getConfig();
-            conf.updateIfSet("layer", _layer);
-            conf.updateIfSet("level", _level);
-            conf.updateIfSet("attribute", _attribute);
+            conf.set("layer", _layer);
+            conf.set("level", _level);
+            conf.set("attribute", _attribute);
             return conf;
         }
 
diff --git a/src/osgEarthDrivers/feature_tfs/FeatureSourceTFS.cpp b/src/osgEarthDrivers/feature_tfs/FeatureSourceTFS.cpp
index f1f5631..cd2cd54 100644
--- a/src/osgEarthDrivers/feature_tfs/FeatureSourceTFS.cpp
+++ b/src/osgEarthDrivers/feature_tfs/FeatureSourceTFS.cpp
@@ -21,13 +21,18 @@
 #include <osgEarth/Registry>
 #include <osgEarth/XmlUtils>
 #include <osgEarth/FileUtils>
+
 #include <osgEarthFeatures/FeatureSource>
 #include <osgEarthFeatures/Filter>
+#include <osgEarthFeatures/FilterContext>
 #include <osgEarthFeatures/BufferFilter>
 #include <osgEarthFeatures/ScaleFilter>
 #include <osgEarthFeatures/MVT>
 #include <osgEarthFeatures/OgrUtils>
+#include <osgEarthFeatures/FeatureCursor>
+
 #include <osgEarthUtil/TFS>
+
 #include <osg/Notify>
 #include <osgDB/FileNameUtils>
 #include <osgDB/FileUtils>
@@ -112,7 +117,7 @@ public:
             fp->setTiled( true );
             fp->setFirstLevel( *_options.minLevel() );
             fp->setMaxLevel( *_options.maxLevel() );
-            fp->setProfile( profile );
+            fp->setProfile( profile.get() );
             if ( _options.geoInterp().isSet() )
                 fp->geoInterp() = _options.geoInterp().get();
         }
@@ -303,20 +308,16 @@ public:
         }
 
         //If we have any filters, process them here before the cursor is created
-        if (!getFilters().empty())
+        if (getFilters() && !getFilters()->empty() && !features.empty())
         {
-            // preprocess the features using the filter list:
-            if ( features.size() > 0 )
-            {
-                FilterContext cx;
-                cx.setProfile( getFeatureProfile() );
-                cx.extent() = query.tileKey()->getExtent();
+            FilterContext cx;
+            cx.setProfile(getFeatureProfile());
+            cx.extent() = query.tileKey()->getExtent();
 
-                for( FeatureFilterList::const_iterator i = getFilters().begin(); i != getFilters().end(); ++i )
-                {
-                    FeatureFilter* filter = i->get();
-                    cx = filter->push( features, cx );
-                }
+            for (FeatureFilterChain::const_iterator i = getFilters()->begin(); i != getFilters()->end(); ++i)
+            {
+                FeatureFilter* filter = i->get();
+                cx = filter->push(features, cx);
             }
         }
 
@@ -371,8 +372,7 @@ public:
 
 private:
     const TFSFeatureOptions         _options;    
-    FeatureSchema                   _schema;
-    osg::ref_ptr<CacheBin>          _cacheBin;
+    FeatureSchema                   _schema;    
     osg::ref_ptr<osgDB::Options>    _readOptions;    
     TFSLayer                        _layer;
     bool                            _layerValid;
diff --git a/src/osgEarthDrivers/feature_tfs/TFSFeatureOptions b/src/osgEarthDrivers/feature_tfs/TFSFeatureOptions
index f9c1c3c..f8f37f1 100644
--- a/src/osgEarthDrivers/feature_tfs/TFSFeatureOptions
+++ b/src/osgEarthDrivers/feature_tfs/TFSFeatureOptions
@@ -65,11 +65,11 @@ namespace osgEarth { namespace Drivers
     public:
         Config getConfig() const {
             Config conf = FeatureSourceOptions::getConfig();
-            conf.updateIfSet( "url", _url ); 
-            conf.updateIfSet( "format", _format );
-            conf.updateIfSet( "invert_y", _invertY);
-            conf.updateIfSet( "min_level", _minLevel);
-            conf.updateIfSet( "max_level", _maxLevel);
+            conf.set( "url", _url ); 
+            conf.set( "format", _format );
+            conf.set( "invert_y", _invertY);
+            conf.set( "min_level", _minLevel);
+            conf.set( "max_level", _maxLevel);
             return conf;
         }
 
diff --git a/src/osgEarthDrivers/feature_wfs/FeatureSourceWFS.cpp b/src/osgEarthDrivers/feature_wfs/FeatureSourceWFS.cpp
index d8c8687..4a57ffc 100644
--- a/src/osgEarthDrivers/feature_wfs/FeatureSourceWFS.cpp
+++ b/src/osgEarthDrivers/feature_wfs/FeatureSourceWFS.cpp
@@ -20,12 +20,16 @@
 
 #include <osgEarth/Registry>
 #include <osgEarth/FileUtils>
+#include <osgEarth/URI>
+
 #include <osgEarthFeatures/FeatureSource>
 #include <osgEarthFeatures/Filter>
-#include <osgEarthFeatures/BufferFilter>
-#include <osgEarthFeatures/ScaleFilter>
-#include <osgEarthUtil/WFS>
+#include <osgEarthFeatures/FilterContext>
+#include <osgEarthFeatures/FeatureCursor>
 #include <osgEarthFeatures/OgrUtils>
+
+#include <osgEarthUtil/WFS>
+
 #include <osg/Notify>
 #include <osgDB/FileNameUtils>
 #include <osgDB/FileUtils>
@@ -36,7 +40,6 @@
 #include <ogr_api.h>
 
 
-
 //#undef  OE_DEBUG
 //#define OE_DEBUG OE_INFO
 
@@ -241,7 +244,7 @@ public:
         {
             return ".xml";
         }        
-		else if (isJSON(mime))
+        else if (isJSON(mime))
         {
             return ".json";
         }        
@@ -268,15 +271,22 @@ public:
 
     std::string createURL(const Symbology::Query& query)
     {
+        char sep = _options.url()->full().find_first_of('?') == std::string::npos? '?' : '&';
+
         std::stringstream buf;
-        buf << _options.url()->full() << "?SERVICE=WFS&VERSION=1.0.0&REQUEST=GetFeature";
+        buf << _options.url()->full() << sep << "SERVICE=WFS&VERSION=1.0.0&REQUEST=GetFeature";
         buf << "&TYPENAME=" << _options.typeName().get();
         
         std::string outputFormat = "geojson";
         if (_options.outputFormat().isSet()) outputFormat = _options.outputFormat().get();
         buf << "&OUTPUTFORMAT=" << outputFormat;
 
-        if (_options.maxFeatures().isSet())
+        // If the Query limit is set, use that.  Otherwise use the globally defined maxFeatures setting.
+        if (query.limit().isSet())
+        {
+            buf << "&MAXFEATURES=" << query.limit().get();
+        }
+        else if (_options.maxFeatures().isSet())
         {
             buf << "&MAXFEATURES=" << _options.maxFeatures().get();
         }
@@ -298,7 +308,14 @@ public:
                    "&X=" << tileX <<
                    "&Y=" << tileY;
         }
-        else if (query.bounds().isSet())
+	// BBOX and CQL_FILTER are mutually exclusive. Give CQL_FILTER priority if specified.
+	// NOTE: CQL_FILTER is a non-standard vendor parameter. See:
+	// http://docs.geoserver.org/latest/en/user/services/wfs/vendor.html
+	else if (query.expression().isSet())
+	{
+	    buf << "&CQL_FILTER=" << osgEarth::URI::urlEncode(query.expression().get());
+	}
+	else if (query.bounds().isSet())
         {            
             double buffer = *_options.buffer();            
             buf << "&BBOX=" << std::setprecision(16)
@@ -307,6 +324,7 @@ public:
                             << query.bounds().get().xMax() + buffer << ","
                             << query.bounds().get().yMax() + buffer;
         }
+
         std::string str;
         str = buf.str();
         return str;
@@ -347,19 +365,15 @@ public:
         }
 
         //If we have any filters, process them here before the cursor is created
-        if (!getFilters().empty())
+        if (getFilters() && !getFilters()->empty() && !features.empty())
         {
-            // preprocess the features using the filter list:
-            if ( features.size() > 0 )
-            {
-                FilterContext cx;
-                cx.setProfile( getFeatureProfile() );
+            FilterContext cx;
+            cx.setProfile( getFeatureProfile() );
 
-                for( FeatureFilterList::const_iterator i = getFilters().begin(); i != getFilters().end(); ++i )
-                {
-                    FeatureFilter* filter = i->get();
-                    cx = filter->push( features, cx );
-                }
+            for( FeatureFilterChain::const_iterator i = getFilters()->begin(); i != getFilters()->end(); ++i )
+            {
+                FeatureFilter* filter = i->get();
+                cx = filter->push( features, cx );
             }
         }
 
@@ -374,7 +388,6 @@ public:
             }
         }
 
-        //result = new FeatureListCursor(features);
         result = dataOK ? new FeatureListCursor( features ) : 0L;
 
         if ( !result )
diff --git a/src/osgEarthDrivers/feature_wfs/WFSFeatureOptions b/src/osgEarthDrivers/feature_wfs/WFSFeatureOptions
index acc639e..39c0bd3 100644
--- a/src/osgEarthDrivers/feature_wfs/WFSFeatureOptions
+++ b/src/osgEarthDrivers/feature_wfs/WFSFeatureOptions
@@ -81,13 +81,13 @@ namespace osgEarth { namespace Drivers
     public:
         Config getConfig() const {
             Config conf = FeatureSourceOptions::getConfig();
-            conf.updateIfSet( "url", _url ); 
-            conf.updateIfSet( "geometry_profile", _geometryProfileConf );
-            conf.updateIfSet( "typename", _typename );
-            conf.updateIfSet( "outputformat", _outputFormat);
-            conf.updateIfSet( "maxfeatures", _maxFeatures );
-            conf.updateIfSet( "disable_tiling", _disableTiling );
-            conf.updateIfSet( "request_buffer", _buffer);
+            conf.set( "url", _url ); 
+            conf.set( "geometry_profile", _geometryProfileConf );
+            conf.set( "typename", _typename );
+            conf.set( "outputformat", _outputFormat);
+            conf.set( "maxfeatures", _maxFeatures );
+            conf.set( "disable_tiling", _disableTiling );
+            conf.set( "request_buffer", _buffer);
 
             return conf;
         }
diff --git a/src/osgEarthDrivers/feature_xyz/CMakeLists.txt b/src/osgEarthDrivers/feature_xyz/CMakeLists.txt
new file mode 100644
index 0000000..c3d7095
--- /dev/null
+++ b/src/osgEarthDrivers/feature_xyz/CMakeLists.txt
@@ -0,0 +1,18 @@
+SET(TARGET_SRC
+    FeatureSourceXYZ.cpp
+)
+
+SET(TARGET_H
+    XYZFeatureOptions
+)
+
+INCLUDE_DIRECTORIES( ${GDAL_INCLUDE_DIR} )
+SET(TARGET_COMMON_LIBRARIES ${TARGET_COMMON_LIBRARIES} osgEarthFeatures osgEarthSymbology osgEarthUtil)
+SET(TARGET_LIBRARIES_VARS GDAL_LIBRARY)
+SETUP_PLUGIN(osgearth_feature_xyz)
+
+
+# to install public driver includes:
+SET(LIB_NAME feature_xyz)
+SET(LIB_PUBLIC_HEADERS ${TARGET_H})
+INCLUDE(ModuleInstallOsgEarthDriverIncludes OPTIONAL)
diff --git a/src/osgEarthDrivers/feature_xyz/FeatureSourceXYZ.cpp b/src/osgEarthDrivers/feature_xyz/FeatureSourceXYZ.cpp
new file mode 100644
index 0000000..8119345
--- /dev/null
+++ b/src/osgEarthDrivers/feature_xyz/FeatureSourceXYZ.cpp
@@ -0,0 +1,416 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+* Copyright 2016 Pelican Mapping
+* http://osgearth.org
+*
+* osgEarth is free software; you can redistribute it and/or modify
+* it under the terms of the GNU Lesser General Public License as published by
+* the Free Software Foundation; either version 2 of the License, or
+* (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+* GNU Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public License
+* along with this program.  If not, see <http://www.gnu.org/licenses/>
+*/
+#include "XYZFeatureOptions"
+
+#include <osgEarth/Registry>
+#include <osgEarth/XmlUtils>
+#include <osgEarth/FileUtils>
+
+#include <osgEarthFeatures/FeatureSource>
+#include <osgEarthFeatures/Filter>
+#include <osgEarthFeatures/FilterContext>
+#include <osgEarthFeatures/MVT>
+#include <osgEarthFeatures/OgrUtils>
+#include <osgEarthFeatures/FeatureCursor>
+
+#include <osgEarthUtil/TFS>
+
+#include <osg/Notify>
+#include <osgDB/FileNameUtils>
+#include <osgDB/FileUtils>
+#include <list>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <ogr_api.h>
+
+#ifdef WIN32
+#include <windows.h>
+#endif
+
+#define LC "[XYZ FeatureSource] "
+
+using namespace osgEarth;
+using namespace osgEarth::Util;
+using namespace osgEarth::Features;
+using namespace osgEarth::Drivers;
+
+#define OGR_SCOPED_LOCK GDAL_SCOPED_LOCK
+
+/**
+* A FeatureSource that reads features from a templated URL
+* 
+*/
+class XYZFeatureSource : public FeatureSource
+{
+public:
+    XYZFeatureSource(const XYZFeatureOptions& options ) :
+      FeatureSource( options ),
+          _options     ( options )      
+      {                
+      }
+
+      /** Destruct the object, cleaning up and OGR handles. */
+      virtual ~XYZFeatureSource()
+      {               
+          //nop
+      }
+
+      //override
+      Status initialize(const osgDB::Options* readOptions)
+      { 
+          // make a local copy of the read options.
+          _readOptions = Registry::cloneOrCreateOptions(readOptions);
+
+          FeatureProfile* fp = 0L;
+
+          // Try to get the results from the settings instead
+          if ( !_options.profile().isSet())
+          {
+              return Status::Error(Status::ConfigurationError, "XYZ driver requires an explicit profile");
+          }
+
+          if (!_options.minLevel().isSet() || !_options.maxLevel().isSet())
+          {
+              return Status::Error(Status::ConfigurationError, "XYZ driver requires a min and max level");
+          }
+
+          _template = _options.url()->full();
+
+          _rotateStart = _template.find("[");
+          _rotateEnd   = _template.find("]");
+          if ( _rotateStart != std::string::npos && _rotateEnd != std::string::npos && _rotateEnd-_rotateStart > 1 )
+          {
+              _rotateString  = _template.substr(_rotateStart, _rotateEnd-_rotateStart+1);
+              _rotateChoices = _template.substr(_rotateStart+1, _rotateEnd-_rotateStart-1);
+          }
+
+
+          osg::ref_ptr<const Profile> profile = Profile::create( *_options.profile() );    
+          fp = new FeatureProfile(profile->getExtent());
+          fp->setTiled( true );
+          fp->setFirstLevel( *_options.minLevel() );
+          fp->setMaxLevel( *_options.maxLevel() );
+          fp->setProfile( profile.get() );
+          if ( _options.geoInterp().isSet() )
+              fp->geoInterp() = _options.geoInterp().get();
+
+          setFeatureProfile(fp);
+
+          return Status::OK();
+      }
+
+
+      bool getFeatures( const std::string& buffer, const TileKey& key, const std::string& mimeType, FeatureList& features )
+      {            
+          if (mimeType == "application/x-protobuf" || mimeType == "binary/octet-stream")
+          {
+              std::stringstream in(buffer);
+              return MVT::read(in, key, features);
+          }
+          else
+          {            
+              // find the right driver for the given mime type
+              OGR_SCOPED_LOCK;
+
+              // find the right driver for the given mime type
+              OGRSFDriverH ogrDriver =
+                  isJSON(mimeType) ? OGRGetDriverByName( "GeoJSON" ) :
+                  isGML(mimeType)  ? OGRGetDriverByName( "GML" ) :
+                  0L;
+
+              // fail if we can't find an appropriate OGR driver:
+              if ( !ogrDriver )
+              {
+                  OE_WARN << LC << "Error reading TFS response; cannot grok content-type \"" << mimeType << "\""
+                      << std::endl;
+                  return false;
+              }
+
+              OGRDataSourceH ds = OGROpen( buffer.c_str(), FALSE, &ogrDriver );
+
+              if ( !ds )
+              {
+                  OE_WARN << LC << "Error reading TFS response" << std::endl;
+                  return false;
+              }
+
+              // read the feature data.
+              OGRLayerH layer = OGR_DS_GetLayer(ds, 0);
+              if ( layer )
+              {
+                  const SpatialReference* srs = getFeatureProfile()->getSRS();
+
+                  OGR_L_ResetReading(layer);                                
+                  OGRFeatureH feat_handle;
+                  while ((feat_handle = OGR_L_GetNextFeature( layer )) != NULL)
+                  {
+                      if ( feat_handle )
+                      {
+                          osg::ref_ptr<Feature> f = OgrUtils::createFeature( feat_handle, getFeatureProfile() );
+                          if ( f.valid() && !isBlacklisted(f->getFID()) )
+                          {
+                              features.push_back( f.release() );
+                          }
+                          OGR_F_Destroy( feat_handle );
+                      }
+                  }
+              }
+
+              // Destroy the datasource
+              OGR_DS_Destroy( ds );
+          }
+
+          return true;
+      }
+
+
+      std::string getExtensionForMimeType(const std::string& mime)
+      {
+          //OGR is particular sometimes about the extension of files when it's reading them so it's good to have
+          //the temp file have an appropriate extension
+          if ((mime.compare("text/xml") == 0) ||
+              (mime.compare("text/xml; subtype=gml/2.1.2") == 0) ||
+              (mime.compare("text/xml; subtype=gml/3.1.1") == 0)
+              )
+          {
+              return ".xml";
+          }        
+          else if ((mime.compare("application/json") == 0) ||
+              (mime.compare("json") == 0) ||            
+
+              (mime.compare("application/x-javascript") == 0) ||
+              (mime.compare("text/javascript") == 0) ||
+              (mime.compare("text/x-javascript") == 0) ||
+              (mime.compare("text/x-json") == 0)                 
+              )
+          {
+              return ".json";
+          }        
+          return "";
+      }
+
+      bool isGML( const std::string& mime ) const
+      {
+          return
+              startsWith(mime, "text/xml");
+      }
+
+
+      bool isJSON( const std::string& mime ) const
+      {
+          return
+              (mime.compare("application/json") == 0)         ||
+              (mime.compare("json") == 0)                     ||            
+
+              (mime.compare("application/x-javascript") == 0) ||
+              (mime.compare("text/javascript") == 0)          ||
+              (mime.compare("text/x-javascript") == 0)        ||
+              (mime.compare("text/x-json") == 0);
+      }
+
+      URI createURL(const Symbology::Query& query)
+      {     
+          if (query.tileKey().isSet())
+          {
+              const TileKey &key = query.tileKey().get();
+              unsigned int tileX = key.getTileX();
+              unsigned int tileY = key.getTileY();
+              unsigned int level = key.getLevelOfDetail();
+
+              if (_options.invertY() == false)
+              {                
+                  unsigned int numRows, numCols;
+                  key.getProfile()->getNumTiles(key.getLevelOfDetail(), numCols, numRows);
+                  tileY  = numRows - tileY - 1;            
+              }
+
+
+              std::string location = _template;
+
+              // support OpenLayers template style:
+              replaceIn( location, "${x}", Stringify() << tileX );
+              replaceIn( location, "${y}", Stringify() << tileY );
+              replaceIn( location, "${z}", Stringify() << key.getLevelOfDetail() );
+
+              // failing that, legacy osgearth style:
+              replaceIn( location, "{x}", Stringify() << tileX );
+              replaceIn( location, "{y}", Stringify() << tileY );
+              replaceIn( location, "{z}", Stringify() << key.getLevelOfDetail() );
+
+              std::string cacheKey;
+
+              if ( !_rotateChoices.empty() )
+              {
+                  cacheKey = location;
+                  unsigned index = (++_rotate_iter) % _rotateChoices.size();
+                  replaceIn( location, _rotateString, Stringify() << _rotateChoices[index] );
+              }
+
+
+              URI uri( location, _options.url()->context() );
+              if ( !cacheKey.empty() )
+                  uri.setCacheKey( cacheKey );
+
+              return uri;
+          }
+          return URI();
+      }
+
+      FeatureCursor* createFeatureCursor(const Symbology::Query& query)
+      {
+          FeatureCursor* result = 0L;
+
+          URI uri =  createURL( query );
+          if (uri.empty()) return 0;
+
+          // check the blacklist:
+          if ( Registry::instance()->isBlacklisted(uri.full()) )
+              return 0L;
+
+          OE_DEBUG << LC << uri.full() << std::endl;
+
+          // read the data:
+          ReadResult r = uri.readString( _readOptions.get() );
+
+          const std::string& buffer = r.getString();
+          const Config&      meta   = r.metadata();
+
+          bool dataOK = false;
+
+          FeatureList features;
+          if ( !buffer.empty() )
+          {
+              // Get the mime-type from the metadata record if possible
+              std::string mimeType = r.metadata().value( IOMetadata::CONTENT_TYPE );
+              //If the mimetype is empty then try to set it from the format specification
+              if (mimeType.empty())
+              {
+                  if (_options.format().value() == "json") mimeType = "json";
+                  else if (_options.format().value().compare("gml") == 0) mimeType = "text/xml";
+                  else if (_options.format().value().compare("pbf") == 0) mimeType = "application/x-protobuf";
+              }
+              dataOK = getFeatures( buffer, *query.tileKey(), mimeType, features );
+          }
+
+          if ( dataOK )
+          {
+              OE_DEBUG << LC << "Read " << features.size() << " features" << std::endl;
+          }
+
+          //If we have any filters, process them here before the cursor is created
+          if (getFilters() && !getFilters()->empty() && !features.empty())
+          {
+              FilterContext cx;
+              cx.setProfile(getFeatureProfile());
+              cx.extent() = query.tileKey()->getExtent();
+
+              for (FeatureFilterChain::const_iterator i = getFilters()->begin(); i != getFilters()->end(); ++i)
+              {
+                  FeatureFilter* filter = i->get();
+                  cx = filter->push(features, cx);
+              }
+          }
+
+          // If we have any features and we have an fid attribute, override the fid of the features
+          if (_options.fidAttribute().isSet())
+          {
+              for (FeatureList::iterator itr = features.begin(); itr != features.end(); ++itr)
+              {
+                  std::string attr = itr->get()->getString(_options.fidAttribute().get());                
+                  FeatureID fid = as<long>(attr, 0);
+                  itr->get()->setFID( fid );
+              }
+          }
+
+          //result = new FeatureListCursor(features);
+          result = dataOK ? new FeatureListCursor( features ) : 0L;
+
+          if ( !result )
+              Registry::instance()->blacklist( uri.full() );
+
+          return result;
+      }
+
+      virtual bool supportsGetFeature() const
+      {
+          return false;
+      }
+
+      virtual Feature* getFeature( FeatureID fid )
+      {
+          // not supported for TFS.
+          return 0;
+      }
+
+      virtual bool isWritable() const
+      {
+          return false;
+      }
+
+      virtual const FeatureSchema& getSchema() const
+      {
+          //TODO:  Populate the schema from the DescribeFeatureType call
+          return _schema;
+      }
+
+      virtual osgEarth::Symbology::Geometry::Type getGeometryType() const
+      {
+          return Geometry::TYPE_UNKNOWN;
+      }
+
+
+
+private:
+    const XYZFeatureOptions         _options;    
+    FeatureSchema                   _schema;    
+    osg::ref_ptr<osgDB::Options>    _readOptions;    
+    std::string                     _template;
+    std::string                     _rotateChoices;
+    std::string                     _rotateString;
+    std::string::size_type          _rotateStart, _rotateEnd;
+    OpenThreads::Atomic             _rotate_iter;
+
+};
+
+
+class XYZFeatureSourceFactory : public FeatureSourceDriver
+{
+public:
+    XYZFeatureSourceFactory()
+    {
+        supportsExtension( "osgearth_feature_xyz", "XYZ feature driver for osgEarth" );
+    }
+
+    virtual const char* className() const
+    {
+        return "XYZ Feature Reader";
+    }
+
+    virtual ReadResult readObject(const std::string& file_name, const Options* options) const
+    {
+        if ( !acceptsExtension(osgDB::getLowerCaseFileExtension( file_name )))
+            return ReadResult::FILE_NOT_HANDLED;
+
+        return ReadResult( new XYZFeatureSource( getFeatureSourceOptions(options) ) );
+    }
+};
+
+REGISTER_OSGPLUGIN(osgearth_feature_xyz, XYZFeatureSourceFactory)
+
diff --git a/src/osgEarthDriversDisabled/feature_mapnikvectortiles/MVTFeatureOptions b/src/osgEarthDrivers/feature_xyz/XYZFeatureOptions
similarity index 51%
rename from src/osgEarthDriversDisabled/feature_mapnikvectortiles/MVTFeatureOptions
rename to src/osgEarthDrivers/feature_xyz/XYZFeatureOptions
index 357086c..bfb9f48 100644
--- a/src/osgEarthDriversDisabled/feature_mapnikvectortiles/MVTFeatureOptions
+++ b/src/osgEarthDrivers/feature_xyz/XYZFeatureOptions
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2016 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -16,8 +16,8 @@
  * You should have received a copy of the GNU Lesser General Public License
  * along with this program.  If not, see <http://www.gnu.org/licenses/>
  */
-#ifndef OSGEARTH_DRIVER_MVT_FEATURE_SOURCE_OPTIONS
-#define OSGEARTH_DRIVER_MVT_FEATURE_SOURCE_OPTIONS 1
+#ifndef OSGEARTH_DRIVER_XYZ_FEATURE_SOURCE_OPTIONS
+#define OSGEARTH_DRIVER_XYZ_FEATURE_SOURCE_OPTIONS 1
 
 #include <osgEarth/Common>
 #include <osgEarth/URI>
@@ -29,29 +29,46 @@ namespace osgEarth { namespace Drivers
     using namespace osgEarth::Features;
 
     /**
-     * Options for the TFS feature driver.
+     * Options for the XYZ feature driver.
      */
-    class MVTFeatureOptions : public FeatureSourceOptions // NO EXPORT; header only
+    class XYZFeatureOptions : public FeatureSourceOptions // NO EXPORT; header only
     {
     public:
-        /** Base URL of the TFS service */
         optional<URI>& url() { return _url; }
         const optional<URI>& url() const { return _url; }
+                
+        /** Data format extension of TFS data (json, gml) */
+        optional<std::string>& format() { return _format; }
+        const optional<std::string>& format() const { return _format; }        
+
+        optional<bool>& invertY() { return _invertY; }
+        const optional<bool>& invertY() const { return _invertY; }
+
+        optional<int>& minLevel() { return _minLevel; }
+        const optional<int>& minLevel() const { return _minLevel; }
+
+        optional<int>& maxLevel() { return _maxLevel; }
+        const optional<int>& maxLevel() const { return _maxLevel; }
 
     public:
-        MVTFeatureOptions( const ConfigOptions& opt =ConfigOptions() ) :
-          FeatureSourceOptions( opt )
+        XYZFeatureOptions( const ConfigOptions& opt =ConfigOptions() ) :
+          FeatureSourceOptions( opt ),
+          _format("json")
           {
-            setDriver( "mapnikvectortiles" );            
+            setDriver( "xyz" );            
             fromConfig( _conf );
         }
 
-        virtual ~MVTFeatureOptions() { }
+        virtual ~XYZFeatureOptions() { }
 
     public:
         Config getConfig() const {
             Config conf = FeatureSourceOptions::getConfig();
-            conf.updateIfSet( "url", _url ); 
+            conf.set( "url", _url ); 
+            conf.set( "format", _format );
+            conf.set( "invert_y", _invertY);
+            conf.set( "min_level", _minLevel);
+            conf.set( "max_level", _maxLevel);
             return conf;
         }
 
@@ -64,13 +81,20 @@ namespace osgEarth { namespace Drivers
     private:
         void fromConfig( const Config& conf ) {
             conf.getIfSet( "url", _url );
+            conf.getIfSet( "format", _format );
+            conf.getIfSet( "invert_y", _invertY );
+            conf.getIfSet( "min_level", _minLevel);
+            conf.getIfSet( "max_level", _maxLevel);
         }
 
         optional<URI>         _url;        
         optional<std::string> _format;
+        optional<bool>        _invertY;
+        optional<int>         _minLevel;
+        optional<int>         _maxLevel;
     };
 
 } } // namespace osgEarth::Drivers
 
-#endif // OSGEARTH_DRIVER_MVT_FEATURE_SOURCE_OPTIONS
+#endif // OSGEARTH_DRIVER_TFS_FEATURE_SOURCE_OPTIONS
 
diff --git a/src/osgEarthDrivers/featurefilter_intersect/IntersectFeatureFilter.cpp b/src/osgEarthDrivers/featurefilter_intersect/IntersectFeatureFilter.cpp
index 0799998..c815ee2 100644
--- a/src/osgEarthDrivers/featurefilter_intersect/IntersectFeatureFilter.cpp
+++ b/src/osgEarthDrivers/featurefilter_intersect/IntersectFeatureFilter.cpp
@@ -18,15 +18,19 @@
  */
 #include "IntersectFeatureFilterOptions"
 
-#include <osgEarthFeatures/Filter>
-
-#include <osgDB/FileNameUtils>
-#include <osgDB/FileUtils>
 #include <osgEarth/Registry>
 #include <osgEarth/ImageUtils>
+
+#include <osgEarthFeatures/Filter>
+#include <osgEarthFeatures/FeatureCursor>
 #include <osgEarthFeatures/FeatureSource>
+#include <osgEarthFeatures/FilterContext>
+
 #include <osgEarthSymbology/Geometry>
 
+#include <osgDB/FileNameUtils>
+#include <osgDB/FileUtils>
+
 #define LC "[Intersect FeatureFilter] "
 
 using namespace osgEarth;
diff --git a/src/osgEarthDrivers/featurefilter_join/JoinFeatureFilter.cpp b/src/osgEarthDrivers/featurefilter_join/JoinFeatureFilter.cpp
index 9d7123a..2efefd4 100644
--- a/src/osgEarthDrivers/featurefilter_join/JoinFeatureFilter.cpp
+++ b/src/osgEarthDrivers/featurefilter_join/JoinFeatureFilter.cpp
@@ -19,12 +19,14 @@
 #include "JoinFeatureFilterOptions"
 
 #include <osgEarthFeatures/Filter>
+#include <osgEarthFeatures/FeatureCursor>
 
 #include <osgDB/FileNameUtils>
 #include <osgDB/FileUtils>
 #include <osgEarth/Registry>
 #include <osgEarth/ImageUtils>
 #include <osgEarthFeatures/FeatureSource>
+#include <osgEarthFeatures/FilterContext>
 #include <osgEarthSymbology/Geometry>
 
 #define LC "[Intersect FeatureFilter] "
diff --git a/src/osgEarthDrivers/gdal/GDALOptions b/src/osgEarthDrivers/gdal/GDALOptions
index 73d9e8c..9779a44 100644
--- a/src/osgEarthDrivers/gdal/GDALOptions
+++ b/src/osgEarthDrivers/gdal/GDALOptions
@@ -147,10 +147,10 @@ namespace osgEarth { namespace Drivers
         Config getConfig() const
         {
             Config conf = TileSourceOptions::getConfig();
-            conf.updateIfSet( "url", _url );
-            conf.updateIfSet( "connection", _connection );
-            conf.updateIfSet( "extensions", _extensions );
-            conf.updateIfSet( "black_extensions", _blackExtensions );
+            conf.set( "url", _url );
+            conf.set( "connection", _connection );
+            conf.set( "extensions", _extensions );
+            conf.set( "black_extensions", _blackExtensions );
 
             if ( _interpolation.isSet() ) {
                 if ( _interpolation.value() == osgEarth::INTERP_NEAREST ) conf.update( "interpolation", "nearest" );
@@ -158,12 +158,12 @@ namespace osgEarth { namespace Drivers
                 else if ( _interpolation.value() == osgEarth::INTERP_BILINEAR ) conf.update( "interpolation", "bilinear" );
             }
 
-            conf.updateIfSet( "max_data_level_override", _maxDataLevelOverride);
-            conf.updateIfSet( "subdataset", _subDataSet);
+            conf.set( "max_data_level_override", _maxDataLevelOverride);
+            conf.set( "subdataset", _subDataSet);
 
-            conf.updateIfSet( "interp_imagery", _interpolateImagery);
+            conf.set( "interp_imagery", _interpolateImagery);
 
-            conf.updateObjIfSet( "warp_profile", _warpProfile );
+            conf.setObj( "warp_profile", _warpProfile );
 
             conf.updateNonSerializable( "GDALOptions::ExternalDataset", _externalDataset.get() );
 
diff --git a/src/osgEarthDrivers/gdal/ReaderWriterGDAL.cpp b/src/osgEarthDrivers/gdal/ReaderWriterGDAL.cpp
index ff0fb44..899a2c5 100644
--- a/src/osgEarthDrivers/gdal/ReaderWriterGDAL.cpp
+++ b/src/osgEarthDrivers/gdal/ReaderWriterGDAL.cpp
@@ -119,7 +119,7 @@ typedef struct
 } BandProperty;
 
 static void
-getFiles(const std::string &file, const std::vector<std::string> &exts, const std::vector<std::string> &blackExts, std::vector<std::string> &files)
+getFiles(const osgDB::Options& options, const std::string &file, const std::vector<std::string> &exts, const std::vector<std::string> &blackExts, std::vector<std::string> &files)
 {
     if (osgDB::fileType(file) == osgDB::DIRECTORY)
     {
@@ -128,7 +128,7 @@ getFiles(const std::string &file, const std::vector<std::string> &exts, const st
         {
             if (*itr == "." || *itr == "..") continue;
             std::string f = osgDB::concatPaths(file, *itr);
-            getFiles(f, exts, blackExts, files);
+            getFiles(options, f, exts, blackExts, files);
         }
     }
     else
@@ -165,7 +165,14 @@ getFiles(const std::string &file, const std::vector<std::string> &exts, const st
 
         if (fileValid)
         {
-          files.push_back(osgDB::convertFileNameToNativeStyle(file));
+            std::string fullFilename = file;
+            if (!osgDB::fileExists(file))
+            {
+                fullFilename = osgDB::findDataFile(file, &options);
+                if (fullFilename.empty())
+                    fullFilename = file;
+            }
+            files.push_back(osgDB::convertFileNameToNativeStyle(fullFilename));
         }
     }
 }
@@ -623,6 +630,37 @@ GDALDatasetH GDALAutoCreateWarpedVRTforPolarStereographic(
 }
 
 
+/**
+ * Gets the GeoExtent of the given filename.
+ */
+GeoExtent getGeoExtent(std::string& filename)
+{
+    GDALDataset* ds = (GDALDataset*)GDALOpen(filename.c_str(), GA_ReadOnly );
+    if (!ds)
+    {
+        return GeoExtent::INVALID;
+    }
+
+    // Get the geotransforms
+    double geotransform[6];
+    ds->GetGeoTransform(geotransform);
+
+    double minX, minY, maxX, maxY;
+
+    GDALApplyGeoTransform(geotransform, 0.0, ds->GetRasterYSize(), &minX, &minY);
+    GDALApplyGeoTransform(geotransform, ds->GetRasterXSize(), 0.0, &maxX, &maxY);
+
+    std::string srsString = ds->GetProjectionRef();
+    const SpatialReference* srs = SpatialReference::create(srsString);
+
+    GDALClose(ds);
+
+    GeoExtent ext(srs, minX, minY, maxX, maxY);
+    return ext;
+}
+
+
+
 class GDALTileSource : public TileSource
 {
 public:
@@ -740,7 +778,7 @@ public:
 					OE_DEBUG << LC << "Blacklisting Extension: " << blackExts[i] << std::endl;
 				}
 
-                getFiles(source, exts, blackExts, files);
+                                getFiles(*_dbOptions, source, exts, blackExts, files);
 
                 OE_INFO << LC << "Identified " << files.size() << " files:" << std::endl;
                 for (unsigned int i = 0; i < files.size(); ++i)
@@ -769,7 +807,7 @@ public:
 
                 //Try to load the VRT file from the cache so we don't have to build it each time.
                 if (_cacheBin.valid())
-                {                
+                {
                     ReadResult result = _cacheBin->readString( vrtKey, 0L);
                     if (result.succeeded())
                     {
@@ -932,7 +970,7 @@ public:
             }
         }
 
-     
+
         //Get the initial geotransform
         _srcDS->GetGeoTransform(_geotransform);
 
@@ -947,7 +985,7 @@ public:
         // The warp profile, if provided, takes precedence.
         if ( warpProfile )
         {
-            profile = warpProfile;
+            profile = warpProfile.get();
             if ( profile )
             {
                 OE_DEBUG << LC << INDENT << "Using warp Profile: " << profile->toString() <<  std::endl;
@@ -969,6 +1007,7 @@ public:
         {
             OE_DEBUG << LC << INDENT << "Creating Profile from source's geographic SRS: " << src_srs->getName() <<  std::endl;
             profile = Profile::create(src_srs.get(), -180.0, -90.0, 180.0, 90.0, 2u, 1u);
+            //profile = osgEarth::Registry::instance()->getGlobalGeodeticProfile();
             if ( !profile )
             {
                 return Status::Error( Status::ResourceUnavailable, Stringify()
@@ -1076,6 +1115,9 @@ public:
             pixelToGeo(_warpedDS->GetRasterXSize(), 0.0, maxX, maxY);
         }
 
+
+
+
         OE_DEBUG << LC << INDENT << "Geo extents: " << minX << ", " << minY << " -> " << maxX << ", " << maxY << std::endl;
 
         if ( !profile )
@@ -1116,8 +1158,8 @@ public:
                 _maxDataLevel = i;
                 double w, h;
                 profile->getTileDimensions(i, w, h);
-                double resX = (w / (double)_options.tileSize().value() );
-                double resY = (h / (double)_options.tileSize().value() );
+                double resX = w / (double)getPixelsPerTile();
+                double resY = h / (double)getPixelsPerTile();
 
                 if (resX < maxResolution || resY < maxResolution)
                 {
@@ -1128,12 +1170,44 @@ public:
             OE_INFO << LC << INDENT << _options.url().value().full() << " max Data Level: " << _maxDataLevel << std::endl;
         }
 
+        // If the input dataset is a VRT, then get the individual files in the dataset and use THEM for the DataExtents.
+        // A VRT will create a potentially very large virtual dataset from sparse datasets, so using the extents from the underlying files
+        // will allow osgEarth to only create tiles where there is actually data.
+        DataExtentList dataExtents;
+        if (strcmp(_warpedDS->GetDriver()->GetDescription(), "VRT") == 0)
+        {
+            char **papszFileList = _warpedDS->GetFileList();
+            if (papszFileList != NULL)
+            {
+                for( int i = 0; papszFileList[i] != NULL; i++ )
+                {
+                    std::string file = papszFileList[i];
+                    GeoExtent ext = getGeoExtent(file);
+                    if (ext.isValid())
+                    {
+                        dataExtents.push_back(DataExtent(ext, 0, _maxDataLevel));
+                    }
+                }
+            }
+        }
+
+
         osg::ref_ptr< SpatialReference > srs = SpatialReference::create( warpedSRSWKT );
         // record the data extent in profile space:
-        _extents = GeoExtent( srs, minX, minY, maxX, maxY);
+        _bounds = Bounds(minX, minY, maxX, maxY);
+        _extents = GeoExtent( srs.get(), _bounds);
         GeoExtent profile_extent = _extents.transform( profile->getSRS() );
 
-        getDataExtents().push_back( DataExtent(profile_extent, 0, _maxDataLevel) );
+        if (dataExtents.empty())
+        {
+            // Use the extents of the whole file.
+            getDataExtents().push_back( DataExtent(profile_extent, 0, _maxDataLevel) );
+        }
+        else
+        {
+            // Use the DataExtents from the subfiles of the VRT.
+            getDataExtents().insert(getDataExtents().end(), dataExtents.begin(), dataExtents.end());
+        }
 
         //Set the profile
         setProfile( profile );
@@ -1267,7 +1341,7 @@ public:
     }
 
     osg::Image* createImage( const TileKey&        key,
-                             ProgressCallback*     progress)
+        ProgressCallback*     progress)
     {
         if (key.getLevelOfDetail() > _maxDataLevel)
         {
@@ -1278,123 +1352,328 @@ public:
 
         GDAL_SCOPED_LOCK;
 
-        int tileSize = _options.tileSize().value();
+        int tileSize = getPixelsPerTile(); //_options.tileSize().value();
 
         osg::ref_ptr<osg::Image> image;
-        if (intersects(key)) //TODO: I think this test is OBE -gw
+
+        //Get the extents of the tile
+        double xmin, ymin, xmax, ymax;
+        key.getExtent().getBounds(xmin, ymin, xmax, ymax);
+
+        // Compute the intersection of the incoming key with the data extents of the dataset
+        osgEarth::GeoExtent intersection = key.getExtent().intersectionSameSRS( _extents );
+        if (!intersection.isValid())
         {
-            //Get the extents of the tile
-            double xmin, ymin, xmax, ymax;
-            key.getExtent().getBounds(xmin, ymin, xmax, ymax);
+            return 0;
+        }
 
-            // Compute the intersection of the incoming key with the data extents of the dataset
-            osgEarth::GeoExtent intersection = key.getExtent().intersectionSameSRS( _extents );
+        double west = intersection.xMin();
+        double east = intersection.xMax();
+        double north = intersection.yMax();
+        double south = intersection.yMin();
 
-            // Determine the read window
-            double src_min_x, src_min_y, src_max_x, src_max_y;
-            // Get the pixel coordiantes of the intersection
-            geoToPixel( intersection.xMin(), intersection.yMax(), src_min_x, src_min_y);
-            geoToPixel( intersection.xMax(), intersection.yMin(), src_max_x, src_max_y);
+        // The extents and the intersection will be normalized between -180 and 180 longitude if they are geographic.
+        // However, the georeferencing will expect the coordinates to be in the same longitude frame as the original dataset,
+        // so the intersection bounds are adjusted here if necessary so that the values line up with the georeferencing.
+        if (_extents.getSRS()->isGeographic())
+        {
+            // Shift the bounds to the right.
+            if (east < _bounds.xMin())
+            {
+                while (east < _bounds.xMin())
+                {
+                    west += 360.0;
+                    east += 360.0;
+                }
+            }
+            // Shift the bounds to the left.
+            else if (west > _bounds.xMax())
+            {
+                while (west > _bounds.xMax())
+                {
+                    west -= 360.0;
+                    east -= 360.0;
+                }
+            }
+        }
+        
+        // Determine the read window
+        double src_min_x, src_min_y, src_max_x, src_max_y;
+        // Get the pixel coordiantes of the intersection
+        geoToPixel( west, intersection.yMax(), src_min_x, src_min_y);
+        geoToPixel( east, intersection.yMin(), src_max_x, src_max_y);
+
+        // Convert the doubles to integers.  We floor the mins and ceil the maximums to give the widest window possible.
+        src_min_x = floor(src_min_x);
+        src_min_y = floor(src_min_y);
+        src_max_x = ceil(src_max_x);
+        src_max_y = ceil(src_max_y);
+
+        int off_x = (int)( src_min_x );
+        int off_y = (int)( src_min_y );
+        int width  = (int)(src_max_x - src_min_x);
+        int height = (int)(src_max_y - src_min_y);
+
+
+        int rasterWidth = _warpedDS->GetRasterXSize();
+        int rasterHeight = _warpedDS->GetRasterYSize();
+        if (off_x + width > rasterWidth || off_y + height > rasterHeight)
+        {
+            OE_WARN << LC << "Read window outside of bounds of dataset.  Source Dimensions=" << rasterWidth << "x" << rasterHeight << " Read Window=" << off_x << ", " << off_y << " " << width << "x" << height << std::endl;
+        }
 
-            // Convert the doubles to integers.  We floor the mins and ceil the maximums to give the widest window possible.
-            src_min_x = floor(src_min_x);
-            src_min_y = floor(src_min_y);
-            src_max_x = ceil(src_max_x);
-            src_max_y = ceil(src_max_y);
+        // Determine the destination window
 
-            int off_x = (int)( src_min_x );
-            int off_y = (int)( src_min_y );
-            int width  = (int)(src_max_x - src_min_x);
-            int height = (int)(src_max_y - src_min_y);
+        // Compute the offsets in geo coordinates of the intersection from the TileKey
+        double offset_left = intersection.xMin() - xmin;
+        double offset_top = ymax - intersection.yMax();
 
 
-            int rasterWidth = _warpedDS->GetRasterXSize();
-            int rasterHeight = _warpedDS->GetRasterYSize();
-            if (off_x + width > rasterWidth || off_y + height > rasterHeight)
-            {
-                OE_WARN << LC << "Read window outside of bounds of dataset.  Source Dimensions=" << rasterWidth << "x" << rasterHeight << " Read Window=" << off_x << ", " << off_y << " " << width << "x" << height << std::endl;
-            }
+        int target_width = (int)ceil((intersection.width() / key.getExtent().width())*(double)tileSize);
+        int target_height = (int)ceil((intersection.height() / key.getExtent().height())*(double)tileSize);
+        int tile_offset_left = (int)floor((offset_left / key.getExtent().width()) * (double)tileSize);
+        int tile_offset_top = (int)floor((offset_top / key.getExtent().height()) * (double)tileSize);
 
-            // Determine the destination window
+        // Compute spacing
+        double dx       = (xmax - xmin) / (tileSize-1);
+        double dy       = (ymax - ymin) / (tileSize-1);
 
-            // Compute the offsets in geo coordinates of the intersection from the TileKey
-            double offset_left = intersection.xMin() - xmin;
-            double offset_top = ymax - intersection.yMax();
+        OE_DEBUG << LC << "ReadWindow " << off_x << "," << off_y << " " << width << "x" << height << std::endl;
+        OE_DEBUG << LC << "DestWindow " << tile_offset_left << "," << tile_offset_top << " " << target_width << "x" << target_height << std::endl;
+
+
+        //Return if parameters are out of range.
+        if (width <= 0 || height <= 0 || target_width <= 0 || target_height <= 0)
+        {
+            return 0;
+        }
 
 
-            int target_width = (int)ceil((intersection.width() / key.getExtent().width())*(double)tileSize);
-            int target_height = (int)ceil((intersection.height() / key.getExtent().height())*(double)tileSize);
-            int tile_offset_left = (int)floor((offset_left / key.getExtent().width()) * (double)tileSize);
-            int tile_offset_top = (int)floor((offset_top / key.getExtent().height()) * (double)tileSize);
 
-            // Compute spacing
-            double dx       = (xmax - xmin) / (tileSize-1);
-            double dy       = (ymax - ymin) / (tileSize-1);
+        GDALRasterBand* bandRed = findBandByColorInterp(_warpedDS, GCI_RedBand);
+        GDALRasterBand* bandGreen = findBandByColorInterp(_warpedDS, GCI_GreenBand);
+        GDALRasterBand* bandBlue = findBandByColorInterp(_warpedDS, GCI_BlueBand);
+        GDALRasterBand* bandAlpha = findBandByColorInterp(_warpedDS, GCI_AlphaBand);
 
-            OE_DEBUG << LC << "ReadWindow " << off_x << "," << off_y << " " << width << "x" << height << std::endl;
-            OE_DEBUG << LC << "DestWindow " << tile_offset_left << "," << tile_offset_top << " " << target_width << "x" << target_height << std::endl;
+        GDALRasterBand* bandGray = findBandByColorInterp(_warpedDS, GCI_GrayIndex);
 
+        GDALRasterBand* bandPalette = findBandByColorInterp(_warpedDS, GCI_PaletteIndex);
 
-            //Return if parameters are out of range.
-            if (width <= 0 || height <= 0 || target_width <= 0 || target_height <= 0)
+        if (!bandRed && !bandGreen && !bandBlue && !bandAlpha && !bandGray && !bandPalette)
+        {
+            OE_DEBUG << LC << "Could not determine bands based on color interpretation, using band count" << std::endl;
+            //We couldn't find any valid bands based on the color interp, so just make an educated guess based on the number of bands in the file
+            //RGB = 3 bands
+            if (_warpedDS->GetRasterCount() == 3)
+            {
+                bandRed   = _warpedDS->GetRasterBand( 1 );
+                bandGreen = _warpedDS->GetRasterBand( 2 );
+                bandBlue  = _warpedDS->GetRasterBand( 3 );
+            }
+            //RGBA = 4 bands
+            else if (_warpedDS->GetRasterCount() == 4)
             {
-                return 0;
+                bandRed   = _warpedDS->GetRasterBand( 1 );
+                bandGreen = _warpedDS->GetRasterBand( 2 );
+                bandBlue  = _warpedDS->GetRasterBand( 3 );
+                bandAlpha = _warpedDS->GetRasterBand( 4 );
             }
+            //Gray = 1 band
+            else if (_warpedDS->GetRasterCount() == 1)
+            {
+                bandGray = _warpedDS->GetRasterBand( 1 );
+            }
+            //Gray + alpha = 2 bands
+            else if (_warpedDS->GetRasterCount() == 2)
+            {
+                bandGray  = _warpedDS->GetRasterBand( 1 );
+                bandAlpha = _warpedDS->GetRasterBand( 2 );
+            }
+        }
+
+
 
+        //The pixel format is always RGBA to support transparency
+        GLenum pixelFormat = GL_RGBA;
 
 
-            GDALRasterBand* bandRed = findBandByColorInterp(_warpedDS, GCI_RedBand);
-            GDALRasterBand* bandGreen = findBandByColorInterp(_warpedDS, GCI_GreenBand);
-            GDALRasterBand* bandBlue = findBandByColorInterp(_warpedDS, GCI_BlueBand);
-            GDALRasterBand* bandAlpha = findBandByColorInterp(_warpedDS, GCI_AlphaBand);
+        if (bandRed && bandGreen && bandBlue)
+        {
+            unsigned char *red = new unsigned char[target_width * target_height];
+            unsigned char *green = new unsigned char[target_width * target_height];
+            unsigned char *blue = new unsigned char[target_width * target_height];
+            unsigned char *alpha = new unsigned char[target_width * target_height];
 
-            GDALRasterBand* bandGray = findBandByColorInterp(_warpedDS, GCI_GrayIndex);
+            //Initialize the alpha values to 255.
+            memset(alpha, 255, target_width * target_height);
 
-            GDALRasterBand* bandPalette = findBandByColorInterp(_warpedDS, GCI_PaletteIndex);
+            image = new osg::Image;
+            image->allocateImage(tileSize, tileSize, 1, pixelFormat, GL_UNSIGNED_BYTE);
+            memset(image->data(), 0, image->getImageSizeInBytes());
 
-            if (!bandRed && !bandGreen && !bandBlue && !bandAlpha && !bandGray && !bandPalette)
+            //Nearest interpolation just uses RasterIO to sample the imagery and should be very fast.
+            if (!*_options.interpolateImagery() || _options.interpolation() == INTERP_NEAREST)
             {
-                OE_DEBUG << LC << "Could not determine bands based on color interpretation, using band count" << std::endl;
-                //We couldn't find any valid bands based on the color interp, so just make an educated guess based on the number of bands in the file
-                //RGB = 3 bands
-                if (_warpedDS->GetRasterCount() == 3)
+                bandRed->RasterIO(GF_Read, off_x, off_y, width, height, red, target_width, target_height, GDT_Byte, 0, 0);
+                bandGreen->RasterIO(GF_Read, off_x, off_y, width, height, green, target_width, target_height, GDT_Byte, 0, 0);
+                bandBlue->RasterIO(GF_Read, off_x, off_y, width, height, blue, target_width, target_height, GDT_Byte, 0, 0);
+
+                if (bandAlpha)
                 {
-                    bandRed   = _warpedDS->GetRasterBand( 1 );
-                    bandGreen = _warpedDS->GetRasterBand( 2 );
-                    bandBlue  = _warpedDS->GetRasterBand( 3 );
+                    bandAlpha->RasterIO(GF_Read, off_x, off_y, width, height, alpha, target_width, target_height, GDT_Byte, 0, 0);
                 }
-                //RGBA = 4 bands
-                else if (_warpedDS->GetRasterCount() == 4)
+
+                for (int src_row = 0, dst_row = tile_offset_top;
+                    src_row < target_height;
+                    src_row++, dst_row++)
                 {
-                    bandRed   = _warpedDS->GetRasterBand( 1 );
-                    bandGreen = _warpedDS->GetRasterBand( 2 );
-                    bandBlue  = _warpedDS->GetRasterBand( 3 );
-                    bandAlpha = _warpedDS->GetRasterBand( 4 );
+                    for (int src_col = 0, dst_col = tile_offset_left;
+                        src_col < target_width;
+                        ++src_col, ++dst_col)
+                    {
+                        unsigned char r = red[src_col + src_row * target_width];
+                        unsigned char g = green[src_col + src_row * target_width];
+                        unsigned char b = blue[src_col + src_row * target_width];
+                        unsigned char a = alpha[src_col + src_row * target_width];
+                        *(image->data(dst_col, dst_row) + 0) = r;
+                        *(image->data(dst_col, dst_row) + 1) = g;
+                        *(image->data(dst_col, dst_row) + 2) = b;
+                        if (!isValidValue( r, bandRed)    ||
+                            !isValidValue( g, bandGreen)  ||
+                            !isValidValue( b, bandBlue)   ||
+                            (bandAlpha && !isValidValue( a, bandAlpha )))
+                        {
+                            a = 0.0f;
+                        }
+                        *(image->data(dst_col, dst_row) + 3) = a;
+                    }
                 }
-                //Gray = 1 band
-                else if (_warpedDS->GetRasterCount() == 1)
+
+                image->flipVertical();
+            }
+            else
+            {
+                //Sample each point exactly
+                for (unsigned int c = 0; c < (unsigned int)tileSize; ++c)
                 {
-                    bandGray = _warpedDS->GetRasterBand( 1 );
+                    double geoX = xmin + (dx * (double)c);
+                    for (unsigned int r = 0; r < (unsigned int)tileSize; ++r)
+                    {
+                        double geoY = ymin + (dy * (double)r);
+                        *(image->data(c,r) + 0) = (unsigned char)getInterpolatedValue(bandRed,  geoX,geoY,false);
+                        *(image->data(c,r) + 1) = (unsigned char)getInterpolatedValue(bandGreen,geoX,geoY,false);
+                        *(image->data(c,r) + 2) = (unsigned char)getInterpolatedValue(bandBlue, geoX,geoY,false);
+                        if (bandAlpha != NULL)
+                            *(image->data(c,r) + 3) = (unsigned char)getInterpolatedValue(bandAlpha,geoX, geoY, false);
+                        else
+                            *(image->data(c,r) + 3) = 255;
+                    }
                 }
-                //Gray + alpha = 2 bands
-                else if (_warpedDS->GetRasterCount() == 2)
+            }
+
+            delete []red;
+            delete []green;
+            delete []blue;
+            delete []alpha;
+        }
+        else if (bandGray)
+        {
+            if ( getOptions().coverage() == true )
+            {
+                GDALDataType gdalDataType = bandGray->GetRasterDataType();
+                int          gdalSampleSize;
+                GLenum       glDataType;
+                GLint        internalFormat;
+
+                switch(gdalDataType)
                 {
-                    bandGray  = _warpedDS->GetRasterBand( 1 );
-                    bandAlpha = _warpedDS->GetRasterBand( 2 );
+                case GDT_Byte:
+                    glDataType = GL_FLOAT;
+                    gdalSampleSize = 1;
+                    internalFormat = GL_LUMINANCE32F_ARB;
+                    break;
+
+                case GDT_UInt16:
+                case GDT_Int16:
+                    glDataType = GL_FLOAT;
+                    gdalSampleSize = 2;
+                    internalFormat = GL_LUMINANCE32F_ARB;
+                    break;
+
+                default:
+                    glDataType = GL_FLOAT;
+                    gdalSampleSize = 4;
+                    internalFormat = GL_LUMINANCE32F_ARB;
                 }
-            }
 
+                // Create an un-normalized luminance image to hold coverage values.
+                image = new osg::Image();
+                image->allocateImage( tileSize, tileSize, 1, GL_LUMINANCE, glDataType );
+                image->setInternalTextureFormat( internalFormat );
+                ImageUtils::markAsUnNormalized( image.get(), true );
+                memset(image->data(), 0, image->getImageSizeInBytes());
+
+                ImageUtils::PixelWriter write(image.get());
+
+                // initialize all coverage texels to NODATA. -gw
+                osg::Vec4 temp;
+                temp.r() = NO_DATA_VALUE;
+
+                for(int s=0; s<image->s(); ++s) {
+                    for(int t=0; t<image->t(); ++t) {
+                        write(temp, s, t);
+                    }
+                }
+
+                // coverage data; one channel data that is not subject to interpolated values
+                unsigned char* data = new unsigned char[target_width * target_height * gdalSampleSize];
+                memset(data, 0, target_width * target_height * gdalSampleSize);
 
 
-            //The pixel format is always RGBA to support transparency
-            GLenum pixelFormat = GL_RGBA;
+                int success;
+                float nodata = bandGray->GetNoDataValue(&success);
+                if ( !success )
+                    nodata = NO_DATA_VALUE; //getNoDataValue(); //getOptions().noDataValue().get();
+
+                CPLErr err = bandGray->RasterIO(GF_Read, off_x, off_y, width, height, data, target_width, target_height, gdalDataType, 0, 0);
+                if ( err == CE_None )
+                {
+                    // copy from data to image.
+                    for (int src_row = 0, dst_row = tile_offset_top; src_row < target_height; src_row++, dst_row++)
+                    {
+                        for (int src_col = 0, dst_col = tile_offset_left; src_col < target_width; ++src_col, ++dst_col)
+                        {
+                            unsigned char* ptr = &data[(src_col + src_row*target_width)*gdalSampleSize];
 
+                            float value =
+                                gdalSampleSize == 1 ? (float)(*ptr) :
+                                gdalSampleSize == 2 ? (float)*(unsigned short*)ptr :
+                                gdalSampleSize == 4 ? *(float*)ptr :
+                                NO_DATA_VALUE;
 
-            if (bandRed && bandGreen && bandBlue)
+                            if ( !isValidValue_noLock(value, bandGray) )
+                                value = NO_DATA_VALUE;
+
+                            temp.r() = value;
+                            write(temp, dst_col, dst_row);
+                        }
+                    }
+
+                    // TODO: can we replace this by writing rows in reverse order? -gw
+                    image->flipVertical();
+                }
+                else // err != CE_None
+                {
+                    OE_WARN << LC << "RasterIO failed.\n";
+                    // TODO - handle error condition
+                }
+
+                delete [] data;
+            }
+
+            else // greyscale image (not a coverage)
             {
-                unsigned char *red = new unsigned char[target_width * target_height];
-                unsigned char *green = new unsigned char[target_width * target_height];
-                unsigned char *blue = new unsigned char[target_width * target_height];
+                unsigned char *gray = new unsigned char[target_width * target_height];
                 unsigned char *alpha = new unsigned char[target_width * target_height];
 
                 //Initialize the alpha values to 255.
@@ -1404,12 +1683,10 @@ public:
                 image->allocateImage(tileSize, tileSize, 1, pixelFormat, GL_UNSIGNED_BYTE);
                 memset(image->data(), 0, image->getImageSizeInBytes());
 
-                //Nearest interpolation just uses RasterIO to sample the imagery and should be very fast.
+
                 if (!*_options.interpolateImagery() || _options.interpolation() == INTERP_NEAREST)
                 {
-                    bandRed->RasterIO(GF_Read, off_x, off_y, width, height, red, target_width, target_height, GDT_Byte, 0, 0);
-                    bandGreen->RasterIO(GF_Read, off_x, off_y, width, height, green, target_width, target_height, GDT_Byte, 0, 0);
-                    bandBlue->RasterIO(GF_Read, off_x, off_y, width, height, blue, target_width, target_height, GDT_Byte, 0, 0);
+                    bandGray->RasterIO(GF_Read, off_x, off_y, width, height, gray, target_width, target_height, GDT_Byte, 0, 0);
 
                     if (bandAlpha)
                     {
@@ -1424,17 +1701,13 @@ public:
                             src_col < target_width;
                             ++src_col, ++dst_col)
                         {
-                            unsigned char r = red[src_col + src_row * target_width];
-                            unsigned char g = green[src_col + src_row * target_width];
-                            unsigned char b = blue[src_col + src_row * target_width];
+                            unsigned char g = gray[src_col + src_row * target_width];
                             unsigned char a = alpha[src_col + src_row * target_width];
-                            *(image->data(dst_col, dst_row) + 0) = r;
+                            *(image->data(dst_col, dst_row) + 0) = g;
                             *(image->data(dst_col, dst_row) + 1) = g;
-                            *(image->data(dst_col, dst_row) + 2) = b;
-                            if (!isValidValue( r, bandRed)    ||
-                                !isValidValue( g, bandGreen)  ||
-                                !isValidValue( b, bandBlue)   ||
-                                (bandAlpha && !isValidValue( a, bandAlpha )))
+                            *(image->data(dst_col, dst_row) + 2) = g;
+                            if (!isValidValue( g, bandGray) ||
+                                (bandAlpha && !isValidValue( a, bandAlpha)))
                             {
                                 a = 0.0f;
                             }
@@ -1446,281 +1719,114 @@ public:
                 }
                 else
                 {
-                    //Sample each point exactly
-                    for (unsigned int c = 0; c < (unsigned int)tileSize; ++c)
+                    for (int r = 0; r < tileSize; ++r)
                     {
-                        double geoX = xmin + (dx * (double)c);
-                        for (unsigned int r = 0; r < (unsigned int)tileSize; ++r)
+                        double geoY   = ymin + (dy * (double)r);
+
+                        for (int c = 0; c < tileSize; ++c)
                         {
-                            double geoY = ymin + (dy * (double)r);
-                            *(image->data(c,r) + 0) = (unsigned char)getInterpolatedValue(bandRed,  geoX,geoY,false);
-                            *(image->data(c,r) + 1) = (unsigned char)getInterpolatedValue(bandGreen,geoX,geoY,false);
-                            *(image->data(c,r) + 2) = (unsigned char)getInterpolatedValue(bandBlue, geoX,geoY,false);
+                            double geoX = xmin + (dx * (double)c);
+                            float  color = getInterpolatedValue(bandGray,geoX,geoY,false);
+
+                            *(image->data(c,r) + 0) = (unsigned char)color;
+                            *(image->data(c,r) + 1) = (unsigned char)color;
+                            *(image->data(c,r) + 2) = (unsigned char)color;
                             if (bandAlpha != NULL)
-                                *(image->data(c,r) + 3) = (unsigned char)getInterpolatedValue(bandAlpha,geoX, geoY, false);
+                                *(image->data(c,r) + 3) = (unsigned char)getInterpolatedValue(bandAlpha,geoX,geoY,false);
                             else
                                 *(image->data(c,r) + 3) = 255;
                         }
                     }
                 }
 
-                delete []red;
-                delete []green;
-                delete []blue;
+                delete []gray;
                 delete []alpha;
-            }            
-            else if (bandGray)
-            {
-                if ( getOptions().coverage() == true )
-                {                    
-                    GDALDataType gdalDataType = bandGray->GetRasterDataType();
-                    int          gdalSampleSize;
-                    GLenum       glDataType;
-                    GLint        internalFormat;
-
-                    switch(gdalDataType)
-                    {
-                    case GDT_Byte:
-                        glDataType = GL_FLOAT;
-                        gdalSampleSize = 1;
-                        internalFormat = GL_LUMINANCE32F_ARB;
-                        break;
-
-                    case GDT_UInt16:
-                    case GDT_Int16:
-                        glDataType = GL_FLOAT;
-                        gdalSampleSize = 2;
-                        internalFormat = GL_LUMINANCE32F_ARB;
-                        break;
-
-                    default:
-                        glDataType = GL_FLOAT;
-                        gdalSampleSize = 4;
-                        internalFormat = GL_LUMINANCE32F_ARB;
-                    }
-
-                    // Create an un-normalized luminance image to hold coverage values.
-                    image = new osg::Image();                    
-                    image->allocateImage( tileSize, tileSize, 1, GL_LUMINANCE, glDataType );
-                    image->setInternalTextureFormat( internalFormat );
-                    ImageUtils::markAsUnNormalized( image, true );
-                    memset(image->data(), 0, image->getImageSizeInBytes());
-                
-                    ImageUtils::PixelWriter write(image); 
-                    
-                    // initialize all coverage texels to NODATA. -gw
-                    osg::Vec4 temp;
-                    temp.r() = NO_DATA_VALUE;
-
-                    for(int s=0; s<image->s(); ++s) {
-                        for(int t=0; t<image->t(); ++t) {
-                            write(temp, s, t);
-                        }
-                    }
-                    
-                    // coverage data; one channel data that is not subject to interpolated values
-                    unsigned char* data = new unsigned char[target_width * target_height * gdalSampleSize];
-                    memset(data, 0, target_width * target_height * gdalSampleSize);
-
-
-                    int success;
-                    float nodata = bandGray->GetNoDataValue(&success);
-                    if ( !success )
-                        nodata = getOptions().noDataValue().get();
-
-                    CPLErr err = bandGray->RasterIO(GF_Read, off_x, off_y, width, height, data, target_width, target_height, gdalDataType, 0, 0);
-                    if ( err == CE_None )
-                    {
-                        // copy from data to image.
-                        for (int src_row = 0, dst_row = tile_offset_top; src_row < target_height; src_row++, dst_row++)
-                        {
-                            for (int src_col = 0, dst_col = tile_offset_left; src_col < target_width; ++src_col, ++dst_col)
-                            {
-                                unsigned char* ptr = &data[(src_col + src_row*target_width)*gdalSampleSize];
-
-                                float value = 
-                                    gdalSampleSize == 1 ? (float)(*ptr) :
-                                    gdalSampleSize == 2 ? (float)*(unsigned short*)ptr :
-                                    gdalSampleSize == 4 ? *(float*)ptr :
-                                                          NO_DATA_VALUE;
-
-                                if ( !isValidValue_noLock(value, bandGray) )
-                                    value = NO_DATA_VALUE;
+            }
+        }
+        else if (bandPalette)
+        {
+            //Pallete indexed imagery doesn't support interpolation currently and only uses nearest
+            //b/c interpolating pallete indexes doesn't make sense.
+            unsigned char *palette = new unsigned char[target_width * target_height];
 
-                                temp.r() = value;
-                                write(temp, dst_col, dst_row);
-                            }
-                        }
+            image = new osg::Image;
 
-                        // TODO: can we replace this by writing rows in reverse order? -gw
-                        image->flipVertical();
-                    }
-                    else // err != CE_None
-                    {
-                        OE_WARN << LC << "RasterIO failed.\n";
-                        // TODO - handle error condition
+            if ( _options.coverage() == true )
+            {
+                image->allocateImage(tileSize, tileSize, 1, GL_LUMINANCE, GL_FLOAT);
+                image->setInternalTextureFormat(GL_LUMINANCE32F_ARB);
+                ImageUtils::markAsUnNormalized(image.get(), true);
+
+                // initialize all coverage texels to NODATA. -gw
+                osg::Vec4 temp;
+                temp.r() = NO_DATA_VALUE;
+                ImageUtils::PixelWriter write(image.get());
+                for(int s=0; s<image->s(); ++s) {
+                    for(int t=0; t<image->t(); ++t) {
+                        write(temp, s, t);
                     }
-
-                    delete [] data;
                 }
-                
-                else // greyscale image (not a coverage)
-                {
-                    unsigned char *gray = new unsigned char[target_width * target_height];
-                    unsigned char *alpha = new unsigned char[target_width * target_height];
+            }
+            else
+            {
+                image->allocateImage(tileSize, tileSize, 1, pixelFormat, GL_UNSIGNED_BYTE);
+                memset(image->data(), 0, image->getImageSizeInBytes());
+            }
 
-                    //Initialize the alpha values to 255.
-                    memset(alpha, 255, target_width * target_height);
+            bandPalette->RasterIO(GF_Read, off_x, off_y, width, height, palette, target_width, target_height, GDT_Byte, 0, 0);
 
-                    image = new osg::Image;
-                    image->allocateImage(tileSize, tileSize, 1, pixelFormat, GL_UNSIGNED_BYTE);
-                    memset(image->data(), 0, image->getImageSizeInBytes());
+            ImageUtils::PixelWriter write(image.get());
 
+            for (int src_row = 0, dst_row = tile_offset_top;
+                src_row < target_height;
+                src_row++, dst_row++)
+            {
+                for (int src_col = 0, dst_col = tile_offset_left;
+                    src_col < target_width;
+                    ++src_col, ++dst_col)
+                {
+                    unsigned char p = palette[src_col + src_row * target_width];
 
-                    if (!*_options.interpolateImagery() || _options.interpolation() == INTERP_NEAREST)
+                    if ( _options.coverage() == true )
                     {
-                        bandGray->RasterIO(GF_Read, off_x, off_y, width, height, gray, target_width, target_height, GDT_Byte, 0, 0);
-
-                        if (bandAlpha)
-                        {
-                            bandAlpha->RasterIO(GF_Read, off_x, off_y, width, height, alpha, target_width, target_height, GDT_Byte, 0, 0);
-                        }
-
-                        for (int src_row = 0, dst_row = tile_offset_top;
-                            src_row < target_height;
-                            src_row++, dst_row++)
-                        {
-                            for (int src_col = 0, dst_col = tile_offset_left;
-                                src_col < target_width;
-                                ++src_col, ++dst_col)
-                            {
-                                unsigned char g = gray[src_col + src_row * target_width];
-                                unsigned char a = alpha[src_col + src_row * target_width];
-                                *(image->data(dst_col, dst_row) + 0) = g;
-                                *(image->data(dst_col, dst_row) + 1) = g;
-                                *(image->data(dst_col, dst_row) + 2) = g;
-                                if (!isValidValue( g, bandGray) ||
-                                   (bandAlpha && !isValidValue( a, bandAlpha)))
-                                {
-                                    a = 0.0f;
-                                }
-                                *(image->data(dst_col, dst_row) + 3) = a;
-                            }
-                        }
+                        osg::Vec4 pixel;
+                        if ( isValidValue(p, bandPalette) )
+                            pixel.r() = (float)p;
+                        else
+                            pixel.r() = NO_DATA_VALUE;
 
-                        image->flipVertical();
+                        write(pixel, dst_col, dst_row);
                     }
                     else
                     {
-                        for (int r = 0; r < tileSize; ++r)
+                        osg::Vec4ub color;
+                        getPalleteIndexColor( bandPalette, p, color );
+                        if (!isValidValue( p, bandPalette))
                         {
-                            double geoY   = ymin + (dy * (double)r);
-
-                            for (int c = 0; c < tileSize; ++c)
-                            {
-                                double geoX = xmin + (dx * (double)c);
-                                float  color = getInterpolatedValue(bandGray,geoX,geoY,false);
-
-                                *(image->data(c,r) + 0) = (unsigned char)color;
-                                *(image->data(c,r) + 1) = (unsigned char)color;
-                                *(image->data(c,r) + 2) = (unsigned char)color;
-                                if (bandAlpha != NULL)
-                                    *(image->data(c,r) + 3) = (unsigned char)getInterpolatedValue(bandAlpha,geoX,geoY,false);
-                                else
-                                    *(image->data(c,r) + 3) = 255;
-                            }
+                            color.a() = 0.0f;
                         }
-                    }
 
-                    delete []gray;
-                    delete []alpha;
-                }
-            }
-            else if (bandPalette)
-            {
-                //Pallete indexed imagery doesn't support interpolation currently and only uses nearest
-                //b/c interpolating pallete indexes doesn't make sense.
-                unsigned char *palette = new unsigned char[target_width * target_height];
-                
-                image = new osg::Image;
-
-                if ( _options.coverage() == true )
-                {
-                    image->allocateImage(tileSize, tileSize, 1, GL_LUMINANCE, GL_FLOAT);
-                    image->setInternalTextureFormat(GL_LUMINANCE32F_ARB);
-                    ImageUtils::markAsUnNormalized(image, true);
-
-                    // initialize all coverage texels to NODATA. -gw
-                    osg::Vec4 temp;
-                    temp.r() = NO_DATA_VALUE;
-                    ImageUtils::PixelWriter write(image);
-                    for(int s=0; s<image->s(); ++s) {
-                        for(int t=0; t<image->t(); ++t) {
-                            write(temp, s, t);
-                        }
-                    }
-                }
-                else
-                {
-                    image->allocateImage(tileSize, tileSize, 1, pixelFormat, GL_UNSIGNED_BYTE);
-                    memset(image->data(), 0, image->getImageSizeInBytes());
-                }
-
-                bandPalette->RasterIO(GF_Read, off_x, off_y, width, height, palette, target_width, target_height, GDT_Byte, 0, 0);
-
-                ImageUtils::PixelWriter write(image);
-
-                for (int src_row = 0, dst_row = tile_offset_top;
-                    src_row < target_height;
-                    src_row++, dst_row++)
-                {
-                    for (int src_col = 0, dst_col = tile_offset_left;
-                        src_col < target_width;
-                        ++src_col, ++dst_col)
-                    {
-                        unsigned char p = palette[src_col + src_row * target_width];
-
-                        if ( _options.coverage() == true )
-                        {    
-                            osg::Vec4 pixel;
-                            if ( isValidValue(p, bandPalette) )
-                                pixel.r() = (float)p;
-                            else
-                                pixel.r() = NO_DATA_VALUE;
-
-                            write(pixel, dst_col, dst_row);
-                        }
-                        else
-                        {                            
-                            osg::Vec4ub color;
-                            getPalleteIndexColor( bandPalette, p, color );
-                            if (!isValidValue( p, bandPalette))
-                            {
-                                color.a() = 0.0f;
-                            }
-
-                            *(image->data(dst_col, dst_row) + 0) = color.r();
-                            *(image->data(dst_col, dst_row) + 1) = color.g();
-                            *(image->data(dst_col, dst_row) + 2) = color.b();
-                            *(image->data(dst_col, dst_row) + 3) = color.a();
-                        }
+                        *(image->data(dst_col, dst_row) + 0) = color.r();
+                        *(image->data(dst_col, dst_row) + 1) = color.g();
+                        *(image->data(dst_col, dst_row) + 2) = color.b();
+                        *(image->data(dst_col, dst_row) + 3) = color.a();
                     }
                 }
+            }
 
-                image->flipVertical();
+            image->flipVertical();
 
-                delete [] palette;
+            delete [] palette;
 
-            }
-            else
-            {
-                OE_WARN
-                    << LC << "Could not find red, green and blue bands or gray bands in "
-                    << _options.url()->full()
-                    << ".  Cannot create image. " << std::endl;
+        }
+        else
+        {
+            OE_WARN
+                << LC << "Could not find red, green and blue bands or gray bands in "
+                << _options.url()->full()
+                << ".  Cannot create image. " << std::endl;
 
-                return NULL;
-            }
+            return NULL;
         }
 
         return image.release();
@@ -1892,7 +1998,7 @@ public:
 
         GDAL_SCOPED_LOCK;
 
-        int tileSize = _options.tileSize().value();
+        int tileSize = getPixelsPerTile();
 
         //Allocate the heightfield
         osg::ref_ptr<osg::HeightField> hf = new osg::HeightField;
@@ -2286,6 +2392,7 @@ private:
     double       _invtransform[6];
 
     GeoExtent _extents;
+    Bounds _bounds;
 
     const GDALOptions _options;
 
diff --git a/src/osgEarthDrivers/kml/KMLReader.cpp b/src/osgEarthDrivers/kml/KMLReader.cpp
index 43211cd..a1439f6 100644
--- a/src/osgEarthDrivers/kml/KMLReader.cpp
+++ b/src/osgEarthDrivers/kml/KMLReader.cpp
@@ -30,6 +30,8 @@
 using namespace osgEarth_kml;
 using namespace osgEarth;
 
+#undef LC
+#define LC "[KMLReader] "
 
 KMLReader::KMLReader( MapNode* mapNode, const KMLOptions* options ) :
 _mapNode( mapNode ),
@@ -41,6 +43,7 @@ _options( options )
 osg::Node*
 KMLReader::read( std::istream& in, const osgDB::Options* dbOptions )
 {
+    OE_INFO << LC << "Loading KML.." << std::endl;
     // pull the URI context out of the DB options:
     URIContext context(dbOptions);
 
@@ -52,13 +55,11 @@ KMLReader::read( std::istream& in, const osgDB::Options* dbOptions )
     xmlStr = buffer.str();
 	xml_document<> doc;
 	doc.parse<0>(&xmlStr[0]);
-    osg::Timer_t end = osg::Timer::instance()->tick();
-	OE_INFO << "Loaded KML in " << osg::Timer::instance()->delta_s(start, end) << std::endl;
 
-    start = osg::Timer::instance()->tick();
 	osg::Node* node = read(doc, dbOptions);
-    end = osg::Timer::instance()->tick();
-	OE_INFO << "Parsed KML in " << osg::Timer::instance()->delta_s(start, end) << std::endl;
+
+    osg::Timer_t end = osg::Timer::instance()->tick();
+	OE_INFO << LC << "Loaded KML in " << osg::Timer::instance()->delta_s(start, end) << std::endl;
 	node->setName( context.referrer() );
 
 	return node;
@@ -118,22 +119,22 @@ KMLReader::read( xml_document<>& doc, const osgDB::Options* dbOptions )
         osg::Timer_t start = osg::Timer::instance()->tick();
         kmlRoot.scan ( top, cx );    // first pass
         osg::Timer_t end = osg::Timer::instance()->tick();
-        OE_INFO << "Scan1 took " << osg::Timer::instance()->delta_s(start, end) << std::endl;
+        OE_INFO << LC << "  Scan1 took " << osg::Timer::instance()->delta_s(start, end) << std::endl;
 
         start = osg::Timer::instance()->tick();
         kmlRoot.scan2( top, cx );   // second pass
         end = osg::Timer::instance()->tick();
-        OE_INFO << "Scan2 took " << osg::Timer::instance()->delta_s(start, end) << std::endl;
+        OE_INFO << LC << "  Scan2 took " << osg::Timer::instance()->delta_s(start, end) << std::endl;
 
         start = osg::Timer::instance()->tick();
         kmlRoot.build( top, cx );   // third pass.
         end = osg::Timer::instance()->tick();
-        OE_INFO << "build took " << osg::Timer::instance()->delta_s(start, end) << std::endl;
+        OE_INFO << LC << "  build took " << osg::Timer::instance()->delta_s(start, end) << std::endl;
     }
 
     URIResultCache* cacheUsed = URIResultCache::from(cx._dbOptions.get());
     CacheStats stats = cacheUsed->getStats();
-    OE_INFO << LC << "URI Cache: " << stats._queries << " reads, " << (stats._hitRatio*100.0) << "% hits" << std::endl;
+    OE_INFO << LC << "  URI Cache: " << stats._queries << " reads, " << (stats._hitRatio*100.0) << "% hits" << std::endl;
 
     // Make sure the KML gets rendered after the terrain.
     root->getOrCreateStateSet()->setRenderBinDetails(2, "RenderBin");
diff --git a/src/osgEarthDrivers/kml/KML_Placemark.cpp b/src/osgEarthDrivers/kml/KML_Placemark.cpp
index 11e56f0..6ac5418 100644
--- a/src/osgEarthDrivers/kml/KML_Placemark.cpp
+++ b/src/osgEarthDrivers/kml/KML_Placemark.cpp
@@ -112,6 +112,11 @@ KML_Placemark::build( xml_node<>* node, KMLContext& cx )
                 // the annotation name:
                 std::string name = getValue(node, "name");
 
+                if (!name.empty())
+                {
+                    OE_INFO << LC << "Placemark: " << name << std::endl;
+                }
+
                 AnnotationNode* featureNode = 0L;
                 AnnotationNode* iconNode    = 0L;
                 AnnotationNode* modelNode   = 0L;
@@ -122,7 +127,7 @@ KML_Placemark::build( xml_node<>* node, KMLContext& cx )
                     // if there's a model, render that - models do NOT get labels.
                     if ( model )
                     {
-                        ModelNode* node = new ModelNode( cx._mapNode, style, cx._dbOptions );
+                        ModelNode* node = new ModelNode( cx._mapNode, style, cx._dbOptions.get() );
                         node->setPosition( position );
 
                         // model scale:
@@ -155,7 +160,7 @@ KML_Placemark::build( xml_node<>* node, KMLContext& cx )
                     // is there an icon?
                     if ( icon )
                     {
-                        iconNode = new PlaceNode( cx._mapNode, position, style, cx._dbOptions );
+                        iconNode = new PlaceNode( cx._mapNode, position, style, cx._dbOptions.get() );
                     }
 
                     else if ( !model && text && !name.empty() )
@@ -238,7 +243,18 @@ KML_Placemark::build( xml_node<>* node, KMLContext& cx )
                     }
                     if ( featureNode )
                     {
-                        cx._groupStack.top()->addChild( featureNode );
+                        osg::Node* child = featureNode;
+
+                        // If this feature node is map-clamped, we most likely need a depth-offset
+                        // shader to prevent z-fighting with the terrain.
+                        if (alt && alt->clamping() == alt->CLAMP_TO_TERRAIN && alt->technique() == alt->TECHNIQUE_MAP)
+                        {
+                            DepthOffsetGroup* g = new DepthOffsetGroup();
+                            g->addChild( featureNode );
+                            child = g;
+                        }
+                
+                        cx._groupStack.top()->addChild( child );
                         KML_Feature::build( node, cx, featureNode );
                     }
                 }
diff --git a/src/osgEarthDrivers/kml/KML_PolyStyle.cpp b/src/osgEarthDrivers/kml/KML_PolyStyle.cpp
index 9b9657a..34aeea5 100644
--- a/src/osgEarthDrivers/kml/KML_PolyStyle.cpp
+++ b/src/osgEarthDrivers/kml/KML_PolyStyle.cpp
@@ -25,40 +25,35 @@ KML_PolyStyle::scan( xml_node<>* node, Style& style, KMLContext& cx )
 {
 	if (node)
 	{
+        PolygonSymbol* poly = style.getOrCreate<PolygonSymbol>();
+
 		Color color(Color::White);
 		std::string colorVal = getValue(node, "color");
 		if (!colorVal.empty())
 		{
 			color = Color(Stringify() << "#" << colorVal, Color::ABGR);
+            poly->fill()->color() = color;
 		}
 
-		bool fill = true;	// By default it is true
-		std::string fillVal = getValue(node, "fill");
-		if (!fillVal.empty())
-		{
-			fill = (as<int>(fillVal, 1) == 1);
-			if (!fill)
-			{
-				color.a() = 0;
-			}
-		}
-
-		if (!colorVal.empty() || !style.has<PolygonSymbol>())
-		{
-			PolygonSymbol* poly = style.getOrCreate<PolygonSymbol>();
-			poly->fill()->color() = color;
-		}
+        if (poly)
+        {
+		    bool fill = true;	// By default it is true
+		    std::string fillVal = getValue(node, "fill");
+		    if (!fillVal.empty())
+		    {
+    			fill = (as<int>(fillVal, 1) == 1);
+			    if (!fill)
+			    {
+                    poly->fill()->color().a() = 0.0f;
+			    }
+		    }
 
-		bool outline = true;	// By default it is true
-		std::string outlineVal = getValue(node, "outline");
-		if (!outlineVal.empty())
-		{
-			outline = (as<int>(outlineVal, 0) == 1);
-		}
-		if (!outline)
-		{
-			LineSymbol* line = style.getOrCreate<LineSymbol>();
-			line->stroke()->color().a() = 0;
-		}
+		    std::string outlineVal = getValue(node, "outline");
+		    if (!outlineVal.empty())
+		    {
+			    bool outline = (as<int>(outlineVal, 0) == 1);
+                poly->outline() = outline;
+		    }
+        }
 	}
 }
diff --git a/src/osgEarthDrivers/kml/rapidxml_ext.hpp b/src/osgEarthDrivers/kml/rapidxml_ext.hpp
index c1fc433..c057938 100644
--- a/src/osgEarthDrivers/kml/rapidxml_ext.hpp
+++ b/src/osgEarthDrivers/kml/rapidxml_ext.hpp
@@ -28,6 +28,7 @@ inline std::string getAttr(xml_node<>* node, const std::string& key)
  */
 inline std::string getChildValue(xml_node<>* node, const std::string& key)
 {
+    std::string result;
 	if (node)
 	{
 		xml_node<>* child = node->first_node(key.c_str(), 0, false);
@@ -35,19 +36,23 @@ inline std::string getChildValue(xml_node<>* node, const std::string& key)
 		{
             if (child->value_size() > 0)
             {
-                return child->value();
+                result = child->value();
             }
             else //Try to read CDATA node
             {
                 child = child->first_node();
                 if (child)
                 {
-                    return child->value();
+                    result = child->value();
                 }
             }
 		}                
-	}
-	return "";
+
+    }
+    if (!result.empty())
+        osgEarth::trim2(result);
+
+	return result;
 }
 
 /*
diff --git a/src/osgEarthDrivers/label_annotation/AnnotationLabelSource.cpp b/src/osgEarthDrivers/label_annotation/AnnotationLabelSource.cpp
index c06ce73..d3e61c2 100644
--- a/src/osgEarthDrivers/label_annotation/AnnotationLabelSource.cpp
+++ b/src/osgEarthDrivers/label_annotation/AnnotationLabelSource.cpp
@@ -18,6 +18,7 @@
  */
 #include <osgEarthFeatures/LabelSource>
 #include <osgEarthFeatures/FeatureSourceIndexNode>
+#include <osgEarthFeatures/FilterContext>
 #include <osgEarthAnnotation/LabelNode>
 #include <osgEarthAnnotation/PlaceNode>
 #include <osgEarth/DepthOffset>
diff --git a/src/osgEarthDrivers/mapinspector/MapInspectorExtension b/src/osgEarthDrivers/mapinspector/MapInspectorExtension
index 1197627..d4742c8 100644
--- a/src/osgEarthDrivers/mapinspector/MapInspectorExtension
+++ b/src/osgEarthDrivers/mapinspector/MapInspectorExtension
@@ -34,8 +34,7 @@ namespace osgEarth { namespace MapInspector
      */
     class MapInspectorExtension : public Extension,
                                   public ExtensionInterface<MapNode>,
-                                  public ExtensionInterface<Control>,
-                                  public MapCallback
+                                  public ExtensionInterface<Control>
     {
     public:
         META_Object(osgearth_ext_MapInspector, MapInspectorExtension);
@@ -47,6 +46,7 @@ namespace osgEarth { namespace MapInspector
         // DTOR
         virtual ~MapInspectorExtension();
 
+        void updateUI();
 
     public: // ExtensionInterface<MapNode>
 
@@ -60,11 +60,7 @@ namespace osgEarth { namespace MapInspector
         bool connect(Control* control);
 
         bool disconnect(Control* control);
-
         
-    public: // MapCallback
-
-        void onMapModelChanged(const MapModelChange& change);
 
 
     protected: // Object
@@ -75,6 +71,7 @@ namespace osgEarth { namespace MapInspector
     private:
         osg::observer_ptr<MapNode> _mapNode;
         osg::ref_ptr<Control>      _ui;
+        osg::ref_ptr<MapCallback>  _mapCallback;
 
         void ctor();
     };
diff --git a/src/osgEarthDrivers/mapinspector/MapInspectorExtension.cpp b/src/osgEarthDrivers/mapinspector/MapInspectorExtension.cpp
index 07ab4d8..311e4ff 100644
--- a/src/osgEarthDrivers/mapinspector/MapInspectorExtension.cpp
+++ b/src/osgEarthDrivers/mapinspector/MapInspectorExtension.cpp
@@ -35,6 +35,20 @@ using namespace osgEarth::MapInspector;
 REGISTER_OSGEARTH_EXTENSION(osgearth_mapinspector, MapInspectorExtension)
 
 
+namespace
+{
+    struct MapCallbackProxy : public MapCallback
+    {
+        MapInspectorExtension* _extension;
+        MapCallbackProxy(MapInspectorExtension* extension) : _extension(extension) { }
+        void onMapModelChanged(const MapModelChange& change)
+        {
+            _extension->updateUI();
+        }
+    };
+}
+
+
 MapInspectorExtension::MapInspectorExtension()
 {
     ctor();
@@ -55,11 +69,12 @@ MapInspectorExtension::ctor()
 {
     OE_INFO << LC << "loaded\n";
     _ui = new MapInspectorUI();
+    _mapCallback = new MapCallbackProxy(this);
 }
 
 
 void 
-MapInspectorExtension::onMapModelChanged(const MapModelChange& change)
+MapInspectorExtension::updateUI()
 {
     osg::ref_ptr<MapNode> mapNode;
     _mapNode.lock(mapNode);
@@ -73,7 +88,7 @@ MapInspectorExtension::connect(MapNode* mapNode)
     if ( mapNode )
     {
         _mapNode = mapNode;
-        _mapNode->getMap()->addMapCallback(this);
+        _mapNode->getMap()->addMapCallback(_mapCallback.get());
         static_cast<MapInspectorUI*>(_ui.get())->reinit(mapNode);
     }
     
@@ -86,7 +101,7 @@ MapInspectorExtension::disconnect(MapNode* mapNode)
     OE_INFO << LC << "disconnected\n";
 
     if ( mapNode )
-        mapNode->getMap()->removeMapCallback(this);
+        mapNode->getMap()->removeMapCallback(_mapCallback.get());
 
     static_cast<MapInspectorUI*>(_ui.get())->reinit(0L);
     return true;
diff --git a/src/osgEarthDrivers/mapinspector/MapInspectorUI b/src/osgEarthDrivers/mapinspector/MapInspectorUI
index 5ec9311..73cb8d9 100644
--- a/src/osgEarthDrivers/mapinspector/MapInspectorUI
+++ b/src/osgEarthDrivers/mapinspector/MapInspectorUI
@@ -21,6 +21,8 @@
 
 #include <osgEarth/MapCallback>
 #include <osgEarth/MapNode>
+#include <osgEarth/TerrainLayer>
+#include <osgEarth/ModelLayer>
 #include <osgEarthUtil/Controls>
 #include <osg/View>
 
diff --git a/src/osgEarthDrivers/mapinspector/MapInspectorUI.cpp b/src/osgEarthDrivers/mapinspector/MapInspectorUI.cpp
index b6748ad..2c7f5c4 100644
--- a/src/osgEarthDrivers/mapinspector/MapInspectorUI.cpp
+++ b/src/osgEarthDrivers/mapinspector/MapInspectorUI.cpp
@@ -63,22 +63,19 @@ MapInspectorUI::reinit(MapNode* mapNode)
         
         Map* map = mapNode->getMap();
 
-        for(int i=0; i<map->getNumElevationLayers(); ++i)
+        for (unsigned i = 0; i < map->getNumLayers(); ++i)
         {
-            ElevationLayer* layer = map->getElevationLayerAt(i);
-            addTerrainLayer( layer, mapNode );
-        }
-
-        for(int i=0; i<map->getNumImageLayers(); ++i)
-        {
-            ImageLayer* layer = map->getImageLayerAt(i);
-            addTerrainLayer( layer, mapNode );
-        }
+            TerrainLayer* terrainLayer = map->getLayerAt<TerrainLayer>(i);
+            if (terrainLayer)
+            {
+                addTerrainLayer(terrainLayer, mapNode);
+            }
 
-        for(int i=0; i<map->getNumModelLayers(); ++i)
-        {
-            ModelLayer* layer = map->getModelLayerAt(i);
-            addModelLayer( layer, mapNode );
+            ModelLayer* modelLayer = map->getLayerAt<ModelLayer>(i);
+            if (modelLayer)
+            {
+                addModelLayer(modelLayer, mapNode);
+            }
         }
     }
     else
@@ -103,18 +100,25 @@ MapInspectorUI::addTerrainLayer(TerrainLayer* layer,
 
     osg::ref_ptr<MultiGeometry> collection = new MultiGeometry();
 
-    DataExtentList exlist;
-    if (layer->getDataExtents(exlist))
+    const DataExtentList& exlist = layer->getDataExtents();
+    if (!exlist.empty())
     {
         for(DataExtentList::const_iterator i = exlist.begin(); i != exlist.end(); ++i)
         {
             const DataExtent& e = *i;
-            Polygon* p = new Polygon();
-            p->push_back( e.xMin(), e.yMin() );
-            p->push_back( e.xMax(), e.yMin() );
-            p->push_back( e.xMax(), e.yMax() );
-            p->push_back( e.xMin(), e.yMax() );
-            collection->add( p );
+            if (e.isValid())
+            {
+                Polygon* p = new Polygon();
+                p->push_back( e.west(), e.south() );
+                p->push_back( e.east(), e.south() );
+                p->push_back( e.east(), e.north() );
+                p->push_back( e.west(), e.north() );
+                collection->add( p );
+            }
+            else
+            {
+                OE_WARN << LC << "Invalid data extent: " << e.toString() << std::endl;
+            }
         }
 
         // poly:
diff --git a/src/osgEarthDrivers/mask_feature/FeatureMaskOptions b/src/osgEarthDrivers/mask_feature/FeatureMaskOptions
index f252501..243cbe9 100644
--- a/src/osgEarthDrivers/mask_feature/FeatureMaskOptions
+++ b/src/osgEarthDrivers/mask_feature/FeatureMaskOptions
@@ -52,7 +52,7 @@ namespace osgEarth { namespace Drivers
     public:
         Config getConfig() const {
             Config conf = MaskSourceOptions::getConfig();
-            conf.updateObjIfSet( "features", _featureOptions );
+            conf.setObj( "features", _featureOptions );
             return conf;
         }
 
diff --git a/src/osgEarthDrivers/mask_feature/FeatureMaskSource.cpp b/src/osgEarthDrivers/mask_feature/FeatureMaskSource.cpp
index 87828ac..b9c286a 100644
--- a/src/osgEarthDrivers/mask_feature/FeatureMaskSource.cpp
+++ b/src/osgEarthDrivers/mask_feature/FeatureMaskSource.cpp
@@ -20,8 +20,11 @@
 #include <osgEarth/MaskSource>
 #include <osgEarth/Registry>
 #include <osgEarth/Map>
+
 #include <osgEarthFeatures/TransformFilter>
 #include <osgEarthFeatures/FeatureSource>
+#include <osgEarthFeatures/FilterContext>
+#include <osgEarthFeatures/FeatureCursor>
 
 #include <osgDB/FileNameUtils>
 #include <OpenThreads/Mutex>
diff --git a/src/osgEarthDrivers/mbtiles/MBTilesOptions b/src/osgEarthDrivers/mbtiles/MBTilesOptions
index d5721e1..eadf34a 100644
--- a/src/osgEarthDrivers/mbtiles/MBTilesOptions
+++ b/src/osgEarthDrivers/mbtiles/MBTilesOptions
@@ -69,10 +69,10 @@ namespace osgEarth { namespace Drivers
     public:
         Config getConfig() const {
             Config conf = TileSourceOptions::getConfig();
-            conf.updateIfSet("filename", _filename);            
-            conf.updateIfSet("format", _format);            
-            conf.updateIfSet("compute_levels", _computeLevels);
-            conf.updateIfSet("compress", _compress);
+            conf.set("filename", _filename);    
+            conf.set("format", _format);            
+            conf.set("compute_levels", _computeLevels);
+            conf.set("compress", _compress);
             return conf;
         }
 
@@ -83,6 +83,7 @@ namespace osgEarth { namespace Drivers
 
         void fromConfig( const Config& conf ) {
             conf.getIfSet( "filename", _filename );
+            conf.getIfSet( "url", _filename ); // compat for consistency with other drivers
             conf.getIfSet( "format", _format );
             conf.getIfSet( "compute_levels", _computeLevels );
             conf.getIfSet( "compress", _compress );
diff --git a/src/osgEarthDrivers/mbtiles/MBTilesTileSource.cpp b/src/osgEarthDrivers/mbtiles/MBTilesTileSource.cpp
index c55eb76..7808f9a 100644
--- a/src/osgEarthDrivers/mbtiles/MBTilesTileSource.cpp
+++ b/src/osgEarthDrivers/mbtiles/MBTilesTileSource.cpp
@@ -49,7 +49,7 @@ namespace
         rw = osgDB::Registry::instance()->getReaderWriterForMimeType( format );
         if ( rw == 0L )
         {
-            rw = osgDB::Registry::instance()->getReaderWriterForExtension( format ); 
+            rw = osgDB::Registry::instance()->getReaderWriterForExtension( format );
         }
         return rw;
     }
@@ -59,7 +59,7 @@ namespace
 
 MBTilesTileSource::MBTilesTileSource(const TileSourceOptions& options) :
 TileSource( options ),
-_options  ( options ),      
+_options  ( options ),
 _database ( NULL ),
 _minLevel ( 0 ),
 _maxLevel ( 20 ),
@@ -70,12 +70,19 @@ _forceRGB ( false )
 
 Status
 MBTilesTileSource::initialize(const osgDB::Options* dbOptions)
-{    
-    _dbOptions = Registry::instance()->cloneOrCreateOptions( dbOptions );    
-    
+{
+    _dbOptions = Registry::instance()->cloneOrCreateOptions( dbOptions );
+
     bool readWrite = (MODE_WRITE & (int)getMode()) != 0;
 
-    std::string fullFilename = _options.filename()->full();   
+    std::string fullFilename = _options.filename()->full();
+    if (!osgDB::fileExists(fullFilename))
+    {
+        fullFilename = osgDB::findDataFile(fullFilename, dbOptions);
+        if (fullFilename.empty())
+            fullFilename = _options.filename()->full();
+    }
+
     bool isNewDatabase = readWrite && !osgDB::fileExists(fullFilename);
 
     if ( isNewDatabase )
@@ -102,17 +109,17 @@ MBTilesTileSource::initialize(const osgDB::Options* dbOptions)
 
     // Try to open (or create) the database. We use SQLITE_OPEN_NOMUTEX to do
     // our own mutexing.
-    int flags = readWrite 
+    int flags = readWrite
         ? (SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_NOMUTEX)
         : (SQLITE_OPEN_READONLY | SQLITE_OPEN_NOMUTEX);
-     
+
     int rc = sqlite3_open_v2( fullFilename.c_str(), &_database, flags, 0L );
     if ( rc != 0 )
-    {                        
+    {
         return Status::Error( Status::ResourceUnavailable, Stringify()
             << "Database \"" << fullFilename << "\": " << sqlite3_errmsg(_database) );
     }
-    
+
     // New database setup:
     if ( isNewDatabase )
     {
@@ -166,12 +173,12 @@ MBTilesTileSource::initialize(const osgDB::Options* dbOptions)
         std::string profileStr;
         getMetaData( "profile", profileStr );
 
-        // The data format (e.g., png, jpg, etc.). Any format passed in 
+        // The data format (e.g., png, jpg, etc.). Any format passed in
         // in the options is superceded by the one in the database metadata.
         std::string metaDataFormat;
         getMetaData( "format", metaDataFormat );
         if ( !metaDataFormat.empty() )
-            _tileFormat = metaDataFormat;        
+            _tileFormat = metaDataFormat;
 
         // Try to get it from the options.
         if (_tileFormat.empty())
@@ -200,7 +207,7 @@ MBTilesTileSource::initialize(const osgDB::Options* dbOptions)
                 return Status::Error(Status::ServiceUnavailable, "Cannot find compressor \"" + compression + "\"");
             else
                 OE_INFO << LC << "Data is compressed (" << compression << ")" << std::endl;
-        }        
+        }
 
         // Set the profile
         const Profile* profile = getProfile();
@@ -221,17 +228,17 @@ MBTilesTileSource::initialize(const osgDB::Options* dbOptions)
 
                 if ( !profile )
                 {
-                    return Status::Error( Stringify() << "Profile not recognized: " << profileStr );
+                    OE_WARN << LC << "Profile \"" << profileStr << "\" not recognized; defaulting to spherical-mercator\n";
                 }
             }
-            
+
             if (!profile)
             {
                 // Spherical mercator is the MBTiles default.
                 profile = osgEarth::Registry::instance()->getSphericalMercatorProfile();
             }
 
-            setProfile( profile );     
+            setProfile( profile );
             OE_INFO << LC << "Profile = " << profileStr << std::endl;
         }
 
@@ -243,24 +250,31 @@ MBTilesTileSource::initialize(const osgDB::Options* dbOptions)
             StringTokenizer(",").tokenize(boundsStr, tokens);
             if (tokens.size() == 4)
             {
-                GeoExtent extent(
-                    osgEarth::SpatialReference::get("wgs84"),
-                    osgEarth::as<double>(tokens[0], 0.0),
-                    osgEarth::as<double>(tokens[1], 0.0), // south
-                    osgEarth::as<double>(tokens[2], 0.0), // east
-                    osgEarth::as<double>(tokens[3], 0.0)  // north
-                    );
+                double minLon = osgEarth::as<double>(tokens[0], 0.0);
+                double minLat = osgEarth::as<double>(tokens[1], 0.0);
+                double maxLon = osgEarth::as<double>(tokens[2], 0.0);
+                double maxLat = osgEarth::as<double>(tokens[3], 0.0);
 
-                this->getDataExtents().push_back(DataExtent(extent, _minLevel, _maxLevel));
-
-                OE_INFO << LC << "Bounds = " << extent.toString() << std::endl;
+                GeoExtent extent(osgEarth::SpatialReference::get("wgs84"), minLon, minLat, maxLon, maxLat);
+                if (extent.isValid())
+                {
+                    // Using 0 for the minLevel is not technically correct, but we use it instead of the proper minLevel to force osgEarth to subdivide
+                    // since we don't really handle DataExtents with minLevels > 0 just yet.
+                    getDataExtents().push_back(DataExtent(extent, 0, _maxLevel));                
+                    OE_INFO << LC << "Bounds = " << extent.toString() << std::endl;                    
+                }
+                else
+                {
+                    OE_WARN << LC << "MBTiles has invalid bounds " << extent.toString() << std::endl;
+                }                
             }
         }
         else
         {
-            this->getDataExtents().push_back(DataExtent(getProfile()->getExtent(), _minLevel, _maxLevel));
+            // Using 0 for the minLevel is not technically correct, but we use it instead of the proper minLevel to force osgEarth to subdivide
+            // since we don't really handle DataExtents with minLevels > 0 just yet.
+            this->getDataExtents().push_back(DataExtent(getProfile()->getExtent(), 0, _maxLevel));
         }
-
     }
 
     // do we require RGB? for jpeg?
@@ -276,7 +290,7 @@ MBTilesTileSource::initialize(const osgDB::Options* dbOptions)
     memset(data, 0, 4 * size * size);
 
     return STATUS_OK;
-}    
+}
 
 
 CachePolicy
@@ -301,7 +315,7 @@ MBTilesTileSource::createImage(const TileKey&    key,
 
     if (z < (int)_minLevel)
     {
-        return _emptyImage.get();            
+        return _emptyImage.get();
     }
 
     if (z > (int)_maxLevel)
@@ -324,7 +338,7 @@ MBTilesTileSource::createImage(const TileKey&    key,
         return NULL;
     }
 
-    bool valid = true;        
+    bool valid = true;
 
     sqlite3_bind_int( select, 1, z );
     sqlite3_bind_int( select, 2, x );
@@ -333,7 +347,7 @@ MBTilesTileSource::createImage(const TileKey&    key,
     osg::Image* result = NULL;
     rc = sqlite3_step( select );
     if ( rc == SQLITE_ROW)
-    {                     
+    {
         // the pointer returned from _blob gets freed internally by sqlite, supposedly
         const char* data = (const char*)sqlite3_column_blob( select, 0 );
         int dataLen = sqlite3_column_bytes( select, 0 );
@@ -363,7 +377,7 @@ MBTilesTileSource::createImage(const TileKey&    key,
             osgDB::ReaderWriter::ReadResult rr = _rw->readImage( inputStream, _dbOptions.get() );
             if (rr.validImage())
             {
-                result = rr.takeImage();                
+                result = rr.takeImage();
             }
         }
     }
@@ -377,7 +391,7 @@ MBTilesTileSource::createImage(const TileKey&    key,
     return result;
 }
 
-bool 
+bool
 MBTilesTileSource::storeImage(const TileKey&    key,
                               osg::Image*       image,
                               ProgressCallback* progress)
@@ -407,7 +421,7 @@ MBTilesTileSource::storeImage(const TileKey&    key,
     }
 
     std::string value = buf.str();
-    
+
     // compress if necessary:
     if ( _compressor.valid() )
     {
@@ -461,7 +475,7 @@ MBTilesTileSource::storeImage(const TileKey&    key,
         OE_WARN << LC << "Failed query: " << query << "(" << rc << ")" << sqlite3_errstr(rc) << "; " << sqlite3_errmsg(_database) << std::endl;
 #else
         OE_WARN << LC << "Failed query: " << query << "(" << rc << ")" << rc << "; " << sqlite3_errmsg(_database) << std::endl;
-#endif        
+#endif
         ok = false;
     }
 
@@ -497,7 +511,7 @@ MBTilesTileSource::getMetaData(const std::string& key, std::string& value)
 
     rc = sqlite3_step( select );
     if ( rc == SQLITE_ROW)
-    {                     
+    {
         value = (char*)sqlite3_column_text( select, 0 );
     }
     else
@@ -544,7 +558,7 @@ MBTilesTileSource::putMetaData(const std::string& key, const std::string& value)
 
 void
 MBTilesTileSource::computeLevels()
-{        
+{
     Threading::ScopedMutexLock exclusiveLock(_mutex);
 
     osg::Timer_t startTime = osg::Timer::instance()->tick();
@@ -558,7 +572,7 @@ MBTilesTileSource::computeLevels()
 
     rc = sqlite3_step( select );
     if ( rc == SQLITE_ROW)
-    {                     
+    {
         _minLevel = sqlite3_column_int( select, 0 );
         _maxLevel = sqlite3_column_int( select, 1 );
         OE_DEBUG << LC << "Min=" << _minLevel << " Max=" << _maxLevel << std::endl;
@@ -566,8 +580,8 @@ MBTilesTileSource::computeLevels()
     else
     {
         OE_DEBUG << LC << "SQL QUERY failed for " << query << ": " << std::endl;
-    }        
-    sqlite3_finalize( select );        
+    }
+    sqlite3_finalize( select );
     osg::Timer_t endTime = osg::Timer::instance()->tick();
     OE_DEBUG << LC << "Computing levels took " << osg::Timer::instance()->delta_s(startTime, endTime ) << " s" << std::endl;
 }
@@ -590,7 +604,7 @@ MBTilesTileSource::createTables()
         return false;
     }
 
-    query = 
+    query =
         "CREATE TABLE IF NOT EXISTS tiles ("
         " zoom_level integer,"
         " tile_column integer,"
@@ -616,7 +630,7 @@ MBTilesTileSource::createTables()
         OE_WARN << LC << "Failed to create index on table [tiles]: " << errorMsg << std::endl;
         sqlite3_free( errorMsg );
         // keep going...
-        // return false; 
+        // return false;
     }
 
     // TODO: support "grids" and "grid_data" tables if necessary.
@@ -625,7 +639,7 @@ MBTilesTileSource::createTables()
 }
 
 std::string
-MBTilesTileSource::getExtension() const 
+MBTilesTileSource::getExtension() const
 {
     return _tileFormat;
 }
diff --git a/src/osgEarthDrivers/model_feature_geom/FeatureGeomModelOptions b/src/osgEarthDrivers/model_feature_geom/FeatureGeomModelOptions
index 3f54f44..c10f116 100644
--- a/src/osgEarthDrivers/model_feature_geom/FeatureGeomModelOptions
+++ b/src/osgEarthDrivers/model_feature_geom/FeatureGeomModelOptions
@@ -28,11 +28,12 @@ namespace osgEarth { namespace Drivers
     using namespace osgEarth;
     using namespace osgEarth::Features;
 
-    class FeatureGeomModelOptions : public FeatureModelSourceOptions // NO EXPORT; header only
+    class FeatureGeomModelOptions : public FeatureModelSourceOptions,
+                                    public GeometryCompilerOptions // NO EXPORT; header only
     {
-    public:
-        GeometryCompilerOptions& compilerOptions() { return _compilerOptions; }
-        const GeometryCompilerOptions& compilerOptions() const { return _compilerOptions; }
+    //public:
+    //    GeometryCompilerOptions& compilerOptions() { return _compilerOptions; }
+    //    const GeometryCompilerOptions& compilerOptions() const { return _compilerOptions; }
 
     public:
         FeatureGeomModelOptions( const ConfigOptions& options =ConfigOptions() ) :
@@ -50,8 +51,7 @@ namespace osgEarth { namespace Drivers
 
             // merges the configurations together, so you can still specify 
             // compiler options at the model level
-            Config compilerConfig = _compilerOptions.getConfig();
-            conf.merge( compilerConfig );
+            conf.merge( GeometryCompilerOptions::getConfig() );
 
             return conf;
         }
@@ -59,16 +59,13 @@ namespace osgEarth { namespace Drivers
     protected:
         virtual void mergeConfig( const Config& conf ) {
             FeatureModelSourceOptions::mergeConfig( conf );
-            _compilerOptions.mergeConfig( conf );
             fromConfig( conf );
         }
 
     private:
         void fromConfig( const Config& conf ) {
-            _compilerOptions.mergeConfig(conf);
+            GeometryCompilerOptions::fromConfig(conf);
         }
-
-        GeometryCompilerOptions _compilerOptions;
     };
 
 } } // namespace osgEarth::Drivers
diff --git a/src/osgEarthDrivers/model_feature_geom/FeatureGeomModelSource.cpp b/src/osgEarthDrivers/model_feature_geom/FeatureGeomModelSource.cpp
index cde82ab..4bd5b89 100644
--- a/src/osgEarthDrivers/model_feature_geom/FeatureGeomModelSource.cpp
+++ b/src/osgEarthDrivers/model_feature_geom/FeatureGeomModelSource.cpp
@@ -58,7 +58,7 @@ namespace
 
         FeatureNodeFactory* createFeatureNodeFactory()
         {
-            return new GeomFeatureNodeFactory(_options.compilerOptions());
+            return new GeomFeatureNodeFactory(_options);
         }
 
     private:
diff --git a/src/osgEarthDrivers/model_simple/SimpleModelOptions b/src/osgEarthDrivers/model_simple/SimpleModelOptions
index 891acda..7e17d0b 100644
--- a/src/osgEarthDrivers/model_simple/SimpleModelOptions
+++ b/src/osgEarthDrivers/model_simple/SimpleModelOptions
@@ -81,13 +81,13 @@ namespace osgEarth { namespace Drivers
     public:
         Config getConfig() const {
             Config conf = ModelSourceOptions::getConfig();
-            conf.updateIfSet( "url", _url );
-            conf.updateIfSet( "lod_scale", _lod_scale );
-            conf.updateIfSet( "location", _location );
-            conf.updateIfSet( "orientation", _orientation);
-            conf.updateIfSet( "loading_priority_scale", _loadingPriorityScale );
-            conf.updateIfSet( "loading_priority_offset", _loadingPriorityOffset );
-            conf.updateIfSet( "paged", _paged);
+            conf.set( "url", _url );
+            conf.set( "lod_scale", _lod_scale );
+            conf.set( "location", _location );
+            conf.set( "orientation", _orientation);
+            conf.set( "loading_priority_scale", _loadingPriorityScale );
+            conf.set( "loading_priority_offset", _loadingPriorityOffset );
+            conf.set( "paged", _paged);
 
             conf.addIfSet( "shader_policy", "disable",  _shaderPolicy, SHADERPOLICY_DISABLE );
             conf.addIfSet( "shader_policy", "inherit",  _shaderPolicy, SHADERPOLICY_INHERIT );
diff --git a/src/osgEarthDrivers/model_simple/SimpleModelSource.cpp b/src/osgEarthDrivers/model_simple/SimpleModelSource.cpp
index 3d870e6..c59e712 100644
--- a/src/osgEarthDrivers/model_simple/SimpleModelSource.cpp
+++ b/src/osgEarthDrivers/model_simple/SimpleModelSource.cpp
@@ -271,7 +271,7 @@ public:
                 osg::ref_ptr<StateSetCache> cache = new StateSetCache();
 
                 Registry::shaderGenerator().run(
-                    result,
+                    result.get(),
                     _options.url()->base(),
                     cache.get() );
             }
diff --git a/src/osgEarthDrivers/noise/CMakeLists.txt b/src/osgEarthDrivers/noise/CMakeLists.txt
deleted file mode 100644
index 0f877c9..0000000
--- a/src/osgEarthDrivers/noise/CMakeLists.txt
+++ /dev/null
@@ -1,24 +0,0 @@
-#
-# Noise generator plugin
-#
-
-set(TARGET_SRC
-	NoiseExtension.cpp
-	NoiseTerrainEffect.cpp
-	${SHADERS_CPP} )
-	
-set(LIB_PUBLIC_HEADERS
-	NoiseExtension
-	NoiseOptions
-	NoiseTerrainEffect)
-	
-set(TARGET_H
-	${LIB_PUBLIC_HEADERS} )
-
-set(TARGET_COMMON_LIBRARIES ${TARGET_COMMON_LIBRARIES}
-    osgEarthUtil)
-	
-setup_plugin(osgearth_noise)
-
-# to install public driver includes:
-set(LIB_NAME noise)
diff --git a/src/osgEarthDrivers/noise/NoiseExtension b/src/osgEarthDrivers/noise/NoiseExtension
deleted file mode 100644
index 0b572d1..0000000
--- a/src/osgEarthDrivers/noise/NoiseExtension
+++ /dev/null
@@ -1,74 +0,0 @@
-/* -*-c++-*- */
-/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2016 Pelican Mapping
- * http://osgearth.org
- *
- * osgEarth is free software; you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>
- */
-#ifndef OSGEARTH_NOISE_EXTENSION
-#define OSGEARTH_NOISE_EXTENSION 1
-
-#include "NoiseOptions"
-#include "NoiseTerrainEffect"
-#include <osgEarth/Extension>
-#include <osgEarth/MapNode>
-#include <osgEarthUtil/Controls>
-
-namespace osgEarth { namespace Noise
-{
-    using namespace osgEarth;
-    using namespace osgEarth::Util::Controls;
-
-    /**
-     * Extension for loading the normal mapping effect on demand.
-     */
-    class NoiseExtension : public Extension,
-                           public ExtensionInterface<MapNode>,
-                           public NoiseOptions
-    {
-    public:
-        META_Object(osgearth_ext_normalmap, NoiseExtension);
-
-        // CTORs
-        NoiseExtension();
-        NoiseExtension(const NoiseOptions& options);
-
-        // DTOR
-        virtual ~NoiseExtension();
-
-
-    public: // Extension
-
-        virtual void setDBOptions(const osgDB::Options* dbOptions);
-
-        virtual const ConfigOptions& getConfigOptions() const { return *this; }
-
-    public: // ExtensionInterface<MapNode>
-
-        bool connect(MapNode* mapNode);
-
-        bool disconnect(MapNode* mapNode);
-
-
-    protected: // Object
-        NoiseExtension(const NoiseExtension& rhs, const osg::CopyOp& op) { }
-
-    private:
-        osg::ref_ptr<const osgDB::Options> _dbOptions;
-        osg::ref_ptr<NoiseTerrainEffect>   _effect;
-    };
-
-} } // namespace osgEarth::Noise
-
-#endif // OSGEARTH_NOISE_EXTENSION
diff --git a/src/osgEarthDrivers/noise/NoiseExtension.cpp b/src/osgEarthDrivers/noise/NoiseExtension.cpp
deleted file mode 100644
index 935bffd..0000000
--- a/src/osgEarthDrivers/noise/NoiseExtension.cpp
+++ /dev/null
@@ -1,80 +0,0 @@
-/* -*-c++-*- */
-/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2016 Pelican Mapping
- * http://osgearth.org
- *
- * osgEarth is free software; you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>
- */
-#include "NoiseExtension"
-#include "NoiseTerrainEffect"
-#include <osgEarth/TerrainEngineNode>
-
-using namespace osgEarth;
-using namespace osgEarth::Noise;
-
-#define LC "[NoiseExtension] "
-
-
-REGISTER_OSGEARTH_EXTENSION( osgearth_noise, NoiseExtension )
-
-
-NoiseExtension::NoiseExtension()
-{
-    //nop
-}
-
-NoiseExtension::NoiseExtension(const NoiseOptions& options) :
-NoiseOptions( options )
-{
-    //nop
-}
-
-NoiseExtension::~NoiseExtension()
-{
-    //nop
-}
-
-void
-NoiseExtension::setDBOptions(const osgDB::Options* dbOptions)
-{
-    _dbOptions = dbOptions;
-}
-
-bool
-NoiseExtension::connect(MapNode* mapNode)
-{
-    if ( !mapNode )
-    {
-        OE_WARN << LC << "Illegal: MapNode cannot be null." << std::endl;
-        return false;
-    }
-
-    _effect = new NoiseTerrainEffect( *this );
-
-    mapNode->getTerrainEngine()->addEffect( _effect.get() );
-    
-    return true;
-}
-
-bool
-NoiseExtension::disconnect(MapNode* mapNode)
-{
-    if ( mapNode )
-    {
-        mapNode->getTerrainEngine()->removeEffect( _effect.get() );
-    }
-    _effect = 0L;
-    return true;
-}
-
diff --git a/src/osgEarthDrivers/noise/NoiseOptions b/src/osgEarthDrivers/noise/NoiseOptions
deleted file mode 100644
index 5c7c0e5..0000000
--- a/src/osgEarthDrivers/noise/NoiseOptions
+++ /dev/null
@@ -1,82 +0,0 @@
-/* -*-c++-*- */
-/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2016 Pelican Mapping
- * http://osgearth.org
- *
- * osgEarth is free software; you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>
- */
-#ifndef OSGEARTH_DRIVER_NOISE_OPTIONS
-#define OSGEARTH_DRIVER_NOISE_OPTIONS 1
-
-#include <osgEarth/Common>
-#include <osgEarth/URI>
-
-namespace osgEarth { namespace Noise
-{
-    using namespace osgEarth;
-
-    /**
-     * Options governing the terrain noise texture.
-     */
-    class NoiseOptions : public DriverConfigOptions // NO EXPORT; header only
-    {
-    public:
-        /** Size of the noise texture in the U and V dimensions. Default = 1024. */
-        optional<unsigned>& size() { return _size; }
-        const optional<unsigned>& size() const { return _size; }
-
-        /** Number of channels of noise to make (1-4) default = 1. */
-        optional<unsigned>& numChannels() { return _numChannels; }
-        const optional<unsigned>& numChannels() const { return _numChannels; }
-
-
-    public:
-        NoiseOptions( const ConfigOptions& opt =ConfigOptions() ) : DriverConfigOptions( opt )
-        {
-            setDriver( "noise" );
-            _size.init( 1024u );
-            _numChannels.init( 1u );
-            fromConfig( _conf );
-        }
-
-        virtual ~NoiseOptions() { }
-
-    public:
-        Config getConfig() const {
-            Config conf = DriverConfigOptions::getConfig();
-            conf.addIfSet( "size", _size );
-            conf.addIfSet( "num_channels", _numChannels );
-            return conf;
-        }
-
-    protected:
-        void mergeConfig( const Config& conf ) {
-            DriverConfigOptions::mergeConfig( conf );
-            fromConfig( conf );
-        }
-
-    private:
-        void fromConfig( const Config& conf ) {
-            conf.getIfSet( "size", _size );
-            conf.getIfSet( "num_channels", _numChannels );
-        }
-
-        optional<unsigned> _size;
-        optional<unsigned> _numChannels;
-    };
-
-} } // namespace osgEarth::Noise
-
-#endif // OSGEARTH_DRIVER_NOISE_OPTIONS
-
diff --git a/src/osgEarthDrivers/noise/NoiseTerrainEffect b/src/osgEarthDrivers/noise/NoiseTerrainEffect
deleted file mode 100644
index 50a0bf1..0000000
--- a/src/osgEarthDrivers/noise/NoiseTerrainEffect
+++ /dev/null
@@ -1,65 +0,0 @@
-/* -*-c++-*- */
-/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2012 Pelican Mapping
-* http://osgearth.org
-*
-* osgEarth is free software; you can redistribute it and/or modify
-* it under the terms of the GNU Lesser General Public License as published by
-* the Free Software Foundation; either version 2 of the License, or
-* (at your option) any later version.
-*
-* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
-* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
-* IN THE SOFTWARE.
-*
-* You should have received a copy of the GNU Lesser General Public License
-* along with this program.  If not, see <http://www.gnu.org/licenses/>
-*/
-#ifndef OSGEARTH_NOISE_TERRAIN_EFFECT_H
-#define OSGEARTH_NOISE_TERRAIN_EFFECT_H
-
-#include "NoiseOptions"
-
-#include <osgEarth/TerrainEffect>
-#include <osg/Uniform>
-#include <osg/Texture2D>
-
-using namespace osgEarth;
-
-namespace osgEarth { namespace Noise
-{
-    /**
-     * Effect that activates a multi-resolution noise texture on the terrain.
-     */
-    class NoiseTerrainEffect : public TerrainEffect
-    {
-    public:
-        /** construct a new terrain effect. */
-        NoiseTerrainEffect(const NoiseOptions& options);        
-
-
-    public: // TerrainEffect interface
-
-        void onInstall(TerrainEngineNode* engine);
-
-        void onUninstall(TerrainEngineNode* engine);
-
-
-    protected:
-        virtual ~NoiseTerrainEffect() { }
-
-        osg::Texture* createNoiseTexture() const;
-
-        NoiseOptions                 _options;
-        int                          _texImageUnit;
-        osg::ref_ptr<osg::Texture>   _tex;
-        osg::ref_ptr<osg::Uniform>   _samplerUniform;
-    };
-
-} } // namespace osgEarth::Noise
-
-#endif // OSGEARTH_NOISE_TERRAIN_EFFECT_H
diff --git a/src/osgEarthDrivers/noise/NoiseTerrainEffect.cpp b/src/osgEarthDrivers/noise/NoiseTerrainEffect.cpp
deleted file mode 100644
index c891a60..0000000
--- a/src/osgEarthDrivers/noise/NoiseTerrainEffect.cpp
+++ /dev/null
@@ -1,167 +0,0 @@
-/* -*-c++-*- */
-/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2012 Pelican Mapping
-* http://osgearth.org
-*
-* osgEarth is free software; you can redistribute it and/or modify
-* it under the terms of the GNU Lesser General Public License as published by
-* the Free Software Foundation; either version 2 of the License, or
-* (at your option) any later version.
-*
-* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
-* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
-* IN THE SOFTWARE.
-*
-* You should have received a copy of the GNU Lesser General Public License
-* along with this program.  If not, see <http://www.gnu.org/licenses/>
-*/
-#include "NoiseTerrainEffect"
-
-#include <osgEarth/Registry>
-#include <osgEarth/Capabilities>
-#include <osgEarth/TerrainEngineNode>
-#include <osgEarth/ImageUtils>
-#include <osgEarthUtil/SimplexNoise>
-
-#include <osgDB/WriteFile>
-
-#define LC "[Noise] "
-
-#define SAMPLER_NAME "oe_noise_tex"
-//#define MATRIX_NAME  "oe_nmap_normalTexMatrix"
-
-using namespace osgEarth;
-using namespace osgEarth::Noise;
-
-NoiseTerrainEffect::NoiseTerrainEffect(const NoiseOptions& options) :
-_options     ( options ),
-_texImageUnit( -1 )
-{
-    _tex = createNoiseTexture();
-}
-
-void
-NoiseTerrainEffect::onInstall(TerrainEngineNode* engine)
-{
-    if ( engine )
-    {
-        engine->getResources()->reserveTextureImageUnit(_texImageUnit, "Noise");
-        if ( _texImageUnit >= 0 )
-        {        
-            osg::StateSet* stateset = engine->getOrCreateStateSet();
-            stateset->setTextureAttribute( _texImageUnit, _tex.get() );
-            stateset->addUniform( new osg::Uniform(SAMPLER_NAME, _texImageUnit) );
-            OE_INFO << LC << "Noise generator installed!\n";
-        }
-        else
-        {
-            OE_WARN << LC << "No texture image units available; noise disabled.\n";
-        }
-    }
-}
-
-
-void
-NoiseTerrainEffect::onUninstall(TerrainEngineNode* engine)
-{
-    if ( engine && _texImageUnit >= 0 )
-    {
-        osg::StateSet* stateset = engine->getStateSet();
-        if ( stateset )
-        {
-            stateset->removeUniform( SAMPLER_NAME );
-            stateset->removeTextureAttribute( _texImageUnit, _tex.get() );
-        }
-
-        engine->getResources()->releaseTextureImageUnit( _texImageUnit );
-        _texImageUnit = -1;
-    }
-}
-
-
-osg::Texture*
-NoiseTerrainEffect::createNoiseTexture() const
-{
-    const int size  = (int)osg::clampBetween(_options.size().get(), 1u, 16384u);
-    const int chans = (int)osg::clampBetween(_options.numChannels().get(), 1u, 4u);
-
-    GLenum type = chans > 2 ? GL_RGBA : GL_LUMINANCE;
-    
-    osg::Image* image = new osg::Image();
-    image->allocateImage(size, size, 1, type, GL_UNSIGNED_BYTE);
-
-    // 0 = rocky mountains..
-    // 1 = warping...
-    const float F[4] = { 4.0f, 16.0f, 4.0f, 8.0f };
-    const float P[4] = { 0.8f,  0.6f, 0.8f, 0.9f };
-    const float L[4] = { 2.2f,  1.7f, 3.0f, 4.0f };
-    
-    for(int k=0; k<chans; ++k)
-    {
-        // Configure the noise function:
-        osgEarth::Util::SimplexNoise noise;
-        noise.setNormalize( true );
-        noise.setRange( 0.0, 1.0 );
-        noise.setFrequency( F[k] );
-        noise.setPersistence( P[k] );
-        noise.setLacunarity( L[k] );
-        noise.setOctaves( 8 );
-
-        float nmin = 10.0f;
-        float nmax = -10.0f;
-
-        // write repeating noise to the image:
-        ImageUtils::PixelReader read ( image );
-        ImageUtils::PixelWriter write( image );
-        for(int t=0; t<size; ++t)
-        {
-            double rt = (double)t/size;
-            for(int s=0; s<size; ++s)
-            {
-                double rs = (double)s/(double)size;
-
-                double n = noise.getTiledValue(rs, rt);
-
-                n = osg::clampBetween(n, 0.0, 1.0);
-
-                if ( n < nmin ) nmin = n;
-                if ( n > nmax ) nmax = n;
-                osg::Vec4f v = read(s, t);
-                v[k] = n;
-                write(v, s, t);
-            }
-        }
-   
-        // histogram stretch to [0..1]
-        for(int x=0; x<size*size; ++x)
-        {
-            int s = x%size, t = x/size;
-            osg::Vec4f v = read(s, t);
-            v[k] = osg::clampBetween((v[k]-nmin)/(nmax-nmin), 0.0f, 1.0f);
-            write(v, s, t);
-        }
-
-        //OE_INFO << LC << "Noise: MIN = " << nmin << "; MAX = " << nmax << "\n";
-    }
-
-#if 0
-    std::string filename("noise.png");
-    osgDB::writeImageFile(*image, filename);
-    OE_NOTICE << LC << "Wrote noise texture to " << filename << "\n";
-#endif
-
-    // make a texture:
-    osg::Texture2D* tex = new osg::Texture2D( image );
-    tex->setWrap(tex->WRAP_S, tex->REPEAT);
-    tex->setWrap(tex->WRAP_T, tex->REPEAT);
-    tex->setFilter(tex->MIN_FILTER, tex->LINEAR_MIPMAP_LINEAR);
-    tex->setFilter(tex->MAG_FILTER, tex->LINEAR);
-    tex->setMaxAnisotropy( 4.0f );
-    tex->setUnRefImageDataAfterApply( true );
-
-    return tex;
-}
diff --git a/src/osgEarthDrivers/ocean_simple/ElevationProxyImageLayer b/src/osgEarthDrivers/ocean_simple/ElevationProxyImageLayer
index f53fe1c..0523100 100644
--- a/src/osgEarthDrivers/ocean_simple/ElevationProxyImageLayer
+++ b/src/osgEarthDrivers/ocean_simple/ElevationProxyImageLayer
@@ -49,7 +49,7 @@ namespace osgEarth { namespace SimpleOcean
 
         virtual TileSource* createTileSource();
 
-        virtual bool isKeyInRange( const TileKey& key ) const;
+        virtual bool isKeyInLegalRange( const TileKey& key ) const;
 
         virtual bool isCached( const TileKey& key ) const;
 
diff --git a/src/osgEarthDrivers/ocean_simple/ElevationProxyImageLayer.cpp b/src/osgEarthDrivers/ocean_simple/ElevationProxyImageLayer.cpp
index f3d26cb..8d0412c 100644
--- a/src/osgEarthDrivers/ocean_simple/ElevationProxyImageLayer.cpp
+++ b/src/osgEarthDrivers/ocean_simple/ElevationProxyImageLayer.cpp
@@ -19,6 +19,7 @@
 #include "ElevationProxyImageLayer"
 
 #include <osgEarth/HeightFieldUtils>
+#include <osgEarth/ImageUtils>
 
 using namespace osgEarth;
 using namespace osgEarth::SimpleOcean;
@@ -27,11 +28,11 @@ using namespace osgEarth::SimpleOcean;
 
 
 ElevationProxyImageLayer::ElevationProxyImageLayer(const Map* sourceMap,
-                                                   const ImageLayerOptions& options ) :
-ImageLayer( options ),
+                                                   const ImageLayerOptions& inoptions ) :
+ImageLayer( inoptions ),
 _mapf     ( sourceMap )
 {
-    _runtimeOptions.cachePolicy() = CachePolicy::NO_CACHE;
+    options().cachePolicy() = CachePolicy::NO_CACHE;
 }
 
 TileSource*
@@ -41,9 +42,9 @@ ElevationProxyImageLayer::createTileSource()
 }
 
 bool
-ElevationProxyImageLayer::isKeyInRange( const TileKey& key ) const
+ElevationProxyImageLayer::isKeyInLegalRange( const TileKey& key ) const
 {
-    return key.getLevelOfDetail() <= *_runtimeOptions.maxLevel();
+    return key.getLevelOfDetail() <= options().maxLevel().get();
 }
 
 bool
@@ -64,22 +65,31 @@ ElevationProxyImageLayer::createImage(const TileKey& key, ProgressCallback* prog
         }
     }
 
-    osg::ref_ptr<osg::HeightField> hf = HeightFieldUtils::createReferenceHeightField(key.getExtent(), 257,257, true );
+    osg::ref_ptr<osg::HeightField> hf = HeightFieldUtils::createReferenceHeightField(key.getExtent(), 64, 64, 0, true );
 
     if ( _mapf.populateHeightField(hf, key, true, 0L) )
     {
         // encode the heightfield as a 16-bit normalized LUNIMANCE image
         osg::Image* image = new osg::Image();
-        image->allocateImage(hf->getNumColumns(), hf->getNumRows(), 1, GL_LUMINANCE, GL_UNSIGNED_SHORT);
-        image->setInternalTextureFormat( GL_LUMINANCE16 );
+        image->allocateImage(hf->getNumColumns(), hf->getNumRows(), 1, GL_RED, GL_FLOAT); //GL_LUMINANCE, GL_UNSIGNED_SHORT);
+        image->setInternalTextureFormat( GL_R32F );
         const osg::FloatArray* floats = hf->getFloatArray();
-        for( unsigned int i = 0; i < floats->size(); ++i  )
-        {
-            int col = i % hf->getNumColumns();
-            int row = i / hf->getNumColumns();
-            *(unsigned short*)image->data( col, row ) = (unsigned short)(32768 + (short)floats->at(i));
+        ImageUtils::PixelWriter write(image);
+        for (unsigned t = 0; t < image->t(); ++t) {
+            for (unsigned s = 0; s < image->s(); ++s) {
+                float v = floats->at(t*image->s()+s);
+                write(osg::Vec4(v,v,v,v), s, t);
+            }
         }
 
+        //for( unsigned int i = 0; i < floats->size(); ++i  )
+        //{
+        //    int col = i % hf->getNumColumns();
+        //    int row = i / hf->getNumColumns();
+        //    *(float*)image->data(col, row) = floats->at(i);
+        //    //*(unsigned short*)image->data( col, row ) = (unsigned short)(32768 + (short)floats->at(i));
+        //}
+
         return GeoImage( image, key.getExtent() );
     }
     else
diff --git a/src/osgEarthDrivers/ocean_simple/SimpleOcean.FS.glsl b/src/osgEarthDrivers/ocean_simple/SimpleOcean.FS.glsl
index d713651..7f69ad5 100644
--- a/src/osgEarthDrivers/ocean_simple/SimpleOcean.FS.glsl
+++ b/src/osgEarthDrivers/ocean_simple/SimpleOcean.FS.glsl
@@ -1,10 +1,12 @@
 #version $GLSL_VERSION_STR
+$GLSL_DEFAULT_PRECISION_FLOAT
 
 #pragma vp_name       SimpleOcean Proxy FS
 #pragma vp_entryPoint oe_ocean_fragment
 #pragma vp_location   fragment_coloring
 #pragma vp_order      0.6
-#pragma vp_define     USE_OCEAN_MASK
+
+#pragma import_defines(OE_SIMPLE_OCEAN_USE_TEXTURE, OE_SIMPLE_OCEAN_USE_MASK)
 
 // clamps a value to the vmin/vmax range, then re-maps it to the r0/r1 range:
 float ocean_remap( float val, float vmin, float vmax, float r0, float r1 )  
@@ -13,9 +15,11 @@ float ocean_remap( float val, float vmin, float vmax, float r0, float r1 )
     return r0 + vr * (r1-r0);  
 }  
 
-// uniforms
-uniform bool ocean_has_surface_tex;           // whether there's a surface texture
+#ifdef OE_SIMPLE_OCEAN_USE_TEXTURE
 uniform sampler2D ocean_surface_tex;          // surface texture
+in vec4 ocean_surface_tex_coord;         // surface texture coords
+#endif
+
 uniform float ocean_seaLevel;                 // sea level offset
 uniform float ocean_lowFeather;               // offset from sea level at which to start feathering out
 uniform float ocean_highFeather;              // offset from sea level at which to stop feathering out
@@ -27,11 +31,9 @@ uniform float oe_ocean_alpha;                 // The ocean alpha
 // inputs from vertex stage
 in float ocean_v_msl;                    // elevation (MSL) of camera
 in float ocean_v_range;                  // distance from camera to current vertex
-in float ocean_v_enorm;                  // normalized terrain height at vertex [0..1]
-in vec4 ocean_surface_tex_coord;         // surface texture coords
 
 
-#ifdef USE_OCEAN_MASK
+#ifdef OE_SIMPLE_OCEAN_USE_MASK
 
 uniform sampler2D ocean_data;
 in vec4 ocean_mask_tex_coord;
@@ -49,18 +51,17 @@ void oe_ocean_fragment(inout vec4 color)
        ocean_v_range, 
        ocean_max_range - ocean_fade_range, ocean_max_range * rangeFactor, 
        1.0, 0.0);  
-
+    
+#ifdef OE_SIMPLE_OCEAN_USE_TEXTURE
     // start with the surface texture.
-    if (ocean_has_surface_tex)  
-    {  
-        float a1 = texture(ocean_surface_tex, ocean_surface_tex_coord.xy).r;  
-        float a2 = -texture(ocean_surface_tex, ocean_surface_tex_coord.zw).r;  
-        const float contrast = 1.0;
-        float brightness = 1.0 + 0.5*(a1+a2);
-        color = clamp(((color-0.5)*contrast + 0.5) * brightness, 0.0, 1.0);
-        color.a = ocean_baseColor.a; 
-        color.a = max(color.a, ocean_baseColor.a);
-    }  
+    float a1 = texture(ocean_surface_tex, ocean_surface_tex_coord.xy).r;  
+    float a2 = -texture(ocean_surface_tex, ocean_surface_tex_coord.zw).r;  
+    const float contrast = 1.0;
+    float brightness = 1.0 + 0.5*(a1+a2);
+    color = clamp(((color-0.5)*contrast + 0.5) * brightness, 0.0, 1.0);
+    color.a = ocean_baseColor.a; 
+    color.a = max(color.a, ocean_baseColor.a);
+#endif // OE_SIMPLE_OCEAN_USE_TEXTURE
 
     // effect of the terrain mask [0..1] in the alpha component.
     float maskEffect = texture(ocean_data, ocean_mask_tex_coord.xy).a;  
@@ -68,10 +69,12 @@ void oe_ocean_fragment(inout vec4 color)
     // color it
     color = vec4( color.rgb, maskEffect * rangeEffect * color.a * oe_ocean_alpha );  
 
-    //"    color = vec4( 1, 0, 0, 1 );   // debugging
+    //    color = vec4( maskEffect, 0, 0, 1 );   // debugging
 }
 
-#else
+#else // no mask
+
+in float ocean_terrainHeight; // terrain height at vertex in proxy Map
 
 // Proxy layer version:
 void oe_ocean_fragment(inout vec4 color)  
@@ -87,28 +90,24 @@ void oe_ocean_fragment(inout vec4 color)
         ocean_max_range - ocean_fade_range, ocean_max_range * rangeFactor, 
         1.0, 0.0);  
 
+#ifdef OE_SIMPLE_OCEAN_USE_TEXTURE
     // start with the surface texture.
-    if (ocean_has_surface_tex)  
-    {  
-        float a1 = texture(ocean_surface_tex, ocean_surface_tex_coord.xy).r;  
-        float a2 = -texture(ocean_surface_tex, ocean_surface_tex_coord.zw).r;  
-        const float contrast = 1.0;
-        float brightness = 1.0 + 0.5*(a1+a2);
-        color = clamp(((color-0.5)*contrast + 0.5) * brightness, 0.0, 1.0);
-        color.a = ocean_baseColor.a; 
-        color.a = max(color.a, ocean_baseColor.a);
-    }  
-
-    // un-normalize the heightfield data
-    float terrainHeight = (ocean_v_enorm * 65535.0) - 32768.0;  
+    float a1 = texture(ocean_surface_tex, ocean_surface_tex_coord.xy).r;  
+    float a2 = -texture(ocean_surface_tex, ocean_surface_tex_coord.zw).r;  
+    const float contrast = 1.0;
+    float brightness = 1.0 + 0.5*(a1+a2);
+    color = clamp(((color-0.5)*contrast + 0.5) * brightness, 0.0, 1.0);
+    color.a = ocean_baseColor.a; 
+    color.a = max(color.a, ocean_baseColor.a);
+#endif // OE_SIMPLE_OCEAN_USE_TEXTURE
 
     // heightfield's effect on alpha [0..1]
-    float terrainEffect = ocean_remap( terrainHeight, ocean_seaLevel+ocean_lowFeather, ocean_seaLevel+ocean_highFeather, 1.0, 0.0 );   
+    float terrainEffect = ocean_remap( ocean_terrainHeight, ocean_seaLevel+ocean_lowFeather, ocean_seaLevel+ocean_highFeather, 1.0, 0.0 );   
 
     // color it
     color = vec4( color.rgb, terrainEffect * rangeEffect * oe_ocean_alpha * color.a );  
 
-    //     color = vec4( 1, 0, 0, 1 );   // debugging
+   //      color = vec4( terrainEffect, 0, 0, 1 );   // debugging
 }
 
 #endif
diff --git a/src/osgEarthDrivers/ocean_simple/SimpleOcean.VS.glsl b/src/osgEarthDrivers/ocean_simple/SimpleOcean.VS.glsl
index e58d977..431dc84 100644
--- a/src/osgEarthDrivers/ocean_simple/SimpleOcean.VS.glsl
+++ b/src/osgEarthDrivers/ocean_simple/SimpleOcean.VS.glsl
@@ -1,9 +1,23 @@
 #version $GLSL_VERSION_STR
+$GLSL_DEFAULT_PRECISION_FLOAT
 
 #pragma vp_name       SimpleOcean with Proxy VS
 #pragma vp_entryPoint oe_ocean_vertex
 #pragma vp_location   vertex_view
-#pragma vp_define     USE_OCEAN_MASK
+
+#pragma import_defines(OE_SIMPLE_OCEAN_USE_TEXTURE, OE_SIMPLE_OCEAN_USE_MASK)
+
+// uniforms
+uniform mat4 osg_ViewMatrixInverse;  
+uniform float osg_FrameTime;  
+uniform float ocean_seaLevel;                 // sea level offset
+
+// outputs to fragment stage 
+out float ocean_v_msl;                      // elevation (MSL) of camera
+out float ocean_v_range;                    // distance from camera to current vertex
+
+#ifdef OE_SIMPLE_OCEAN_USE_TEXTURE
+out vec4 ocean_surface_tex_coord;       // tex coords for surface texture
 
 // convert an ecef coordinate to lon/lat (low precision)
 vec2 ocean_xyz_to_spherical(in vec3 xyz)  
@@ -14,25 +28,14 @@ vec2 ocean_xyz_to_spherical(in vec3 xyz)
     return vec2(lon,lat);  
 }  
 
-// uniforms
-uniform mat4 osg_ViewMatrixInverse;  
-uniform float osg_FrameTime;  
-uniform sampler2D ocean_data;                 // heightfield encoded into 16 bit texture
-uniform float ocean_seaLevel;                 // sea level offset
-uniform sampler2D ocean_surface_tex;          // surface texture
-uniform bool ocean_has_surface_tex;           // whether there's a surface texture
+#endif
 
-// outputs to fragment stage
-out vec4 ocean_surface_tex_coord;  
-out float ocean_v_msl;                      // elevation (MSL) of camera
-out float ocean_v_range;                    // distance from camera to current vertex
-out float ocean_v_enorm;                    // normalized terrain height at vertex [0..1]
 
 // stage global
 vec3 vp_Normal;
 
 
-#ifdef USE_OCEAN_MASK
+#ifdef OE_SIMPLE_OCEAN_USE_MASK
 out vec4 ocean_mask_tex_coord;
 
 // Ocean mask version:
@@ -57,17 +60,24 @@ void oe_ocean_vertex(inout vec4 VertexMODEL)
 
     // disatnce to camera:
     ocean_v_range = ocean_v_msl;  
-
+    
+#ifdef OE_SIMPLE_OCEAN_USE_TEXTURE
     // scale the texture mapping to something reasonable:
     vec4 worldVertex = osg_ViewMatrixInverse * mvVertex;  
     vec2 lonlat = ocean_xyz_to_spherical( worldVertex.xyz/worldVertex.w );  
     ocean_surface_tex_coord.xy = lonlat/0.0005;
     ocean_surface_tex_coord.zw = ocean_surface_tex_coord.xy;  
     ocean_surface_tex_coord.w -= mod(0.1*osg_FrameTime,25.0)/25.0; 
+#endif
 }
 
 #else
 
+uniform sampler2D oe_ocean_proxyTex; // heightfield encoded into 16 bit texture
+uniform mat4 oe_ocean_proxyMat;      // texture matrix for elevation data
+vec4 oe_layer_tilec;                // stage global tile coordinates
+out float ocean_terrainHeight;      // terrain height at proxy Map vertex.
+
 // Proxy layer version:
 void oe_ocean_vertex(inout vec4 VertexVIEW)  
 {  
@@ -79,8 +89,8 @@ void oe_ocean_vertex(inout vec4 VertexVIEW)
 
     VertexVIEW = mvVertex2;  
 
-    // read normalized [0..1] elevation data from the height texture:
-    ocean_v_enorm = texture2D( ocean_data, gl_MultiTexCoord0.st ).r;  
+    // read elevation data from proxy height texture
+    ocean_terrainHeight = texture(oe_ocean_proxyTex, (oe_ocean_proxyMat*oe_layer_tilec).st).r;
 
     // send interpolated params to the fs:
     vec4 eye = osg_ViewMatrixInverse * vec4(0,0,0,1);  
@@ -91,12 +101,14 @@ void oe_ocean_vertex(inout vec4 VertexVIEW)
     // disatnce to camera:
     ocean_v_range = ocean_v_msl;  
 
+#ifdef OE_SIMPLE_OCEAN_USE_TEXTURE
     // scale the texture mapping to something reasonable:
     vec4 worldVertex = osg_ViewMatrixInverse * mvVertex;  
     vec2 lonlat = ocean_xyz_to_spherical( worldVertex.xyz/worldVertex.w );  
     ocean_surface_tex_coord.xy = lonlat/0.0005;
     ocean_surface_tex_coord.zw = ocean_surface_tex_coord.xy;  
     ocean_surface_tex_coord.w -= mod(0.1*osg_FrameTime,25.0)/25.0; 
+#endif
  }
 
 #endif
diff --git a/src/osgEarthDrivers/ocean_simple/SimpleOceanNode.cpp b/src/osgEarthDrivers/ocean_simple/SimpleOceanNode.cpp
index 7e4c81c..9329fa8 100644
--- a/src/osgEarthDrivers/ocean_simple/SimpleOceanNode.cpp
+++ b/src/osgEarthDrivers/ocean_simple/SimpleOceanNode.cpp
@@ -21,15 +21,17 @@
 #include "SimpleOceanShaders"
 #include <osgEarth/Map>
 #include <osgEarth/ShaderFactory>
-#include <osgEarth/TextureCompositor>
+#include <osgEarth/TerrainResources>
 #include <osgEarth/ImageUtils>
 #include <osgEarth/CullingUtils>
-#include <osgEarthUtil/SimplexNoise>
+#include <osgEarth/SimplexNoise>
 #include <osgEarthDrivers/engine_mp/MPTerrainEngineOptions>
+#include <osgEarthDrivers/engine_rex/RexTerrainEngineOptions>
 
 #include <osg/CullFace>
 #include <osg/Depth>
 #include <osg/Texture2D>
+#include <osg/Material>
 
 #define LC "[SimpleOceanNode] "
 
@@ -37,6 +39,7 @@ using namespace osgEarth;
 using namespace osgEarth::Util;
 using namespace osgEarth::SimpleOcean;
 using namespace osgEarth::Drivers::MPTerrainEngine;
+using namespace osgEarth::Drivers::RexTerrainEngine;
 
 namespace
 {
@@ -122,22 +125,22 @@ SimpleOceanNode::rebuild()
         if ( mno.enableLighting().isSet() )
             mno.enableLighting() = *mno.enableLighting();
 
-        MPTerrainEngineOptions mpoptions;
-        mpoptions.heightFieldSkirtRatio() = 0.0;      // don't want to see skirts
-        mpoptions.minLOD() = maxLOD().get(); // weird, I know
+        RexTerrainEngineOptions terrainoptions;
 
-        // so we can the surface from underwater:
-        mpoptions.clusterCulling() = false;       // want to see underwater
+        terrainoptions.enableBlending() = true;        // gotsta blend with the main node
 
-        mpoptions.enableBlending() = true;        // gotsta blend with the main node
+        terrainoptions.color() = baseColor().get();
 
-        mpoptions.color() = baseColor().get();
+        terrainoptions.tileSize() = 5;
 
-        mno.setTerrainOptions( mpoptions );
+        mno.setTerrainOptions( terrainoptions );
 
         // make the ocean's map node:
         MapNode* oceanMapNode = new MapNode( oceanMap, mno );
 
+        // set up the shaders.
+        osg::StateSet* ss = this->getOrCreateStateSet();
+
         // if the caller requested a mask layer, install that now.
         if ( maskLayer().isSet() )
         {
@@ -154,7 +157,10 @@ SimpleOceanNode::rebuild()
             maskLayer()->visible() = false;
 
             ImageLayer* layer = new ImageLayer("ocean-mask", maskLayer().get());
-            oceanMap->addImageLayer( layer );
+            oceanMap->addLayer( layer );
+
+            ss->setDefine("OE_SIMPLE_OCEAN_USE_MASK");
+            OE_INFO << LC << "Using mask layer \"" << layer->getName() << "\"\n";
         }
 
         // otherwise, install a "proxy layer" that will use the elevation data in the map
@@ -166,32 +172,22 @@ SimpleOceanNode::rebuild()
             // parent map and turns them into encoded images for our shader to use.
             ImageLayerOptions epo( "ocean-proxy" );
             epo.cachePolicy() = CachePolicy::NO_CACHE;
-            oceanMap->addImageLayer( new ElevationProxyImageLayer(mapNode->getMap(), epo) );
+            epo.shared() = true;
+            epo.visible() = false;
+            epo.shareTexUniformName() = "oe_ocean_proxyTex";
+            epo.shareTexMatUniformName() = "oe_ocean_proxyMat";
+            oceanMap->addLayer( new ElevationProxyImageLayer(mapNode->getMap(), epo) );
+            OE_INFO << LC << "Using elevation proxy layer\n";
         }
 
         this->addChild( oceanMapNode );
 
-        // set up the shaders.
-        osg::StateSet* ss = this->getOrCreateStateSet();
-
         // install the shaders on the ocean map node.
         VirtualProgram* vp = VirtualProgram::getOrCreate( ss );
-        vp->setName( "osgEarth SimpleOcean" );
-        
+        vp->setName( "osgEarth SimpleOcean" );        
         Shaders shaders;
-        shaders.define("USE_OCEAN_MASK", maskLayer().isSet());
         shaders.loadAll(vp, 0L);
 
-        //// use the appropriate shader for the active technique:
-        //std::string vertSource = maskLayer().isSet() ? source_vertMask : source_vertProxy;
-        //std::string fragSource = maskLayer().isSet() ? source_fragMask : source_fragProxy;
-
-        //vp->setFunction( "oe_ocean_vertex",   vertSource, ShaderComp::LOCATION_VERTEX_VIEW );
-        //vp->setFunction( "oe_ocean_fragment", fragSource, ShaderComp::LOCATION_FRAGMENT_COLORING, 0.6f );
-
-        // install the slot attribute(s)
-        ss->getOrCreateUniform( "ocean_data", osg::Uniform::SAMPLER_2D )->set( 0 );
-
         // set up the options uniforms.
 
         _seaLevel = new osg::Uniform(osg::Uniform::FLOAT, "ocean_seaLevel");
@@ -220,17 +216,15 @@ SimpleOceanNode::rebuild()
 
         // load up a surface texture
         osg::ref_ptr<osg::Image> surfaceImage;
-        ss->getOrCreateUniform( "ocean_has_surface_tex", osg::Uniform::BOOL )->set( false );
         if ( textureURI().isSet() )
         {
-            //TODO: enable cache support here?
             surfaceImage = textureURI()->getImage();
         }
 
-        if ( !surfaceImage.valid() )
-        {
-            surfaceImage = createSurfaceImage();
-        }
+        //if ( !surfaceImage.valid() )
+        //{
+        //    surfaceImage = createSurfaceImage();
+        //}
 
         if ( surfaceImage.valid() )
         {
@@ -240,9 +234,11 @@ SimpleOceanNode::rebuild()
             tex->setWrap  ( osg::Texture::WRAP_S, osg::Texture::REPEAT );
             tex->setWrap  ( osg::Texture::WRAP_T, osg::Texture::REPEAT );
 
-            ss->setTextureAttributeAndModes( 2, tex, 1 );
-            ss->getOrCreateUniform( "ocean_surface_tex", osg::Uniform::SAMPLER_2D )->set( 2 );
-            ss->getOrCreateUniform( "ocean_has_surface_tex", osg::Uniform::BOOL )->set( true );
+            ss->setTextureAttributeAndModes( 5, tex, 1 );
+            ss->getOrCreateUniform( "ocean_surface_tex", osg::Uniform::SAMPLER_2D )->set( 5 );
+
+            ss->setDefine("OE_SIMPLE_OCEAN_USE_TEXTURE");
+            OE_INFO << LC << "Using a surface texture (" << surfaceImage->getFileName() << ")\n";
         }
 
         // remove backface culling so we can see underwater
diff --git a/src/osgEarthDrivers/ocean_simple/SimpleOceanOptions b/src/osgEarthDrivers/ocean_simple/SimpleOceanOptions
index 54aa90c..f0e27db 100644
--- a/src/osgEarthDrivers/ocean_simple/SimpleOceanOptions
+++ b/src/osgEarthDrivers/ocean_simple/SimpleOceanOptions
@@ -103,17 +103,17 @@ namespace osgEarth { namespace SimpleOcean
 
     public:
         Config getConfig() const {
-            Config conf = OceanOptions::newConfig();
-            conf.updateIfSet("sea_level",           _seaLevel );
-            conf.updateIfSet("high_feather_offset", _highFeatherOffset );
-            conf.updateIfSet("low_feather_offset",  _lowFeatherOffset );
-            conf.updateIfSet("max_range",           _maxRange );
-            conf.updateIfSet("fade_range",          _fadeRange );
-            conf.updateIfSet("max_lod",             _maxLOD );
-            conf.updateIfSet("base_color",          _baseColor );
-            conf.updateIfSet("texture_url",         _textureURI );
-            conf.updateObjIfSet("mask_layer",       _maskLayerOptions );
-            conf.updateIfSet("render_bin_number",   _renderBinNumber);
+            Config conf = OceanOptions::getConfig();
+            conf.set("sea_level",           _seaLevel );
+            conf.set("high_feather_offset", _highFeatherOffset );
+            conf.set("low_feather_offset",  _lowFeatherOffset );
+            conf.set("max_range",           _maxRange );
+            conf.set("fade_range",          _fadeRange );
+            conf.set("max_lod",             _maxLOD );
+            conf.set("base_color",          _baseColor );
+            conf.set("texture_url",         _textureURI );
+            conf.setObj("mask_layer",       _maskLayerOptions );
+            conf.set("render_bin_number",   _renderBinNumber);
             return conf;
         }
 
diff --git a/src/osgEarthDrivers/ocean_triton/TritonDriver.cpp b/src/osgEarthDrivers/ocean_triton/TritonDriver.cpp
index 12dc25f..91ee5b6 100644
--- a/src/osgEarthDrivers/ocean_triton/TritonDriver.cpp
+++ b/src/osgEarthDrivers/ocean_triton/TritonDriver.cpp
@@ -77,7 +77,9 @@ namespace osgEarth { namespace Triton
     public: // OceanNodeFactory
 
         OceanNode* createOceanNode(MapNode* mapNode) {
-            return new TritonNode(mapNode, *this);
+            TritonNode* node = new TritonNode(*this);
+            node->setMapNode(mapNode);
+            return node;
         }
 
     protected:
diff --git a/src/osgEarthDrivers/osg/OSGOptions b/src/osgEarthDrivers/osg/OSGOptions
index a1d03a5..93ee139 100644
--- a/src/osgEarthDrivers/osg/OSGOptions
+++ b/src/osgEarthDrivers/osg/OSGOptions
@@ -39,15 +39,11 @@ namespace osgEarth { namespace Drivers
         optional<bool>& addAlpha() { return _addAlpha; }
         const optional<bool>& addAlpha() const { return _addAlpha; }
 
-        optional<unsigned>& maxDataLevel() { return _maxDataLevel; }
-        const optional<unsigned>& maxDataLevel() const { return _maxDataLevel; }
-
     public:
         OSGOptions( const TileSourceOptions& opt =TileSourceOptions() ) :
             TileSourceOptions( opt ),
             _lum2rgba( false ),
-            _addAlpha( false ),
-            _maxDataLevel( 21 )
+            _addAlpha( false )
         {
             setDriver( "osg" );
             fromConfig( _conf );
@@ -59,10 +55,9 @@ namespace osgEarth { namespace Drivers
     public:
         Config getConfig() const {
             Config conf = TileSourceOptions::getConfig();
-            conf.updateIfSet("url", _url );
-            conf.updateIfSet("luminance_to_rgba", _lum2rgba);
-            conf.updateIfSet("add_alpha", _addAlpha);
-            conf.updateIfSet("max_data_level", _maxDataLevel );
+            conf.set("url", _url );
+            conf.set("luminance_to_rgba", _lum2rgba);
+            conf.set("add_alpha", _addAlpha);
             return conf;
         }
 
@@ -77,13 +72,11 @@ namespace osgEarth { namespace Drivers
             conf.getIfSet( "url", _url );
             conf.getIfSet( "luminance_to_rgba", _lum2rgba );
             conf.getIfSet( "add_alpha", _addAlpha);
-            conf.getIfSet( "max_data_level", _maxDataLevel );
         }
 
         optional<URI>  _url;
         optional<bool> _lum2rgba;
         optional<bool> _addAlpha;
-        optional<unsigned> _maxDataLevel;
     };
 
 } } // namespace osgEarth::Drivers
diff --git a/src/osgEarthDrivers/osg/OSGTileSource.cpp b/src/osgEarthDrivers/osg/OSGTileSource.cpp
index 7fe5a7e..53ca6d9 100644
--- a/src/osgEarthDrivers/osg/OSGTileSource.cpp
+++ b/src/osgEarthDrivers/osg/OSGTileSource.cpp
@@ -59,13 +59,21 @@ class OSGTileSource : public TileSource
 {
 public:
     OSGTileSource( const TileSourceOptions& options ) :
-      TileSource( options ),      
-      _maxDataLevel( 21 ),
+      TileSource( options ),
       _options( options )
     {
         //nop
     }
 
+    // By default don't cache local data from this layer
+    CachePolicy getCachePolicyHint(const Profile* targetProfile) const
+    {
+        if (_options.url()->isRemote() == false)
+            return CachePolicy::NO_CACHE;
+        else
+            return CachePolicy::DEFAULT;
+    }
+
     Status initialize( const osgDB::Options* dbOptions )
     {
         osg::ref_ptr<osgDB::Options> localOptions = Registry::instance()->cloneOrCreateOptions(dbOptions);        
@@ -94,19 +102,11 @@ public:
         // calculate and store the maximum LOD for which to return data
         if ( image.valid() )
         {
-            if ( _options.maxDataLevel().isSet() )
-            {
-                _maxDataLevel = *_options.maxDataLevel();
-            }
-            else
-            {
-                int minSpan = osg::minimum( image->s(), image->t() );
-                int tileSize = _options.tileSize().value();
-                _maxDataLevel = (int)LOG2((minSpan/tileSize)+1);
-                //OE_NOTICE << "[osgEarth::OSG driver] minSpan=" << minSpan << ", _tileSize=" << tileSize << ", maxDataLevel = " << _maxDataLevel << std::endl;
-            }
+            int minSpan = osg::minimum( image->s(), image->t() );
+            int tileSize = getPixelsPerTile();
+            _maxLOD = (int)LOG2((minSpan/tileSize)+1);
             
-            getDataExtents().push_back( DataExtent(getProfile()->getExtent(), 0, _maxDataLevel) );
+            getDataExtents().push_back( DataExtent(getProfile()->getExtent(), 0, _maxLOD) );
 
             bool computeAlpha =
                 (_options.convertLuminanceToRGBA() == true && image->getPixelFormat() == GL_LUMINANCE) ||
@@ -132,17 +132,11 @@ public:
 
         return STATUS_OK;
     }
-    
-    //override
-    unsigned int getMaxDataLevel() const 
-    {
-        return _maxDataLevel;
-    }
 
     osg::Image*
     createImage( const TileKey& key, ProgressCallback* progress )
     {
-        if ( !_image.valid() || key.getLevelOfDetail() > getMaxDataLevel() )
+        if (!_image.valid() || key.getLOD() > _maxLOD)
             return NULL;
 
         GeoImage cropped = _image.crop( key.getExtent(), true, getPixelsPerTile(), getPixelsPerTile(), *_options.bilinearReprojection() );
@@ -157,9 +151,9 @@ public:
 
 private:
     std::string      _extension;
-    int              _maxDataLevel;
     GeoImage         _image;
     const OSGOptions _options;
+    unsigned         _maxLOD;
 };
 
 
diff --git a/src/osgEarthDrivers/quadkey/CMakeLists.txt b/src/osgEarthDrivers/quadkey/CMakeLists.txt
deleted file mode 100644
index ed17f70..0000000
--- a/src/osgEarthDrivers/quadkey/CMakeLists.txt
+++ /dev/null
@@ -1,15 +0,0 @@
-SET(TARGET_SRC
-  ReaderWriterQuadKey.cpp
-)
-SET(TARGET_H
-  QuadKeyOptions
-)
-
-SET(TARGET_COMMON_LIBRARIES ${TARGET_COMMON_LIBRARIES} osgEarthUtil)
-
-SETUP_PLUGIN(osgearth_quadkey)
-
-# to install public driver includes:
-SET(LIB_NAME quadkey)
-SET(LIB_PUBLIC_HEADERS QuadKeyOptions)
-INCLUDE(ModuleInstallOsgEarthDriverIncludes OPTIONAL)
diff --git a/src/osgEarthDrivers/quadkey/QuadKeyOptions b/src/osgEarthDrivers/quadkey/QuadKeyOptions
deleted file mode 100644
index 952bdb3..0000000
--- a/src/osgEarthDrivers/quadkey/QuadKeyOptions
+++ /dev/null
@@ -1,82 +0,0 @@
-/* -*-c++-*- */
-/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2016 Pelican Mapping
- * http://osgearth.org
- *
- * osgEarth is free software; you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>
- */
-#ifndef OSGEARTH_DRIVER_QUADKEY_OPTIONS
-#define OSGEARTH_DRIVER_QUADKEY_OPTIONS 1
-
-#include <osgEarth/Common>
-#include <osgEarth/TileSource>
-#include <osgEarth/URI>
-
-namespace osgEarth { namespace Drivers
-{
-    using namespace osgEarth;
-
-    class QuadKeyOptions : public TileSourceOptions // NO EXPORT; header only
-    {
-    public:
-        optional<URI>& url() { return _url; }
-        const optional<URI>& url() const { return _url; }
-
-        optional<std::string>& format() { return _format; }
-        const optional<std::string>& format() const { return _format; }
-
-    public:
-        QuadKeyOptions( const TileSourceOptions& opt =TileSourceOptions() ) : TileSourceOptions( opt )
-        {
-            setDriver( "quadkey" );
-            fromConfig( _conf );
-        }
-
-        QuadKeyOptions( const std::string& inUrl ) : TileSourceOptions()
-        {
-            setDriver( "quadkey" );
-            fromConfig( _conf );
-            url() = inUrl;
-        }
-
-        virtual ~QuadKeyOptions() { }
-
-    public:
-        Config getConfig() const {
-            Config conf = TileSourceOptions::getConfig();
-            conf.updateIfSet("url", _url);
-            conf.updateIfSet("format", _format);
-            return conf;
-        }
-
-    protected:
-        void mergeConfig( const Config& conf ) {
-            TileSourceOptions::mergeConfig( conf );
-            fromConfig( conf );
-        }
-
-    private:
-        void fromConfig( const Config& conf ) {
-            conf.getIfSet( "url", _url );
-            conf.getIfSet( "format", _format );
-        }
-
-        optional<URI>         _url;
-        optional<std::string> _format;
-    };
-
-} } // namespace osgEarth::Drivers
-
-#endif // OSGEARTH_DRIVER_QUADKEY_OPTIONS
-
diff --git a/src/osgEarthDrivers/quadkey/ReaderWriterQuadKey.cpp b/src/osgEarthDrivers/quadkey/ReaderWriterQuadKey.cpp
deleted file mode 100644
index 1be6cf6..0000000
--- a/src/osgEarthDrivers/quadkey/ReaderWriterQuadKey.cpp
+++ /dev/null
@@ -1,199 +0,0 @@
-/* -*-c++-*- */
-/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2016 Pelican Mapping
-* http://osgearth.org
-*
-* osgEarth is free software; you can redistribute it and/or modify
-* it under the terms of the GNU Lesser General Public License as published by
-* the Free Software Foundation; either version 2 of the License, or
-* (at your option) any later version.
-*
-* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
-* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
-* IN THE SOFTWARE.
-*
-* You should have received a copy of the GNU Lesser General Public License
-* along with this program.  If not, see <http://www.gnu.org/licenses/>
-*/
-
-#include <osgEarth/TileSource>
-#include <osgEarth/FileUtils>
-#include <osgEarth/ImageUtils>
-#include <osgEarth/Registry>
-
-#include <osg/Notify>
-#include <osgDB/FileNameUtils>
-#include <osgDB/FileUtils>
-#include <osgDB/Registry>
-#include <osgDB/ReadFile>
-
-#include <OpenThreads/Atomic>
-
-#include <sstream>
-#include <iomanip>
-#include <string.h>
-
-#include "QuadKeyOptions"
-
-using namespace osgEarth;
-using namespace osgEarth::Drivers;
-
-#define LC "[QuadKey driver] "
-#define OE_TEST OE_DEBUG
-
-
-class QuadKeySource : public TileSource
-{
-public:
-    QuadKeySource(const TileSourceOptions& options) : 
-        TileSource(options), _options(options), _rotate_iter(0u), _rotateStart(0), _rotateEnd(0)
-    {
-        //nop
-    }
-
-
-    Status initialize(const osgDB::Options* dbOptions)
-    {
-        _dbOptions = Registry::instance()->cloneOrCreateOptions(dbOptions);        
-
-        URI uri = _options.url().value();
-        if ( uri.empty() )
-        {
-            return Status::Error( Status::ConfigurationError, "Fail: driver requires a valid \"url\" property" );
-        }
-
-        // The quadkey driver always uses spherical mercator.
-        // Bing maps profile is spherical mercator with 2x2 tiles are the root.
-        const Profile* profile = Profile::create(
-            SpatialReference::get("spherical-mercator"),
-            MERC_MINX, MERC_MINY, MERC_MAXX, MERC_MAXY,
-            2, 2);
-
-        setProfile( profile );
-
-
-        
-        _template = uri.full();
-        
-        _rotateStart = _template.find("[");
-        _rotateEnd   = _template.find("]");
-        if ( _rotateStart != std::string::npos && _rotateEnd != std::string::npos && _rotateEnd-_rotateStart > 1 )
-        {
-            _rotateString  = _template.substr(_rotateStart, _rotateEnd-_rotateStart+1);
-            _rotateChoices = _template.substr(_rotateStart+1, _rotateEnd-_rotateStart-1);
-        }
-
-        _format = _options.format().isSet() 
-            ? *_options.format()
-            : osgDB::getLowerCaseFileExtension( uri.base() );
-
-        return STATUS_OK;
-    }
-
-
-    osg::Image* createImage(const TileKey&     key,
-                            ProgressCallback*  progress )
-    {
-        unsigned x, y;
-        key.getTileXY( x, y );
-
-        std::string location = _template;
-
-        std::string quadkey = getQuadKey(key);
-
-        // support OpenLayers template style:
-        replaceIn( location, "${key}", quadkey );
-
-        // failing that, legacy osgearth style:
-        replaceIn( location, "{key}", quadkey );
-
-        std::string cacheKey;
-
-        if ( !_rotateChoices.empty() )
-        {
-            cacheKey = location;
-            unsigned index = (++_rotate_iter) % _rotateChoices.size();
-            replaceIn( location, _rotateString, Stringify() << _rotateChoices[index] );
-        }
-
-
-        URI uri( location, _options.url()->context() );
-        if ( !cacheKey.empty() )
-            uri.setCacheKey( cacheKey );
-
-        OE_TEST << LC << "URI: " << uri.full() << ", key: " << uri.cacheKey() << std::endl;
-
-        return uri.getImage( _dbOptions.get(), progress );
-    }
-
-    virtual std::string getExtension() const 
-    {
-        return _format;
-    }
-
-private:
-    std::string getQuadKey(const TileKey& key)
-    {
-        unsigned int tile_x, tile_y;
-        key.getTileXY(tile_x, tile_y);
-        unsigned int lod = key.getLevelOfDetail();
-
-        std::stringstream ss;
-        for( unsigned i = (int)lod+1; i > 0; i-- )
-        {
-            char digit = '0';
-            unsigned mask = 1 << (i-1);
-            if ( (tile_x & mask) != 0 )
-            {
-                digit++;
-            }
-            if ( (tile_y & mask) != 0 )
-            {
-                digit += 2;
-            }
-            ss << digit;
-        }
-        return ss.str();
-    }
-
-    const QuadKeyOptions   _options;
-    std::string            _format;
-    std::string            _template;
-    std::string            _rotateChoices;
-    std::string            _rotateString;
-    std::string::size_type _rotateStart, _rotateEnd;
-    OpenThreads::Atomic    _rotate_iter;
-
-    osg::ref_ptr<osgDB::Options> _dbOptions;
-};
-
-
-
-
-class QuadKeyTileSourceDriver : public TileSourceDriver
-{
-public:
-    QuadKeyTileSourceDriver()
-    {
-        supportsExtension( "osgearth_quadkey", "QuadKey Driver" );
-    }
-
-    virtual const char* className() const
-    {
-        return "QuadKey Driver";
-    }
-
-    virtual ReadResult readObject(const std::string& file_name, const Options* options) const
-    {
-        if ( !acceptsExtension(osgDB::getLowerCaseFileExtension( file_name )))
-            return ReadResult::FILE_NOT_HANDLED;
-
-        return new QuadKeySource( getTileSourceOptions(options) );
-    }
-};
-
-REGISTER_OSGPLUGIN(osgearth_quadkey, QuadKeyTileSourceDriver)
diff --git a/src/osgEarthDrivers/refresh/CMakeLists.txt b/src/osgEarthDrivers/refresh/CMakeLists.txt
deleted file mode 100644
index ac91407..0000000
--- a/src/osgEarthDrivers/refresh/CMakeLists.txt
+++ /dev/null
@@ -1,13 +0,0 @@
-SET(TARGET_SRC
-  ReaderWriterRefresh.cpp
-)
-SET(TARGET_H
-  RefreshOptions
-)
-
-SETUP_PLUGIN(osgearth_refresh)
-
-# to install public driver includes:
-SET(LIB_NAME refresh)
-SET(LIB_PUBLIC_HEADERS RefreshOptions)
-INCLUDE(ModuleInstallOsgEarthDriverIncludes OPTIONAL)
diff --git a/src/osgEarthDrivers/refresh/ReaderWriterRefresh.cpp b/src/osgEarthDrivers/refresh/ReaderWriterRefresh.cpp
deleted file mode 100644
index 20145dd..0000000
--- a/src/osgEarthDrivers/refresh/ReaderWriterRefresh.cpp
+++ /dev/null
@@ -1,251 +0,0 @@
-/* -*-c++-*- */
-/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2016 Pelican Mapping
-* http://osgearth.org
-*
-* osgEarth is free software; you can redistribute it and/or modify
-* it under the terms of the GNU Lesser General Public License as published by
-* the Free Software Foundation; either version 2 of the License, or
-* (at your option) any later version.
-*
-* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
-* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
-* IN THE SOFTWARE.
-*
-* You should have received a copy of the GNU Lesser General Public License
-* along with this program.  If not, see <http://www.gnu.org/licenses/>
-*/
-
-#include <osgEarth/TileSource>
-#include <osgEarth/FileUtils>
-#include <osgEarth/ImageUtils>
-#include <osgEarth/Registry>
-
-#include <osg/Notify>
-#include <osgDB/FileNameUtils>
-#include <osgDB/FileUtils>
-#include <osgDB/Registry>
-#include <osgDB/ReadFile>
-#include <osgDB/WriteFile>
-#include <osg/ImageStream>
-#include <osg/ImageSequence>
-
-#include <sstream>
-#include <iomanip>
-#include <string.h>
-
-#include "RefreshOptions"
-
-using namespace osgEarth;
-using namespace osgEarth::Drivers;
-
-#define LC "[Refresh driver] "
-
-/*
- * LoadImageOperation is a simple operation that simply loads an image in a background thread
- */
-class LoadImageOperation : public osg::Operation
-{
-public:
-    LoadImageOperation(const std::string& filename):
-      _filename(filename),
-          _done(false)
-      {
-      }
-
-      void operator()(osg::Object*)
-      {
-          //Try to load the image a few times.  If you happen to be writing the file at the
-          //same time as the plugin tries to load it it can fail.
-          unsigned int maxTries = 5;
-          for (unsigned int i = 0; i < maxTries; i++)
-          {
-              _image = osgDB::readImageFile( _filename );                                     
-              if (_image.valid()) break;              
-          }
-          _done = true;
-      }
-
-      bool _done;
-      osg::ref_ptr< osg::Image > _image;
-      std::string _filename;
-};
-
-
-
-/*
- * RefreshImage is a special ImageStream that reloads an image from a filename and
- * updates it's internal image data.
- */
-class RefreshImage : public osg::ImageStream
-{
-public:
-
-    RefreshImage(const std::string& filename, double time):
-      _filename(filename),
-          _time(time),
-          _lastUpdateTime(0),
-          osg::ImageStream()
-      {                    
-          osg::ref_ptr< osg::Image > image = osgDB::readImageFile( filename );
-          if (image.valid()) copyImage( image.get() );
-      }      
-
-
-      /**
-       * Tell OpenSceneGraph that we require an update call
-       */
-      virtual bool requiresUpdateCall() const { return true; }
-
-      ~RefreshImage()
-      {
-      }
-
-      static osg::OperationsThread* getOperationsThread() 
-      {          
-          //Create an Operations Thread.  This thread is static and is not deleted b/c 
-          //there are issues with calling cancel on static threads on Windows.  The thread itself will die
-          //cleanly, but the application will hang waiting for the cancel call in OpenThreads to return.
-          //This is something that needs to be fixed in OpenThreads so we can maintain a static threadpool.
-          static osg::OperationsThread* _thread = 0;
-          static OpenThreads::Mutex _mutex;
-
-          if (!_thread)
-          {
-              OpenThreads::ScopedLock< OpenThreads::Mutex > lock(_mutex);
-              if (!_thread)
-              {
-                  _thread = new osg::OperationsThread;
-                  _thread->start();
-              }            
-          }    
-          return _thread;                          
-
-      }
-
-      //If the loadImageOp is complete then update the contents of this image with the new pixels
-      void updateImage() 
-      {
-          if (_loadImageOp.valid() && _loadImageOp->_done)
-          {              
-              osg::ref_ptr< osg::Image > image = _loadImageOp->_image.get();
-              if (image.valid())
-              {
-                  copyImage( image.get() );
-              }
-              _lastUpdateTime = osg::Timer::instance()->time_s();
-              _loadImageOp = 0;
-          }
-      }
-
-
-      /**
-       * Copies the contents of the given image into this image.
-       */
-      void copyImage( osg::Image* image)
-      {
-          if (image)
-          {
-              unsigned char* data = new unsigned char[ image->getTotalSizeInBytes() ];
-              memcpy(data, image->data(), image->getTotalSizeInBytes());
-              setImage(image->s(), image->t(), image->r(), image->getInternalTextureFormat(), image->getPixelFormat(), image->getDataType(), data, osg::Image::USE_NEW_DELETE, image->getPacking());                            
-          }
-      }
-
-      /** update method for osg::Image subclasses that update themselves during the update traversal.*/
-      virtual void update(osg::NodeVisitor* nv)
-      {                               
-          updateImage();
-          double time = osg::Timer::instance()->time_s();
-          osg::Timer_t ticks = osg::Timer::instance()->tick();          
-          //If we've let enough time elapse and we're not waiting on an existing load image operation then add one to the queue
-          if (!_loadImageOp.valid() && (time - _lastUpdateTime > _time))
-          {
-              std::stringstream ss;
-              std::string file = ss.str();              
-              _loadImageOp = new LoadImageOperation(_filename);
-              getOperationsThread()->add( _loadImageOp.get() );
-          }
-      }
-
-      std::string _filename;
-      double _time;
-      double _lastUpdateTime;
-      osg::ref_ptr< LoadImageOperation > _loadImageOp;      
-};
-
-
-class RefreshSource : public TileSource
-{
-public:
-    RefreshSource(const TileSourceOptions& options) : TileSource(options), _options(options)
-    {
-    }
-
-
-    Status initialize(const osgDB::Options* dbOptions)
-    {        
-        setProfile( osgEarth::Registry::instance()->getGlobalGeodeticProfile() );
-        return STATUS_OK;
-    }
-
-
-    osg::Image* createImage(
-        const TileKey&        key,
-        ProgressCallback*     progress )
-    {        
-        return new RefreshImage( _options.url()->full(), *_options.frequency());     
-    }
-
-    bool isDynamic() const
-    {
-        //Tell osgEarth that this is a dynamic image
-        return true;
-    }
-
-    virtual int getPixelsPerTile() const
-    {
-        return 256;
-    }
-
-    virtual std::string getExtension()  const 
-    {
-        return osgDB::getFileExtension( _options.url()->full() );
-    }
-
-private:
-    const RefreshOptions      _options;
-    
-};
-
-
-
-
-class ReaderWriterRefresh : public TileSourceDriver
-{
-public:
-    ReaderWriterRefresh()
-    {
-        supportsExtension( "osgearth_refresh", "Refresh" );
-    }
-
-    virtual const char* className() const
-    {
-        return "ReaderWriterRefresh";
-    }
-
-    virtual ReadResult readObject(const std::string& file_name, const Options* options) const
-    {
-        if ( !acceptsExtension(osgDB::getLowerCaseFileExtension( file_name )))
-            return ReadResult::FILE_NOT_HANDLED;
-
-        return new RefreshSource( getTileSourceOptions(options) );
-    }
-};
-
-REGISTER_OSGPLUGIN(osgearth_refresh, ReaderWriterRefresh)
-
diff --git a/src/osgEarthDrivers/refresh/RefreshOptions b/src/osgEarthDrivers/refresh/RefreshOptions
deleted file mode 100644
index 0871b2e..0000000
--- a/src/osgEarthDrivers/refresh/RefreshOptions
+++ /dev/null
@@ -1,85 +0,0 @@
-/* -*-c++-*- */
-/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2016 Pelican Mapping
- * http://osgearth.org
- *
- * osgEarth is free software; you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>
- */
-#ifndef OSGEARTH_DRIVER_REFRESH_MAPLAYERFACTORY
-#define OSGEARTH_DRIVER_REFRESH_MAPLAYERFACTORY 1
-
-#include <osgEarth/Common>
-#include <osgEarth/TileSource>
-#include <osgEarth/URI>
-
-namespace osgEarth { namespace Drivers
-{
-    using namespace osgEarth;
-
-    class RefreshOptions : public TileSourceOptions // NO EXPORT; header only
-    {
-    public:
-        optional<URI>& url() { return _url; }
-        const optional<URI>& url() const { return _url; }
-
-        optional<double>& frequency() { return _frequency; }
-        const optional<double>& frequency() const { return _frequency; }
-
-    public:
-        RefreshOptions( const TileSourceOptions& opt =TileSourceOptions() ) : TileSourceOptions( opt )
-        {
-            setDriver( "refresh" );
-            frequency() = 2.0;            
-            fromConfig( _conf );
-        }
-
-        RefreshOptions( const std::string& inUrl, double inFrequency ) : TileSourceOptions()
-        {
-            setDriver( "refresh" );
-            fromConfig( _conf );
-            _url = inUrl;
-             _frequency = inFrequency;
-        }
-
-        /** dtor */
-        virtual ~RefreshOptions() { }
-
-    public:
-        Config getConfig() const {
-            Config conf = TileSourceOptions::getConfig();
-            conf.updateIfSet("url", _url);
-            conf.updateIfSet("frequency", _frequency);            
-            return conf;
-        }
-
-    protected:
-        void mergeConfig( const Config& conf ) {
-            TileSourceOptions::mergeConfig( conf );
-            fromConfig( conf );
-        }
-
-    private:
-        void fromConfig( const Config& conf ) {
-            conf.getIfSet( "url", _url );
-            conf.getIfSet( "frequency", _frequency );
-        }
-
-        optional<URI>         _url;
-        optional<double>      _frequency;
-    };
-
-} } // namespace osgEarth::Drivers
-
-#endif // OSGEARTH_DRIVER_TMS_MAPLAYERFACTORY
-
diff --git a/src/osgEarthDrivers/script_engine_duktape/DuktapeEngine.cpp b/src/osgEarthDrivers/script_engine_duktape/DuktapeEngine.cpp
index 5eab418..533ad74 100644
--- a/src/osgEarthDrivers/script_engine_duktape/DuktapeEngine.cpp
+++ b/src/osgEarthDrivers/script_engine_duktape/DuktapeEngine.cpp
@@ -308,7 +308,7 @@ DuktapeEngine::run(const std::string&   code,
 
     if ( !ok )
     {
-        OE_WARN << LC << "Error: source =" << std::endl << code << std::endl;
+        OE_DEBUG << LC << "Error: source =" << std::endl << code << std::endl;
     }
 
     // pop the return value:
diff --git a/src/osgEarthDrivers/script_engine_javascriptcore/CMakeLists.txt b/src/osgEarthDrivers/script_engine_javascriptcore/CMakeLists.txt
deleted file mode 100644
index 534b5c4..0000000
--- a/src/osgEarthDrivers/script_engine_javascriptcore/CMakeLists.txt
+++ /dev/null
@@ -1,26 +0,0 @@
-IF(JAVASCRIPTCORE_FOUND)
-
-INCLUDE_DIRECTORIES( ${JAVASCRIPTCORE_INCLUDE_DIR} )
-
-SET(TARGET_SRC
-    JavaScriptCoreEngineFactory.cpp
-    JavaScriptCoreEngine.cpp
-    JSWrappers.cpp
-)
-
-SET(TARGET_H
-    JavaScriptCoreEngine
-    JSWrappers
-)
-
-SET(TARGET_COMMON_LIBRARIES ${TARGET_COMMON_LIBRARIES} ${JAVASCRIPTCORE_LIBRARY} osgEarthFeatures osgEarthSymbology)
-
-SETUP_PLUGIN(osgearth_scriptengine_javascriptcore)
-
-# to install public driver includes:
-SET(LIB_NAME scriptengine_javascriptcore)
-SET(LIB_PUBLIC_HEADERS ${TARGET_H} )
-INCLUDE(ModuleInstallOsgEarthDriverIncludes OPTIONAL)
-
-ENDIF(JAVASCRIPTCORE_FOUND)
-
diff --git a/src/osgEarthDrivers/script_engine_javascriptcore/JSWrappers b/src/osgEarthDrivers/script_engine_javascriptcore/JSWrappers
deleted file mode 100644
index f3dbeae..0000000
--- a/src/osgEarthDrivers/script_engine_javascriptcore/JSWrappers
+++ /dev/null
@@ -1,33 +0,0 @@
-/* -*-c++-*- */
-/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
- * http://osgearth.org
- *
- * osgEarth is free software; you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>
- */
-
-#ifndef OSGEARTHDRIVERS_JAVASCRIPTCORE_JSWRAPPERS_H
-#define OSGEARTHDRIVERS_JAVASCRIPTCORE_JSWRAPPERS_H 1
-
-#import <JavaScriptCore/JavaScriptCore.h>
-
-class JSUtils
-{
-public:
-    static char* JSStringRef_to_CharArray(JSStringRef jsString);
-};
-
-JSClassRef JSFeature_class(JSContextRef ctx);
-
-#endif // OSGEARTHDRIVERS_JAVASCRIPTCORE_JSWRAPPERS_H
\ No newline at end of file
diff --git a/src/osgEarthDrivers/script_engine_javascriptcore/JSWrappers.cpp b/src/osgEarthDrivers/script_engine_javascriptcore/JSWrappers.cpp
deleted file mode 100644
index 76899c3..0000000
--- a/src/osgEarthDrivers/script_engine_javascriptcore/JSWrappers.cpp
+++ /dev/null
@@ -1,87 +0,0 @@
-//
-//  JSWrappers.cpp
-//  OSGEARTH
-//
-//  Created by Jeff Smith on 5/22/13.
-//
-//
-
-#include "JSWrappers"
-#include <osgEarthFeatures/Feature>
-#include <osgEarth/Notify>
-
-
-char* JSUtils::JSStringRef_to_CharArray(JSStringRef jsString)
-{
-    char* buf = 0L;
-    int len = JSStringGetLength(jsString) + 1;
-    buf = (char*) malloc(len*sizeof(char));
-    JSStringGetUTF8CString(jsString, buf, len);
-    
-    return buf;
-}
-
-// --------------------------------------------------------------------------
-// JSFeature
-
-static void JSFeature_initialize(JSContextRef ctx, JSObjectRef object)
-{
-    //NOP
-}
-
-static void JSFeature_finalize(JSObjectRef object)
-{
-    //NOP
-}
-
-static JSValueRef JSFeature_getProperty(JSContextRef ctx, JSObjectRef object, JSStringRef propertyName, JSValueRef* exception)
-{
-    osgEarth::Features::Feature *feature = static_cast<osgEarth::Features::Feature*>(JSObjectGetPrivate(object));
-
-    char* attrBuf = JSUtils::JSStringRef_to_CharArray(propertyName);
-    if (attrBuf)
-    {
-        std::string attr(attrBuf);
-        delete [] attrBuf;
-        
-        if (attr == "attributes" || attr == "attrs")
-        {
-            return object;
-        }
-        
-        osgEarth::Features::AttributeTable::const_iterator it = feature->getAttrs().find(attr);
-        if (it != feature->getAttrs().end())
-        {
-            osgEarth::Features::AttributeType atype = (*it).second.first;
-            switch (atype)
-            {
-                case osgEarth::Features::ATTRTYPE_BOOL:
-                    return JSValueMakeBoolean(ctx, (*it).second.getBool());
-                case osgEarth::Features::ATTRTYPE_DOUBLE:
-                    return JSValueMakeNumber(ctx, (*it).second.getDouble());
-                case osgEarth::Features::ATTRTYPE_INT:
-                    return JSValueMakeNumber(ctx, (*it).second.getInt());
-                default:
-                    return JSValueMakeString(ctx, JSStringCreateWithUTF8CString((*it).second.getString().c_str()));
-            }
-        }
-    }
-
-    return JSValueMakeNull(ctx);
-}
-
-//Caches and returns the JSShape class.
-JSClassRef JSFeature_class(JSContextRef ctx)
-{
-    static JSClassRef jsClass;
-    if (!jsClass) {
-        JSClassDefinition classDefinition = kJSClassDefinitionEmpty;
-        classDefinition.initialize = JSFeature_initialize;
-        classDefinition.finalize = JSFeature_finalize;
-        classDefinition.getProperty = JSFeature_getProperty;
-        
-        jsClass = JSClassCreate(&classDefinition);
-    }
-    
-    return jsClass;
-}
\ No newline at end of file
diff --git a/src/osgEarthDrivers/script_engine_javascriptcore/JavaScriptCoreEngine b/src/osgEarthDrivers/script_engine_javascriptcore/JavaScriptCoreEngine
deleted file mode 100644
index 02d1678..0000000
--- a/src/osgEarthDrivers/script_engine_javascriptcore/JavaScriptCoreEngine
+++ /dev/null
@@ -1,59 +0,0 @@
-/* -*-c++-*- */
-/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
- * http://osgearth.org
- *
- * osgEarth is free software; you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>
- */
-
-#ifndef OSGEARTHDRIVERS_JAVASCRIPTCORE_ENGINE_H
-#define OSGEARTHDRIVERS_JAVASCRIPTCORE_ENGINE_H 1
-
-#include <osgEarth/StringUtils>
-#include <osgEarthFeatures/Feature>
-#include <osgEarthFeatures/Script>
-#include <osgEarthFeatures/ScriptEngine>
-
-#include <JavaScriptCore/JavaScriptCore.h>
-
-//namespace osgEarth { namespace Drivers { namespace JavaScriptCore
-//{
-
-  using namespace osgEarth::Features;
-
-  class JavaScriptCoreEngine : public ScriptEngine
-  {
-  public:
-    JavaScriptCoreEngine(const ScriptEngineOptions& options =ScriptEngineOptions());
-    virtual ~JavaScriptCoreEngine();
-
-    bool supported(std::string lang) { return osgEarth::toLower(lang).compare("javascript") == 0; }
-    bool supported(Script* script) { return script && supported(script->getLanguage()); }
-
-    ScriptResult run(Script* script, osgEarth::Features::Feature const* feature=0L, osgEarth::Features::FilterContext const* context=0L);
-    ScriptResult run(const std::string& code, osgEarth::Features::Feature const* feature=0L, osgEarth::Features::FilterContext const* context=0L);
-
-    ScriptResult call(const std::string& function, osgEarth::Features::Feature const* feature=0L, osgEarth::Features::FilterContext const* context=0L);
-
-  protected:
-    /** Compiles and runs javascript in the current context. */
-    ScriptResult executeScript(const std::string& script);
-
-  protected:
-    JSGlobalContextRef _ctx;
-  };
-
-//} } } // namespace osgEarth::Drivers::JavaScriptCore
-
-#endif // OSGEARTHDRIVERS_JAVASCRIPTCORE_ENGINE_H
diff --git a/src/osgEarthDrivers/script_engine_javascriptcore/JavaScriptCoreEngine.cpp b/src/osgEarthDrivers/script_engine_javascriptcore/JavaScriptCoreEngine.cpp
deleted file mode 100644
index 1ce5a33..0000000
--- a/src/osgEarthDrivers/script_engine_javascriptcore/JavaScriptCoreEngine.cpp
+++ /dev/null
@@ -1,126 +0,0 @@
-/* -*-c++-*- */
-/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
- * http://osgearth.org
- *
- * osgEarth is free software; you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>
- */
-
-#include "JavaScriptCoreEngine"
-#include "JSWrappers"
-
-#include <osgEarthFeatures/Script>
-#include <osgEarthFeatures/ScriptEngine>
-#include <osgEarth/Notify>
-#include <osgEarth/StringUtils>
-
-#include <JavaScriptCore/JavaScriptCore.h>
-
-using namespace osgEarth;
-using namespace osgEarth::Features;
-
-#define LC "[JavaScriptCoreEngine] "
-
-
-//----------------------------------------------------------------------------
-
-JavaScriptCoreEngine::JavaScriptCoreEngine(const ScriptEngineOptions& options)
-: ScriptEngine(options)
-{
-    // Create JavaScript execution context.
-    _ctx = JSGlobalContextCreate(NULL);
-    
-  if (options.script().isSet() && !options.script()->getCode().empty())
-  {
-    // Compile and run the script
-    ScriptResult result = executeScript(options.script()->getCode());
-    if (!result.success())
-      OE_WARN << LC << "Error reading javascript: " << result.message() << std::endl;
-  }
-}
-
-JavaScriptCoreEngine::~JavaScriptCoreEngine()
-{
-    // Release JavaScript execution context.
-    JSGlobalContextRelease(_ctx);
-}
-
-ScriptResult
-JavaScriptCoreEngine::executeScript(const std::string& script)
- {   
-    // Evaluate script.
-    JSStringRef scriptJS = JSStringCreateWithUTF8CString(script.c_str());
-    JSValueRef result = JSEvaluateScript(_ctx, scriptJS, NULL, NULL, 0, NULL);
-    JSStringRelease(scriptJS);
-    
-    // Convert result to string, unless result is NULL.
-    char* buf = 0L;
-    if (result) {
-        JSStringRef resultStringJS = JSValueToStringCopy(_ctx, result, NULL);
-        buf = JSUtils::JSStringRef_to_CharArray(resultStringJS);
-        JSStringRelease(resultStringJS);
-    }
-    
-    if (buf)
-    {
-        std::string resultStr(buf);
-        delete [] buf;
-
-        return ScriptResult(resultStr);
-    }
-
-  return ScriptResult("");
-}
-
-ScriptResult
-JavaScriptCoreEngine::run(Script* script, osgEarth::Features::Feature const* feature, osgEarth::Features::FilterContext const* context)
-{
-  if (!script)
-    return ScriptResult(EMPTY_STRING, false, "Script is null.");
-
-  return run(script->getCode(), feature, context);
-}
-
-ScriptResult
-JavaScriptCoreEngine::run(const std::string& code, osgEarth::Features::Feature const* feature, osgEarth::Features::FilterContext const* context)
-{
-  if (code.empty())
-    return ScriptResult(EMPTY_STRING, false, "Script is empty.");
-
-
-  if (feature)
-  {
-      JSStringRef featureStr = JSStringCreateWithUTF8CString("feature");
-      JSObjectRef jsFeature = JSObjectMake(_ctx, JSFeature_class(_ctx), const_cast<osgEarth::Features::Feature*>(feature));
-      JSObjectSetProperty(_ctx, JSContextGetGlobalObject(_ctx), featureStr, jsFeature, kJSPropertyAttributeNone, NULL);
-  }
-
-    
-  //TODO: Wrap FilterContext and set as global property
-  //if (context)
-  //{
-  //}
-
-    
-  // Compile and run the script
-  ScriptResult result = executeScript(code);
-
-  return result;
-}
-
-ScriptResult
-JavaScriptCoreEngine::call(const std::string& function, osgEarth::Features::Feature const* feature, osgEarth::Features::FilterContext const* context)
-{
-    return ScriptResult("");
-}
diff --git a/src/osgEarthDrivers/script_engine_javascriptcore/JavaScriptCoreEngineFactory.cpp b/src/osgEarthDrivers/script_engine_javascriptcore/JavaScriptCoreEngineFactory.cpp
deleted file mode 100644
index 1368b0d..0000000
--- a/src/osgEarthDrivers/script_engine_javascriptcore/JavaScriptCoreEngineFactory.cpp
+++ /dev/null
@@ -1,50 +0,0 @@
-/* -*-c++-*- */
-/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
- * http://osgearth.org
- *
- * osgEarth is free software; you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>
- */
-#include <osgDB/ReaderWriter>
-#include "JavaScriptCoreEngine"
-#include <osgEarthFeatures/ScriptEngine>
-#include <osgEarth/Common>
-#include <osgEarth/Config>
-#include <osgDB/FileNameUtils>
-
-
-class JavaScriptCoreEngineFactory : public osgEarth::Features::ScriptEngineDriver
-{
-public:
-    JavaScriptCoreEngineFactory()
-    {
-        supportsExtension( "osgearth_scriptengine_javascript", "osgEarth scriptengine javascript plugin" );
-        supportsExtension( "osgearth_scriptengine_javascript_javascriptcore", "osgEarth scriptengine javascript JavaScriptCore plugin" );
-    }
-
-    virtual const char* className()
-    {
-        return "osgEarth ScriptEngine Javascript JavaScriptCore Plugin";
-    }
-
-    virtual ReadResult readObject(const std::string& file_name, const osgDB::ReaderWriter::Options* options) const
-    {
-      if ( !acceptsExtension(osgDB::getLowerCaseFileExtension( file_name )) )
-            return osgDB::ReaderWriter::ReadResult::FILE_NOT_HANDLED;
-
-        return osgDB::ReaderWriter::ReadResult( new JavaScriptCoreEngine( getScriptEngineOptions(options) ) );
-    }
-};
-
-REGISTER_OSGPLUGIN(osgearth_scriptengine_javascriptcore, JavaScriptCoreEngineFactory)
diff --git a/src/osgEarthDrivers/script_engine_v8/CMakeLists.txt b/src/osgEarthDrivers/script_engine_v8/CMakeLists.txt
deleted file mode 100644
index 72d611c..0000000
--- a/src/osgEarthDrivers/script_engine_v8/CMakeLists.txt
+++ /dev/null
@@ -1,29 +0,0 @@
-IF(V8_FOUND AND USE_V8)
-
-INCLUDE_DIRECTORIES( ${V8_INCLUDE_DIR} )
-
-SET(TARGET_SRC
-    JavascriptEngineV8Factory.cpp
-    JavascriptEngineV8.cpp
-    JSWrappers.cpp
-)
-
-SET(TARGET_H
-    JavascriptEngineV8
-    JSWrappers
-    V8Util
-)
-
-SET(TARGET_COMMON_LIBRARIES ${TARGET_COMMON_LIBRARIES} osgEarthFeatures)
-
-SET(TARGET_LIBRARIES_VARS ${TARGET_LIBRARIES_VARS} V8_LIBRARY V8_BASE_LIBRARY V8_SNAPSHOT_LIBRARY V8_ICUUC_LIBRARY V8_ICUI18N_LIBRARY)
-
-SETUP_PLUGIN(osgearth_scriptengine_javascript)
-
-# to install public driver includes:
-SET(LIB_NAME scriptengine_javascript)
-SET(LIB_PUBLIC_HEADERS ${TARGET_H} )
-INCLUDE(ModuleInstallOsgEarthDriverIncludes OPTIONAL)
-
-ENDIF(V8_FOUND AND USE_V8)
-
diff --git a/src/osgEarthDrivers/script_engine_v8/JSWrappers b/src/osgEarthDrivers/script_engine_v8/JSWrappers
deleted file mode 100644
index a8597bd..0000000
--- a/src/osgEarthDrivers/script_engine_v8/JSWrappers
+++ /dev/null
@@ -1,233 +0,0 @@
-/* -*-c++-*- */
-/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
- * http://osgearth.org
- *
- * osgEarth is free software; you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>
- */
-
-#ifndef OSGEARTH_DRIVER_JS_FEATURE_CONTEXT_H
-#define OSGEARTH_DRIVER_JS_FEATURE_CONTEXT_H 1
-
-#include <osgEarth/StringUtils>
-#include <osgEarthFeatures/Feature>
-//#include <osgEarthFeatures/Script>
-//#include <osgEarthFeatures/ScriptEngine>
-
-#include <v8.h>
-
-// ---------------------------------------------------------------------------
-
-class JSFeature
-{
-public:
-  static v8::Handle<v8::Object> WrapFeature(v8::Isolate* isolate, osgEarth::Features::Feature* feature, bool freeObject=false);
-  static const std::string& GetObjectType() { return _objectType; }
-
-protected:
-  static const std::string _objectType;
-
-  static v8::Handle<v8::ObjectTemplate> GetObjectTemplate(v8::Isolate* isolate);
-  static v8::Handle<v8::ObjectTemplate> GetAttributesObjectTemplate(v8::Isolate* isolate);
-
-  static v8::Handle<v8::Value> GetFeatureAttr(const std::string& attr, osgEarth::Features::Feature const* feature);
-  static void PropertyCallback(v8::Local<v8::String> name, const v8::PropertyCallbackInfo<v8::Value>& info);
-  static void AttrPropertyCallback(v8::Local<v8::String> name, const v8::PropertyCallbackInfo<v8::Value>& info);
-
-  static void FreeFeatureCallback(v8::Isolate* isolate, v8::Persistent<v8::Object>* handle, osgEarth::Features::Feature* parameter);
-};
-
-// ---------------------------------------------------------------------------
-
-class JSSymbologyGeometry
-{
-public:
-  static v8::Handle<v8::Object> WrapGeometry(v8::Isolate* isolate, osgEarth::Symbology::Geometry* geometry, bool freeObject=false);
-  static const std::string& GetObjectType() { return _objectType; }
-
-protected:
-  static const std::string _objectType;
-
-  static v8::Handle<v8::ObjectTemplate> GetObjectTemplate(v8::Isolate* isolate);
-  static void PropertyCallback(v8::Local<v8::String> name, const v8::PropertyCallbackInfo<v8::Value>& info);
-  static void IndexedPropertyCallback(uint32_t index, const v8::PropertyCallbackInfo<v8::Value>& info);
-
-  static void FreeGeometryCallback(v8::Isolate* isolate, v8::Persistent<v8::Object>* handle, osgEarth::Symbology::Geometry* parameter);
-};
-
-// ---------------------------------------------------------------------------
-
-class JSBounds
-{
-public:
-  static v8::Handle<v8::Object> WrapBounds(v8::Isolate* isolate, osgEarth::Bounds* bounds, bool freeObject=false);
-  static const std::string& GetObjectType() { return _objectType; }
-  
-protected:
-  static const std::string _objectType;
-
-  static v8::Handle<v8::ObjectTemplate> GetObjectTemplate(v8::Isolate* isolate);
-  static void PropertyCallback(v8::Local<v8::String> name, const v8::PropertyCallbackInfo<v8::Value>& info);
-
-  static void ContainsCallback(const v8::FunctionCallbackInfo<v8::Value>& info);
-  static void UnionCallback(const v8::FunctionCallbackInfo<v8::Value>& info);
-  static void IntersectionCallback(const v8::FunctionCallbackInfo<v8::Value>& info);
-
-  static void FreeBoundsCallback(v8::Isolate* isolate, v8::Persistent<v8::Object>* handle, osgEarth::Bounds* parameter);
-};
-
-// ---------------------------------------------------------------------------
-
-class JSVec3d
-{
-public:
-  static v8::Handle<v8::Object> WrapVec3d(v8::Isolate* isolate, osg::Vec3d* vec, bool freeObject=false);
-  static const std::string& GetObjectType() { return _objectType; }
-
-protected:
-  static const std::string _objectType;
-
-  static v8::Handle<v8::ObjectTemplate> GetObjectTemplate(v8::Isolate* isolate);
-  static void PropertyCallback(v8::Local<v8::String> name, const v8::PropertyCallbackInfo<v8::Value>& info);
-  static void IndexedPropertyCallback(uint32_t index, const v8::PropertyCallbackInfo<v8::Value>& info);
-
-  static void FreeVecCallback(v8::Isolate* isolate, v8::Persistent<v8::Object>* handle, osg::Vec3d* parameter);
-};
-
-// ---------------------------------------------------------------------------
-
-class JSFilterContext
-{
-public:
-  static v8::Handle<v8::Object> WrapFilterContext(v8::Isolate* isolate, osgEarth::Features::FilterContext* context, bool freeObject=false);
-  static const std::string& GetObjectType() { return _objectType; }
-
-protected:
-  static const std::string _objectType;
-
-  static v8::Handle<v8::ObjectTemplate> GetObjectTemplate(v8::Isolate* isolate);
-  static void PropertyCallback(v8::Local<v8::String> name, const v8::PropertyCallbackInfo<v8::Value>& info);
-
-  static void ToLocalCallback(const v8::FunctionCallbackInfo<v8::Value>& info);
-  static void ToWorldCallback(const v8::FunctionCallbackInfo<v8::Value>& info);
-  static void ToMapCallback(const v8::FunctionCallbackInfo<v8::Value>& info);
-  static void FromMapCallback(const v8::FunctionCallbackInfo<v8::Value>& info);
-
-  static void FreeContextCallback(v8::Isolate* isolate, v8::Persistent<v8::Object>* handle, osgEarth::Features::FilterContext* parameter);
-};
-
-// ---------------------------------------------------------------------------
-
-class JSSession
-{
-public:
-  static v8::Handle<v8::Object> WrapSession(v8::Isolate* isolate, osgEarth::Features::Session* session, bool freeObject=false);
-  static const std::string& GetObjectType() { return _objectType; }
-
-protected:
-  static const std::string _objectType;
-
-  static v8::Handle<v8::ObjectTemplate> GetObjectTemplate(v8::Isolate* isolate);
-  static void PropertyCallback(v8::Local<v8::String> name, const v8::PropertyCallbackInfo<v8::Value>& info);
-
-#if 0
-  static void ResolveUriCallback(const v8::FunctionCallbackInfo<v8::Value>& info);
-#endif
-
-  static void FreeSessionCallback(v8::Isolate* isolate, v8::Persistent<v8::Object>* handle, osgEarth::Features::Session* parameter);
-};
-
-// ---------------------------------------------------------------------------
-
-class JSMapInfo
-{
-public:
-  static v8::Handle<v8::Object> WrapMapInfo(v8::Isolate* isolate, osgEarth::MapInfo* mapInfo, bool freeObject=false);
-  static const std::string& GetObjectType() { return _objectType; }
-
-protected:
-  static const std::string _objectType;
-
-  static v8::Handle<v8::ObjectTemplate> GetObjectTemplate(v8::Isolate* isolate);
-  static void PropertyCallback(v8::Local<v8::String> name, const v8::PropertyCallbackInfo<v8::Value>& info);
-
-#if 0
-  static void ToMapCallback(const v8::FunctionCallbackInfo<v8::Value>& info);
-  static void MapToWorldCallback(const v8::FunctionCallbackInfo<v8::Value>& info);
-  static void WorldToMapCallback(const v8::FunctionCallbackInfo<v8::Value>& info);
-#endif
-
-  static void FreeMapInfoCallback(v8::Isolate* isolate, v8::Persistent<v8::Object>* handle, osgEarth::MapInfo* parameter);
-};
-
-// ---------------------------------------------------------------------------
-
-class JSFeatureProfile
-{
-public:
-  static v8::Handle<v8::Object> WrapFeatureProfile(v8::Isolate* isolate, osgEarth::Features::FeatureProfile* context, bool freeObject=false);
-  static const std::string& GetObjectType() { return _objectType; }
-
-protected:
-  static const std::string _objectType;
-
-  static v8::Handle<v8::ObjectTemplate> GetObjectTemplate(v8::Isolate* isolate);
-  static void PropertyCallback(v8::Local<v8::String> name, const v8::PropertyCallbackInfo<v8::Value>& info);
-
-  static void FreeProfileCallback(v8::Isolate* isolate, v8::Persistent<v8::Object>* handle, osgEarth::Features::FeatureProfile* parameter);
-};
-
-// ---------------------------------------------------------------------------
-
-class JSGeoExtent
-{
-public:
-  static v8::Handle<v8::Object> WrapGeoExtent(v8::Isolate* isolate, osgEarth::GeoExtent* extent, bool freeObject=false);
-  static const std::string& GetObjectType() { return _objectType; }
-
-protected:
-  static const std::string _objectType;
-
-  static v8::Handle<v8::ObjectTemplate> GetObjectTemplate(v8::Isolate* isolate);
-  static void PropertyCallback(v8::Local<v8::String> name, const v8::PropertyCallbackInfo<v8::Value>& info);
-
-  static void ContainsCallback(const v8::FunctionCallbackInfo<v8::Value>& info);
-  static void IntersectsCallback(const v8::FunctionCallbackInfo<v8::Value>& info);
-
-  static void FreeGeoExtentCallback(v8::Isolate* isolate, v8::Persistent<v8::Object>* handle, osgEarth::GeoExtent* parameter);
-};
-
-// ---------------------------------------------------------------------------
-
-class JSSpatialReference
-{
-public:
-  static v8::Handle<v8::Object> WrapSpatialReference(v8::Isolate* isolate, osgEarth::SpatialReference* srs, bool freeObject=false);
-  static const std::string& GetObjectType() { return _objectType; }
-
-protected:
-  static const std::string _objectType;
-
-  static v8::Handle<v8::ObjectTemplate> GetObjectTemplate(v8::Isolate* isolate);
-  static void PropertyCallback(v8::Local<v8::String> name, const v8::PropertyCallbackInfo<v8::Value>& info);
-
-  static void EquivalenceCallback(const v8::FunctionCallbackInfo<v8::Value>& info);
-  static void TangentPlaneCallback(const v8::FunctionCallbackInfo<v8::Value>& info);
-
-  static void FreeSpatialReferenceCallback(v8::Isolate* isolate, v8::Persistent<v8::Object>* handle, osgEarth::SpatialReference* parameter);
-};
-
-// ---------------------------------------------------------------------------
-
-#endif // OSGEARTH_DRIVER_JS_FEATURE_CONTEXT_H
\ No newline at end of file
diff --git a/src/osgEarthDrivers/script_engine_v8/JSWrappers.cpp b/src/osgEarthDrivers/script_engine_v8/JSWrappers.cpp
deleted file mode 100644
index 2297056..0000000
--- a/src/osgEarthDrivers/script_engine_v8/JSWrappers.cpp
+++ /dev/null
@@ -1,1294 +0,0 @@
-/* -*-c++-*- */
-/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
- * http://osgearth.org
- *
- * osgEarth is free software; you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>
- */
-
-#include <osgEarthDrivers/script_engine_v8/JSWrappers>
-#include <osgEarthDrivers/script_engine_v8/V8Util>
-#include <osgEarthFeatures/FilterContext>
-#include <osgEarth/StringUtils>
-#include <v8.h>
-
-using namespace osgEarth::Features;
-
-// ---------------------------------------------------------------------------
-const std::string JSFeature::_objectType = "JSFeature";
-
-v8::Handle<v8::Object>
-JSFeature::WrapFeature(v8::Isolate* isolate, osgEarth::Features::Feature* feature, bool freeObject)
-{
-  v8::HandleScope handle_scope(isolate);
-
-  if (!feature)
-  {
-    v8::Handle<v8::Object> obj;
-    return handle_scope.Close(obj);
-  }
-
-  v8::Handle<v8::Object> obj = V8Util::WrapObject(isolate, feature, GetObjectTemplate(isolate));
-
-  if (freeObject)
-  {
-    v8::Persistent<v8::Object> weakRef;
-    weakRef.Reset(v8::Isolate::GetCurrent(), obj);
-    weakRef.MakeWeak(feature, &FreeFeatureCallback);
-  }
-
-  return handle_scope.Close(obj);
-}
-
-v8::Handle<v8::ObjectTemplate>
-JSFeature::GetObjectTemplate(v8::Isolate* isolate)
-{
-  v8::HandleScope handle_scope(isolate);
-
-  v8::Handle<v8::ObjectTemplate> feat_instance = v8::ObjectTemplate::New();
-
-  feat_instance->Set(v8::String::New(V8_OBJECT_TYPE_PROPERTY), v8::String::New(GetObjectType().c_str()));
-  feat_instance->SetInternalFieldCount(1);
-  feat_instance->SetNamedPropertyHandler(PropertyCallback);
-
-  return handle_scope.Close(feat_instance);
-}
-
-v8::Handle<v8::ObjectTemplate>
-JSFeature::GetAttributesObjectTemplate(v8::Isolate* isolate)
-{
-  v8::HandleScope handle_scope(isolate);
-
-  v8::Handle<v8::ObjectTemplate> attr_instance = v8::ObjectTemplate::New();
-
-  attr_instance->Set(v8::String::New(V8_OBJECT_TYPE_PROPERTY), v8::String::New("JSFeature_Attrs"));
-  attr_instance->SetInternalFieldCount(1);
-  attr_instance->SetNamedPropertyHandler(AttrPropertyCallback);
-
-  return handle_scope.Close(attr_instance);
-}
-
-v8::Handle<v8::Value>
-JSFeature::GetFeatureAttr(const std::string& attr, Feature const* feature)
-{
-  AttributeTable::const_iterator it = feature->getAttrs().find(attr);
-
-  // If the key is not present return an empty handle as signal
-  if (it == feature->getAttrs().end())
-    return v8::Handle<v8::Value>();
-
-  // Otherwise fetch the value and wrap it in a JavaScript string
-  osgEarth::Features::AttributeType atype = (*it).second.first;
-  switch (atype)
-  {
-    case osgEarth::Features::ATTRTYPE_BOOL:
-      return v8::Boolean::New((*it).second.second.set ? (*it).second.getBool() : false);
-    case osgEarth::Features::ATTRTYPE_DOUBLE:
-      if ((*it).second.second.set)
-        return v8::Number::New((*it).second.getDouble());
-      else
-        return v8::Undefined();
-    case osgEarth::Features::ATTRTYPE_INT:
-      if ((*it).second.second.set)
-        return v8::Integer::New((*it).second.getInt());
-      else
-        return v8::Undefined();
-    default:
-      std::string val = (*it).second.second.set ? (*it).second.getString() : "";
-      return v8::String::New(val.c_str(), val.length());
-  }
-}
-
-void
-JSFeature::PropertyCallback(v8::Local<v8::String> name, const v8::PropertyCallbackInfo<v8::Value>& info)
-{
-  Feature* feature = V8Util::UnwrapObject<Feature>(info.Holder());
-
-  v8::String::Utf8Value utf8_value(name);
-  std::string prop(*utf8_value);
-
-  if (!feature || prop.empty())
-    return;
-
-  v8::Local<v8::Value> value;
-  if (prop == "fid")
-    value = v8::Uint32::New(feature->getFID());
-  else if (prop == "attrs" || prop == "attributes")
-    value = V8Util::WrapObject(v8::Isolate::GetCurrent(), feature, GetAttributesObjectTemplate(v8::Isolate::GetCurrent()));
-  else if (prop == "geometry")
-    value = JSSymbologyGeometry::WrapGeometry(v8::Isolate::GetCurrent(), feature->getGeometry());
-
-  if (!value.IsEmpty())
-    info.GetReturnValue().Set(value);
-}
-
-void
-JSFeature::AttrPropertyCallback(v8::Local<v8::String> name, const v8::PropertyCallbackInfo<v8::Value>& info)
-{
-  Feature* feature = V8Util::UnwrapObject<Feature>(info.Holder());
-
-  v8::String::Utf8Value utf8_value(name);
-  std::string attr(*utf8_value);
-
-  if (!feature || attr.empty())
-    return;
-
-  v8::Local<v8::Value> value = GetFeatureAttr(attr, feature);
-  if (!value.IsEmpty())
-    info.GetReturnValue().Set(value);
-}
-
-void
-JSFeature::FreeFeatureCallback(v8::Isolate* isolate, v8::Persistent<v8::Object>* handle, osgEarth::Features::Feature* parameter)
-{
-  delete parameter;
-  handle->Dispose();
-}
-
-// ---------------------------------------------------------------------------
-
-const std::string JSSymbologyGeometry::_objectType = "JSSymbologyGeometry";
-
-v8::Handle<v8::Object>
-JSSymbologyGeometry::WrapGeometry(v8::Isolate* isolate, osgEarth::Symbology::Geometry* geometry, bool freeObject)
-{
-  v8::HandleScope handle_scope(isolate);
-
-  v8::Handle<v8::Object> obj = V8Util::WrapObject(isolate, geometry, GetObjectTemplate(isolate));
-
-  if (freeObject)
-  {
-    v8::Persistent<v8::Object> weakRef;
-    weakRef.Reset(v8::Isolate::GetCurrent(), obj);
-    weakRef.MakeWeak(geometry, &FreeGeometryCallback);
-  }
-
-  return handle_scope.Close(obj);
-}
-
-v8::Handle<v8::ObjectTemplate>
-JSSymbologyGeometry::GetObjectTemplate(v8::Isolate* isolate)
-{
-  v8::HandleScope handle_scope(isolate);
-
-  v8::Handle<v8::ObjectTemplate> template_instance = v8::ObjectTemplate::New();
-
-  template_instance->Set(v8::String::New(V8_OBJECT_TYPE_PROPERTY), v8::String::New(GetObjectType().c_str()));
-  template_instance->SetInternalFieldCount(1);
-  template_instance->SetNamedPropertyHandler(PropertyCallback);
-  template_instance->SetIndexedPropertyHandler(IndexedPropertyCallback);
-
-  return handle_scope.Close(template_instance);
-}
-
-void
-JSSymbologyGeometry::PropertyCallback(v8::Local<v8::String> name, const v8::PropertyCallbackInfo<v8::Value>& info)
-{
-  osgEarth::Symbology::Geometry* geom = V8Util::UnwrapObject<osgEarth::Symbology::Geometry>(info.Holder());
-
-  v8::String::Utf8Value utf8_value(name);
-  std::string prop(*utf8_value);
-
-  if (!geom || prop.empty())
-    return;
-
-  v8::Local<v8::Value> value;
-
-  if (prop == "totalPointCount")
-    value = v8::Integer::New(geom->getTotalPointCount());
-  else if (prop == "numComponents")
-    value = v8::Uint32::New(geom->getNumComponents());
-  else if (prop == "bounds")
-  {
-    osgEarth::Bounds bounds = geom->getBounds();
-    osgEarth::Bounds* newBounds = new osgEarth::Bounds();
-    newBounds->set(bounds.xMin(), bounds.yMin(), bounds.zMin(), bounds.xMax(), bounds.yMax(), bounds.zMax());
-    value = JSBounds::WrapBounds(v8::Isolate::GetCurrent(), newBounds, true);
-  }
-  else if (prop == "type")
-    value = v8::String::New(osgEarth::Symbology::Geometry::toString(geom->getType()).c_str());
-  else if (prop == "componentType")
-    value = v8::String::New(osgEarth::Symbology::Geometry::toString(geom->getComponentType()).c_str());
-
-  if (!value.IsEmpty())
-    info.GetReturnValue().Set(value);
-}
-
-void
-JSSymbologyGeometry::IndexedPropertyCallback(uint32_t index, const v8::PropertyCallbackInfo<v8::Value>& info)
-{
-  osgEarth::Symbology::Geometry* geom = V8Util::UnwrapObject<osgEarth::Symbology::Geometry>(info.Holder());
-
-  v8::Local<v8::Value> value;
-  
-  if (geom)
-    value = JSVec3d::WrapVec3d(v8::Isolate::GetCurrent(), &((*geom)[index]));
-
-  if (!value.IsEmpty())
-    info.GetReturnValue().Set(value);
-}
-
-void
-JSSymbologyGeometry::FreeGeometryCallback(v8::Isolate* isolate, v8::Persistent<v8::Object>* handle, osgEarth::Symbology::Geometry* parameter)
-{
-  delete parameter;
-  handle->Dispose();
-}
-
-// ---------------------------------------------------------------------------
-
-const std::string JSBounds::_objectType = "JSBounds";
-
-v8::Handle<v8::Object>
-JSBounds::WrapBounds(v8::Isolate* isolate, osgEarth::Bounds* bounds, bool freeObject)
-{
-  v8::HandleScope handle_scope(isolate);
-
-  v8::Handle<v8::Object> obj = V8Util::WrapObject(isolate, bounds, GetObjectTemplate(isolate));
-
-  if (freeObject)
-  {
-    v8::Persistent<v8::Object> weakRef;
-    weakRef.Reset(v8::Isolate::GetCurrent(), obj);
-    weakRef.MakeWeak(bounds, &FreeBoundsCallback);
-  }
-
-  return handle_scope.Close(obj);
-}
-
-v8::Handle<v8::ObjectTemplate>
-JSBounds::GetObjectTemplate(v8::Isolate* isolate)
-{
-  v8::HandleScope handle_scope(isolate);
-
-  v8::Handle<v8::ObjectTemplate> template_instance = v8::ObjectTemplate::New();
-
-  template_instance->Set(v8::String::New(V8_OBJECT_TYPE_PROPERTY), v8::String::New(GetObjectType().c_str()));
-  template_instance->SetInternalFieldCount(1);
-  template_instance->SetNamedPropertyHandler(PropertyCallback);
-
-  template_instance->Set(v8::String::New("contains"), v8::FunctionTemplate::New(ContainsCallback));
-  template_instance->Set(v8::String::New("unionWith"), v8::FunctionTemplate::New(UnionCallback));
-  template_instance->Set(v8::String::New("intersectionWith"), v8::FunctionTemplate::New(IntersectionCallback));
-
-  return handle_scope.Close(template_instance);
-}
-
-void
-JSBounds::PropertyCallback(v8::Local<v8::String> name, const v8::PropertyCallbackInfo<v8::Value>& info)
-{
-  osgEarth::Bounds* bounds = V8Util::UnwrapObject<osgEarth::Bounds>(info.Holder());
-
-  v8::String::Utf8Value utf8_value(name);
-  std::string prop(*utf8_value);
-
-  if (!bounds || prop.empty())
-    return;
-
-  v8::Local<v8::Value> value;
-
-  if (prop == "valid")
-    value = v8::Boolean::New(bounds->valid());
-  else if (prop == "xMin")
-    value = v8::Number::New(bounds->xMin());
-  else if (prop == "xMax")
-    value = v8::Number::New(bounds->xMax());
-  else if (prop == "yMin")
-    value = v8::Number::New(bounds->yMin());
-  else if (prop == "yMax")
-    value = v8::Number::New(bounds->yMax());
-  else if (prop == "zMin")
-    value = v8::Number::New(bounds->zMin());
-  else if (prop == "zMax")
-    value = v8::Number::New(bounds->zMax());
-  else if (prop == "center")
-  {
-    osg::Vec3d* vec = new osg::Vec3d(bounds->center());
-    value = JSVec3d::WrapVec3d(v8::Isolate::GetCurrent(), vec, true);
-  }
-  else if (prop == "radius")
-    value = v8::Number::New(bounds->radius());
-  else if (prop == "width")
-    value = v8::Number::New(bounds->width());
-  else if (prop == "height")
-    value = v8::Number::New(bounds->height());
-
-  if (!value.IsEmpty())
-    info.GetReturnValue().Set(value);
-}
-
-void
-JSBounds::ContainsCallback(const v8::FunctionCallbackInfo<v8::Value>& info)
-{
-  osgEarth::Bounds* bounds = V8Util::UnwrapObject<osgEarth::Bounds>(info.Holder());
-
-  if (bounds)
-  {
-    v8::Local<v8::Value> value;
-
-    if (info.Length() == 1 && info[0]->IsObject())  // Bounds
-    {
-      v8::Local<v8::Object> obj( v8::Local<v8::Object>::Cast(info[0]) );
-      if (V8Util::CheckObjectType(obj, JSBounds::GetObjectType()))
-      {
-        osgEarth::Bounds* rhs = V8Util::UnwrapObject<osgEarth::Bounds>(obj);
-        value = v8::Boolean::New(bounds->contains(*rhs));
-      }
-    }
-    else if (info.Length() == 2)
-    {
-      value = v8::Boolean::New(bounds->contains(info[0]->NumberValue(), info[1]->NumberValue()));
-    }
-
-    if (!value.IsEmpty())
-      info.GetReturnValue().Set(value);
-  }
-}
-
-void
-JSBounds::UnionCallback(const v8::FunctionCallbackInfo<v8::Value>& info)
-{
-  osgEarth::Bounds* bounds = V8Util::UnwrapObject<osgEarth::Bounds>(info.Holder());
-
-  if (bounds)
-  {
-    v8::Local<v8::Value> value;
-
-    if (info.Length() == 1 && info[0]->IsObject())  // Bounds
-    {
-      v8::Local<v8::Object> obj( v8::Local<v8::Object>::Cast(info[0]) );
-
-      if (V8Util::CheckObjectType(obj, JSBounds::GetObjectType()))
-      {
-        osgEarth::Bounds* rhs = V8Util::UnwrapObject<osgEarth::Bounds>(obj);
-        osgEarth::Bounds* outBounds = new osgEarth::Bounds();
-        outBounds->expandBy(bounds->unionWith(*rhs));
-        value = WrapBounds(v8::Isolate::GetCurrent(), outBounds, true);
-      }
-    }
-
-    if (!value.IsEmpty())
-      info.GetReturnValue().Set(value);
-  }
-}
-
-void
-JSBounds::IntersectionCallback(const v8::FunctionCallbackInfo<v8::Value>& info)
-{
-  osgEarth::Bounds* bounds = V8Util::UnwrapObject<osgEarth::Bounds>(info.Holder());
-
-  if (bounds)
-  {
-    v8::Local<v8::Value> value;
-
-    if (info.Length() == 1 && info[0]->IsObject())  // Bounds
-    {
-      v8::Local<v8::Object> obj( v8::Local<v8::Object>::Cast(info[0]) );
-
-      if (V8Util::CheckObjectType(obj, JSBounds::GetObjectType()))
-      {
-        osgEarth::Bounds* rhs = V8Util::UnwrapObject<osgEarth::Bounds>(obj);
-        osgEarth::Bounds* outBounds = new osgEarth::Bounds();
-        outBounds->expandBy(bounds->intersectionWith(*rhs));
-        value = WrapBounds(v8::Isolate::GetCurrent(), outBounds, true);
-      }
-    }
-
-    if (!value.IsEmpty())
-      info.GetReturnValue().Set(value);
-  }
-}
-
-void
-JSBounds::FreeBoundsCallback(v8::Isolate* isolate, v8::Persistent<v8::Object>* handle, osgEarth::Bounds* parameter)
-{
-  delete parameter;
-  handle->Dispose();
-}
-
-// ---------------------------------------------------------------------------
-
-const std::string JSVec3d::_objectType = "JSVec3d";
-
-v8::Handle<v8::Object>
-JSVec3d::WrapVec3d(v8::Isolate* isolate, osg::Vec3d* vec, bool freeObject)
-{
-  v8::HandleScope handle_scope(isolate);
-
-  v8::Handle<v8::Object> obj = V8Util::WrapObject(isolate, vec, GetObjectTemplate(isolate));
-
-  if (freeObject)
-  {
-    v8::Persistent<v8::Object> weakRef;
-    weakRef.Reset(v8::Isolate::GetCurrent(), obj);
-    weakRef.MakeWeak(vec, &FreeVecCallback);
-  }
-
-  return handle_scope.Close(obj);
-}
-
-v8::Handle<v8::ObjectTemplate>
-JSVec3d::GetObjectTemplate(v8::Isolate* isolate)
-{
-  v8::HandleScope handle_scope(isolate);
-
-  v8::Handle<v8::ObjectTemplate> template_instance = v8::ObjectTemplate::New();
-
-  template_instance->Set(v8::String::New(V8_OBJECT_TYPE_PROPERTY), v8::String::New(GetObjectType().c_str()));
-  template_instance->SetInternalFieldCount(1);
-  template_instance->SetNamedPropertyHandler(PropertyCallback);
-  template_instance->SetIndexedPropertyHandler(IndexedPropertyCallback);
-
-  return handle_scope.Close(template_instance);
-}
-
-void
-JSVec3d::PropertyCallback(v8::Local<v8::String> name, const v8::PropertyCallbackInfo<v8::Value>& info)
-{
-  osg::Vec3d* v = V8Util::UnwrapObject<osg::Vec3d>(info.Holder());
-
-  v8::String::Utf8Value utf8_value(name);
-  std::string prop(*utf8_value);
-
-  if (!v || prop.empty())
-    return;
-
-  v8::Local<v8::Value> value;
-
-  if (prop == "x")
-    value = v8::Number::New(v->x());
-  if (prop == "y")
-    value = v8::Number::New(v->y());
-  if (prop == "z")
-    value = v8::Number::New(v->z());
-
-  if (!value.IsEmpty())
-    info.GetReturnValue().Set(value);
-}
-
-void
-JSVec3d::IndexedPropertyCallback(uint32_t index, const v8::PropertyCallbackInfo<v8::Value>& info)
-{
-  osg::Vec3d* v = V8Util::UnwrapObject<osg::Vec3d>(info.Holder());
-
-  if (!v || index > 2)
-    return;
-
-  info.GetReturnValue().Set(v8::Number::New((*v)[index]));
-}
-
-void JSVec3d::FreeVecCallback(v8::Isolate* isolate, v8::Persistent<v8::Object>* handle, osg::Vec3d* parameter)
-{
-  delete parameter;
-  handle->Dispose();
-}
-
-// ---------------------------------------------------------------------------
-
-const std::string JSFilterContext::_objectType = "JSFilterContext";
-
-v8::Handle<v8::Object>
-JSFilterContext::WrapFilterContext(v8::Isolate* isolate, osgEarth::Features::FilterContext* context, bool freeObject)
-{
-  v8::HandleScope handle_scope(isolate);
-
-  if (!context)
-  {
-    v8::Handle<v8::Object> obj;
-    return handle_scope.Close(obj);
-  }
-
-  v8::Handle<v8::Object> obj = V8Util::WrapObject(isolate, context, GetObjectTemplate(isolate));
-
-  if (freeObject)
-  {
-    v8::Persistent<v8::Object> weakRef;
-    weakRef.Reset(v8::Isolate::GetCurrent(), obj);
-    weakRef.MakeWeak(context, &FreeContextCallback);
-  }
-
-  return handle_scope.Close(obj);
-}
-
-v8::Handle<v8::ObjectTemplate>
-JSFilterContext::GetObjectTemplate(v8::Isolate* isolate)
-{
-  v8::HandleScope handle_scope(isolate);
-
-  v8::Handle<v8::ObjectTemplate> template_instance = v8::ObjectTemplate::New();
-
-  template_instance->Set(v8::String::New(V8_OBJECT_TYPE_PROPERTY), v8::String::New(GetObjectType().c_str()));
-  template_instance->SetInternalFieldCount(1);
-  template_instance->SetNamedPropertyHandler(PropertyCallback);
-
-#if 0
-  template_instance->Set(v8::String::New("toLocal"), v8::FunctionTemplate::New(ToLocalCallback));
-  template_instance->Set(v8::String::New("toWorld"), v8::FunctionTemplate::New(ToWorldCallback));
-  template_instance->Set(v8::String::New("toMap"), v8::FunctionTemplate::New(ToMapCallback));
-  template_instance->Set(v8::String::New("fromMap"), v8::FunctionTemplate::New(FromMapCallback));
-#endif
-
-  return handle_scope.Close(template_instance);
-}
-
-void
-JSFilterContext::PropertyCallback(v8::Local<v8::String> name, const v8::PropertyCallbackInfo<v8::Value>& info)
-{
-  FilterContext* context = V8Util::UnwrapObject<FilterContext>(info.Holder());
-
-  v8::String::Utf8Value utf8_value(name);
-  std::string prop(*utf8_value);
-
-  if (!context || prop.empty())
-    return;
-
-  v8::Local<v8::Value> value;
-
-  if (prop == "session")
-    value = JSSession::WrapSession(v8::Isolate::GetCurrent(), const_cast<Session*>(context->getSession()));
-  if (prop == "profile")
-    value = JSFeatureProfile::WrapFeatureProfile(v8::Isolate::GetCurrent(), const_cast<FeatureProfile*>(context->profile()));
-  if (prop == "extent" && context->extent().isSet())
-    value = JSGeoExtent::WrapGeoExtent(v8::Isolate::GetCurrent(), const_cast<osgEarth::GeoExtent*>(&context->extent().get()));
-  //if (prop == "geocentric")
-  //  value = v8::Boolean::New(context->isGeocentric());
-
-  if (!value.IsEmpty())
-    info.GetReturnValue().Set(value);
-}
-
-void
-JSFilterContext::ToLocalCallback(const v8::FunctionCallbackInfo<v8::Value>& info)
-{
-  FilterContext* context = V8Util::UnwrapObject<FilterContext>(info.Holder());
-
-  v8::Local<v8::Value> value;
-
-  if (context && info.Length() == 1 && info[0]->IsObject())
-  {
-    v8::Local<v8::Object> obj( v8::Local<v8::Object>::Cast(info[0]) );
-
-    /*if (V8Util::CheckObjectType(obj, JSGeometry::GetObjectType()))  // Geometry
-    {
-      osgEarth::Symbology::Geometry* geometry = V8Util::UnwrapObject<osgEarth::Symbology::Geometry>(obj);
-
-      return 
-    }*/
-    
-    if (V8Util::CheckObjectType(obj, JSVec3d::GetObjectType()))  // Vec3d
-    {
-      osg::Vec3d* vec = V8Util::UnwrapObject<osg::Vec3d>(obj);
-      osg::Vec3d* local = new osg::Vec3d(context->toLocal(*vec));
-      value = JSVec3d::WrapVec3d(v8::Isolate::GetCurrent(), local, true);
-    }
-  }
-
-  if (!value.IsEmpty())
-    info.GetReturnValue().Set(value);
-}
-
-void
-JSFilterContext::ToWorldCallback(const v8::FunctionCallbackInfo<v8::Value>& info)
-{
-  FilterContext* context = V8Util::UnwrapObject<FilterContext>(info.Holder());
-
-  v8::Local<v8::Value> value;
-
-  if (context && info.Length() == 1 && info[0]->IsObject())
-  {
-    v8::Local<v8::Object> obj( v8::Local<v8::Object>::Cast(info[0]) );
-
-    /*if (V8Util::CheckObjectType(obj, JSGeometry::GetObjectType()))  // Geometry
-    {
-      osgEarth::Symbology::Geometry* geometry = V8Util::UnwrapObject<osgEarth::Symbology::Geometry>(obj);
-
-      return 
-    }*/
-    
-    if (V8Util::CheckObjectType(obj, JSVec3d::GetObjectType()))  // Vec3d
-    {
-      osg::Vec3d* vec = V8Util::UnwrapObject<osg::Vec3d>(obj);
-      osg::Vec3d* world = new osg::Vec3d(context->toWorld(*vec));
-      value = JSVec3d::WrapVec3d(v8::Isolate::GetCurrent(), world, true);
-    }
-  }
-
-  if (!value.IsEmpty())
-    info.GetReturnValue().Set(value);
-}
-
-void
-JSFilterContext::ToMapCallback(const v8::FunctionCallbackInfo<v8::Value>& info)
-{
-  FilterContext* context = V8Util::UnwrapObject<FilterContext>(info.Holder());
-
-  v8::Local<v8::Value> value;
-
-  if (context && info.Length() == 1 && info[0]->IsObject())
-  {
-    v8::Local<v8::Object> obj( v8::Local<v8::Object>::Cast(info[0]) );
-    
-    if (V8Util::CheckObjectType(obj, JSVec3d::GetObjectType()))  // Vec3d
-    {
-      osg::Vec3d* vec = V8Util::UnwrapObject<osg::Vec3d>(obj);
-      osg::Vec3d* map = new osg::Vec3d(context->toMap(*vec));
-      value = JSVec3d::WrapVec3d(v8::Isolate::GetCurrent(), map, true);
-    }
-  }
-
-  if (!value.IsEmpty())
-    info.GetReturnValue().Set(value);
-}
-
-void
-JSFilterContext::FromMapCallback(const v8::FunctionCallbackInfo<v8::Value>& info)
-{
-  FilterContext* context = V8Util::UnwrapObject<FilterContext>(info.Holder());
-
-  v8::Local<v8::Value> value;
-
-  if (context && info.Length() == 1 && info[0]->IsObject())
-  {
-    v8::Local<v8::Object> obj( v8::Local<v8::Object>::Cast(info[0]) );
-    
-    if (V8Util::CheckObjectType(obj, JSVec3d::GetObjectType()))  // Vec3d
-    {
-      osg::Vec3d* map = V8Util::UnwrapObject<osg::Vec3d>(obj);
-      osg::Vec3d* local = new osg::Vec3d(context->fromMap(*map));
-      value = JSVec3d::WrapVec3d(v8::Isolate::GetCurrent(), local, true);
-    }
-  }
-
-  if (!value.IsEmpty())
-    info.GetReturnValue().Set(value);
-}
-
-void
-JSFilterContext::FreeContextCallback(v8::Isolate* isolate, v8::Persistent<v8::Object>* handle, osgEarth::Features::FilterContext* parameter)
-{
-  delete parameter;
-  handle->Dispose();
-}
-
-// ---------------------------------------------------------------------------
-
-const std::string JSSession::_objectType = "JSSession";
-
-v8::Handle<v8::Object>
-JSSession::WrapSession(v8::Isolate* isolate, osgEarth::Features::Session* session, bool freeObject)
-{
-  v8::HandleScope handle_scope(isolate);
-
-  v8::Handle<v8::Object> obj = V8Util::WrapObject(isolate, session, GetObjectTemplate(isolate));
-
-  if (freeObject)
-  {
-    v8::Persistent<v8::Object> weakRef;
-    weakRef.Reset(v8::Isolate::GetCurrent(), obj);
-    weakRef.MakeWeak(session, &FreeSessionCallback);
-  }
-
-  return handle_scope.Close(obj);
-}
-
-v8::Handle<v8::ObjectTemplate>
-JSSession::GetObjectTemplate(v8::Isolate* isolate)
-{
-  v8::HandleScope handle_scope(isolate);
-
-  v8::Handle<v8::ObjectTemplate> template_instance = v8::ObjectTemplate::New();
-
-  template_instance->Set(v8::String::New(V8_OBJECT_TYPE_PROPERTY), v8::String::New(GetObjectType().c_str()));
-  template_instance->SetInternalFieldCount(1);
-  template_instance->SetNamedPropertyHandler(PropertyCallback);
-
-#if 0
-  template_instance->Set(v8::String::New("resolveURI"), v8::FunctionTemplate::New(ResolveUriCallback));
-#endif
-
-  return handle_scope.Close(template_instance);
-}
-
-void
-JSSession::PropertyCallback(v8::Local<v8::String> name, const v8::PropertyCallbackInfo<v8::Value>& info)
-{
-  Session* session = V8Util::UnwrapObject<Session>(info.Holder());
-
-  v8::String::Utf8Value utf8_value(name);
-  std::string prop(*utf8_value);
-
-  if (!session || prop.empty())
-    return;
-
-  if (prop == "mapInfo")
-    info.GetReturnValue().Set(JSMapInfo::WrapMapInfo(v8::Isolate::GetCurrent(), const_cast<osgEarth::MapInfo*>(&session->getMapInfo())));
-}
-
-#if 0
-void
-JSSession::ResolveUriCallback(const v8::FunctionCallbackInfo<v8::Value>& info)
-{
-  Session* session = V8Util::UnwrapObject<Session>(info.Holder());
-
-  v8::Local<v8::Value> value;
-
-  if (session && info.Length() == 1 && info[0]->IsString())
-  {
-    v8::String::Utf8Value utf8_value(info[0]->ToString());
-    std::string uri(*utf8_value);
-    value = v8::String::New(session->resolveURI(uri).c_str());
-  }
-
-  if (!value.IsEmpty())
-    info.GetReturnValue().Set(value);
-}
-#endif
-
-void
-JSSession::FreeSessionCallback(v8::Isolate* isolate, v8::Persistent<v8::Object>* handle, osgEarth::Features::Session* parameter)
-{
-  delete parameter;
-  handle->Dispose();
-}
-
-// ---------------------------------------------------------------------------
-
-const std::string JSMapInfo::_objectType = "JSMapInfo";
-
-v8::Handle<v8::Object>
-JSMapInfo::WrapMapInfo(v8::Isolate* isolate, osgEarth::MapInfo* mapInfo, bool freeObject)
-{
-  v8::HandleScope handle_scope(isolate);
-
-  v8::Handle<v8::Object> obj = V8Util::WrapObject(isolate, mapInfo, GetObjectTemplate(isolate));
-
-  if (freeObject)
-  {
-    v8::Persistent<v8::Object> weakRef;
-    weakRef.Reset(v8::Isolate::GetCurrent(), obj);
-    weakRef.MakeWeak(mapInfo, &FreeMapInfoCallback);
-  }
-
-  return handle_scope.Close(obj);
-}
-
-v8::Handle<v8::ObjectTemplate>
-JSMapInfo::GetObjectTemplate(v8::Isolate* isolate)
-{
-  v8::HandleScope handle_scope(isolate);
-
-  v8::Handle<v8::ObjectTemplate> template_instance = v8::ObjectTemplate::New();
-
-  template_instance->Set(v8::String::New(V8_OBJECT_TYPE_PROPERTY), v8::String::New(GetObjectType().c_str()));
-  template_instance->SetInternalFieldCount(1);
-  template_instance->SetNamedPropertyHandler(PropertyCallback);
-
-#if 0
-  template_instance->Set(v8::String::New("toMapPoint"), v8::FunctionTemplate::New(ToMapCallback));
-  template_instance->Set(v8::String::New("mapPointToWorldPoint"), v8::FunctionTemplate::New(MapToWorldCallback));
-  template_instance->Set(v8::String::New("worldPointToMapPoint"), v8::FunctionTemplate::New(WorldToMapCallback));
-#endif
-
-  return handle_scope.Close(template_instance);
-}
-
-void
-JSMapInfo::PropertyCallback(v8::Local<v8::String> name, const v8::PropertyCallbackInfo<v8::Value>& info)
-{
-  osgEarth::MapInfo* mapInfo = V8Util::UnwrapObject<osgEarth::MapInfo>(info.Holder());
-
-  v8::String::Utf8Value utf8_value(name);
-  std::string prop(*utf8_value);
-
-  if (!mapInfo || prop.empty())
-    return;
-
-  v8::Local<v8::Value> value;
-
-  if (prop == "geocentric")
-    value = v8::Boolean::New(mapInfo->isGeocentric());
-  if (prop == "cube")
-    value = v8::Boolean::New(mapInfo->isCube());
-  if (prop == "plateCarre" || prop == "platecarre")
-    value = v8::Boolean::New(mapInfo->isPlateCarre());
-  if (prop == "projectedSRS")
-    value = v8::Boolean::New(mapInfo->isProjectedSRS());
-  if (prop == "geographicSRS")
-    value = v8::Boolean::New(mapInfo->isGeographicSRS());
-
-  if (!value.IsEmpty())
-    info.GetReturnValue().Set(value);
-}
-
-#if 0
-void
-JSMapInfo::ToMapCallback(const v8::FunctionCallbackInfo<v8::Value>& info)
-{
-  osgEarth::MapInfo* mapInfo = V8Util::UnwrapObject<osgEarth::MapInfo>(info.Holder());
-
-  v8::Local<v8::Value> value;
-
-  if (mapInfo && info.Length() == 2 && info[0]->IsObject() && info[1]->IsObject()) // Vec3d & SpatialReference
-  {
-    v8::Local<v8::Object> obj0( v8::Object::Cast(*info[0]) );
-    v8::Local<v8::Object> obj1( v8::Object::Cast(*info[1]) );
-
-    if (V8Util::CheckObjectType(obj0, JSVec3d::GetObjectType()) && V8Util::CheckObjectType(obj1, JSSpatialReference::GetObjectType()))
-    {
-      osg::Vec3d* input = V8Util::UnwrapObject<osg::Vec3d>(obj0);
-      osgEarth::SpatialReference* srs = V8Util::UnwrapObject<osgEarth::SpatialReference>(obj1);
-
-      osg::Vec3d* out = new osg::Vec3d();
-
-      GeoPoint mapPoint;
-      mapPoint.fromWorld( srs, *input );
-      out->set( mapPoint.x(), mapPoint.y(), mapPoint.z() );
-
-      value = JSVec3d::WrapVec3d(out, true);
-
-      //if (mapInfo->toMapPoint(*input, srs, *out))
-      //  value = JSVec3d::WrapVec3d(out, true);
-
-      delete out;
-    }
-  }
-
-  if (!value.IsEmpty())
-    info.GetReturnValue().Set(value);
-}
-
-void
-JSMapInfo::MapToWorldCallback(const v8::FunctionCallbackInfo<v8::Value>& info)
-{
-  osgEarth::MapInfo* mapInfo = V8Util::UnwrapObject<osgEarth::MapInfo>(info.Holder());
-  
-  v8::Local<v8::Value> value;
-
-  if (mapInfo && info.Length() == 1 && info[0]->IsObject()) // Vec3d
-  {
-    v8::Local<v8::Object> obj( v8::Local<v8::Object>::Cast(info[0]) );
-    if (V8Util::CheckObjectType(obj, JSVec3d::GetObjectType()))
-    {
-      osg::Vec3d* input = V8Util::UnwrapObject<osg::Vec3d>(obj);
-
-      osg::Vec3d* out = new osg::Vec3d();
-      if (mapInfo->mapPointToWorldPoint(*input, *out))
-        value = JSVec3d::WrapVec3d(out, true);
-
-      delete out;
-    }
-  }
-
-  if (!value.IsEmpty())
-    info.GetReturnValue().Set(value);
-}
-
-void
-JSMapInfo::WorldToMapCallback(const v8::FunctionCallbackInfo<v8::Value>& info)
-{
-  osgEarth::MapInfo* mapInfo = V8Util::UnwrapObject<osgEarth::MapInfo>(info.Holder());
-
-  v8::Local<v8::Value> value;
-
-  if (mapInfo && info.Length() == 1 && info[0]->IsObject()) // Vec3d
-  {
-    v8::Local<v8::Object> obj( v8::Local<v8::Object>::Cast(info[0]) );
-    if (V8Util::CheckObjectType(obj, JSVec3d::GetObjectType()))
-    {
-      osg::Vec3d* input = V8Util::UnwrapObject<osg::Vec3d>(obj);
-
-      osg::Vec3d* out = new osg::Vec3d();
-      if (mapInfo->worldPointToMapPoint(*input, *out))
-        value = JSVec3d::WrapVec3d(out, true);
-
-      delete out;
-    }
-  }
-
-  if (!value.IsEmpty())
-    info.GetReturnValue().Set(value);
-}
-#endif
-
-void
-JSMapInfo::FreeMapInfoCallback(v8::Isolate* isolate, v8::Persistent<v8::Object>* handle, osgEarth::MapInfo* parameter)
-{
-  delete parameter;
-  handle->Dispose();
-}
-
-// ---------------------------------------------------------------------------
-
-const std::string JSFeatureProfile::_objectType = "JSFeatureProfile";
-
-v8::Handle<v8::Object>
-JSFeatureProfile::WrapFeatureProfile(v8::Isolate* isolate, osgEarth::Features::FeatureProfile* profile, bool freeObject)
-{
-  v8::HandleScope handle_scope(isolate);
-
-  v8::Handle<v8::Object> obj = V8Util::WrapObject(isolate, profile, GetObjectTemplate(isolate));
-
-  if (freeObject)
-  {
-    v8::Persistent<v8::Object> weakRef;
-    weakRef.Reset(v8::Isolate::GetCurrent(), obj);
-    weakRef.MakeWeak(profile, &FreeProfileCallback);
-  }
-
-  return handle_scope.Close(obj);
-}
-
-v8::Handle<v8::ObjectTemplate>
-JSFeatureProfile::GetObjectTemplate(v8::Isolate* isolate)
-{
-  v8::HandleScope handle_scope(isolate);
-
-  v8::Handle<v8::ObjectTemplate> template_instance = v8::ObjectTemplate::New();
-
-  template_instance->Set(v8::String::New(V8_OBJECT_TYPE_PROPERTY), v8::String::New(GetObjectType().c_str()));
-  template_instance->SetInternalFieldCount(1);
-  template_instance->SetNamedPropertyHandler(PropertyCallback);
-
-  //template_instance->Set(v8::String::New("toLocal"),
-
-  return handle_scope.Close(template_instance);
-}
-
-void
-JSFeatureProfile::PropertyCallback(v8::Local<v8::String> name, const v8::PropertyCallbackInfo<v8::Value>& info)
-{
-  FeatureProfile* profile = V8Util::UnwrapObject<FeatureProfile>(info.Holder());
-
-  v8::String::Utf8Value utf8_value(name);
-  std::string prop(*utf8_value);
-
-  if (!profile || prop.empty())
-    return;
-
-  v8::Local<v8::Value> value;
-
-  if (prop == "extent")
-    value = JSGeoExtent::WrapGeoExtent(v8::Isolate::GetCurrent(), const_cast<osgEarth::GeoExtent*>(&profile->getExtent()));
-  if (prop == "srs")
-    value = JSSpatialReference::WrapSpatialReference(v8::Isolate::GetCurrent(), const_cast<osgEarth::SpatialReference*>(profile->getSRS()));
-
-  if (!value.IsEmpty())
-    info.GetReturnValue().Set(value);
-}
-
-void
-JSFeatureProfile::FreeProfileCallback(v8::Isolate* isolate, v8::Persistent<v8::Object>* handle, osgEarth::Features::FeatureProfile* parameter)
-{
-  delete parameter;
-  handle->Dispose();
-}
-
-// ---------------------------------------------------------------------------
-
-const std::string JSGeoExtent::_objectType = "JSGeoExtent";
-
-v8::Handle<v8::Object>
-JSGeoExtent::WrapGeoExtent(v8::Isolate* isolate, osgEarth::GeoExtent* extent, bool freeObject)
-{
-  v8::HandleScope handle_scope(isolate);
-
-  v8::Handle<v8::Object> obj = V8Util::WrapObject(isolate, extent, GetObjectTemplate(isolate));
-
-  if (freeObject)
-  {
-    v8::Persistent<v8::Object> weakRef;
-    weakRef.Reset(v8::Isolate::GetCurrent(), obj);
-    weakRef.MakeWeak(extent, &FreeGeoExtentCallback);
-  }
-
-  return handle_scope.Close(obj);
-}
-
-v8::Handle<v8::ObjectTemplate>
-JSGeoExtent::GetObjectTemplate(v8::Isolate* isolate)
-{
-  v8::HandleScope handle_scope(isolate);
-
-  v8::Handle<v8::ObjectTemplate> template_instance = v8::ObjectTemplate::New();
-
-  template_instance->Set(v8::String::New(V8_OBJECT_TYPE_PROPERTY), v8::String::New(GetObjectType().c_str()));
-  template_instance->SetInternalFieldCount(1);
-  template_instance->SetNamedPropertyHandler(PropertyCallback);
-
-  template_instance->Set(v8::String::New("contains"), v8::FunctionTemplate::New(ContainsCallback));
-  template_instance->Set(v8::String::New("intersects"), v8::FunctionTemplate::New(IntersectsCallback));
-
-  return handle_scope.Close(template_instance);
-}
-
-void
-JSGeoExtent::PropertyCallback(v8::Local<v8::String> name, const v8::PropertyCallbackInfo<v8::Value>& info)
-{
-  osgEarth::GeoExtent* extent = V8Util::UnwrapObject<osgEarth::GeoExtent>(info.Holder());
-
-  v8::String::Utf8Value utf8_value(name);
-  std::string prop(*utf8_value);
-
-  if (!extent || prop.empty())
-    return;
-
-  v8::Local<v8::Value> value;
-
-  if (prop == "srs")
-    value = JSSpatialReference::WrapSpatialReference(v8::Isolate::GetCurrent(), const_cast<osgEarth::SpatialReference*>(extent->getSRS()));
-  if (prop == "xMin")
-    value = v8::Number::New(extent->xMin());
-  if (prop == "xMax")
-    value = v8::Number::New(extent->xMax());
-  if (prop == "yMin")
-    value = v8::Number::New(extent->yMin());
-  if (prop == "yMax")
-    value = v8::Number::New(extent->yMax());
-  if (prop == "width")
-    value = v8::Number::New(extent->width());
-  if (prop == "height")
-    value = v8::Number::New(extent->height());
-  if (prop == "crossesAntimeridian")
-    value = v8::Boolean::New(extent->crossesAntimeridian());
-  if (prop == "valid")
-    value = v8::Boolean::New(extent->isValid());
-  if (prop == "defined")
-    value = v8::Boolean::New(extent->defined());
-  if (prop == "area")
-    value = v8::Number::New(extent->area());
-  //if (prop == "toString")
-  //  value = v8::String::New(extent->toString().c_str());
-
-  if (!value.IsEmpty())
-    info.GetReturnValue().Set(value);
-}
-
-void
-JSGeoExtent::ContainsCallback(const v8::FunctionCallbackInfo<v8::Value>& info)
-{
-  osgEarth::GeoExtent* extent = V8Util::UnwrapObject<osgEarth::GeoExtent>(info.Holder());
-  v8::Local<v8::Value> value;
-
-  if (extent)
-  {
-    if (info.Length() == 1 && info[0]->IsObject())  // Bounds
-    {
-      v8::Local<v8::Object> obj( v8::Local<v8::Object>::Cast(info[0]) );
-
-      if (V8Util::CheckObjectType(obj, JSBounds::GetObjectType()))
-      {
-        osgEarth::Bounds* bounds = V8Util::UnwrapObject<osgEarth::Bounds>(obj);
-        value = v8::Boolean::New(extent->contains(*bounds));
-      }
-    }
-    else if (info.Length() == 2 /*&& info[0]->IsNumber() && info[1]->IsNumber()*/)  // x and y
-    {
-      value = v8::Boolean::New(extent->contains(info[0]->NumberValue(), info[1]->NumberValue()));
-    }
-    else if (info.Length() == 3 && /*info[0]->IsNumber() && info[1]->IsNumber() &&*/ info[2]->IsObject())  // x, y, and SpatialReference
-    {
-      v8::Local<v8::Object> obj( v8::Local<v8::Object>::Cast(info[2]) );
-
-      if (V8Util::CheckObjectType(obj, JSSpatialReference::GetObjectType()))
-      {
-        osgEarth::SpatialReference* srs = V8Util::UnwrapObject<osgEarth::SpatialReference>(obj);
-        value = v8::Boolean::New(extent->contains(info[0]->NumberValue(), info[1]->NumberValue(), srs));
-      }
-    }
-  }
-
-  if (!value.IsEmpty())
-    info.GetReturnValue().Set(value);
-}
-
-void
-JSGeoExtent::IntersectsCallback(const v8::FunctionCallbackInfo<v8::Value>& info)
-{
-  osgEarth::GeoExtent* extent = V8Util::UnwrapObject<osgEarth::GeoExtent>(info.Holder());
-
-  if (!extent)
-    return;
-
-  v8::Local<v8::Value> value;
-
-  if (info.Length() == 1 && info[0]->IsObject())  // GeoExtent
-  {
-    v8::Local<v8::Object> obj( v8::Local<v8::Object>::Cast(info[0]) );
-
-    if (V8Util::CheckObjectType(obj, JSGeoExtent::GetObjectType()))
-    {
-      osgEarth::GeoExtent* rhs = V8Util::UnwrapObject<osgEarth::GeoExtent>(obj);
-      value = v8::Boolean::New(extent->intersects(*rhs));
-    }
-  }
-
-  if (!value.IsEmpty())
-    info.GetReturnValue().Set(value);
-}
-
-void
-JSGeoExtent::FreeGeoExtentCallback(v8::Isolate* isolate, v8::Persistent<v8::Object>* handle, osgEarth::GeoExtent* parameter)
-{
-  delete parameter;
-  handle->Dispose();
-}
-
-// ---------------------------------------------------------------------------
-
-const std::string JSSpatialReference::_objectType = "JSSpatialReference";
-
-v8::Handle<v8::Object>
-JSSpatialReference::WrapSpatialReference(v8::Isolate* isolate, osgEarth::SpatialReference* srs, bool freeObject)
-{
-  v8::HandleScope handle_scope(isolate);
-
-  v8::Handle<v8::Object> obj = V8Util::WrapObject(isolate, srs, GetObjectTemplate(isolate));
-
-  if (freeObject)
-  {
-    v8::Persistent<v8::Object> weakRef;
-    weakRef.Reset(v8::Isolate::GetCurrent(), obj);
-    weakRef.MakeWeak(srs, &FreeSpatialReferenceCallback);
-  }
-
-  return handle_scope.Close(obj);
-}
-
-v8::Handle<v8::ObjectTemplate>
-JSSpatialReference::GetObjectTemplate(v8::Isolate* isolate)
-{
-  v8::HandleScope handle_scope(isolate);
-
-  v8::Handle<v8::ObjectTemplate> template_instance = v8::ObjectTemplate::New();
-
-  template_instance->Set(v8::String::New(V8_OBJECT_TYPE_PROPERTY), v8::String::New(GetObjectType().c_str()));
-  template_instance->SetInternalFieldCount(1);
-  template_instance->SetNamedPropertyHandler(PropertyCallback);
-
-  template_instance->Set(v8::String::New("isEquivalentTo"), v8::FunctionTemplate::New(EquivalenceCallback));
-  template_instance->Set(v8::String::New("createTangentPlaneSRS"), v8::FunctionTemplate::New(TangentPlaneCallback));
-  //template_instance->Set(v8::String::New("createTransMercFromLongitude"), v8::FunctionTemplate::New(equivalenceCallback));
-  //template_instance->Set(v8::String::New("createUTMFromLongitude"), v8::FunctionTemplate::New(equivalenceCallback));
-
-  return handle_scope.Close(template_instance);
-}
-
-void
-JSSpatialReference::PropertyCallback(v8::Local<v8::String> name, const v8::PropertyCallbackInfo<v8::Value>& info)
-{
-  osgEarth::SpatialReference* srs = V8Util::UnwrapObject<osgEarth::SpatialReference>(info.Holder());
-
-  v8::String::Utf8Value utf8_value(name);
-  std::string prop(*utf8_value);
-  
-  if (!srs || prop.empty())
-    return;
-
-  v8::Local<v8::Value> value;
-
-  if (prop == "geographic")
-    value = v8::Boolean::New(srs->isGeographic());
-  if (prop == "projected")
-    value = v8::Boolean::New(srs->isProjected());
-  if (prop == "mercator")
-    value = v8::Boolean::New(srs->isMercator());
-  if (prop == "sphericalMercator")
-    value = v8::Boolean::New(srs->isSphericalMercator());
-  if (prop == "northPolar")
-    value = v8::Boolean::New(srs->isNorthPolar());
-  if (prop == "southPolar")
-    value = v8::Boolean::New(srs->isSouthPolar());
-  if (prop == "userDefined")
-    value = v8::Boolean::New(srs->isUserDefined());
-  if (prop == "contiguous")
-    value = v8::Boolean::New(srs->isContiguous());
-  if (prop == "cube")
-    value = v8::Boolean::New(srs->isCube());
-  if (prop == "LTP" || prop == "ltp")
-    value = v8::Boolean::New(srs->isLTP());
-  if (prop == "name")
-    value = v8::String::New(srs->getName().c_str());
-  if (prop == "WKT" || prop == "wkt")
-    value = v8::String::New(srs->getWKT().c_str());
-  if (prop == "initType")
-    value = v8::String::New(srs->getInitType().c_str());
-  if (prop == "horizInitString")
-    value = v8::String::New(srs->getHorizInitString().c_str());
-  if (prop == "vertInitString")
-    value = v8::String::New(srs->getVertInitString().c_str());
-  if (prop == "datumName")
-    value = v8::String::New(srs->getDatumName().c_str());
-  if (prop == "geographicSRS")
-    value = JSSpatialReference::WrapSpatialReference(v8::Isolate::GetCurrent(), const_cast<osgEarth::SpatialReference*>(srs->getGeographicSRS()));
-
-  if (!value.IsEmpty())
-    info.GetReturnValue().Set(value);
-}
-
-void
-JSSpatialReference::EquivalenceCallback(const v8::FunctionCallbackInfo<v8::Value>& info)
-{
-  osgEarth::SpatialReference* srs = V8Util::UnwrapObject<osgEarth::SpatialReference>(info.Holder());
-
-  if (!srs)
-    return;
-
-  v8::Local<v8::Value> value;
-
-  if (info.Length() == 1 && info[0]->IsObject())  // SpatialReference
-  {
-    v8::Local<v8::Object> obj( v8::Local<v8::Object>::Cast(info[0]) );
-
-    if (V8Util::CheckObjectType(obj, JSSpatialReference::GetObjectType()))
-    {
-      osgEarth::SpatialReference* rhs = V8Util::UnwrapObject<osgEarth::SpatialReference>(obj);
-      value = v8::Boolean::New(srs->isEquivalentTo(rhs));
-    }
-  }
-
-  if (!value.IsEmpty())
-    info.GetReturnValue().Set(value);
-}
-
-void
-JSSpatialReference::TangentPlaneCallback(const v8::FunctionCallbackInfo<v8::Value>& info)
-{
-  osgEarth::SpatialReference* srs = V8Util::UnwrapObject<osgEarth::SpatialReference>(info.Holder());
-
-  if (!srs)
-    return;
-
-  v8::Local<v8::Value> value;
-
-  if (info.Length() == 1 && info[0]->IsObject())  // Vec3d
-  {
-    v8::Local<v8::Object> obj( v8::Local<v8::Object>::Cast(info[0]) );
-
-    if (V8Util::CheckObjectType(obj, JSVec3d::GetObjectType()))
-    {
-      osg::Vec3d* vec = V8Util::UnwrapObject<osg::Vec3d>(obj);
-      value = JSSpatialReference::WrapSpatialReference(v8::Isolate::GetCurrent(), const_cast<SpatialReference*>(srs->createTangentPlaneSRS(*vec)), true);
-    }
-  }
-
-  if (!value.IsEmpty())
-    info.GetReturnValue().Set(value);
-}
-
-void
-JSSpatialReference::FreeSpatialReferenceCallback(v8::Isolate* isolate, v8::Persistent<v8::Object>* handle, osgEarth::SpatialReference* parameter)
-{
-  osg::ref_ptr<osgEarth::SpatialReference> srs = parameter;
-  handle->Dispose();
-}
diff --git a/src/osgEarthDrivers/script_engine_v8/JavascriptEngineV8 b/src/osgEarthDrivers/script_engine_v8/JavascriptEngineV8
deleted file mode 100644
index e7adb08..0000000
--- a/src/osgEarthDrivers/script_engine_v8/JavascriptEngineV8
+++ /dev/null
@@ -1,74 +0,0 @@
-/* -*-c++-*- */
-/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
- * http://osgearth.org
- *
- * osgEarth is free software; you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>
- */
-
-#ifndef OSGEARTHDRIVERS_JAVASCRIPT_ENGINE_V8_H
-#define OSGEARTHDRIVERS_JAVASCRIPT_ENGINE_V8_H 1
-
-#include <osgEarth/StringUtils>
-#include <osgEarthFeatures/Feature>
-#include <osgEarthFeatures/Script>
-#include <osgEarthFeatures/ScriptEngine>
-
-#include <v8.h>
-
-//namespace osgEarth { namespace Drivers { namespace JavascriptV8
-//{
-
-  using namespace osgEarth::Features;
-
-  class JavascriptEngineV8 : public ScriptEngine
-  {
-  public:
-    JavascriptEngineV8(const ScriptEngineOptions& options =ScriptEngineOptions());
-    virtual ~JavascriptEngineV8();
-
-    bool supported(std::string lang) { return osgEarth::toLower(lang).compare("javascript") == 0; }
-    bool supported(Script* script) { return script && supported(script->getLanguage()); }
-
-    ScriptResult run(Script* script, osgEarth::Features::Feature const* feature=0L, osgEarth::Features::FilterContext const* context=0L);
-    ScriptResult run(const std::string& code, osgEarth::Features::Feature const* feature=0L, osgEarth::Features::FilterContext const* context=0L);
-
-    ScriptResult call(const std::string& function, osgEarth::Features::Feature const* feature=0L, osgEarth::Features::FilterContext const* context=0L);
-
-  protected:
-    static void logCallback(const v8::FunctionCallbackInfo<v8::Value>& info);
-    //static void constructFeatureCallback(const v8::FunctionCallbackInfo<v8::Value> &info);
-    static void constructBoundsCallback(const v8::FunctionCallbackInfo<v8::Value> &info);
-    static void constructVec3dCallback(const v8::FunctionCallbackInfo<v8::Value> &info);
-    static void constructGeoExtentCallback(const v8::FunctionCallbackInfo<v8::Value> &info);
-
-    static void constructSpatialReferenceCallback(const v8::FunctionCallbackInfo<v8::Value> &info);
-    //static void constructSymbologyGeometryCallback(const v8::FunctionCallbackInfo<v8::Value> &info);
-
-    v8::Handle<v8::Context> createGlobalContext();
-
-    /** Compiles and runs javascript in the current context. */
-    ScriptResult executeScript(v8::Handle<v8::String> script, bool ignoreUndefinedResult=false);
-
-  protected:
-    v8::Persistent<v8::Context> _globalContext;
-    v8::Isolate* _isolate;
-  private:
-    ScriptResult createErrorResult( std::string prefix, const v8::TryCatch& try_catch );
-
-  };
-
-//} } } // namespace osgEarth::Drivers::JavascriptV8
-
-#endif // OSGEARTHDRIVERS_JAVASCRIPT_ENGINE_V8_H
diff --git a/src/osgEarthDrivers/script_engine_v8/JavascriptEngineV8.cpp b/src/osgEarthDrivers/script_engine_v8/JavascriptEngineV8.cpp
deleted file mode 100644
index a37ffd8..0000000
--- a/src/osgEarthDrivers/script_engine_v8/JavascriptEngineV8.cpp
+++ /dev/null
@@ -1,474 +0,0 @@
-/* -*-c++-*- */
-/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
- * http://osgearth.org
- *
- * osgEarth is free software; you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>
- */
-
-#include <osgEarthDrivers/script_engine_v8/JavascriptEngineV8>
-#include <osgEarthDrivers/script_engine_v8/JSWrappers>
-#include <osgEarthDrivers/script_engine_v8/V8Util>
-
-#include <osgEarthFeatures/Script>
-#include <osgEarthFeatures/ScriptEngine>
-#include <osgEarth/Notify>
-#include <osgEarth/StringUtils>
-
-#include <v8.h>
-
-using namespace osgEarth;
-using namespace osgEarth::Features;
-
-#define LC "[JavascriptEngineV8] "
-
-
-//----------------------------------------------------------------------------
-
-JavascriptEngineV8::JavascriptEngineV8(const ScriptEngineOptions& options)
-: ScriptEngine(options)
-{
-  _isolate = v8::Isolate::New();
-
-  v8::Locker locker(_isolate);
-  v8::Isolate::Scope isolate_scope(_isolate);
-
-  v8::HandleScope handle_scope(_isolate);
-
-  _globalContext.Reset(_isolate, createGlobalContext());
-  if (options.script().isSet() && !options.script()->getCode().empty())
-  {
-    // Create a nested handle scope
-    v8::HandleScope local_handle_scope(_isolate);
-
-    // Enter the global context
-    v8::Local<v8::Context> globalContext = *reinterpret_cast<v8::Local<v8::Context>*>(&_globalContext);
-    v8::Context::Scope context_scope(globalContext);
-
-    // Compile and run the script
-    ScriptResult result = executeScript(v8::String::New(options.script()->getCode().c_str(), options.script()->getCode().length()), true);
-    if (!result.success())
-      OE_WARN << LC << "Error reading javascript: " << result.message() << std::endl;
-  }
-}
-
-JavascriptEngineV8::~JavascriptEngineV8()
-{
-  {
-    v8::Locker locker(_isolate);
-    v8::Isolate::Scope isolate_scope(_isolate);
-
-    _globalContext.Dispose();
-  }
-
-  _isolate->Dispose();
-}
-
-v8::Handle<v8::Context>
-JavascriptEngineV8::createGlobalContext()
-{
-  v8::Handle<v8::ObjectTemplate> global = v8::ObjectTemplate::New();
-
-  // add callback for global logging
-  global->Set(v8::String::New("log"), v8::FunctionTemplate::New(logCallback));
-
-  // add constructor callbacks for native objects
-  //global->Set(v8::String::New("Feature"), v8::FunctionTemplate::New(constructFeatureCallback));
-  global->Set(v8::String::New("Bounds"), v8::FunctionTemplate::New(constructBoundsCallback));
-  global->Set(v8::String::New("Vec3d"), v8::FunctionTemplate::New(constructVec3dCallback));
-  global->Set(v8::String::New("GeoExtent"), v8::FunctionTemplate::New(constructGeoExtentCallback));
-  global->Set(v8::String::New("SpatialReference"), v8::FunctionTemplate::New(constructSpatialReferenceCallback));
-  //global->Set(v8::String::New("Geometry"), v8::FunctionTemplate::New(constructSymbologyGeometryCallback));
-  
-  return v8::Context::New(_isolate, NULL, global);
-}
-
-void
-JavascriptEngineV8::logCallback(const v8::FunctionCallbackInfo<v8::Value>& info)
-{
-  if (info.Length() < 1) return;
-  v8::HandleScope scope(v8::Isolate::GetCurrent());
-  v8::Handle<v8::Value> arg = info[0];
-  v8::String::AsciiValue value(arg);
-  
-  OE_WARN << LC << "javascript message: " << (*value) << std::endl;
-}
-
-ScriptResult 
-JavascriptEngineV8::createErrorResult( std::string prefix, const v8::TryCatch& try_catch )
-{
-    std::ostringstream str;
-
-    v8::String::AsciiValue error( try_catch.Exception() );
-    v8::Handle<v8::Message> message = try_catch.Message();
-
-    str << prefix << ": ";
-
-    if( !message.IsEmpty() ) {
-      //v8::String::AsciiValue filename(message->GetScriptResourceName());
-      int linenum = message->GetLineNumber();
-      str << "line " << linenum << " cols [" << message->GetStartColumn() << "-" << message->GetEndColumn() << "] " << std::string( *error ) << std::endl;
-      v8::String::AsciiValue sourceline( message->GetSourceLine() );
-      str << std::string( *sourceline ) << std::endl;
-      /*
-      v8::String::Utf8Value stack_trace(try_catch.StackTrace());
-      if (stack_trace.length() > 0) {
-      str <<  std::string(*stack_trace) << std::endl;
-      }
-      */
-    }
-    else {
-      str << std::string( *error );
-    }
-    return ScriptResult( EMPTY_STRING, false, str.str() );
-
-}
-ScriptResult
-JavascriptEngineV8::executeScript(v8::Handle<v8::String> script, bool ignoreUndefinedResult)
-{
-  v8::String::Utf8Value utf8_value(script);
-  std::string scriptStr(*utf8_value);
-
-  // Handle scope for temporary handles.
-  v8::HandleScope handle_scope(_isolate);
-
-  // TryCatch for any script errors
-  v8::TryCatch try_catch;
-
-  // Compile the script
-  v8::Handle<v8::Script> compiled_script = v8::Script::Compile(script);
-  if (compiled_script.IsEmpty())
-  {
-    return createErrorResult( "Script compile error", try_catch );
-  }
-
-  // Run the script
-  v8::Handle<v8::Value> result = compiled_script->Run();
-  if (result.IsEmpty())
-  {
-    return createErrorResult( "Script result was empty", try_catch );
-  }
-  
-  if (result->IsUndefined() && !ignoreUndefinedResult)
-    return ScriptResult(EMPTY_STRING, false, "Script result was undefined");
-
-  v8::String::AsciiValue ascii(result);
-  return ScriptResult(std::string(*ascii));
-}
-
-ScriptResult
-JavascriptEngineV8::run(Script* script, osgEarth::Features::Feature const* feature, osgEarth::Features::FilterContext const* context)
-{
-  if (!script)
-    return ScriptResult(EMPTY_STRING, false, "Script is null.");
-
-  return run(script->getCode(), feature, context);
-}
-
-ScriptResult
-JavascriptEngineV8::run(const std::string& code, osgEarth::Features::Feature const* feature, osgEarth::Features::FilterContext const* context)
-{
-  if (code.empty())
-    return ScriptResult(EMPTY_STRING, false, "Script is empty.");
-
-  v8::Locker locker(_isolate);
-  v8::Isolate::Scope isolate_scope(_isolate);
-
-  v8::HandleScope handle_scope(_isolate);
-
-  v8::Local<v8::Context> globalContext = *reinterpret_cast<v8::Local<v8::Context>*>(&_globalContext);
-
-  v8::Context::Scope context_scope(globalContext);
-
-  if (feature)
-  {
-    v8::Handle<v8::Object> fObj = JSFeature::WrapFeature(_isolate, const_cast<Feature*>(feature));
-    if (!fObj.IsEmpty())
-      globalContext->Global()->Set(v8::String::New("feature"), fObj);
-  }
-
-  if (context)
-  {
-    v8::Handle<v8::Object> cObj = JSFilterContext::WrapFilterContext(_isolate, const_cast<FilterContext*>(context));
-    if (!cObj.IsEmpty())
-      globalContext->Global()->Set(v8::String::New("context"), cObj);
-  }
-
-  // Compile and run the script
-  ScriptResult result = executeScript(v8::String::New(code.c_str(), code.length()));
-
-
-  return result;
-}
-
-ScriptResult
-JavascriptEngineV8::call(const std::string& function, osgEarth::Features::Feature const* feature, osgEarth::Features::FilterContext const* context)
-{
-  if (function.empty())
-    return ScriptResult(EMPTY_STRING, false, "Empty function name parameter.");
-
-  // Lock for V8 multithreaded uses
-  v8::Locker locker(_isolate);
-  v8::Isolate::Scope isolate_scope(_isolate);
-
-  v8::HandleScope handle_scope(_isolate);
-
-  v8::Local<v8::Context> globalContext = *reinterpret_cast<v8::Local<v8::Context>*>(&_globalContext);
-
-  v8::Context::Scope context_scope(globalContext);
-
-  // Attempt to fetch the function from the global object.
-  v8::Handle<v8::String> func_name = v8::String::New(function.c_str(), function.length());
-  v8::Handle<v8::Value> func_val = globalContext->Global()->Get(func_name);
-
-  // If there is no function, or if it is not a function, bail out
-  if (!func_val->IsFunction())
-    return ScriptResult(EMPTY_STRING, false, "Function not found in script.");
-
-  v8::Handle<v8::Function> func_func = v8::Handle<v8::Function>::Cast(func_val);
-
-  if (feature)
-  {
-    v8::Handle<v8::Object> fObj = JSFeature::WrapFeature(_isolate, const_cast<Feature*>(feature));
-    if (!fObj.IsEmpty())
-      globalContext->Global()->Set(v8::String::New("feature"), fObj);
-  }
-
-  if (context)
-  {
-    v8::Handle<v8::Object> cObj = JSFilterContext::WrapFilterContext(_isolate, const_cast<FilterContext*>(context));
-    if (!cObj.IsEmpty())
-      globalContext->Global()->Set(v8::String::New("context"), cObj);
-  }
-
-  // Set up an exception handler before calling the Eval function
-  v8::TryCatch try_catch;
-
-  // Invoke the specifed function
-  //const int argc = 1;
-  //v8::Handle<v8::Value> argv[argc] = { fObj };
-  //v8::Handle<v8::Value> result = func_func->Call(_globalContext->Global(), argc, argv);
-  v8::Handle<v8::Value> result = func_func->Call(globalContext->Global(), 0, NULL);
-
-  if (result.IsEmpty())
-  {
-    return ScriptResult(EMPTY_STRING, false, "Function result was empty.");
-  }
-  else
-  {
-    v8::String::AsciiValue ascii(result);
-    return ScriptResult(std::string(*ascii));
-  }
-}
-
-//----------------------------------------------------------------------------
-// Constructor callbacks for constructing native objects in javascript
-
-//void
-//JavascriptEngineV8::constructFeatureCallback(const v8::FunctionCallbackInfo<v8::Value> &info)
-//{
-//  if (!info.IsConstructCall()) 
-//    return v8::ThrowException(v8::String::New("Cannot call constructor as function"));
-// 
-//	v8::HandleScope handle_scope(_isolate);
-// 
-//  Feature* feature;
-//  if (info.Length() == 0)
-//  {
-//    feature = new Feature();
-//  }
-//  else if (info.Length() == 1 && info[0]->IsUint32())
-//  {
-//    feature = new Feature(info[0]->Uint32Value());
-//  }
-//  else
-//  {
-//    //TODO: add support for other Feature constructors
-//  }
-//
-//  if (feature)
-//    return JSFeature::WrapFeature(feature, true);
-//
-//  return v8::ThrowException(v8::String::New("Unsupported arguments in constructor call"));
-//}
-
-void
-JavascriptEngineV8::constructBoundsCallback(const v8::FunctionCallbackInfo<v8::Value> &info)
-{
-  if (!info.IsConstructCall())
-  {
-    v8::ThrowException(v8::String::New("Cannot call constructor as function"));
-    return;
-  }
- 
-	v8::HandleScope handle_scope(v8::Isolate::GetCurrent());
- 
-  osgEarth::Bounds* bounds;
-  //if (info.Length() == 0)
-  //  bounds = new osgEarth::Bounds();
-  //else
-  if (info.Length() == 4)
-    bounds = new osgEarth::Bounds(info[0]->NumberValue(), info[1]->NumberValue(), info[2]->NumberValue(), info[3]->NumberValue());
-
-  if (!bounds)
-  {
-    v8::ThrowException(v8::String::New("Unsupported arguments in constructor call"));
-    return;
-  }
-
-  info.GetReturnValue().Set(JSBounds::WrapBounds(v8::Isolate::GetCurrent(), bounds, true));
-}
-
-void
-JavascriptEngineV8::constructVec3dCallback(const v8::FunctionCallbackInfo<v8::Value> &info)
-{
-  if (!info.IsConstructCall()) 
-  {
-    v8::ThrowException(v8::String::New("Cannot call constructor as function"));
-    return;
-  }
- 
-  v8::HandleScope handle_scope(v8::Isolate::GetCurrent());
-
-  osg::Vec3d* vec;
-  if (info.Length() == 0)
-    vec = new osg::Vec3d();
-  else if (info.Length() == 1 && info[0]->IsObject())
-  {
-    v8::Local<v8::Object> obj( v8::Local<v8::Object>::Cast(info[0]) );
-
-    if (V8Util::CheckObjectType(obj, JSVec3d::GetObjectType()))
-    {
-      osg::Vec3d* rhs = V8Util::UnwrapObject<osg::Vec3d>(obj);
-      vec = new osg::Vec3d(*rhs);
-    }
-  }
-  else if (info.Length() == 3)
-    vec = new osg::Vec3d(info[0]->NumberValue(), info[1]->NumberValue(), info[2]->NumberValue());
-
-  if (!vec)
-  {
-    v8::ThrowException(v8::String::New("Unsupported arguments in constructor call"));
-    return;
-  }
-  
-  info.GetReturnValue().Set(JSVec3d::WrapVec3d(v8::Isolate::GetCurrent(), vec, true));
-
-}
-
-void
-JavascriptEngineV8::constructGeoExtentCallback(const v8::FunctionCallbackInfo<v8::Value> &info)
-{
-  if (!info.IsConstructCall())
-  {
-    v8::ThrowException(v8::String::New("Cannot call constructor as function"));
-    return;
-  }
-
-  v8::HandleScope handle_scope(v8::Isolate::GetCurrent());
-  osgEarth::GeoExtent* extent = 0L;// = new osgEarth::GeoExtent(
-
-  //if (info.Length() == 0)
-  //  extent = new osgEarth::GeoExtent();
-  //else
-  if (info.Length() == 1 && info[0]->IsObject())
-  {
-    v8::Local<v8::Object> obj( v8::Local<v8::Object>::Cast(info[0]) );
-
-    //if (V8Util::CheckObjectType(obj, JSSpatialReference::GetObjectType()))
-    //{
-    //  osgEarth::SpatialReference* srs = V8Util::UnwrapObject<osgEarth::SpatialReference>(obj);
-    //  extent = new osgEarth::GeoExtent(srs);
-    //}
-    //else
-    if (V8Util::CheckObjectType(obj, JSGeoExtent::GetObjectType()))
-    {
-      osgEarth::GeoExtent* rhs = V8Util::UnwrapObject<osgEarth::GeoExtent>(obj);
-      extent = new osgEarth::GeoExtent(*rhs);
-    }
-  }
-  else if (info.Length() == 2 && info[0]->IsObject() && info[1]->IsObject())
-  {
-    v8::Local<v8::Object> obj0( v8::Local<v8::Object>::Cast(info[0]) );
-    v8::Local<v8::Object> obj1( v8::Local<v8::Object>::Cast(info[1]) );
-
-    if (V8Util::CheckObjectType(obj0, JSSpatialReference::GetObjectType()) && V8Util::CheckObjectType(obj1, JSBounds::GetObjectType()))
-    {
-      osgEarth::SpatialReference* srs = V8Util::UnwrapObject<osgEarth::SpatialReference>(obj0);
-      osgEarth::Bounds* bounds = V8Util::UnwrapObject<osgEarth::Bounds>(obj1);
-      extent = new osgEarth::GeoExtent(srs, *bounds);
-    }
-  }
-  else if (info.Length() == 5 && info[0]->IsObject())
-  {
-    v8::Local<v8::Object> obj( v8::Local<v8::Object>::Cast(info[0]) );
-
-    if (V8Util::CheckObjectType(obj, JSSpatialReference::GetObjectType()))
-    {
-      osgEarth::SpatialReference* srs = V8Util::UnwrapObject<osgEarth::SpatialReference>(obj);
-      extent = new osgEarth::GeoExtent(srs, info[1]->NumberValue(), info[2]->NumberValue(), info[3]->NumberValue(), info[4]->NumberValue());
-    }
-  }
-
-  if (!extent)
-  {
-    v8::ThrowException(v8::String::New("Unsupported arguments in constructor call"));
-    return;
-  }
-
-  info.GetReturnValue().Set(JSGeoExtent::WrapGeoExtent(v8::Isolate::GetCurrent(), extent, true));
-}
-
-void
-JavascriptEngineV8::constructSpatialReferenceCallback(const v8::FunctionCallbackInfo<v8::Value> &info)
-{
-  if (!info.IsConstructCall())
-  {
-    v8::ThrowException(v8::String::New("Cannot call constructor as function"));
-    return;
-  }
-
-	v8::HandleScope handle_scope(v8::Isolate::GetCurrent());
- 
-  osgEarth::SpatialReference* srs;
-  if (info.Length() == 1 && info[0]->IsString())
-  {
-    v8::String::Utf8Value utf8_value(info[0]->ToString());
-    std::string init(*utf8_value);
-    srs = osgEarth::SpatialReference::create(init);
-  }
-
-  if (!srs)
-  {
-    v8::ThrowException(v8::String::New("Unsupported arguments in constructor call"));
-    return;
-  }
-
-  info.GetReturnValue().Set(JSSpatialReference::WrapSpatialReference(v8::Isolate::GetCurrent(), srs, true));
-}
-
-//void
-//JavascriptEngineV8::constructSymbologyGeometryCallback(const v8::FunctionCallbackInfo<v8::Value> &info)
-//{
-//	v8::HandleScope handle_scope(_isolate);
-// 
-//  osgEarth::Symbology::Geometry* geom;
-//  if (info.Length() == 2)
-//    geom = new osgEarth::Symbology::Geometry::create(
-//
-//  if (geom)
-//    info.GetReturnValue().Set(JSBounds::WrapBounds(bounds, true));
-//
-//  //return v8::ThrowException(v8::String::New("Unsupported arguments"));
-//}
\ No newline at end of file
diff --git a/src/osgEarthDrivers/script_engine_v8/JavascriptEngineV8Factory.cpp b/src/osgEarthDrivers/script_engine_v8/JavascriptEngineV8Factory.cpp
deleted file mode 100644
index 3088009..0000000
--- a/src/osgEarthDrivers/script_engine_v8/JavascriptEngineV8Factory.cpp
+++ /dev/null
@@ -1,50 +0,0 @@
-/* -*-c++-*- */
-/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
- * http://osgearth.org
- *
- * osgEarth is free software; you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>
- */
-#include <osgDB/ReaderWriter>
-#include <osgEarthDrivers/script_engine_v8/JavascriptEngineV8>
-#include <osgEarthFeatures/ScriptEngine>
-#include <osgEarth/Common>
-#include <osgEarth/Config>
-#include <osgDB/FileNameUtils>
-
-
-class JavascriptEngineV8Factory : public osgEarth::Features::ScriptEngineDriver
-{
-public:
-    JavascriptEngineV8Factory()
-    {
-        supportsExtension( "osgearth_scriptengine_javascript", "osgEarth scriptengine javascript plugin" );
-        supportsExtension( "osgearth_scriptengine_javascript_v8", "osgEarth scriptengine javascript V8 plugin" );
-    }
-
-    virtual const char* className()
-    {
-        return "osgEarth ScriptEngine Javascript V8 Plugin";
-    }
-
-    virtual ReadResult readObject(const std::string& file_name, const osgDB::ReaderWriter::Options* options) const
-    {
-      if ( !acceptsExtension(osgDB::getLowerCaseFileExtension( file_name )) )
-            return osgDB::ReaderWriter::ReadResult::FILE_NOT_HANDLED;
-
-        return osgDB::ReaderWriter::ReadResult( new JavascriptEngineV8( getScriptEngineOptions(options) ) );
-    }
-};
-
-REGISTER_OSGPLUGIN(osgearth_scriptengine_javascript, JavascriptEngineV8Factory)
diff --git a/src/osgEarthDrivers/script_engine_v8/V8Util b/src/osgEarthDrivers/script_engine_v8/V8Util
deleted file mode 100644
index 4a1f633..0000000
--- a/src/osgEarthDrivers/script_engine_v8/V8Util
+++ /dev/null
@@ -1,79 +0,0 @@
-/* -*-c++-*- */
-/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
- * http://osgearth.org
- *
- * osgEarth is free software; you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>
- */
-
-#ifndef OSGEARTH_DRIVER_V8_UTIL_H
-#define OSGEARTH_DRIVER_V8_UTIL_H 1
-
-#include <v8.h>
-#include <iostream>
-
-#define V8_OBJECT_TYPE_PROPERTY "__object_type"
-
-class V8Util
-{
-public:
-  typedef std::map< std::string, v8::Handle<v8::Value> > V8PropertyMap;
-
-  template<typename T>
-  static v8::Handle<v8::Object> WrapObject(v8::Isolate* isolate, T* obj,  v8::Handle<v8::ObjectTemplate> templ)
-  {
-    return WrapObject(isolate, obj, templ, V8PropertyMap());
-  }
-
-  template<typename T>
-  static v8::Handle<v8::Object> WrapObject(v8::Isolate* isolate, T* obj,  v8::Handle<v8::ObjectTemplate> templ, const V8PropertyMap& props)
-  {
-    v8::HandleScope handle_scope(isolate);
-
-    v8::Handle<v8::Object> result = templ->NewInstance();
-
-    v8::Handle<v8::External> obj_ptr = v8::External::New(obj);
-    result->SetInternalField(0, obj_ptr);
-
-    for (V8PropertyMap::const_iterator it = props.begin(); it != props.end(); ++it)
-      result->Set(v8::String::New(it->first.c_str(), it->first.length()), it->second);
-
-    return handle_scope.Close(result);
-  }
-
-  template<typename T>
-  static T* UnwrapObject(v8::Handle<v8::Object> obj)
-  {
-    v8::Handle<v8::External> field = v8::Handle<v8::External>::Cast(obj->GetInternalField(0));
-    void* ptr = field->Value();
-    return static_cast<T*>(ptr);
-  }
-
-  static bool CheckObjectType(v8::Handle<v8::Object> obj, const std::string& objType)
-  {
-    v8::Local<v8::Value> typeVal = obj->Get(v8::String::New(V8_OBJECT_TYPE_PROPERTY));
-    if (!typeVal.IsEmpty())
-    {
-      v8::String::Utf8Value utf8_value(typeVal);
-      std::string typeStr(*utf8_value);
-
-      if (typeStr == objType)
-        return true;
-    }
-
-    return false;
-  }
-};
-
-#endif // OSGEARTH_DRIVER_V8_UTIL_H
diff --git a/src/osgEarthDrivers/sky_gl/CMakeLists.txt b/src/osgEarthDrivers/sky_gl/CMakeLists.txt
index ea14c74..ef4b6cc 100644
--- a/src/osgEarthDrivers/sky_gl/CMakeLists.txt
+++ b/src/osgEarthDrivers/sky_gl/CMakeLists.txt
@@ -6,7 +6,6 @@ SET(TARGET_SRC
 SET(TARGET_H
     GLSkyOptions
 	GLSkyNode
-    GLSkyShaders
 )
 
 SET(TARGET_COMMON_LIBRARIES ${TARGET_COMMON_LIBRARIES}
diff --git a/src/osgEarthDrivers/sky_gl/GLSkyExtension.cpp b/src/osgEarthDrivers/sky_gl/GLSkyExtension.cpp
index a607847..3900084 100644
--- a/src/osgEarthDrivers/sky_gl/GLSkyExtension.cpp
+++ b/src/osgEarthDrivers/sky_gl/GLSkyExtension.cpp
@@ -23,6 +23,7 @@
 #include <osgDB/FileNameUtils>
 #include <osgEarth/Map>
 #include <osgEarth/MapNode>
+#include <osgEarth/NodeUtils>
 #include <osgEarthUtil/Sky>
 #include <osgEarthUtil/Controls>
 #include <osgEarthUtil/ExampleResources>
@@ -61,7 +62,7 @@ namespace osgEarth { namespace GLSky
     public: // ExtensionInterface<ui::Control>
 
         bool connect( ui::Control* );
-        bool disconnect( ui::Control* ) { return true; }
+        bool disconnect( ui::Control* );
 
     public: // SkyNodeFactory
 
@@ -71,6 +72,7 @@ namespace osgEarth { namespace GLSky
         GLSkyExtension(const GLSkyExtension&, const osg::CopyOp&) { }
         virtual ~GLSkyExtension() { }
 
+        osg::ref_ptr<ui::Control> _ui;
         osg::ref_ptr<SkyNode> _skyNode;
     };
 
@@ -94,36 +96,16 @@ GLSkyOptions(options)
 bool
 GLSkyExtension::connect(MapNode* mapNode)
 {
-    OE_INFO << LC << "Hello world.\n";
-    
-    // find the tip top of the tree that MapNode is in:
-    osg::Node* top = mapNode;
-    while (top->getNumParents() > 0 && std::string(top->getParent(0)->className()) != "Camera")
-        top = top->getParent(0);
-
-    osg::Group* topParent = top->getNumParents() > 0 ? top->getParent(0) : 0L;
-
-    // make the sky node
-    if ( !_skyNode.valid() )
-    {
-        _skyNode = createSkyNode( mapNode->getMap()->getProfile() );
-    }
-     
-    // insert the new sky node at the top of the tree.
-    _skyNode->addChild( top );
-
-    if ( topParent )
-    {
-        topParent->addChild( _skyNode.get() );
-        topParent->removeChild( top );
-    }
-
+    _skyNode = createSkyNode(mapNode->getMap()->getProfile());
+    osgEarth::insertParent(_skyNode.get(), mapNode);
     return true;
 }
 
 bool
 GLSkyExtension::disconnect(MapNode* mapNode)
 {
+    osgEarth::removeGroup(_skyNode.get());
+    _skyNode = 0L;
     return true;
 }
 
@@ -146,6 +128,15 @@ GLSkyExtension::connect(ui::Control* control)
     return true;
 }
 
+bool
+GLSkyExtension::disconnect(ui::Control* control)
+{
+    ui::Container* container = dynamic_cast<ui::Container*>(control);
+    if (container && _ui.valid())
+        container->removeChild(_ui.get());
+    return true;
+}
+
 SkyNode*
 GLSkyExtension::createSkyNode(const Profile* profile)
 {
diff --git a/src/osgEarthDrivers/sky_gl/GLSkyNode b/src/osgEarthDrivers/sky_gl/GLSkyNode
index 68e5430..2b180fb 100644
--- a/src/osgEarthDrivers/sky_gl/GLSkyNode
+++ b/src/osgEarthDrivers/sky_gl/GLSkyNode
@@ -49,14 +49,13 @@ namespace osgEarth { namespace GLSky
 
     public: // SkyNode
 
-        osg::Light* getSunLight() { return _light.get(); }
+        osg::Light* getSunLight() const { return _light.get(); }
 
         void attach(osg::View* view, int lightNum);
 
         void onSetEphemeris();
         void onSetDateTime();
         void onSetReferencePoint();
-        void onSetMinimumAmbient();
 
     protected:
         virtual ~GLSkyNode();
diff --git a/src/osgEarthDrivers/sky_gl/GLSkyNode.cpp b/src/osgEarthDrivers/sky_gl/GLSkyNode.cpp
index 9c434a5..8586b29 100644
--- a/src/osgEarthDrivers/sky_gl/GLSkyNode.cpp
+++ b/src/osgEarthDrivers/sky_gl/GLSkyNode.cpp
@@ -21,13 +21,14 @@
 */
 
 #include "GLSkyNode"
-#include "GLSkyShaders"
-#include <osgEarthUtil/Ephemeris>
+#include <osg/LightSource>
 
 #include <osgEarth/VirtualProgram>
 #include <osgEarth/SpatialReference>
 #include <osgEarth/GeoData>
+#include <osgEarth/Lighting>
 #include <osgEarth/PhongLightingEffect>
+#include <osgEarthUtil/Ephemeris>
 
 #define LC "[GLSkyNode] "
 
@@ -55,11 +56,13 @@ void
 GLSkyNode::initialize(const Profile* profile)
 {
     _profile = profile;
-    _light = new osg::Light(0);
+    //_light = new osg::Light(0);
+    _light = new LightGL3(0);
+    _light->setDataVariance(_light->DYNAMIC);
     _light->setAmbient(osg::Vec4(0.1f, 0.1f, 0.1f, 1.0f));
     _light->setDiffuse(osg::Vec4(1.0f, 1.0f, 1.0f, 1.0f));
     _light->setSpecular(osg::Vec4(1.0f, 1.0f, 1.0f, 1.0f));
-    
+
     if ( _options.ambient().isSet() )
     {
         float a = osg::clampBetween(_options.ambient().get(), 0.0f, 1.0f);
@@ -70,9 +73,16 @@ GLSkyNode::initialize(const Profile* profile)
     osg::StateSet* stateset = this->getOrCreateStateSet();
 
     _lighting = new PhongLightingEffect();
-    _lighting->setCreateLightingUniform( false );
+    //_lighting->setCreateLightingUniform( false );
     _lighting->attach( stateset );
 
+    // install the Sun as a lightsource.
+    osg::LightSource* lightSource = new osg::LightSource();
+    lightSource->setLight(_light.get());
+    lightSource->setCullingActive(false);
+    this->addChild( lightSource );
+    lightSource->addCullCallback(new LightSourceGL3UniformGenerator());
+
     onSetDateTime();
 }
 
@@ -107,7 +117,7 @@ GLSkyNode::onSetDateTime()
     if ( _profile->getSRS()->isGeographic() )
     {
         sunPosECEF.normalize();
-        getSunLight()->setPosition( osg::Vec4(sunPosECEF, 0.0) );
+        _light->setPosition(osg::Vec4(sunPosECEF, 0.0)); // directional light
     }
     else
     {
@@ -137,20 +147,18 @@ GLSkyNode::onSetDateTime()
 }
 
 void
-GLSkyNode::onSetMinimumAmbient()
-{
-    // GLSky doesn't adjust the ambient lighting automatically, so just set it.
-    _light->setAmbient( getMinimumAmbient() );
-}
-
-void
 GLSkyNode::attach( osg::View* view, int lightNum )
 {
     if ( !view ) return;
 
     _light->setLightNum( lightNum );
-    view->setLight( _light.get() );
-    view->setLightingMode( osg::View::SKY_LIGHT );
+    
+    // install the light in the view (so other modules can access it, like shadowing)
+    view->setLight(_light.get());
+
+    // Tell the view not to automatically include a light.
+    view->setLightingMode( osg::View::NO_LIGHT );
 
+    // initial date/time setup.
     onSetDateTime();
 }
diff --git a/src/osgEarthDrivers/sky_gl/GLSkyShaders b/src/osgEarthDrivers/sky_gl/GLSkyShaders
deleted file mode 100644
index 6e94cab..0000000
--- a/src/osgEarthDrivers/sky_gl/GLSkyShaders
+++ /dev/null
@@ -1,130 +0,0 @@
-/* -*-c++-*- */
-/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2016 Pelican Mapping
- * http://osgearth.org
- *
- * osgEarth is free software; you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>
- */
-#ifndef OSGEARTH_DRIVER_GL_SKY_SHADERS
-#define OSGEARTH_DRIVER_GL_SKY_SHADERS 1
-
-#include <osgEarth/VirtualProgram>
-
-namespace osgEarth { namespace GLSky
-{
-#ifdef OSG_GLES2_AVAILABLE
-    static const char* Phong_Vertex =
-        "#version " GLSL_VERSION_STR "\n"
-        GLSL_DEFAULT_PRECISION_FLOAT "\n"
-
-        "uniform bool oe_mode_GL_LIGHTING; \n"
-        "varying vec4 oe_lighting_adjustment; \n"
-        "varying vec4 oe_lighting_zero_vec; \n"
-
-        "vec3 vp_Normal; \n"
-
-        "void oe_sky_vertex_main(inout vec4 VertexVIEW) \n"
-        "{ \n"
-        "    if ( oe_mode_GL_LIGHTING == false ) return; \n"
-        "    oe_lighting_adjustment = vec4(1.0); \n"
-        "    vec3 N = vp_Normal; \n"
-        "    float NdotL = dot( N, normalize(gl_LightSource[0].position.xyz) ); \n"
-        "    NdotL = max( 0.0, NdotL ); \n"
-
-        // NOTE: See comment in the fragment shader below for an explanation of
-        //       this oe_zero_vec value.
-        "    oe_lighting_zero_vec = vec4(0.0); \n"
-
-        "    vec4 adj = \n"
-        "        gl_FrontLightProduct[0].ambient + \n"
-        "        gl_FrontLightProduct[0].diffuse * NdotL; \n"
-        "    oe_lighting_adjustment = clamp( adj, 0.0, 1.0 ); \n"
-        "} \n";
-
-    static const char* Phong_Fragment =
-        "#version " GLSL_VERSION_STR "\n"
-        GLSL_DEFAULT_PRECISION_FLOAT "\n"
-        
-        "uniform bool oe_mode_GL_LIGHTING; \n"
-        "varying vec4 oe_lighting_adjustment; \n"
-        "varying vec4 oe_lighting_zero_vec; \n"
-
-        "void oe_sky_fragment_main(inout vec4 color) \n"
-        "{ \n"
-        "    if ( oe_mode_GL_LIGHTING == false ) return; \n"
-        //NOTE: The follow was changed from the single line
-        //      "color *= oe_lighting_adjustment" to the current code to fix
-        //      an issue on iOS devices.  Adding a varying vec4 value set to
-        //      (0.0,0.0,0.0,0.0) to the color should not make a difference,
-        //      but it is part of the solution to the issue we were seeing.
-        //      Without it and the additional lines of code, the globe was
-        //      rendering textureless (just a white surface with lighting).
-        "    float alpha = color.a; \n"
-        "    color = color * oe_lighting_adjustment + oe_lighting_zero_vec; \n"
-        "    color.a = alpha; \n"
-        "} \n";
-
-#else
-
-    static const char* Phong_Vertex =
-        "#version " GLSL_VERSION_STR "\n"
-        GLSL_DEFAULT_PRECISION_FLOAT "\n"
-        
-        "uniform bool oe_mode_GL_LIGHTING; \n"
-        "varying vec3 oe_glsky_vertexView3; \n"
-
-        "void oe_sky_vertex_main(inout vec4 VertexVIEW) \n"
-        "{ \n"
-        "    if ( oe_mode_GL_LIGHTING == false ) return; \n"
-        "    oe_glsky_vertexView3 = VertexVIEW.xyz / VertexVIEW.w; \n"
-        "} \n";
-
-    static const char* Phong_Fragment =
-        "#version " GLSL_VERSION_STR "\n"
-        GLSL_DEFAULT_PRECISION_FLOAT "\n"
-
-        "uniform bool oe_mode_GL_LIGHTING; \n"
-        "varying vec3 oe_glsky_vertexView3; \n"
-        
-        "vec3 vp_Normal; \n"
-
-        "void oe_sky_fragment_main(inout vec4 color) \n"
-        "{ \n"        
-        "    if ( oe_mode_GL_LIGHTING == false ) return; \n"
-
-        "    vec3 L = normalize(gl_LightSource[0].position.xyz); \n"
-        "    vec3 V = normalize(oe_glsky_vertexView3); \n"
-        "    vec3 N = normalize(vp_Normal); \n"
-        "    vec3 R = normalize(-reflect(L,N)); \n"
-
-        "    float NdotL = max(dot(N,L), 0.0); \n"
-
-        "    vec4 ambient = gl_FrontLightProduct[0].ambient; \n"
-        "    vec4 diffuse = clamp(gl_FrontLightProduct[0].diffuse * NdotL, 0.0, 1.0); \n"
-        "    vec4 specular= vec4(0); \n"
-#if 0
-        "    if (NdotL > 0.0) { \n"
-        "        vec3 HV = normalize(L+V); \n"
-        "        float HVdotN = max(dot(HV,N), 0.0); \n"
-        "        specular = gl_FrontLightProduct[0].specular * pow(HVdotN, 16.0); \n"
-        "    } \n"
-#endif
-
-        "    color.rgb = ambient.rgb + diffuse.rgb*color.rgb + specular.rgb; \n"
-        "} \n";
-#endif
-
-} } // namespace osgEarth::Drivers::GLSky
-
-#endif //OSGEARTH_DRIVER_GL_SKY_SHADERS
diff --git a/src/osgEarthDrivers/sky_silverlining/SilverLiningDriver.cpp b/src/osgEarthDrivers/sky_silverlining/SilverLiningDriver.cpp
index 63912d5..75748d2 100644
--- a/src/osgEarthDrivers/sky_silverlining/SilverLiningDriver.cpp
+++ b/src/osgEarthDrivers/sky_silverlining/SilverLiningDriver.cpp
@@ -21,6 +21,7 @@
 #include <osgDB/FileNameUtils>
 #include <osgEarth/Map>
 #include <osgEarth/MapNode>
+#include <osgEarth/NodeUtils>
 #include <osgEarthUtil/Controls>
 #include <osgEarthUtil/ExampleResources>
 
diff --git a/src/osgEarthDrivers/sky_simple/SimpleSky.Atmosphere.frag.glsl b/src/osgEarthDrivers/sky_simple/SimpleSky.Atmosphere.frag.glsl
index 6092654..7b653f3 100644
--- a/src/osgEarthDrivers/sky_simple/SimpleSky.Atmosphere.frag.glsl
+++ b/src/osgEarthDrivers/sky_simple/SimpleSky.Atmosphere.frag.glsl
@@ -11,9 +11,10 @@ uniform float atmos_g;
 uniform float atmos_g2; 
 uniform float atmos_fWeather; 
 
-varying vec3 atmos_v3Direction; 	
-varying vec3 atmos_mieColor; 
-varying vec3 atmos_rayleighColor; 
+in vec3 atmos_v3Direction; 	
+in vec3 atmos_mieColor; 
+in vec3 atmos_rayleighColor; 
+in float atmos_renderFromSpace;
 
 const float fExposure = 4.0; 
 
@@ -27,8 +28,12 @@ void atmos_fragment_main(inout vec4 color)
     float fCos = dot(atmos_v3LightDir, atmos_v3Direction) / length(atmos_v3Direction); 
     float fRayleighPhase = 1.0;  // 0.75 * (1.0 + fCos*fCos); 
     float fMiePhase = 1.5 * ((1.0 - atmos_g2) / (2.0 + atmos_g2)) * (1.0 + fCos*fCos) / atmos_fastpow(1.0 + atmos_g2 - 2.0*atmos_g*fCos, 1.5); 
-    vec3 f4Color = fRayleighPhase * atmos_rayleighColor + fMiePhase * atmos_mieColor; 
+    vec3 f4Color = fRayleighPhase * atmos_rayleighColor + fMiePhase * atmos_mieColor;
+    
     vec3 skyColor = 1.0 - exp(f4Color * -fExposure); 
-    color.rgb = skyColor.rgb*atmos_fWeather; 
-    color.a = (skyColor.r+skyColor.g+skyColor.b) * 2.0; 
+    vec4 atmosColor;
+    atmosColor.rgb = skyColor.rgb*atmos_fWeather; 
+    atmosColor.a = (skyColor.r+skyColor.g+skyColor.b) * 2.0;
+
+    color = mix(atmosColor, vec4(f4Color,1.0), atmos_renderFromSpace);
 }
diff --git a/src/osgEarthDrivers/sky_simple/SimpleSky.Atmosphere.vert.glsl b/src/osgEarthDrivers/sky_simple/SimpleSky.Atmosphere.vert.glsl
index b001807..20517ca 100644
--- a/src/osgEarthDrivers/sky_simple/SimpleSky.Atmosphere.vert.glsl
+++ b/src/osgEarthDrivers/sky_simple/SimpleSky.Atmosphere.vert.glsl
@@ -25,9 +25,10 @@ uniform float atmos_fScaleOverScaleDepth;     // fScale / fScaleDepth
 uniform int atmos_nSamples; 	
 uniform float atmos_fSamples; 				
 
-varying vec3 atmos_v3Direction; 
-varying vec3 atmos_mieColor; 
-varying vec3 atmos_rayleighColor; 
+out vec3 atmos_v3Direction; 
+out vec3 atmos_mieColor; 
+out vec3 atmos_rayleighColor; 
+out float atmos_renderFromSpace;
 
 vec3 vVec; 
 float atmos_fCameraHeight;    // The camera's current height 		
@@ -138,8 +139,10 @@ void atmos_SkyFromAtmosphere(void)
     atmos_v3Direction = vVec - v3Pos; 				
 } 
 
+uniform float FFF;
+
 void atmos_vertex_main(inout vec4 VertexVIEW) 
-{ 
+{
     // Get camera position and height 
     vVec = osg_ViewMatrixInverse[3].xyz; 
     atmos_fCameraHeight = length(vVec); 
@@ -147,9 +150,16 @@ void atmos_vertex_main(inout vec4 VertexVIEW)
     if(atmos_fCameraHeight >= atmos_fOuterRadius)
     { 
         atmos_SkyFromSpace(); 
+        //atmos_renderFromSpace = 1.0;
     } 
     else
     { 
         atmos_SkyFromAtmosphere(); 
-    } 
+        //atmos_renderFromSpace = 0.0;
+    }
+
+    // Transition from space to atmosphere
+    atmos_renderFromSpace = 1.0 - clamp(
+        (atmos_fOuterRadius-atmos_fCameraHeight)/50000,
+        0.0, 1.0 );
 }
diff --git a/src/osgEarthDrivers/sky_simple/SimpleSky.Ground.ONeil.frag.glsl b/src/osgEarthDrivers/sky_simple/SimpleSky.Ground.ONeil.frag.glsl
index 4c8635a..5c27cda 100644
--- a/src/osgEarthDrivers/sky_simple/SimpleSky.Ground.ONeil.frag.glsl
+++ b/src/osgEarthDrivers/sky_simple/SimpleSky.Ground.ONeil.frag.glsl
@@ -5,63 +5,163 @@ $GLSL_DEFAULT_PRECISION_FLOAT
 #pragma vp_location   fragment_lighting
 #pragma vp_order      0.8
 
-uniform bool oe_mode_GL_LIGHTING; 
-uniform float atmos_exposure;   // scene exposure (ground level)
-varying vec3 atmos_lightDir;    // light direction (view coords)
-varying vec3 atmos_color;       // atmospheric lighting color
-varying vec3 atmos_atten;       // atmospheric lighting attentuation factor
-varying vec3 atmos_up;          // earth up vector at fragment (in view coords)
-varying float atmos_space;      // camera altitude (0=ground, 1=atmos outer radius)
-varying vec3 atmos_vert; 
+#pragma import_defines(OE_LIGHTING, OE_NUM_LIGHTS)
+
+uniform float oe_sky_exposure;           // HDR scene exposure (ground level)
+uniform float oe_sky_ambientBoostFactor; // ambient sunlight booster for daytime
+
+in vec3 atmos_lightDir;    // light direction (view coords)
+in vec3 atmos_color;       // atmospheric lighting color
+in vec3 atmos_atten;       // atmospheric lighting attenuation factor
+in vec3 atmos_up;          // earth up vector at fragment (in view coords)
+in float atmos_space;      // camera altitude (0=ground, 1=atmos outer radius)
+in vec3 atmos_vert; 
         
 vec3 vp_Normal;          // surface normal (from osgEarth)
 
+// Parameters of each light:
+struct osg_LightSourceParameters 
+{   
+   vec4 ambient;
+   vec4 diffuse;
+   vec4 specular;
+   vec4 position;
+   vec3 spotDirection;
+   float spotExponent;
+   float spotCutoff;
+   float spotCosCutoff;
+   float constantAttenuation;
+   float linearAttenuation;
+   float quadraticAttenuation;
+
+   bool enabled;
+};  
+uniform osg_LightSourceParameters osg_LightSource[OE_NUM_LIGHTS];
+
+// Surface material:
+struct osg_MaterialParameters  
+{   
+   vec4 emission;    // Ecm   
+   vec4 ambient;     // Acm   
+   vec4 diffuse;     // Dcm   
+   vec4 specular;    // Scm   
+   float shininess;  // Srm  
+};  
+uniform osg_MaterialParameters osg_FrontMaterial; 
+
+
 void atmos_fragment_main(inout vec4 color) 
 { 
-    if ( oe_mode_GL_LIGHTING == false )
+#ifndef OE_LIGHTING
+    return;
+#endif
+
+    // See:
+    // https://en.wikipedia.org/wiki/Phong_reflection_model
+    // https://www.opengl.org/sdk/docs/tutorials/ClockworkCoders/lighting.php
+    // https://en.wikibooks.org/wiki/GLSL_Programming/GLUT/Multiple_Lights
+
+    vec3 N = normalize(vp_Normal);
+
+    float shine = clamp(osg_FrontMaterial.shininess, 1.0, 128.0); 
+    
+    vec3 U = normalize(atmos_up);
+
+    // Accumulate the lighting, starting with material emission. We are currently
+    // omitting the ambient term for now since we are not using LightModel ambience.
+    vec3 totalLighting =
+        osg_FrontMaterial.emission.rgb;
+        // + osg_FrontMaterial.ambient.rgb * osg_LightModel.ambient.rgb;
+
+    int numLights = OE_NUM_LIGHTS;
+
+    for (int i=0; i<numLights; ++i)
     {
-        return; 
-    }
+        const float attenuation = 1.0;
+
+        if (osg_LightSource[i].enabled)
+        {
+            float attenuation = 1.0;
+            vec3 L; // vertex-to-light-source vector.
 
-    vec3 ambient = gl_LightSource[0].ambient.rgb;
-    float minAmbient = ambient.r;
+            // directional light:
+            if (osg_LightSource[i].position.w == 0.0)
+            {
+                L = normalize(osg_LightSource[i].position.xyz);
+            }
 
-    vec3 N = normalize(vp_Normal); 
-    vec3 L = normalize(atmos_lightDir); //normalize(gl_LightSource[0].position.xyz); 
-    vec3 U = normalize(atmos_up); 
+            // point or spot light:
+            else
+            {
+                // calculate VL, the vertex-to-light vector:
+                vec4 V = vec4(atmos_vert, 1.0) * osg_LightSource[i].position.w;
+                vec4 VL4 = osg_LightSource[i].position - V;
+                L = normalize(VL4.xyz);
 
-    const float maxAmbient = 0.5;
-    float daytime = max(0.0, dot(U,L));
-    float ambientLightLevel = clamp(daytime, minAmbient, maxAmbient);
+                // calculate attenuation:
+                float distance = length(VL4);
+                attenuation = 1.0 / (
+                    osg_LightSource[i].constantAttenuation +
+                    osg_LightSource[i].linearAttenuation * distance +
+                    osg_LightSource[i].quadraticAttenuation * distance * distance);
 
-    float NdotL = max(dot(N,L), 0.0);
+                // for a spot light, the attenuation help form the cone:
+                if (osg_LightSource[i].spotCutoff <= 90.0)
+                {
+                    vec3 D = normalize(osg_LightSource[i].spotDirection);
+                    float clampedCos = max(0.0, dot(-L,D));
+                    attenuation = clampedCos < osg_LightSource[i].spotCosCutoff ?
+                        0.0 :
+                        attenuation * pow(clampedCos, osg_LightSource[i].spotExponent);
+                }
+            }
 
-    const float lowAlt  = 1.0;
-    const float highAlt = 14.0;
-    float altitudeInfluence = 1.0 - clamp( (atmos_space-lowAlt)/(highAlt-lowAlt), 0.0, 1.0);
-    float useNormals = altitudeInfluence * (1.0-ambientLightLevel);
+            // a term indicating whether it's daytime for light 0 (the sun).
+            float dayTerm = i==0? dot(U,L) : 1.0;
 
-    // try to brighten up surfaces the sun is shining on
-    float overExposure = 1.0;
+            // This term boosts the ambient lighting for the sun (light 0) when it's daytime.
+            // TODO: make the boostFactor a uniform?
+            float ambientBoost = i==0? 1.0 + oe_sky_ambientBoostFactor*clamp(2.0*(dayTerm-0.5), 0.0, 1.0) : 1.0;
 
-    // calculate the base scene color. Skip ambience since we'll be
-    // factoring that in later.
-    vec4 sceneColor = mix(color*overExposure, color*NdotL, useNormals);
+            vec3 ambientReflection =
+                attenuation
+                * osg_FrontMaterial.ambient.rgb
+                * osg_LightSource[i].ambient.rgb
+                * ambientBoost;
 
-    if (NdotL > 0.0 ) { 
-        vec3 V = normalize(atmos_vert); 
-        vec3 H = reflect(-L, N);
-        float HdotN = max(dot(H,N), 0.0); 
-        float shine = clamp(gl_FrontMaterial.shininess, 1.0, 128.0); 
-        sceneColor += gl_FrontLightProduct[0].specular * pow(HdotN, shine); 
-    } 
+            float NdotL = max(dot(N,L), 0.0); 
 
-    // clamp the attentuation to the minimum ambient lighting:
-    vec3 attenuation = max(atmos_atten, ambient); 
+            // this term, applied to light 0 (the sun), attenuates the diffuse light
+            // during the nighttime, so that geometry doesn't get lit based on its
+            // normals during the night.
+            float diffuseAttenuation = clamp(dayTerm+0.35, 0.0, 1.0);
+            
+            vec3 diffuseReflection =
+                attenuation
+                * diffuseAttenuation
+                * osg_LightSource[i].diffuse.rgb * osg_FrontMaterial.diffuse.rgb
+                * NdotL;
+                
+            vec3 specularReflection = vec3(0.0);
+            if (NdotL > 0.0)
+            {
+                vec3 H = reflect(-L,N); 
+                float HdotN = max(dot(H,N), 0.0); 
 
-    // ramp exposure from ground (full) to space (50%).
-    float exposure = atmos_exposure*clamp(1.0-atmos_space, 0.5, 1.0); 
+                specularReflection =
+                    attenuation
+                    * osg_LightSource[i].specular.rgb
+                    * osg_FrontMaterial.specular.rgb
+                    * pow(HdotN, shine);
+            }
 
-    vec3 atmosColor = 1.0 - exp(-exposure * (atmos_color + sceneColor.rgb * attenuation)); 
-    color.rgb = gl_FrontMaterial.emission.rgb + atmosColor; 
+            totalLighting += ambientReflection + diffuseReflection + specularReflection;
+        }
+    }
+    
+    // add the atmosphere color, and scale by the lighting.
+    color.rgb = (color.rgb + atmos_color) * totalLighting;
+    
+    // Simulate HDR by applying an exposure factor (1.0 is none, 2-3 are reasonable)
+    color.rgb = 1.0 - exp(-oe_sky_exposure * color.rgb);
 }
diff --git a/src/osgEarthDrivers/sky_simple/SimpleSky.Ground.ONeil.vert.glsl b/src/osgEarthDrivers/sky_simple/SimpleSky.Ground.ONeil.vert.glsl
index a1acf31..586b212 100644
--- a/src/osgEarthDrivers/sky_simple/SimpleSky.Ground.ONeil.vert.glsl
+++ b/src/osgEarthDrivers/sky_simple/SimpleSky.Ground.ONeil.vert.glsl
@@ -5,11 +5,13 @@ $GLSL_DEFAULT_PRECISION_FLOAT
 #pragma vp_location   vertex_view
 #pragma vp_order      0.5
 
-uniform bool oe_mode_GL_LIGHTING; 
+#pragma import_defines(OE_LIGHTING, OE_NUM_LIGHTS)
+
+//uniform bool oe_mode_GL_LIGHTING; 
 
 uniform mat4 osg_ViewMatrixInverse;   // world camera position in [3].xyz 
 uniform mat4 osg_ViewMatrix;          // GL view matrix 
-uniform vec3 atmos_v3LightDir;        // The direction vector to the light source 
+//uniform vec3 atmos_v3LightDir;        // The direction vector to the light source 
 uniform vec3 atmos_v3InvWavelength;   // 1 / pow(wavelength,4) for the rgb channels 
 uniform float atmos_fOuterRadius;     // Outer atmosphere radius 
 uniform float atmos_fOuterRadius2;    // fOuterRadius^2 		
@@ -25,19 +27,47 @@ uniform float atmos_fScaleOverScaleDepth;     // fScale / fScaleDepth
 uniform int atmos_nSamples; 	
 uniform float atmos_fSamples; 
 
-varying vec3 atmos_color;          // primary sky light color
-varying vec3 atmos_atten;          // sky light attenuation factor
-varying vec3 atmos_lightDir;       // light direction in view space
+out vec3 atmos_color;          // primary sky light color
+out vec3 atmos_atten;          // sky light attenuation factor
+out vec3 atmos_lightDir;       // light direction in view space
         
 float atmos_fCameraHeight;            // The camera's current height 		
 float atmos_fCameraHeight2;           // fCameraHeight^2 
 
-varying vec3 atmos_up;             // earth up vector at vertex location (not the normal)
-varying float atmos_space;         // [0..1]: camera: 0=inner radius (ground); 1.0=outer radius
-varying vec3 atmos_vert; 
+out vec3 atmos_up;             // earth up vector at vertex location (not the normal)
+out float atmos_space;         // [0..1]: camera: 0=inner radius (ground); 1.0=outer radius
+out vec3 atmos_vert; 
 
 vec3 vp_Normal;             // surface normal (from osgEarth)
 
+
+// Toatl number of lights in the scene
+//uniform int osg_NumLights;
+
+// Parameters of each light:
+struct osg_LightSourceParameters 
+{   
+   vec4 ambient;              // Aclarri   
+   vec4 diffuse;              // Dcli   
+   vec4 specular;             // Scli   
+   vec4 position;             // Ppli   
+   //vec4 halfVector;           // Derived: Hi   
+   vec3 spotDirection;        // Sdli   
+   float spotExponent;        // Srli   
+   float spotCutoff;          // Crli                              
+                              // (range: [0.0,90.0], 180.0)   
+   float spotCosCutoff;       // Derived: cos(Crli)                 
+                              // (range: [1.0,0.0],-1.0)   
+   float constantAttenuation; // K0   
+   float linearAttenuation;   // K1   
+   float quadraticAttenuation;// K2  
+
+   bool enabled;
+};  
+uniform osg_LightSourceParameters osg_LightSource[1];
+
+
+
 float atmos_scale(float fCos) 	
 { 
     float x = 1.0 - fCos; 
@@ -145,12 +175,16 @@ void atmos_GroundFromAtmosphere(in vec4 vertexVIEW)
 } 
 
 void atmos_vertex_main(inout vec4 vertexVIEW) 
-{ 
-    if ( oe_mode_GL_LIGHTING == false ) return; 
+{
+#ifndef OE_LIGHTING
+    return;
+#endif
+
+    //if ( oe_mode_GL_LIGHTING == false ) return; 
 
     atmos_fCameraHeight = length(osg_ViewMatrixInverse[3].xyz); 
     atmos_fCameraHeight2 = atmos_fCameraHeight*atmos_fCameraHeight; 
-    atmos_lightDir = normalize(gl_LightSource[0].position.xyz);  // view space
+    atmos_lightDir = normalize(osg_LightSource[0].position.xyz);  // view space
     atmos_vert = vertexVIEW.xyz; 
 
     atmos_space = max(0.0, (atmos_fCameraHeight-atmos_fInnerRadius)/(atmos_fOuterRadius-atmos_fInnerRadius));
diff --git a/src/osgEarthDrivers/sky_simple/SimpleSky.Moon.frag.glsl b/src/osgEarthDrivers/sky_simple/SimpleSky.Moon.frag.glsl
index 2cf02c3..30ce29d 100644
--- a/src/osgEarthDrivers/sky_simple/SimpleSky.Moon.frag.glsl
+++ b/src/osgEarthDrivers/sky_simple/SimpleSky.Moon.frag.glsl
@@ -1,10 +1,12 @@
 #version $GLSL_VERSION_STR
 $GLSL_DEFAULT_PRECISION_FLOAT
 
-varying vec4 moon_TexCoord;
+in vec4 moon_TexCoord;
 uniform sampler2D moonTex;
 
+out vec4 out_FragColor;
+
 void main( void ) 
 { 
-   gl_FragColor = texture2D(moonTex, moon_TexCoord.st);
+   out_FragColor = texture(moonTex, moon_TexCoord.st);
 }
diff --git a/src/osgEarthDrivers/sky_simple/SimpleSky.Moon.vert.glsl b/src/osgEarthDrivers/sky_simple/SimpleSky.Moon.vert.glsl
index 420b269..03e14c4 100644
--- a/src/osgEarthDrivers/sky_simple/SimpleSky.Moon.vert.glsl
+++ b/src/osgEarthDrivers/sky_simple/SimpleSky.Moon.vert.glsl
@@ -1,11 +1,10 @@
 #version $GLSL_VERSION_STR
 $GLSL_DEFAULT_PRECISION_FLOAT
 
-uniform mat4 osg_ModelViewProjectionMatrix;
-varying vec4 moon_TexCoord;
+out vec4 moon_TexCoord;
 
 void main() 
 { 
     moon_TexCoord = gl_MultiTexCoord0; 
-    gl_Position = osg_ModelViewProjectionMatrix * gl_Vertex; 
+    gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; 
 }
diff --git a/src/osgEarthDrivers/sky_simple/SimpleSky.Stars.GLES.frag.glsl b/src/osgEarthDrivers/sky_simple/SimpleSky.Stars.GLES.frag.glsl
index a393abf..8e0a3a1 100644
--- a/src/osgEarthDrivers/sky_simple/SimpleSky.Stars.GLES.frag.glsl
+++ b/src/osgEarthDrivers/sky_simple/SimpleSky.Stars.GLES.frag.glsl
@@ -1,9 +1,12 @@
-#version $GLSL_VERSION_STR 
-$GLSL_DEFAULT_PRECISION_FLOAT 
+#version $GLSL_VERSION_STR
+$GLSL_DEFAULT_PRECISION_FLOAT
 
 varying float visibility; 
-varying vec4 osg_FrontColor; 
+in vec4 osg_FrontColor; 
+
+out vec4 out_FragColor;
+
 void main( void ) 
 { 
-    gl_FragColor = osg_FrontColor * visibility; 
+    out_FragColor = osg_FrontColor * visibility; 
 }
diff --git a/src/osgEarthDrivers/sky_simple/SimpleSky.Stars.GLES.vert.glsl b/src/osgEarthDrivers/sky_simple/SimpleSky.Stars.GLES.vert.glsl
index 730b7c2..6e2db7d 100644
--- a/src/osgEarthDrivers/sky_simple/SimpleSky.Stars.GLES.vert.glsl
+++ b/src/osgEarthDrivers/sky_simple/SimpleSky.Stars.GLES.vert.glsl
@@ -1,10 +1,11 @@
-#version $GLSL_VERSION_STR 
-$GLSL_DEFAULT_PRECISION_FLOAT 
+#version $GLSL_VERSION_STR
+$GLSL_DEFAULT_PRECISION_FLOAT
 
 uniform vec3 atmos_v3LightDir; 
 uniform mat4 osg_ViewMatrixInverse; 
-varying float visibility; 
-varying vec4 osg_FrontColor; 
+
+out float visibility; 
+out vec4 osg_FrontColor; 
 
 float remap( float val, float vmin, float vmax, float r0, float r1 ) 
 { 
diff --git a/src/osgEarthDrivers/sky_simple/SimpleSky.Stars.frag.glsl b/src/osgEarthDrivers/sky_simple/SimpleSky.Stars.frag.glsl
index 4fe4b8e..3d0899c 100644
--- a/src/osgEarthDrivers/sky_simple/SimpleSky.Stars.frag.glsl
+++ b/src/osgEarthDrivers/sky_simple/SimpleSky.Stars.frag.glsl
@@ -1,12 +1,15 @@
 #version $GLSL_VERSION_STR
+$GLSL_DEFAULT_PRECISION_FLOAT
 
 in float visibility; 
 in vec4 osg_FrontColor; 
 
+out vec4 out_FragColor;
+
 void main( void ) 
 { 
     float b1 = 1.0-(2.0*abs(gl_PointCoord.s-0.5)); 
     float b2 = 1.0-(2.0*abs(gl_PointCoord.t-0.5)); 
     float i = b1*b1 * b2*b2; 
-    gl_FragColor = osg_FrontColor * i * visibility; 
+    out_FragColor = osg_FrontColor * i * visibility; 
 }
diff --git a/src/osgEarthDrivers/sky_simple/SimpleSky.Stars.vert.glsl b/src/osgEarthDrivers/sky_simple/SimpleSky.Stars.vert.glsl
index 3652426..03d26df 100644
--- a/src/osgEarthDrivers/sky_simple/SimpleSky.Stars.vert.glsl
+++ b/src/osgEarthDrivers/sky_simple/SimpleSky.Stars.vert.glsl
@@ -1,10 +1,14 @@
 #version $GLSL_VERSION_STR
+$GLSL_DEFAULT_PRECISION_FLOAT
 
 uniform vec3 atmos_v3LightDir; 
 uniform mat4 osg_ViewMatrixInverse; 
 out float visibility; 
 out vec4 osg_FrontColor; 
 
+layout(location=0) in vec4 vertex;
+layout(location=3) in vec4 color;
+
 float remap( float val, float vmin, float vmax, float r0, float r1 ) 
 { 
     float vr = (clamp(val, vmin, vmax)-vmin)/(vmax-vmin); 
@@ -13,9 +17,9 @@ float remap( float val, float vmin, float vmax, float r0, float r1 )
 
 void main() 
 { 
-    osg_FrontColor = gl_Color; 
-    gl_PointSize = gl_Color.r * 14.0; 
-    gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; 
+    osg_FrontColor = color; //vec4(1,1,1,1); //gl_Color; 
+    gl_PointSize = color.r * 14.0; //gl_Color.r * 14.0; 
+    gl_Position = gl_ModelViewProjectionMatrix * vertex; 
     vec3 eye = osg_ViewMatrixInverse[3].xyz; 
     float hae = length(eye) - 6378137.0; 
     // highness: visibility increases with altitude
diff --git a/src/osgEarthDrivers/sky_simple/SimpleSky.Sun.frag.glsl b/src/osgEarthDrivers/sky_simple/SimpleSky.Sun.frag.glsl
index 0ae7273..8da7682 100644
--- a/src/osgEarthDrivers/sky_simple/SimpleSky.Sun.frag.glsl
+++ b/src/osgEarthDrivers/sky_simple/SimpleSky.Sun.frag.glsl
@@ -1,8 +1,8 @@
-#version $GLSL_VERSION_STR 
-$GLSL_DEFAULT_PRECISION_FLOAT 
+#version $GLSL_VERSION_STR
+$GLSL_DEFAULT_PRECISION_FLOAT
 
-uniform float atmos_sunAlpha; 
-varying vec3 atmos_v3Direction; 
+in vec3 atmos_v3Direction;
+out vec4 out_FragColor;
 
 float atmos_fastpow(in float x, in float y) 
 { 
@@ -13,6 +13,7 @@ void main( void )
 { 
    float fCos = -atmos_v3Direction[2];          
    float fMiePhase = 0.050387596899224826 * (1.0 + fCos*fCos) / atmos_fastpow(1.9024999999999999 - -1.8999999999999999*fCos, 1.5); 
-   gl_FragColor.rgb = fMiePhase*vec3(.3,.3,.2); 
-   gl_FragColor.a = atmos_sunAlpha*gl_FragColor.r; 
+   out_FragColor.rgb = fMiePhase*vec3(.3,.3,.2);
+   // Alpha needs to scale from full at the center (where red == 1) to 0 on the edges of sun disc
+   out_FragColor.a = out_FragColor.r;
 }
diff --git a/src/osgEarthDrivers/sky_simple/SimpleSky.Sun.vert.glsl b/src/osgEarthDrivers/sky_simple/SimpleSky.Sun.vert.glsl
index 62f0f22..070c1cc 100644
--- a/src/osgEarthDrivers/sky_simple/SimpleSky.Sun.vert.glsl
+++ b/src/osgEarthDrivers/sky_simple/SimpleSky.Sun.vert.glsl
@@ -1,7 +1,7 @@
 #version $GLSL_VERSION_STR
 $GLSL_DEFAULT_PRECISION_FLOAT
 
-varying vec3 atmos_v3Direction; 
+out vec3 atmos_v3Direction; 
 
 void main() 
 { 
diff --git a/src/osgEarthDrivers/sky_simple/SimpleSkyExtension.cpp b/src/osgEarthDrivers/sky_simple/SimpleSkyExtension.cpp
index e8bb059..f375404 100644
--- a/src/osgEarthDrivers/sky_simple/SimpleSkyExtension.cpp
+++ b/src/osgEarthDrivers/sky_simple/SimpleSkyExtension.cpp
@@ -21,6 +21,7 @@
 #include <osgDB/FileNameUtils>
 #include <osgEarth/Map>
 #include <osgEarth/MapNode>
+#include <osgEarth/NodeUtils>
 #include <osgEarthUtil/Controls>
 #include <osgEarthUtil/ExampleResources>
 
@@ -63,7 +64,8 @@ namespace osgEarth { namespace SimpleSky
 
         bool disconnect(MapNode* mapNode)
         {
-            //todo
+            osgEarth::removeGroup(_skynode.get());
+            _skynode = 0L;
             return true;
         }
 
@@ -91,12 +93,15 @@ namespace osgEarth { namespace SimpleSky
         {
             ui::Container* container = dynamic_cast<ui::Container*>(control);
             if (container)
-                container->addControl(SkyControlFactory::create(_skynode.get()));
+                _ui = container->addControl(SkyControlFactory::create(_skynode.get()));
             return true;
         }
 
         bool disconnect(ui::Control* control)
         {
+            ui::Container* container = dynamic_cast<ui::Container*>(control);
+            if (container && _ui.valid())
+                container->removeChild(_ui.get());
             return true;
         }
 
@@ -116,6 +121,7 @@ namespace osgEarth { namespace SimpleSky
 
 
     private:
+        osg::ref_ptr<ui::Control> _ui;
         osg::ref_ptr<SkyNode> _skynode;
     };
 
diff --git a/src/osgEarthDrivers/sky_simple/SimpleSkyNode b/src/osgEarthDrivers/sky_simple/SimpleSkyNode
index 8711e0f..5784d55 100644
--- a/src/osgEarthDrivers/sky_simple/SimpleSkyNode
+++ b/src/osgEarthDrivers/sky_simple/SimpleSkyNode
@@ -48,7 +48,7 @@ namespace osgEarth { namespace SimpleSky
 
     public: // SkyNode
 
-        osg::Light* getSunLight() { return _light.get(); }
+        osg::Light* getSunLight() const { return _light.get(); }
 
         void attach(osg::View* view, int lightNum);
 
@@ -58,7 +58,6 @@ namespace osgEarth { namespace SimpleSky
         void onSetMoonVisible();
         void onSetStarsVisible();
         void onSetAtmosphereVisible();
-        void onSetMinimumAmbient();
 
     public: // osg::Node
 
diff --git a/src/osgEarthDrivers/sky_simple/SimpleSkyNode.cpp b/src/osgEarthDrivers/sky_simple/SimpleSkyNode.cpp
index f68a0c1..19b8f68 100644
--- a/src/osgEarthDrivers/sky_simple/SimpleSkyNode.cpp
+++ b/src/osgEarthDrivers/sky_simple/SimpleSkyNode.cpp
@@ -36,6 +36,7 @@
 #include <osgEarth/ShaderFactory>
 #include <osgEarth/ShaderGenerator>
 #include <osgEarth/Shaders>
+#include <osgEarth/Lighting>
 
 #include <osg/MatrixTransform>
 #include <osg/ShapeDrawable>
@@ -182,8 +183,8 @@ namespace
 
             int i_plus_1 = i < segments-1? i+1 : 0;
             el->push_back( 0 );
-            el->push_back( 1 + i_plus_1 );
             el->push_back( 1 + i );
+            el->push_back( 1 + i_plus_1 );
         }
 
         return geom;
@@ -214,12 +215,19 @@ SimpleSkyNode::initialize(const SpatialReference* srs)
 
     osg::Vec3f lightPos(0.0f, 1.0f, 0.0f);
 
-    _light = new osg::Light( 0 );
-    _light->setPosition( osg::Vec4f(0.0f, 0.0f, 1.0, 0.0f) );
-    _light->setAmbient ( osg::Vec4f(0.03f, 0.03f, 0.03f, 1.0f) );
+    _light = new LightGL3( 0 );
+    _light->setPosition( osg::Vec4f(0.0f, 0.0f, 1.0f, 0.0f) );
+    _light->setAmbient ( osg::Vec4f(0.1f, 0.1f, 0.1f, 1.0f) );
     _light->setDiffuse ( osg::Vec4f(1.0f, 1.0f, 1.0f, 1.0f) );
     _light->setSpecular( osg::Vec4f(1.0f, 1.0f, 1.0f, 1.0f) );
 
+    // install the Sun as a lightsource.
+    osg::LightSource* lightSource = new osg::LightSource();
+    lightSource->setLight(_light.get());
+    lightSource->setCullingActive(false);
+    this->addChild( lightSource );
+    lightSource->addCullCallback(new LightSourceGL3UniformGenerator());
+
     if ( _options.ambient().isSet() )
     {
         float a = osg::clampBetween(_options.ambient().get(), 0.0f, 1.0f);
@@ -246,13 +254,17 @@ SimpleSkyNode::initialize(const SpatialReference* srs)
     
     if ( Registry::capabilities().supportsGLSL() )
     {
+        osg::StateSet* stateset = this->getOrCreateStateSet();
+
         _lightPosUniform = new osg::Uniform(osg::Uniform::FLOAT_VEC3, "atmos_v3LightDir");
         _lightPosUniform->set( lightPos / lightPos.length() );
-        this->getOrCreateStateSet()->addUniform( _lightPosUniform.get() );
+        stateset->addUniform( _lightPosUniform.get() );
 
         // default GL_LIGHTING uniform setting
-        this->getOrCreateStateSet()->addUniform(
-            Registry::shaderFactory()->createUniformForGLMode(GL_LIGHTING, 1) );
+        //stateset->addUniform(
+        //    Registry::shaderFactory()->createUniformForGLMode(GL_LIGHTING, 1) );
+
+        stateset->setDefine(OE_LIGHTING_DEFINE, osg::StateAttribute::ON);
 
         // make the uniforms and the terrain lighting shaders.
         makeSceneLighting();
@@ -344,21 +356,22 @@ SimpleSkyNode::onSetDateTime()
 }
 
 void
-SimpleSkyNode::onSetMinimumAmbient()
-{
-    _light->setAmbient( getMinimumAmbient() );
-}
-
-void
 SimpleSkyNode::attach( osg::View* view, int lightNum )
 {
     if ( !view || !_light.valid() )
         return;
 
     _light->setLightNum( lightNum );
-    view->setLight( _light.get() );
-    view->setLightingMode( osg::View::SKY_LIGHT );
+
+    // black background
     view->getCamera()->setClearColor( osg::Vec4(0,0,0,1) );
+    
+    // install the light in the view (so other modules can access it, like shadowing)
+    view->setLight(_light.get());
+
+    // Tell the view not to automatically include a light.
+    view->setLightingMode( osg::View::NO_LIGHT );
+
 
     onSetDateTime();
 }
@@ -366,7 +379,11 @@ SimpleSkyNode::attach( osg::View* view, int lightNum )
 void
 SimpleSkyNode::setSunPosition(const osg::Vec3& pos)
 {
-    _light->setPosition( osg::Vec4(pos, 0.0f) );
+    osg::Vec3 npos = pos;
+    npos.normalize();
+    _light->setPosition( osg::Vec4(npos, 0.0f) ); // directional light
+
+    //OE_NOTICE << pos.x() << ", " << pos.y() << ", " << pos.z() << std::endl;
     
     if ( _lightPosUniform.valid() )
     {
@@ -437,7 +454,7 @@ SimpleSkyNode::makeSceneLighting()
     else
     {
         _phong = new PhongLightingEffect();
-        _phong->setCreateLightingUniform( false );
+        //_phong->setCreateLightingUniform( false );
         _phong->attach( stateset );
         OE_INFO << LC << "Using Phong lighting\n";
     }
@@ -460,6 +477,7 @@ SimpleSkyNode::makeSceneLighting()
 
     float Scale = 1.0f / (_outerRadius - _innerRadius);
 
+    //TODO: make all these constants. -gw
     stateset->getOrCreateUniform( "atmos_v3InvWavelength", osg::Uniform::FLOAT_VEC3 )->set( RGB_wl );
     stateset->getOrCreateUniform( "atmos_fInnerRadius",    osg::Uniform::FLOAT )->set( _innerRadius );
     stateset->getOrCreateUniform( "atmos_fInnerRadius2",   osg::Uniform::FLOAT )->set( _innerRadius * _innerRadius );
@@ -477,7 +495,10 @@ SimpleSkyNode::makeSceneLighting()
     stateset->getOrCreateUniform( "atmos_nSamples",        osg::Uniform::INT )->set( Samples );
     stateset->getOrCreateUniform( "atmos_fSamples",        osg::Uniform::FLOAT )->set( (float)Samples );
     stateset->getOrCreateUniform( "atmos_fWeather",        osg::Uniform::FLOAT )->set( Weather );
-    stateset->getOrCreateUniform( "atmos_exposure",        osg::Uniform::FLOAT )->set( _options.exposure().value() );
+
+    // options:
+    stateset->getOrCreateUniform("oe_sky_exposure",           osg::Uniform::FLOAT )->set( _options.exposure().value() );
+    stateset->getOrCreateUniform("oe_sky_ambientBoostFactor", osg::Uniform::FLOAT)->set(_options.daytimeAmbientBoost().get());
 }
 
 void
@@ -544,11 +565,7 @@ SimpleSkyNode::makeSun()
     osg::StateSet* set = sun->getOrCreateStateSet();
     set->setMode( GL_BLEND, 1 );
 
-    set->getOrCreateUniform( "atmos_sunAlpha", osg::Uniform::FLOAT )->set( 1.0f );
-
     // configure the stateset
-    set->setMode( GL_LIGHTING, osg::StateAttribute::OFF );
-    set->setMode( GL_CULL_FACE, osg::StateAttribute::OFF );
     set->setAttributeAndModes( new osg::Depth(osg::Depth::ALWAYS, 0, 1, false), osg::StateAttribute::ON );
 
     // create shaders
@@ -565,7 +582,7 @@ SimpleSkyNode::makeSun()
         ShaderLoader::load(pkg.Sun_Frag, pkg) );
     program->addShader( fs );
 
-    set->setAttributeAndModes( program, osg::StateAttribute::ON );
+    set->setAttributeAndModes( program, osg::StateAttribute::ON | osg::StateAttribute::PROTECTED );
 
     // A nested camera isolates the projection matrix calculations so the node won't 
     // affect the clip planes in the rest of the scene.
@@ -598,7 +615,7 @@ SimpleSkyNode::makeMoon()
     osg::Geometry* geom = s_makeEllipsoidGeometry( em.get(), em->getRadiusEquator(), true );    
     //TODO:  Embed this texture in code or provide a way to have a default resource directory for osgEarth.
     //       Right now just need to have this file somewhere in your OSG_FILE_PATH
-    osg::Image* image = osgDB::readImageFile( "moon_1024x512.jpg" );
+    osg::ref_ptr<osg::Image> image = osgDB::readRefImageFile( "moon_1024x512.jpg" );
     osg::Texture2D * texture = new osg::Texture2D( image );
     texture->setFilter(osg::Texture::MIN_FILTER,osg::Texture::LINEAR);
     texture->setFilter(osg::Texture::MAG_FILTER,osg::Texture::LINEAR);
@@ -619,7 +636,7 @@ SimpleSkyNode::makeMoon()
     set->setAttributeAndModes( new osg::Depth(osg::Depth::ALWAYS, 0, 1, false), osg::StateAttribute::ON );
     set->setAttributeAndModes( new osg::BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA), osg::StateAttribute::ON );
     
-#ifdef OSG_GLES2_AVAILABLE
+#if 1 //defined(OSG_GLES2_AVAILABLE) || defined(OSG_GLES3_AVAILABLE)
 
     set->addUniform(new osg::Uniform("moonTex", 0));
 
@@ -753,7 +770,10 @@ SimpleSkyNode::buildStarGeometry(const std::vector<StarData>& stars)
 
     osg::StateSet* sset = geometry->getOrCreateStateSet();
 
+#if !defined(OSG_GL3_AVAILABLE)
+    // In GL3, PointSprite is no longer available, and is always on.
     sset->setTextureAttributeAndModes( 0, new osg::PointSprite(), osg::StateAttribute::ON );
+#endif
     sset->setMode( GL_VERTEX_PROGRAM_POINT_SIZE, osg::StateAttribute::ON );
 
     Shaders pkg;
diff --git a/src/osgEarthDrivers/sky_simple/SimpleSkyOptions b/src/osgEarthDrivers/sky_simple/SimpleSkyOptions
index 00827f7..e6e2541 100644
--- a/src/osgEarthDrivers/sky_simple/SimpleSkyOptions
+++ b/src/osgEarthDrivers/sky_simple/SimpleSkyOptions
@@ -34,9 +34,10 @@ namespace osgEarth { namespace SimpleSky
     public:
         SimpleSkyOptions(const ConfigOptions& options =ConfigOptions()) :
           SkyOptions(options),
-          _atmosphericLighting(true),
-          _exposure           (3.8f),
-          _allowWireframe     (false)
+          _atmosphericLighting (true),
+          _exposure            (2.4f),
+          _daytimeAmbientBoost (5.0f),
+          _allowWireframe      (false)
         {
             setDriver( "simple" );
             fromConfig( _conf );
@@ -45,15 +46,21 @@ namespace osgEarth { namespace SimpleSky
 
     public: // properties
 
-        /** Use advanced atmospheric lighting (instead of simple shading) */
+        /** Use advanced atmospheric lighting on the terrain (instead of simple shading) */
         optional<bool>& atmosphericLighting() { return _atmosphericLighting; }
         const optional<bool>& atmosphericLighting() const { return _atmosphericLighting; }
 
-        /** Exposure factor for ground lighting when using advanced lighting.
-          * Default is 3.0; [1..4] is a good range. */
+        /** Exposure factor for simulated HDR ground lighting. Default is in CTOR */
         optional<float>& exposure() { return _exposure; }
         const optional<float>& exposure() const { return _exposure; }
 
+        /** Factor for boosting the ambient lighting of the sun during the daytime. Without
+          * this, geometry like buildings or steep hillsides can be lit only based on their
+          * normals and can appear too dark during the daytime.
+          * 0=off, 2-10 are reasonable values. */
+        optional<float>& daytimeAmbientBoost() { return _daytimeAmbientBoost; }
+        const optional<float>& daytimeAmbientBoost() const { return _daytimeAmbientBoost; }
+
         /** replacement star definition file */
         optional<std::string>& starFile() { return _starFile; }
         const optional<std::string>& starFile() const { return _starFile; }
@@ -67,6 +74,7 @@ namespace osgEarth { namespace SimpleSky
             Config conf = SkyOptions::getConfig();
             conf.addIfSet("atmospheric_lighting", _atmosphericLighting);
             conf.addIfSet("exposure", _exposure);
+            conf.addIfSet("daytime_ambient_boost", _daytimeAmbientBoost);
             conf.addIfSet("star_file", _starFile);
             conf.addIfSet("allow_wireframe", _allowWireframe);
             return conf;
@@ -82,12 +90,14 @@ namespace osgEarth { namespace SimpleSky
         void fromConfig( const Config& conf ) {
             conf.getIfSet("atmospheric_lighting", _atmosphericLighting);
             conf.getIfSet("exposure", _exposure);
+            conf.getIfSet("daytime_ambient_boost", _daytimeAmbientBoost);
             conf.getIfSet("star_file", _starFile);
             conf.getIfSet("allow_wireframe", _allowWireframe);
         }
 
         optional<bool>        _atmosphericLighting;
         optional<float>       _exposure;
+        optional<float>       _daytimeAmbientBoost;
         optional<std::string> _starFile;
         optional<bool>        _allowWireframe;
     };
diff --git a/src/osgEarthDrivers/skyview/SkyViewOptions b/src/osgEarthDrivers/skyview/SkyViewOptions
index 58dc354..fabfbaf 100644
--- a/src/osgEarthDrivers/skyview/SkyViewOptions
+++ b/src/osgEarthDrivers/skyview/SkyViewOptions
@@ -47,7 +47,7 @@ namespace osgEarth { namespace Drivers
     public:
         Config getConfig() const {
             Config conf = TileSourceOptions::getConfig();
-            conf.updateObjIfSet("image", _imageOptions );
+            conf.setObj("image", _imageOptions );
             return conf;
         }
 
diff --git a/src/osgEarthDrivers/splat_mask/CMakeLists.txt b/src/osgEarthDrivers/splat_mask/CMakeLists.txt
deleted file mode 100644
index b30ef7f..0000000
--- a/src/osgEarthDrivers/splat_mask/CMakeLists.txt
+++ /dev/null
@@ -1,14 +0,0 @@
-SET(TARGET_SRC SplatMaskDriver.cpp)
-SET(TARGET_H SplatMaskOptions)
-
-SET(TARGET_COMMON_LIBRARIES ${TARGET_COMMON_LIBRARIES}
-	osgEarthUtil
-)
-
-SETUP_PLUGIN(osgearth_splat_mask)
-
-# to install public driver includes:
-SET(LIB_NAME splat_mask)
-SET(LIB_PUBLIC_HEADERS SplatMaskOptions)
-INCLUDE(ModuleInstallOsgEarthDriverIncludes OPTIONAL)
-
diff --git a/src/osgEarthDrivers/splat_mask/SplatMaskDriver.cpp b/src/osgEarthDrivers/splat_mask/SplatMaskDriver.cpp
deleted file mode 100644
index f4f9cdd..0000000
--- a/src/osgEarthDrivers/splat_mask/SplatMaskDriver.cpp
+++ /dev/null
@@ -1,205 +0,0 @@
-/* -*-c++-*- */
-/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2016 Pelican Mapping
- * http://osgearth.org
- *
- * osgEarth is free software; you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>
- */
-
-#include <osgEarth/TileSource>
-#include <osgEarth/Registry>
-#include <osgEarth/URI>
-#include <osgEarth/ImageUtils>
-#include <osgEarth/ElevationLayer>
-
-#include <osgEarthUtil/SimplexNoise>
-
-#include <osgEarthDrivers/gdal/GDALOptions>
-using namespace osgEarth::Drivers;
-
-#include <osg/Notify>
-#include <osgDB/FileNameUtils>
-#include <osgDB/FileUtils>
-#include <osgDB/Registry>
-
-
-#include "SplatMaskOptions"
-
-using namespace osgEarth;
-using namespace osgEarth::Drivers;
-
-namespace osgEarth { namespace Drivers { namespace SplatMask
-{
-    class SplatMaskTileSource : public TileSource
-    {
-    public:
-        SplatMaskTileSource( const TileSourceOptions& options ) : 
-          TileSource( options ), 
-          _options(options)
-        {
-            _noise.setOctaves( 24 );
-        }
-
-    public: // TileSource interface
-
-        Status initialize(const osgDB::Options* dbOptions)
-        {            
-            _dbOptions = Registry::instance()->cloneOrCreateOptions( dbOptions );         
-            setProfile( osgEarth::Registry::instance()->getGlobalGeodeticProfile() );
-
-            GDALOptions gdal;
-            gdal.url() = *_options.classificationPath();
-            gdal.interpolation() = osgEarth::INTERP_NEAREST;
-            gdal.bilinearReprojection() = false;
-            gdal.tileSize() = 5;
-            ElevationLayerOptions classOptions("splat", gdal);
-            classOptions.cachePolicy() = CachePolicy::NO_CACHE;
-            _classLayer = new ElevationLayer(classOptions);
-
-            return STATUS_OK;
-        }
-    
-        /** Tell the terrain engine not to cache tiles form this source by default. */
-        CachePolicy getCachePolicyHint(const Profile*) const
-        {
-            return CachePolicy::NO_CACHE;
-        }
-
-        osg::Image* createImage(const TileKey&        key,
-                                ProgressCallback*     progress)
-        {
-            TileKey hfkey( key );
-            GeoHeightField classTable;
-            while( !classTable.valid() && hfkey.valid() )
-            {
-                classTable = _classLayer->createHeightField(hfkey, progress);
-                if ( !classTable.valid() )
-                    hfkey = hfkey.createParentKey();
-            }
-
-            if ( !classTable.valid() )
-            {
-                OE_WARN << "no classification. sorry." << std::endl;
-                return 0L;
-            }
-
-            const SpatialReference* srs = key.getProfile()->getSRS();
-
-            osg::Image* image = new osg::Image();
-            image->allocateImage( getPixelsPerTile(), getPixelsPerTile(), 1, GL_RGBA, GL_UNSIGNED_BYTE );
-
-            double dx = key.getExtent().width()  / (double)(image->s()-1);
-            double dy = key.getExtent().height() / (double)(image->t()-1);
-
-            ImageUtils::PixelWriter write(image);
-            for(int s=0; s<image->s(); ++s)
-            {
-                for(int t=0; t<image->t(); ++t)
-                {
-                    double lon = key.getExtent().xMin() + (double)s * dx;
-                    double lat = key.getExtent().yMin() + (double)t * dy;
-
-                    osg::Vec3d world(lon, lat, 0.0);
-                    if ( srs->isGeographic() )
-                    {
-                        srs->transform(world, srs->getECEF(), world);
-                        world.normalize();
-                    }
-
-                    double n = _noise.getValue(world.x(), world.y(), world.z());
-
-                    float r = n <  -0.5            ? 1.0f : 0.0f;
-                    float g = n >= -0.5 && n < 0.0 ? 1.0f : 0.0f;
-                    float b = n >=  0.0 && n < 0.5 ? 1.0f : 0.0f;
-                    float a = n >=  0.5            ? 1.0f : 0.0f;
-
-                    float rs = (float)s / (float)image->s();
-                    float rt = (float)t / (float)image->t();
-
-                    float elevation = 0.0f;
-                
-                    classTable.getElevation(
-                        key.getExtent().getSRS(),
-                        lon, lat, osgEarth::INTERP_NEAREST,
-                        key.getExtent().getSRS(),
-                        elevation);
-
-                    int cv = (int)elevation;
-                    float contrast = _options.contrast().get();
-
-                    if ( cv > 220 ) {
-                        r = g = b = a = 0.0f; // nodata
-                    }
-                    else if ( cv == 210 ) {
-                        a += contrast;
-                        r *= 0.1f, b *= 0.1f, g *= 0.1f;
-                    }
-                    else if ( cv < 130 ) {
-                        b += contrast;
-                        a = 0.0f;
-                    }
-                    else if ( cv >= 130 && cv <= 200 ) {
-                        g += contrast;
-                        a = 0.0f;
-                    }
-                    else {
-                        r += contrast;
-                        a = 0.0f;
-                    }
-
-                    osg::Vec4f value(r,g,b,a);
-                    value /= r+g+b+a;
-
-                    write(value, s, t);
-                }
-            }
-
-            return image;
-        }
-
-
-
-    private:
-        osg::ref_ptr<ElevationLayer> _classLayer;
-        SplatMaskOptions             _options;
-        osg::ref_ptr<osgDB::Options> _dbOptions;
-        osgEarth::Util::SimplexNoise _noise;
-    };
-
-
-    class SplatMaskDriver : public TileSourceDriver
-    {
-        public:
-            SplatMaskDriver()
-            {
-                supportsExtension( "osgearth_splat_mask", "Detail texture splat mask generator" );
-            }
-
-            const char* className() const
-            {
-                return "Detail Splat Mask Driver";
-            }
-
-            ReadResult readObject(const std::string& file_name, const Options* options) const
-            {
-                if ( !acceptsExtension(osgDB::getLowerCaseFileExtension( file_name )))
-                    return ReadResult::FILE_NOT_HANDLED;
-
-                return new SplatMaskTileSource( getTileSourceOptions(options) );
-            }
-    };
-
-    REGISTER_OSGPLUGIN(osgearth_splat_mask, SplatMaskDriver)
-
-} } } // namespace osgEarth::Drivers::SplatMask
diff --git a/src/osgEarthDrivers/splat_mask/SplatMaskOptions b/src/osgEarthDrivers/splat_mask/SplatMaskOptions
deleted file mode 100644
index f4723b8..0000000
--- a/src/osgEarthDrivers/splat_mask/SplatMaskOptions
+++ /dev/null
@@ -1,79 +0,0 @@
-/* -*-c++-*- */
-/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2016 Pelican Mapping
- * http://osgearth.org
- *
- * osgEarth is free software; you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>
- */
-#ifndef OSGEARTH_DRIVER_SPLAT_MASK_OPTIONS
-#define OSGEARTH_DRIVER_SPLAT_MASK_OPTIONS 1
-
-#include <osgEarth/Common>
-#include <osgEarth/TileSource>
-
-namespace osgEarth { namespace Drivers { namespace SplatMask
-{
-    using namespace osgEarth;
-
-    /**
-     * Options for the SplatMask driver
-     */
-    class SplatMaskOptions : public TileSourceOptions // NO EXPORT; header only
-    {
-    public:
-        SplatMaskOptions( const TileSourceOptions& opt =TileSourceOptions() ) :
-            TileSourceOptions( opt ),
-            _contrast( 0.0f )
-        {
-            setDriver( "splat_mask" );
-            fromConfig( _conf );
-        }
-
-        /** dtor */
-        virtual ~SplatMaskOptions() { }
-
-        optional<float>& contrast() { return _contrast; }
-        const optional<float>& contrast() const { return _contrast; }
-        
-        optional<std::string>& classificationPath() { return _classPath; }
-        const optional<std::string>& classificationPath() const { return _classPath; }
-
-    public:
-        Config getConfig() const {
-            Config conf = TileSourceOptions::getConfig();
-            conf.addIfSet("contrast", _contrast);
-            conf.addIfSet("classification_path", _classPath);
-            return conf;
-        }
-
-    protected:
-        void mergeConfig( const Config& conf ) {
-            TileSourceOptions::mergeConfig( conf );
-            fromConfig( conf );
-        }
-
-    private:
-        void fromConfig( const Config& conf ) {
-            conf.getIfSet("contrast", _contrast);
-            conf.getIfSet("classification_path", _classPath);
-        }
-
-        optional<float> _contrast;
-        optional<std::string> _classPath;
-    };
-
-} } } // namespace osgEarth::Drivers::SplatMask
-
-#endif // OSGEARTH_DRIVER_SPLAT_MASK_OPTIONS
-
diff --git a/src/osgEarthDrivers/template_matclass/CMakeLists.txt b/src/osgEarthDrivers/template_matclass/CMakeLists.txt
deleted file mode 100644
index dde6e8c..0000000
--- a/src/osgEarthDrivers/template_matclass/CMakeLists.txt
+++ /dev/null
@@ -1,16 +0,0 @@
-
-SET(TARGET_COMMON_LIBRARIES ${TARGET_COMMON_LIBRARIES} osgEarthFeatures osgEarthSymbology)
-
-SET(TARGET_SRC
-	TemplateMatClassDriver.cpp)
-	
-SET(TARGET_H
-	TemplateMatClassOptions
-)
-
-SETUP_PLUGIN(osgearth_template_matclass)
-
-# to install public driver includes:
-SET(LIB_NAME template_matclass)
-SET(LIB_PUBLIC_HEADERS TemplateMatClassOptions)
-INCLUDE(ModuleInstallOsgEarthDriverIncludes OPTIONAL)
diff --git a/src/osgEarthDrivers/template_matclass/TemplateMatClassDriver.cpp b/src/osgEarthDrivers/template_matclass/TemplateMatClassDriver.cpp
deleted file mode 100644
index 9b9a130..0000000
--- a/src/osgEarthDrivers/template_matclass/TemplateMatClassDriver.cpp
+++ /dev/null
@@ -1,167 +0,0 @@
-/* -*-c++-*- */
-/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2016 Pelican Mapping
- * http://osgearth.org
- *
- * osgEarth is free software; you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>
- */
-
-#include <osgEarthFeatures/FeatureTileSource>
-#include <osgEarthFeatures/TransformFilter>
-#include <osgEarthSymbology/Style>
-#include <osgEarth/Registry>
-#include <osgEarth/FileUtils>
-#include <osgEarth/ImageLayer>
-
-#include <osg/Notify>
-#include <osgDB/FileNameUtils>
-#include <osgDB/FileUtils>
-#include <osgDB/Registry>
-#include <osgDB/ReadFile>
-#include <osgDB/WriteFile>
-
-#include "TemplateMatClassOptions"
-
-#include <sstream>
-
-#define LC "[MatClass] "
-
-using namespace osgEarth;
-using namespace osgEarth::Features;
-using namespace osgEarth::Drivers;
-
-/********************************************************************/
-
-class TemplateMatClassTileSource : public TileSource
-{
-private:
-    osg::ref_ptr<ImageLayer>     _imageLayer;
-    osg::ref_ptr<FeatureSource>  _featureSource;
-    osg::ref_ptr<osgDB::Options> _dbOptions;
-    TemplateMatClassOptions      _options;
-
-public:
-    TemplateMatClassTileSource( const TileSourceOptions& options ) :
-        TileSource( options ),
-        _options( options )
-    {
-        //nop
-    }
-
-public: // TileSource interface
-
-    // override
-    // One-time initialization. This is called by osgEarth.
-    Status initialize(const osgDB::Options* dbOptions)
-    {
-        _dbOptions = Registry::instance()->cloneOrCreateOptions(dbOptions);
-
-        const Profile* profile = getProfile();
-        if ( !profile )
-        {
-            profile = osgEarth::Registry::instance()->getGlobalGeodeticProfile();
-            setProfile( profile );
-        }
-
-        // load the image layer:
-        if ( _options.imageLayerOptions().isSet() )
-        {
-            _imageLayer = new ImageLayer( _options.imageLayerOptions().get() );
-            _imageLayer->setTargetProfileHint( profile );
-            // TODO: what about the cache?
-        }
-
-        // load the feature source:
-        if ( _options.featureSourceOptions().isSet() )
-        {
-            _featureSource = FeatureSourceFactory::create( _options.featureSourceOptions().get() );
-            _featureSource->open( _dbOptions.get() );
-        }
-
-        return Status::OK();
-    }
-
-    // Tells the layer not to cache data from this tile source.
-    CachePolicy getCachePolicyHint(const Profile* profile) const 
-    {
-        return CachePolicy::NO_CACHE;
-    }
-
-    // override
-    // Creates an image.
-    osg::Image* createImage(const TileKey&    key,
-                            ProgressCallback* progress )
-    {
-        if ( !_imageLayer.valid() || !_featureSource.valid() )
-            return 0L;
-
-        // fetch the image for this key:
-        GeoImage image = _imageLayer->createImage(key, progress);
-        if ( !image.valid() )
-            return 0L;
-
-        // fetch a set of features for this key. The features are in their
-        // own SRS, so we need to transform:
-        const SpatialReference* featureSRS = _featureSource->getFeatureProfile()->getSRS();
-        GeoExtent extentInFeatureSRS = key.getExtent().transform( featureSRS );
-
-        // assemble a spatial query. It helps if your features have a spatial index.
-        Query query;
-        query.bounds() = extentInFeatureSRS.bounds();
-        //query.expression() = ... // SQL expression compatible with data source
-        osg::ref_ptr<FeatureCursor> cursor = _featureSource->createFeatureCursor(query);
-
-        // create a new image to return.
-        osg::Image* output = new osg::Image();
-        //output->allocateImage(128, 128, 1, GL_RGB, GL_UNSIGNED_BYTE);
-
-        // do your magic here.
-
-        return output;
-    }
-};
-
-
-/**
- * Plugin entry point for the AGGLite feature rasterizer
- */
-class TemplateMatClassDriver : public TileSourceDriver
-{
-    public:
-        TemplateMatClassDriver() {}
-
-        virtual const char* className() const
-        {
-            return "Template mat class driver";
-        }
-        
-        virtual bool acceptsExtension(const std::string& extension) const
-        {
-            return
-                osgDB::equalCaseInsensitive( extension, "osgearth_template_matclass" );
-        }
-
-        virtual ReadResult readObject(const std::string& file_name, const Options* options) const
-        {
-            std::string ext = osgDB::getFileExtension( file_name );
-            if ( !acceptsExtension( ext ) )
-            {
-                return ReadResult::FILE_NOT_HANDLED;
-            }
-
-            return new TemplateMatClassTileSource( getTileSourceOptions(options) );
-        }
-};
-
-REGISTER_OSGPLUGIN(osgearth_template_matclass, TemplateMatClassDriver)
diff --git a/src/osgEarthDrivers/template_matclass/TemplateMatClassOptions b/src/osgEarthDrivers/template_matclass/TemplateMatClassOptions
deleted file mode 100644
index dcbc90e..0000000
--- a/src/osgEarthDrivers/template_matclass/TemplateMatClassOptions
+++ /dev/null
@@ -1,83 +0,0 @@
-/* -*-c++-*- */
-/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2016 Pelican Mapping
- * http://osgearth.org
- *
- * osgEarth is free software; you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>
- */
-#ifndef OSGEARTH_DRIVER_TEMPLATE_MAT_CLASS_DRIVEROPTIONS
-#define OSGEARTH_DRIVER_TEMPLATE_MAT_CLASS_DRIVEROPTIONS 1
-
-#include <osgEarth/Common>
-#include <osgEarth/TileSource>
-#include <osgEarthFeatures/FeatureSource>
-
-namespace osgEarth { namespace Drivers
-{
-    using namespace osgEarth;
-    using namespace osgEarth::Features;
-
-    /**
-     * Options structure for configuring the driver.
-     * getConfig() and fromConfig() let you serialize to/from the earth file.
-     */
-    class TemplateMatClassOptions : public TileSourceOptions // NO EXPORT; header only
-    {
-    public:
-        // Constructor - do not modify
-        TemplateMatClassOptions( const TileSourceOptions& options =TileSourceOptions() )
-            : TileSourceOptions( options )
-        {
-            setDriver( "template_matclass" );
-            fromConfig( _conf );
-        }
-
-        // Source image layer specification
-        optional<ImageLayerOptions>& imageLayerOptions() { return _imageLayerOptions; }
-        const optional<ImageLayerOptions>& imageLayerOptions() const { return _imageLayerOptions; }
-
-        // Vector feature source specification
-        optional<FeatureSourceOptions>& featureSourceOptions() { return _featureSourceOptions; }
-        const optional<FeatureSourceOptions>& featureSourceOptions() const { return _featureSourceOptions; }
-
-        // dtor
-        virtual ~TemplateMatClassOptions() { }
-
-    public:
-        Config getConfig() const {
-            Config conf = TileSourceOptions::getConfig();
-            conf.addObjIfSet( "image", _imageLayerOptions );
-            conf.addObjIfSet( "features", _featureSourceOptions );
-            return conf;
-        }
-
-    protected:
-        void mergeConfig( const Config& conf ) {
-            TileSourceOptions::mergeConfig( conf );
-            fromConfig(conf);
-        }
-
-    private:
-        void fromConfig( const Config& conf ) {
-            conf.getObjIfSet( "image", _imageLayerOptions );
-            conf.getObjIfSet( "features", _featureSourceOptions );
-        }
-
-        optional<ImageLayerOptions>    _imageLayerOptions;
-        optional<FeatureSourceOptions> _featureSourceOptions;
-    };
-
-} } // namespace osgEarth::Drivers
-
-#endif // OSGEARTH_DRIVER_TEMPLATE_MAT_CLASS_DRIVEROPTIONS
diff --git a/src/osgEarthDrivers/terrainshader/TerrainShaderExtension.cpp b/src/osgEarthDrivers/terrainshader/TerrainShaderExtension.cpp
index be4da5a..4568c21 100644
--- a/src/osgEarthDrivers/terrainshader/TerrainShaderExtension.cpp
+++ b/src/osgEarthDrivers/terrainshader/TerrainShaderExtension.cpp
@@ -44,12 +44,12 @@ namespace
         {
             const std::vector<TerrainShaderOptions::Code>& code = _options.code();
 
-            for(unsigned i=0; i<code.size(); ++i)
+            for (unsigned i = 0; i < code.size(); ++i)
             {
-		std::ostringstream oss;
-		oss << i;
+                std::ostringstream oss;
+                oss << i;
                 std::string fn = code[i]._uri.isSet() ? code[i]._uri->full() : "$code." + oss.str();
-                _package.add( fn, code[i]._source );
+                _package.add(fn, code[i]._source);
             }
         }
 
diff --git a/src/osgEarthDrivers/tilecache/CMakeLists.txt b/src/osgEarthDrivers/tilecache/CMakeLists.txt
deleted file mode 100644
index 2fced27..0000000
--- a/src/osgEarthDrivers/tilecache/CMakeLists.txt
+++ /dev/null
@@ -1,17 +0,0 @@
-
-
-SET(TARGET_COMMON_LIBRARIES ${TARGET_COMMON_LIBRARIES})
-
-SET(TARGET_SRC ReaderWriterTileCache.cpp)
-
-SET(TARGET_H TileCacheOptions)
-
-SETUP_PLUGIN(osgearth_tilecache)
-
-
-
-# to install public driver includes:
-SET(LIB_NAME tilecache)
-SET(LIB_PUBLIC_HEADERS TileCacheOptions)
-INCLUDE(ModuleInstallOsgEarthDriverIncludes OPTIONAL)
-
diff --git a/src/osgEarthDrivers/tilecache/ReaderWriterTileCache.cpp b/src/osgEarthDrivers/tilecache/ReaderWriterTileCache.cpp
deleted file mode 100644
index 01329f5..0000000
--- a/src/osgEarthDrivers/tilecache/ReaderWriterTileCache.cpp
+++ /dev/null
@@ -1,131 +0,0 @@
-/* -*-c++-*- */
-/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2016 Pelican Mapping
- * http://osgearth.org
- *
- * osgEarth is free software; you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>
- */
-
-#include <osgEarth/TileSource>
-#include <osgEarth/Registry>
-#include <osgEarth/ImageToHeightFieldConverter>
-#include <osgEarth/FileUtils>
-#include <osgEarth/URI>
-
-#include <osg/Notify>
-#include <osgDB/FileNameUtils>
-#include <osgDB/FileUtils>
-#include <osgDB/Registry>
-#include <osgDB/ReadFile>
-#include <osgDB/WriteFile>
-
-#include "TileCacheOptions"
-
-#include <sstream>
-
-using namespace osgEarth;
-using namespace osgEarth::Drivers;
-
-
-class TileCacheSource : public TileSource
-{
-public:
-    TileCacheSource( const TileSourceOptions& options ) 
-        : TileSource( options ), _options( options )
-    {
-    }
-
-    Status initialize( const osgDB::Options* dbOptions )
-    {
-        _dbOptions = Registry::instance()->cloneOrCreateOptions(dbOptions);        
-
-        if ( !getProfile() )
-        {
-            // Assume it is global-geodetic
-            setProfile( osgEarth::Registry::instance()->getGlobalGeodeticProfile() );
-        }
-
-        return STATUS_OK;
-    }
-
-    osg::Image* createImage( const TileKey& key, ProgressCallback* progress)
-    {
-        unsigned int level, tile_x, tile_y;
-        level = key.getLevelOfDetail();
-        key.getTileXY( tile_x, tile_y );
-
-        unsigned int numCols, numRows;
-        key.getProfile()->getNumTiles(level, numCols, numRows);
-        
-        // need to invert the y-tile index
-        tile_y = numRows - tile_y - 1;
-
-        char buf[2048];
-        sprintf( buf, "%s/%s/%02d/%03d/%03d/%03d/%03d/%03d/%03d.%s",
-            _options.url()->full().c_str(),
-            _options.layer()->c_str(),
-            level,
-            (tile_x / 1000000),
-            (tile_x / 1000) % 1000,
-            (tile_x % 1000),
-            (tile_y / 1000000),
-            (tile_y / 1000) % 1000,
-            (tile_y % 1000),
-            _options.format()->c_str() );
-
-       
-        std::string path(buf);
-        return URI(path).readImage( _dbOptions.get(), progress ).releaseImage();
-    }
-
-    virtual std::string getExtension()  const 
-    {
-        return _options.format().value();
-    }
-
-private:
-    const TileCacheOptions       _options;
-    osg::ref_ptr<osgDB::Options> _dbOptions;
-};
-
-// Reads tiles from a TileCache disk cache.
-class TileCacheSourceFactory : public TileSourceDriver
-{
-    public:
-        TileCacheSourceFactory() {}
-
-        virtual const char* className() const
-        {
-            return "TileCache disk cache ReaderWriter";
-        }
-        
-        virtual bool acceptsExtension(const std::string& extension) const
-        {
-            return osgDB::equalCaseInsensitive( extension, "osgearth_tilecache" );
-        }
-
-        virtual ReadResult readObject(const std::string& file_name, const Options* options) const
-        {
-            std::string ext = osgDB::getFileExtension( file_name );
-            if ( !acceptsExtension( ext ) )
-            {
-                return ReadResult::FILE_NOT_HANDLED;
-            }
-
-            return new TileCacheSource( getTileSourceOptions(options) );
-        }
-};
-
-REGISTER_OSGPLUGIN(osgearth_tilecache, TileCacheSourceFactory)
-
diff --git a/src/osgEarthDrivers/tilecache/TileCacheOptions b/src/osgEarthDrivers/tilecache/TileCacheOptions
deleted file mode 100644
index 8c78438..0000000
--- a/src/osgEarthDrivers/tilecache/TileCacheOptions
+++ /dev/null
@@ -1,79 +0,0 @@
-/* -*-c++-*- */
-/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2016 Pelican Mapping
- * http://osgearth.org
- *
- * osgEarth is free software; you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>
- */
-#ifndef OSGEARTH_DRIVER_TILECACHE_DRIVEROPTIONS
-#define OSGEARTH_DRIVER_TILECACHE_DRIVEROPTIONS 1
-
-#include <osgEarth/Common>
-#include <osgEarth/TileSource>
-
-namespace osgEarth { namespace Drivers
-{
-    using namespace osgEarth;
-
-    class TileCacheOptions : public TileSourceOptions // NO EXPORT; header only
-    {
-    public:
-        optional<URI>& url() { return _url; }
-        const optional<URI>& url() const { return _url; }
-
-        optional<std::string>& layer() { return _layer; }
-        const optional<std::string>& layer() const { return _layer; }
-
-        optional<std::string>& format() { return _format; }
-        const optional<std::string>& format() const { return _format; }
-
-    public:
-        TileCacheOptions( const TileSourceOptions& opt =TileSourceOptions() ) :
-            TileSourceOptions( opt )
-        {
-            setDriver( "tilecache" );
-            fromConfig( _conf );
-        }
-
-        /** dtor */
-        virtual ~TileCacheOptions() { }
-
-    protected:
-        Config getConfig() const {
-            Config conf = TileSourceOptions::getConfig();
-            conf.updateIfSet("url", _url );
-            conf.updateIfSet("layer", _layer);
-            conf.updateIfSet("format", _format);
-            return conf;
-        }
-        void mergeConfig( const Config& conf ) {
-            TileSourceOptions::mergeConfig( conf );
-            fromConfig( conf );
-        }
-    private:
-        void fromConfig( const Config& conf ) {
-            conf.getIfSet( "url", _url );
-            conf.getIfSet( "layer", _layer );
-            conf.getIfSet( "format", _format );
-        }
-
-        optional<URI>         _url;
-        optional<std::string> _layer;
-        optional<std::string> _format;
-    };
-
-} } // namespace osgEarth::Drivers
-
-#endif // OSGEARTH_DRIVER_TILECACHE_DRIVEROPTIONS
-
diff --git a/src/osgEarthDrivers/tileindex/TileIndexOptions b/src/osgEarthDrivers/tileindex/TileIndexOptions
index 9257ec5..3ea4c91 100644
--- a/src/osgEarthDrivers/tileindex/TileIndexOptions
+++ b/src/osgEarthDrivers/tileindex/TileIndexOptions
@@ -51,7 +51,7 @@ namespace osgEarth { namespace Drivers
         Config getConfig() const
         {
             Config conf = TileSourceOptions::getConfig();
-            conf.updateIfSet( "url", _url );
+            conf.set( "url", _url );
             return conf;
         }
 
diff --git a/src/osgEarthDrivers/tileservice/CMakeLists.txt b/src/osgEarthDrivers/tileservice/CMakeLists.txt
deleted file mode 100644
index 2f7f4c8..0000000
--- a/src/osgEarthDrivers/tileservice/CMakeLists.txt
+++ /dev/null
@@ -1,15 +0,0 @@
-SET(TARGET_SRC
-    ReaderWriterTileService.cpp
-)
-SET(TARGET_H
-    TileServiceOptions
-)
-
-SETUP_PLUGIN(osgearth_tileservice)
-
-
-# to install public driver includes:
-SET(LIB_NAME tileservice)
-SET(LIB_PUBLIC_HEADERS TileServiceOptions)
-INCLUDE(ModuleInstallOsgEarthDriverIncludes OPTIONAL)
-
diff --git a/src/osgEarthDrivers/tileservice/ReaderWriterTileService.cpp b/src/osgEarthDrivers/tileservice/ReaderWriterTileService.cpp
deleted file mode 100644
index e6afb48..0000000
--- a/src/osgEarthDrivers/tileservice/ReaderWriterTileService.cpp
+++ /dev/null
@@ -1,138 +0,0 @@
-/* -*-c++-*- */
-/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2016 Pelican Mapping
- * http://osgearth.org
- *
- * osgEarth is free software; you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>
- */
-
-#include <osgEarth/TileSource>
-#include <osgEarth/ImageToHeightFieldConverter>
-#include <osgEarth/Registry>
-#include <osgEarth/URI>
-#include <osgEarth/StringUtils>
-
-#include <osgDB/FileNameUtils>
-#include <osgDB/FileUtils>
-#include <osgDB/Registry>
-#include <osgDB/ReadFile>
-#include <osgDB/WriteFile>
-
-#include <sstream>
-#include <stdlib.h>
-#include <iomanip>
-
-#include "TileServiceOptions"
-
-using namespace osgEarth;
-using namespace osgEarth::Drivers;
-
-//http://www.worldwindcentral.com/wiki/TileService
-class TileServiceSource : public TileSource
-{
-public:
-	TileServiceSource( const TileSourceOptions& options ) : TileSource( options ), _options(options)
-    {        
-        _formatToUse =
-            _options.format()->empty() ? "png" : _options.format().value();
-    }
-
-public:
-    Status initialize( const osgDB::Options* dbOptions )
-    {
-        _dbOptions = Registry::instance()->cloneOrCreateOptions(dbOptions);        
-
-        if ( !getProfile() )
-        {
-            // Assume it is global geodetic
-            setProfile( osgEarth::Registry::instance()->getGlobalGeodeticProfile() );
-        }
-
-        return STATUS_OK;
-    }
-
-public:
-    osg::Image* createImage(
-        const TileKey&        key,
-        ProgressCallback*     progress )
-    {        
-        return URI( createURL(key) ).readImage( _dbOptions.get(), progress ).releaseImage();
-    }
-
-    osg::HeightField* createHeightField(
-        const TileKey&        key,
-        ProgressCallback*     progress )
-    {
-        //NOP
-        return NULL;
-    }
-
-    std::string createURL( const TileKey& key ) const
-    {
-        unsigned int x, y;
-        key.getTileXY(x, y);
-
-        unsigned int lod = key.getLevelOfDetail()+1;
-
-        //http://s0.tileservice.worldwindcentral.com/getTile?interface=map&version=1&dataset=bmng.topo.bathy.200401&level=0&x=0&y=0
-        return Stringify() 
-            << _options.url()->full() << "interface=map&version=1"
-            << "&dataset=" << _options.dataset().value()
-            << "&level=" << lod
-            << "&x=" << x
-            << "&y=" << y
-            << "&." << _formatToUse; //Add this to trick osg into using the correct loader.
-    }
-
-    virtual std::string getExtension()  const 
-    {
-        return _formatToUse;
-    }
-
-private:
-    std::string                  _formatToUse;
-    const TileServiceOptions     _options;
-    osg::ref_ptr<osgDB::Options> _dbOptions;
-};
-
-
-class TileServiceSourceFactory : public TileSourceDriver
-{
-    public:
-        TileServiceSourceFactory() {}
-
-        virtual const char* className() const
-        {
-            return "TileService Reader";
-        }
-        
-        virtual bool acceptsExtension(const std::string& extension) const
-        {
-            return osgDB::equalCaseInsensitive( extension, "osgearth_tileservice" );
-        }
-
-        virtual ReadResult readObject(const std::string& file_name, const Options* opt) const
-        {
-            std::string ext = osgDB::getFileExtension( file_name );
-            if ( !acceptsExtension( ext ) )
-            {
-                return ReadResult::FILE_NOT_HANDLED;
-            }
-
-            return new TileServiceSource( getTileSourceOptions(opt) );
-        }
-};
-
-REGISTER_OSGPLUGIN(osgearth_tileservice, TileServiceSourceFactory)
-
diff --git a/src/osgEarthDrivers/tileservice/TileServiceOptions b/src/osgEarthDrivers/tileservice/TileServiceOptions
deleted file mode 100644
index 68915ec..0000000
--- a/src/osgEarthDrivers/tileservice/TileServiceOptions
+++ /dev/null
@@ -1,79 +0,0 @@
-/* -*-c++-*- */
-/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2016 Pelican Mapping
- * http://osgearth.org
- *
- * osgEarth is free software; you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>
- */
-#ifndef OSGEARTH_DRIVER_TILESERVICE_DRIVEROPTIONS
-#define OSGEARTH_DRIVER_TILESERVICE_DRIVEROPTIONS 1
-
-#include <osgEarth/Common>
-#include <osgEarth/TileSource>
-
-namespace osgEarth { namespace Drivers
-{
-    using namespace osgEarth;
-
-    class TileServiceOptions : public TileSourceOptions // NO EXPORT; header only
-    {
-    public:
-        optional<URI>& url() { return _url; }
-        const optional<URI>& url() const { return _url; }
-
-        optional<std::string>& dataset() { return _dataset; }
-        const optional<std::string>& dataset() const { return _dataset; }
-
-        optional<std::string>& format() { return _format; }
-        const optional<std::string>& format() const { return _format; }
-
-    public:
-        TileServiceOptions( const TileSourceOptions& opt =TileSourceOptions() ) : TileSourceOptions( opt )
-        {
-            setDriver( "tileservice" );
-            fromConfig( _conf );
-        }
-
-        /** dtor */
-        virtual ~TileServiceOptions() { }
-
-    public:
-        Config getConfig() const {
-            Config conf = TileSourceOptions::getConfig();
-            conf.updateIfSet("dataset", _dataset);
-            conf.updateIfSet("url", _url );
-            conf.updateIfSet("format", _format);
-            return conf;
-        }
-    protected:
-        void mergeConfig( const Config& conf ) {
-            TileSourceOptions::mergeConfig( conf );
-            fromConfig(conf);
-        }
-
-    private:
-        void fromConfig( const Config& conf ) {
-            conf.getIfSet( "dataset", _dataset );
-            conf.getIfSet( "url", _url );
-            conf.getIfSet( "format", _format );
-        }
-
-        optional<URI> _url;
-        optional<std::string> _dataset, _format;
-    };
-
-} } // namespace osgEarth::Drivers
-
-#endif // OSGEARTH_DRIVER_TILESERVICE_DRIVEROPTIONS
-
diff --git a/src/osgEarthDrivers/tms/TMSOptions b/src/osgEarthDrivers/tms/TMSOptions
index cbfd75d..2088559 100644
--- a/src/osgEarthDrivers/tms/TMSOptions
+++ b/src/osgEarthDrivers/tms/TMSOptions
@@ -71,9 +71,9 @@ namespace osgEarth { namespace Drivers
 
         Config getConfig() const {
             Config conf = TileSourceOptions::getConfig();
-            conf.updateIfSet("url", _url);
-            conf.updateIfSet("tms_type", _tmsType);
-            conf.updateIfSet("format", _format);
+            conf.set("url", _url);
+            conf.set("tms_type", _tmsType);
+            conf.set("format", _format);
             return conf;
         }
 
diff --git a/src/osgEarthDrivers/tms/TMSTileSource.cpp b/src/osgEarthDrivers/tms/TMSTileSource.cpp
index 79fe7ac..6ab6665 100644
--- a/src/osgEarthDrivers/tms/TMSTileSource.cpp
+++ b/src/osgEarthDrivers/tms/TMSTileSource.cpp
@@ -94,15 +94,17 @@ TMSTileSource::initialize(const osgDB::Options* dbOptions)
             profile,
             extents,
             _options.format().value(),
-            _options.tileSize().value(), 
-            _options.tileSize().value() );
+            256,
+            256);
+            //getPixelsPerTile(),
+            //getPixelsPerTile() );
 
         // If this is a new repo, write the tilemap file to disk now.
         if ( isNewRepo )
         {
             if ( !_options.format().isSet() )
             {
-                return Status::Error(Status::ConfigurationError, "Cannot create new repo with required [format] property");
+                return Status::Error(Status::ConfigurationError, "Missing required \"format\" property: e.g. png, jpg");
             }
 
             TMS::TileMapReaderWriter::write( _tileMap.get(), tmsURI.full() );
@@ -156,11 +158,25 @@ TMSTileSource::initialize(const osgDB::Options* dbOptions)
         }
         else
         {
-            //Push back a single area that encompasses the whole profile going up to the max level
-            this->getDataExtents().push_back(DataExtent(profile->getExtent(), 0, _tileMap->getMaxLevel()));
+            
+            GeoExtent extent;
+
+            // Push back a single area that covers the "bounds" present in the TMS dataset.
+            if (_tileMap.valid())
+            {
+                double minX, minY, maxX, maxY;
+                _tileMap->getExtents(minX, minY, maxX, maxY);
+                extent = GeoExtent(profile->getSRS(), minX, minY, maxX, maxY);
+            }
+
+            // If the extent isn't valid, use the profile's extent.
+            if (!extent.isValid() || extent.width() <= 0.0 || extent.height() <= 0.0 )
+            {
+                extent = profile->getExtent();
+            }
+            this->getDataExtents().push_back(DataExtent(extent, 0, _tileMap->getMaxLevel()));
         }
     }
- 
     return STATUS_OK;
 }
 
@@ -223,7 +239,7 @@ TMSTileSource::createImage(const TileKey&    key,
         if (image.valid() && _options.coverage() == true)
         {
             image->setInternalTextureFormat(GL_LUMINANCE32F_ARB);
-            ImageUtils::markAsUnNormalized(image, true);
+            ImageUtils::markAsUnNormalized(image.get(), true);
         }
 
         return image.release();
diff --git a/src/osgEarthDrivers/vpb/VPBOptions b/src/osgEarthDrivers/vpb/VPBOptions
index 68766e2..2ce1fbe 100644
--- a/src/osgEarthDrivers/vpb/VPBOptions
+++ b/src/osgEarthDrivers/vpb/VPBOptions
@@ -89,15 +89,15 @@ namespace osgEarth { namespace Drivers
     public:
         Config getConfig() const {
             Config conf = TileSourceOptions::getConfig();
-            conf.updateIfSet("url", _url);
-            conf.updateIfSet("primary_split_level", _primarySplitLevel);
-            conf.updateIfSet("secondary_split_level", _secondarySplitLevel);
-            conf.updateIfSet("layer", _layer);
-            conf.updateIfSet("layer_setname", _layerSetName);
-            conf.updateIfSet("num_tiles_wide_at_lod_0", _widthLod0 );
-            conf.updateIfSet("num_tiles_high_at_lod_0", _heightLod0 );
-            conf.updateIfSet("base_name", _baseName );
-            conf.updateIfSet("terrain_tile_cache_size", _terrainTileCacheSize);
+            conf.set("url", _url);
+            conf.set("primary_split_level", _primarySplitLevel);
+            conf.set("secondary_split_level", _secondarySplitLevel);
+            conf.set("layer", _layer);
+            conf.set("layer_setname", _layerSetName);
+            conf.set("num_tiles_wide_at_lod_0", _widthLod0 );
+            conf.set("num_tiles_high_at_lod_0", _heightLod0 );
+            conf.set("base_name", _baseName );
+            conf.set("terrain_tile_cache_size", _terrainTileCacheSize);
             if ( _dirStruct.isSet() ) {
                 if ( _dirStruct == DS_FLAT ) conf.update("directory_structure", "flat");
                 else if ( _dirStruct == DS_TASK ) conf.update("directory_structure", "task");
diff --git a/src/osgEarthDrivers/wcs/WCS11Source.cpp b/src/osgEarthDrivers/wcs/WCS11Source.cpp
index bad44a9..5514a77 100644
--- a/src/osgEarthDrivers/wcs/WCS11Source.cpp
+++ b/src/osgEarthDrivers/wcs/WCS11Source.cpp
@@ -148,8 +148,8 @@ WCS11Source::createRequest( const TileKey& key ) const
     double lon_min, lat_min, lon_max, lat_max;
     key.getExtent().getBounds( lon_min, lat_min, lon_max, lat_max );
 
-    int lon_samples = _options.tileSize().value();
-    int lat_samples = _options.tileSize().value();
+    int lon_samples = getPixelsPerTile();
+    int lat_samples = getPixelsPerTile();
     double lon_interval = (lon_max-lon_min)/(double)(lon_samples-1);
     double lat_interval = (lat_max-lat_min)/(double)(lat_samples-1);
 
diff --git a/src/osgEarthDrivers/wcs/WCSOptions b/src/osgEarthDrivers/wcs/WCSOptions
index fbe661f..1cf8807 100644
--- a/src/osgEarthDrivers/wcs/WCSOptions
+++ b/src/osgEarthDrivers/wcs/WCSOptions
@@ -64,12 +64,12 @@ namespace osgEarth { namespace Drivers
     public:
         Config getConfig() const {
             Config conf = TileSourceOptions::getConfig();
-            conf.updateIfSet("url", _url);
-            conf.updateIfSet("identifier", _identifier);
-            conf.updateIfSet("format", _format);
-            conf.updateIfSet("elevation_unit", _elevationUnit);
-            conf.updateIfSet("srs", _srs);
-            conf.updateIfSet("range_subset", _rangeSubset);
+            conf.set("url", _url);
+            conf.set("identifier", _identifier);
+            conf.set("format", _format);
+            conf.set("elevation_unit", _elevationUnit);
+            conf.set("srs", _srs);
+            conf.set("range_subset", _rangeSubset);
             return conf;
         }
 
diff --git a/src/osgEarthDrivers/wms/ReaderWriterWMS.cpp b/src/osgEarthDrivers/wms/ReaderWriterWMS.cpp
index ade62fc..93e0ebb 100644
--- a/src/osgEarthDrivers/wms/ReaderWriterWMS.cpp
+++ b/src/osgEarthDrivers/wms/ReaderWriterWMS.cpp
@@ -151,8 +151,8 @@ public:
             << "&FORMAT=" << ( wmsFormatToUse.empty() ? std::string("image/") + _formatToUse : wmsFormatToUse )
             << "&STYLES=" << _options.style().value()
             << (_options.wmsVersion().value() == "1.3.0" ? "&CRS=" : "&SRS=") << _srsToUse            
-            << "&WIDTH="<< _options.tileSize().value()
-            << "&HEIGHT="<< _options.tileSize().value()
+            << "&WIDTH="<< getPixelsPerTile()
+            << "&HEIGHT=" << getPixelsPerTile()
             << "&BBOX=%lf,%lf,%lf,%lf";
 
         // then the optional keys:
@@ -246,8 +246,8 @@ public:
                 _formatToUse,
                 _options.style().value(),
                 _srsToUse,
-                _options.tileSize().value(),
-                _options.tileSize().value(),
+                getPixelsPerTile(),
+                getPixelsPerTile(),
                 patterns );
 
             if (patterns.size() > 0)
@@ -448,7 +448,7 @@ public:
             return ImageUtils::createEmptyImage();
         }
 
-        _sequenceCache.insert( seq );
+        _sequenceCache.insert( seq.get() );
         return seq.release();
     }
 
@@ -492,11 +492,6 @@ public:
         return uri;
     }
 
-    virtual int getPixelsPerTile() const
-    {
-        return _options.tileSize().value();
-    }
-
     virtual std::string getExtension()  const 
     {
         return _formatToUse;
diff --git a/src/osgEarthDrivers/wms/WMSOptions b/src/osgEarthDrivers/wms/WMSOptions
index b2f259c..bf01df3 100644
--- a/src/osgEarthDrivers/wms/WMSOptions
+++ b/src/osgEarthDrivers/wms/WMSOptions
@@ -89,20 +89,20 @@ namespace osgEarth { namespace Drivers
     public:
         Config getConfig() const {
             Config conf = TileSourceOptions::getConfig();
-            conf.updateIfSet("url", _url);
-            conf.updateIfSet("capabilities_url", _capabilitiesUrl);
-            conf.updateIfSet("tile_service_url", _tileServiceUrl);
-            conf.updateIfSet("layers", _layers);
-            conf.updateIfSet("style", _style);
-            conf.updateIfSet("format", _format);
-            conf.updateIfSet("wms_format", _wmsFormat);
-            conf.updateIfSet("wms_version", _wmsVersion);
-            conf.updateIfSet("elevation_unit", _elevationUnit);
-            conf.updateIfSet("srs", _srs);
-            conf.updateIfSet("crs", _crs);
-            conf.updateIfSet("transparent", _transparent);
-            conf.updateIfSet("times", _times);
-            conf.updateIfSet("seconds_per_frame", _secondsPerFrame );
+            conf.set("url", _url);
+            conf.set("capabilities_url", _capabilitiesUrl);
+            conf.set("tile_service_url", _tileServiceUrl);
+            conf.set("layers", _layers);
+            conf.set("style", _style);
+            conf.set("format", _format);
+            conf.set("wms_format", _wmsFormat);
+            conf.set("wms_version", _wmsVersion);
+            conf.set("elevation_unit", _elevationUnit);
+            conf.set("srs", _srs);
+            conf.set("crs", _crs);
+            conf.set("transparent", _transparent);
+            conf.set("times", _times);
+            conf.set("seconds_per_frame", _secondsPerFrame );
             return conf;
         }
 
@@ -127,6 +127,7 @@ namespace osgEarth { namespace Drivers
             conf.getIfSet("crs", _crs);
             conf.getIfSet("transparent", _transparent);
             conf.getIfSet("times", _times);
+            conf.getIfSet("time", _times); // alternative
             conf.getIfSet("seconds_per_frame", _secondsPerFrame );
         }
 
diff --git a/src/osgEarthDrivers/xyz/ReaderWriterXYZ.cpp b/src/osgEarthDrivers/xyz/ReaderWriterXYZ.cpp
index a331b08..d922d9a 100644
--- a/src/osgEarthDrivers/xyz/ReaderWriterXYZ.cpp
+++ b/src/osgEarthDrivers/xyz/ReaderWriterXYZ.cpp
@@ -50,94 +50,134 @@ class XYZSource : public TileSource
 {
 public:
     XYZSource(const TileSourceOptions& options) : 
-        TileSource(options), _options(options), _rotate_iter(0u), _rotateStart(0), _rotateEnd(0)
-    {
-        //nop
-    }
-
-
-    Status initialize(const osgDB::Options* dbOptions)
-    {
-        _dbOptions = Registry::instance()->cloneOrCreateOptions(dbOptions);        
-
-        URI xyzURI = _options.url().value();
-        if ( xyzURI.empty() )
-        {
-            return Status::Error( Status::ConfigurationError, "Fail: driver requires a valid \"url\" property" );
-        }
-
-        // driver requires a profile.
-        if ( !getProfile() )
-        {
-            return Status::Error( Status::ConfigurationError, "An explicit profile definition is required by the XYZ driver." );
-        }
-
-        _template = xyzURI.full();
-        
-        _rotateStart = _template.find("[");
-        _rotateEnd   = _template.find("]");
-        if ( _rotateStart != std::string::npos && _rotateEnd != std::string::npos && _rotateEnd-_rotateStart > 1 )
-        {
-            _rotateString  = _template.substr(_rotateStart, _rotateEnd-_rotateStart+1);
-            _rotateChoices = _template.substr(_rotateStart+1, _rotateEnd-_rotateStart-1);
-        }
-
-        _format = _options.format().isSet() 
-            ? *_options.format()
-            : osgDB::getLowerCaseFileExtension( xyzURI.base() );
-
-        return STATUS_OK;
-    }
-
-
-    osg::Image* createImage(const TileKey&     key,
-                            ProgressCallback*  progress )
-    {
-        unsigned x, y;
-        key.getTileXY( x, y );
-
-        if ( _options.invertY() == true )
-        {
-            unsigned cols=0, rows=0;
-            key.getProfile()->getNumTiles( key.getLevelOfDetail(), cols, rows );
-            y = rows - y - 1;
-        }
-
-        std::string location = _template;
-
-        // support OpenLayers template style:
-        replaceIn( location, "${x}", Stringify() << x );
-        replaceIn( location, "${y}", Stringify() << y );
-        replaceIn( location, "${z}", Stringify() << key.getLevelOfDetail() );
-
-        // failing that, legacy osgearth style:
-        replaceIn( location, "{x}", Stringify() << x );
-        replaceIn( location, "{y}", Stringify() << y );
-        replaceIn( location, "{z}", Stringify() << key.getLevelOfDetail() );
-
-        std::string cacheKey;
-
-        if ( !_rotateChoices.empty() )
-        {
-            cacheKey = location;
-            unsigned index = (++_rotate_iter) % _rotateChoices.size();
-            replaceIn( location, _rotateString, Stringify() << _rotateChoices[index] );
-        }
-
-
-        URI uri( location, _options.url()->context() );
-        if ( !cacheKey.empty() )
-            uri.setCacheKey( cacheKey );
-
-        OE_TEST << LC << "URI: " << uri.full() << ", key: " << uri.cacheKey() << std::endl;
-
-        return uri.getImage( _dbOptions.get(), progress );
-    }
-
-    virtual std::string getExtension() const 
-    {
-        return _format;
-    }
+      TileSource(options), _options(options), _rotate_iter(0u), _rotateStart(0), _rotateEnd(0)
+      {
+          //nop
+      }
+
+
+      Status initialize(const osgDB::Options* dbOptions)
+      {
+          _dbOptions = Registry::instance()->cloneOrCreateOptions(dbOptions);        
+
+          URI xyzURI = _options.url().value();
+          if ( xyzURI.empty() )
+          {
+              return Status::Error( Status::ConfigurationError, "Fail: driver requires a valid \"url\" property" );
+          }
+
+          // driver requires a profile.
+          if ( !getProfile() )
+          {
+              return Status::Error( Status::ConfigurationError, "An explicit profile definition is required by the XYZ driver." );
+          }
+
+          _template = xyzURI.full();
+
+          _rotateStart = _template.find("[");
+          _rotateEnd   = _template.find("]");
+          if ( _rotateStart != std::string::npos && _rotateEnd != std::string::npos && _rotateEnd-_rotateStart > 1 )
+          {
+              _rotateString  = _template.substr(_rotateStart, _rotateEnd-_rotateStart+1);
+              _rotateChoices = _template.substr(_rotateStart+1, _rotateEnd-_rotateStart-1);
+          }
+
+          _format = _options.format().isSet() 
+              ? *_options.format()
+              : osgDB::getLowerCaseFileExtension( xyzURI.base() );
+
+          return STATUS_OK;
+      }
+
+
+      osg::Image* createImage(const TileKey&     key,
+          ProgressCallback*  progress )
+      {
+          unsigned x, y;
+          key.getTileXY( x, y );
+
+          if ( _options.invertY() == true )
+          {
+              unsigned cols=0, rows=0;
+              key.getProfile()->getNumTiles( key.getLevelOfDetail(), cols, rows );
+              y = rows - y - 1;
+          }
+
+          std::string location = _template;
+
+          // support OpenLayers template style:
+          replaceIn( location, "${x}", Stringify() << x );
+          replaceIn( location, "${y}", Stringify() << y );
+          replaceIn( location, "${z}", Stringify() << key.getLevelOfDetail() );
+
+          // failing that, legacy osgearth style:
+          replaceIn( location, "{x}", Stringify() << x );
+          replaceIn( location, "{y}", Stringify() << y );
+          replaceIn( location, "{z}", Stringify() << key.getLevelOfDetail() );
+
+          std::string cacheKey;
+
+          if ( !_rotateChoices.empty() )
+          {
+              cacheKey = location;
+              unsigned index = (++_rotate_iter) % _rotateChoices.size();
+              replaceIn( location, _rotateString, Stringify() << _rotateChoices[index] );
+          }
+
+
+          URI uri( location, _options.url()->context() );
+          if ( !cacheKey.empty() )
+              uri.setCacheKey( cacheKey );
+
+          OE_TEST << LC << "URI: " << uri.full() << ", key: " << uri.cacheKey() << std::endl;
+
+          return uri.getImage( _dbOptions.get(), progress );
+      }
+
+      virtual std::string getExtension() const 
+      {
+          return _format;
+      }
+
+      osg::HeightField* createHeightField( const TileKey&        key,
+          ProgressCallback*     progress)
+      {
+          // MapBox encoded elevation PNG.
+          // https://www.mapbox.com/blog/terrain-rgb/
+          if (_options.elevationEncoding().value() == "mapbox")
+          {
+              if (getStatus().isError())
+                  return 0L;
+
+              osg::HeightField *hf = 0;
+              osg::ref_ptr<osg::Image> image = createImage(key, progress);
+              if (image.valid())
+              {
+                  // Allocate the heightfield.
+                  hf = new osg::HeightField();
+                  hf->allocate( image->s(), image->t() );
+
+                  ImageUtils::PixelReader reader(image.get());
+                  for (unsigned int c = 0; c < image->s(); c++)
+                  {
+                      for (unsigned int r = 0; r < image->t(); r++)
+                      {
+                          osg::Vec4 pixel = reader(c, r);
+                          pixel.r() *= 255.0;
+                          pixel.g() *= 255.0;
+                          pixel.b() *= 255.0;
+                          float h = -10000.0f + ((pixel.r() * 256.0f * 256.0f + pixel.g() * 256.0f + pixel.b()) * 0.1f);
+                          hf->setHeight(c, r, h);
+                      }
+                  }
+              }
+              return hf;        
+          }
+          else
+          {
+              return TileSource::createHeightField( key, progress );
+          }
+      }
 
 private:
     const XYZOptions       _options;
diff --git a/src/osgEarthDrivers/xyz/XYZOptions b/src/osgEarthDrivers/xyz/XYZOptions
index f6b6cbf..ce3a541 100644
--- a/src/osgEarthDrivers/xyz/XYZOptions
+++ b/src/osgEarthDrivers/xyz/XYZOptions
@@ -39,6 +39,11 @@ namespace osgEarth { namespace Drivers
         optional<std::string>& format() { return _format; }
         const optional<std::string>& format() const { return _format; }
 
+        optional<std::string>& elevationEncoding() { return _elevationEncoding; }
+        const optional<std::string>& elevationEncoding() const { return _elevationEncoding; }
+
+
+
     public:
         XYZOptions( const TileSourceOptions& opt =TileSourceOptions() ) : TileSourceOptions( opt )
         {
@@ -58,9 +63,10 @@ namespace osgEarth { namespace Drivers
     public:
         Config getConfig() const {
             Config conf = TileSourceOptions::getConfig();
-            conf.updateIfSet("url", _url);
-            conf.updateIfSet("format", _format);
-            conf.updateIfSet("invert_y", _invertY);
+            conf.set("url", _url);
+            conf.set("format", _format);
+            conf.set("invert_y", _invertY);
+            conf.set("elevation_encoding", _elevationEncoding);
             return conf;
         }
 
@@ -75,11 +81,13 @@ namespace osgEarth { namespace Drivers
             conf.getIfSet( "url", _url );
             conf.getIfSet( "format", _format );
             conf.getIfSet( "invert_y", _invertY );
+            conf.getIfSet( "elevation_encoding", _elevationEncoding );
         }
 
         optional<URI>         _url;
         optional<std::string> _format;
         optional<bool>        _invertY;
+        optional<std::string> _elevationEncoding;
     };
 
 } } // namespace osgEarth::Drivers
diff --git a/src/osgEarthDrivers/yahoo/CMakeLists.txt b/src/osgEarthDrivers/yahoo/CMakeLists.txt
deleted file mode 100644
index 69e9194..0000000
--- a/src/osgEarthDrivers/yahoo/CMakeLists.txt
+++ /dev/null
@@ -1,9 +0,0 @@
-SET(TARGET_SRC ReaderWriterYahoo.cpp)
-SET(TARGET_H YahooOptions)
-SETUP_PLUGIN(osgearth_yahoo)
-
-# to install public driver includes:
-SET(LIB_NAME yahoo)
-SET(LIB_PUBLIC_HEADERS YahooOptions)
-INCLUDE(ModuleInstallOsgEarthDriverIncludes OPTIONAL)
-
diff --git a/src/osgEarthDrivers/yahoo/ReaderWriterYahoo.cpp b/src/osgEarthDrivers/yahoo/ReaderWriterYahoo.cpp
deleted file mode 100644
index f3408aa..0000000
--- a/src/osgEarthDrivers/yahoo/ReaderWriterYahoo.cpp
+++ /dev/null
@@ -1,151 +0,0 @@
-/* -*-c++-*- */
-/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2016 Pelican Mapping
- * http://osgearth.org
- *
- * osgEarth is free software; you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>
- */
-
-#include <osgEarth/TileSource>
-#include <osgEarth/Registry>
-#include <osgEarth/URI>
-
-#include <osg/Notify>
-#include <osgDB/FileNameUtils>
-#include <osgDB/FileUtils>
-#include <osgDB/Registry>
-#include <osgDB/ReadFile>
-#include <osgDB/WriteFile>
-#include <sstream>
-
-#include "YahooOptions"
-
-using namespace osgEarth;
-using namespace osgEarth::Drivers;
-
-class YahooSource : public TileSource
-{
-public:
-    YahooSource( const TileSourceOptions& options ) : TileSource( options ), _options(options)
-    {
-        //nop
-    }
-
-    // Yahoo! uses spherical mercator, but the top LOD is a 2x2 tile set.
-    Status initialize(const osgDB::Options* readOptions)
-    {
-        // no caching of source tiles.  Yahoo TOS does not allow it.
-        _readOptions = readOptions;
-
-        // always a sperhical mercator profile
-        setProfile( Profile::create( "spherical-mercator", "", 2, 2 ) );
-
-        return STATUS_OK;
-    }
-
-    osg::Image* createImage(const TileKey& key, ProgressCallback* progress)
-    {
-        std::stringstream buf;
-
-        std::string dataset = 
-            _options.dataset().isSet() ? _options.dataset().value() : "roads";
-        
-        if ( dataset == "roads" || dataset == "map" )
-        {            
-            // http://us.maps1.yimg.com/us.tile.maps.yimg.com/tl?v=4.1&md=2&x=0&y=0&z=2&r=1
-            unsigned int tile_x, tile_y;
-            key.getTileXY( tile_x, tile_y );
-            unsigned int zoom = key.getLevelOfDetail();
-            unsigned int size_x, size_y;
-            key.getProfile()->getNumTiles( zoom, size_x, size_y );
-
-            buf << "http://us.maps1.yimg.com/us.tile.maps.yimg.com/tl"
-                << "?v=4.1&md=2&r=1"
-                << "&x=" << (int)tile_x
-                << "&y=" << ((int)size_y-1-(int)tile_y) - (int)size_y/2
-                << "&z=" << zoom + 2;
-        }
-        else if ( dataset == "aerial" || dataset == "satellite" )
-        {
-            unsigned int tile_x, tile_y;
-            key.getTileXY( tile_x, tile_y );
-            unsigned int zoom = key.getLevelOfDetail();
-            unsigned int size_x, size_y;
-            key.getProfile()->getNumTiles( zoom, size_x, size_y );
-
-            buf << "http://us.maps3.yimg.com/aerial.maps.yimg.com/ximg"
-                << "?v=1.8&s=256&t=a&r=1"
-                << "&x=" << (int)tile_x
-                << "&y=" << ((int)size_y-1-(int)tile_y) - (int)size_y/2
-                << "&z=" << zoom + 2;
-        }
-
-        std::string base;
-        base = buf.str();
-
-        OE_DEBUG << key.str() << "=" << base << std::endl;
-
-        return URI(base).readImage( _readOptions.get() ).releaseImage();
-    }
-
-    osg::HeightField* createHeightField(const TileKey&        key,
-                                        ProgressCallback*     progress )
-    {
-        //NI
-        OE_WARN << "[Yahoo] Driver does not support heightfields" << std::endl;
-        return NULL;
-    }
-
-    std::string getExtension()  const 
-    {
-        //All Yahoo tiles are in JPEG format
-        return "jpg";
-    }
-    
-    /** Tell the terrain engine not to cache tiles form this source. */
-    CachePolicy getCachePolicyHint(const Profile*) const
-    {
-        return CachePolicy::NO_CACHE;
-    }
-
-private:
-    const YahooOptions                 _options;
-    osg::ref_ptr<const osgDB::Options> _readOptions;
-};
-
-
-class ReaderWriterYahoo : public TileSourceDriver
-{
-    public:
-        ReaderWriterYahoo()
-        {
-            supportsExtension( "osgearth_yahoo", "Yahoo maps data" );
-        }
-
-        virtual const char* className() const
-        {
-            return "Yahoo Imagery ReaderWriter";
-        }
-
-        virtual ReadResult readObject(const std::string& file_name, const Options* options) const
-        {
-            if ( !acceptsExtension(osgDB::getLowerCaseFileExtension( file_name )))
-                return ReadResult::FILE_NOT_HANDLED;
-
-            return new YahooSource( getTileSourceOptions(options) );
-        }
-};
-
-REGISTER_OSGPLUGIN(osgearth_yahoo, ReaderWriterYahoo)
-
diff --git a/src/osgEarthDrivers/yahoo/YahooOptions b/src/osgEarthDrivers/yahoo/YahooOptions
deleted file mode 100644
index 097b85b..0000000
--- a/src/osgEarthDrivers/yahoo/YahooOptions
+++ /dev/null
@@ -1,69 +0,0 @@
-/* -*-c++-*- */
-/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2016 Pelican Mapping
- * http://osgearth.org
- *
- * osgEarth is free software; you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>
- */
-#ifndef OSGEARTH_DRIVER_YAHOO_DRIVEROPTIONS
-#define OSGEARTH_DRIVER_YAHOO_DRIVEROPTIONS 1
-
-#include <osgEarth/Common>
-#include <osgEarth/TileSource>
-
-namespace osgEarth { namespace Drivers
-{
-    using namespace osgEarth;
-
-    class YahooOptions : public TileSourceOptions // NO EXPORT; header only
-    {
-    public:
-        optional<std::string>& dataset() { return _dataset; }
-        const optional<std::string>& dataset() const { return _dataset; }
-
-    public:
-        YahooOptions( const TileSourceOptions& opt =TileSourceOptions() ) : TileSourceOptions( opt )
-        {
-            setDriver( "yahoo" );
-            fromConfig( _conf );
-        }
-
-        /** dtor */
-        virtual ~YahooOptions() { }
-
-    public:
-        Config getConfig() const {
-            Config conf = TileSourceOptions::getConfig();
-            conf.updateIfSet("dataset", _dataset);
-            return conf;
-        }
-
-    protected:
-        void mergeConfig( const Config& conf ) {
-            TileSourceOptions::mergeConfig( conf );            
-            fromConfig( conf );
-        }
-
-    private:
-        void fromConfig( const Config& conf ) {
-            conf.getIfSet( "dataset", _dataset );
-        }
-
-        optional<std::string> _dataset;
-    };
-
-} } // namespace osgEarth::Drivers
-
-#endif // OSGEARTH_DRIVER_YAHOO_DRIVEROPTIONS
-
diff --git a/src/osgEarthDriversDisabled/feature_mapnikvectortiles/CMakeLists.txt b/src/osgEarthDriversDisabled/feature_mapnikvectortiles/CMakeLists.txt
deleted file mode 100644
index 7f0214e..0000000
--- a/src/osgEarthDriversDisabled/feature_mapnikvectortiles/CMakeLists.txt
+++ /dev/null
@@ -1,29 +0,0 @@
-FIND_PACKAGE(Protobuf)
-
-IF(SQLITE3_FOUND AND PROTOBUF_FOUND)
-
-INCLUDE_DIRECTORIES( ${SQLITE3_INCLUDE_DIR} ${PROTOBUF_INCLUDE_DIR} ${CMAKE_CURRENT_BINARY_DIR})
-
-PROTOBUF_GENERATE_CPP(PROTO_CPP PROTO_H vector_tile.proto)
-
-SET(TARGET_SRC
-    FeatureSourceMVT.cpp
-    ${PROTO_CPP}
-)
-
-SET(TARGET_H   	
-    MVTFeatureOptions
-    ${PROTO_H}
-)
-
-SET(TARGET_COMMON_LIBRARIES ${TARGET_COMMON_LIBRARIES} osgEarthFeatures osgEarthSymbology osgEarthUtil)
-SET(TARGET_LIBRARIES_VARS SQLITE3_LIBRARY PROTOBUF_LIBRARY)
-SETUP_PLUGIN(osgearth_feature_mapnikvectortiles)
-
-
-# to install public driver includes:
-SET(LIB_NAME feature_mapnikvectortiles)
-SET(LIB_PUBLIC_HEADERS ${TARGET_H})
-INCLUDE(ModuleInstallOsgEarthDriverIncludes OPTIONAL)
-
-ENDIF(SQLITE3_FOUND AND PROTOBUF_FOUND)
\ No newline at end of file
diff --git a/src/osgEarthDriversDisabled/feature_mapnikvectortiles/FeatureSourceMVT.cpp b/src/osgEarthDriversDisabled/feature_mapnikvectortiles/FeatureSourceMVT.cpp
deleted file mode 100644
index 22b8e1e..0000000
--- a/src/osgEarthDriversDisabled/feature_mapnikvectortiles/FeatureSourceMVT.cpp
+++ /dev/null
@@ -1,446 +0,0 @@
-/* -*-c++-*- */
-/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
- * http://osgearth.org
- *
- * osgEarth is free software; you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>
- */
-#include "MVTFeatureOptions"
-
-#include <osgEarth/Registry>
-#include <osgEarth/XmlUtils>
-#include <osgEarth/FileUtils>
-#include <osgEarth/GeoData>
-#include <osgEarthFeatures/FeatureSource>
-#include <osgEarthFeatures/Filter>
-#include <osgEarthFeatures/BufferFilter>
-#include <osgEarthFeatures/ScaleFilter>
-#include <osg/Notify>
-#include <osgDB/FileNameUtils>
-#include <osgDB/FileUtils>
-#include <list>
-#include <stdio.h>
-#include <stdlib.h>
-#include <sqlite3.h>
-
-#include "vector_tile.pb.h"
-
-#define LC "[MVT FeatureSource] "
-
-using namespace osgEarth;
-using namespace osgEarth::Features;
-using namespace osgEarth::Drivers;
-
-#define CMD_BITS 3
-#define CMD_MOVETO 1
-#define CMD_LINETO 2
-#define CMD_CLOSEPATH 7
-
-// https://github.com/mapbox/mapnik-vector-tile/blob/master/examples/c%2B%2B/tileinfo.cpp
-enum CommandType {
-    SEG_END    = 0,
-    SEG_MOVETO = 1,
-    SEG_LINETO = 2,
-    SEG_CLOSE = (0x40 | 0x0f)
-};
-
-enum eGeomType {
-    Unknown = 0,
-    Point = 1,
-    LineString = 2,
-    Polygon = 3
-};
-
-class MVTFeatureSource : public FeatureSource
-{
-public:
-    MVTFeatureSource(const MVTFeatureOptions& options ) :
-      FeatureSource( options ),
-      _options     ( options )
-    {
-        _compressor = osgDB::Registry::instance()->getObjectWrapperManager()->findCompressor("zlib");
-        if (!_compressor.valid())
-        {
-           OE_WARN << LC << "Failed to get zlib compressor" << std::endl;
-        }
-    }
-
-    /** Destruct the object, cleaning up and OGR handles. */
-    virtual ~MVTFeatureSource()
-    {               
-        //nop
-    }
-
-    //override
-    void initialize( const osgDB::Options* dbOptions )
-    {
-        _dbOptions = dbOptions ? osg::clone(dbOptions) : 0L;
-        std::string fullFilename = _options.url()->full();
-
-        int rc = sqlite3_open_v2( fullFilename.c_str(), &_database, SQLITE_OPEN_READONLY, 0L );
-        if ( rc != 0 )
-        {          
-            OE_WARN << LC << "Failed to open database " << sqlite3_errmsg(_database);
-        }
-    }
-
-
-    /** Called once at startup to create the profile for this feature set. Successful profile
-        creation implies that the datasource opened succesfully. */
-    const FeatureProfile* createFeatureProfile()
-    {
-        const osgEarth::Profile* profile = osgEarth::Registry::instance()->getSphericalMercatorProfile();
-        FeatureProfile* result = new FeatureProfile(profile->getExtent());
-        result->setTiled(true);
-        std::string minLevelStr, maxLevelStr;
-        if (getMetaData("minzoom", minLevelStr) && getMetaData("maxzoom", maxLevelStr))
-        {
-            _minLevel = as<int>(minLevelStr, 0);
-            _maxLevel = as<int>(maxLevelStr, 0);
-            OE_INFO << LC << "Got levels from metadata " << _minLevel << ", " << _maxLevel << std::endl;
-        }
-        else
-        {
-            computeLevels();
-            OE_INFO << LC << "Got levels from database " << _minLevel << ", " << _maxLevel << std::endl;
-        }
-
-
-        // We use the max level for the first level as well since we don't really support
-        // non-additive feature sources yet.  Use the proper min level in the future.
-        result->setFirstLevel(_maxLevel);
-        result->setMaxLevel(_maxLevel);
-        result->setProfile(profile);
-        result->geoInterp() = osgEarth::GEOINTERP_RHUMB_LINE;
-        return result;
-    }
-
-    int zig_zag_decode(int n)
-    {
-        return (n >> 1) ^ (-(n & 1));
-    }
-
-    FeatureCursor* createFeatureCursor( const Symbology::Query& query )
-    {
-        //OE_NOTICE << "Getting feature cursor" << query.tileKey()->str() << std::endl;
-
-        TileKey key = *query.tileKey();
-
-        int z = key.getLevelOfDetail();
-        int tileX = key.getTileX();
-        int tileY = key.getTileY();
-
-        GeoPoint ll(key.getProfile()->getSRS(), key.getExtent().xMin(), key.getExtent().yMin(), 0, ALTMODE_ABSOLUTE);
-        ll = ll.transform(SpatialReference::create("epsg:4326"));
-        //OE_NOTICE << "Requesting key with ll " << ll.x() << ", " << ll.y() << std::endl;
-
-
-        unsigned int numRows, numCols;
-        key.getProfile()->getNumTiles(key.getLevelOfDetail(), numCols, numRows);
-        tileY  = numRows - tileY - 1;
-
-        //Get the image
-        sqlite3_stmt* select = NULL;
-        std::string queryStr = "SELECT tile_data from tiles where zoom_level = ? AND tile_column = ? AND tile_row = ?";
-        int rc = sqlite3_prepare_v2( _database, queryStr.c_str(), -1, &select, 0L );
-        if ( rc != SQLITE_OK )
-        {
-            OE_WARN << LC << "Failed to prepare SQL: " << queryStr << "; " << sqlite3_errmsg(_database) << std::endl;
-            return NULL;
-        }
-
-        bool valid = true;        
-
-        sqlite3_bind_int( select, 1, z );
-        sqlite3_bind_int( select, 2, tileX );
-        sqlite3_bind_int( select, 3, tileY );
-
-        rc = sqlite3_step( select );
-
-        FeatureList features;
-
-        if ( rc == SQLITE_ROW)
-        {                     
-            // the pointer returned from _blob gets freed internally by sqlite, supposedly
-            const char* data = (const char*)sqlite3_column_blob( select, 0 );
-            int dataLen = sqlite3_column_bytes( select, 0 );
-
-            std::string dataBuffer( data, dataLen );
-
-            // decompress if necessary:
-            if ( _compressor.valid() )
-            {
-                std::istringstream inputStream(dataBuffer);
-                std::string value;
-                if ( !_compressor->decompress(inputStream, value) )
-                {
-                    OE_WARN << LC << "Decompression failed" << std::endl;
-                    valid = false;
-                }
-                else
-                {
-                    dataBuffer = value;
-                }
-            }
-
-            
-            
-            mapnik::vector::tile tile;
-
-            if (tile.ParseFromString(dataBuffer))
-            {
-                // Get the layer in question
-                for (unsigned int i = 0; i < tile.layers().size(); i++)
-                {
-                    const mapnik::vector::tile_layer &layer = tile.layers().Get(i);
-
-                    for (unsigned int j = 0; j < layer.features().size(); j++)
-                    {
-                        const mapnik::vector::tile_feature &feature = layer.features().Get(j);
-
-                        osg::ref_ptr< osgEarth::Symbology::Geometry > geometry; 
-
-                        eGeomType geomType = static_cast<eGeomType>(feature.type());
-                        if (geomType == ::Polygon)
-                        {
-                            //OE_NOTICE << "Polygon " << std::endl;
-                            geometry = new osgEarth::Symbology::Polygon();
-                        }
-                        else if (geomType == ::LineString)
-                        {
-                            //OE_NOTICE << "LineString" << std::endl;
-                            geometry = new osgEarth::Symbology::LineString();
-                        }
-                        else if (geomType == ::Point)
-                        {
-                            //OE_NOTICE << "Point" << std::endl;
-                            geometry = new osgEarth::Symbology::PointSet();
-                        }
-                        else
-                        {
-                            //OE_NOTICE << "uknown" << std::endl;
-                            geometry = new osgEarth::Symbology::LineString();
-                        }
-
-                        osg::ref_ptr< Feature > oeFeature = new Feature(geometry, key.getProfile()->getSRS());
-                        features.push_back(oeFeature.get());
-
-                        unsigned int length = 0;
-                        unsigned int g_length = 0;
-                        int cmd = -1;
-                        const int cmd_bits = 3;
-
-                        unsigned int tileres = layer.extent();
-
-                        int x = 0;
-                        int y = 0;
-
-                        for (int k = 0; k < feature.geometry_size();)
-                        {
-                            if (!length)
-                            {
-                                unsigned int cmd_length = feature.geometry(k++);
-                                cmd = cmd_length & ((1 << cmd_bits) - 1);
-                                length = cmd_length >> cmd_bits;
-                                g_length = 0;
-                            } 
-                            if (length > 0)
-                            {
-                                length--;
-                                if (cmd == SEG_MOVETO || cmd == SEG_LINETO)
-                                {
-                                    int px = feature.geometry(k++);
-                                    int py = feature.geometry(k++);
-                                    px = zig_zag_decode(px);
-                                    py = zig_zag_decode(py);
-                                    if (cmd == SEG_MOVETO)
-                                    {
-                                        x += px;
-                                        y += py;
-                                    }
-                                    else if (cmd == SEG_LINETO)
-                                    {
-                                        x += px;
-                                        y += py;
-
-                                        double width = key.getExtent().width();
-                                        double height = key.getExtent().height();
-
-                                        double geoX = key.getExtent().xMin() + (width/(double)tileres) * (double)x;
-                                        double geoY = key.getExtent().yMax() - (height/(double)tileres) * (double)y;
-                                        geometry->push_back(geoX, geoY, 0);
-                                    }
-                                }
-                                else if (cmd == (SEG_CLOSE & ((1 << cmd_bits) - 1)))
-                                {
-                                    geometry->push_back(geometry->front());
-                                }
-                            }
-                        }
-
-                        geometry->rewind(osgEarth::Symbology::Geometry::ORIENTATION_CCW);
-
-                       
-                    }
-                }
-            }
-            else
-            {
-                OE_DEBUG << "Failed to parse, not surprising" << std::endl;
-            }
-        }
-        else
-        {
-            //OE_NOTICE << LC << "SQL QUERY failed for " << queryStr << ": " << std::endl;
-            valid = false;
-        }
-
-        sqlite3_finalize( select );
-
-        if (!features.empty())
-        {
-            return new FeatureListCursor(features);
-        }
-
-        return 0;
-    }
-
-    /**
-    * Gets the Feature with the given FID
-    * @returns
-    *     The Feature with the given FID or NULL if not found.
-    */
-    virtual Feature* getFeature( FeatureID fid )
-    {
-        return 0;
-    }
-
-    virtual bool isWritable() const
-    {
-        return false;
-    }
-
-    virtual const FeatureSchema& getSchema() const
-    {
-        //TODO:  Populate the schema from the DescribeFeatureType call
-        return _schema;
-    }
-
-    virtual osgEarth::Symbology::Geometry::Type getGeometryType() const
-    {
-        return Geometry::TYPE_UNKNOWN;
-    }
-
-    bool getMetaData(const std::string& key, std::string& value)
-    {
-        //get the metadata
-        sqlite3_stmt* select = NULL;
-        std::string query = "SELECT value from metadata where name = ?";
-        int rc = sqlite3_prepare_v2( _database, query.c_str(), -1, &select, 0L );
-        if ( rc != SQLITE_OK )
-        {
-            OE_WARN << LC << "Failed to prepare SQL: " << query << "; " << sqlite3_errmsg(_database) << std::endl;
-            return false;
-        }
-
-
-        bool valid = true;
-        std::string keyStr = std::string( key );
-        rc = sqlite3_bind_text( select, 1, keyStr.c_str(), keyStr.length(), SQLITE_STATIC );
-        if (rc != SQLITE_OK )
-        {
-            OE_WARN << LC << "Failed to bind text: " << query << "; " << sqlite3_errmsg(_database) << std::endl;
-            return false;
-        }
-
-        rc = sqlite3_step( select );
-        if ( rc == SQLITE_ROW)
-        {                     
-            value = (char*)sqlite3_column_text( select, 0 );
-        }
-        else
-        {
-            OE_DEBUG << LC << "SQL QUERY failed for " << query << ": " << std::endl;
-            valid = false;
-        }
-
-        sqlite3_finalize( select );
-        return valid;
-    }
-
-    void computeLevels()
-    {        
-
-        osg::Timer_t startTime = osg::Timer::instance()->tick();
-        sqlite3_stmt* select = NULL;
-        std::string query = "SELECT min(zoom_level), max(zoom_level) from tiles";
-        int rc = sqlite3_prepare_v2( _database, query.c_str(), -1, &select, 0L );
-        if ( rc != SQLITE_OK )
-        {
-            OE_WARN << LC << "Failed to prepare SQL: " << query << "; " << sqlite3_errmsg(_database) << std::endl;
-        }
-
-        rc = sqlite3_step( select );
-        if ( rc == SQLITE_ROW)
-        {                     
-            _minLevel = sqlite3_column_int( select, 0 );
-            _maxLevel = sqlite3_column_int( select, 1 );
-            OE_DEBUG << LC << "Min=" << _minLevel << " Max=" << _maxLevel << std::endl;
-        }
-        else
-        {
-            OE_DEBUG << LC << "SQL QUERY failed for " << query << ": " << std::endl;
-        }        
-        sqlite3_finalize( select );        
-        osg::Timer_t endTime = osg::Timer::instance()->tick();
-        OE_DEBUG << LC << "Computing levels took " << osg::Timer::instance()->delta_s(startTime, endTime ) << " s" << std::endl;
-    }
-
-
-
-private:
-    const MVTFeatureOptions         _options;    
-    FeatureSchema                   _schema;
-    osg::ref_ptr<osgDB::Options>    _dbOptions;    
-    osg::ref_ptr<osgDB::BaseCompressor> _compressor;
-    sqlite3* _database;
-    unsigned int _minLevel;
-    unsigned int _maxLevel;
-};
-
-
-class MVTFeatureSourceFactory : public FeatureSourceDriver
-{
-public:
-    MVTFeatureSourceFactory()
-    {
-        supportsExtension( "osgearth_feature_mapnikvectortiles", "Mapnik Vector Tiles feature driver for osgEarth" );
-    }
-
-    virtual const char* className()
-    {
-        return "Mapnik Vector Tiles Feature Reader";
-    }
-
-    virtual ReadResult readObject(const std::string& file_name, const Options* options) const
-    {
-        if ( !acceptsExtension(osgDB::getLowerCaseFileExtension( file_name )))
-            return ReadResult::FILE_NOT_HANDLED;
-
-        return ReadResult( new MVTFeatureSource( getFeatureSourceOptions(options) ) );
-    }
-};
-
-REGISTER_OSGPLUGIN(osgearth_feature_mapnikvectortiles, MVTFeatureSourceFactory)
-
diff --git a/src/osgEarthDriversDisabled/feature_mapnikvectortiles/vector_tile.proto b/src/osgEarthDriversDisabled/feature_mapnikvectortiles/vector_tile.proto
deleted file mode 100644
index e25ac3b..0000000
--- a/src/osgEarthDriversDisabled/feature_mapnikvectortiles/vector_tile.proto
+++ /dev/null
@@ -1,92 +0,0 @@
-// Protocol Version 1
-
-package mapnik.vector;
-
-option optimize_for = LITE_RUNTIME;
-
-message tile {
-        enum GeomType {
-             Unknown = 0;
-             Point = 1;
-             LineString = 2;
-             Polygon = 3;
-        }
-
-        // Variant type encoding
-        message value {
-                // Exactly one of these values may be present in a valid message
-                optional string string_value = 1;
-                optional float float_value = 2;
-                optional double double_value = 3;
-                optional int64 int_value = 4;
-                optional uint64 uint_value = 5;
-                optional sint64 sint_value = 6;
-                optional bool bool_value = 7;
-
-                extensions 8 to max;
-        }
-
-        message feature {
-                optional uint64 id = 1;
-
-                // Tags of this feature. Even numbered values refer to the nth
-                // value in the keys list on the tile message, odd numbered
-                // values refer to the nth value in the values list on the tile
-                // message.
-                repeated uint32 tags = 2 [ packed = true ];
-
-                // The type of geometry stored in this feature.
-                optional GeomType type = 3 [ default = Unknown ];
-
-                // Contains a stream of commands and parameters (vertices). The
-                // repeat count is shifted to the left by 3 bits. This means
-                // that the command has 3 bits (0-7). The repeat count
-                // indicates how often this command is to be repeated. Defined
-                // commands are:
-                // - MoveTo:    1   (2 parameters follow)
-                // - LineTo:    2   (2 parameters follow)
-                // - ClosePath: 7   (no parameters follow)
-                //
-                // Ex.: MoveTo(3, 6), LineTo(8, 12), LineTo(20, 34), ClosePath
-                // Encoded as: [ 9 3 6 18 5 6 12 22 7 ]
-                //                                  == command type 7 (ClosePath)
-                //                             ===== relative LineTo(+12, +22) == LineTo(20, 34)
-                //                         === relative LineTo(+5, +6) == LineTo(8, 12)
-                //                      == [00010 010] = command type 2 (LineTo), length 2
-                //                  === relative MoveTo(+3, +6)
-                //              == [00001 001] = command type 1 (MoveTo), length 1
-                // Commands are encoded as uint32 varints, vertex parameters are
-                // encoded as sint32 varints (zigzag). Vertex parameters are
-                // also encoded as deltas to the previous position. The original
-                // position is (0,0)
-                repeated uint32 geometry = 4 [ packed = true ];
-        }
-
-        message layer {
-                // Any compliant implementation must first read the version
-                // number encoded in this message and choose the correct
-                // implementation for this version number before proceeding to
-                // decode other parts of this message.
-                required uint32 version = 15 [ default = 1 ];
-
-                required string name = 1;
-
-                // The actual features in this tile.
-                repeated feature features = 2;
-
-                // Dictionary encoding for keys
-                repeated string keys = 3;
-
-                // Dictionary encoding for values
-                repeated value values = 4;
-
-                // The bounding box in this tile spans from 0..4095 units
-                optional uint32 extent = 5 [ default = 4096 ];
-
-                extensions 16 to max;
-        }
-
-        repeated layer layers = 3;
-
-        extensions 16 to 8191;
-}
diff --git a/src/osgEarthExtensions/CMakeLists.txt b/src/osgEarthExtensions/CMakeLists.txt
deleted file mode 100644
index 7210852..0000000
--- a/src/osgEarthExtensions/CMakeLists.txt
+++ /dev/null
@@ -1,29 +0,0 @@
-PROJECT(OSGEARTH_EXTENSIONS)
-
-INCLUDE_DIRECTORIES(${OSG_INCLUDE_DIRS} )
-
-SET(CMAKE_SHARED_MODULE_PREFIX ${OSGEARTH_PLUGIN_PREFIX})
-
-IF(MSVC80)
-  IF(NOT OSGEARTH_MSVC_GENERATE_PLUGINS_AND_WRAPPERS_MANIFESTS)
-    SET(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} /MANIFEST:NO")
-  ENDIF(NOT OSGEARTH_MSVC_GENERATE_PLUGINS_AND_WRAPPERS_MANIFESTS)
-ENDIF(MSVC80)
-
-SET(TARGET_DEFAULT_PREFIX "osgdb_")
-SET(TARGET_DEFAULT_LABEL_PREFIX "Extension")
-
-#OpenThreads, osg, osgDB and osgUtil are included elsewhere.
-SET(TARGET_COMMON_LIBRARIES 
-    osgEarth
-)
-
-# Folder name for plugins
-SET(OSGEARTH_EXTENSIONS_FOLDER Extensions)
-
-SUBDIRLIST(EXTENSION_DIRS ${CMAKE_CURRENT_SOURCE_DIR})
-
-FOREACH(subdir ${EXTENSION_DIRS})
-    # MESSAGE("Adding extension ${subdir}")
-    ADD_SUBDIRECTORY(${subdir})
-ENDFOREACH()
diff --git a/src/osgEarthFeatures/AltitudeFilter.cpp b/src/osgEarthFeatures/AltitudeFilter.cpp
index 2f8385d..9090d86 100644
--- a/src/osgEarthFeatures/AltitudeFilter.cpp
+++ b/src/osgEarthFeatures/AltitudeFilter.cpp
@@ -145,12 +145,13 @@ AltitudeFilter::pushAndDontClamp( FeatureList& features, FilterContext& cx )
 void
 AltitudeFilter::pushAndClamp( FeatureList& features, FilterContext& cx )
 {
+    OE_START_TIMER(pushAndClamp);
+    unsigned total = 0;
+
     const Session* session = cx.getSession();
 
     // the map against which we'll be doing elevation clamping
-    //MapFrame mapf = session->createMapFrame( Map::ELEVATION_LAYERS );
-    MapFrame mapf = session->createMapFrame( 
-        (Map::ModelParts)(Map::TERRAIN_LAYERS | Map::MODEL_LAYERS) );
+    MapFrame mapf = session->createMapFrame();
 
     const SpatialReference* mapSRS = mapf.getProfile()->getSRS();
     osg::ref_ptr<const SpatialReference> featureSRS = cx.profile()->getSRS();
@@ -158,9 +159,6 @@ AltitudeFilter::pushAndClamp( FeatureList& features, FilterContext& cx )
     // establish an elevation query interface based on the features' SRS.
     ElevationQuery eq( mapf );
 
-    // want a result even if it's low res
-    eq.setFallBackOnNoData( true );
-
     NumericExpression scaleExpr;
     if ( _altitude->verticalScale().isSet() )
         scaleExpr = *_altitude->verticalScale();
@@ -182,7 +180,6 @@ AltitudeFilter::pushAndClamp( FeatureList& features, FilterContext& cx )
     bool vertEquiv =
         featureSRS->isVertEquivalentTo( mapSRS );
 
-
     for( FeatureList::iterator i = features.begin(); i != features.end(); ++i )
     {
         Feature* feature = i->get();
@@ -209,7 +206,7 @@ AltitudeFilter::pushAndClamp( FeatureList& features, FilterContext& cx )
 
         osgEarth::Bounds bounds = feature->getGeometry()->getBounds();
         const osg::Vec2d& center = bounds.center2d();
-        GeoPoint centroid(featureSRS, center.x(), center.y());
+        GeoPoint centroid(featureSRS.get(), center.x(), center.y());
         double   centroidElevation = 0.0;
 
         // If we aren't doing per vertex clamping go ahead and get the centroid.
@@ -217,7 +214,12 @@ AltitudeFilter::pushAndClamp( FeatureList& features, FilterContext& cx )
         // are clamped to the whole multipolygon and not per polygon.
         if (!perVertex)
         {
-            eq.getElevation( centroid, centroidElevation, _maxRes );
+            centroidElevation = eq.getElevation( centroid, _maxRes );
+            // Check for NO_DATA_VALUE and use zero instead.
+            if (centroidElevation == NO_DATA_VALUE)
+            {
+                centroidElevation = 0.0;
+            }
         }
         
         GeometryIterator gi( feature->getGeometry() );
@@ -225,44 +227,48 @@ AltitudeFilter::pushAndClamp( FeatureList& features, FilterContext& cx )
         {
             Geometry* geom = gi.next();
 
+            total += geom->size();
+
             // Absolute heights in Z. Only need to collect the HATs; the geometry
             // remains unchanged.
             if ( _altitude->clamping() == AltitudeSymbol::CLAMP_ABSOLUTE )
             {
                 if ( perVertex )
                 {
-                    std::vector<double> elevations;
-                    elevations.reserve( geom->size() );
+                    std::vector<float> elevations;
 
-                    if ( eq.getElevations( geom->asVector(), featureSRS, elevations, _maxRes ) )
+                    if ( eq.getElevations( geom->asVector(), featureSRS.get(), elevations, _maxRes ) )
                     {
                         for( unsigned i=0; i<geom->size(); ++i )
                         {
                             osg::Vec3d& p = (*geom)[i];
 
-                            p.z() *= scaleZ;
-                            p.z() += offsetZ;
+                            if (elevations[i] != NO_DATA_VALUE)
+                            {
+                                p.z() *= scaleZ;
+                                p.z() += offsetZ;
 
-                            double z = p.z();
+                                double z = p.z();
 
-                            if ( !vertEquiv )
-                            {
-                                osg::Vec3d tempgeo;
-                                if ( !featureSRS->transform(p, mapSRS->getGeographicSRS(), tempgeo) )
-                                    z = tempgeo.z();
-                            }
+                                if ( !vertEquiv )
+                                {
+                                    osg::Vec3d tempgeo;
+                                    if ( !featureSRS->transform(p, mapSRS->getGeographicSRS(), tempgeo) )
+                                        z = tempgeo.z();
+                                }
 
-                            double hat = z - elevations[i];
+                                double hat = z - elevations[i];
 
-                            if ( hat > maxHAT )
-                                maxHAT = hat;
-                            if ( hat < minHAT )
-                                minHAT = hat;
+                                if ( hat > maxHAT )
+                                    maxHAT = hat;
+                                if ( hat < minHAT )
+                                    minHAT = hat;
 
-                            if ( elevations[i] > maxTerrainZ )
-                                maxTerrainZ = elevations[i];
-                            if ( elevations[i] < minTerrainZ )
-                                minTerrainZ = elevations[i];
+                                if ( elevations[i] > maxTerrainZ )
+                                    maxTerrainZ = elevations[i];
+                                if ( elevations[i] < minTerrainZ )
+                                    minTerrainZ = elevations[i];
+                            }
                         }
                     }
                 }
@@ -306,37 +312,40 @@ AltitudeFilter::pushAndClamp( FeatureList& features, FilterContext& cx )
 
                 if ( perVertex )
                 {
-                    std::vector<double> elevations;
+                    std::vector<float> elevations;
                     elevations.reserve( geom->size() );
-
-                    if ( eq.getElevations( geom->asVector(), featureSRS, elevations, _maxRes ) )
+                    
+                    if ( eq.getElevations( geom->asVector(), featureSRS.get(), elevations, _maxRes ) )
                     {
                         for( unsigned i=0; i<geom->size(); ++i )
                         {
                             osg::Vec3d& p = (*geom)[i];
 
-                            p.z() *= scaleZ;
-                            p.z() += offsetZ;
-
-                            double hat = p.z();
-                            p.z() = elevations[i] + p.z();
-
-                            // if necessary, convert the Z value (which is now in the map's SRS) back to
-                            // the feature's SRS.
-                            if ( !vertEquiv )
+                            if (elevations[i] != NO_DATA_VALUE)
                             {
-                                featureSRSwithMapVertDatum->transform(p, featureSRS, p);
+                                p.z() *= scaleZ;
+                                p.z() += offsetZ;
+
+                                double hat = p.z();
+                                p.z() = elevations[i] + p.z();
+
+                                // if necessary, convert the Z value (which is now in the map's SRS) back to
+                                // the feature's SRS.
+                                if ( !vertEquiv )
+                                {
+                                    featureSRSwithMapVertDatum->transform(p, featureSRS.get(), p);
+                                }
+
+                                if ( hat > maxHAT )
+                                    maxHAT = hat;
+                                if ( hat < minHAT )
+                                    minHAT = hat;
+
+                                if ( elevations[i] > maxTerrainZ )
+                                    maxTerrainZ = elevations[i];
+                                if ( elevations[i] < minTerrainZ )
+                                    minTerrainZ = elevations[i];
                             }
-
-                            if ( hat > maxHAT )
-                                maxHAT = hat;
-                            if ( hat < minHAT )
-                                minHAT = hat;
-
-                            if ( elevations[i] > maxTerrainZ )
-                                maxTerrainZ = elevations[i];
-                            if ( elevations[i] < minTerrainZ )
-                                minTerrainZ = elevations[i];
                         }
                     }
                 }
@@ -355,7 +364,7 @@ AltitudeFilter::pushAndClamp( FeatureList& features, FilterContext& cx )
                         // the feature's SRS.
                         if ( !vertEquiv )
                         {
-                            featureSRSwithMapVertDatum->transform(p, featureSRS, p);
+                            featureSRSwithMapVertDatum->transform(p, featureSRS.get(), p);
                         }
 
                         if ( hat > maxHAT )
@@ -376,7 +385,7 @@ AltitudeFilter::pushAndClamp( FeatureList& features, FilterContext& cx )
             {
                 if ( perVertex )
                 {
-                    eq.getElevations( geom->asVector(), featureSRS, true, _maxRes );
+                    eq.getElevations( geom->asVector(), featureSRS.get(), true, _maxRes );
                     
                     // if necessary, transform the Z values (which are now in the map SRS) back
                     // into the feature's SRS.
@@ -389,7 +398,7 @@ AltitudeFilter::pushAndClamp( FeatureList& features, FilterContext& cx )
                         for( unsigned i=0; i<geom->size(); ++i )
                         {
                             osg::Vec3d& p = (*geom)[i];
-                            featureSRSwithMapVertDatum->transform(p, featureSRS, p);
+                            featureSRSwithMapVertDatum->transform(p, featureSRS.get(), p);
                         }
                     }
                 }
@@ -405,7 +414,7 @@ AltitudeFilter::pushAndClamp( FeatureList& features, FilterContext& cx )
                         p.z() = centroidElevation;
                         if ( !vertEquiv )
                         {
-                            featureSRSWithMapVertDatum->transform(p, featureSRS, p);
+                            featureSRSWithMapVertDatum->transform(p, featureSRS.get(), p);
                         }
                     }
                 }
@@ -433,4 +442,7 @@ AltitudeFilter::pushAndClamp( FeatureList& features, FilterContext& cx )
             feature->set( "__max_terrain_z", maxTerrainZ );
         }
     }
+
+    double t = OE_GET_TIMER(pushAndClamp);
+    OE_DEBUG << LC << "pushAndClamp: tpp = " << (t / (double)total)*1000000.0 << " us\n";
 }
diff --git a/src/osgEarthFeatures/BufferFilter.cpp b/src/osgEarthFeatures/BufferFilter.cpp
index 461f152..518e26b 100644
--- a/src/osgEarthFeatures/BufferFilter.cpp
+++ b/src/osgEarthFeatures/BufferFilter.cpp
@@ -17,6 +17,7 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>
  */
 #include <osgEarthFeatures/BufferFilter>
+#include <osgEarthFeatures/FilterContext>
 
 #define LC "[BufferFilter] "
 
diff --git a/src/osgEarthFeatures/BuildGeometryFilter b/src/osgEarthFeatures/BuildGeometryFilter
index 8d818d3..58dc97c 100644
--- a/src/osgEarthFeatures/BuildGeometryFilter
+++ b/src/osgEarthFeatures/BuildGeometryFilter
@@ -81,6 +81,28 @@ namespace osgEarth { namespace Features
         optional<float>& maxPolygonTilingAngle() { return _maxPolyTilingAngle_deg; }
         const optional<float>& maxPolygonTilingAngle() const { return _maxPolyTilingAngle_deg; }
 
+        /**
+         * Whether to run vertex order optimizations on the resulting geometry.
+         * This can speed up draw performance at the expense of increasing build time.
+         */
+        optional<bool>& optimizeVertexOrdering() { return _optimizeVertexOrdering; }
+        const optional<bool>& optimizeVertexOrdering() const { return _optimizeVertexOrdering; }
+
+        /**
+         * Maximum angle at which the smoother (for 3D polygon normal generation) should
+         * smooth normals across polygon boundaries. Default is 0 degrees. This corresponds
+         * to the SmoothingVisitor::setCreaseAngle value.
+         */
+        optional<Angle>& maxCreaseAngle() { return _maximumCreaseAngle; }
+        const optional<Angle>& maxCreaseAngle() const { return _maximumCreaseAngle; }
+
+        /**
+         * Whether to use GPU-generated geometry for lines with pixel width, as a replacement
+         * for the deprecated glLineWidth arttribute
+         */
+        optional<bool>& useGPULines() { return _useGPULines; }
+        const optional<bool>& useGPULines() const { return _useGPULines; }
+
     protected:
         Style                      _style;
 
@@ -88,6 +110,9 @@ namespace osgEarth { namespace Features
         optional<GeoInterpolation> _geoInterp;
         optional<StringExpression> _featureNameExpr;
         optional<float>            _maxPolyTilingAngle_deg;
+        optional<bool>             _optimizeVertexOrdering;
+        optional<Angle>            _maximumCreaseAngle;
+        optional<bool>             _useGPULines;
         
         void tileAndBuildPolygon(
             Geometry*               input,
@@ -108,7 +133,7 @@ namespace osgEarth { namespace Features
 
         osg::Geode* processPolygons        (FeatureList& input, FilterContext& cx);
         osg::Geode* processLines           (FeatureList& input, FilterContext& cx);
-        osg::Geode* processPolygonizedLines(FeatureList& input, bool twosided, FilterContext& cx);
+        osg::Group* processPolygonizedLines(FeatureList& input, bool twosided, FilterContext& cx);
         osg::Geode* processPoints          (FeatureList& input, FilterContext& cx);
     };
 
diff --git a/src/osgEarthFeatures/BuildGeometryFilter.cpp b/src/osgEarthFeatures/BuildGeometryFilter.cpp
index fdb0ea0..489db60 100644
--- a/src/osgEarthFeatures/BuildGeometryFilter.cpp
+++ b/src/osgEarthFeatures/BuildGeometryFilter.cpp
@@ -20,6 +20,7 @@
 #include <osgEarthFeatures/Session>
 #include <osgEarthFeatures/FeatureSourceIndexNode>
 #include <osgEarthFeatures/PolygonizeLines>
+#include <osgEarthFeatures/GPULines>
 #include <osgEarthSymbology/TextSymbol>
 #include <osgEarthSymbology/PointSymbol>
 #include <osgEarthSymbology/LineSymbol>
@@ -35,14 +36,12 @@
 #include <osg/LineWidth>
 #include <osg/LineStipple>
 #include <osg/Point>
-#include <osg/Depth>
-#include <osg/PolygonOffset>
 #include <osg/MatrixTransform>
+#include <osg/TriangleIndexFunctor>
 #include <osgText/Text>
 #include <osgUtil/Tessellator>
 #include <osgUtil/Optimizer>
 #include <osgUtil/Simplifier>
-#include <osgUtil/SmoothingVisitor>
 #include <osgDB/WriteFile>
 #include <osg/Version>
 #include <iterator>
@@ -67,7 +66,7 @@ namespace
         return isCCW(x1, y1, x3, y3, x4, y4) != isCCW(x2, y2, x3, y3, x4, y4) && isCCW(x1, y1, x2, y2, x3, y3) != isCCW(x1, y1, x2, y2, x4, y4);
     }
 
-    bool holeCompare(osgEarth::Symbology::Ring* i, osgEarth::Symbology::Ring* j)
+    bool holeCompare(const osg::ref_ptr<Ring>& i, const osg::ref_ptr<Ring>& j)
     {
         return i->getBounds().xMax() > j->getBounds().xMax();
     }
@@ -97,7 +96,9 @@ BuildGeometryFilter::BuildGeometryFilter( const Style& style ) :
 _style        ( style ),
 _maxAngle_deg ( 180.0 ),
 _geoInterp    ( GEOINTERP_RHUMB_LINE ),
-_maxPolyTilingAngle_deg( 45.0f )
+_maxPolyTilingAngle_deg( 45.0f ),
+_optimizeVertexOrdering( false ),
+_maximumCreaseAngle( 0.0f )
 {
     //nop
 }
@@ -109,14 +110,15 @@ BuildGeometryFilter::processPolygons(FeatureList& features, FilterContext& conte
 
     bool makeECEF = false;
     const SpatialReference* featureSRS = 0L;
-    const SpatialReference* mapSRS = 0L;
+    //const SpatialReference* mapSRS = 0L;
+    const SpatialReference* outputSRS = 0L;
 
     // set up the reference system info:
     if ( context.isGeoreferenced() )
     {
-        makeECEF   = context.getSession()->getMapInfo().isGeocentric();
         featureSRS = context.extent()->getSRS();
-        mapSRS     = context.getSession()->getMapInfo().getProfile()->getSRS();
+        outputSRS  = context.getOutputSRS();
+        makeECEF   = context.getOutputSRS()->isGeographic();
     }
 
     for( FeatureList::iterator f = features.begin(); f != features.end(); ++f )
@@ -155,7 +157,7 @@ BuildGeometryFilter::processPolygons(FeatureList& features, FilterContext& conte
 
             // resolve the color:
             osg::Vec4f primaryColor = poly->fill()->color();
-            
+
             osg::ref_ptr<osg::Geometry> osgGeom = new osg::Geometry();
             osgGeom->setUseVertexBufferObjects( true );
             osgGeom->setUseDisplayList( false );
@@ -190,7 +192,7 @@ BuildGeometryFilter::processPolygons(FeatureList& features, FilterContext& conte
                 hats->push_back( i->z() );
 
             // build the geometry:
-            tileAndBuildPolygon(part, featureSRS, mapSRS, makeECEF, true, osgGeom, w2l);
+            tileAndBuildPolygon(part, featureSRS, outputSRS, makeECEF, true, osgGeom.get(), w2l);
             //buildPolygon(part, featureSRS, mapSRS, makeECEF, true, osgGeom, w2l);
 
             osg::Vec3Array* allPoints = static_cast<osg::Vec3Array*>(osgGeom->getVertexArray());
@@ -212,7 +214,7 @@ BuildGeometryFilter::processPolygons(FeatureList& features, FilterContext& conte
                     }
 
                     double threshold = osg::DegreesToRadians( *_maxAngle_deg );
-                    OE_TEST << "Running mesh subdivider with threshold " << *_maxAngle_deg << std::endl;
+                    //OE_TEST << "Running mesh subdivider with threshold " << *_maxAngle_deg << std::endl;
 
                     MeshSubdivider ms( _world2local, _local2world );
                     if ( input->geoInterp().isSet() )
@@ -233,13 +235,13 @@ BuildGeometryFilter::processPolygons(FeatureList& features, FilterContext& conte
 
                 // record the geometry's primitive set(s) in the index:
                 if ( context.featureIndex() )
-                    context.featureIndex()->tagDrawable( osgGeom, input );
-        
+                    context.featureIndex()->tagDrawable( osgGeom.get(), input );
+
                 // install clamping attributes if necessary
                 if (_style.has<AltitudeSymbol>() &&
                     _style.get<AltitudeSymbol>()->technique() == AltitudeSymbol::TECHNIQUE_GPU)
                 {
-                    Clamping::applyDefaultClampingAttrs( osgGeom, input->getDouble("__oe_verticalOffset", 0.0) );
+                    Clamping::applyDefaultClampingAttrs( osgGeom.get(), input->getDouble("__oe_verticalOffset", 0.0) );
                 }
             }
             else
@@ -248,31 +250,54 @@ BuildGeometryFilter::processPolygons(FeatureList& features, FilterContext& conte
             }
         }
     }
-    
+
     OE_TEST << LC << "Num drawables = " << geode->getNumDrawables() << "\n";
     return geode;
 }
 
+namespace
+{
+    struct CopyHeightsCallback : public PolygonizeLinesOperator::Callback
+    {
+        osg::FloatArray* _heights;
+        osg::ref_ptr<osg::FloatArray> _newHeights;
+        CopyHeightsCallback(osg::FloatArray* heights) : _heights(heights) {
+            if (_heights) {
+                _newHeights = new osg::FloatArray();
+                _newHeights->reserve(_heights->size() * 3);
+            }
+        }
+        void operator()(unsigned i) {
+            if (_newHeights.valid() && _heights) {
+                _newHeights->push_back((*_heights)[i]);
+            }
+        }
+    };
+}
 
-osg::Geode*
-BuildGeometryFilter::processPolygonizedLines(FeatureList&   features, 
+osg::Group*
+BuildGeometryFilter::processPolygonizedLines(FeatureList&   features,
                                              bool           twosided,
                                              FilterContext& context)
 {
-    osg::Geode* geode = new osg::Geode();
+    osg::Group* group = new osg::Group;    
 
     // establish some referencing
     bool                    makeECEF   = false;
     const SpatialReference* featureSRS = 0L;
-    const SpatialReference* mapSRS     = 0L;
+    const SpatialReference* outputSRS  = 0L;
 
     if ( context.isGeoreferenced() )
     {
-        makeECEF   = context.getSession()->getMapInfo().isGeocentric();
         featureSRS = context.extent()->getSRS();
-        mapSRS     = context.getSession()->getMapInfo().getProfile()->getSRS();
+        outputSRS = context.getOutputSRS();
+        makeECEF = outputSRS->isGeographic();
     }
 
+    // We need to create a different geode for each texture that is used so they can share statesets.
+    typedef std::map< std::string, osg::ref_ptr< osg::Geode > > TextureToGeodeMap;
+    TextureToGeodeMap geodes;    
+
     // iterate over all features.
     for( FeatureList::iterator i = features.begin(); i != features.end(); ++i )
     {
@@ -285,6 +310,38 @@ BuildGeometryFilter::processPolygonizedLines(FeatureList&   features,
         if ( !line )
             continue;
 
+        std::string imageURI;
+
+        // Image URI
+        if (line->imageURI().isSet() && context.getSession() && context.getSession()->getResourceCache())
+        {
+            StringExpression temp( *line->imageURI() );
+            imageURI = input->eval( temp, context.getSession());            
+        }
+
+        // Try to find the existing geode, otherwise create one.
+        osg::ref_ptr< osg::Geode > geode;
+        TextureToGeodeMap::iterator itr = geodes.find(imageURI);
+        if (itr != geodes.end())
+        {
+            geode = itr->second;
+        }
+        else
+        {
+            geode = new osg::Geode;
+
+            // Create the texture for the geode.
+            if (imageURI.empty() == false)
+            {
+                osg::ref_ptr<osg::Texture> tex;
+                if (context.getSession()->getResourceCache()->getOrCreateLineTexture(imageURI, tex, context.getDBOptions()))
+                {
+                    geode->getOrCreateStateSet()->setTextureAttributeAndModes(0, tex.get(), 1);
+                }
+            }
+            geodes[imageURI] = geode;
+        }
+
         // run a symbol script if present.
         if ( line->script().isSet() )
         {
@@ -294,6 +351,7 @@ BuildGeometryFilter::processPolygonizedLines(FeatureList&   features,
 
         // The operator we'll use to make lines into polygons.
         PolygonizeLinesOperator polygonizer( *line->stroke() );
+        //GPULinesOperator gpuLines(*line->stroke() );
 
         // iterate over all the feature's geometry parts. We will treat
         // them as lines strings.
@@ -312,20 +370,31 @@ BuildGeometryFilter::processPolygonizedLines(FeatureList&   features,
             if ( part->size() < 2 )
                 continue;
 
+            // GPU clamping enabled?
+            bool gpuClamping =
+                _style.has<AltitudeSymbol>() &&
+                _style.get<AltitudeSymbol>()->technique() == AltitudeSymbol::TECHNIQUE_GPU;
+
             // collect all the pre-transformation HAT (Z) values.
-            osg::ref_ptr<osg::FloatArray> hats = new osg::FloatArray();
-            hats->reserve( part->size() );
-            for(Geometry::const_iterator i = part->begin(); i != part->end(); ++i )
-                hats->push_back( i->z() );
+            osg::ref_ptr<osg::FloatArray> hats = 0L;
+            if (gpuClamping)
+            {
+                hats = new osg::FloatArray();
+                hats->reserve( part->size() );
+                for(Geometry::const_iterator i = part->begin(); i != part->end(); ++i )
+                    hats->push_back( i->z() );
+            }
 
-            // transform the geometry into the target SRS and localize it about 
+            // transform the geometry into the target SRS and localize it about
             // a local reference point.
             osg::ref_ptr<osg::Vec3Array> verts   = new osg::Vec3Array();
             osg::ref_ptr<osg::Vec3Array> normals = new osg::Vec3Array();
-            transformAndLocalize( part->asVector(), featureSRS, verts.get(), normals.get(), mapSRS, _world2local, makeECEF );
+            transformAndLocalize( part->asVector(), featureSRS, verts.get(), normals.get(), outputSRS, _world2local, makeECEF );
 
             // turn the lines into polygons.
-            osg::Geometry* geom = polygonizer( verts.get(), normals.get(), twosided );
+            CopyHeightsCallback copyHeights(hats.get());
+            osg::Geometry* geom = polygonizer( verts.get(), normals.get(), gpuClamping? &copyHeights : 0L, twosided );
+            //osg::Geometry* geom = gpuLines(verts.get());
             if ( geom )
             {
                 geode->addDrawable( geom );
@@ -334,19 +403,41 @@ BuildGeometryFilter::processPolygonizedLines(FeatureList&   features,
             // record the geometry's primitive set(s) in the index:
             if ( context.featureIndex() )
                 context.featureIndex()->tagDrawable( geom, input );
-        
+
             // install clamping attributes if necessary
-            if (_style.has<AltitudeSymbol>() &&
-                _style.get<AltitudeSymbol>()->technique() == AltitudeSymbol::TECHNIQUE_GPU)
+            if (gpuClamping)
             {
                 Clamping::applyDefaultClampingAttrs( geom, input->getDouble("__oe_verticalOffset", 0.0) );
-                Clamping::setHeights( geom, hats.get() );
-            }
+                Clamping::setHeights( geom, copyHeights._newHeights.get() );
+                //OE_WARN << "heights = " << hats->size() << ", new hats = " << copyHeights._newHeights->size() << ", verts=" << geom->getVertexArray()->getNumElements() << std::endl;
+            }            
         }
+        polygonizer.installShaders( geode.get() );
+    }
 
-        polygonizer.installShaders( geode );
+    for (TextureToGeodeMap::iterator itr = geodes.begin(); itr != geodes.end(); ++itr)
+    {
+        // Optimize the Geode
+        osg::Geode* geode = itr->second.get();
+        osgUtil::Optimizer::MergeGeometryVisitor mg;
+        mg.setTargetMaximumNumberOfVertices(65536);
+        geode->accept(mg);
+
+        if (_optimizeVertexOrdering == true)
+        {
+            osgUtil::Optimizer o;
+            o.optimize( geode,
+                osgUtil::Optimizer::INDEX_MESH
+                | osgUtil::Optimizer::VERTEX_PRETRANSFORM
+                | osgUtil::Optimizer::VERTEX_POSTTRANSFORM
+                );
+        }
+
+        // Add it to the group
+        group->addChild( geode );
     }
-    return geode;
+
+    return group;
 }
 
 
@@ -354,31 +445,40 @@ osg::Geode*
 BuildGeometryFilter::processLines(FeatureList& features, FilterContext& context)
 {
     osg::Geode* geode = new osg::Geode();
-
+    
     bool makeECEF = false;
     const SpatialReference* featureSRS = 0L;
-    const SpatialReference* mapSRS = 0L;
+    const SpatialReference* outputSRS = 0L;
 
     // set up referencing information:
     if ( context.isGeoreferenced() )
     {
-        makeECEF   = context.getSession()->getMapInfo().isGeocentric();
         featureSRS = context.extent()->getSRS();
-        mapSRS     = context.getSession()->getMapInfo().getProfile()->getSRS();
+        outputSRS  = context.getOutputSRS();
+        makeECEF = outputSRS->isGeographic();
     }
 
+    optional<Stroke> masterStroke;
+    
     for( FeatureList::iterator f = features.begin(); f != features.end(); ++f )
     {
         Feature* input = f->get();
 
         // extract the required line symbol; bail out if not found.
-        const LineSymbol* line = 
+        const LineSymbol* line =
             input->style().isSet() && input->style()->has<LineSymbol>() ? input->style()->get<LineSymbol>() :
             _style.get<LineSymbol>();
 
+        // if there's no line symbol, bail.
         if ( !line )
             continue;
 
+        // save the first stroke to use for shader generation of GPU lines if used
+        if (!masterStroke.isSet())
+        {
+            masterStroke = line->stroke().get();
+        }
+
         // run a symbol script if present.
         if ( line->script().isSet() )
         {
@@ -386,6 +486,9 @@ BuildGeometryFilter::processLines(FeatureList& features, FilterContext& context)
             input->eval( temp, &context );
         }
 
+        // GPU line generator (if used)
+        GPULinesOperator gpuLines(line->stroke().get());
+
         GeometryIterator parts( input->getGeometry(), true );
         while( parts.hasMore() )
         {
@@ -396,81 +499,106 @@ BuildGeometryFilter::processLines(FeatureList& features, FilterContext& context)
                 continue;
 
             // collect all the pre-transformation HAT (Z) values.
+            // (Note: GPU Lines have doubled-up vertices, so for that case we 
+            // have to add 2 hats for every input point.)
             osg::ref_ptr<osg::FloatArray> hats = new osg::FloatArray();
-            hats->reserve( part->size() );
+
+            if (_useGPULines == true)
+                hats->reserve(part->size() * 2);
+            else
+                hats->reserve(part->size());
+
             for(Geometry::const_iterator i = part->begin(); i != part->end(); ++i )
+            {
                 hats->push_back( i->z() );
+                if (_useGPULines == true)
+                    hats->push_back(i->z());
+            }
 
             // if the underlying geometry is a ring (or a polygon), use a line loop; otherwise
             // use a line strip.
-            GLenum primMode = dynamic_cast<Ring*>(part) ? GL_LINE_LOOP : GL_LINE_STRIP;
-
+            bool isRing = (dynamic_cast<Ring*>(part) != 0L);
+            
             // resolve the color:
             osg::Vec4f primaryColor = line->stroke()->color();
-            
-            osg::ref_ptr<osg::Geometry> osgGeom = new osg::Geometry();
-            osgGeom->setUseVertexBufferObjects( true );
-            osgGeom->setUseDisplayList( false );
 
-            // embed the feature name if requested. Warning: blocks geometry merge optimization!
-            if ( _featureNameExpr.isSet() )
-            {
-                const std::string& name = input->eval( _featureNameExpr.mutable_value(), &context );
-                osgGeom->setName( name );
-            }
-
-            // build the geometry:
+            // generate the geometry:
             osg::Vec3Array* allPoints = new osg::Vec3Array();
 
-            transformAndLocalize( part->asVector(), featureSRS, allPoints, mapSRS, _world2local, makeECEF );
+            transformAndLocalize( part->asVector(), featureSRS, allPoints, outputSRS, _world2local, makeECEF );
 
-            osgGeom->addPrimitiveSet( new osg::DrawArrays(primMode, 0, allPoints->getNumElements()) );
-            osgGeom->setVertexArray( allPoints );
+            osg::ref_ptr<osg::Geometry> osgGeom;
 
-            if ( input->style().isSet() )
+            if (_useGPULines == true)
             {
-                //TODO: re-evaluate this. does it hinder geometry merging?
-                applyLineSymbology( osgGeom->getOrCreateStateSet(), line );
+                // Lines tessellated on the GPU - replacement for deprecated glLineWidth
+                osgGeom = gpuLines(allPoints, isRing);
             }
-            
-            // subdivide the mesh if necessary to conform to an ECEF globe;
-            // but if the tessellation is set to zero, or if the style specifies a
-            // tessellation size, skip this step.
-            if ( makeECEF && !line->tessellation().isSetTo(0) && !line->tessellationSize().isSet() )
+            else
             {
-                double threshold = osg::DegreesToRadians( *_maxAngle_deg );
-                OE_DEBUG << "Running mesh subdivider with threshold " << *_maxAngle_deg << std::endl;
-
-                MeshSubdivider ms( _world2local, _local2world );
-                //ms.setMaxElementsPerEBO( INT_MAX );
-                if ( input->geoInterp().isSet() )
-                    ms.run( *osgGeom, threshold, *input->geoInterp() );
-                else
-                    ms.run( *osgGeom, threshold, *_geoInterp );
+                // normal GL lines
+                osgGeom = new osg::Geometry();
+                osgGeom->setUseVertexBufferObjects(true);
+                osgGeom->setUseDisplayList(false);
+                GLenum primMode = isRing ? GL_LINE_LOOP : GL_LINE_STRIP;
+                osgGeom->addPrimitiveSet(new osg::DrawArrays(primMode, 0, allPoints->getNumElements()));
+                osgGeom->setVertexArray(allPoints);
             }
 
-            // assign the primary color (PER_VERTEX required for later optimization)
-            osg::Vec4Array* colors = new osg::Vec4Array;
-            colors->assign( osgGeom->getVertexArray()->getNumElements(), primaryColor );
-            osgGeom->setColorArray( colors );
-            osgGeom->setColorBinding( osg::Geometry::BIND_PER_VERTEX );
+            if (osgGeom.valid())
+            {
+                // embed the feature name if requested. Warning: blocks geometry merge optimization!
+                if ( _featureNameExpr.isSet() )
+                {
+                    const std::string& name = input->eval( _featureNameExpr.mutable_value(), &context );
+                    osgGeom->setName( name );
+                }
 
-            geode->addDrawable( osgGeom );
+                // subdivide the mesh if necessary to conform to an ECEF globe;
+                // but if the tessellation is set to zero, or if the style specifies a
+                // tessellation size, skip this step.
+                if ( makeECEF && !line->tessellation().isSetTo(0) && !line->tessellationSize().isSet() )
+                {
+                    double threshold = osg::DegreesToRadians( *_maxAngle_deg );
+                    OE_DEBUG << "Running mesh subdivider with threshold " << *_maxAngle_deg << std::endl;
 
-            // record the geometry's primitive set(s) in the index:
-            if ( context.featureIndex() )
-                context.featureIndex()->tagDrawable( osgGeom, input );
-        
-            // install clamping attributes if necessary
-            if (_style.has<AltitudeSymbol>() &&
-                _style.get<AltitudeSymbol>()->technique() == AltitudeSymbol::TECHNIQUE_GPU)
-            {
-                Clamping::applyDefaultClampingAttrs( osgGeom, input->getDouble("__oe_verticalOffset", 0.0) );
-                Clamping::setHeights( osgGeom, hats.get() );
+                    MeshSubdivider ms( _world2local, _local2world );
+                    //ms.setMaxElementsPerEBO( INT_MAX );
+                    if ( input->geoInterp().isSet() )
+                        ms.run( *osgGeom, threshold, *input->geoInterp() );
+                    else
+                        ms.run( *osgGeom, threshold, *_geoInterp );
+                }
+
+                // assign the primary color (PER_VERTEX required for later optimization)
+                osg::Vec4Array* colors = new osg::Vec4Array;
+                colors->assign( osgGeom->getVertexArray()->getNumElements(), primaryColor );
+                osgGeom->setColorArray( colors );
+                osgGeom->setColorBinding( osg::Geometry::BIND_PER_VERTEX );
+
+                geode->addDrawable( osgGeom );
+
+                // record the geometry's primitive set(s) in the index:
+                if ( context.featureIndex() )
+                    context.featureIndex()->tagDrawable( osgGeom.get(), input );
+
+                // install clamping attributes if necessary
+                if (_style.has<AltitudeSymbol>() &&
+                    _style.get<AltitudeSymbol>()->technique() == AltitudeSymbol::TECHNIQUE_GPU)
+                {
+                    Clamping::applyDefaultClampingAttrs( osgGeom.get(), input->getDouble("__oe_verticalOffset", 0.0) );
+                    Clamping::setHeights( osgGeom.get(), hats.get() );
+                }
             }
         }
     }
-    
+
+    if (_useGPULines == true)
+    {
+        GPULinesOperator op(masterStroke.get());
+        op.installShaders(geode);
+    }
+
     return geode;
 }
 
@@ -482,14 +610,15 @@ BuildGeometryFilter::processPoints(FeatureList& features, FilterContext& context
 
     bool makeECEF = false;
     const SpatialReference* featureSRS = 0L;
-    const SpatialReference* mapSRS = 0L;
+    const SpatialReference* outputSRS = 0L;
 
     // set up referencing information:
     if ( context.isGeoreferenced() )
     {
-        makeECEF   = context.getSession()->getMapInfo().isGeocentric();
+        //makeECEF   = context.getSession()->getMapInfo().isGeocentric();
         featureSRS = context.extent()->getSRS();
-        mapSRS     = context.getSession()->getMapInfo().getProfile()->getSRS();
+        outputSRS  = context.getOutputSRS();
+        makeECEF = outputSRS->isGeographic();
     }
 
     for( FeatureList::iterator f = features.begin(); f != features.end(); ++f )
@@ -517,7 +646,7 @@ BuildGeometryFilter::processPoints(FeatureList& features, FilterContext& context
 
             // resolve the color:
             osg::Vec4f primaryColor = point->fill()->color();
-            
+
             osg::ref_ptr<osg::Geometry> osgGeom = new osg::Geometry();
             osgGeom->setUseVertexBufferObjects( true );
             osgGeom->setUseDisplayList( false );
@@ -532,7 +661,7 @@ BuildGeometryFilter::processPoints(FeatureList& features, FilterContext& context
             // build the geometry:
             osg::Vec3Array* allPoints = new osg::Vec3Array();
 
-            transformAndLocalize( part->asVector(), featureSRS, allPoints, mapSRS, _world2local, makeECEF );
+            transformAndLocalize( part->asVector(), featureSRS, allPoints, outputSRS, _world2local, makeECEF );
 
             osgGeom->addPrimitiveSet( new osg::DrawArrays(GL_POINTS, 0, allPoints->getNumElements()) );
             osgGeom->setVertexArray( allPoints );
@@ -549,22 +678,22 @@ BuildGeometryFilter::processPoints(FeatureList& features, FilterContext& context
             osgGeom->setColorArray( colors );
             osgGeom->setColorBinding( osg::Geometry::BIND_PER_VERTEX );
 
-            geode->addDrawable( osgGeom );
+            geode->addDrawable( osgGeom.get() );
 
             // record the geometry's primitive set(s) in the index:
             if ( context.featureIndex() )
-                context.featureIndex()->tagDrawable( osgGeom, input );
-        
+                context.featureIndex()->tagDrawable( osgGeom.get(), input );
+
             // install clamping attributes if necessary
             if (_style.has<AltitudeSymbol>() &&
                 _style.get<AltitudeSymbol>()->technique() == AltitudeSymbol::TECHNIQUE_GPU)
-            {            
-                Clamping::applyDefaultClampingAttrs( osgGeom, input->getDouble("__oe_verticalOffset", 0.0) );
-                Clamping::setHeights( osgGeom, hats.get() );
+            {
+                Clamping::applyDefaultClampingAttrs( osgGeom.get(), input->getDouble("__oe_verticalOffset", 0.0) );
+                Clamping::setHeights( osgGeom.get(), hats.get() );
             }
         }
     }
-    
+
     return geode;
 }
 
@@ -583,7 +712,7 @@ osg::PrimitiveSet* copy( FROM* src, unsigned offset )
 /**
  * Converts an osg::Geometry to use osg::DrawElementsUInt if it doesn't already.
  * This only works on Geometries that are already using DrawElementsUInt, DrawElementsUByte, or DrawElementsUShort
- * We do this to normalize the primitive set types so that we can merge multiple geometries 
+ * We do this to normalize the primitive set types so that we can merge multiple geometries
  * into one later down the road.
  */
 void convertToDrawElementsUInt(osg::Geometry* geometry)
@@ -602,7 +731,7 @@ void convertToDrawElementsUInt(osg::Geometry* geometry)
                 newPS = copy<osg::DrawElementsUByte, osg::DrawElementsUInt>(static_cast<osg::DrawElementsUByte*>(ps), 0);
             }
             else if (dynamic_cast<osg::DrawElementsUShort*>(ps))
-            {             
+            {
                 newPS = copy<osg::DrawElementsUShort, osg::DrawElementsUInt>(static_cast<osg::DrawElementsUShort*>(ps), 0);
             }
 
@@ -657,7 +786,7 @@ void tileGeometry(Geometry* geometry, const SpatialReference* featureSRS, unsign
     z /= geometry->size();
 
     osg::ref_ptr<Polygon> poly = new Polygon;
-    poly->resize( 4 );        
+    poly->resize( 4 );
 
     for(int x=0; x<(int)numCols; ++x)
     {
@@ -676,7 +805,7 @@ void tileGeometry(Geometry* geometry, const SpatialReference* featureSRS, unsign
                 while( gi.hasMore() )
                 {
                     Geometry* geom = gi.next();
-                    out.push_back( geom );                                                
+                    out.push_back( geom );
                 }
             }
         }
@@ -695,7 +824,7 @@ void downsizeGeometry(Geometry* geometry, const SpatialReference* featureSRS, un
         // Tile the geometry.
         GeometryCollection tmp;
         tileGeometry(geometry, featureSRS, 2, 2, tmp );
-        
+
         for (unsigned int i = 0; i < tmp.size(); i++)
         {
             Geometry* g = tmp[i].get();
@@ -728,7 +857,7 @@ void prepareForTesselation(Geometry* geometry, const SpatialReference* featureSR
 {
     // Clear the output list.
     GeometryCollection tiles;
-    
+
     unsigned int count = geometry->size();
 
     unsigned int tx = 1;
@@ -742,14 +871,14 @@ void prepareForTesselation(Geometry* geometry, const SpatialReference* featureSR
     {
         // Determine the tile size based on the extent.
         tx = ceil( featureExtentDeg.width() / targetTileSizeDeg );
-        ty = ceil (featureExtentDeg.height() / targetTileSizeDeg );        
+        ty = ceil (featureExtentDeg.height() / targetTileSizeDeg );
     }
     else if (count > maxPointsPerTile)
     {
         // Determine the size based on the number of points.
         unsigned numTiles = ((double)count / (double)maxPointsPerTile) + 1u;
         tx = ceil(sqrt((double)numTiles));
-        ty = tx;        
+        ty = tx;
     }
 
     if (tx == 1 && ty == 1)
@@ -792,7 +921,7 @@ void prepareForTesselation(Geometry* geometry, const SpatialReference* featureSR
 void
 BuildGeometryFilter::tileAndBuildPolygon(Geometry*               ring,
                                          const SpatialReference* featureSRS,
-                                         const SpatialReference* mapSRS,
+                                         const SpatialReference* outputSRS,
                                          bool                    makeECEF,
                                          bool                    tessellate,
                                          osg::Geometry*          osgGeom,
@@ -806,13 +935,13 @@ BuildGeometryFilter::tileAndBuildPolygon(Geometry*               ring,
         OE_WARN << LC << "Ring is NULL.\n";
         return;
     }
-    
+
     // Tile the incoming polygon if necessary
     // NB: this breaks down at higher latitudes; see https://github.com/gwaldron/osgearth/issues/746
 
     GeometryCollection tiles;
     if (_maxPolyTilingAngle_deg.isSet())
-        prepareForTesselation( ring, featureSRS, _maxPolyTilingAngle_deg.get(), MAX_POINTS_PER_CROP_TILE, tiles);    
+        prepareForTesselation( ring, featureSRS, _maxPolyTilingAngle_deg.get(), MAX_POINTS_PER_CROP_TILE, tiles);
     else
         tiles.push_back( ring );
 
@@ -832,12 +961,12 @@ BuildGeometryFilter::tileAndBuildPolygon(Geometry*               ring,
 
             // establish a local plane for this cell based on its centroid:
             GeoPoint cellCenter(featureSRS, geom->getBounds().center());
-            cellCenter.transform(mapSRS, cellCenter);                        
+            cellCenter.transform(outputSRS, cellCenter);
             osg::Matrix world2cell;
             cellCenter.createWorldToLocal( world2cell );
 
             // build the localized polygon:
-            buildPolygon(geom, featureSRS, mapSRS, makeECEF, temp.get(), world2cell);
+            buildPolygon(geom, featureSRS, outputSRS, makeECEF, temp.get(), world2cell);
 
             // if successful, transform the verts back into our master LTP:
             if ( temp->getNumPrimitiveSets() > 0 )
@@ -883,7 +1012,7 @@ BuildGeometryFilter::tileAndBuildPolygon(Geometry*               ring,
         // dump out some info to help debug.
         if (geode->getNumDrawables() != 1)
         {
-            OE_WARN << LC << "MergeGeometryVisitor failed to merge geometries into a single one.  Num drawables " << geode->getNumDrawables() << std::endl;            
+            OE_WARN << LC << "MergeGeometryVisitor failed to merge geometries into a single one.  Num drawables " << geode->getNumDrawables() << std::endl;
             for (unsigned int i = 0; i < geode->getNumDrawables(); i++)
             {
                 osg::Geometry* g = geode->getDrawable(i)->asGeometry();
@@ -906,15 +1035,13 @@ BuildGeometryFilter::tileAndBuildPolygon(Geometry*               ring,
         osgGeom->setVertexArray( geode->getDrawable(0)->asGeometry()->getVertexArray() );
         osgGeom->setPrimitiveSetList( geode->getDrawable(0)->asGeometry()->getPrimitiveSetList() );
     }
-
-    osgUtil::SmoothingVisitor::smooth( *osgGeom );
 }
 
 // builds and tessellates a polygon (with or without holes)
 void
 BuildGeometryFilter::buildPolygon(Geometry*               ring,
                                   const SpatialReference* featureSRS,
-                                  const SpatialReference* mapSRS,
+                                  const SpatialReference* outputSRS,
                                   bool                    makeECEF,
                                   osg::Geometry*          osgGeom,
                                   const osg::Matrixd      &world2local)
@@ -925,7 +1052,7 @@ BuildGeometryFilter::buildPolygon(Geometry*               ring,
     ring->rewind(osgEarth::Symbology::Geometry::ORIENTATION_CCW);
 
     osg::ref_ptr<osg::Vec3Array> allPoints = new osg::Vec3Array();
-    transformAndLocalize( ring->asVector(), featureSRS, allPoints.get(), mapSRS, world2local, makeECEF );
+    transformAndLocalize( ring->asVector(), featureSRS, allPoints.get(), outputSRS, world2local, makeECEF );
 
     Polygon* poly = dynamic_cast<Polygon*>(ring);
     if ( poly )
@@ -941,7 +1068,7 @@ BuildGeometryFilter::buildPolygon(Geometry*               ring,
                 hole->rewind(osgEarth::Symbology::Geometry::ORIENTATION_CW);
 
                 osg::ref_ptr<osg::Vec3Array> holePoints = new osg::Vec3Array();
-                transformAndLocalize( hole->asVector(), featureSRS, holePoints.get(), mapSRS, world2local, makeECEF );
+                transformAndLocalize( hole->asVector(), featureSRS, holePoints.get(), outputSRS, world2local, makeECEF );
 
                 // find the point with the highest x value
                 unsigned int hCursor = 0;
@@ -1055,7 +1182,7 @@ BuildGeometryFilter::buildPolygon(Geometry*               ring,
 
                     insertPoints->push_back((*holePoints)[hCursor]);
                     insertPoints->push_back((*allPoints)[edgeCursor]);
-                    
+
                     // insert new points into outer loop
                     osg::Vec3Array::iterator it = edgeCursor == allPoints->size() - 1 ? allPoints->end() : allPoints->begin() + (edgeCursor + 1);
                     allPoints->insert(it, insertPoints->begin(), insertPoints->end());
@@ -1063,7 +1190,7 @@ BuildGeometryFilter::buildPolygon(Geometry*               ring,
             }
         }
     }
-    
+
     GLenum mode = GL_LINE_LOOP;
     if ( osgGeom->getVertexArray() == 0L )
     {
@@ -1077,33 +1204,78 @@ BuildGeometryFilter::buildPolygon(Geometry*               ring,
         //v->reserve(v->size() + allPoints->size());
         std::copy(allPoints->begin(), allPoints->end(), std::back_inserter(*v));
     }
-
-    //// Normal computation.
-    //// Not completely correct, but better than no normals at all. TODO: update this
-    //// to generate a proper normal vector in ECEF mode.
-    ////
-    //// We cannot accurately rely on triangles from the tessellation, since we could have
-    //// very "degraded" triangles (close to a line), and the normal computation would be bad.
-    //// In this case, we would have to average the normal vector over each triangle of the polygon.
-    //// The Newell's formula is simpler and more direct here.
-    //osg::Vec3 normal( 0.0, 0.0, 0.0 );
-    //for ( size_t i = 0; i < poly->size(); ++i )
-    //{
-    //    osg::Vec3 pi = (*poly)[i];
-    //    osg::Vec3 pj = (*poly)[ (i+1) % poly->size() ];
-    //    normal[0] += ( pi[1] - pj[1] ) * ( pi[2] + pj[2] );
-    //    normal[1] += ( pi[2] - pj[2] ) * ( pi[0] + pj[0] );
-    //    normal[2] += ( pi[0] - pj[0] ) * ( pi[1] + pj[1] );
-    //}
-    //normal.normalize();
-
-    //osg::Vec3Array* normals = new osg::Vec3Array();
-    //normals->push_back( normal );
-    //osgGeom->setNormalArray( normals );
-    //osgGeom->setNormalBinding( osg::Geometry::BIND_OVERALL );
 }
 
 
+namespace
+{
+    struct GenerateNormalFunctor
+    {
+        osg::Vec3Array* _verts;
+        osg::Vec3Array* _normals;
+
+        GenerateNormalFunctor() : _verts(0L), _normals(0L) { }
+
+        void set(osg::Vec3Array *cb, osg::Vec3Array *nb)
+        {
+            _verts = cb;
+            _normals = nb;
+        }
+
+        inline void operator()(unsigned i1, unsigned i2, unsigned i3)
+        {
+            const osg::Vec3& v1 = (*_verts)[i1];
+            const osg::Vec3& v2 = (*_verts)[i2];
+            const osg::Vec3& v3 = (*_verts)[i3];
+
+            // calc orientation of triangle.
+            osg::Vec3 normal = (v2 - v1) ^ (v3 - v1);
+            normal.normalize();
+            
+            (*_normals)[i1] += normal;
+            (*_normals)[i2] += normal;
+            (*_normals)[i3] += normal;
+        }
+
+        void finish()
+        {
+            for (unsigned i = 0; i < _normals->size(); ++i)
+            {
+                (*_normals)[i].normalize();
+            }
+        }
+    };
+
+    struct GenerateNormals : public osg::NodeVisitor
+    {
+        GenerateNormals() : osg::NodeVisitor()
+        {
+            setTraversalMode(TRAVERSE_ALL_CHILDREN);
+            setNodeMaskOverride(~0);
+        }
+
+        inline void apply(osg::Drawable& drawable)
+        {
+            osg::Geometry* geom = drawable.asGeometry();
+            if (geom)
+            {                
+                osg::Vec3Array* verts = dynamic_cast<osg::Vec3Array*>(geom->getVertexArray());
+
+                osg::Vec3Array* normals = new osg::Vec3Array(verts->size());
+                normals->setBinding(normals->BIND_PER_VERTEX);
+                
+                osg::TriangleIndexFunctor<GenerateNormalFunctor> f;
+                f.set(verts, normals);
+                geom->accept(f);
+                f.finish();
+
+                geom->setNormalArray(normals);
+            }
+            traverse(drawable);
+        }
+    };
+}
+
 osg::Node*
 BuildGeometryFilter::push( FeatureList& input, FilterContext& context )
 {
@@ -1121,7 +1293,26 @@ BuildGeometryFilter::push( FeatureList& input, FilterContext& context )
     FeatureList polygonizedLines;
     FeatureList points;
 
-    for(FeatureList::iterator i = input.begin(); i != input.end(); ++i)
+    FeatureList splitFeatures;
+
+    // Split features across the dateline if necessary
+    if (context.getOutputSRS() && !context.getOutputSRS()->isGeographic())
+    {
+        for(FeatureList::iterator itr = input.begin(); itr != input.end(); ++itr)
+        {
+            Feature* f = itr->get();
+            FeatureList tmpSplit;
+            f->splitAcrossDateLine(tmpSplit);
+            splitFeatures.insert(splitFeatures.end(), tmpSplit.begin(), tmpSplit.end());
+        }
+    }
+    else
+    {
+        // Just copy the input list to the split list
+        std::copy(input.begin(), input.end(), std::back_inserter(splitFeatures));
+    }
+
+    for(FeatureList::iterator i = splitFeatures.begin(); i != splitFeatures.end(); ++i)
     {
         Feature* f = i->get();
 
@@ -1140,6 +1331,15 @@ BuildGeometryFilter::push( FeatureList& input, FilterContext& context )
             has_pointsymbol    = has_pointsymbol    || (f->style()->has<PointSymbol>());
         }
 
+        // if there's a polygon with outlining disabled, nix the line symbol.
+        if (has_polysymbol)
+        {
+            if (poly && poly->outline() == false)
+                has_linesymbol = false;
+            else if (f->style().isSet() && f->style()->has<PolygonSymbol>() && f->style()->get<PolygonSymbol>()->outline() == false)
+                has_linesymbol = false;
+        }
+
         // if no style is set, use the geometry type:
         if ( !has_polysymbol && !has_linesymbol && !has_polylinesymbol && !has_pointsymbol && f->getGeometry() )
         {
@@ -1165,7 +1365,22 @@ BuildGeometryFilter::push( FeatureList& input, FilterContext& context )
         }
 
         if ( has_polysymbol )
-            polygons.push_back( f );
+        {
+#if 0 // Placeholder. We probably need this, but let's wait and see
+            // split polygons that cross the andimeridian:
+            if (f->getSRS()->isGeographic() &&
+                f->calculateExtent().crossesAntimeridian())
+            {
+                FeatureList temp;
+                f->splitAcrossDateLine(temp);
+                std::copy(temp.begin(), temp.end(), std::back_inserter(polygons));
+            }
+            else
+#endif
+            {
+                polygons.push_back( f );
+            }
+        }
 
         if ( has_linesymbol )
             lines.push_back( f );
@@ -1189,11 +1404,21 @@ BuildGeometryFilter::push( FeatureList& input, FilterContext& context )
             mg.setTargetMaximumNumberOfVertices(65536);
             geode->accept(mg);
 
-            osgUtil::Optimizer o;
-            o.optimize( geode.get(), 
-                osgUtil::Optimizer::INDEX_MESH |
-                osgUtil::Optimizer::VERTEX_PRETRANSFORM |
-                osgUtil::Optimizer::VERTEX_POSTTRANSFORM );
+            if (_optimizeVertexOrdering == true)
+            {
+                osg::Timer_t t = osg::Timer::instance()->tick();
+                osgUtil::Optimizer o;
+                o.optimize( geode.get(),
+                    osgUtil::Optimizer::INDEX_MESH |
+                    osgUtil::Optimizer::VERTEX_PRETRANSFORM |
+                    osgUtil::Optimizer::VERTEX_POSTTRANSFORM );
+                OE_INFO << "Vertex ordering optimization took " << osg::Timer::instance()->delta_s(t, osg::Timer::instance()->tick()) << std::endl;
+            }
+
+            // Generate normals. CANNOT use OSG's SmoothingVisitor because it adds verts
+            // but ignores other vertex attribute arrays.
+            GenerateNormals gen;
+            geode->accept(gen);
 
             result->addChild( geode.get() );
         }
@@ -1203,26 +1428,19 @@ BuildGeometryFilter::push( FeatureList& input, FilterContext& context )
     {
         OE_TEST << LC << "Building " << polygonizedLines.size() << " polygonized lines." << std::endl;
         bool twosided = polygons.size() > 0 ? false : true;
-        osg::ref_ptr<osg::Geode> geode = processPolygonizedLines(polygonizedLines, twosided, context);
-        if ( geode->getNumDrawables() > 0 )
-        {
-            osgUtil::Optimizer::MergeGeometryVisitor mg;
-            mg.setTargetMaximumNumberOfVertices(65536);
-            geode->accept(mg);
+        osg::ref_ptr< osg::Group > lines = processPolygonizedLines(polygonizedLines, twosided, context);
 
-            osgUtil::Optimizer o;
-            o.optimize( geode.get(), 
-                osgUtil::Optimizer::INDEX_MESH |
-                osgUtil::Optimizer::VERTEX_PRETRANSFORM |
-                osgUtil::Optimizer::VERTEX_POSTTRANSFORM );
-
-            result->addChild( geode.get() );
+        if (lines->getNumChildren() > 0)
+        {
+            result->addChild( lines.get() );
         }
+
     }
 
     if ( lines.size() > 0 )
     {
         OE_TEST << LC << "Building " << lines.size() << " lines." << std::endl;
+        
         osg::ref_ptr<osg::Geode> geode = processLines(lines, context);
         if ( geode->getNumDrawables() > 0 )
         {
@@ -1230,7 +1448,11 @@ BuildGeometryFilter::push( FeatureList& input, FilterContext& context )
             mg.setTargetMaximumNumberOfVertices(65536);
             geode->accept(mg);
 
-            applyLineSymbology( geode->getOrCreateStateSet(), line );
+            if (_useGPULines == false)
+            {
+                applyLineSymbology( geode->getOrCreateStateSet(), line );
+            }
+
             result->addChild( geode.get() );
         }
     }
@@ -1255,7 +1477,7 @@ BuildGeometryFilter::push( FeatureList& input, FilterContext& context )
         _style.get<AltitudeSymbol>()->technique() == AltitudeSymbol::TECHNIQUE_GPU)
     {
         Clamping::installHasAttrsUniform( result->getOrCreateStateSet() );
-    }    
+    }
 
     // Prepare buffer objects.
     AllocateAndMergeBufferObjectsVisitor allocAndMerge;
diff --git a/src/osgEarthFeatures/BuildTextFilter b/src/osgEarthFeatures/BuildTextFilter
index c214f47..2948a9f 100644
--- a/src/osgEarthFeatures/BuildTextFilter
+++ b/src/osgEarthFeatures/BuildTextFilter
@@ -21,10 +21,8 @@
 #define OSGEARTHFEATURES_BUILD_TEXT_FILTER_H 1
 
 #include <osgEarthFeatures/Common>
-#include <osgEarthFeatures/Feature>
 #include <osgEarthFeatures/Filter>
 #include <osgEarthSymbology/Style>
-#include <osg/Geode>
 
 namespace osgEarth { namespace Features 
 {
diff --git a/src/osgEarthFeatures/BuildTextFilter.cpp b/src/osgEarthFeatures/BuildTextFilter.cpp
index 71305df..999e52f 100644
--- a/src/osgEarthFeatures/BuildTextFilter.cpp
+++ b/src/osgEarthFeatures/BuildTextFilter.cpp
@@ -17,7 +17,7 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>
  */
 #include <osgEarthFeatures/BuildTextFilter>
-//#include <osgEarthFeatures/BuildTextOperator> // this should be in symbology -gw
+#include <osgEarthFeatures/FilterContext>
 #include <osgEarthFeatures/LabelSource>
 #include <osgEarthSymbology/TextSymbol>
 #include <osgText/Text>
diff --git a/src/osgEarthFeatures/CMakeLists.txt b/src/osgEarthFeatures/CMakeLists.txt
index b7c9575..53ad2f0 100644
--- a/src/osgEarthFeatures/CMakeLists.txt
+++ b/src/osgEarthFeatures/CMakeLists.txt
@@ -10,6 +10,18 @@ ENDIF(GEOS_FOUND)
 
 SET(LIB_NAME osgEarthFeatures)
 
+set(TARGET_GLSL
+    GPULinesScreenProj.glsl)
+
+set(SHADERS_CPP "${CMAKE_CURRENT_BINARY_DIR}/AutoGenShaders.cpp")
+
+set(TARGET_IN
+    Shaders.cpp.in)
+
+configure_shaders(
+    Shaders.cpp.in
+    ${SHADERS_CPP}
+    ${TARGET_GLSL} )
 
 SET(HEADER_PATH ${OSGEARTH_SOURCE_DIR}/include/${LIB_NAME})
 SET(LIB_PUBLIC_HEADERS
@@ -25,22 +37,24 @@ SET(LIB_PUBLIC_HEADERS
     Feature
     FeatureCursor
     FeatureDisplayLayout
-    FeatureDrawSet
     FeatureIndex
     FeatureListSource
+    FeatureMaskLayer
     FeatureModelGraph
+    FeatureModelLayer
     FeatureModelSource
     FeatureSource
     FeatureSourceIndexNode
+    FeatureSourceLayer
     FeatureTileSource
     Filter
     FilterContext
     GeometryCompiler
     GeometryUtils
+    GPULines
     LabelSource
     MVT
     OgrUtils
-    OptimizerHints
     PolygonizeLines
     ResampleFilter
     ScaleFilter
@@ -49,6 +63,7 @@ SET(LIB_PUBLIC_HEADERS
     Script
     ScriptEngine
     ScriptFilter
+    Shaders
     SubstituteModelFilter
     TessellateOperator
     TextSymbolizer
@@ -68,21 +83,23 @@ SET(TARGET_SRC
     Feature.cpp
     FeatureCursor.cpp
     FeatureDisplayLayout.cpp
-    FeatureDrawSet.cpp
     FeatureListSource.cpp
+    FeatureMaskLayer.cpp
     FeatureModelGraph.cpp
+    FeatureModelLayer.cpp
     FeatureModelSource.cpp
     FeatureSource.cpp
     FeatureSourceIndexNode.cpp
+    FeatureSourceLayer.cpp
     FeatureTileSource.cpp
     Filter.cpp
     FilterContext.cpp
     GeometryCompiler.cpp
     GeometryUtils.cpp
+    GPULines.cpp
     LabelSource.cpp
     MVT.cpp
     OgrUtils.cpp
-    OptimizerHints.cpp
     PolygonizeLines.cpp
     ResampleFilter.cpp
     ScaleFilter.cpp
@@ -94,7 +111,8 @@ SET(TARGET_SRC
     TessellateOperator.cpp
     TextSymbolizer.cpp
     TransformFilter.cpp
-    VirtualFeatureSource.cpp    
+    VirtualFeatureSource.cpp 
+    ${SHADERS_CPP}
 )
 
 IF(PROTOBUF_FOUND AND PROTOBUF_PROTOC_EXECUTABLE)
@@ -116,9 +134,12 @@ IF(PROTOBUF_FOUND AND PROTOBUF_PROTOC_EXECUTABLE)
 
 ENDIF()
 
-ADD_LIBRARY(${LIB_NAME} ${OSGEARTH_USER_DEFINED_DYNAMIC_OR_STATIC}
+ADD_LIBRARY(${LIB_NAME}
+    ${OSGEARTH_USER_DEFINED_DYNAMIC_OR_STATIC}
     ${LIB_PUBLIC_HEADERS}
     ${TARGET_SRC}
+    ${TARGET_GLSL}
+    ${TARGET_IN}
 )
 
 INCLUDE_DIRECTORIES(${GDAL_INCLUDE_DIR})
@@ -135,13 +156,14 @@ LINK_INTERNAL(${LIB_NAME}
     osgEarthSymbology
 )
 
-SET(LINK_VARS OSG_LIBRARY OSGUTIL_LIBRARY OSGSIM_LIBRARY OSGTERRAIN_LIBRARY OSGDB_LIBRARY OSGFX_LIBRARY OSGVIEWER_LIBRARY OSGTEXT_LIBRARY OSGGA_LIBRARY OPENTHREADS_LIBRARY)
+SET(LINK_VARS
+    OSG_LIBRARY OSGUTIL_LIBRARY OSGSIM_LIBRARY OSGTERRAIN_LIBRARY OSGDB_LIBRARY OSGFX_LIBRARY
+    OSGVIEWER_LIBRARY OSGTEXT_LIBRARY OSGGA_LIBRARY OPENTHREADS_LIBRARY)
+
 IF(PROTOBUF_FOUND AND PROTOBUF_PROTOC_EXECUTABLE)
-  SET(LINK_VARS ${LINK_VARS} PROTOBUF_LIBRARY)
+  list(APPEND LINK_VARS PROTOBUF_LIBRARIES)
 ENDIF()
 
-
-
 LINK_WITH_VARIABLES(${LIB_NAME} ${LINK_VARS})
 
 LINK_CORELIB_DEFAULT(${LIB_NAME} ${CMAKE_THREAD_LIBS_INIT} ${MATH_LIBRARY})
diff --git a/src/osgEarthFeatures/CentroidFilter.cpp b/src/osgEarthFeatures/CentroidFilter.cpp
index 78057c7..87b55bb 100644
--- a/src/osgEarthFeatures/CentroidFilter.cpp
+++ b/src/osgEarthFeatures/CentroidFilter.cpp
@@ -17,6 +17,7 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>
  */
 #include <osgEarthFeatures/CentroidFilter>
+#include <osgEarthFeatures/FilterContext>
 
 #define LC "[CentroidFilter] "
 
diff --git a/src/osgEarthFeatures/ConvertTypeFilter.cpp b/src/osgEarthFeatures/ConvertTypeFilter.cpp
index 62aec86..5fdcc72 100644
--- a/src/osgEarthFeatures/ConvertTypeFilter.cpp
+++ b/src/osgEarthFeatures/ConvertTypeFilter.cpp
@@ -17,8 +17,6 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>
  */
 #include <osgEarthFeatures/ConvertTypeFilter>
-#include <list>
-#include <deque>
 
 using namespace osgEarth;
 using namespace osgEarth::Features;
diff --git a/src/osgEarthFeatures/ExtrudeGeometryFilter b/src/osgEarthFeatures/ExtrudeGeometryFilter
index a927bb1..999cf1d 100644
--- a/src/osgEarthFeatures/ExtrudeGeometryFilter
+++ b/src/osgEarthFeatures/ExtrudeGeometryFilter
@@ -21,7 +21,6 @@
 #define OSGEARTHFEATURES_EXTRUDE_GEOMETRY_FILTER_H 1
 
 #include <osgEarthFeatures/Common>
-#include <osgEarthFeatures/Feature>
 #include <osgEarthFeatures/Filter>
 #include <osgEarthSymbology/Expression>
 #include <osgEarthSymbology/Style>
@@ -29,13 +28,19 @@
 #include <vector>
 #include <list>
 
+namespace osgEarth {
+    namespace Symbology {
+        class ResourceLibrary;
+    }
+}
+
 namespace osgEarth { namespace Features 
 {
     using namespace osgEarth;
     using namespace osgEarth::Symbology;
     
     class FeatureSourceIndex;
-
+    class FeatureIndexBuilder;
 
     /**
      * Extrudes footprint geometry into 3D geometry
diff --git a/src/osgEarthFeatures/ExtrudeGeometryFilter.cpp b/src/osgEarthFeatures/ExtrudeGeometryFilter.cpp
index f45d1c6..f05c17f 100644
--- a/src/osgEarthFeatures/ExtrudeGeometryFilter.cpp
+++ b/src/osgEarthFeatures/ExtrudeGeometryFilter.cpp
@@ -19,12 +19,16 @@
 #include <osgEarthFeatures/ExtrudeGeometryFilter>
 #include <osgEarthFeatures/Session>
 #include <osgEarthFeatures/FeatureSourceIndexNode>
-#include <osgEarthSymbology/ResourceCache>
+
+#include <osgEarthSymbology/ResourceLibrary>
+#include <osgEarthSymbology/StyleSheet>
+
 #include <osgEarth/ECEF>
 #include <osgEarth/ImageUtils>
 #include <osgEarth/Clamping>
 #include <osgEarth/Utils>
 #include <osgEarth/Tessellator>
+
 #include <osg/Geode>
 #include <osg/Geometry>
 #include <osg/MatrixTransform>
@@ -162,6 +166,13 @@ ExtrudeGeometryFilter::reset( const FilterContext& context )
 
             // if there's a line symbol, use it to outline the extruded data.
             _outlineSymbol = _style.get<LineSymbol>();
+
+            // ...unless a wall poly symbol overrides it.
+            if (_wallPolygonSymbol.valid() && _wallPolygonSymbol->outline() == false)
+                _outlineSymbol = 0L;
+
+            if (_roofPolygonSymbol.valid() && _roofPolygonSymbol->outline() == false)
+                _outlineSymbol = 0L;
         }
 
         // backup plan for skin symbols:
diff --git a/src/osgEarthFeatures/Feature b/src/osgEarthFeatures/Feature
index 136e158..42a5147 100644
--- a/src/osgEarthFeatures/Feature
+++ b/src/osgEarthFeatures/Feature
@@ -20,9 +20,10 @@
 #define OSGEARTHFEATURES_FEATURE_H 1
 
 #include <osgEarthFeatures/Common>
-#include <osgEarthFeatures/FilterContext>
+
 #include <osgEarthSymbology/Geometry>
 #include <osgEarthSymbology/Style>
+
 #include <osgEarth/GeoCommon>
 #include <osgEarth/SpatialReference>
 #include <osg/Array>
@@ -34,6 +35,7 @@ namespace osgEarth { namespace Features
 {
     using namespace osgEarth;
     using namespace osgEarth::Symbology;
+
     class FilterContext;
     class Session;
 
@@ -144,8 +146,6 @@ namespace osgEarth { namespace Features
         /** Copy contructor */
         Feature( const Feature& rhs, const osg::CopyOp& copyop =osg::CopyOp::DEEP_COPY_ALL );
 
-        virtual ~Feature() { }
-
         META_Object( osgEarthFeatures, Feature );
 
     public:
@@ -161,6 +161,11 @@ namespace osgEarth { namespace Features
         void setFID(FeatureID fid);
 
         /**
+         * Gets the GeoExtent of this Feature
+         */
+        GeoExtent getExtent() const;
+
+        /**
          * The geometry in this feature.
          */
         void setGeometry( Symbology::Geometry* geom );
@@ -192,6 +197,11 @@ namespace osgEarth { namespace Features
          */
         static bool getWorldBoundingPolytope( const osg::BoundingSphered& bs, const SpatialReference* srs, osg::Polytope& out_polytope );
 
+        /**
+         * Calculates the extent of this feature.
+         */
+        GeoExtent calculateExtent() const;
+
 
         const AttributeTable& getAttrs() const { return _attrs; }
 
@@ -226,11 +236,11 @@ namespace osgEarth { namespace Features
         const optional<GeoInterpolation>& geoInterp() const { return _geoInterp; }
 
         /** populates the variables of an expression with attribute values and evals the expression. */
-        double eval(NumericExpression& expr, FilterContext const* context=0L) const;
+        double eval(NumericExpression& expr, const FilterContext* context) const;
         double eval(NumericExpression& expr, Session* session) const;
         
         /** populates the variables of an expression with attribute values and evals the expression. */
-        const std::string& eval(StringExpression& expr, FilterContext const* context=0L) const;
+        const std::string& eval(StringExpression& expr, const FilterContext* context) const;
         const std::string& eval(StringExpression& expr, Session* session) const;
 
     public:
@@ -238,7 +248,7 @@ namespace osgEarth { namespace Features
         std::string getGeoJSON() const;
 
         /** Gets a FeatureList as a GeoJSON FeatureCollection */
-        static std::string featuresToGeoJSON( FeatureList& features);
+        static std::string featuresToGeoJSON(const FeatureList& features);
 
     public:
         /**
@@ -246,12 +256,19 @@ namespace osgEarth { namespace Features
          */
         void transform( const SpatialReference* srs );
 
+        /**
+         * Splits this feature into multiple features if it is a geodetic feature and cross the date line.
+         */
+        void splitAcrossDateLine(FeatureList& splitFeatures);
+
     protected:
 
         Feature( FeatureID fid =0L );
 
+        virtual ~Feature();
+
         FeatureID                            _fid;
-        osg::ref_ptr<Symbology::Geometry>    _geom;
+        osg::ref_ptr<Geometry>               _geom;
         osg::ref_ptr<const SpatialReference> _srs;
         AttributeTable                       _attrs;
         optional<Style>                      _style;
diff --git a/src/osgEarthFeatures/Feature.cpp b/src/osgEarthFeatures/Feature.cpp
index 998f5f0..bb1d7f0 100644
--- a/src/osgEarthFeatures/Feature.cpp
+++ b/src/osgEarthFeatures/Feature.cpp
@@ -17,8 +17,10 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>
  */
 #include <osgEarthFeatures/Feature>
-#include <osgEarthFeatures/Session>
+#include <osgEarthFeatures/FilterContext>
 #include <osgEarthFeatures/GeometryUtils>
+#include <osgEarthFeatures/ScriptEngine>
+
 #include <osgEarth/StringUtils>
 #include <osgEarth/JsonUtils>
 #include <algorithm>
@@ -176,6 +178,11 @@ _srs      ( rhs._srs.get() )
     dirty();
 }
 
+Feature::~Feature()
+{
+    //nop
+}
+
 FeatureID
 Feature::getFID() const 
 {
@@ -188,6 +195,16 @@ Feature::setFID(FeatureID fid)
     _fid = fid;
 }
 
+GeoExtent
+Feature::getExtent() const
+{
+    if (!getSRS() || !getGeometry())
+    {
+        return GeoExtent::INVALID;
+    }
+    return GeoExtent(getSRS(), getGeometry()->getBounds());
+}
+
 void
 Feature::setSRS( const SpatialReference* srs )
 {
@@ -402,7 +419,11 @@ Feature::eval( StringExpression& expr, FilterContext const* context ) const
           if (result.success())
             val = result.asString();
           else
-            OE_WARN << LC << "Feature Script error on '" << expr.expr() << "': " << result.message() << std::endl;
+          {
+            // Couldn't execute it as code, just take it as a string literal.
+            val = i->first;
+            OE_DEBUG << LC << "Feature Script error on '" << expr.expr() << "': " << result.message() << std::endl;
+          }
         }
       }
 
@@ -434,7 +455,11 @@ Feature::eval(StringExpression& expr, Session* session) const
                 if (result.success())
                     val = result.asString();
                 else
-                    OE_WARN << LC << "Feature Script error on '" << expr.expr() << "': " << result.message() << std::endl;
+                {
+                    // Couldn't execute it as code, just take it as a string literal.
+                    val = i->first;
+                    OE_DEBUG << LC << "Feature Script error on '" << expr.expr() << "': " << result.message() << std::endl;
+                }
             }
         }
 
@@ -495,6 +520,7 @@ bool Feature::getWorldBoundingPolytope( const osg::BoundingSphered& bs, const Sp
 {
     if ( bs.valid() )
     {
+        out_polytope.getMaskStack().clear();
         out_polytope.clear();
 
         // add planes for the four sides of the BS. Normals point inwards.
@@ -533,6 +559,18 @@ bool Feature::getWorldBoundingPolytope( const osg::BoundingSphered& bs, const Sp
     return false;
 }
 
+GeoExtent
+Feature::calculateExtent() const
+{    
+    GeoExtent e(getSRS());
+    ConstGeometryIterator gi(getGeometry(), false);
+    while (gi.hasMore()) {
+        const Geometry* part = gi.next();
+        for (Geometry::const_iterator v = part->begin(); v != part->end(); ++v)
+            e.expandToInclude(*v);
+    }
+    return e;
+}
 
 std::string
 Feature::getGeoJSON() const
@@ -608,16 +646,16 @@ Feature::getGeoJSON() const
     return Json::FastWriter().write( root );
 }
 
-std::string Feature::featuresToGeoJSON( FeatureList& features)
+std::string Feature::featuresToGeoJSON( const FeatureList& features)
 {
     std::stringstream buf;
 
     buf << "{\"type\": \"FeatureCollection\", \"features\": [";
 
-    FeatureList::iterator last = features.end();
+    FeatureList::const_iterator last = features.end();
     last--;
 
-    for (FeatureList::iterator i = features.begin(); i != features.end(); i++)
+    for (FeatureList::const_iterator i = features.begin(); i != features.end(); i++)
     {
         buf << i->get()->getGeoJSON();
         if (i != last)
@@ -650,3 +688,47 @@ void Feature::transform( const SpatialReference* srs )
     }
     setSRS( srs );
 }
+
+void Feature::splitAcrossDateLine(FeatureList& splitFeatures)
+{
+    splitFeatures.clear();
+    
+     // If the feature is geodetic, try to split it across the dateline.
+    if (getSRS() && getSRS()->isGeodetic())
+    {
+        GeoExtent extent(getSRS(), getGeometry()->getBounds());
+        // Only split the feature if it crosses the antimerdian
+        if (extent.crossesAntimeridian())
+        {
+            // This tries to split features across the dateline in three different zones.  -540 to -180, -180 to 180, and 180 to 540.
+            double minLon = -540;
+            for (int i = 0; i < 3; i++)
+            {
+                double offset = minLon - -180.0;
+                double maxLon = minLon + 360.0;
+                Bounds bounds(minLon, -90.0, maxLon, 90.0);
+                osg::ref_ptr< Geometry > croppedGeometry;
+                if (getGeometry()->crop(bounds, croppedGeometry))
+                {
+                    // If the geometry was cropped, offset the x coordinate so it's within normal longitude ranges.
+                    for (int j = 0; j < croppedGeometry->size(); j++)
+                    {
+                        (*croppedGeometry)[j].x() -= offset;
+                    }
+                    osg::ref_ptr< Feature > croppedFeature = new Feature(*this);
+                    // Make sure the feature is wound correctly.
+                    croppedGeometry->rewind(osgEarth::Symbology::Geometry::ORIENTATION_CCW);
+                    croppedFeature->setGeometry(croppedGeometry.get());
+                    splitFeatures.push_back(croppedFeature);
+                }
+                minLon += 360.0;
+            }
+        }
+    }
+
+    // If we didn't actually split the feature then just add the original
+    if (splitFeatures.empty())
+    {
+        splitFeatures.push_back( this );
+    }   
+}
diff --git a/src/osgEarthFeatures/FeatureCursor b/src/osgEarthFeatures/FeatureCursor
index 8fcc347..7f6a4ba 100644
--- a/src/osgEarthFeatures/FeatureCursor
+++ b/src/osgEarthFeatures/FeatureCursor
@@ -29,6 +29,8 @@ namespace osgEarth { namespace Features
 {   
     using namespace osgEarth;
 
+    //class FeatureFilterChain;
+
     /**
      * A cursor that lets you iterate over a collection of features returned 
      * from a feature query performed on a FeatureStore.
@@ -40,9 +42,9 @@ namespace osgEarth { namespace Features
         virtual Feature* nextFeature() =0;
 
     public:
-        void fill( FeatureList& output );
+        virtual ~FeatureCursor();
 
-        virtual ~FeatureCursor() { }
+        void fill( FeatureList& output );
     };
 
     /**
@@ -53,13 +55,14 @@ namespace osgEarth { namespace Features
     {
     public:
         FeatureListCursor(const FeatureList& input);
-        
-        virtual ~FeatureListCursor() { }
 
         virtual bool hasMore() const;
         virtual Feature* nextFeature();
 
     protected:
+        
+        virtual ~FeatureListCursor();
+
         FeatureList           _features;
         FeatureList::iterator _iter;
         bool                  _clone;
@@ -72,14 +75,15 @@ namespace osgEarth { namespace Features
     {
     public:
         GeometryFeatureCursor( Symbology::Geometry* geom );
-        GeometryFeatureCursor( Symbology::Geometry* geom, const FeatureProfile* fp, const FeatureFilterList& filters );
-        virtual ~GeometryFeatureCursor() { }
+        GeometryFeatureCursor( Symbology::Geometry* geom, const FeatureProfile* fp, const FeatureFilterChain* filters );
         virtual bool hasMore() const;
         virtual Feature* nextFeature();
+
     protected:
+        virtual ~GeometryFeatureCursor();
         osg::ref_ptr<Symbology::Geometry> _geom;
         osg::ref_ptr<const FeatureProfile> _featureProfile;
-        const FeatureFilterList _filters;
+        osg::ref_ptr<const FeatureFilterChain> _filterChain;
         osg::ref_ptr<Feature> _lastFeature;
     };
 
diff --git a/src/osgEarthFeatures/FeatureCursor.cpp b/src/osgEarthFeatures/FeatureCursor.cpp
index 2c0050d..700311e 100644
--- a/src/osgEarthFeatures/FeatureCursor.cpp
+++ b/src/osgEarthFeatures/FeatureCursor.cpp
@@ -17,6 +17,7 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>
  */
 #include <osgEarthFeatures/FeatureCursor>
+#include <osgEarthFeatures/Filter>
 
 using namespace osgEarth::Features;
 using namespace osgEarth::Symbology;
@@ -24,6 +25,11 @@ using namespace OpenThreads;
 
 //---------------------------------------------------------------------------
 
+FeatureCursor::~FeatureCursor()
+{
+    //nop
+}
+
 void
 FeatureCursor::fill( FeatureList& list )
 {
@@ -42,6 +48,11 @@ _clone   ( false )
     _iter = _features.begin();
 }
 
+FeatureListCursor::~FeatureListCursor()
+{
+    //nop
+}
+
 bool
 FeatureListCursor::hasMore() const
 {
@@ -66,10 +77,15 @@ _geom( geom )
 
 GeometryFeatureCursor::GeometryFeatureCursor(Geometry* geom,
                                              const FeatureProfile* fp,
-                                             const FeatureFilterList& filters) :
+                                             const FeatureFilterChain* filters) :
 _geom          ( geom ),
 _featureProfile( fp ),
-_filters       ( filters )
+_filterChain   ( filters )
+{
+    //nop
+}
+
+GeometryFeatureCursor::~GeometryFeatureCursor()
 {
     //nop
 }
@@ -96,9 +112,12 @@ GeometryFeatureCursor::nextFeature()
         FeatureList list;
         list.push_back( _lastFeature.get() );
 
-        for( FeatureFilterList::const_iterator i = _filters.begin(); i != _filters.end(); ++i )
+        if (_filterChain.valid())
         {
-            cx = i->get()->push( list, cx );
+            for( FeatureFilterChain::const_iterator i = _filterChain->begin(); i != _filterChain->end(); ++i )
+            {
+                cx = i->get()->push( list, cx );
+            }
         }
 
         if ( list.empty() )
diff --git a/src/osgEarthFeatures/FeatureDisplayLayout b/src/osgEarthFeatures/FeatureDisplayLayout
index a097de9..dcb8093 100644
--- a/src/osgEarthFeatures/FeatureDisplayLayout
+++ b/src/osgEarthFeatures/FeatureDisplayLayout
@@ -57,7 +57,7 @@ namespace osgEarth { namespace Features
         optional<std::string>& styleName() { return _styleName; }
         const optional<std::string>& styleName() const { return _styleName; }
 
-
+        
         virtual ~FeatureLevel() { }
 
 
@@ -151,6 +151,12 @@ namespace osgEarth { namespace Features
         optional<float>& minExpiryTime() { return _minExpiryTime; }
         const optional<float>& minExpiryTime() const { return _minExpiryTime; }
 
+        /**
+         * Whether tiles are paged in vs. created at load time (default, paged=true)
+         */
+        optional<bool>& paged() { return _paged; }
+        const optional<bool>& paged() const { return _paged; }
+
 
         /** Adds a new feature level */
         void addLevel( const FeatureLevel& level );
@@ -179,6 +185,7 @@ namespace osgEarth { namespace Features
         optional<float> _priorityOffset;
         optional<float> _priorityScale;
         optional<float> _minExpiryTime;
+        optional<bool>  _paged;
         typedef std::multimap<float,FeatureLevel> Levels;
         Levels _levels;
 
diff --git a/src/osgEarthFeatures/FeatureDisplayLayout.cpp b/src/osgEarthFeatures/FeatureDisplayLayout.cpp
index 39e2f70..bd3a40e 100644
--- a/src/osgEarthFeatures/FeatureDisplayLayout.cpp
+++ b/src/osgEarthFeatures/FeatureDisplayLayout.cpp
@@ -73,7 +73,8 @@ _maxRange      ( 0.0f ),
 _cropFeatures  ( false ),
 _priorityOffset( 0.0f ),
 _priorityScale ( 1.0f ),
-_minExpiryTime ( 0.0f )
+_minExpiryTime ( 0.0f ),
+_paged(true)
 {
     fromConfig( conf );
 }
@@ -89,6 +90,7 @@ FeatureDisplayLayout::fromConfig( const Config& conf )
     conf.getIfSet( "min_expiry_time",  _minExpiryTime );
     conf.getIfSet( "min_range",        _minRange );
     conf.getIfSet( "max_range",        _maxRange );
+    conf.getIfSet("paged", _paged);
     ConfigSet children = conf.children( "level" );
     for( ConfigSet::const_iterator i = children.begin(); i != children.end(); ++i )
         addLevel( FeatureLevel( *i ) );
@@ -106,6 +108,7 @@ FeatureDisplayLayout::getConfig() const
     conf.addIfSet( "min_expiry_time",  _minExpiryTime );
     conf.addIfSet( "min_range",        _minRange );
     conf.addIfSet( "max_range",        _maxRange );
+    conf.addIfSet("paged", _paged);
     for( Levels::const_iterator i = _levels.begin(); i != _levels.end(); ++i )
         conf.add( i->second.getConfig() );
     return conf;
diff --git a/src/osgEarthFeatures/FeatureDrawSet b/src/osgEarthFeatures/FeatureDrawSet
deleted file mode 100644
index e918e35..0000000
--- a/src/osgEarthFeatures/FeatureDrawSet
+++ /dev/null
@@ -1,91 +0,0 @@
-/* -*-c++-*- */
-/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2016 Pelican Mapping
- * http://osgearth.org
- *
- * osgEarth is free software; you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>
- */
-#ifndef OSGEARTHFEATURES_FEATURE_DRAW_SET_H
-#define OSGEARTHFEATURES_FEATURE_DRAW_SET_H 1
-
-#include <osgEarthFeatures/Common>
-#include <osg/Geometry>
-#include <set>
-
-namespace osgEarth { namespace Features
-{
-    /**
-     * Contains a catalog of nodes and/or primitive set groups that comprise
-     * a single Feature in the scene.
-     */
-    class OSGEARTHFEATURES_EXPORT FeatureDrawSet
-    {     
-    public: // types
-        typedef osg::Geometry::PrimitiveSetList                        PrimitiveSets;
-        struct DrawableSlice {
-            osg::ref_ptr<osg::Drawable> drawable;
-            PrimitiveSets               primSets;
-            osg::Matrixd                local2world;
-        };
-        //typedef std::pair< osg::ref_ptr<osg::Drawable>, PrimitiveSets> DrawableSlice;
-        typedef std::vector<DrawableSlice>                             DrawableSlices;
-        typedef std::vector< osg::ref_ptr<osg::Node> >                 Nodes;
-
-    public:
-        FeatureDrawSet();
-        virtual ~FeatureDrawSet();
-
-        /** Nodes comprising this draw set */
-        Nodes& nodes() { return _nodes; }
-        const Nodes& nodes() const { return _nodes; }
-
-        /** Drawable/primitive-set-list pairs comprising this draw set */
-        DrawableSlices& slices() { return _slices; }
-        const DrawableSlices& slices() const { return _slices; }
-
-        /** Gets the primitive sets list associated with a drawable, creating the entry as necessary */
-        PrimitiveSets& getOrCreateSlice(osg::Drawable* d);
-
-        /** Gets a slice, given a drawable; or slices().end() if not found. */
-        DrawableSlices::iterator slice(osg::Drawable* d);
-        DrawableSlices::const_iterator slice(osg::Drawable* d) const;
-
-        /** Whether the draw set is empty */
-        bool empty() const { return _nodes.empty() && _slices.empty(); }
-
-        /** Sets the visibility of the draw set. */
-        void setVisible( bool value );
-
-        /** Clears out this draw set */
-        void clear();
-
-        /** Collects a set containing primitive indicies used in a slice. */
-        void collectPrimitiveIndexSet( const DrawableSlice& slice, std::set<unsigned>& output ) const;
-        
-        /** Creates a shallow copy of the draw set under its own scene graph */
-        osg::Node* createCopy();
-
-    private:
-        Nodes          _nodes;
-        DrawableSlices _slices;
-
-        typedef std::vector<unsigned> Masks;
-
-        bool            _visible;
-        Masks           _invisibleMasks;
-    };
-
-} } // namespace osgEarth::Features
-
-#endif // OSGEARTHFEATURES_FEATURE_DRAW_SET_H
diff --git a/src/osgEarthFeatures/FeatureDrawSet.cpp b/src/osgEarthFeatures/FeatureDrawSet.cpp
deleted file mode 100644
index 9076b84..0000000
--- a/src/osgEarthFeatures/FeatureDrawSet.cpp
+++ /dev/null
@@ -1,257 +0,0 @@
-/* -*-c++-*- */
-/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2016 Pelican Mapping
- * http://osgearth.org
- *
- * osgEarth is free software; you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>
- */
-#include <osgEarthFeatures/FeatureDrawSet>
-#include <osgEarth/LineFunctor>
-#include <osg/Geode>
-#include <osg/MatrixTransform>
-#include <osg/NodeVisitor>
-
-using namespace osgEarth;
-using namespace osgEarth::Features;
-
-#define LC "[FeatureDrawSet] "
-
-//-----------------------------------------------------------------------------
-
-namespace
-{
-    // walks a node path, accumulating the state, when merges in the final set and returns the result.
-    osg::StateSet* accumulateStateSet( const osg::NodePath& path, osg::StateSet* final )
-    {
-        osg::StateSet* s = new osg::StateSet();
-        for( osg::NodePath::const_iterator i = path.begin(); i != path.end(); ++i )
-        {
-            if ( (*i)->getStateSet() )
-                s->merge( *(*i)->getStateSet() );
-        }
-
-        if ( final )
-            s->merge( *final );
-        return s;
-    }
-}
-
-//-----------------------------------------------------------------------------
-
-FeatureDrawSet::FeatureDrawSet() :
-_visible( true )
-{
-    //nop
-}
-
-FeatureDrawSet::~FeatureDrawSet()
-{
-}
-
-
-FeatureDrawSet::PrimitiveSets&
-FeatureDrawSet::getOrCreateSlice(osg::Drawable* d)
-{
-    for( DrawableSlices::iterator i = _slices.begin(); i != _slices.end(); ++i )
-    {
-        if ( i->drawable.get() == d )
-        {
-            return i->primSets;
-        }
-    }
-
-    _slices.push_back( DrawableSlice() );
-    _slices.back().drawable = d;
-    if ( d && d->getNumParents() > 0 )
-        _slices.back().local2world = osg::computeLocalToWorld( d->getParent(0)->getParentalNodePaths()[0] );
-    return _slices.back().primSets;
-}
-
-FeatureDrawSet::DrawableSlices::iterator 
-FeatureDrawSet::slice(osg::Drawable* d)
-{
-    for( DrawableSlices::iterator i = _slices.begin(); i != _slices.end(); ++i )
-    {
-        if ( i->drawable.get() == d )
-        {
-            return i;
-        }
-    }
-    return _slices.end();
-}
-
-FeatureDrawSet::DrawableSlices::const_iterator 
-FeatureDrawSet::slice(osg::Drawable* d) const
-{
-    for( DrawableSlices::const_iterator i = _slices.begin(); i != _slices.end(); ++i )
-    {
-        if ( i->drawable.get() == d )
-        {
-            return i;
-        }
-    }
-    return _slices.end();
-}
-
-
-void
-FeatureDrawSet::setVisible( bool visible )
-{
-    if ( _visible )
-    {
-        _invisibleMasks.clear();
-        for( unsigned i=0; i<_nodes.size(); ++i )
-        {
-            _invisibleMasks.push_back( _nodes[i]->getNodeMask() );
-            _nodes[i]->setNodeMask( 0 );
-        }
-
-        for( unsigned i=0; i < _slices.size(); ++i )
-        {
-            DrawableSlice& slice = _slices[i];
-            osg::Geometry* geom = slice.drawable->asGeometry();
-            for( PrimitiveSets::iterator p = slice.primSets.begin(); p != slice.primSets.end(); ++p )
-                geom->removePrimitiveSet( geom->getPrimitiveSetIndex(p->get()) );
-        }
-    }
-
-    else // (!_visible)
-    {
-        for( unsigned i=0; i<_nodes.size(); ++i )
-        {
-            _nodes[i]->setNodeMask( _invisibleMasks[i] );
-        }
-        _invisibleMasks.clear();
-
-        for( unsigned i=0; i < _slices.size(); ++i )
-        {
-            DrawableSlice& slice = _slices[i];
-            osg::Geometry* geom = slice.drawable->asGeometry();
-            for( PrimitiveSets::iterator p = slice.primSets.begin(); p != slice.primSets.end(); ++p )
-                geom->addPrimitiveSet( p->get() );
-        }
-    }
-
-    _visible = visible;
-}
-
-
-void
-FeatureDrawSet::clear()
-{
-    _nodes.clear();
-    _slices.clear();
-    _invisibleMasks.clear();
-    _visible = true;
-}
-
-
-osg::Node*
-FeatureDrawSet::createCopy()
-{
-    osg::Group* group = new osg::Group();
-
-    for( Nodes::iterator n = _nodes.begin(); n != _nodes.end(); ++n )
-    {
-        osg::Node* node = n->get();
-        osg::Node* nodeCopy = osg::clone(node, osg::CopyOp::SHALLOW_COPY);
-        osg::Matrix local2world = osg::computeLocalToWorld( node->getParentalNodePaths()[0] );
-        if ( !local2world.isIdentity() )
-        {
-            osg::MatrixTransform* xform = new osg::MatrixTransform(local2world);
-            xform->addChild( nodeCopy );
-            group->addChild( xform );
-        }
-        else
-        {
-            group->addChild( nodeCopy );
-        }
-    }
-
-    osg::Geode* geode = 0L;
-    for( DrawableSlices::iterator p = _slices.begin(); p != _slices.end(); ++p )
-    {
-        DrawableSlice& slice = *p;
-
-        osg::Drawable* d = slice.drawable.get();
-
-        const PrimitiveSets& psets = slice.primSets;
-        if ( psets.size() > 0 )
-        {        
-            osg::Geometry* featureGeom = d->asGeometry();
-
-            osg::NodePath path = featureGeom->getParent(0)->getParentalNodePaths()[0];
-
-            // make a shallow copy of the geometry (share all the buffer arrays)
-            osg::Geometry* copiedGeom = new osg::Geometry( *featureGeom, osg::CopyOp::SHALLOW_COPY );
-            copiedGeom->setPrimitiveSetList( psets );
-
-            // build the state set do it matches:
-            copiedGeom->setStateSet( accumulateStateSet(path, copiedGeom->getStateSet()) );
-
-            // add to our geode
-            if ( !geode )
-                geode = new osg::Geode();
-
-            geode->addDrawable( copiedGeom );
-
-            // include a matrix transform if necessary:
-            osg::Matrix local2world = osg::computeLocalToWorld( path );
-            if ( !local2world.isIdentity() )
-            {
-                osg::MatrixTransform* xform = new osg::MatrixTransform(local2world);
-                xform->addChild( geode );
-                group->addChild( xform );
-            }
-            else
-            {
-                group->addChild( geode );
-            }
-        }
-    }
-
-    return group;
-}
-
-
-namespace
-{
-    struct IndexCollector
-    {
-        void operator()(GLuint i) {
-            _set->insert( unsigned(i) );
-        }
-        void operator()(GLushort i) { 
-            _set->insert( unsigned(i) );
-        }
-        void operator()(GLubyte i) { 
-            _set->insert( unsigned(i) );
-        }
-
-        std::set<unsigned>* _set;
-    };
-}
-
-
-void
-FeatureDrawSet::collectPrimitiveIndexSet( const DrawableSlice& slice, std::set<unsigned>& output ) const
-{
-    for( PrimitiveSets::const_iterator p = slice.primSets.begin(); p != slice.primSets.end(); ++p )
-    {
-        SimpleIndexFunctor<IndexCollector> f;
-        f._set = &output;
-        p->get()->accept( f );
-    }
-}
-
diff --git a/src/osgEarthFeatures/FeatureListSource b/src/osgEarthFeatures/FeatureListSource
index 8afb80c..7d4e9fd 100644
--- a/src/osgEarthFeatures/FeatureListSource
+++ b/src/osgEarthFeatures/FeatureListSource
@@ -25,7 +25,6 @@
 
 #include <osgEarthFeatures/Common>
 #include <osgEarthFeatures/Feature>
-#include <osgEarthFeatures/FeatureCursor>
 #include <osgEarthFeatures/FeatureSource>
 
 #include <osgEarth/Profile>
@@ -33,10 +32,12 @@
 
 namespace osgEarth { namespace Features
 {   
+    class FeatureCursor;
+
     /**
      * @deprecated - use a FeatureNode instead
      */
-    class OSGEARTHFEATURES_EXPORT FeatureListSource : public osgEarth::Features::FeatureSource
+    class OSGEARTHFEATURES_EXPORT FeatureListSource : public FeatureSource
     {
     public:
         /**
@@ -52,6 +53,8 @@ namespace osgEarth { namespace Features
         FeatureListSource(const GeoExtent& defaultExtent );
 
         virtual ~FeatureListSource() { }
+        
+        virtual Status initialize(const osgDB::Options* readOptions) { return Status::OK();  }
 
         virtual FeatureCursor* createFeatureCursor( const Symbology::Query& query );
 
diff --git a/src/osgEarthFeatures/FeatureListSource.cpp b/src/osgEarthFeatures/FeatureListSource.cpp
index c653e83..636ccb9 100644
--- a/src/osgEarthFeatures/FeatureListSource.cpp
+++ b/src/osgEarthFeatures/FeatureListSource.cpp
@@ -17,6 +17,8 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>
  */
 #include <osgEarthFeatures/FeatureListSource>
+#include <osgEarthFeatures/FeatureCursor>
+#include <osgEarthFeatures/Filter>
 
 using namespace osgEarth::Features;
 
@@ -44,7 +46,7 @@ FeatureListSource::createFeatureCursor( const Symbology::Query& query )
     FeatureList cursorFeatures;
     for (FeatureList::iterator itr = _features.begin(); itr != _features.end(); ++itr)
     {
-        Feature* feature = new osgEarth::Features::Feature(*(itr->get()), osg::CopyOp::DEEP_COPY_ALL);        
+        Feature* feature = new Feature(*(itr->get()), osg::CopyOp::DEEP_COPY_ALL);        
         cursorFeatures.push_back( feature );
     }    
     return new FeatureListCursor( cursorFeatures );
diff --git a/src/osgEarthFeatures/FeatureMaskLayer b/src/osgEarthFeatures/FeatureMaskLayer
new file mode 100644
index 0000000..e291fa7
--- /dev/null
+++ b/src/osgEarthFeatures/FeatureMaskLayer
@@ -0,0 +1,124 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+ * Copyright 2016 Pelican Mapping
+ * http://osgearth.org
+ *
+ * osgEarth is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+
+#ifndef OSGEARTH_FEATURE_MASK_LAYER_H
+#define OSGEARTH_FEATURE_MASK_LAYER_H 1
+
+#include <osgEarthFeatures/Common>
+#include <osgEarthFeatures/FeatureSourceLayer>
+
+#include <osgEarth/MaskLayer>
+#include <osgEarth/LayerListener>
+
+namespace osgEarth { namespace Features
+{
+    /**
+     * Configuration options for a FeatureMaskLayer.
+     */
+    class OSGEARTHFEATURES_EXPORT FeatureMaskLayerOptions : public MaskLayerOptions
+    {
+    public:
+        FeatureMaskLayerOptions(const ConfigOptions& options =ConfigOptions());
+
+        /** dtor */
+        virtual ~FeatureMaskLayerOptions() { }
+        
+        /** Map layer containing the feature data */
+        optional<std::string>& featureSourceLayer() { return _featureSourceLayer; }
+        const optional<std::string>& featureSourceLayer() const { return _featureSourceLayer; }
+
+        /** Embedded feature data definition */
+        optional<FeatureSourceOptions>& featureSource() { return _featureSource; }
+        const optional<FeatureSourceOptions>& featureSource() const { return _featureSource; }
+
+    public:
+        virtual Config getConfig() const;
+        virtual void mergeConfig( const Config& conf );
+
+    private:
+        void fromConfig( const Config& conf );
+
+        optional<std::string> _featureSourceLayer;
+        optional<FeatureSourceOptions> _featureSource;
+    };
+
+
+    /**
+     * MaskLayer whose boundary geometry comes from a feature source.
+     */
+    class OSGEARTHFEATURES_EXPORT FeatureMaskLayer : public MaskLayer
+    {
+    public:
+        META_Layer(osgEarth, FeatureMaskLayer, FeatureMaskLayerOptions);
+
+        /**
+         * Constructs a new mask layer based on a configuration setup.
+         */
+        FeatureMaskLayer(const FeatureMaskLayerOptions& options =FeatureMaskLayerOptions());
+
+        /** The feature source layer from which to get boundary features */
+        void setFeatureSourceLayer(FeatureSourceLayer* layer);
+
+        /** The feature source from which to get boundary features */
+        void setFeatureSource(FeatureSource* source);
+
+    public: // MaskLayer
+
+        /** 
+         * Gets the geometric boundary polygon representing the area of the
+         * terrain to mask out.
+         */
+        virtual osg::Vec3dArray* getOrCreateMaskBoundary(
+            float                   heightScale,
+            const SpatialReference* srs,
+            ProgressCallback*       progress );
+
+    public: // Layer
+
+        virtual const Status& open();
+
+    protected: // Layer
+
+        virtual void addedToMap(const Map*);
+
+        virtual void removedFromMap(const Map*);
+
+    protected:
+
+        /** Create from subclass. */
+        FeatureMaskLayer(FeatureMaskLayerOptions*);
+
+        /** dtor */
+        virtual ~FeatureMaskLayer();
+
+    private:
+
+        LayerListener<FeatureMaskLayer, FeatureSourceLayer> _featureSourceLayerListener;
+
+        void create();
+
+        osg::ref_ptr<FeatureSource> _featureSource;
+        osg::ref_ptr<osg::Vec3dArray> _boundary;
+        OpenThreads::Mutex _boundaryMutex;
+    };
+
+} } // namespace osgEarth::Features
+
+#endif // OSGEARTH_FEATURE_MASK_LAYER_H
+
diff --git a/src/osgEarthFeatures/FeatureMaskLayer.cpp b/src/osgEarthFeatures/FeatureMaskLayer.cpp
new file mode 100644
index 0000000..3d08387
--- /dev/null
+++ b/src/osgEarthFeatures/FeatureMaskLayer.cpp
@@ -0,0 +1,209 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+ * Copyright 2016 Pelican Mapping
+ * http://osgearth.org
+ *
+ * osgEarth is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+#include <osgEarthFeatures/FeatureMaskLayer>
+#include <osgEarthFeatures/FeatureCursor>
+
+using namespace osgEarth;
+using namespace osgEarth::Features;
+
+#define LC "[FeatureMaskLayer] "
+
+REGISTER_OSGEARTH_LAYER(feature_mask, FeatureMaskLayer);
+REGISTER_OSGEARTH_LAYER(mask, FeatureMaskLayer);
+
+//........................................................................
+
+FeatureMaskLayerOptions::FeatureMaskLayerOptions(const ConfigOptions& options) :
+MaskLayerOptions(options)
+{
+    fromConfig(_conf);
+}
+
+void
+FeatureMaskLayerOptions::mergeConfig( const Config& conf )
+{
+    ConfigOptions::mergeConfig( conf );
+    fromConfig( conf );
+}
+
+void
+FeatureMaskLayerOptions::fromConfig(const Config& conf)
+{
+    conf.getIfSet("feature_source", _featureSourceLayer);
+    conf.getObjIfSet("features", _featureSource);
+}
+
+Config
+FeatureMaskLayerOptions::getConfig() const
+{
+    Config conf = MaskLayerOptions::getConfig();
+    conf.key() = "feature_mask";
+    conf.addIfSet("feature_source", _featureSourceLayer);
+    conf.addObjIfSet("features", _featureSource);
+    return conf;
+}
+
+//........................................................................
+
+FeatureMaskLayer::FeatureMaskLayer(const FeatureMaskLayerOptions& options) :
+MaskLayer(&_optionsConcrete),
+_options(&_optionsConcrete),
+_optionsConcrete(options)
+{
+    init();
+}
+
+FeatureMaskLayer::FeatureMaskLayer(FeatureMaskLayerOptions* optionsPtr) :
+MaskLayer(optionsPtr),
+_options(optionsPtr)
+{
+    //nop - init called from base class
+}
+
+FeatureMaskLayer::~FeatureMaskLayer()
+{
+    //nop
+}
+
+void
+FeatureMaskLayer::setFeatureSourceLayer(FeatureSourceLayer* layer)
+{
+    if (layer && layer->getStatus().isError())
+    {
+        setStatus(Status::Error(Status::ResourceUnavailable, "Feature source layer is unavailable; check for error"));
+        return;
+    }
+
+    if (layer)
+        OE_INFO << LC << "Feature source layer is \"" << layer->getName() << "\"\n";
+
+    setFeatureSource(layer ? layer->getFeatureSource() : 0L);
+}
+
+void
+FeatureMaskLayer::setFeatureSource(FeatureSource* source)
+{
+    if (_featureSource != source)
+    {
+        if (source)
+            OE_INFO << LC << "Setting feature source \"" << source->getName() << "\"\n";
+
+        _featureSource = source;
+
+        if (source && source->getStatus().isError())
+        {
+            setStatus(source->getStatus());
+            return;
+        }
+
+        create();
+    }
+}
+
+const Status&
+FeatureMaskLayer::open()
+{
+    if (options().featureSource().isSet())
+    {
+        FeatureSource* fs = FeatureSourceFactory::create(options().featureSource().get());
+        if (fs)
+        {
+            fs->setReadOptions(getReadOptions());
+            fs->open();
+            setFeatureSource(fs);
+        }
+        else
+        {
+            setStatus(Status(Status::ConfigurationError, "Cannot create feature source"));
+        }
+    }
+    return MaskLayer::open();
+}
+
+osg::Vec3dArray*
+FeatureMaskLayer::getOrCreateMaskBoundary(float heightScale,
+                                          const SpatialReference* srs,
+                                          ProgressCallback* progress)
+{
+    if (!_featureSource.valid())
+        return 0L;
+
+    if (!_boundary.valid())
+    {
+        Threading::ScopedMutexLock lock(_boundaryMutex);
+        if (!_boundary.valid())
+        {
+            osg::ref_ptr<FeatureCursor> cursor = _featureSource->createFeatureCursor();
+            if (cursor.valid() && cursor->hasMore())
+            {
+                Feature* f = cursor->nextFeature();
+                if (f && f->getGeometry())
+                {
+                    f->transform(srs);
+                    _boundary = f->getGeometry()->createVec3dArray();
+                }
+            }
+        }
+    }
+
+    return _boundary.get();
+}
+
+void
+FeatureMaskLayer::addedToMap(const Map* map)
+{
+    OE_DEBUG << LC << "addedToMap\n";
+
+    if (options().featureSourceLayer().isSet())
+    {
+        _featureSourceLayerListener.clear();
+
+        _featureSourceLayerListener.listen(
+            map,
+            options().featureSourceLayer().get(),
+            this,
+            &FeatureMaskLayer::setFeatureSourceLayer);
+    }
+
+    create();
+}
+
+void
+FeatureMaskLayer::removedFromMap(const Map* map)
+{
+    _featureSourceLayerListener.clear();
+}
+
+void
+FeatureMaskLayer::create()
+{
+    if (!_featureSource.valid())
+    {
+        setStatus(Status(Status::ConfigurationError, "No feature source available"));
+        return;
+    }
+
+    if (!_featureSource->getFeatureProfile())
+    {
+        setStatus(Status(Status::ConfigurationError, "Feature source cannot report profile (is it open?)"));
+        return;
+    }
+
+    setStatus(Status::OK());
+}
diff --git a/src/osgEarthFeatures/FeatureModelGraph b/src/osgEarthFeatures/FeatureModelGraph
index 903b302..253e4fd 100644
--- a/src/osgEarthFeatures/FeatureModelGraph
+++ b/src/osgEarthFeatures/FeatureModelGraph
@@ -22,25 +22,21 @@
 
 #include <osgEarthFeatures/Common>
 #include <osgEarthFeatures/FeatureModelSource>
-#include <osgEarthFeatures/Session>
 #include <osgEarthSymbology/Style>
-#include <osgEarth/OverlayNode>
 #include <osgEarth/NodeUtils>
 #include <osgEarth/ThreadingUtils>
-#include <osgEarth/DepthOffset>
+#include <osgEarth/SceneGraphCallback>
 #include <osgDB/Callbacks>
 #include <osg/Node>
 #include <set>
 
-namespace osgEarth {
-    class ClampableNode;
-}
-
 namespace osgEarth { namespace Features
 {
     using namespace osgEarth;
     using namespace osgEarth::Symbology;
 
+    class Session;
+
     /**
      * A scene graph that renders feature data.
      * This class will handle all the internals of selecting features, gridding feature
@@ -53,33 +49,37 @@ namespace osgEarth { namespace Features
         /**
          * Constructs a new model graph.
          *
+         * @param session
+         *      Session under which to create elements in this graph
          * @param options
          *      Model source options
          * @param factory
          *      Node factory that will be invoked to compile feature data into nodes
-         * @param session
-         *      Session under which to create elements in this graph
-         * @param preMergeOperations
-         *      Node operations that will execute in the pager thread after it finishes
-         *      building a node
-         * @param postMergeOperations
-         *      Node operations that will execute after merging a node into the graph
          */
         FeatureModelGraph(
             Session*                         session,
             const FeatureModelSourceOptions& options,
             FeatureNodeFactory*              factory,
-            ModelSource*                     modelSource,
-            RefNodeOperationVector*          preMergeOperations,
-            RefNodeOperationVector*          postMergeOperations);
-        
-        /* @deprecated */
+            SceneGraphCallbacks*             callbacks);
+
+        /**
+         * Constructs a new model graph.
+         *
+         * @param session
+         *      Session under which to create elements in this graph
+         * @param options
+         *      Model source options
+         * @param factory
+         *      Node factory that will be invoked to compile feature data into nodes
+         * @param callbacks
+         *      List of callbacks to invoke as nodes are created and merged into the scene graph.
+         */
         FeatureModelGraph(
             Session*                         session,
             const FeatureModelSourceOptions& options,
             FeatureNodeFactory*              factory,
-            RefNodeOperationVector*          preMergeOperations,
-            RefNodeOperationVector*          postMergeOperations);
+            ModelSource*                     modelSource,
+            SceneGraphCallbacks*             callbacks);
 
         /**
          * Loads and returns a subnode. Used internally for paging.
@@ -90,6 +90,11 @@ namespace osgEarth { namespace Features
             const osgDB::Options* readOptions);
 
         /**
+         * Set a scene graph callback host for this FMG
+         */
+        void setSceneGraphCallbacks(SceneGraphCallbacks* callbacks);
+
+        /**
          * Style sheet associated with this feature graph.
          */
         StyleSheet* getStyles() { return _session->styles(); }
@@ -102,12 +107,7 @@ namespace osgEarth { namespace Features
         /**
          * Session associated with this feature graph.
          */
-        Session* getSession() { return _session; }
-
-        /**
-         * UID given to this feature graph when registered with the pseudo-loader
-         */
-        UID getUID() const { return _uid; }
+        Session* getSession();
 
         /**
          * Mark the feature graph dirty and in need of regeneration 
@@ -119,6 +119,9 @@ namespace osgEarth { namespace Features
          */
         const std::vector<const FeatureLevel*>& getLevels() const { return _lodmap; };
 
+        //! Options passed in.
+        const FeatureModelSourceOptions& options() const { return _options; }
+
     public: // osg::Node
 
         virtual void traverse(osg::NodeVisitor& nv);
@@ -201,7 +204,6 @@ namespace osgEarth { namespace Features
         FeatureModelSourceOptions        _options;
         osg::ref_ptr<FeatureNodeFactory> _factory;
         osg::ref_ptr<Session>            _session;
-        UID                              _uid;
         std::set<std::string>            _blacklist;
         Threading::ReadWriteMutex        _blacklistMutex;
         GeoExtent                        _usableFeatureExtent;
@@ -213,38 +215,22 @@ namespace osgEarth { namespace Features
         bool                             _dirty;
         bool                             _pendingUpdate;
         std::vector<const FeatureLevel*> _lodmap;
-        OpenThreads::Mutex               _clampableMutex;
-
-        osg::Group*                      _overlayInstalled;
-        osg::ref_ptr<osg::Group>         _overlayPlaceholder;
-        osg::ref_ptr<ClampableNode>      _clampable;
-        DepthOffsetAdapter               _depthOffsetAdapter;
+        OpenThreads::ReentrantMutex      _redrawMutex;
 
         OpenThreads::Atomic _cacheReads;
         OpenThreads::Atomic _cacheHits;
 
-        enum OverlayChange {
-            OVERLAY_NO_CHANGE,
-            OVERLAY_INSTALL_PLACEHOLDER,
-            OVERLAY_INSTALL_CLAMPABLE,
-            OVERLAY_INSTALL_DRAPEABLE
-        };
-        OverlayChange                    _overlayChange;
-
         osg::ref_ptr<osgDB::FileLocationCallback> _defaultFileLocationCallback;
 
-        osg::ref_ptr<RefNodeOperationVector> _preMergeOperations;
-        osg::ref_ptr<RefNodeOperationVector> _postMergeOperations;
-
         osg::observer_ptr<ModelSource> _modelSource;
 
         osg::ref_ptr<FeatureSourceIndex> _featureIndex;
 
+        osg::ref_ptr<SceneGraphCallbacks> _sgCallbacks;
+
         void runPreMergeOperations(osg::Node* node);
         void runPostMergeOperations(osg::Node* node);
         void applyRenderSymbology(const Style& style, osg::Node* node);
-        void checkForGlobalStyles(const Style& style);
-        void changeOverlay();
         bool createOrUpdateNode(FeatureCursor*, const Style&, FilterContext&, const osgDB::Options*, osg::ref_ptr<osg::Node>& output);
     };
 
diff --git a/src/osgEarthFeatures/FeatureModelGraph.cpp b/src/osgEarthFeatures/FeatureModelGraph.cpp
index 1d08f0a..42cd6df 100644
--- a/src/osgEarthFeatures/FeatureModelGraph.cpp
+++ b/src/osgEarthFeatures/FeatureModelGraph.cpp
@@ -20,12 +20,10 @@
 #include <osgEarthFeatures/FeatureModelGraph>
 #include <osgEarthFeatures/CropFilter>
 #include <osgEarthFeatures/FeatureSourceIndexNode>
-#include <osgEarthFeatures/Session>
+#include <osgEarthFeatures/FilterContext>
 
 #include <osgEarth/Map>
 #include <osgEarth/Capabilities>
-#include <osgEarth/Clamping>
-#include <osgEarth/ClampableNode>
 #include <osgEarth/CullingUtils>
 #include <osgEarth/ElevationLOD>
 #include <osgEarth/ElevationQuery>
@@ -33,10 +31,13 @@
 #include <osgEarth/NodeUtils>
 #include <osgEarth/Registry>
 #include <osgEarth/ThreadingUtils>
+#include <osgEarth/Utils>
 
 #include <osg/CullFace>
 #include <osg/PagedLOD>
 #include <osg/ProxyNode>
+#include <osg/PolygonOffset>
+#include <osg/Depth>
 #include <osgDB/FileNameUtils>
 #include <osgDB/ReaderWriter>
 #include <osgDB/WriteFile>
@@ -55,6 +56,8 @@ using namespace osgEarth::Symbology;
 #define OE_TEST OE_NULL
 //#define OE_TEST OE_NOTICE
 
+#define USER_OBJECT_NAME "osgEarth.FeatureModelGraph"
+
 namespace
 {
     // callback to force features onto the high-latency queue.
@@ -75,15 +78,10 @@ namespace
 
 namespace
 {
-    UID                               _uid         = 0;
-    Threading::ReadWriteMutex         _fmgMutex;
-    typedef std::map<UID, osg::observer_ptr<FeatureModelGraph> > FMGRegistry;
-    FMGRegistry _fmgRegistry;
-
-    static std::string s_makeURI( UID uid, unsigned lod, unsigned x, unsigned y ) 
+    static std::string s_makeURI(unsigned lod, unsigned x, unsigned y)
     {
         std::stringstream buf;
-        buf << uid << "." << lod << "_" << x << "_" << y << ".osgearth_pseudo_fmg";
+        buf << lod << "_" << x << "_" << y << ".osgearth_pseudo_fmg";
         std::string str;
         str = buf.str();
         return str;
@@ -94,40 +92,64 @@ namespace
                                 float minRange, 
                                 float maxRange, 
                                 const FeatureDisplayLayout& layout,
-                                RefNodeOperationVector* postMergeOps,
+                                SceneGraphCallbacks* sgCallbacks,
                                 osgDB::FileLocationCallback* flc,
-                                const osgDB::Options* readOptions)
+                                const osgDB::Options* readOptions,
+                                FeatureModelGraph* fmg)
     {
-#ifdef USE_PROXY_NODE_FOR_TESTING
-        ProxyNode* p = new ProxyNode();
+#ifdef USE_PROXY_NODE
+
+        osg::ProxyNode* p = new osg::ProxyNode();
         p->setCenter( bs.center() );
         p->setRadius( bs.radius() );
         p->setFileName( 0, uri );
-        p->setRange( 0, minRange, maxRange );
-        p->setPriorityOffset( 0, layout.priorityOffset().get() );
-        p->setPriorityScale(0, layout.priorityScale().get() );
+        p->setLoadingExternalReferenceMode(osg::ProxyNode::LOAD_IMMEDIATELY);
+
+        // force onto the high-latency thread pool.
+        osgDB::Options* options = Registry::instance()->cloneOrCreateOptions(readOptions);
+        options->setFileLocationCallback( flc );
+        p->setDatabaseOptions( options );
+        // so we can find the FMG instance in the pseudoloader.
+        options->getOrCreateUserDataContainer()->addUserObject(fmg);
+
+        return p;
+
 #else
-        PagedLODWithNodeOperations* p = new PagedLODWithNodeOperations(postMergeOps);
-        p->setCenter( bs.center() );
-        p->setRadius( bs.radius() );
-        p->setFileName( 0, uri );
-        p->setRange( 0, minRange, maxRange );
-        p->setPriorityOffset( 0, layout.priorityOffset().get() );
-        p->setPriorityScale(0, layout.priorityScale().get() );
+        
+        osg::PagedLOD* p;
+
+        if (sgCallbacks)
+        {
+            PagedLODWithSceneGraphCallbacks* plod = new PagedLODWithSceneGraphCallbacks(sgCallbacks);
+            p = plod;
+        }
+        else
+        {
+            p = new osg::PagedLOD();
+        }
+
+        p->setCenter(bs.center());
+        p->setRadius(bs.radius());
+        p->setFileName(0, uri);
+        p->setRange(0, minRange, maxRange);
+        p->setPriorityOffset(0, layout.priorityOffset().get());
+        p->setPriorityScale(0, layout.priorityScale().get());
         if (layout.minExpiryTime().isSet())
         {
             float value = layout.minExpiryTime() >= 0.0f ? layout.minExpiryTime().get() : FLT_MAX;
             p->setMinimumExpiryTime(0, value);
         }
-            
-#endif
 
         // force onto the high-latency thread pool.
         osgDB::Options* options = Registry::instance()->cloneOrCreateOptions(readOptions);
-        options->setFileLocationCallback( flc );
-        p->setDatabaseOptions( options );
+        options->setFileLocationCallback(flc);
+        p->setDatabaseOptions(options);
+        // so we can find the FMG instance in the pseudoloader.
+        OptionsData<FeatureModelGraph>::set(options, USER_OBJECT_NAME, fmg);
 
         return p;
+
+#endif
     }
 }
 
@@ -152,16 +174,24 @@ struct osgEarthFeatureModelPseudoLoader : public osgDB::ReaderWriter
         if ( !acceptsExtension( osgDB::getLowerCaseFileExtension(uri) ) )
             return ReadResult::FILE_NOT_HANDLED;
 
-        UID uid;
+        //UID uid;
         unsigned lod, x, y;
-        sscanf( uri.c_str(), "%u.%d_%d_%d.%*s", &uid, &lod, &x, &y );
+        sscanf( uri.c_str(), "%d_%d_%d.%*s", &lod, &x, &y );
 
-        osg::ref_ptr<FeatureModelGraph> graph = getGraph(uid);
-        if ( graph.valid() )
+        osg::ref_ptr<FeatureModelGraph> graph;
+        if (!OptionsData<FeatureModelGraph>::lock(readOptions, USER_OBJECT_NAME, graph))
+        {
+           OE_WARN << LC << "Internal error - no FeatureModelGraph object in OptionsData\n";
+           return ReadResult::ERROR_IN_READING_FILE;
+        }
+
+        //osg::ref_ptr<FeatureModelGraph> graph = getGraph(uid);
+        // graph is valid at this point, otherwise the above lock would not succeed
+        //if ( graph.valid() )
         {
             // Take a reference on the map to avoid map destruction during thread operation
-            osg::ref_ptr<const Map> map = graph->getSession()->getMap();
-            if (map.valid() == true)
+            //osg::ref_ptr<const Map> map = graph->getSession()->getMap();
+            //if (map.valid() == true)
             {
                 Registry::instance()->startActivity(uri);
                 osg::Node* node = graph->load(lod, x, y, uri, readOptions);
@@ -169,31 +199,6 @@ struct osgEarthFeatureModelPseudoLoader : public osgDB::ReaderWriter
                 return ReadResult(node);
             }
         }
-
-        return ReadResult::ERROR_IN_READING_FILE;
-    }
-
-    static UID registerGraph( FeatureModelGraph* graph )
-    {
-        Threading::ScopedWriteLock lock( _fmgMutex );
-        UID key = ++_uid;
-        _fmgRegistry[key] = graph;
-        OE_TEST << "Registered FMG " << key << std::endl;
-        return key;
-    }
-
-    static void unregisterGraph( UID uid )
-    {
-        Threading::ScopedWriteLock lock( _fmgMutex );
-        _fmgRegistry.erase( uid );
-        OE_TEST << "UNregistered FMG " << uid << std::endl;
-    }
-
-    static FeatureModelGraph* getGraph( UID uid ) 
-    {
-        Threading::ScopedReadLock lock( _fmgMutex );
-        FMGRegistry::const_iterator i = _fmgRegistry.find( uid );
-        return i != _fmgRegistry.end() ? i->second.get() : 0L;
     }
 };
 
@@ -220,9 +225,9 @@ namespace
     }
 
 
-    struct SetupFading : public NodeOperation
+    struct SetupFading : public SceneGraphCallback
     {
-        void operator()( osg::Node* node )
+        void onPostMergeNode( osg::Node* node )
         {
             osg::Uniform* u = FadeEffect::createStartTimeUniform();
             u->set( (float)osg::Timer::instance()->time_s() );
@@ -237,19 +242,13 @@ namespace
 FeatureModelGraph::FeatureModelGraph(Session*                         session,
                                      const FeatureModelSourceOptions& options,
                                      FeatureNodeFactory*              factory,
-                                     ModelSource*                     modelSource,
-                                     RefNodeOperationVector*          preMergeOperations,
-                                     RefNodeOperationVector*          postMergeOperations) :
+                                     SceneGraphCallbacks*             callbacks) :
 _session            ( session ),
 _options            ( options ),
 _factory            ( factory ),
-_modelSource        ( modelSource ),
-_preMergeOperations ( preMergeOperations ),
-_postMergeOperations( postMergeOperations ),
 _dirty              ( false ),
 _pendingUpdate      ( false ),
-_overlayInstalled   ( 0L ),
-_overlayChange      ( OVERLAY_NO_CHANGE )
+_sgCallbacks        ( callbacks )
 {
     ctor();
 }
@@ -257,18 +256,15 @@ _overlayChange      ( OVERLAY_NO_CHANGE )
 FeatureModelGraph::FeatureModelGraph(Session*                         session,
                                      const FeatureModelSourceOptions& options,
                                      FeatureNodeFactory*              factory,
-                                     RefNodeOperationVector*          preMergeOperations,
-                                     RefNodeOperationVector*          postMergeOperations) :
+                                     ModelSource*                     modelSource,
+                                     SceneGraphCallbacks*             callbacks) :
 _session            ( session ),
 _options            ( options ),
 _factory            ( factory ),
-_modelSource        ( 0L ),
-_preMergeOperations ( preMergeOperations ),
-_postMergeOperations( postMergeOperations ),
+_modelSource        ( modelSource ),
 _dirty              ( false ),
 _pendingUpdate      ( false ),
-_overlayInstalled   ( 0L ),
-_overlayChange      ( OVERLAY_NO_CHANGE )
+_sgCallbacks        (callbacks)
 {
     ctor();
 }
@@ -276,21 +272,14 @@ _overlayChange      ( OVERLAY_NO_CHANGE )
 void
 FeatureModelGraph::ctor()
 {
-    _uid = osgEarthFeatureModelPseudoLoader::registerGraph( this );
+    // So we can pass it to the pseudoloader
+    setName(USER_OBJECT_NAME);
+    
+    OE_TEST << LC << "ctor" << std::endl;
 
     // an FLC that queues feature data on the high-latency thread.
     _defaultFileLocationCallback = new HighLatencyFileLocationCallback();
 
-    // set up the callback queues for pre- and post-merge operations.
-
-    // per-merge ops run in the pager thread:
-    if ( !_preMergeOperations.valid())
-        _preMergeOperations = new RefNodeOperationVector();
-
-    // post-merge ops run in the update traversal:
-    if ( !_postMergeOperations.valid() )
-        _postMergeOperations = new RefNodeOperationVector();
-
     // install the stylesheet in the session if it doesn't already have one.
     if ( !_session->styles() )
         _session->setStyles( _options.styles().get() );
@@ -468,11 +457,9 @@ FeatureModelGraph::ctor()
 
     // If the user requests fade-in, install a post-merge operation that will set the 
     // proper fade time for paged nodes.
-    if ( _options.fading().isSet() )
+    if ( _options.fading().isSet() && _sgCallbacks.valid())
     {
-        _postMergeOperations->mutex().writeLock();
-        _postMergeOperations->push_back( new SetupFading() );
-        _postMergeOperations->mutex().writeUnlock();
+        _sgCallbacks->add(new SetupFading());
         OE_INFO << LC << "Added fading post-merge operation" << std::endl;
     }
 
@@ -483,7 +470,19 @@ FeatureModelGraph::ctor()
 
 FeatureModelGraph::~FeatureModelGraph()
 {
-    osgEarthFeatureModelPseudoLoader::unregisterGraph( _uid );
+    //nop
+}
+
+Session*
+FeatureModelGraph::getSession()
+{
+    return _session.get();
+}
+
+void
+FeatureModelGraph::setSceneGraphCallbacks(SceneGraphCallbacks* host)
+{
+    _sgCallbacks = host;
 }
 
 void
@@ -492,7 +491,7 @@ FeatureModelGraph::dirty()
     _dirty = true;
 }
 
-std::ostream& operator << (std::ostream& in, const osg::Vec3d& v) { in << v.x() << ", " << v.y() << ", " << v.z(); return in; }
+//std::ostream& operator << (std::ostream& in, const osg::Vec3d& v) { in << v.x() << ", " << v.y() << ", " << v.z(); return in; }
 
 osg::BoundingSphered
 FeatureModelGraph::getBoundInWorldCoords(const GeoExtent& extent,
@@ -514,19 +513,26 @@ FeatureModelGraph::getBoundInWorldCoords(const GeoExtent& extent,
     {
         workingExtent = extent.transform( _usableMapExtent.getSRS() ); // safe.
     }
+    
+#if 1
+    return workingExtent.createWorldBoundingSphere(-11000, 9000); // lowest and highest points on earth
 
+#else
     workingExtent.getCentroid( center.x(), center.y() );
     
-    double centerZ = 0.0;    
     if ( mapf )
     {
         // Use an appropriate resolution for this extents width
         double resolution = workingExtent.width();
         ElevationQuery query( *mapf );
-        query.setFallBackOnNoData( true );
         GeoPoint p( mapf->getProfile()->getSRS(), center, ALTMODE_ABSOLUTE );
-        query.getElevation( p, center.z(), resolution );
-        centerZ = center.z();
+        float elevation = query.getElevation( p, resolution );
+        // Check for NO_DATA_VALUE and use zero instead.
+        if (elevation == NO_DATA_VALUE)
+        {
+            elevation = 0.0f;
+        }
+        center.z() = elevation;
     }    
 
     corner.x() = workingExtent.xMin();
@@ -563,6 +569,7 @@ FeatureModelGraph::getBoundInWorldCoords(const GeoExtent& extent,
     }
 
     return osg::BoundingSphered( center, (center-corner).length() );
+#endif
 }
 
 osg::Node*
@@ -600,22 +607,30 @@ FeatureModelGraph::setupPaging()
         bs.radius() * _options.layout()->tileSizeFactor().value();
 
     // build the URI for the top-level paged LOD:
-    std::string uri = s_makeURI( _uid, 0, 0, 0 );
-
-    // bulid the top level Paged LOD:
-    osg::Group* pagedNode = createPagedNode( 
-        bs, 
-        uri, 
-        0.0f, 
-        maxRange, 
-        _options.layout().get(),
-        //*_options.layout()->priorityOffset(), 
-        //*_options.layout()->priorityScale(),
-        _postMergeOperations.get(),
-        _defaultFileLocationCallback.get(),
-        getSession()->getDBOptions() );
-
-    return pagedNode;
+    std::string uri = s_makeURI( 0, 0, 0 );
+
+    // bulid the top level node:
+    osg::Node* topNode;
+
+    if (options().layout()->paged() == true)
+    {
+        topNode = createPagedNode( 
+            bs, 
+            uri, 
+            0.0f, 
+            maxRange, 
+            _options.layout().get(),
+            _sgCallbacks.get(),
+            _defaultFileLocationCallback.get(),
+            getSession()->getDBOptions(),
+            this);
+    }
+    else
+    {
+        topNode = load(0, 0, 0, uri, getSession()->getDBOptions());
+    }
+
+    return topNode;
 }
 
 
@@ -627,8 +642,7 @@ FeatureModelGraph::load(unsigned lod, unsigned tileX, unsigned tileY,
                         const std::string& uri,
                         const osgDB::Options* readOptions)
 {
-    OE_DEBUG << LC
-        << "load: " << lod << "_" << tileX << "_" << tileY << std::endl;
+    OE_TEST << LC << "load " << lod << "_" << tileX << "_" << tileY << std::endl;
 
     osg::Group* result = 0L;
     
@@ -647,6 +661,9 @@ FeatureModelGraph::load(unsigned lod, unsigned tileX, unsigned tileY,
 
             // Calculate the bounds of this new tile:
             MapFrame mapf = _session->createMapFrame();
+            if (!mapf.isValid())
+                return 0L;
+
             osg::BoundingSphered tileBound = getBoundInWorldCoords( tileExtent, &mapf );
 
             // Apply the tile range multiplier to calculate a max camera range. The max range is
@@ -659,7 +676,6 @@ FeatureModelGraph::load(unsigned lod, unsigned tileX, unsigned tileY,
             //OE_NOTICE << "  tileFactor = " << tileFactor << " maxRange=" << maxRange << " radius=" << tileBound.radius() << std::endl;
             
 
-#if 1
             // Construct a tile key that will be used to query the source for this tile.            
             // The tilekey x, y, z that is computed in the FeatureModelGraph uses a lower left origin,
             // osgEarth tilekeys use a lower left so we need to invert it.
@@ -668,9 +684,7 @@ FeatureModelGraph::load(unsigned lod, unsigned tileX, unsigned tileY,
             int invertedTileY = h - tileY - 1;
 
             TileKey key(lod, tileX, invertedTileY, featureProfile->getProfile());
-#else
-            TileKey key(lod, tileX, tileY, featureProfile->getProfile());
-#endif
+
             geometry = buildTile( level, tileExtent, &key, readOptions );
             result = geometry;
         }
@@ -828,7 +842,7 @@ FeatureModelGraph::buildSubTilePagedLODs(unsigned        parentLOD,
                 maxRange = subtile_bs.radius() * _options.layout()->tileSizeFactor().value();
             }
 
-            std::string uri = s_makeURI( _uid, subtileLOD, u, v );
+            std::string uri = s_makeURI( subtileLOD, u, v );
 
             // check the blacklist to make sure we haven't unsuccessfully tried
             // this URI before
@@ -847,18 +861,26 @@ FeatureModelGraph::buildSubTilePagedLODs(unsigned        parentLOD,
                     << "; maxrange = " << maxRange
                     << std::endl;
 
-                osg::Group* pagedNode = createPagedNode( 
-                    subtile_bs, 
-                    uri, 
-                    0.0f, maxRange, 
-                    _options.layout().get(),
-                    //*_options.layout()->priorityOffset(), 
-                    //*_options.layout()->priorityScale(),
-                    _postMergeOperations.get(),
-                    _defaultFileLocationCallback.get(),
-                    readOptions);
-
-                parent->addChild( pagedNode );
+                osg::Node* childNode;
+
+                if (options().layout()->paged() == true)
+                {
+                    childNode = createPagedNode( 
+                        subtile_bs, 
+                        uri, 
+                        0.0f, maxRange, 
+                        _options.layout().get(),
+                        _sgCallbacks.get(),
+                        _defaultFileLocationCallback.get(),
+                        readOptions,
+                        this);
+                }
+                else
+                {
+                    childNode = load(subtileLOD, u, v, uri, readOptions);
+                }
+
+                parent->addChild( childNode );
             }
         }
     }
@@ -974,6 +996,8 @@ FeatureModelGraph::buildTile(const FeatureLevel& level,
                              const TileKey* key,
                              const osgDB::Options* readOptions)
 {
+    OE_TEST << LC << "buildTile " << (key? key->str(): "no key") << std::endl;
+
     osg::ref_ptr<osg::Group> group;
 
     // Try to read it from a cache:
@@ -1013,7 +1037,7 @@ FeatureModelGraph::buildTile(const FeatureLevel& level,
         if ( key )
             query.tileKey() = *key;
 
-        query.setMap( _session->getMap() );
+        query.setMap(_session->createMapFrame());// _session->getMap() );
 
         // does the level have a style name set?
         if ( level.styleName().isSet() )
@@ -1054,7 +1078,10 @@ FeatureModelGraph::buildTile(const FeatureLevel& level,
         }
 
         // cache it if appropriate.
-        writeTileToCache(cacheKey, group.get(), readOptions);
+        if (_options.nodeCaching() == true)
+        {
+            writeTileToCache(cacheKey, group.get(), readOptions);
+        }
     }
 
     if ( group->getNumChildren() > 0 )
@@ -1112,6 +1139,8 @@ FeatureModelGraph::build(const Style&          defaultStyle,
                          FeatureIndexBuilder*  index,
                          const osgDB::Options* readOptions)
 {
+    OE_TEST << LC << "build " << workingExtent.toString() << std::endl;
+
     osg::ref_ptr<osg::Group> group = new osg::Group();
 
     FeatureSource* source = _session->getFeatureSource();
@@ -1183,10 +1212,10 @@ FeatureModelGraph::build(const Style&          defaultStyle,
                 {
                     // merge the selector's query into the existing query
                     Query combinedQuery = baseQuery.combineWith( *sel.query() );
-                    combinedQuery.setMap( _session->getMap() );
+                    combinedQuery.setMap(_session->createMapFrame());// _session->getMap() );
 
                     // query, sort, and add each style group to th parent:
-                    queryAndSortIntoStyleGroups( combinedQuery, *sel.styleExpression(), index, group, readOptions );
+                    queryAndSortIntoStyleGroups( combinedQuery, *sel.styleExpression(), index, group.get(), readOptions );
                 }
 
                 // otherwise, all feature returned by this query will have the same style:
@@ -1198,7 +1227,7 @@ FeatureModelGraph::build(const Style&          defaultStyle,
 
                     // .. and merge it's query into the existing query
                     Query combinedQuery = baseQuery.combineWith( *sel.query() );
-                    combinedQuery.setMap( _session->getMap() );
+                    combinedQuery.setMap(_session->createMapFrame());// _session->getMap() );
 
                     // then create the node.
                     osg::Group* styleGroup = createStyleGroup( combinedStyle, combinedQuery, index, readOptions );
@@ -1259,7 +1288,7 @@ FeatureModelGraph::buildStyleGroups(const StyleSelector*  selector,
                                     osg::Group*           parent,
                                     const osgDB::Options* readOptions)
 {
-    OE_TEST << LC << "buildStyleGroups: " << selector->name() << std::endl;
+    OE_TEST << LC << "buildStyleGroups " << selector->name() << std::endl;
 
     // if the selector uses an expression to select the style name, then we must perform the
     // query and then SORT the features into style groups.
@@ -1267,7 +1296,7 @@ FeatureModelGraph::buildStyleGroups(const StyleSelector*  selector,
     {
         // merge the selector's query into the existing query
         Query combinedQuery = baseQuery.combineWith( *selector->query() );
-        combinedQuery.setMap( _session->getMap() );
+        combinedQuery.setMap(_session->createMapFrame());// _session->getMap() );
 
         // query, sort, and add each style group to the parent:
         queryAndSortIntoStyleGroups( combinedQuery, *selector->styleExpression(), index, parent, readOptions );
@@ -1284,7 +1313,7 @@ FeatureModelGraph::buildStyleGroups(const StyleSelector*  selector,
 
         // .. and merge it's query into the existing query
         Query combinedQuery = baseQuery.combineWith( *selector->query() );
-        combinedQuery.setMap( _session->getMap() );
+        combinedQuery.setMap(_session->createMapFrame());// _session->getMap() );
 
         // then create the node.
         osg::Node* node = createStyleGroup(style, combinedQuery, index, readOptions);
@@ -1308,6 +1337,8 @@ FeatureModelGraph::queryAndSortIntoStyleGroups(const Query&            query,
                                                osg::Group*             parent,
                                                const osgDB::Options*   readOptions)
 {
+    OE_TEST << LC << "queryAndSortIntoStyleGroups " << std::endl;
+
     // the profile of the features
     const FeatureProfile* featureProfile = _session->getFeatureSource()->getFeatureProfile();
 
@@ -1349,7 +1380,7 @@ FeatureModelGraph::queryAndSortIntoStyleGroups(const Query&            query,
         Style combinedStyle;
 
         // if the style string begins with an open bracket, it's an inline style definition.
-        if ( styleString.length() > 0 && styleString.at(0) == '{' )
+        if ( styleString.length() > 0 && styleString[0] == '{' )
         {
             Config conf( "style", styleString );
             conf.setReferrer( styleExpr.uriContext().referrer() );
@@ -1386,9 +1417,9 @@ FeatureModelGraph::createStyleGroup(const Style&          style,
                                     const FilterContext&  contextPrototype,
                                     const osgDB::Options* readOptions)
 {
-    osg::Group* styleGroup = 0L;
+    OE_TEST << LC << "createStyleGroup " << style.getName() << std::endl;
 
-    OE_DEBUG << LC << "Created style group \"" << style.getName() << "\"\n";
+    osg::Group* styleGroup = 0L;
 
     FilterContext context(contextPrototype);
 
@@ -1447,6 +1478,8 @@ FeatureModelGraph::createStyleGroup(const Style&          style,
                                     FeatureIndexBuilder*  index,
                                     const osgDB::Options* readOptions)
 {
+    OE_TEST << LC << "createStyleGroup " << style.getName() << std::endl;
+
     osg::Group* styleGroup = 0L;
 
     // the profile of the features
@@ -1478,82 +1511,6 @@ FeatureModelGraph::createStyleGroup(const Style&          style,
     return styleGroup;
 }
 
-
-void
-FeatureModelGraph::checkForGlobalStyles( const Style& style )
-{
-    OpenThreads::ScopedLock< OpenThreads::Mutex > lk(_clampableMutex);
-
-    const AltitudeSymbol* alt = style.get<AltitudeSymbol>();
-    if ( alt )
-    {
-        if (alt->clamping() == AltitudeSymbol::CLAMP_TO_TERRAIN || 
-            alt->clamping() == AltitudeSymbol::CLAMP_RELATIVE_TO_TERRAIN)
-        {
-            if ( alt->technique() == AltitudeSymbol::TECHNIQUE_GPU && !_clampable.valid() )
-            {
-                _clampable = new ClampableNode( 0L );
-                _overlayChange = OVERLAY_INSTALL_CLAMPABLE;
-            }
-        }
-    }
-    
-    const RenderSymbol* render = style.get<RenderSymbol>();
-
-    if ( _clampable.valid() )
-    {
-        // if we're using extrusion, don't perform depth offsetting:
-        const ExtrusionSymbol* extrusion = style.get<ExtrusionSymbol>();
-        if ( extrusion )
-        {
-            DepthOffsetOptions d = _clampable->getDepthOffsetOptions();
-            d.enabled() = false;
-            _clampable->setDepthOffsetOptions( d );
-        }
-
-        // check for explicit depth offset render settings (note, this could
-        // override the automatic disable put in place by the presence of an
-        // ExtrusionSymbol above)
-        if ( render && render->depthOffset().isSet() )
-        {
-            _clampable->setDepthOffsetOptions(*render->depthOffset());
-        }
-    }
-
-#if 0
-    else 
-    {
-        if ( render && render->depthOffset().isSet() )
-        {
-            _depthOffsetAdapter.setGraph( this );
-            _depthOffsetAdapter.setDepthOffsetOptions( *render->depthOffset() );
-        }
-
-        if ( render && render->renderBin().isSet() )
-        {
-            osg::StateSet* ss = getOrCreateStateSet();
-            ss->setRenderBinDetails(
-                ss->getBinNumber(),
-                render->renderBin().get() );
-        }
-
-        if ( render && render->order().isSet() )
-        {
-            osg::StateSet* ss = getOrCreateStateSet();
-            ss->setRenderBinDetails(
-                (int)render->order()->eval(),
-                ss->getBinName().empty() ? "DepthSortedBin" : ss->getBinName() );
-        }
-
-        if ( render && render->transparent() == true )
-        {
-            osg::StateSet* ss = getOrCreateStateSet();
-            ss->setRenderingHint( ss->TRANSPARENT_BIN );
-        }
-    }
-#endif
-}
-
 void
 FeatureModelGraph::applyRenderSymbology(const Style& style, osg::Node* node)
 {
@@ -1562,8 +1519,9 @@ FeatureModelGraph::applyRenderSymbology(const Style& style, osg::Node* node)
     {
         if ( render->depthOffset().isSet() )
         {
-            _depthOffsetAdapter.setGraph( this );
-            _depthOffsetAdapter.setDepthOffsetOptions( *render->depthOffset() );
+            DepthOffsetAdapter doa;
+            doa.setGraph(node);
+            doa.setDepthOffsetOptions( *render->depthOffset() );
         }
 
         if ( render->renderBin().isSet() )
@@ -1587,6 +1545,15 @@ FeatureModelGraph::applyRenderSymbology(const Style& style, osg::Node* node)
             osg::StateSet* ss = node->getOrCreateStateSet();
             ss->setRenderingHint( ss->TRANSPARENT_BIN );
         }
+        
+        if (render->decal() == true)
+        {
+            getOrCreateStateSet()->setAttributeAndModes(
+                new osg::PolygonOffset(-1,-1), 1);
+
+            getOrCreateStateSet()->setAttributeAndModes(
+                new osg::Depth(osg::Depth::LEQUAL, 0, 1, false));
+        }
     }
 }
 
@@ -1595,11 +1562,6 @@ FeatureModelGraph::getOrCreateStyleGroupFromFactory(const Style& style)
 {
     osg::Group* styleGroup = _factory->getOrCreateStyleGroup( style, _session.get() );
 
-    // Check the style and see if we need to active GPU clamping. GPU clamping
-    // is currently all-or-nothing for a single FMG.
-    // Warning. This needs attention w.r.t. caching, since the "global" styles don't cache. -gw
-    checkForGlobalStyles( style );
-
     // Apply render symbology at the style group level.
     applyRenderSymbology(style, styleGroup);
 
@@ -1617,13 +1579,10 @@ FeatureModelGraph::traverse(osg::NodeVisitor& nv)
               _session->getFeatureSource()->outOfSyncWith(_featureSourceRev) ||
               (_modelSource.valid() && _modelSource->outOfSyncWith(_modelSourceRev))))
         {
-            _pendingUpdate = true;
-            ADJUST_UPDATE_TRAV_COUNT( this, 1 );
-        }
+            OE_TEST << LC << "out of sync - requesting update" << std::endl;
 
-        else if ( _overlayChange != OVERLAY_NO_CHANGE )
-        {
-            ADJUST_UPDATE_TRAV_COUNT( this, 1 );
+            _pendingUpdate = true;
+            ADJUST_UPDATE_TRAV_COUNT( this, +1 );
         }
     }
 
@@ -1631,17 +1590,12 @@ FeatureModelGraph::traverse(osg::NodeVisitor& nv)
     {
         if ( _pendingUpdate )
         {
+            OE_TEST << LC << "pending update detected" << std::endl;
+
             redraw();
             _pendingUpdate = false;
             ADJUST_UPDATE_TRAV_COUNT( this, -1 );
         }
-
-        else if ( _overlayChange != OVERLAY_NO_CHANGE )
-        {
-            changeOverlay();
-            _overlayChange = OVERLAY_NO_CHANGE;
-            ADJUST_UPDATE_TRAV_COUNT( this, -1 );
-        }
     }
 
     osg::Group::traverse(nv);
@@ -1650,81 +1604,27 @@ FeatureModelGraph::traverse(osg::NodeVisitor& nv)
 void
 FeatureModelGraph::runPreMergeOperations(osg::Node* node)
 {
-   if ( _preMergeOperations.valid() )
+   if (_sgCallbacks.valid())
    {
-      _preMergeOperations->mutex().readLock();
-      for( NodeOperationVector::iterator i = _preMergeOperations->begin(); i != _preMergeOperations->end(); ++i )
-      {
-         i->get()->operator()( node );
-      }
-      _preMergeOperations->mutex().readUnlock();
+       _sgCallbacks->firePreMergeNode(node);
    }
 }
 
 void
 FeatureModelGraph::runPostMergeOperations(osg::Node* node)
 {
-   if ( _postMergeOperations.valid() )
+   if (_sgCallbacks.valid())
    {
-      _postMergeOperations->mutex().readLock();
-      for( NodeOperationVector::iterator i = _postMergeOperations->begin(); i != _postMergeOperations->end(); ++i )
-      {
-         i->get()->operator()( node );
-      }
-      _postMergeOperations->mutex().readUnlock();
+       _sgCallbacks->firePostMergeNode(node);
    }
 }
 
-
-void
-FeatureModelGraph::changeOverlay()
-{
-    OpenThreads::ScopedLock< OpenThreads::Mutex > lk(_clampableMutex);
-
-    if (_overlayChange == OVERLAY_INSTALL_CLAMPABLE &&
-        _clampable.valid()                          && 
-        _clampable.get() != _overlayInstalled )
-    {
-        runPostMergeOperations( _clampable.get() );
-        osgEarth::replaceGroup( _overlayInstalled, _clampable.get() );
-        _overlayInstalled   = _clampable.get();
-        //_drapeable          = 0L;
-        _overlayPlaceholder = 0L;
-        OE_DEBUG << LC << "Installed clampable decorator on layer " << getName() << std::endl;
-    }
-
-    //else if (
-    //    _overlayChange == OVERLAY_INSTALL_DRAPEABLE && 
-    //    _drapeable.valid()                          && 
-    //    _drapeable.get() != _overlayInstalled )
-    //{
-    //    runPostMergeOperations( _drapeable.get() );
-    //    osgEarth::replaceGroup( _overlayInstalled, _drapeable.get() );
-    //    _overlayInstalled   = _drapeable.get();
-    //    _overlayPlaceholder = 0L;
-    //    _clampable          = 0L;
-    //    OE_DEBUG << LC << "Installed drapeable decorator on layer " << getName() << std::endl;
-    //}
-
-    else if (
-        _overlayChange == OVERLAY_INSTALL_PLACEHOLDER && 
-        _overlayPlaceholder.valid()                   && 
-        _overlayPlaceholder.get() != _overlayInstalled)
-    {
-        runPostMergeOperations( _overlayPlaceholder.get() );
-        osgEarth::replaceGroup( _overlayInstalled, _overlayPlaceholder.get() );
-        _overlayInstalled = _overlayPlaceholder.get();
-        _clampable        = 0L;
-        //_drapeable        = 0L;
-        OE_INFO << LC << "Installed null decorator on layer " << getName() << std::endl;
-    }
-}
-
-
 void
 FeatureModelGraph::redraw()
 {
-    OpenThreads::ScopedLock< OpenThreads::Mutex > lk(_clampableMutex);
+    OpenThreads::ScopedLock< OpenThreads::ReentrantMutex > lk(_redrawMutex);
+
+    OE_TEST << LC << "redraw " << std::endl;
 
     // clear it out
     removeChildren( 0, getNumChildren() );
@@ -1738,12 +1638,6 @@ FeatureModelGraph::redraw()
             _options.featureIndexing().get() );
     }
 
-    // zero out any decorators
-    _clampable          = 0L;
-    //_drapeable          = 0L;
-    _overlayPlaceholder = new osg::Group();
-    _overlayInstalled   = _overlayPlaceholder;
-
     osg::Node* node = 0;
     // if there's a display schema in place, set up for quadtree paging.
     if ( _options.layout().isSet() || _useTiledSource )
@@ -1756,6 +1650,8 @@ FeatureModelGraph::redraw()
         
         //Remove all current children
         node = buildTile(defaultLevel, GeoExtent::INVALID, 0, _session->getDBOptions());
+        // We're just building the entire node now with no paging, so run the post merge operations immediately.
+        runPostMergeOperations(node);
     }
 
     float minRange = -FLT_MAX;
@@ -1774,7 +1670,9 @@ FeatureModelGraph::redraw()
     
     //If they've specified a min/max range, setup an LOD
     if ( minRange != -FLT_MAX || maxRange != FLT_MAX )
-    {        
+    {
+        OE_INFO << LC << "Elevation LOD set to " << minRange << " => " << maxRange << std::endl;
+
         // todo: revisit this, make sure this is still right.
         ElevationLOD *lod = new ElevationLOD(_session->getMapInfo().getSRS(), minRange, maxRange );
         lod->addChild( node );
@@ -1792,13 +1690,6 @@ FeatureModelGraph::redraw()
         node = fader;
     }
 
-    // overlay placeholder. this will make it easier to 
-    // replace with a clamper/draper later if necessary
-    {
-        _overlayInstalled->addChild( node );
-        node = _overlayInstalled;
-    }
-
     addChild( node );
 
     _session->getFeatureSource()->sync( _featureSourceRev );
diff --git a/src/osgEarthFeatures/FeatureModelLayer b/src/osgEarthFeatures/FeatureModelLayer
new file mode 100644
index 0000000..e1f7d8b
--- /dev/null
+++ b/src/osgEarthFeatures/FeatureModelLayer
@@ -0,0 +1,130 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+ * Copyright 2016 Pelican Mapping
+ * http://osgearth.org
+ *
+ * osgEarth is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+#ifndef OSGEARTH_FEATURES_FEATURE_MODEL_LAYER
+#define OSGEARTH_FEATURES_FEATURE_MODEL_LAYER 1
+
+#include <osgEarthFeatures/Common>
+#include <osgEarthFeatures/FeatureSource>
+#include <osgEarthFeatures/FeatureSourceLayer>
+#include <osgEarthFeatures/GeometryCompiler>
+#include <osgEarthFeatures/FeatureModelSource>
+#include <osgEarthFeatures/Session>
+#include <osgEarthSymbology/Style>
+#include <osgEarth/Layer>
+#include <osgEarth/LayerListener>
+#include <osgEarth/SceneGraphCallback>
+
+namespace osgEarth { namespace Features
+{
+    using namespace osgEarth;
+
+    /**
+     * Serializable options to configure a FeatureModelLayer
+     */
+    class OSGEARTHFEATURES_EXPORT FeatureModelLayerOptions : public VisibleLayerOptions,
+                                                             public FeatureModelOptions,
+                                                             public GeometryCompilerOptions
+    {
+    public:
+        // constructor
+        FeatureModelLayerOptions(const ConfigOptions& co = ConfigOptions());
+        
+        /** Name of the feature source layer to use for flattening. */
+        optional<std::string>& featureSourceLayer() { return _featureSourceLayer; }
+        const optional<std::string>& featureSourceLayer() const { return _featureSourceLayer; }
+
+    public: // LayerOptions
+        virtual Config getConfig() const;
+
+    protected: // LayerOptions
+        virtual void mergeConfig(const Config& conf);
+        void fromConfig(const Config& conf);
+        
+    private:
+        optional<std::string> _featureSourceLayer;
+    };
+
+
+    /**
+     * Layer that creates a scene graph from feature data and symbology.
+     */
+    class OSGEARTHFEATURES_EXPORT FeatureModelLayer : public VisibleLayer
+    {
+    public:
+        META_Layer(osgEarth, FeatureModelLayer, FeatureModelLayerOptions);
+
+        // Create a feature layer with defaults. Use options() to set it up before adding
+        // the layer to a Map.
+        FeatureModelLayer();
+
+        // Create a layer with initial options.
+        FeatureModelLayer(const FeatureModelLayerOptions& options);
+
+        // Options used to initialize this layer.
+        const FeatureModelLayerOptions& getFeatureModelLayerOptions() const { return *_options; }
+
+        // Feature source layer from which to get a feature source
+        void setFeatureSourceLayer(FeatureSourceLayer* layer);
+        
+        // the feature source from which to read flattening geometry
+        void setFeatureSource(FeatureSource* fs);
+
+        // access to scene graph callbacks
+        SceneGraphCallbacks* getSceneGraphCallbacks() { return _sgCallbacks.get(); }
+
+    public: // Layer
+
+        // opens the layer and returns the status
+        virtual const Status& open();
+
+        // The Node representing this layer.
+        virtual osg::Node* getOrCreateNode();
+
+        //! Extent of the feature layer, if available (INVALID if not)
+        virtual const GeoExtent& getExtent() const;
+
+    protected: // Layer
+        
+        // called by the map when this layer is added
+        virtual void addedToMap(const class Map*);
+
+        // called by the map when this layer is removed
+        virtual void removedFromMap(const class Map*);
+
+        // post-ctor initialization
+        virtual void init();
+
+    protected:
+
+        virtual ~FeatureModelLayer();
+
+    private:
+        osg::ref_ptr<FeatureSource> _featureSource;
+        LayerListener<FeatureModelLayer, FeatureSourceLayer> _featureSourceLayerListener;
+        osg::ref_ptr<class Session> _session;
+        osg::ref_ptr<osg::Group> _root;
+        bool _graphDirty;
+        void create();
+
+        osg::ref_ptr<SceneGraphCallbacks> _sgCallbacks;
+    };
+
+} } // namespace osgEarth::Features
+
+#endif // OSGEARTH_FEATURES_FEATURE_MODEL_LAYER
diff --git a/src/osgEarthFeatures/FeatureModelLayer.cpp b/src/osgEarthFeatures/FeatureModelLayer.cpp
new file mode 100644
index 0000000..613c9e7
--- /dev/null
+++ b/src/osgEarthFeatures/FeatureModelLayer.cpp
@@ -0,0 +1,255 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+ * Copyright 2016 Pelican Mapping
+ * http://osgearth.org
+ *
+ * osgEarth is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+#include <osgEarthFeatures/FeatureModelLayer>
+#include <osgEarthFeatures/FeatureModelGraph>
+
+using namespace osgEarth;
+using namespace osgEarth::Features;
+using namespace osgEarth::Symbology;
+
+#define LC "[FeatureModelLayer] "
+
+#define OE_TEST OE_NULL
+
+REGISTER_OSGEARTH_LAYER(feature_model, FeatureModelLayer);
+
+//...........................................................................
+
+FeatureModelLayerOptions::FeatureModelLayerOptions(const ConfigOptions& options) :
+VisibleLayerOptions(options),
+FeatureModelOptions(options),
+GeometryCompilerOptions(options)
+{
+    fromConfig(_conf);
+}
+        
+void FeatureModelLayerOptions::fromConfig(const Config& conf)
+{
+    conf.getIfSet("feature_source", _featureSourceLayer);
+}
+
+Config
+FeatureModelLayerOptions::getConfig() const
+{
+    Config conf = VisibleLayerOptions::getConfig();
+    conf.merge(FeatureModelOptions::getConfig());
+    conf.merge(GeometryCompilerOptions::getConfig());
+    conf.key() = "feature_model";
+
+    conf.set("feature_source", _featureSourceLayer);
+    return conf;
+}
+
+void FeatureModelLayerOptions::mergeConfig(const Config& conf)
+{
+    VisibleLayerOptions::mergeConfig(conf);
+    fromConfig(conf);
+}
+
+//...........................................................................
+
+FeatureModelLayer::FeatureModelLayer() :
+VisibleLayer(&_optionsConcrete),
+_options(&_optionsConcrete)
+{
+    init();
+}
+
+FeatureModelLayer::FeatureModelLayer(const FeatureModelLayerOptions& options) :
+VisibleLayer(&_optionsConcrete),
+_options(&_optionsConcrete),
+_optionsConcrete(options)
+{
+    init();
+}
+
+FeatureModelLayer::~FeatureModelLayer()
+{
+    //NOP
+}
+
+void
+FeatureModelLayer::init()
+{
+    VisibleLayer::init();
+
+    _root = new osg::Group();
+
+    // Assign the layer's state set to the root node:
+    _root->setStateSet(this->getOrCreateStateSet());
+
+    // Callbacks for paged data
+    _sgCallbacks = new SceneGraphCallbacks();
+
+    // Graph needs rebuilding
+    _graphDirty = true;
+
+    // Depth sorting by default
+    getOrCreateStateSet()->setRenderingHint(osg::StateSet::TRANSPARENT_BIN);
+
+}
+
+void
+FeatureModelLayer::setFeatureSourceLayer(FeatureSourceLayer* layer)
+{
+    if (layer && layer->getStatus().isError())
+    {
+        setStatus(Status::Error(Status::ResourceUnavailable, "Feature source layer is unavailable; check for error"));
+        return;
+    }
+
+    if (layer)
+        OE_INFO << LC << "Feature source layer is \"" << layer->getName() << "\"\n";
+
+    setFeatureSource(layer ? layer->getFeatureSource() : 0L);
+}
+
+void
+FeatureModelLayer::setFeatureSource(FeatureSource* source)
+{
+    OE_TEST << LC << "setFeatureSource" << std::endl;
+
+    if (_featureSource != source)
+    {
+        if (source)
+            OE_INFO << LC << "Setting feature source \"" << source->getName() << "\"\n";
+
+        _featureSource = source;
+
+        if (source && source->getStatus().isError())
+        {
+            setStatus(source->getStatus());
+            return;
+        }
+
+        // feature source changed, so the graph needs rebuilding
+        _graphDirty = true;
+
+        // create the scene graph
+        create();
+    }
+}
+
+osg::Node*
+FeatureModelLayer::getOrCreateNode()
+{
+    return _root.get();
+}
+
+const Status&
+FeatureModelLayer::open()
+{
+    OE_TEST << LC << "open" << std::endl;
+
+    if (options().featureSource().isSet())
+    {
+        FeatureSource* fs = FeatureSourceFactory::create(options().featureSource().get());
+        if (fs)
+        {
+            fs->setReadOptions(getReadOptions());
+            fs->open();
+            setFeatureSource(fs);
+        }
+        else
+        {
+            setStatus(Status(Status::ConfigurationError, "Cannot create feature source"));
+        }
+    }
+    return VisibleLayer::open();
+}
+
+const GeoExtent&
+FeatureModelLayer::getExtent() const
+{
+    static GeoExtent s_invalid;
+
+    return _featureSource.valid() && _featureSource->getFeatureProfile() ?
+        _featureSource->getFeatureProfile()->getExtent() :
+        s_invalid;
+}
+
+void
+FeatureModelLayer::addedToMap(const Map* map)
+{
+    OE_TEST << LC << "addedToMap" << std::endl;
+
+    // Save a reference to the map since we'll need it to
+    // create a new session object later.
+    _session = new Session(
+        map, 
+        options().styles().get(), 
+        0L,  // feature source - will set later
+        getReadOptions());
+
+    if (options().featureSourceLayer().isSet())
+    {
+        _featureSourceLayerListener.listen(
+            map,
+            options().featureSourceLayer().get(),
+            this,
+            &FeatureModelLayer::setFeatureSourceLayer);
+    }
+
+    // re-create the graph if necessary.
+    create();
+}
+
+void
+FeatureModelLayer::removedFromMap(const Map* map)
+{
+    _featureSourceLayerListener.clear();
+}
+
+void
+FeatureModelLayer::create()
+{
+    OE_TEST << LC << "create" << std::endl;
+
+    if (_graphDirty)
+    {
+        if (_featureSource.valid() && _session.valid())
+        {
+            // connect the session to the features:
+            _session->setFeatureSource(_featureSource.get());
+
+            // the factory builds nodes for the model graph:
+            FeatureNodeFactory* nodeFactory = new GeomFeatureNodeFactory(options());
+
+            // group that will build all the feature geometry:
+            FeatureModelGraph* fmg = new FeatureModelGraph(
+                _session.get(),
+                options(),
+                nodeFactory,
+                _sgCallbacks.get());
+
+            _root->removeChildren(0, _root->getNumChildren());
+            _root->addChild(fmg);
+
+            // clear the dirty flag.
+            _graphDirty = false;
+
+            setStatus(Status::OK());
+        }
+
+        else if (getStatus().isOK())
+        {
+            setStatus(Status(Status::ConfigurationError));
+        }
+    }
+}
diff --git a/src/osgEarthFeatures/FeatureModelSource b/src/osgEarthFeatures/FeatureModelSource
index 8c08852..eae5235 100644
--- a/src/osgEarthFeatures/FeatureModelSource
+++ b/src/osgEarthFeatures/FeatureModelSource
@@ -25,26 +25,24 @@
 #include <osgEarthFeatures/FeatureSourceIndexNode>
 #include <osgEarthFeatures/FeatureDisplayLayout>
 #include <osgEarthFeatures/GeometryCompiler>
-#include <osgEarthSymbology/Style>
+#include <osgEarthSymbology/StyleSheet>
 #include <osgEarth/FadeEffect>
 #include <osgEarth/ModelSource>
 #include <osgEarth/Map>
-#include <osgEarth/CachePolicy>
 #include <osg/Node>
-#include <osgDB/ReaderWriter>
-#include <list>
 
 namespace osgEarth { namespace Features
 {
     using namespace osgEarth;
     using namespace osgEarth::Symbology;
-
-    class OSGEARTHFEATURES_EXPORT FeatureModelSourceOptions : public ModelSourceOptions
+    
+    class FeatureModelOptions
     {
     public: //properties
 
-        optional<FeatureSourceOptions>& featureOptions() { return _featureOptions; }
-        const optional<FeatureSourceOptions>& featureOptions() const { return _featureOptions; }
+        /** Embedded feature source configuration. */
+        optional<FeatureSourceOptions>& featureSource() { return _featureSource; }
+        const optional<FeatureSourceOptions>& featureSource() const { return _featureSource; }
 
         osg::ref_ptr<StyleSheet>& styles() { return _styles; }
         const osg::ref_ptr<StyleSheet>& styles() const { return _styles; }
@@ -82,19 +80,52 @@ namespace osgEarth { namespace Features
         optional<bool>& alphaBlending() { return _alphaBlending; }
         const optional<bool>& alphaBlending() const { return _alphaBlending; }
 
-        /** Explicity caching policy for data from the underlying feature source */
-        optional<CachePolicy>& cachePolicy() { return _cachePolicy; }
-        const optional<CachePolicy>& cachePolicy() const { return _cachePolicy; }
-
         /** Fading properties */
         optional<FadeOptions>& fading() { return _fading; }
         const optional<FadeOptions>& fading() const { return _fading; }
 
+        /** Whether to enable caching of actual OSG nodes. default = false. */
+        optional<bool>& nodeCaching() { return _nodeCaching; }
+        const optional<bool>& nodeCaching() const { return _nodeCaching; }
+
         /** Debug: whether to enable a session-wide resource cache (default=true) */
         optional<bool>& sessionWideResourceCache() { return _sessionWideResourceCache; }
         const optional<bool>& sessionWideResourceCache() const { return _sessionWideResourceCache; }
 
     public:
+        FeatureModelOptions(const ConfigOptions& co =ConfigOptions());
+
+        void fromConfig(const Config& conf);
+        Config getConfig() const;
+
+    protected:
+        optional<FeatureSourceOptions>      _featureSource;
+        optional<FeatureDisplayLayout>      _layout;
+        optional<StringExpression>          _featureNameExpr;
+        optional<bool>                      _lit;
+        optional<double>                    _maxGranularity_deg;
+        optional<bool>                      _clusterCulling;
+        optional<bool>                      _backfaceCulling;
+        optional<bool>                      _alphaBlending;
+        optional<FadeOptions>               _fading;
+        optional<FeatureSourceIndexOptions> _featureIndexing;
+        optional<bool>                      _sessionWideResourceCache;
+        optional<std::string>               _featureSourceLayer;
+        optional<bool>                      _nodeCaching;
+        osg::ref_ptr<StyleSheet>            _styles;
+    };
+
+
+
+    class OSGEARTHFEATURES_EXPORT FeatureModelSourceOptions : public ModelSourceOptions,
+                                                              public FeatureModelOptions
+    {
+    public: //properties
+
+        optional<FeatureSourceOptions>& featureOptions() { return _featureOptions; }
+        const optional<FeatureSourceOptions>& featureOptions() const { return _featureOptions; }
+
+    public:
         /** A live feature source instance to use. Note, this does not serialize. */
         osg::ref_ptr<FeatureSource>& featureSource() { return _featureSource; }
         const osg::ref_ptr<FeatureSource>& featureSource() const { return _featureSource; }
@@ -115,21 +146,8 @@ namespace osgEarth { namespace Features
     private:
         void fromConfig( const Config& conf );
 
-        optional<FeatureSourceOptions>      _featureOptions;
-        optional<FeatureDisplayLayout>      _layout;
-        optional<StringExpression>          _featureNameExpr;
-        optional<bool>                      _lit;
-        optional<double>                    _maxGranularity_deg;
-        optional<bool>                      _clusterCulling;
-        optional<bool>                      _backfaceCulling;
-        optional<bool>                      _alphaBlending;
-        optional<CachePolicy>               _cachePolicy;
-        optional<FadeOptions>               _fading;
-        optional<FeatureSourceIndexOptions> _featureIndexing;
-        optional<bool>                      _sessionWideResourceCache;
-
-        osg::ref_ptr<StyleSheet>            _styles;
-        osg::ref_ptr<FeatureSource>         _featureSource;
+        optional<FeatureSourceOptions> _featureOptions;
+        osg::ref_ptr<FeatureSource>    _featureSource;
     };
 
 
diff --git a/src/osgEarthFeatures/FeatureModelSource.cpp b/src/osgEarthFeatures/FeatureModelSource.cpp
index b03138c..533f840 100644
--- a/src/osgEarthFeatures/FeatureModelSource.cpp
+++ b/src/osgEarthFeatures/FeatureModelSource.cpp
@@ -24,6 +24,8 @@
 #include <osgEarth/Registry>
 #include <osgEarth/Capabilities>
 #include <osgEarth/DrapeableNode>
+#include <osgEarth/ClampableNode>
+#include <osgEarth/Lighting>
 #include <osg/Notify>
 
 using namespace osgEarth;
@@ -32,16 +34,80 @@ using namespace osgEarth::Symbology;
 
 #define LC "[FeatureModelSource] "
 
-//------------------------------------------------------------------------
+//........................................................................
 
-FeatureModelSourceOptions::FeatureModelSourceOptions( const ConfigOptions& options ) :
-ModelSourceOptions ( options ),
+FeatureModelOptions::FeatureModelOptions(const ConfigOptions& co) :
 _lit               ( true ),
 _maxGranularity_deg( 1.0 ),
 _clusterCulling    ( true ),
 _backfaceCulling   ( true ),
 _alphaBlending     ( true ),
-_sessionWideResourceCache( true )
+_sessionWideResourceCache( true ),
+_nodeCaching(false)
+{
+    fromConfig(co.getConfig());
+}
+
+void
+FeatureModelOptions::fromConfig(const Config& conf)
+{
+    conf.getObjIfSet("features", _featureSource);
+
+    conf.getObjIfSet( "styles",           _styles );
+    conf.getObjIfSet( "layout",           _layout );
+    conf.getObjIfSet( "paging",           _layout ); // backwards compat.. to be deprecated
+    conf.getObjIfSet( "fading",           _fading );
+    conf.getObjIfSet( "feature_name",     _featureNameExpr );
+    conf.getObjIfSet( "feature_indexing", _featureIndexing );
+
+    conf.getIfSet( "lighting",         _lit );
+    conf.getIfSet( "max_granularity",  _maxGranularity_deg );
+    conf.getIfSet( "cluster_culling",  _clusterCulling );
+    conf.getIfSet( "backface_culling", _backfaceCulling );
+    conf.getIfSet( "alpha_blending",   _alphaBlending );
+    conf.getIfSet( "node_caching",     _nodeCaching );
+    
+    conf.getIfSet( "session_wide_resource_cache", _sessionWideResourceCache );
+
+    // Support a singleton style (convenience)
+    optional<Style> style;
+    conf.getObjIfSet("style", style);
+    if (style.isSet())
+    {
+        if (_styles.valid() == false) _styles = new StyleSheet();
+        _styles->addStyle(style.get());
+    }
+}
+
+Config
+FeatureModelOptions::getConfig() const
+{
+    Config conf;
+    conf.setObj("features", _featureSource);
+
+    conf.setObj( "styles",           _styles );
+    conf.setObj( "layout",           _layout );
+    conf.setObj( "fading",           _fading );
+    conf.setObj( "feature_name",     _featureNameExpr );
+    conf.setObj( "feature_indexing", _featureIndexing );
+
+    conf.set( "lighting",         _lit );
+    conf.set( "max_granularity",  _maxGranularity_deg );
+    conf.set( "cluster_culling",  _clusterCulling );
+    conf.set( "backface_culling", _backfaceCulling );
+    conf.set( "alpha_blending",   _alphaBlending );
+    conf.set( "node_caching",     _nodeCaching );
+    
+    conf.set( "session_wide_resource_cache", _sessionWideResourceCache );
+
+    return conf;
+}
+
+//........................................................................
+
+FeatureModelSourceOptions::FeatureModelSourceOptions(const ConfigOptions& options) :
+ModelSourceOptions(options),
+FeatureModelOptions()
 {
     fromConfig( _conf );
 }
@@ -51,11 +117,10 @@ FeatureModelSourceOptions::fromConfig( const Config& conf )
 {
     conf.getObjIfSet( "features", _featureOptions );
     _featureSource = conf.getNonSerializable<FeatureSource>("feature_source");
-
+    
     conf.getObjIfSet( "styles",           _styles );
     conf.getObjIfSet( "layout",           _layout );
     conf.getObjIfSet( "paging",           _layout ); // backwards compat.. to be deprecated
-    conf.getObjIfSet( "cache_policy",     _cachePolicy );
     conf.getObjIfSet( "fading",           _fading );
     conf.getObjIfSet( "feature_name",     _featureNameExpr );
     conf.getObjIfSet( "feature_indexing", _featureIndexing );
@@ -65,6 +130,7 @@ FeatureModelSourceOptions::fromConfig( const Config& conf )
     conf.getIfSet( "cluster_culling",  _clusterCulling );
     conf.getIfSet( "backface_culling", _backfaceCulling );
     conf.getIfSet( "alpha_blending",   _alphaBlending );
+    conf.getIfSet( "node_caching",     _nodeCaching );
     
     conf.getIfSet( "session_wide_resource_cache", _sessionWideResourceCache );
 }
@@ -74,25 +140,27 @@ FeatureModelSourceOptions::getConfig() const
 {
     Config conf = ModelSourceOptions::getConfig();
 
-    conf.updateObjIfSet( "features", _featureOptions );    
+    conf.setObj( "features", _featureOptions );    
     if (_featureSource.valid())
     {
         conf.addNonSerializable("feature_source", _featureSource.get());
     }
-    conf.updateObjIfSet( "styles",           _styles );
-    conf.updateObjIfSet( "layout",           _layout );
-    conf.updateObjIfSet( "cache_policy",     _cachePolicy );
-    conf.updateObjIfSet( "fading",           _fading );
-    conf.updateObjIfSet( "feature_name",     _featureNameExpr );
-    conf.updateObjIfSet( "feature_indexing", _featureIndexing );
-
-    conf.updateIfSet( "lighting",         _lit );
-    conf.updateIfSet( "max_granularity",  _maxGranularity_deg );
-    conf.updateIfSet( "cluster_culling",  _clusterCulling );
-    conf.updateIfSet( "backface_culling", _backfaceCulling );
-    conf.updateIfSet( "alpha_blending",   _alphaBlending );
+    conf.set("feature_source", _featureSourceLayer);
+
+    conf.setObj( "styles",           _styles );
+    conf.setObj( "layout",           _layout );
+    conf.setObj( "fading",           _fading );
+    conf.setObj( "feature_name",     _featureNameExpr );
+    conf.setObj( "feature_indexing", _featureIndexing );
+
+    conf.set( "lighting",         _lit );
+    conf.set( "max_granularity",  _maxGranularity_deg );
+    conf.set( "cluster_culling",  _clusterCulling );
+    conf.set( "backface_culling", _backfaceCulling );
+    conf.set( "alpha_blending",   _alphaBlending );
+    conf.set( "node_caching",     _nodeCaching );
     
-    conf.updateIfSet( "session_wide_resource_cache", _sessionWideResourceCache );
+    conf.set( "session_wide_resource_cache", _sessionWideResourceCache );
 
     return conf;
 }
@@ -130,6 +198,7 @@ FeatureModelSource::initialize(const osgDB::Options* readOptions)
     {
         _features = _options.featureSource().get();
     }
+
     else if ( _options.featureOptions().isSet() )
     {
         _features = FeatureSourceFactory::create( _options.featureOptions().value() );
@@ -217,19 +286,8 @@ FeatureModelSource::createNodeImplementation(const Map*        map,
     session->setName( this->getName() );
 
     // Graph that will render feature models. May included paged data.
-    FeatureModelGraph* graph = new FeatureModelGraph( 
-       session,
-       _options,
-       factory,
-       this,
-       _preMergeOps.get(),
-       _postMergeOps.get() );
-
-    graph->setName( session->getName() );
-
-    // then run the ops on the staring graph:
-    firePostProcessors( graph );
-
+    FeatureModelGraph* graph = new FeatureModelGraph(session, _options, factory, getSceneGraphCallbacks());
+    graph->setSceneGraphCallbacks(getSceneGraphCallbacks());
     return graph;
 }
 
@@ -253,6 +311,13 @@ FeatureNodeFactory::getOrCreateStyleGroup(const Style& style,
         group = new DrapeableNode();
     }
 
+    else if (alt &&
+        alt->clamping() == alt->CLAMP_TO_TERRAIN &&
+        alt->technique() == alt->TECHNIQUE_GPU)
+    {
+        group = new ClampableNode();
+    }
+
     // Otherwise, a normal group.
     if ( !group )
     {
@@ -280,8 +345,9 @@ FeatureNodeFactory::getOrCreateStyleGroup(const Style& style,
 
             if ( Registry::capabilities().supportsGLSL() )
             {
-                stateset->addUniform( Registry::shaderFactory()->createUniformForGLMode(
-                    GL_LIGHTING, render->lighting().value()));
+                stateset->setDefine(OE_LIGHTING_DEFINE, render->lighting().get());
+                //stateset->addUniform( Registry::shaderFactory()->createUniformForGLMode(
+                //    GL_LIGHTING, render->lighting().value()));
             }
         }
 
@@ -292,7 +358,7 @@ FeatureNodeFactory::getOrCreateStyleGroup(const Style& style,
                 (render->backfaceCulling() == true ? osg::StateAttribute::ON : osg::StateAttribute::OFF) | osg::StateAttribute::OVERRIDE );
         }
 
-#ifndef OSG_GLES2_AVAILABLE
+#if !(defined(OSG_GLES2_AVAILABLE) || defined(OSG_GLES3_AVAILABLE) || defined(OSG_GL3_AVAILABLE) )
         if ( render->clipPlane().isSet() )
         {
             GLenum mode = GL_CLIP_PLANE0 + (render->clipPlane().value());
diff --git a/src/osgEarthFeatures/FeatureRasterizer.cpp b/src/osgEarthFeatures/FeatureRasterizer.cpp
index 3b346bd..7aec104 100644
--- a/src/osgEarthFeatures/FeatureRasterizer.cpp
+++ b/src/osgEarthFeatures/FeatureRasterizer.cpp
@@ -34,6 +34,8 @@ FeatureRasterizerFactory::create(const std::string& driver,
                                  const Config& driverConf,
                                  const osgDB::ReaderWriter::Options* globalOptions )
 {
+    osg::ref_ptr<FeatureRasterizer> rasterizer;
+
     osg::ref_ptr<PluginOptions> pluginOptions = globalOptions?
         new PluginOptions( *globalOptions ) :
         new PluginOptions();
@@ -41,16 +43,18 @@ FeatureRasterizerFactory::create(const std::string& driver,
     // Setup the plugin options for the source
     pluginOptions->config() = driverConf;
 
-	//Load the source from the a plugin.  The "." prefix causes OSG to select the correct plugin.
+    std::string driverExt = std::string(".osgearth_rasterizer_") + driver;
+
+    //Load the source from the a plugin.  The "." prefix causes OSG to select the correct plugin.
     //For instance, the WMS plugin can be loaded by using ".osgearth_wms" as the filename
-    osg::ref_ptr<FeatureRasterizer> rasterizer = dynamic_cast<FeatureRasterizer*>(
-        osgDB::readObjectFile( ".osgearth_rasterizer_" + driver, pluginOptions.get()));
+    osg::ref_ptr<osg::Object> object = osgDB::readRefObjectFile(driverExt , pluginOptions.get()));
+    rasterizer = dynamic_cast<FeatureRasterizer*>(object.release());
 
-	if ( !rasterizer.valid() )
-	{
-		osg::notify(osg::NOTICE) << "[osgEarth] Warning: Could not load Rasterizer for driver "  << driver << std::endl;
-	}
+    if ( !rasterizer.valid() )
+    {
+        osg::notify(osg::NOTICE) << "[osgEarth] Warning: Could not load Rasterizer for driver "  << driver << std::endl;
+    }
 
-	return rasterizer.release();
+    return rasterizer.release();
 }
 
diff --git a/src/osgEarthFeatures/FeatureSource b/src/osgEarthFeatures/FeatureSource
index 096ca1a..0dcf5fa 100644
--- a/src/osgEarthFeatures/FeatureSource
+++ b/src/osgEarthFeatures/FeatureSource
@@ -22,11 +22,11 @@
 
 #include <osgEarthFeatures/Common>
 #include <osgEarthFeatures/Feature>
-#include <osgEarthFeatures/FeatureCursor>
+
 #include <osgEarthSymbology/Geometry>
 #include <osgEarthSymbology/Query>
-#include <osgEarthFeatures/Filter>
 #include <osgEarthSymbology/Style>
+
 #include <osgEarth/Profile>
 #include <osgEarth/GeoData>
 #include <osgEarth/Cache>
@@ -43,6 +43,9 @@ namespace osgEarth { namespace Features
     using namespace osgEarth;
     using namespace osgEarth::Symbology;
 
+    class FeatureCursor;
+    class FeatureFilterChain;
+
     /**
      * Configuration options for creating a FeatureSource.
      */
@@ -141,7 +144,7 @@ namespace osgEarth { namespace Features
          * Caller takes ownership of the returned object.
          */
         virtual FeatureCursor* createFeatureCursor(const Symbology::Query& query) =0;
-        FeatureCursor* createFeatureCursor() { return createFeatureCursor(Symbology::Query()); }
+        FeatureCursor* createFeatureCursor();
 
         /**
          * Whether this FeatureSource supports inserting and deleting features
@@ -240,7 +243,7 @@ namespace osgEarth { namespace Features
          * Accesses the list of feature filters that will transform features
          * before they are returned in a feature cursor.
          */
-        const FeatureFilterList& getFilters() const;
+        const FeatureFilterChain* getFilters() const;
 
     public: 
 
@@ -268,13 +271,6 @@ namespace osgEarth { namespace Features
         virtual Status initialize(const osgDB::Options* readOptions) =0;
 
         /**
-         * Creates and returns a metadata structure describing the features in a named
-         * feature class. This method is called by the public function getFeatureProfile()
-         * in this same object to create the metadata structure.
-         */
-        //virtual const FeatureProfile* createFeatureProfile(int dummy) =0;
-
-        /**
          * DTOR is protected to prevent this object from being allocated on the stack.
          */
         virtual ~FeatureSource();
@@ -302,7 +298,7 @@ namespace osgEarth { namespace Features
         Threading::ReadWriteMutex          _blacklistMutex;
         std::set<FeatureID>                _blacklist;
         
-        FeatureFilterList                  _filters;
+        osg::ref_ptr<FeatureFilterChain>   _filters;
 
         Status                             _status;
 
diff --git a/src/osgEarthFeatures/FeatureSource.cpp b/src/osgEarthFeatures/FeatureSource.cpp
index 2371bf7..d0516af 100644
--- a/src/osgEarthFeatures/FeatureSource.cpp
+++ b/src/osgEarthFeatures/FeatureSource.cpp
@@ -20,6 +20,7 @@
 #include <osgEarthFeatures/ResampleFilter>
 #include <osgEarthFeatures/BufferFilter>
 #include <osgEarthFeatures/ConvertTypeFilter>
+#include <osgEarthFeatures/FilterContext>
 #include <osgEarth/Registry>
 #include <osg/Notify>
 #include <osgDB/ReadFile>
@@ -76,8 +77,8 @@ FeatureSourceOptions::getConfig() const
 
     conf.updateIfSet   ( "open_write",   _openWrite );
     conf.updateIfSet   ( "name",         _name );
-    conf.updateObjIfSet( "profile",      _profile );
-    conf.updateObjIfSet( "cache_policy", _cachePolicy );
+    conf.setObj( "profile",      _profile );
+    conf.setObj( "cache_policy", _cachePolicy );
     conf.updateIfSet   ( "geo_interpolation", "great_circle", _geoInterp, GEOINTERP_GREAT_CIRCLE );
     conf.updateIfSet   ( "geo_interpolation", "rhumb_line",   _geoInterp, GEOINTERP_RHUMB_LINE );
     conf.updateIfSet   ( "fid_attribute", _fidAttribute );
@@ -119,11 +120,14 @@ FeatureSource::open(const osgDB::Options* readOptions)
     // Create and initialize the filters.
     for(unsigned i=0; i<_options.filters().size(); ++i)
     {
-        const ConfigOptions& conf = _options.filters().at(i);
+        const ConfigOptions& conf = _options.filters()[i];
         FeatureFilter* filter = FeatureFilterRegistry::instance()->create( conf.getConfig(), 0L );
         if ( filter )
         {
-            _filters.push_back( filter );
+            if (_filters.valid() == false)
+                _filters = new FeatureFilterChain();
+
+            _filters->push_back( filter );
             filter->initialize( readOptions );
         }
     }
@@ -138,10 +142,16 @@ FeatureSource::setFeatureProfile(const FeatureProfile* fp)
     _featureProfile = fp;
 }
 
-const FeatureFilterList&
+const FeatureFilterChain*
 FeatureSource::getFilters() const
 {
-    return _filters;
+    return _filters.get();
+}
+
+FeatureCursor*
+FeatureSource::createFeatureCursor()
+{
+    return createFeatureCursor(Symbology::Query());
 }
 
 const FeatureSchema&
@@ -183,12 +193,12 @@ void
 FeatureSource::applyFilters(FeatureList& features, const GeoExtent& extent) const
 {
     // apply filters before returning.
-    if ( !getFilters().empty() )
+    if (_filters.valid() && _filters->empty() == false)
     {
         FilterContext cx;
         cx.setProfile( getFeatureProfile() );
         cx.extent() = extent;
-        for(FeatureFilterList::const_iterator filter = getFilters().begin(); filter != getFilters().end(); ++filter)
+        for(FeatureFilterChain::const_iterator filter = _filters->begin(); filter != _filters->end(); ++filter)
         {
             cx = filter->get()->push( features, cx );
         }
@@ -204,7 +214,7 @@ FeatureSource::applyFilters(FeatureList& features, const GeoExtent& extent) cons
 FeatureSource*
 FeatureSourceFactory::create( const FeatureSourceOptions& options )
 {
-    FeatureSource* featureSource = 0L;
+    osg::ref_ptr<FeatureSource> source;
 
     if ( !options.getDriver().empty() )
     {
@@ -213,13 +223,14 @@ FeatureSourceFactory::create( const FeatureSourceOptions& options )
         osg::ref_ptr<osgDB::Options> rwopts = Registry::instance()->cloneOrCreateOptions();
         rwopts->setPluginData( FEATURE_SOURCE_OPTIONS_TAG, (void*)&options );
 
-        featureSource = dynamic_cast<FeatureSource*>( osgDB::readObjectFile( driverExt, rwopts.get() ) );
-        if ( featureSource )
+        osg::ref_ptr<osg::Object> object = osgDB::readRefObjectFile( driverExt, rwopts.get() );
+        source = dynamic_cast<FeatureSource*>( object.release() );
+        if ( source )
         {
             if ( options.name().isSet() )
-                featureSource->setName( *options.name() );
+                source->setName( *options.name() );
             else
-                featureSource->setName( options.getDriver() );
+                source->setName( options.getDriver() );
         }
         else
         {
@@ -231,7 +242,7 @@ FeatureSourceFactory::create( const FeatureSourceOptions& options )
         OE_WARN << LC << "ILLEGAL null feature driver name" << std::endl;
     }
 
-    return featureSource;
+    return source.release();
 }
 
 //------------------------------------------------------------------------
diff --git a/src/osgEarthFeatures/FeatureSourceIndexNode.cpp b/src/osgEarthFeatures/FeatureSourceIndexNode.cpp
index 4f1e12e..d47603d 100644
--- a/src/osgEarthFeatures/FeatureSourceIndexNode.cpp
+++ b/src/osgEarthFeatures/FeatureSourceIndexNode.cpp
@@ -18,6 +18,7 @@
  */
 #include <osgEarthFeatures/FeatureSourceIndexNode>
 #include <osgEarth/Registry>
+#include <osgEarth/NodeUtils>
 #include <algorithm>
 
 using namespace osgEarth;
diff --git a/src/osgEarthFeatures/FeatureSourceLayer b/src/osgEarthFeatures/FeatureSourceLayer
new file mode 100644
index 0000000..a688aaa
--- /dev/null
+++ b/src/osgEarthFeatures/FeatureSourceLayer
@@ -0,0 +1,94 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+ * Copyright 2016 Pelican Mapping
+ * http://osgearth.org
+ *
+ * osgEarth is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+
+#ifndef OSGEARTH_FEATURES_FEATURE_SOURCE_LAYER_H
+#define OSGEARTH_FEATURES_FEATURE_SOURCE_LAYER_H 1
+
+#include <osgEarthFeatures/FeatureSource>
+
+namespace osgEarth { namespace Features
+{
+    /** Options structure for serialization of the FeatureSourceLayer */
+    class FeatureSourceLayerOptions : public LayerOptions
+    {
+    public:
+        FeatureSourceLayerOptions(const ConfigOptions& co = ConfigOptions()) : LayerOptions(co) {
+            _featureSource = co;
+        }
+
+        /** Feature source configuration */
+        optional<FeatureSourceOptions>& featureSource() { return _featureSource; }
+        const optional<FeatureSourceOptions>& featureSource() const { return _featureSource; }
+
+    public:
+        Config getConfig() const {
+            return _featureSource->getConfig();
+        }
+    private:
+        optional<FeatureSourceOptions> _featureSource;
+    };
+
+    /**
+     * Layer holding a FeatureSource. Create one of these in the Map, and 
+     * other Layers can find it and access the shared FeatureSource.
+
+     * <feature_source>...</feature_source>
+     */
+    class OSGEARTHFEATURES_EXPORT FeatureSourceLayer : public Layer
+    {
+    public:
+        META_Layer(osgEarth, FeatureSourceLayer, FeatureSourceLayerOptions);
+
+        /**
+         * Construct an empty feature source layer.
+         * Call setFeatureSource or configure with options() before adding to a Map.
+         */
+        FeatureSourceLayer();
+
+        /**
+         * Construct a feature source layer with initialization options.
+         */
+        FeatureSourceLayer(const FeatureSourceLayerOptions& options);
+
+        /**
+         * The underlying feature source.
+         * open() must be called before getFeatureSource becomes available
+         */
+        void setFeatureSource(FeatureSource* value);
+        FeatureSource* getFeatureSource() const;
+
+    public: // Layer
+
+        /**
+         * Open the feature source set this layer's status to its status.
+         */
+        virtual const Status& open();
+
+        // Serialize this layer.
+        virtual Config getConfig() const;
+
+
+    protected:
+        osg::ref_ptr<FeatureSource> _featureSource;
+    };
+
+
+} } // namespace osgEarth::Features
+
+#endif // OSGEARTH_FEATURES_FEATURE_SOURCE_LAYER_H
diff --git a/src/osgEarthFeatures/FeatureSourceLayer.cpp b/src/osgEarthFeatures/FeatureSourceLayer.cpp
new file mode 100644
index 0000000..e785ef5
--- /dev/null
+++ b/src/osgEarthFeatures/FeatureSourceLayer.cpp
@@ -0,0 +1,94 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+ * Copyright 2016 Pelican Mapping
+ * http://osgearth.org
+ *
+ * osgEarth is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+#include <osgEarthFeatures/FeatureSourceLayer>
+
+#define LC "[FeatureSourceLayer] " << getName() << ": "
+
+using namespace osgEarth;
+using namespace osgEarth::Features;
+using namespace osgEarth::Symbology;
+using namespace OpenThreads;
+
+namespace osgEarth {
+    namespace Features {
+        REGISTER_OSGEARTH_LAYER(feature_source, FeatureSourceLayer);
+    }
+}
+
+FeatureSourceLayer::FeatureSourceLayer() :
+Layer(&_optionsConcrete),
+_options(&_optionsConcrete)
+{
+    init();
+}
+
+FeatureSourceLayer::FeatureSourceLayer(const FeatureSourceLayerOptions& inOptions) :
+Layer(&_optionsConcrete),
+_options(&_optionsConcrete),
+_optionsConcrete(inOptions)
+{
+    init();
+}
+
+void
+FeatureSourceLayer::setFeatureSource(FeatureSource* value)
+{
+    _featureSource = value;
+}
+
+FeatureSource*
+FeatureSourceLayer::getFeatureSource() const
+{ 
+    return _featureSource.get();
+}
+
+/**
+ * Open the feature source set this layer's status to its status.
+ */
+const Status& 
+FeatureSourceLayer::open()
+{
+    if (!_featureSource.valid())
+    {
+        _featureSource = FeatureSourceFactory::create(options());
+        if (!_featureSource.valid())
+            return setStatus(Status::Error(Status::ServiceUnavailable, "Unable to create feature source"));
+    }
+
+    Status fsStatus = _featureSource->open(getReadOptions());
+    if (fsStatus.isError())
+    {
+        setStatus(fsStatus);
+        _featureSource = 0L;
+    }
+    else
+    {
+        OE_INFO << LC << "Opened feature source OK.\n";
+    }
+
+    return getStatus();
+}
+
+Config
+FeatureSourceLayer::getConfig() const
+{
+    Config conf = Layer::getConfig();
+    conf.key() = "feature_source";
+    return conf;
+}
diff --git a/src/osgEarthFeatures/FeatureTileSource b/src/osgEarthFeatures/FeatureTileSource
index 4f4ef6b..4f261eb 100644
--- a/src/osgEarthFeatures/FeatureTileSource
+++ b/src/osgEarthFeatures/FeatureTileSource
@@ -22,7 +22,7 @@
 
 #include <osgEarthFeatures/Common>
 #include <osgEarthFeatures/FeatureSource>
-#include <osgEarthSymbology/Style>
+#include <osgEarthSymbology/StyleSheet>
 #include <osgEarth/TileSource>
 #include <osgEarth/Map>
 #include <osg/Node>
diff --git a/src/osgEarthFeatures/FeatureTileSource.cpp b/src/osgEarthFeatures/FeatureTileSource.cpp
index dc7d844..da4902b 100644
--- a/src/osgEarthFeatures/FeatureTileSource.cpp
+++ b/src/osgEarthFeatures/FeatureTileSource.cpp
@@ -17,7 +17,11 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>
  */
 #include <osgEarthFeatures/FeatureTileSource>
+#include <osgEarthFeatures/Filter>
+#include <osgEarthFeatures/FeatureCursor>
+
 #include <osgEarth/Registry>
+
 #include <osgDB/WriteFile>
 #include <osg/Notify>
 
@@ -41,8 +45,8 @@ FeatureTileSourceOptions::getConfig() const
 {
     Config conf = TileSourceOptions::getConfig();
 
-    conf.updateObjIfSet( "features", _featureOptions );
-    conf.updateObjIfSet( "styles", _styles );
+    conf.setObj( "features", _featureOptions );
+    conf.setObj( "styles", _styles );
 
     if ( _geomTypeOverride.isSet() ) {
         if ( _geomTypeOverride == Geometry::TYPE_LINESTRING )
@@ -161,7 +165,7 @@ FeatureTileSource::createImage( const TileKey& key, ProgressCallback* progress )
         return 0L;
 
     // style data
-    const StyleSheet* styles = _options.styles();
+    const StyleSheet* styles = _options.styles().get();
 
     // implementation-specific data
     osg::ref_ptr<osg::Referenced> buildData = createBuildData();
@@ -234,7 +238,7 @@ FeatureTileSource::createImage( const TileKey& key, ProgressCallback* progress )
                                 Style combinedStyle;
 
                                 // if the style string begins with an open bracket, it's an inline style definition.
-                                if ( styleString.length() > 0 && styleString.at(0) == '{' )
+                                if ( styleString.length() > 0 && styleString[0] == '{' )
                                 {
                                     Config conf( "style", styleString );
                                     conf.setReferrer( sel.styleExpression().get().uriContext().referrer() );
diff --git a/src/osgEarthFeatures/Filter b/src/osgEarthFeatures/Filter
index 5c089ee..747f494 100644
--- a/src/osgEarthFeatures/Filter
+++ b/src/osgEarthFeatures/Filter
@@ -71,7 +71,11 @@ namespace osgEarth { namespace Features
         virtual ~FeatureFilter();
     };
 
-    typedef std::list< osg::ref_ptr<FeatureFilter> > FeatureFilterList;
+    //! Vector of feature filters (ref counted)
+    class FeatureFilterChain : public osg::Referenced,
+                               public osg::MixinVector<osg::ref_ptr<FeatureFilter> >
+    {
+    };
 
     /**
      * A Factory that can create a FeatureFilter from a Config
@@ -132,11 +136,21 @@ namespace osgEarth { namespace Features
     };
 
 #define OSGEARTH_REGISTER_FEATUREFILTER( CLASSNAME )\
+    extern "C" void osgearth_featurefilter_##KEY(void) {} \
     static osgEarth::Features::RegisterFeatureFilterProxy<CLASSNAME> s_osgEarthRegisterFeatureFilterProxy_##CLASSNAME;
+    
+#define USE_OSGEARTH_FEATUREFILTER( KEY ) \
+    extern "C" void osgearth_featurefilter_##KEY(void); \
+    static osgDB::PluginFunctionProxy proxy_osgearth_featurefilter_##KEY(osgearth_featurefilter_##KEY);
 
 #define OSGEARTH_REGISTER_SIMPLE_FEATUREFILTER( KEY, CLASSNAME)\
+    extern "C" void osgearth_simple_featurefilter_##KEY(void) {} \
     static osgEarth::Features::RegisterFeatureFilterProxy< osgEarth::Features::SimpleFeatureFilterFactory<CLASSNAME> > s_osgEarthRegisterFeatureFilterProxy_##CLASSNAME##KEY(new osgEarth::Features::SimpleFeatureFilterFactory<CLASSNAME>(#KEY));
     
+#define USE_OSGEARTH_SIMPLE_FEATUREFILTER( KEY ) \
+    extern "C" void osgearth_simple_featurefilter_##KEY(void); \
+    static osgDB::PluginFunctionProxy proxy_osgearth_simple_featurefilter_##KEY(osgearth_simple_featurefilter_##KEY);
+    
 
     //--------------------------------------------------------------------
 
@@ -146,20 +160,6 @@ namespace osgEarth { namespace Features
         const ConfigOptions& getConfigOptions(const osgDB::Options* options) const;
     };
 
-    //--------------------------------------------------------------------
-
-    template<typename T>
-    class TemplateFeatureFilter : public Filter, public T
-    {
-    public:
-        FilterContext push( FeatureList& input, FilterContext& context ) {
-            for( FeatureList::iterator i = input.begin(); i != input.end(); ++i ) {
-                T::operator()( i->get(), context );
-            }
-            return context;
-        }
-    };
-
     /**
      * Base class for a filter that converts features into an osg Node.
      */
diff --git a/src/osgEarthFeatures/Filter.cpp b/src/osgEarthFeatures/Filter.cpp
index 242ac9e..68ee234 100644
--- a/src/osgEarthFeatures/Filter.cpp
+++ b/src/osgEarthFeatures/Filter.cpp
@@ -17,6 +17,7 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>
  */
 #include <osgEarthFeatures/Filter>
+#include <osgEarthFeatures/FilterContext>
 #include <osgEarthSymbology/LineSymbol>
 #include <osgEarthSymbology/PointSymbol>
 #include <osgEarth/ECEF>
@@ -103,7 +104,8 @@ FeatureFilterRegistry::create(const Config& conf, const osgDB::Options* dbo)
         dbopt->setPluginData( FEATURE_FILTER_OPTIONS_TAG, (void*)&options );
 
         std::string driverExt = std::string( ".osgearth_featurefilter_" ) + driver;
-        result = dynamic_cast<FeatureFilter*>( osgDB::readObjectFile( driverExt, dbopt.get() ) );
+        osg::ref_ptr<osg::Object> object = osgDB::readRefObjectFile( driverExt, dbopt.get() );
+        result = dynamic_cast<FeatureFilter*>( object.release() );
     }
 
     if ( !result.valid() )
@@ -143,9 +145,12 @@ FeaturesToNodeFilter::computeLocalizers( const FilterContext& context, const osg
 {
     if ( context.isGeoreferenced() )
     {
-        if ( context.getSession()->getMapInfo().isGeocentric() )
+        bool ecef = context.getOutputSRS()->isGeographic();
+
+        if (ecef)
         {
-            const SpatialReference* geogSRS = context.profile()->getSRS()->getGeographicSRS();
+
+            const SpatialReference* geogSRS = context.getOutputSRS()->getGeographicSRS();
             GeoExtent geodExtent = extent.transform( geogSRS );
             if ( geodExtent.width() < 180.0 )
             {
diff --git a/src/osgEarthFeatures/FilterContext b/src/osgEarthFeatures/FilterContext
index b363076..80bbe8d 100644
--- a/src/osgEarthFeatures/FilterContext
+++ b/src/osgEarthFeatures/FilterContext
@@ -22,10 +22,13 @@
 #include <osgEarthFeatures/Common>
 #include <osgEarthFeatures/Feature>
 #include <osgEarthFeatures/Session>
+
 #include <osgEarthSymbology/Geometry>
 #include <osgEarthSymbology/ResourceCache>
+
 #include <osgEarth/GeoData>
 #include <osgEarth/ShaderUtils>
+
 #include <osg/Matrix>
 #include <list>
 #include <vector>
@@ -34,7 +37,8 @@ namespace osgEarth { namespace Features
 {
     using namespace osgEarth;
     using namespace osgEarth::Symbology;
-    class Session;
+
+    //class Session;
     class FeatureProfile;
     class FeatureIndexBuilder;
 
@@ -44,16 +48,18 @@ namespace osgEarth { namespace Features
     class OSGEARTHFEATURES_EXPORT FilterContext
     {
     public:
+        FilterContext();
+
         FilterContext(
-            Session*              session       =0L,
+            Session*              session,
             const FeatureProfile* profile       =0L,
             const GeoExtent&      workingExtent =GeoExtent::INVALID,
             FeatureIndexBuilder*  index         =0L);
 
         FilterContext( const FilterContext& rhs );
-        
-        virtual ~FilterContext() { }
 
+        ~FilterContext();
+        
         /**
          * Assigns a resource cache to use. One is created automatically if you
          * don't assign one.
@@ -65,19 +71,27 @@ namespace osgEarth { namespace Features
          */
         void setProfile( const FeatureProfile* profile );
 
+        /**
+         * Sets the output SRS for feature processing. This is optional.
+         * If you do not set this, the output SRS will be that of the
+         * session's Map.
+         */
+        void setOutputSRS(const SpatialReference* srs) { _outputSRS = srs; }
+        const SpatialReference* getOutputSRS() const;
+
 
     public: // properties
 
         /**
          * Whether this context contains complete georeferencing information.
          */
-        bool isGeoreferenced() const { return _session.valid() && _profile.valid(); }
+        bool isGeoreferenced() const;
 
         /**
          * Access to the Session under which this filter context operates
          */
-        Session* getSession() { return _session.get(); }
-        const Session* getSession() const { return _session.get(); }
+        Session* getSession();
+        const Session* getSession() const;
 
         /**
          * The spatial profile of the feature data in this context.
@@ -181,6 +195,7 @@ namespace osgEarth { namespace Features
         void pushHistory(const std::string& value) { _history.push_back(value); }
 
     protected:
+
         osg::ref_ptr<Session>              _session;
         osg::ref_ptr<const FeatureProfile> _profile;
         bool                               _isGeocentric;
@@ -191,6 +206,7 @@ namespace osgEarth { namespace Features
         FeatureIndexBuilder*               _index;
         optional<ShaderPolicy>             _shaderPolicy;
         std::vector<std::string>           _history;
+        osg::ref_ptr<const SpatialReference> _outputSRS;
     };
 
 } } // namespace osgEarth::Features
diff --git a/src/osgEarthFeatures/FilterContext.cpp b/src/osgEarthFeatures/FilterContext.cpp
index 5c0e23b..15b346b 100644
--- a/src/osgEarthFeatures/FilterContext.cpp
+++ b/src/osgEarthFeatures/FilterContext.cpp
@@ -17,12 +17,23 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>
  */
 #include <osgEarthFeatures/FilterContext>
+#include <osgEarthFeatures/Session>
 #include <osgEarthSymbology/ResourceCache>
 #include <osgEarth/Registry>
 
 using namespace osgEarth;
 using namespace osgEarth::Features;
 
+FilterContext::FilterContext() :
+_session(0L),
+_profile(0L),
+_isGeocentric(false),
+_index(0L),
+_shaderPolicy(osgEarth::SHADERPOLICY_GENERATE)
+{
+    //nop
+}
+
 FilterContext::FilterContext(Session*               session,
                              const FeatureProfile*  profile,
                              const GeoExtent&       workingExtent,
@@ -79,7 +90,13 @@ _inverseReferenceFrame( rhs._inverseReferenceFrame ),
 _resourceCache        ( rhs._resourceCache.get() ),
 _index                ( rhs._index ),
 _shaderPolicy         ( rhs._shaderPolicy ),
-_history              ( rhs._history )
+_history              ( rhs._history ),
+_outputSRS            ( rhs._outputSRS.get() )
+{
+    //nop
+}
+
+FilterContext::~FilterContext()
 {
     //nop
 }
@@ -90,6 +107,42 @@ FilterContext::setProfile(const FeatureProfile* value)
     _profile = value;
 }
 
+Session*
+FilterContext::getSession()
+{
+    return _session.get();
+}
+
+const Session*
+FilterContext::getSession() const
+{
+    return _session.get();
+}
+
+bool
+FilterContext::isGeoreferenced() const
+{ 
+    return _session.valid() && _profile.valid();
+}
+
+const SpatialReference*
+FilterContext::getOutputSRS() const
+{
+    if (_outputSRS.valid())
+        return _outputSRS.get();
+
+    if (_session.valid() && _session->getMapSRS())
+        return _session->getMapSRS();
+
+    if (_profile.valid() && _profile->getSRS())
+        return _profile->getSRS();
+
+    if (_extent.isSet())
+        return _extent->getSRS();
+
+    return SpatialReference::get("wgs84");
+}
+
 const osgDB::Options*
 FilterContext::getDBOptions() const
 {
diff --git a/src/osgEarthFeatures/GPULines b/src/osgEarthFeatures/GPULines
new file mode 100644
index 0000000..448dc28
--- /dev/null
+++ b/src/osgEarthFeatures/GPULines
@@ -0,0 +1,96 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+ * Copyright 2016 Pelican Mapping
+ * http://osgearth.org
+ *
+ * osgEarth is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+#ifndef OSGEARTHFEATURES_GPU_LINES_H
+#define OSGEARTHFEATURES_GPU_LINES_H 1
+
+#include <osgEarthFeatures/Common>
+#include <osgEarthFeatures/Filter>
+#include <osgEarthSymbology/Style>
+#include <osg/Array>
+#include <osg/Geometry>
+
+namespace osgEarth { namespace Features
+{
+    using namespace osgEarth::Symbology;
+
+    /**
+     * Static application-wide settings for GPU Line Generation
+     * You can set these in your app if you don't like the defaults.
+     */
+    class OSGEARTHFEATURES_EXPORT GPULines
+    {        
+    public:
+        //! Binding location for "previous" vertex attribute
+        static int PreviousVertexAttrLocation;
+
+        //! Binding location for "next" vertex attribute
+        static int NextVertexAttrLocation;
+
+        //! Binding location for "width" attribute
+        static int WidthAttrLocation; 
+
+    private:
+        GPULines() { }
+    };
+
+    /**
+     * Assembles a drawable for rendering lines on the GPU.
+     */
+    class OSGEARTHFEATURES_EXPORT GPULinesOperator
+    {
+    public:
+        struct Callback {
+            virtual void operator()(unsigned i) = 0;
+        };
+
+    public:
+        /**
+         * Default constructor
+         */
+        GPULinesOperator();
+
+        /**
+         * Construct the operator
+         * @param[in ] stroke Line rendering properties
+         */
+        GPULinesOperator(const Stroke& stoke);
+
+        /**
+         * Run the GPU lines assembler.
+         *
+         * @param[in ] verts Array of vertices to use to build a GPU line strip
+         * @param[in ] closeTheLoop Whether to connect the last point to the first
+         *
+         * @return Geometry suitable for use with GPU line shader.
+         */
+        osg::Geometry* operator()(osg::Vec3Array* verts, bool closeTheLoop) const;
+
+        /**
+         * Installs the necessary shader to render GPU lines
+         */
+        void installShaders(osg::Node* node) const;
+
+    protected:
+        Stroke _stroke;
+        friend class GPULinesFilter;
+    };
+
+} } // namespace osgEarth::Features
+
+#endif // OSGEARTHFEATURES_GPU_LINES_H
diff --git a/src/osgEarthFeatures/GPULines.cpp b/src/osgEarthFeatures/GPULines.cpp
new file mode 100644
index 0000000..ab76bed
--- /dev/null
+++ b/src/osgEarthFeatures/GPULines.cpp
@@ -0,0 +1,206 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+ * Copyright 2016 Pelican Mapping
+ * http://osgearth.org
+ *
+ * osgEarth is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+#include <osgEarthFeatures/GPULines>
+#include <osgEarthFeatures/FeatureSourceIndexNode>
+#include <osgEarthFeatures/Shaders>
+
+#include <osgEarth/VirtualProgram>
+#include <osgEarth/Utils>
+
+#include <osg/Depth>
+#include <osg/CullFace>
+
+#define LC "[GPULines] "
+
+#if defined(OSG_GLES1_AVAILABLE) || defined(OSG_GLES2_AVAILABLE) || defined(OSG_GLES3_AVAILABLE)
+#define OE_GLES_AVAILABLE
+#endif
+
+using namespace osgEarth::Features;
+
+
+// References:
+// https://mattdesl.svbtle.com/drawing-lines-is-hard
+// https://github.com/mattdesl/webgl-lines
+
+
+// static attribute binding locations. Changable by the user.
+int GPULines::PreviousVertexAttrLocation = 9;
+int GPULines::NextVertexAttrLocation = 10;
+int GPULines::WidthAttrLocation = 11;
+
+
+GPULinesOperator::GPULinesOperator()
+{
+    //nop
+}
+
+GPULinesOperator::GPULinesOperator(const Stroke& stroke) :
+_stroke( stroke )
+{
+    //nop
+}
+
+osg::Geometry*
+GPULinesOperator::operator()(osg::Vec3Array* input, bool isLoop) const
+{
+    if (input == 0L || input->size() <= 1)
+        return 0L;
+
+    osg::Geometry* geom = new osg::Geometry();
+    geom->setUseVertexBufferObjects(true);
+    geom->setUseDisplayList(false);
+
+    bool frontEqualsBack = input->front() == input->back();
+
+    // is this a line loop even though the caller doesn't know it?
+    if (frontEqualsBack)
+        isLoop = true;
+    
+    // if this is a loop and the first and last elements are equal, trim the back:
+    int inputSize = input->size() - (frontEqualsBack? 1 : 0);
+
+    // two output elements per input element:
+    int outputSize = inputSize * 2;
+
+    osg::Vec3Array* positions = new osg::Vec3Array();
+    positions->reserve(outputSize);
+    geom->setVertexArray(positions);
+
+    osg::Vec3Array* previous = new osg::Vec3Array();
+    previous->reserve(outputSize);
+    previous->setBinding(previous->BIND_PER_VERTEX);
+    geom->setVertexAttribArray(GPULines::PreviousVertexAttrLocation, previous);
+    geom->setVertexAttribBinding(GPULines::PreviousVertexAttrLocation, osg::Geometry::BIND_PER_VERTEX);
+    geom->setVertexAttribNormalize(GPULines::PreviousVertexAttrLocation, false);
+
+    osg::Vec3Array* next = new osg::Vec3Array();
+    next->reserve(outputSize);
+    next->setBinding(next->BIND_PER_VERTEX);
+    geom->setVertexAttribArray(GPULines::NextVertexAttrLocation, next);
+    geom->setVertexAttribBinding(GPULines::NextVertexAttrLocation, osg::Geometry::BIND_PER_VERTEX);
+    geom->setVertexAttribNormalize(GPULines::NextVertexAttrLocation, false);
+
+    osg::FloatArray* widths = new osg::FloatArray();
+    widths->reserve(outputSize);
+    widths->setBinding(next->BIND_PER_VERTEX);
+    geom->setVertexAttribArray(GPULines::WidthAttrLocation, widths);
+    geom->setVertexAttribBinding(GPULines::WidthAttrLocation, osg::Geometry::BIND_PER_VERTEX);
+    geom->setVertexAttribNormalize(GPULines::WidthAttrLocation, false);
+
+    float thickness = _stroke.width().get();
+
+    // first populate the positions and direction attributes:
+    for (int i = 0; i < inputSize; ++i)
+    {
+        const osg::Vec3& c = (*input)[i];
+        positions->push_back(c);      
+        positions->push_back(c);
+
+        // opposite directions:
+        widths->push_back(-thickness); 
+        widths->push_back(thickness);
+
+        int prevIndex = i-1;
+        if (prevIndex < 0) 
+            prevIndex = isLoop ? inputSize-1 : i;
+
+        const osg::Vec3& p = (*input)[prevIndex];
+        previous->push_back(p);
+        previous->push_back(p);
+
+        int nextIndex = i+1;
+        if (nextIndex > inputSize-1)
+            nextIndex = isLoop ? 0 : i;
+
+        const osg::Vec3& n = (*input)[nextIndex];
+        next->push_back(n);
+        next->push_back(n);
+    }
+
+    // generate the triangle set.
+    unsigned numEls = (inputSize + (isLoop? 1 : 0)) * 6;
+    osg::DrawElements* els = 
+#ifndef OE_GLES_AVAILABLE
+        numEls > 0xFFFF ? (osg::DrawElements*)new osg::DrawElementsUInt  ( GL_TRIANGLES ) :
+#endif
+        numEls > 0xFF   ? (osg::DrawElements*)new osg::DrawElementsUShort( GL_TRIANGLES ) :
+                          (osg::DrawElements*)new osg::DrawElementsUByte ( GL_TRIANGLES );
+    els->reserveElements(numEls);
+
+    // IMPORTANT!
+    // Don't change the order of these elements! Because of the way
+    // GPU line stippling works, it is critical that the provoking vertex
+    // be at the beginning of each line segment. In this case we are using
+    // GL_TRIANGLES and thus the provoking vertex (PV) is the FINAL vert
+    // in each triangle.
+    int e;
+    for (e = 0; e < positions->size()-2; e += 2)
+    {
+        els->addElement(e+3);
+        els->addElement(e+1);
+        els->addElement(e+0); // PV
+        els->addElement(e+2);
+        els->addElement(e+3);
+        els->addElement(e+0); // PV
+    }
+
+    if (isLoop)
+    {
+        els->addElement(1);
+        els->addElement(e+1);
+        els->addElement(e+0); // PV
+        els->addElement(0);
+        els->addElement(1);
+        els->addElement(e+0); // PV
+    }
+
+    geom->addPrimitiveSet(els);
+    
+    if (_stroke.stipplePattern().isSet())
+    {
+        osg::StateSet* ss = geom->getOrCreateStateSet();
+
+        ss->setDefine(
+            "OE_GPULINES_STIPPLE_PATTERN", 
+            Stringify() << _stroke.stipplePattern().get() );
+
+        ss->setDefine(
+            "OE_GPULINES_STIPPLE_FACTOR",
+            Stringify() << _stroke.stippleFactor().get() );
+    }
+
+    return geom;
+}
+
+
+void
+GPULinesOperator::installShaders(osg::Node* node) const
+{
+    osg::StateSet* ss = node->getOrCreateStateSet();
+    VirtualProgram* vp = VirtualProgram::getOrCreate(ss);
+    GPULineShaders shaders;
+    shaders.loadAll(vp);
+    vp->addBindAttribLocation("oe_GPULines_prev", GPULines::PreviousVertexAttrLocation);
+    vp->addBindAttribLocation("oe_GPULines_next", GPULines::NextVertexAttrLocation);
+    vp->addBindAttribLocation("oe_GPULines_width", GPULines::WidthAttrLocation);
+    ss->setMode(GL_CULL_FACE, osg::StateAttribute::OFF | osg::StateAttribute::OVERRIDE | osg::StateAttribute::PROTECTED);
+    ss->setAttributeAndModes(new osg::Depth(osg::Depth::LEQUAL, 0.0, 1.0, false));
+    ss->setDefine("OE_GPU_LINES");
+}
diff --git a/src/osgEarthFeatures/GPULinesScreenProj.glsl b/src/osgEarthFeatures/GPULinesScreenProj.glsl
new file mode 100644
index 0000000..4d33479
--- /dev/null
+++ b/src/osgEarthFeatures/GPULinesScreenProj.glsl
@@ -0,0 +1,181 @@
+#version $GLSL_VERSION_STR
+
+#pragma vp_name GPU Lines Screen Projected
+#pragma vp_entryPoint oe_GPULinesProj_VS_CLIP
+#pragma vp_location vertex_clip
+#pragma import_defines(OE_GPULINES_STIPPLE_PATTERN, OE_GPU_CLAMPING)
+
+uniform vec2 oe_ViewportSize;
+
+in vec3 oe_GPULines_prev;
+in vec3 oe_GPULines_next;
+in float oe_GPULines_width;
+
+#ifdef OE_GPULINES_STIPPLE_PATTERN
+flat out vec2 oe_GPULines_rv;
+#endif
+
+#ifdef OE_GPU_CLAMPING
+// Stage globals set in GPUClamping.vert.glsl only when OE_GPU_LINES is enabled
+vec4 oe_GPULines_prevViewClamped;
+vec4 oe_GPULines_nextViewClamped;
+#endif // OE_GPU_CLAMPING
+
+void oe_GPULinesProj_VS_CLIP(inout vec4 currClip)
+{
+    vec2 arVec = vec2(
+        oe_ViewportSize.x/oe_ViewportSize.y,
+        1.0);
+
+#if defined(OE_GPU_CLAMPING)
+    vec4 prevClip = gl_ProjectionMatrix * oe_GPULines_prevViewClamped;
+    vec4 nextClip = gl_ProjectionMatrix * oe_GPULines_nextViewClamped;
+#else
+    vec4 prevClip = gl_ModelViewProjectionMatrix * vec4(oe_GPULines_prev, 1.0);
+    vec4 nextClip = gl_ModelViewProjectionMatrix * vec4(oe_GPULines_next, 1.0);
+#endif
+
+    vec2 currUnit = currClip.xy/currClip.w * arVec;
+    vec2 prevUnit = prevClip.xy/prevClip.w * arVec;
+    vec2 nextUnit = nextClip.xy/nextClip.w * arVec;
+
+    float thickness = abs(oe_GPULines_width);
+    float len = thickness;
+    float orientation = sign(oe_GPULines_width);
+
+    vec2 dir = vec2(0.0);
+
+    // We will use this to calculate stippling data:
+    vec2 stippleDir;
+
+    // The following vertex comparisons must be done in model 
+    // space because the equivalency gets mashed after projection.
+
+    // starting point uses (next - current)
+    if (gl_Vertex.xyz == oe_GPULines_prev)
+    {
+        dir = normalize(nextUnit - currUnit);
+        stippleDir = dir;
+    }
+    
+    // ending point uses (current - previous)
+    else if (gl_Vertex.xyz == oe_GPULines_next)
+    {
+        dir = normalize(currUnit - prevUnit);
+        stippleDir = dir;
+    }
+
+    // middle? join
+    else
+    {
+        vec2 dirA = normalize(currUnit - prevUnit);
+        vec2 dirB = normalize(nextUnit - currUnit);
+
+        // Edge case: segment that doubles back on itself:
+        if (dot(dirA,dirB) < -0.99)
+        {
+            dir = dirA;
+        }
+
+        // Normal case - create a mitered corner:
+        else
+        {
+            vec2 tangent = normalize(dirA+dirB);
+            vec2 perp = vec2(-dirA.y, dirA.x);
+            vec2 miter = vec2(-tangent.y, tangent.x);
+            dir = tangent;
+            len = thickness / dot(miter, perp);
+
+            // limit the length of a mitered corner, to prevent unsightly spikes
+            const float limit = 2.0;
+            if (len > thickness*limit)
+            {
+                len = thickness;
+                dir = dirB;
+            }
+        }
+        stippleDir = dirB;
+    }
+
+    // calculate the extrusion vector in pixels
+    // note: seems like it should be len/2, BUT we are in [-1..1] space
+    vec2 extrudePixels = vec2(-dir.y, dir.x) * len;
+
+    // and convert to unit space:
+    vec2 extrudeUnit = extrudePixels / oe_ViewportSize;
+
+    // and from that make a clip-coord offset vector
+    vec4 offset = vec4(extrudeUnit*orientation*currClip.w, 0.0, 0.0);
+    currClip += offset;
+
+#ifdef OE_GPULINES_STIPPLE_PATTERN
+    // Line creation is done. Now, calculate a rotation angle
+    // for use by out fragment shader to do GPU stippling. 
+    // This "rotates" the fragment coordinate onto the X axis so that
+    // we can apply stippling along the direction of the line.
+    // Note: this depends on the GLSL "provoking vertex" being at the 
+    // beginning of the line segment!
+
+    // flip the vector so stippling always proceedes from left to right
+    // regardless of the direction of the segment
+    stippleDir = normalize(stippleDir.x < 0? -stippleDir : stippleDir);
+
+    // calculate the rotation angle that will project the
+    // fragment coord onto the X-axis for stipple pattern sampling.
+    float way = sign(cross(vec3(1, 0, 0), vec3(stippleDir, 0)).z);
+    float angle = acos(dot(vec2(1, 0), stippleDir)) * way;
+
+    // quantize the rotation angle to mitigate precision problems
+    // when connecting segments with slightly different vectors
+    const float pi = 3.14159265359;
+    const float q = pi/8.0;
+    angle = floor(angle/q) * q;
+
+    // send it to the fragment shader.
+    oe_GPULines_rv = vec2(cos(angle), sin(angle));
+#endif
+}
+
+
+[break]
+
+#version $GLSL_VERSION_STR
+
+#pragma vp_name GPU Lines Screen Projected FS
+#pragma vp_entryPoint oe_GPULinesProj_Stippler_FS
+#pragma vp_location fragment_coloring
+#pragma import_defines (OE_GPULINES_STIPPLE_PATTERN, OE_GPULINES_STIPPLE_FACTOR)
+
+#ifdef OE_GPULINES_STIPPLE_PATTERN
+flat in vec2 oe_GPULines_rv;
+#endif
+
+void oe_GPULinesProj_Stippler_FS(inout vec4 color)
+{
+#ifdef OE_GPULINES_STIPPLE_PATTERN
+
+    // we could make these unfiorms if necessary
+    const int pattern = OE_GPULINES_STIPPLE_PATTERN;
+    const int factor = OE_GPULINES_STIPPLE_FACTOR;
+
+    // coordinate of the fragment, shifted to 0:
+    vec2 coord = (gl_FragCoord.xy - 0.5);
+
+    // rotate the frag coord onto the X-axis so we can sample the 
+    // stipple pattern:
+    vec2 coordProj =
+        mat2(oe_GPULines_rv.x, -oe_GPULines_rv.y,
+             oe_GPULines_rv.y,  oe_GPULines_rv.x)
+        * coord;
+
+    // sample the stippling pattern (16-bits repeating)
+    int ci = int(mod(coordProj.x, 16 * factor)) / factor;
+    if ((pattern & (1 << ci)) == 0)
+        discard; 
+
+    // uncomment to debug stipple direction vectors
+    //color.b = 0;
+    //color.r = oe_GPULines_rv.x;
+    //color.g = oe_GPULines_rv.y;
+#endif
+}
diff --git a/src/osgEarthFeatures/GeometryCompiler b/src/osgEarthFeatures/GeometryCompiler
index 5c16579..8c540b9 100644
--- a/src/osgEarthFeatures/GeometryCompiler
+++ b/src/osgEarthFeatures/GeometryCompiler
@@ -25,13 +25,14 @@
 #include <osgEarthFeatures/ResampleFilter>
 #include <osgEarthSymbology/Style>
 #include <osgEarth/GeoMath>
+#include <osgEarth/ShaderUtils>
 
 namespace osgEarth { namespace Features
 {
     using namespace osgEarth;
     using namespace osgEarth::Symbology;
 
-    class OSGEARTHFEATURES_EXPORT GeometryCompilerOptions : public ConfigOptions
+    class OSGEARTHFEATURES_EXPORT GeometryCompilerOptions
     {
     public:
         /**
@@ -40,9 +41,10 @@ namespace osgEarth { namespace Features
         static void setDefaults(const GeometryCompilerOptions& defaults);
 
     public:
+        /**
+         * Construct new copiler options, optionally deserializing them
+         */
         GeometryCompilerOptions(const ConfigOptions& conf =ConfigOptions());
-        
-        virtual ~GeometryCompilerOptions() { }
 
     public:
         /** Maximum span of a generated edge, in degrees. Applicable to geocentric maps only */
@@ -96,19 +98,29 @@ namespace osgEarth { namespace Features
         optional<bool>& optimize() { return _optimize; }
         const optional<bool>& optimize() const { return _optimize; }
 
-        /** Whether to run a geometry validation pass on teh resulting group. This is for debugging
+        /** Whether to run the vertex order optimizer on geometry. */
+        optional<bool>& optimizeVertexOrdering() { return _optimizeVertexOrdering; }
+        const optional<bool>& optimizeVertexOrdering() const { return _optimizeVertexOrdering; }
+
+        /** Whether to run a geometry validation pass on the resulting group. This is for debugging
         purposes and will dump issues to the console. */
         optional<bool>& validate() { return _validate; }
         const optional<bool>& validate() const { return _validate; }
 
         /** Maximum size (angle, degrees) of a polygon tile, when breaking up a large polygon for tessellation;
-        only applies to geocentric maps - detault = 5.0 */
+        only applies to geocentric maps (detault = 5.0) */
         optional<float>& maxPolygonTilingAngle() { return _maxPolyTilingAngle; }
         const optional<float>& maxPolygonTilingAngle() const { return _maxPolyTilingAngle; }
 
+        /** Whether to use GPU-generated geometry for screen-space (pixel) width lines (default=false) */
+        optional<bool>& useGPUScreenSpaceLines() { return _useGPULines; }
+        const optional<bool>& useGPUScreenSpaceLines() const { return _useGPULines; }
+
     public:
         Config getConfig() const;
-        void mergeConfig( const Config& conf );
+
+    protected:
+        void fromConfig( const Config& conf );
 
     private:
         optional<double>               _maxGranularity_deg;
@@ -124,10 +136,11 @@ namespace osgEarth { namespace Features
         optional<ShaderPolicy>         _shaderPolicy;
         optional<bool>                 _optimizeStateSharing;
         optional<bool>                 _optimize;
+        optional<bool>                 _optimizeVertexOrdering;
         optional<bool>                 _validate;
         optional<float>                _maxPolyTilingAngle;
+        optional<bool>                 _useGPULines;
 
-        void fromConfig( const Config& conf );
 
         static GeometryCompilerOptions s_defaults;
 
@@ -135,6 +148,7 @@ namespace osgEarth { namespace Features
        GeometryCompilerOptions(bool); // internal
     };
 
+
     /**
      * Compiles a collection of features against a style.
      */
diff --git a/src/osgEarthFeatures/GeometryCompiler.cpp b/src/osgEarthFeatures/GeometryCompiler.cpp
index c32037d..9638713 100644
--- a/src/osgEarthFeatures/GeometryCompiler.cpp
+++ b/src/osgEarthFeatures/GeometryCompiler.cpp
@@ -17,6 +17,7 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>
  */
 #include "GeometryCompiler"
+
 #include <osgEarthFeatures/BuildGeometryFilter>
 #include <osgEarthFeatures/BuildTextFilter>
 #include <osgEarthFeatures/AltitudeFilter>
@@ -26,19 +27,21 @@
 #include <osgEarthFeatures/SubstituteModelFilter>
 #include <osgEarthFeatures/TessellateOperator>
 #include <osgEarthFeatures/Session>
+
 #include <osgEarth/Utils>
-#include <osgEarth/AutoScale>
 #include <osgEarth/CullingUtils>
 #include <osgEarth/Registry>
 #include <osgEarth/Capabilities>
 #include <osgEarth/ShaderGenerator>
 #include <osgEarth/ShaderUtils>
 #include <osgEarth/Utils>
+
 #include <osg/MatrixTransform>
 #include <osg/Timer>
 #include <osgDB/WriteFile>
 #include <osgUtil/Optimizer>
 
+#include <cstdlib>
 
 #define LC "[GeometryCompiler] "
 
@@ -70,16 +73,20 @@ _shaderPolicy          ( SHADERPOLICY_GENERATE ),
 _geoInterp             ( GEOINTERP_GREAT_CIRCLE ),
 _optimizeStateSharing  ( true ),
 _optimize              ( false ),
+_optimizeVertexOrdering( true ),
 _validate              ( false ),
-_maxPolyTilingAngle    ( 45.0f )
+_maxPolyTilingAngle    ( 45.0f ),
+_useGPULines           ( false )
 {
-   //nop
+    if (::getenv("OSGEARTH_GPU_SCREEN_SPACE_LINES") != 0L)
+    {
+        _useGPULines.init(true);
+    }
 }
 
 //-----------------------------------------------------------------------
 
 GeometryCompilerOptions::GeometryCompilerOptions(const ConfigOptions& conf) :
-ConfigOptions          ( conf ),
 _maxGranularity_deg    ( s_defaults.maxGranularity().value() ),
 _mergeGeometry         ( s_defaults.mergeGeometry().value() ),
 _clustering            ( s_defaults.clustering().value() ),
@@ -90,10 +97,12 @@ _shaderPolicy          ( s_defaults.shaderPolicy().value() ),
 _geoInterp             ( s_defaults.geoInterp().value() ),
 _optimizeStateSharing  ( s_defaults.optimizeStateSharing().value() ),
 _optimize              ( s_defaults.optimize().value() ),
+_optimizeVertexOrdering( s_defaults.optimizeVertexOrdering().value() ),
 _validate              ( s_defaults.validate().value() ),
-_maxPolyTilingAngle    ( s_defaults.maxPolygonTilingAngle().value() )
+_maxPolyTilingAngle    ( s_defaults.maxPolygonTilingAngle().value() ),
+_useGPULines           ( s_defaults.useGPUScreenSpaceLines().value() )
 {
-    fromConfig(_conf);
+    fromConfig(conf.getConfig());
 }
 
 void
@@ -110,8 +119,10 @@ GeometryCompilerOptions::fromConfig( const Config& conf )
     conf.getIfSet   ( "use_vbo", _useVertexBufferObjects);
     conf.getIfSet   ( "optimize_state_sharing", _optimizeStateSharing );
     conf.getIfSet   ( "optimize", _optimize );
+    conf.getIfSet   ( "optimize_vertex_ordering", _optimizeVertexOrdering);
     conf.getIfSet   ( "validate", _validate );
     conf.getIfSet   ( "max_polygon_tiling_angle", _maxPolyTilingAngle );
+    conf.getIfSet   ( "use_gpu_screen_space_lines", _useGPULines );
 
     conf.getIfSet( "shader_policy", "disable",  _shaderPolicy, SHADERPOLICY_DISABLE );
     conf.getIfSet( "shader_policy", "inherit",  _shaderPolicy, SHADERPOLICY_INHERIT );
@@ -121,7 +132,7 @@ GeometryCompilerOptions::fromConfig( const Config& conf )
 Config
 GeometryCompilerOptions::getConfig() const
 {
-    Config conf = ConfigOptions::getConfig();
+    Config conf;
     conf.addIfSet   ( "max_granularity",  _maxGranularity_deg );
     conf.addIfSet   ( "merge_geometry",   _mergeGeometry );
     conf.addIfSet   ( "clustering",       _clustering );
@@ -133,8 +144,10 @@ GeometryCompilerOptions::getConfig() const
     conf.addIfSet   ( "use_vbo", _useVertexBufferObjects);
     conf.addIfSet   ( "optimize_state_sharing", _optimizeStateSharing );
     conf.addIfSet   ( "optimize", _optimize );
+    conf.addIfSet   ( "optimize_vertex_ordering", _optimizeVertexOrdering);
     conf.addIfSet   ( "validate", _validate );
     conf.addIfSet   ( "max_polygon_tiling_angle", _maxPolyTilingAngle );
+    conf.addIfSet   ( "use_gpu_screen_space_lines", _useGPULines );
 
     conf.addIfSet( "shader_policy", "disable",  _shaderPolicy, SHADERPOLICY_DISABLE );
     conf.addIfSet( "shader_policy", "inherit",  _shaderPolicy, SHADERPOLICY_INHERIT );
@@ -142,12 +155,6 @@ GeometryCompilerOptions::getConfig() const
     return conf;
 }
 
-void
-GeometryCompilerOptions::mergeConfig( const Config& conf )
-{
-    ConfigOptions::mergeConfig( conf );
-    fromConfig( conf );
-}
 
 //-----------------------------------------------------------------------
 
@@ -255,13 +262,14 @@ GeometryCompiler::compile(FeatureList&          workingSet,
     const MarkerSymbol*    marker    = style.get<MarkerSymbol>();    // to be deprecated
     const IconSymbol*      icon      = style.get<IconSymbol>();
     const ModelSymbol*     model     = style.get<ModelSymbol>();
+    const RenderSymbol*    render    = style.get<RenderSymbol>();
 
     // Perform tessellation first.
     if ( line )
     {
         if ( line->tessellation().isSet() )
         {
-            TemplateFeatureFilter<TessellateOperator> filter;
+            TessellateOperator filter;
             filter.setNumPartitions( *line->tessellation() );
             filter.setDefaultGeoInterp( _options.geoInterp().get() );
             sharedCX = filter.push( workingSet, sharedCX );
@@ -269,7 +277,7 @@ GeometryCompiler::compile(FeatureList&          workingSet,
         }
         else if ( line->tessellationSize().isSet() )
         {
-            TemplateFeatureFilter<TessellateOperator> filter;
+            TessellateOperator filter;
             filter.setMaxPartitionSize( *line->tessellationSize() );
             filter.setDefaultGeoInterp( _options.geoInterp().get() );
             sharedCX = filter.push( workingSet, sharedCX );
@@ -436,12 +444,6 @@ GeometryCompiler::compile(FeatureList&          workingSet,
             if ( trackHistory ) history.push_back( "substitute" );
 
             resultGroup->addChild( node );
-
-            // enable auto scaling on the group?
-            if ( model && model->autoScale() == true )
-            {
-                resultGroup->getOrCreateStateSet()->setRenderBinDetails(0, osgEarth::AUTO_SCALE_BIN );
-            }
         }
     }
 
@@ -489,8 +491,10 @@ GeometryCompiler::compile(FeatureList&          workingSet,
         }
 
         BuildGeometryFilter filter( style );
+
         filter.maxGranularity() = *_options.maxGranularity();
         filter.geoInterp()      = *_options.geoInterp();
+        filter.useGPULines()    = *_options.useGPUScreenSpaceLines();
 
         if (_options.maxPolygonTilingAngle().isSet())
             filter.maxPolygonTilingAngle() = *_options.maxPolygonTilingAngle();
@@ -498,6 +502,12 @@ GeometryCompiler::compile(FeatureList&          workingSet,
         if ( _options.featureName().isSet() )
             filter.featureName() = *_options.featureName();
 
+        if (_options.optimizeVertexOrdering().isSet())
+            filter.optimizeVertexOrdering() = *_options.optimizeVertexOrdering();
+
+        if (render && render->maxCreaseAngle().isSet())
+            filter.maxCreaseAngle() = render->maxCreaseAngle().get();
+
         osg::Node* node = filter.push( workingSet, sharedCX );
         if ( node )
         {
diff --git a/src/osgEarthFeatures/LabelSource b/src/osgEarthFeatures/LabelSource
index 31710b2..bde466e 100644
--- a/src/osgEarthFeatures/LabelSource
+++ b/src/osgEarthFeatures/LabelSource
@@ -21,7 +21,6 @@
 
 #include <osgEarthFeatures/Common>
 #include <osgEarthFeatures/Feature>
-#include <osgEarthFeatures/FilterContext>
 #include <osgEarth/Config>
 #include <osgEarth/Revisioning>
 #include <osgEarthSymbology/TextSymbol>
@@ -31,6 +30,8 @@ namespace osgEarth { namespace Features
     using namespace osgEarth;
     using namespace osgEarth::Symbology;
 
+    class FilterContext;
+
     /**
      * Configuration options for a label source.
      */
diff --git a/src/osgEarthFeatures/LabelSource.cpp b/src/osgEarthFeatures/LabelSource.cpp
index 690fb6f..b60627d 100644
--- a/src/osgEarthFeatures/LabelSource.cpp
+++ b/src/osgEarthFeatures/LabelSource.cpp
@@ -67,7 +67,7 @@ LabelSource::~LabelSource()
 LabelSource*
 LabelSourceFactory::create( const LabelSourceOptions& options )
 {
-    LabelSource* labelSource = 0L;
+    osg::ref_ptr<LabelSource> source;
 
     if ( !options.getDriver().empty() )
     {
@@ -76,10 +76,11 @@ LabelSourceFactory::create( const LabelSourceOptions& options )
         osg::ref_ptr<osgDB::Options> rwopts = Registry::instance()->cloneOrCreateOptions();
         rwopts->setPluginData( LABEL_SOURCE_OPTIONS_TAG, (void*)&options );
 
-        labelSource = dynamic_cast<LabelSource*>( osgDB::readObjectFile( driverExt, rwopts.get() ) );
-        if ( labelSource )
+        osg::ref_ptr<osg::Object> object = osgDB::readRefObjectFile( driverExt, rwopts.get() );
+        source = dynamic_cast<LabelSource*>( object.release() );
+        if ( source )
         {
-            //modelSource->setName( options.getName() );
+            //source->setName( options.getName() );
             //OE_INFO << "Loaded LabelSource driver \"" << options.getDriver() << "\" OK" << std::endl;
         }
         else
@@ -92,7 +93,7 @@ LabelSourceFactory::create( const LabelSourceOptions& options )
         OE_WARN << LC << "FAIL, illegal null driver specification" << std::endl;
     }
 
-    return labelSource;
+    return source.release();
 }
 
 //------------------------------------------------------------------------
diff --git a/src/osgEarthFeatures/MVT.cpp b/src/osgEarthFeatures/MVT.cpp
index ae2d3fe..9bb5937 100644
--- a/src/osgEarthFeatures/MVT.cpp
+++ b/src/osgEarthFeatures/MVT.cpp
@@ -33,6 +33,8 @@
 using namespace osgEarth;
 using namespace osgEarth::Features;
 
+#define LC "[MVT] "
+
 #define CMD_BITS 3
 #define CMD_MOVETO 1
 #define CMD_LINETO 2
@@ -58,6 +60,254 @@ int zig_zag_decode(int n)
     return (n >> 1) ^ (-(n & 1));
 }
 
+#ifdef OSGEARTH_HAVE_MVT
+
+Geometry* decodeLine(const mapnik::vector::tile_feature& feature, const TileKey& key, unsigned int tileres)
+{
+    unsigned int length = 0;
+    int cmd = -1;
+    const int cmd_bits = 3;
+
+    int x = 0;
+    int y = 0;
+
+    std::vector< osg::ref_ptr< osgEarth::Symbology::LineString > > lines;
+    osg::ref_ptr< osgEarth::Symbology::LineString > currentLine;
+
+    for (int k = 0; k < feature.geometry_size();)
+    {
+        if (!length)
+        {
+            unsigned int cmd_length = feature.geometry(k++);
+            cmd = cmd_length & ((1 << cmd_bits) - 1);
+            length = cmd_length >> cmd_bits;
+        }
+        if (length > 0)
+        {
+            length--;
+
+            if (cmd == SEG_MOVETO || cmd == SEG_LINETO)
+            {
+                if (cmd == SEG_MOVETO)
+                {
+                    currentLine = new osgEarth::Symbology::LineString;
+                    lines.push_back( currentLine.get() );
+                }
+                int px = feature.geometry(k++);
+                int py = feature.geometry(k++);
+                px = zig_zag_decode(px);
+                py = zig_zag_decode(py);
+
+                x += px;
+                y += py;
+
+                double width = key.getExtent().width();
+                double height = key.getExtent().height();
+
+                double geoX = key.getExtent().xMin() + (width/(double)tileres) * (double)x;
+                double geoY = key.getExtent().yMax() - (height/(double)tileres) * (double)y;
+
+                if (currentLine.valid())
+                {
+                    currentLine->push_back(geoX, geoY, 0);
+                }
+            }
+        }
+    }
+
+    currentLine = 0;
+
+    if (lines.size() == 0)
+    {
+        return 0;
+    }
+    else if (lines.size() == 1)
+    {
+        // Just return a simple LineString
+        return lines[0].release();
+    }
+    else
+    {
+        // Return a multilinestring
+        MultiGeometry* multi = new MultiGeometry;
+        for (unsigned int i = 0; i < lines.size(); i++)
+        {
+            multi->add(lines[i].get());
+        }
+        return multi;
+    }
+}
+
+Geometry* decodePoint(const mapnik::vector::tile_feature& feature, const TileKey& key, unsigned int tileres)
+{
+    unsigned int length = 0;
+    int cmd = -1;
+    const int cmd_bits = 3;
+
+    int x = 0;
+    int y = 0;
+
+    osgEarth::Symbology::PointSet *geometry = new osgEarth::Symbology::PointSet();
+
+    for (int k = 0; k < feature.geometry_size();)
+    {
+        if (!length)
+        {
+            unsigned int cmd_length = feature.geometry(k++);
+            cmd = cmd_length & ((1 << cmd_bits) - 1);
+            length = cmd_length >> cmd_bits;
+        }
+        if (length > 0)
+        {
+            length--;
+            if (cmd == SEG_MOVETO || cmd == SEG_LINETO)
+            {
+                int px = feature.geometry(k++);
+                int py = feature.geometry(k++);
+                px = zig_zag_decode(px);
+                py = zig_zag_decode(py);
+
+                x += px;
+                y += py;
+
+                double width = key.getExtent().width();
+                double height = key.getExtent().height();
+
+                double geoX = key.getExtent().xMin() + (width/(double)tileres) * (double)x;
+                double geoY = key.getExtent().yMax() - (height/(double)tileres) * (double)y;
+                geometry->push_back(geoX, geoY, 0);
+            }
+        }
+    }
+
+    return geometry;
+}
+
+Geometry* decodePolygon(const mapnik::vector::tile_feature& feature, const  TileKey& key, unsigned int tileres)
+{
+    /*
+     https://github.com/mapbox/vector-tile-spec/tree/master/2.1
+     Decoding polygons is a bit more difficult than lines or points.
+     A Polygon geometry is either a single polygon or a multipolygon.  Each polygon has one exterior ring and zero or more interior rings.
+     The rings are in sequence and you must check the orientation of the ring to know if it's an exterior ring (new polygon) or an
+     interior ring (inner polygon of the current polygon).
+     */
+
+
+    unsigned int length = 0;
+    int cmd = -1;
+    const int cmd_bits = 3;
+
+    int x = 0;
+    int y = 0;
+
+    // The list of polygons we've collected
+    std::vector< osg::ref_ptr< osgEarth::Symbology::Polygon > > polygons;
+
+    osg::ref_ptr< osgEarth::Symbology::Polygon > currentPolygon;
+
+    osg::ref_ptr< osgEarth::Symbology::Ring > currentRing;
+
+    for (int k = 0; k < feature.geometry_size();)
+    {
+        if (!length)
+        {
+            unsigned int cmd_length = feature.geometry(k++);
+            cmd = cmd_length & ((1 << cmd_bits) - 1);
+            length = cmd_length >> cmd_bits;
+        }
+        if (length > 0)
+        {
+            length--;
+            if (cmd == SEG_MOVETO || cmd == SEG_LINETO)
+            {
+                if (!currentRing)
+                {
+                    currentRing = new osgEarth::Symbology::Ring();
+                }
+
+                int px = feature.geometry(k++);
+                int py = feature.geometry(k++);
+                px = zig_zag_decode(px);
+                py = zig_zag_decode(py);
+
+                x += px;
+                y += py;
+
+                double width = key.getExtent().width();
+                double height = key.getExtent().height();
+
+                double geoX = key.getExtent().xMin() + (width/(double)tileres) * (double)x;
+                double geoY = key.getExtent().yMax() - (height/(double)tileres) * (double)y;
+                currentRing->push_back(geoX, geoY, 0);
+            }
+            else if (cmd == (SEG_CLOSE & ((1 << cmd_bits) - 1)))
+            {
+                // The orientation is the opposite of what we want for features.  clockwise means exterior ring, counter clockwise means interior
+
+                // Figure out what to do with the ring based on the orientation of the ring
+                Geometry::Orientation orientation = currentRing->getOrientation();
+                // Close the ring.
+                currentRing->close();
+
+                // Clockwise means exterior ring.  Start a new polygon and add the ring.
+                if (orientation == Geometry::ORIENTATION_CW)
+                {
+                    // osgearth orientations are reversed from mvt
+                    currentRing->rewind(Geometry::ORIENTATION_CCW);
+
+                    currentPolygon = new osgEarth::Symbology::Polygon(&currentRing->asVector());
+                    polygons.push_back(currentPolygon.get());
+                }
+                else if (orientation == Geometry::ORIENTATION_CCW)
+                // Counter clockwise means a hole, add it to the existing polygon.
+                {
+                    if (currentPolygon.valid())
+                    {
+                        // osgearth orientations are reversed from mvt
+                        currentRing->rewind(Geometry::ORIENTATION_CW);
+                        currentPolygon->getHoles().push_back( currentRing );
+                    }
+                    else
+                    {
+                        // this means we encountered a "hole" without a parent outer ring,
+                        // discard for now -gw
+                        OE_INFO << LC << "Discarding improperly wound polygon (hole without an outer ring)\n";
+                    }
+                }
+
+                // Start a new ring
+                currentRing = 0;
+            }
+        }
+    }
+
+    currentRing = 0;
+    currentPolygon = 0;
+
+    if (polygons.size() == 0)
+    {
+        return 0;
+    }
+    else if (polygons.size() == 1)
+    {
+        // Just return a simple polygon
+        return polygons[0].release();
+    }
+    else
+    {
+        // Return a multipolygon
+        MultiGeometry* multi = new MultiGeometry;
+        for (unsigned int i = 0; i < polygons.size(); i++)
+        {
+            multi->add(polygons[i].get());
+        }
+        return multi;
+    }
+}
+
+#endif
+
 
 bool
     MVT::read(std::istream& in, const TileKey& key, FeatureList& features)
@@ -74,11 +324,12 @@ bool
     }
 
     // Decompress the tile
+    std::string original((std::istreambuf_iterator<char>(in)), std::istreambuf_iterator<char>());
+    in.seekg (0, std::ios::beg);
     std::string value;
-    if ( !compressor->decompress(in, value) )
+    if (!compressor->decompress(in, value))
     {
-        OE_WARN << "Decompression failed" << std::endl;
-        return false;
+        value = original;
     }
 
 
@@ -95,28 +346,8 @@ bool
             {
                 const mapnik::vector::tile_feature &feature = layer.features().Get(j);
 
-                osg::ref_ptr< osgEarth::Symbology::Geometry > geometry;
 
-                eGeomType geomType = static_cast<eGeomType>(feature.type());
-                if (geomType == ::Polygon)
-                {
-                    geometry = new osgEarth::Symbology::Polygon();
-                }
-                else if (geomType == ::LineString)
-                {
-                    geometry = new osgEarth::Symbology::LineString();
-                }
-                else if (geomType == ::Point)
-                {
-                    geometry = new osgEarth::Symbology::PointSet();
-                }
-                else
-                {
-                    geometry = new osgEarth::Symbology::LineString();
-                }
-
-                osg::ref_ptr< Feature > oeFeature = new Feature(geometry, key.getProfile()->getSRS());
-                features.push_back(oeFeature.get());
+                osg::ref_ptr< Feature > oeFeature = new Feature(0, key.getProfile()->getSRS());
 
                 // Set the layer name as "mvt_layer" so we can filter it later
                 oeFeature->set("mvt_layer", layer.name());
@@ -181,53 +412,31 @@ bool
                 }
 
 
-                unsigned int length = 0;
-                int cmd = -1;
-                const int cmd_bits = 3;
 
-                unsigned int tileres = layer.extent();
-
-                int x = 0;
-                int y = 0;
+                osg::ref_ptr< osgEarth::Symbology::Geometry > geometry;
 
-                for (int k = 0; k < feature.geometry_size();)
+                eGeomType geomType = static_cast<eGeomType>(feature.type());
+                if (geomType == ::Polygon)
                 {
-                    if (!length)
-                    {
-                        unsigned int cmd_length = feature.geometry(k++);
-                        cmd = cmd_length & ((1 << cmd_bits) - 1);
-                        length = cmd_length >> cmd_bits;
-                    }
-                    if (length > 0)
-                    {
-                        length--;
-                        if (cmd == SEG_MOVETO || cmd == SEG_LINETO)
-                        {
-                            int px = feature.geometry(k++);
-                            int py = feature.geometry(k++);
-                            px = zig_zag_decode(px);
-                            py = zig_zag_decode(py);
-
-                            x += px;
-                            y += py;
-
-                            double width = key.getExtent().width();
-                            double height = key.getExtent().height();
-
-                            double geoX = key.getExtent().xMin() + (width/(double)tileres) * (double)x;
-                            double geoY = key.getExtent().yMax() - (height/(double)tileres) * (double)y;
-                            geometry->push_back(geoX, geoY, 0);
-                        }
-                        else if (cmd == (SEG_CLOSE & ((1 << cmd_bits) - 1)))
-                        {
-                            geometry->push_back(geometry->front());
-                        }
-                    }
+                    geometry = decodePolygon(feature, key, layer.extent());
+                }
+                else if (geomType == ::LineString)
+                {
+                    geometry = decodeLine(feature, key, layer.extent());
+                }
+                else if (geomType == ::Point)
+                {
+                    geometry = decodePoint(feature, key, layer.extent());
+                }
+                else
+                {
+                    geometry = decodeLine(feature, key, layer.extent());
                 }
 
-                if (geometry->getType() == Geometry::TYPE_POLYGON)
+                if (geometry)
                 {
-                    geometry->rewind(osgEarth::Symbology::Geometry::ORIENTATION_CCW);
+                    oeFeature->setGeometry( geometry.get() );
+                    features.push_back(oeFeature.get());
                 }
             }
         }
diff --git a/src/osgEarthFeatures/OgrUtils.cpp b/src/osgEarthFeatures/OgrUtils.cpp
index b583fc5..7074350 100644
--- a/src/osgEarthFeatures/OgrUtils.cpp
+++ b/src/osgEarthFeatures/OgrUtils.cpp
@@ -25,6 +25,14 @@
 
 using namespace osgEarth::Features;
 
+#ifndef GDAL_VERSION_AT_LEAST
+#define GDAL_VERSION_AT_LEAST(MAJOR, MINOR, REV) ((GDAL_VERSION_MAJOR>MAJOR) || (GDAL_VERSION_MAJOR==MAJOR && (GDAL_VERSION_MINOR>MINOR || (GDAL_VERSION_MINOR==MINOR && GDAL_VERSION_REV>=REV))))
+#endif
+
+#if GDAL_VERSION_AT_LEAST(2,1,0)
+#  define GDAL_HAS_M_TYPES
+#endif
+
 
 void
 OgrUtils::populate( OGRGeometryH geomHandle, Symbology::Geometry* target, int numPoints )
@@ -85,48 +93,67 @@ OgrUtils::createGeometry( OGRGeometryH geomHandle )
 
     OGRwkbGeometryType wkbType = OGR_G_GetGeometryType( geomHandle );        
 
-    if (
-        wkbType == wkbPolygon ||
-        wkbType == wkbPolygon25D )
-    {
-        output = createPolygon( geomHandle );
-    }
-    else if (
-        wkbType == wkbLineString ||
-        wkbType == wkbLineString25D )
+    int numPoints, numGeoms;
+
+    switch (wkbType)
     {
-        int numPoints = OGR_G_GetPointCount( geomHandle );
+    case wkbPolygon:
+    case wkbPolygon25D:
+#ifdef GDAL_HAS_M_TYPES
+    case wkbPolygonM:
+    case wkbPolygonZM:
+#endif
+        output = createPolygon(geomHandle);
+        break;
+
+    case wkbLineString:
+    case wkbLineString25D:
+#ifdef GDAL_HAS_M_TYPES
+    case wkbLineStringM:
+    case wkbLineStringZM:
+#endif
+        numPoints = OGR_G_GetPointCount( geomHandle );
         output = new Symbology::LineString( numPoints );
         populate( geomHandle, output, numPoints );
-    }
-    else if (
-        wkbType == wkbLinearRing )
-    {
-        int numPoints = OGR_G_GetPointCount( geomHandle );
+        break;
+
+    case wkbLinearRing:
+        numPoints = OGR_G_GetPointCount( geomHandle );
         output = new Symbology::Ring( numPoints );
         populate( geomHandle, output, numPoints );
-    }
-    else if ( 
-        wkbType == wkbPoint ||
-        wkbType == wkbPoint25D )
-    {
-        int numPoints = OGR_G_GetPointCount( geomHandle );
+        break;
+
+    case wkbPoint:
+    case wkbPoint25D:
+#ifdef GDAL_HAS_M_TYPES
+    case wkbPointM:
+    case wkbPointZM:
+#endif
+        numPoints = OGR_G_GetPointCount( geomHandle );
         output = new Symbology::PointSet( numPoints );
         populate( geomHandle, output, numPoints );
-    }
-    else if (
-        wkbType == wkbGeometryCollection ||
-        wkbType == wkbGeometryCollection25D ||
-        wkbType == wkbMultiPoint ||
-        wkbType == wkbMultiPoint25D ||
-        wkbType == wkbMultiLineString ||
-        wkbType == wkbMultiLineString25D ||
-        wkbType == wkbMultiPolygon ||
-        wkbType == wkbMultiPolygon25D )
-    {
+        break;
+
+    case wkbGeometryCollection:
+    case wkbGeometryCollection25D:
+    case wkbMultiPoint:
+    case wkbMultiPoint25D:
+    case wkbMultiLineString:
+    case wkbMultiLineString25D:
+    case wkbMultiPolygon:
+    case wkbMultiPolygon25D:
+#ifdef GDAL_HAS_M_TYPES
+    case wkbGeometryCollectionM:
+    case wkbGeometryCollectionZM:
+    case wkbMultiPointM:
+    case wkbMultiPointZM:
+    case wkbMultiLineStringM:
+    case wkbMultiLineStringZM:
+    case wkbMultiPolygonM:
+    case wkbMultiPolygonZM:
+#endif
         Symbology::MultiGeometry* multi = new Symbology::MultiGeometry();
-
-        int numGeoms = OGR_G_GetGeometryCount( geomHandle );
+        numGeoms = OGR_G_GetGeometryCount( geomHandle );
         for( int n=0; n<numGeoms; n++ )
         {
             OGRGeometryH subGeomRef = OGR_G_GetGeometryRef( geomHandle, n );
@@ -136,8 +163,8 @@ OgrUtils::createGeometry( OGRGeometryH geomHandle )
                 if ( geom ) multi->getComponents().push_back( geom );
             }
         } 
-
         output = multi;
+        break;
     }
 
     return output;
diff --git a/src/osgEarthFeatures/OptimizerHints b/src/osgEarthFeatures/OptimizerHints
deleted file mode 100644
index cfb5602..0000000
--- a/src/osgEarthFeatures/OptimizerHints
+++ /dev/null
@@ -1,78 +0,0 @@
-/* -*-c++-*- */
-/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2016 Pelican Mapping
- * http://osgearth.org
- *
- * osgEarth is free software; you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>
- */
-
-#ifndef OSGEARTH_FEATURES_OPTIMIZER_HINTS_H
-#define OSGEARTH_FEATURES_OPTIMIZER_HINTS_H 1
-
-#include <osgEarthFeatures/Common>
-#include <osgUtil/Optimizer>
-
-namespace osgEarth { namespace Features
-{
-    /**
-     * A collection of hints that tells the osgUtil::Optimizer to expressly include or 
-     * exclude certain optimization options.
-     *
-     * Filters that do their own optimization may wish to instruct the general OSG
-     * optimizer to include or exclude certain optimization techniques. The FilterContext
-     * carries an OptimizerHints object that can be used for this purpose.
-     */
-    class OSGEARTHFEATURES_EXPORT OptimizerHints
-    {
-    public:
-        /**
-         * Constructs a empty hints object.
-         */
-        OptimizerHints();
-        
-        /**
-         * Copy constructor.
-         */
-        OptimizerHints( const OptimizerHints& rhs );
-
-        virtual ~OptimizerHints() { }
-
-        /**
-         * Adds optimizer options that the general optimizer should use.
-         */
-        void include( osgUtil::Optimizer::OptimizationOptions options );
-
-        /**
-         * Adds optimizer options that the general optimizer should NOT use.
-         */
-        void exclude( osgUtil::Optimizer::OptimizationOptions options );
-
-        /**
-         * Gets the mask of options that the optimzer should expressly include.
-         */
-        osgUtil::Optimizer::OptimizationOptions getIncludedOptions() const;
-
-        /**
-         * Gets the mask of options that the optimizer should expressly exclude.
-         */
-        osgUtil::Optimizer::OptimizationOptions getExcludedOptions() const;
-
-    private:
-        int included;
-        int excluded;
-    };
-
-} }
-
-#endif // OSGEARTH_FEATURES_OPTIMIZER_HINTS_H
diff --git a/src/osgEarthFeatures/OptimizerHints.cpp b/src/osgEarthFeatures/OptimizerHints.cpp
deleted file mode 100644
index cedcfff..0000000
--- a/src/osgEarthFeatures/OptimizerHints.cpp
+++ /dev/null
@@ -1,59 +0,0 @@
-/* -*-c++-*- */
-/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2016 Pelican Mapping
- * http://osgearth.org
- *
- * osgEarth is free software; you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>
- */
-
-#include <osgEarthFeatures/OptimizerHints>
-
-using namespace osgEarth::Features;
-
-
-OptimizerHints::OptimizerHints()
-{
-    included = 0;
-    excluded = 0;
-}
-
-OptimizerHints::OptimizerHints( const OptimizerHints& rhs )
-{
-    included = rhs.included;
-    excluded = rhs.excluded;
-}
-
-void
-OptimizerHints::include( osgUtil::Optimizer::OptimizationOptions value )
-{
-    included |= (int)value;
-}
-
-void
-OptimizerHints::exclude( osgUtil::Optimizer::OptimizationOptions value )
-{
-    excluded |= (int)value;
-}
-
-osgUtil::Optimizer::OptimizationOptions
-OptimizerHints::getIncludedOptions() const
-{
-    return (osgUtil::Optimizer::OptimizationOptions)included;
-}
-
-osgUtil::Optimizer::OptimizationOptions
-OptimizerHints::getExcludedOptions() const
-{
-    return (osgUtil::Optimizer::OptimizationOptions)excluded;
-}
diff --git a/src/osgEarthFeatures/PolygonizeLines b/src/osgEarthFeatures/PolygonizeLines
index 15fcb54..f8a48c0 100644
--- a/src/osgEarthFeatures/PolygonizeLines
+++ b/src/osgEarthFeatures/PolygonizeLines
@@ -40,6 +40,11 @@ namespace osgEarth { namespace Features
     class OSGEARTHFEATURES_EXPORT PolygonizeLinesOperator
     {
     public:
+        struct Callback {
+            virtual void operator()(unsigned i) = 0;
+        };
+
+    public:
         /**
          * Construct the operator
          * @param[in ] stroke Line rendering properties
@@ -54,11 +59,13 @@ namespace osgEarth { namespace Features
          * @param[in ] normals  Localized normals associated with the input verts.
          *                      Used to determine the plane in which to polygonize each
          *                      line segment. Optional; can be NULL
+         * @param[in ] callback Called for each new point added to the polygonized line
+         *                      with the source line index. Optional, call be NULL.
          * @param[in ] twosided Generate polygons on both sides of the center line.
          *
          * @return Triangulated geometry, including primitive set
          */
-        osg::Geometry* operator()(osg::Vec3Array* verts, osg::Vec3Array* normals, bool twosided =true) const;
+        osg::Geometry* operator()(osg::Vec3Array* verts, osg::Vec3Array* normals, Callback* callback =0L, bool twosided =true) const;
 
         /**
          * Installs an auto-scaling shader on a stateset.
diff --git a/src/osgEarthFeatures/PolygonizeLines.cpp b/src/osgEarthFeatures/PolygonizeLines.cpp
index ea897f8..892e64e 100644
--- a/src/osgEarthFeatures/PolygonizeLines.cpp
+++ b/src/osgEarthFeatures/PolygonizeLines.cpp
@@ -18,7 +18,7 @@
  */
 #include <osgEarthFeatures/PolygonizeLines>
 #include <osgEarthFeatures/FeatureSourceIndexNode>
-#include <osgEarthFeatures/Session>
+#include <osgEarthFeatures/FilterContext>
 #include <osgEarthSymbology/MeshConsolidator>
 #include <osgEarth/VirtualProgram>
 #include <osgEarth/Utils>
@@ -126,9 +126,10 @@ _stroke( stroke )
 
 
 osg::Geometry*
-PolygonizeLinesOperator::operator()(osg::Vec3Array* verts, 
-                                    osg::Vec3Array* normals,
-                                    bool            twosided) const
+PolygonizeLinesOperator::operator()(osg::Vec3Array*  verts, 
+                                    osg::Vec3Array*  normals,
+                                    Callback*        callback,
+                                    bool             twosided) const
 {
     // number of verts on the original line.
     unsigned lineSize = verts->size();
@@ -161,6 +162,13 @@ PolygonizeLinesOperator::operator()(osg::Vec3Array* verts,
     geom->setNormalArray( normals );
     geom->setNormalBinding( osg::Geometry::BIND_PER_VERTEX );
 
+    // run the callback on the initial spine.
+    if (callback)
+    {
+        for (unsigned i = 0; i<verts->size(); ++i)
+            (*callback)(i);
+    }
+
     // Set up the buffering vector attribute array.
     osg::Vec3Array* spine = 0L;
     if ( autoScale )
@@ -207,6 +215,7 @@ PolygonizeLinesOperator::operator()(osg::Vec3Array* verts,
     for( int ss=firstside; ss<=lastside; ++ss )
     {
         float side = ss == 0 ? RIGHT_SIDE : LEFT_SIDE;
+        float tx = side == RIGHT_SIDE? 1.0f : 0.0f;
 
         // iterate over each line segment.
         for( i=0; i<lineSize-1; ++i )
@@ -240,7 +249,8 @@ PolygonizeLinesOperator::operator()(osg::Vec3Array* verts,
 
                 // first tex coord:
                 // TODO: revisit. I believe we have them going x = [-1..1] instead of [0..1] -gw
-                tverts->push_back( osg::Vec2f(1.0*side, (*tverts)[i].y()) );
+                //tverts->push_back( osg::Vec2f(1.0*side, (*tverts)[i].y()) );
+                tverts->push_back( osg::Vec2f(tx, (*tverts)[i].y()) );
 
                 // first normal
                 normals->push_back( (*normals)[i] );
@@ -248,6 +258,8 @@ PolygonizeLinesOperator::operator()(osg::Vec3Array* verts,
                 // buffering vector.
                 if ( spine ) spine->push_back( (*verts)[i] );
 
+                if (callback) (*callback)(i);
+
                 // render the front end-cap.
                 if ( _stroke.lineCap() == Stroke::LINECAP_ROUND )
                 {
@@ -265,9 +277,10 @@ PolygonizeLinesOperator::operator()(osg::Vec3Array* verts,
 
                         verts->push_back( (*verts)[i] + v );
                         addTri( ebo, i, verts->size()-2, verts->size()-1, side );
-                        tverts->push_back( osg::Vec2f(1.0*side, (*tverts)[i].y()) );
+                        tverts->push_back( osg::Vec2f(tx, (*tverts)[i].y()) );
                         normals->push_back( (*normals)[i] );
                         if ( spine ) spine->push_back( (*verts)[i] );
+                        if (callback) (*callback)(i);
                     }
                 }
                 else if ( _stroke.lineCap() == Stroke::LINECAP_SQUARE )
@@ -276,15 +289,17 @@ PolygonizeLinesOperator::operator()(osg::Vec3Array* verts,
 
                     verts->push_back( verts->back() - dir*halfWidth );
                     addTri( ebo, i, verts->size()-2, verts->size()-1, side );
-                    tverts->push_back( osg::Vec2f(1.0*side, (*tverts)[i].y()) );
+                    tverts->push_back( osg::Vec2f(tx, (*tverts)[i].y()) );
                     normals->push_back( normals->back() );
                     if ( spine ) spine->push_back( (*verts)[i] );
+                    if (callback) (*callback)(i);
 
                     verts->push_back( (*verts)[i] - dir*halfWidth );
                     addTri( ebo, i, verts->size()-2, verts->size()-1, side );
-                    tverts->push_back( osg::Vec2f(1.0*side, (*tverts)[i].y()) );
+                    tverts->push_back( osg::Vec2f(tx, (*tverts)[i].y()) );
                     normals->push_back( (*normals)[i] );
                     if ( spine ) spine->push_back( (verts->back() - (*verts)[i]) * sqrt(2.0f) );
+                    if (callback) (*callback)(i);
                 }
             }
             else
@@ -336,10 +351,11 @@ PolygonizeLinesOperator::operator()(osg::Vec3Array* verts,
                     // for *previous* segment.
                     //if ( addedVertex )
                     addTris( ebo, i, prevBufVertPtr, verts->size()-1, side );
-                    tverts->push_back( osg::Vec2f(1.0*side, (*tverts)[i].y()) );
+                    tverts->push_back( osg::Vec2f(tx, (*tverts)[i].y()) );
                     normals->push_back( (*normals)[i] );
 
                     if ( spine ) spine->push_back( (*verts)[i] );
+                    if (callback) (*callback)(i);
                 }
 
                 else if ( _stroke.lineJoin() == Stroke::LINEJOIN_ROUND )
@@ -349,9 +365,10 @@ PolygonizeLinesOperator::operator()(osg::Vec3Array* verts,
 
                     verts->push_back( start );
                     addTris( ebo, i, prevBufVertPtr, verts->size()-1, side );
-                    tverts->push_back( osg::Vec2f(1.0*side, (*tverts)[i].y()) );
+                    tverts->push_back( osg::Vec2f(tx, (*tverts)[i].y()) );
                     normals->push_back( (*normals)[i] );
                     if ( spine ) spine->push_back( (*verts)[i] );
+                    if (callback) (*callback)(i);
 
                     // insert the edge-rounding points:
                     float angle = acosf( (prevBufVec * bufVec)/(halfWidth*halfWidth) );
@@ -367,10 +384,11 @@ PolygonizeLinesOperator::operator()(osg::Vec3Array* verts,
 
                         verts->push_back( (*verts)[i] + v );
                         addTri( ebo, i, verts->size()-1, verts->size()-2, side );
-                        tverts->push_back( osg::Vec2f(1.0*side, (*tverts)[i].y()) );
+                        tverts->push_back( osg::Vec2f(tx, (*tverts)[i].y()) );
                         normals->push_back( (*normals)[i] );
 
                         if ( spine ) spine->push_back( (*verts)[i] );
+                        if (callback) (*callback)(i);
                     }
                 }
 
@@ -387,9 +405,10 @@ PolygonizeLinesOperator::operator()(osg::Vec3Array* verts,
         // record the final point data.
         verts->push_back( (*verts)[i] + prevBufVec );
         addTris( ebo, i, prevBufVertPtr, verts->size()-1, side );
-        tverts->push_back( osg::Vec2f(1.0*side, (*tverts)[i].y()) );
+        tverts->push_back( osg::Vec2f(tx, (*tverts)[i].y()) );
         normals->push_back( (*normals)[i] );
         if ( spine ) spine->push_back( (*verts)[i] );
+        if (callback) (*callback)(i);
 
         if ( _stroke.lineCap() == Stroke::LINECAP_ROUND )
         {
@@ -407,9 +426,10 @@ PolygonizeLinesOperator::operator()(osg::Vec3Array* verts,
                 rotate( circlevec, (side)*a, up, v );
                 verts->push_back( (*verts)[i] + v );
                 addTri( ebo, i, verts->size()-1, verts->size()-2, side );
-                tverts->push_back( osg::Vec2f(1.0*side, (*tverts)[i].y()) );
+                tverts->push_back( osg::Vec2f(tx, (*tverts)[i].y()) );
                 normals->push_back( (*normals)[i] );
                 if ( spine ) spine->push_back( (*verts)[i] );
+                if (callback) (*callback)(i);
             }
         }
         else if ( _stroke.lineCap() == Stroke::LINECAP_SQUARE )
@@ -418,15 +438,17 @@ PolygonizeLinesOperator::operator()(osg::Vec3Array* verts,
 
             verts->push_back( verts->back() + prevDir*halfWidth );
             addTri( ebo, i, verts->size()-1, verts->size()-2, side );
-            tverts->push_back( osg::Vec2f(1.0*side, (*tverts)[i].y()) );
+            tverts->push_back( osg::Vec2f(tx, (*tverts)[i].y()) );
             normals->push_back( normals->back() );
             if ( spine ) spine->push_back( (*verts)[i] );
+            if (callback) (*callback)(i);
 
             verts->push_back( (*verts)[i] + prevDir*halfWidth );
             addTri( ebo, i, verts->size()-1, verts->size()-2, side );
             tverts->push_back( osg::Vec2f(1.0*side, (*tverts)[i].y()) );
             normals->push_back( (*normals)[i] );
             if ( spine ) spine->push_back( (*verts)[i] );
+            if (callback) (*callback)(i);
         }
     }
 
@@ -448,15 +470,6 @@ PolygonizeLinesOperator::operator()(osg::Vec3Array* verts,
         geom->setColorArray( colors );
         geom->setColorBinding( osg::Geometry::BIND_PER_VERTEX );
     }
-     
-#if 0
-    //TESTING
-    osg::Image* image = osgDB::readImageFile("E:/data/textures/road.jpg");
-    osg::Texture2D* tex = new osg::Texture2D(image);
-    tex->setWrap( osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE );
-    tex->setWrap( osg::Texture::WRAP_T, osg::Texture::REPEAT );
-    geom->getOrCreateStateSet()->setTextureAttributeAndModes(0, tex, 1);
-#endif
 
     return geom;
 }
@@ -542,7 +555,7 @@ PolygonizeLinesOperator::installShaders(osg::Node* node) const
     const char* vs =
         "#version " GLSL_VERSION_STR "\n"
         GLSL_DEFAULT_PRECISION_FLOAT "\n"
-        "attribute vec3 oe_polyline_center; \n"
+        "in vec3 oe_polyline_center; \n"
         "uniform float oe_polyline_scale;  \n"
         "uniform float oe_polyline_min_pixels; \n"
         "uniform vec4 oe_PixelSizeVector; \n"
diff --git a/src/osgEarthFeatures/ResampleFilter b/src/osgEarthFeatures/ResampleFilter
index 0d67c19..5e05f25 100644
--- a/src/osgEarthFeatures/ResampleFilter
+++ b/src/osgEarthFeatures/ResampleFilter
@@ -65,6 +65,7 @@ namespace osgEarth { namespace Features
 
         Config getConfig() const {
             Config conf = ConfigOptions::getConfig();
+            conf.key() = "resample";
             conf.addIfSet("min_length", _minLen);
             conf.addIfSet("max_length", _maxLen);
             conf.addIfSet("mode", "linear",       _mode, RESAMPLE_LINEAR);
diff --git a/src/osgEarthFeatures/ResampleFilter.cpp b/src/osgEarthFeatures/ResampleFilter.cpp
index 722789d..3661eb3 100644
--- a/src/osgEarthFeatures/ResampleFilter.cpp
+++ b/src/osgEarthFeatures/ResampleFilter.cpp
@@ -17,10 +17,10 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>
  */
 #include <osgEarthFeatures/ResampleFilter>
+#include <osgEarthFeatures/FilterContext>
 #include <osgEarth/GeoMath>
 #include <osg/io_utils>
 #include <list>
-#include <deque>
 #include <cstdlib>
 
 using namespace osgEarth;
diff --git a/src/osgEarthFeatures/ScaleFilter.cpp b/src/osgEarthFeatures/ScaleFilter.cpp
index 7b35859..707013e 100644
--- a/src/osgEarthFeatures/ScaleFilter.cpp
+++ b/src/osgEarthFeatures/ScaleFilter.cpp
@@ -17,6 +17,7 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>
  */
 #include <osgEarthFeatures/ScaleFilter>
+#include <osgEarthFeatures/FilterContext>
 #include <osgEarth/GeoData>
 
 using namespace osgEarth;
diff --git a/src/osgEarthFeatures/ScatterFilter.cpp b/src/osgEarthFeatures/ScatterFilter.cpp
index d5a42ea..8848614 100644
--- a/src/osgEarthFeatures/ScatterFilter.cpp
+++ b/src/osgEarthFeatures/ScatterFilter.cpp
@@ -17,6 +17,7 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>
  */
 #include <osgEarthFeatures/ScatterFilter>
+#include <osgEarthFeatures/FilterContext>
 #include <osgEarth/GeoMath>
 #include <stdlib.h>
 
diff --git a/src/osgEarthFeatures/ScriptEngine.cpp b/src/osgEarthFeatures/ScriptEngine.cpp
index da81895..a1ad87d 100644
--- a/src/osgEarthFeatures/ScriptEngine.cpp
+++ b/src/osgEarthFeatures/ScriptEngine.cpp
@@ -122,7 +122,7 @@ ScriptEngineFactory::createWithProfile( const Script& script, const std::string&
 ScriptEngine*
 ScriptEngineFactory::create( const ScriptEngineOptions& options, bool quiet)
 {
-    ScriptEngine* scriptEngine = 0L;
+    osg::ref_ptr<ScriptEngine> scriptEngine;
 
     if ( !options.getDriver().empty() )
     {
@@ -133,7 +133,8 @@ ScriptEngineFactory::create( const ScriptEngineOptions& options, bool quiet)
             osg::ref_ptr<osgDB::Options> rwopts = Registry::instance()->cloneOrCreateOptions();
             rwopts->setPluginData( SCRIPT_ENGINE_OPTIONS_TAG, (void*)&options );
 
-            scriptEngine = dynamic_cast<ScriptEngine*>( osgDB::readObjectFile( driverExt, rwopts.get() ) );
+            osg::ref_ptr<osg::Object> object = osgDB::readRefObjectFile( driverExt, rwopts.get() );
+            scriptEngine = dynamic_cast<ScriptEngine*>( object.release() );
             if ( scriptEngine )
             {
                 OE_DEBUG << "Loaded ScriptEngine driver \"" << options.getDriver() << "\" OK" << std::endl;
@@ -157,7 +158,7 @@ ScriptEngineFactory::create( const ScriptEngineOptions& options, bool quiet)
             OE_WARN << LC << "FAIL, illegal null driver specification" << std::endl;
     }
 
-    return scriptEngine;
+    return scriptEngine.release();
 }
 
 //------------------------------------------------------------------------
diff --git a/src/osgEarthFeatures/ScriptFilter.cpp b/src/osgEarthFeatures/ScriptFilter.cpp
index f6bf233..90afa28 100644
--- a/src/osgEarthFeatures/ScriptFilter.cpp
+++ b/src/osgEarthFeatures/ScriptFilter.cpp
@@ -17,6 +17,7 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>
  */
 #include <osgEarthFeatures/ScriptFilter>
+#include <osgEarthFeatures/FilterContext>
 
 using namespace osgEarth;
 using namespace osgEarth::Features;
diff --git a/src/osgEarthFeatures/Session b/src/osgEarthFeatures/Session
index e008c5c..b59a1eb 100644
--- a/src/osgEarthFeatures/Session
+++ b/src/osgEarthFeatures/Session
@@ -21,22 +21,28 @@
 #define OSGEARTH_FEATURES_SESSION_H 1
 
 #include <osgEarthFeatures/Common>
-#include <osgEarthFeatures/ScriptEngine>
-#include <osgEarthSymbology/ResourceCache>
-#include <osgEarthSymbology/StyleSheet>
-#include <osgEarth/StateSetCache>
-#include <osgEarth/ThreadingUtils>
+
 #include <osgEarth/MapInfo>
 #include <osgEarth/MapFrame>
 #include <osgEarth/Map>
-
+#include <osgEarth/URI>
+
+namespace osgEarth {
+    class StateSetCache;
+    namespace Symbology {
+        class ResourceCache;
+        class StyleSheet;
+    }
+    namespace Features {
+        class FeatureSource;
+        class ScriptEngine;
+    }
+}
 namespace osgEarth { namespace Features
 {
     using namespace osgEarth;
     using namespace osgEarth::Symbology;
 
-    class FeatureSource;
-
     /**
      * Session is a state object that exists throughout the life of one or more related
      * feature compilations.
@@ -48,15 +54,22 @@ namespace osgEarth { namespace Features
      * exists one level above this and governs any number of "related" compilations
      * (e.g., the compilation of many grid cells comprising a single feature layer).
      *
-     * Session implements the URIResolver interface to resolve relative URIs.
+     * Note: A Session holds a ref_ptr to a Map, so that it can yield MapFrame objects
+     * to be used and then discarded. Don't hold on to a Session reference indefinitely;
+     * use the Session and destroy it after compilation.
      */
-    class OSGEARTHFEATURES_EXPORT Session : public osg::Referenced
+    class OSGEARTHFEATURES_EXPORT Session : public osg::Object
     {
     public:
-        /**
-         * Constructs a new Session that is tied to a map
-         */
-        Session( const Map* map, StyleSheet* styles =0L, FeatureSource* source =0L, const osgDB::Options* dbOptions =0L );
+        META_Object(osgEarthFeatures, Session);
+
+        //! New session
+        Session(const Map* map);
+
+        Session(const Map* map, StyleSheet* styles);
+        
+        Session(const Map* map, StyleSheet* styles, FeatureSource* source, const osgDB::Options* dbOptions);
+
         virtual ~Session();
 
         /**
@@ -68,7 +81,7 @@ namespace osgEarth { namespace Features
         /**
          * Gets the underlying map (frame) interface in this session
          */
-        MapFrame createMapFrame( Map::ModelParts parts =Map::TERRAIN_LAYERS ) const;
+        MapFrame createMapFrame() const;
 
         /**
          * Gets the map information backing up this session.
@@ -78,15 +91,16 @@ namespace osgEarth { namespace Features
         /** Gets the SRS of the map behind this session */
         const SpatialReference* getMapSRS() const { return _mapInfo.getSRS(); }
 
-        /**
-         * Gets the map related to this session. Be carefull, this is held by an osg::observer_ptr and may be NULL
-         */
-        const Map* getMap() const { return _map.get(); }
-
         /** The style sheet governing this session. */
         void setStyles( StyleSheet* value );
         StyleSheet* styles() const { return _styles.get(); }
 
+        /**
+         * Set the feature source to draw upon in this Session. 
+         * Do no call this after the Session is already in use.
+         */
+        void setFeatureSource(FeatureSource*);
+        
         /** Gets the current feature source */
         FeatureSource* getFeatureSource() const;
 
@@ -102,80 +116,20 @@ namespace osgEarth { namespace Features
         const std::string& getName() const { return _name; }
 
     public:
-        template<typename T>
-        struct CreateFunctor {
-            virtual T* operator()() const =0;
-        };
-
-        /**
-         * Stores an object in the shared Session cache.
-         *
-         * WARNING! Don't store things like nodes in here unless you plan
-         * to clone them. This is a multi-threaded store.
-         *
-         * Returns the object written, OR the already-existing object if overwrite = false
-         * and the key was already taken.
-         */
-        template<typename T>
-        T* putObject( const std::string& key, T* object, bool overwrite =true ) {
-            Threading::ScopedMutexLock lock( _objMapMutex );
-            ObjectMap::iterator i = _objMap.find(key);
-            if ( i != _objMap.end() && !overwrite )
-                return dynamic_cast<T*>(i->second.get());
-            _objMap[key] = object;
-            return object;
-        }
-
-        /**
-         * Gets an object from the shared Session cache.
-         * (returns a ref_ptr so as not to lose its ref in a multi-threaded app)
-         */
-        template<typename T>
-        osg::ref_ptr<T> getObject( const std::string& key ) {
-            Threading::ScopedMutexLock lock( _objMapMutex );
-            ObjectMap::const_iterator i = _objMap.find(key);
-            return i != _objMap.end() ? dynamic_cast<T*>( i->second.get() ) : 0L;
-        }
-
-        template<typename T>
-        bool getOrCreateObject(const std::string& key, osg::ref_ptr<T>& output, const CreateFunctor<T>& create) {
-            Threading::ScopedMutexLock lock( _objMapMutex );
-            ObjectMap::const_iterator i = _objMap.find(key);
-            if ( i != _objMap.end() ) {
-                output = dynamic_cast<T*>( i->second.get() );
-                return true;
-            }
-            else {
-                T* object = create();
-                if ( object ) {
-                    _objMap[key] = object;
-                    output = object;
-                    return true;
-                }
-                else {
-                    return false;
-                }
-            }
-        }
-
-        void removeObject( const std::string& key );
-
-    public:
         /**
          * The cache for optimizing stateset sharing within a session
          */
-        StateSetCache* getStateSetCache() { return _stateSetCache.get(); }
+        StateSetCache* getStateSetCache();
 
     public:
       ScriptEngine* getScriptEngine() const;
 
     private:
-        typedef std::map<std::string, osg::ref_ptr<osg::Referenced> > ObjectMap;
-        ObjectMap                    _objMap;
-        Threading::Mutex             _objMapMutex;
+        void init();
+        void initScriptEngine();
 
-        URIContext                         _uriContext;
-        osg::observer_ptr<const Map>       _map;
+        URIContext                         _uriContext;         
+        osg::ref_ptr<const Map>            _map;
         MapInfo                            _mapInfo;
         osg::ref_ptr<StyleSheet>           _styles;
         osg::ref_ptr<const osgDB::Options> _dbOptions;
@@ -184,6 +138,10 @@ namespace osgEarth { namespace Features
         osg::ref_ptr<StateSetCache>        _stateSetCache;
         osg::ref_ptr<ResourceCache>        _resourceCache;
         std::string                        _name;
+
+        // hidden - support for META_Object
+        Session();
+        Session(const Session& rhs, const osg::CopyOp& op = osg::CopyOp::SHALLOW_COPY);
     };
 
 } }
diff --git a/src/osgEarthFeatures/Session.cpp b/src/osgEarthFeatures/Session.cpp
index 913a893..8171fa6 100644
--- a/src/osgEarthFeatures/Session.cpp
+++ b/src/osgEarthFeatures/Session.cpp
@@ -21,13 +21,13 @@
 #include <osgEarthFeatures/Script>
 #include <osgEarthFeatures/ScriptEngine>
 #include <osgEarthFeatures/FeatureSource>
+
 #include <osgEarthSymbology/ResourceCache>
+#include <osgEarthSymbology/StyleSheet>
+
 #include <osgEarth/FileUtils>
 #include <osgEarth/StringUtils>
 #include <osgEarth/Registry>
-#include <osg/AutoTransform>
-#include <osg/Depth>
-#include <osg/TextureRectangle>
 
 #define LC "[Session] "
 
@@ -36,21 +36,46 @@ using namespace osgEarth::Features;
 
 //---------------------------------------------------------------------------
 
-Session::Session( const Map* map, StyleSheet* styles, FeatureSource* source, const osgDB::Options* dbOptions ) :
-osg::Referenced( true ),
-_map           ( map ),
-_mapInfo       ( map ),
-_featureSource ( source ),
-_dbOptions     ( dbOptions )
+Session::Session() :
+osg::Object(),
+_map(0L),
+_mapInfo(0L)
+{
+    init();
+}
+
+Session::Session(const Map* map) :
+osg::Object(),
+_map(map),
+_mapInfo(map)
+{
+    init();
+}
+
+Session::Session(const Map* map, StyleSheet* styles) :
+osg::Object(),
+_map(map),
+_mapInfo(map),
+_styles(styles)
 {
-    if ( styles )
-        setStyles( styles );
-    else
-        _styles = new StyleSheet();
+    init();
+}
 
-    // if the caller did not provide a dbOptions, take it from the map.
-    if ( map && !dbOptions )
-        _dbOptions = map->getReadOptions();
+Session::Session(const Map* map, StyleSheet* styles, FeatureSource* source, const osgDB::Options* dbOptions) :
+osg::Object(),
+_map(map),
+_mapInfo(map),
+_styles(styles),
+_featureSource(source),
+_dbOptions(dbOptions)
+{
+    init();
+}
+
+void
+Session::init()
+{
+    setStyles(_styles.get());
 
     // A new cache to optimize state changes. Since the cache lives in the Session, any
     // geometry created under this session takes advantage of it. That's reasonable since
@@ -65,10 +90,24 @@ Session::~Session()
     //nop
 }
 
+Session::Session(const Session& rhs, const osg::CopyOp& op) : osg::Object(rhs, op), _mapInfo(0L) { }
+
+
 const osgDB::Options*
 Session::getDBOptions() const
 {
-    return _dbOptions.get();
+    // local options if they were set:
+    if (_dbOptions.valid())
+        return _dbOptions.get();
+
+    // otherwise get them from the map if possible:
+    //osg::ref_ptr<const Map> map;
+    //if (_map.lock(map))
+    //    return map->getReadOptions();
+
+    return _map->getReadOptions();
+
+    return 0L;
 }
 
 void
@@ -84,22 +123,27 @@ Session::getResourceCache()
 }
 
 MapFrame
-Session::createMapFrame( Map::ModelParts parts ) const
+Session::createMapFrame() const
 {
-    return MapFrame( _map.get(), parts );
+    return MapFrame( _map.get() );
 }
 
-void
-Session::removeObject( const std::string& key )
+StateSetCache*
+Session::getStateSetCache()
 {
-    Threading::ScopedMutexLock lock( _objMapMutex );
-    _objMap.erase( key );
+    return _stateSetCache.get();
 }
 
 void
 Session::setStyles( StyleSheet* value )
 {
     _styles = value ? value : new StyleSheet();
+    initScriptEngine();
+}
+
+void
+Session::initScriptEngine()
+{
     _styleScriptEngine = 0L;
 
     // Create a script engine for the StyleSheet
@@ -130,6 +174,12 @@ Session::getScriptEngine() const
     return _styleScriptEngine.get();
 }
 
+void
+Session::setFeatureSource(FeatureSource* fs)
+{
+    _featureSource = fs;
+}
+
 FeatureSource*
 Session::getFeatureSource() const 
 { 
diff --git a/src/osgEarth/Shaders b/src/osgEarthFeatures/Shaders
similarity index 70%
copy from src/osgEarth/Shaders
copy to src/osgEarthFeatures/Shaders
index 01a4f13..bea6dff 100644
--- a/src/osgEarth/Shaders
+++ b/src/osgEarthFeatures/Shaders
@@ -19,25 +19,22 @@
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
 */
-#ifndef OSGEARTH_SHADERS
-#define OSGEARTH_SHADERS 1
+#ifndef OSGEARTHFEATURES_SHADERS
+#define OSGEARTHFEATURES_SHADERS 1
 
+#include <osgEarthFeatures/Common>
 #include <osgEarth/ShaderLoader>
 
-namespace osgEarth
+namespace osgEarth { namespace Features
 {
-    class OSGEARTH_EXPORT Shaders : public ShaderPackage
+    class OSGEARTHFEATURES_EXPORT GPULineShaders : public ShaderPackage
 	{
     public:
-        Shaders();
+        GPULineShaders();
 
-        std::string AlphaEffectFragment;
-        std::string DepthOffsetVertex;
-        std::string DrapingVertex, DrapingFragment;
-        std::string GPUClampingVertex, GPUClampingFragment, GPUClampingVertexLib;
-        std::string InstancingVertex;
+        std::string GPULinesScreenProj;
 	};	
 
-} // namespace osgEarth
+} } // namespace osgEarth::Features
 
-#endif // OSGEARTH_SHADERS
+#endif // OSGEARTHFEATURES_SHADERS
diff --git a/src/osgEarthFeatures/Shaders.cpp.in b/src/osgEarthFeatures/Shaders.cpp.in
new file mode 100644
index 0000000..dd44d95
--- /dev/null
+++ b/src/osgEarthFeatures/Shaders.cpp.in
@@ -0,0 +1,13 @@
+// ***DO NOT EDIT THIS FILE - IT IS AUTOMATICALLY GENERATED BY CMAKE***
+
+#include <osgEarthFeatures/Shaders>
+
+namespace osgEarth { namespace Features
+{
+    GPULineShaders::GPULineShaders()
+    {
+        // GPU Screen Projected Lines
+        GPULinesScreenProj = "GPULinesScreenProj.glsl";
+        _sources[GPULinesScreenProj] = "@GPULinesScreenProj.glsl@";
+    }
+} }
diff --git a/src/osgEarthFeatures/SubstituteModelFilter b/src/osgEarthFeatures/SubstituteModelFilter
index 5c213ea..6637be9 100644
--- a/src/osgEarthFeatures/SubstituteModelFilter
+++ b/src/osgEarthFeatures/SubstituteModelFilter
@@ -27,6 +27,12 @@
 #include <osgEarthSymbology/MarkerResource>
 #include <osgEarth/Containers>
 
+namespace osgEarth {
+    namespace Symbology {
+        class ResourceLibrary;
+    }
+}
+
 namespace osgEarth { namespace Features
 {
     using namespace osgEarth;
diff --git a/src/osgEarthFeatures/SubstituteModelFilter.cpp b/src/osgEarthFeatures/SubstituteModelFilter.cpp
index ac9e47a..4cb3a0e 100644
--- a/src/osgEarthFeatures/SubstituteModelFilter.cpp
+++ b/src/osgEarthFeatures/SubstituteModelFilter.cpp
@@ -18,24 +18,26 @@
  */
 #include <osgEarthFeatures/SubstituteModelFilter>
 #include <osgEarthFeatures/FeatureSourceIndexNode>
-#include <osgEarthFeatures/Session>
+#include <osgEarthFeatures/FilterContext>
 #include <osgEarthFeatures/GeometryUtils>
+
 #include <osgEarthSymbology/MeshConsolidator>
 #include <osgEarthSymbology/MeshFlattener>
+#include <osgEarthSymbology/StyleSheet>
+
 #include <osgEarth/ECEF>
 #include <osgEarth/VirtualProgram>
 #include <osgEarth/DrawInstanced>
 #include <osgEarth/Capabilities>
 #include <osgEarth/ScreenSpaceLayout>
 #include <osgEarth/CullingUtils>
+#include <osgEarth/NodeUtils>
 
 #include <osg/AutoTransform>
 #include <osg/Drawable>
 #include <osg/Geode>
 #include <osg/MatrixTransform>
 #include <osg/NodeVisitor>
-#include <osg/ShapeDrawable>
-#include <osg/AlphaFunc>
 #include <osg/Billboard>
 
 #define LC "[SubstituteModelFilter] "
@@ -383,7 +385,7 @@ namespace
         osg::ref_ptr< osg::Node > clone = (osg::Node*)node->clone(osg::CopyOp::DEEP_COPY_NODES);
        
         // Now remove any geodes
-        FindNodesVisitor<osg::Geode> findGeodes;
+        osgEarth::FindNodesVisitor<osg::Geode> findGeodes;
         clone->accept(findGeodes);
         for (unsigned int i = 0; i < findGeodes._results.size(); i++)
         {
@@ -478,14 +480,14 @@ SubstituteModelFilter::push(FeatureList& features, FilterContext& context)
     // Process the feature set, using clustering if requested
     bool ok = true;
 
-    process( features, symbol, context.getSession(), attachPoint.get(), newContext );
+    process( features, symbol.get(), context.getSession(), attachPoint.get(), newContext );
     if (_cluster)
     {
         // Extract the unclusterable things
-        osg::ref_ptr< osg::Node > unclusterables = extractUnclusterables(attachPoint);
+        osg::ref_ptr< osg::Node > unclusterables = extractUnclusterables(attachPoint.get());
 
         // We run on the attachPoint instead of the main group so that we don't lose the double precision declocalizer transform.
-        MeshFlattener::run(attachPoint);
+        MeshFlattener::run(attachPoint.get());
 
         // Add the unclusterables back to the attach point after the rest of the graph was flattened.
         if (unclusterables.valid())
diff --git a/src/osgEarthFeatures/TessellateOperator b/src/osgEarthFeatures/TessellateOperator
index 695fb7b..d58d6ba 100644
--- a/src/osgEarthFeatures/TessellateOperator
+++ b/src/osgEarthFeatures/TessellateOperator
@@ -82,6 +82,8 @@ namespace osgEarth { namespace Features
          */
         void operator()( Feature* feature, FilterContext& context ) const;
 
+        FilterContext push(FeatureList& input, FilterContext& context) const;
+
     protected:
         optional<Distance> _maxDistance;
         unsigned           _numPartitions;
diff --git a/src/osgEarthFeatures/TessellateOperator.cpp b/src/osgEarthFeatures/TessellateOperator.cpp
index c51ed39..864c189 100644
--- a/src/osgEarthFeatures/TessellateOperator.cpp
+++ b/src/osgEarthFeatures/TessellateOperator.cpp
@@ -101,8 +101,8 @@ TessellateOperator::operator()( Feature* feature, FilterContext& context ) const
     }
 
     Units featureUnits = feature->getSRS() ? feature->getSRS()->getUnits() : Units::METERS;
-    bool isGeo = feature->getSRS() ? feature->getSRS()->isGeographic() : true;
-    GeoInterpolation interp = feature->geoInterp().isSet() ? *feature->geoInterp() : _defaultInterp;
+    bool isGeo = feature->getSRS() ? feature->getSRS()->isGeographic() : false;
+    GeoInterpolation geoInterp = feature->geoInterp().isSet() ? *feature->geoInterp() : _defaultInterp;
 
     double sliceSize = 0.0;
     int    numPartitions = _numPartitions;
@@ -112,6 +112,7 @@ TessellateOperator::operator()( Feature* feature, FilterContext& context ) const
         // copmpute the slice size in feature units.
         double latitude = feature->getGeometry()->getBounds().center().y();
         sliceSize = SpatialReference::transformUnits( _maxDistance.value(), feature->getSRS(), latitude );
+        sliceSize = _maxDistance->as(Units::METERS);
     }
 
     GeometryIterator i( feature->getGeometry(), true );
@@ -131,10 +132,13 @@ TessellateOperator::operator()( Feature* feature, FilterContext& context ) const
             {
                 // calculate slice count
                 if ( sliceSize > 0.0 )
-                    slices = std::max( 1u, (unsigned)((*v - *(v+1)).length() / sliceSize) );
+                {
+                    double dist = GeoMath::distance(*v, *(v + 1), feature->getSRS());
+                    slices = std::max( 1u, (unsigned)(dist / sliceSize) );
+                }
 
                 if ( isGeo )
-                    tessellateGeo( *v, *(v+1), slices, interp, newVerts );
+                    tessellateGeo( *v, *(v+1), slices, geoInterp, newVerts );
                 else
                     tessellateLinear( *v, *(v+1), slices, newVerts );
             }
@@ -142,10 +146,13 @@ TessellateOperator::operator()( Feature* feature, FilterContext& context ) const
             {
                 // calculate slice count
                 if ( sliceSize > 0.0 )
-                    slices = std::max( 1u, (unsigned)((*v - *g->begin()).length() / sliceSize) );
+                {
+                    double dist = GeoMath::distance(*v, *g->begin(), feature->getSRS());
+                    slices = std::max( 1u, (unsigned)(dist / sliceSize) );
+                }
 
                 if ( isGeo )
-                    tessellateGeo( *v, *g->begin(), slices, interp, newVerts );
+                    tessellateGeo( *v, *g->begin(), slices, geoInterp, newVerts );
                 else
                     tessellateLinear( *v, *g->begin(), slices, newVerts );
             }
@@ -159,3 +166,12 @@ TessellateOperator::operator()( Feature* feature, FilterContext& context ) const
         g->swap( newVerts );
     }
 }
+
+FilterContext
+TessellateOperator::push(FeatureList& input, FilterContext& context) const
+{
+    for (FeatureList::iterator i = input.begin(); i != input.end(); ++i) {
+        operator()(i->get(), context);
+    }
+    return context;
+}
\ No newline at end of file
diff --git a/src/osgEarthFeatures/TextSymbolizer.cpp b/src/osgEarthFeatures/TextSymbolizer.cpp
index ce7b392..6c4520a 100644
--- a/src/osgEarthFeatures/TextSymbolizer.cpp
+++ b/src/osgEarthFeatures/TextSymbolizer.cpp
@@ -81,19 +81,28 @@ TextSymbolizer::create(Feature*             feature,
 
     t->setColor( _symbol.valid() && _symbol->fill().isSet() ? _symbol->fill()->color() : Color::White );
 
-    osgText::Font* font = 0L;
+    osg::ref_ptr<osgText::Font> font;
     if ( _symbol.valid() && _symbol->font().isSet() )
     {
-        font = osgText::readFontFile( *_symbol->font() );
+        font = osgText::readRefFontFile( *_symbol->font() );
+        
+#if OSG_VERSION_LESS_THAN(3,5,8)
         // mitigates mipmapping issues that cause rendering artifacts for some fonts/placement
         if ( font )
             font->setGlyphImageMargin( 2 );
+#endif
     }
     if ( !font )
         font = Registry::instance()->getDefaultFont();
+
     if ( font )
+    {
         t->setFont( font );
 
+        // OSG 3.4.1+ adds a program, so we remove it since we're using VPs.
+        t->setStateSet(0L);
+    }
+
     if ( _symbol.valid() )
     {
         // they're the same enum.
diff --git a/src/osgEarthFeatures/TransformFilter.cpp b/src/osgEarthFeatures/TransformFilter.cpp
index 9c26a38..dc939aa 100644
--- a/src/osgEarthFeatures/TransformFilter.cpp
+++ b/src/osgEarthFeatures/TransformFilter.cpp
@@ -17,6 +17,8 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>
  */
 #include <osgEarthFeatures/TransformFilter>
+#include <osgEarthFeatures/Feature>
+#include <osgEarthFeatures/FilterContext>
 #include <osg/ClusterCullingCallback>
 
 #define LC "[TransformFilter] "
diff --git a/src/osgEarthFeatures/VirtualFeatureSource b/src/osgEarthFeatures/VirtualFeatureSource
index 96b1852..21b4dcb 100644
--- a/src/osgEarthFeatures/VirtualFeatureSource
+++ b/src/osgEarthFeatures/VirtualFeatureSource
@@ -43,7 +43,7 @@ namespace osgEarth { namespace Features
     // internal class
     struct FeatureSourceMapping
     {
-        FeatureSourceMapping(FeatureSource* fs, FeaturePredicate* fp) : _source(fs), _predicate(fp) { }
+        FeatureSourceMapping(FeatureSource* fs, FeaturePredicate* fp);
         osg::ref_ptr<FeatureSource>      _source;
         osg::ref_ptr<FeaturePredicate>   _predicate;
     };
@@ -54,9 +54,7 @@ namespace osgEarth { namespace Features
     {
     public:
         /** Construct a new virtual feature source */
-        VirtualFeatureSource() { }
-
-        virtual ~VirtualFeatureSource() { }
+        VirtualFeatureSource();
 
         /**
          * Adds a feature source/predicate mapping. This FeatureSource will draw
@@ -70,6 +68,8 @@ namespace osgEarth { namespace Features
         virtual Feature* getFeature( FeatureID id ) { return 0L; }
 
     protected:
+        virtual ~VirtualFeatureSource();
+
         virtual Status initialize(const osgDB::Options* readOptions);
 
     protected:
diff --git a/src/osgEarthFeatures/VirtualFeatureSource.cpp b/src/osgEarthFeatures/VirtualFeatureSource.cpp
index fd71596..49914ef 100644
--- a/src/osgEarthFeatures/VirtualFeatureSource.cpp
+++ b/src/osgEarthFeatures/VirtualFeatureSource.cpp
@@ -17,6 +17,8 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>
  */
 #include <osgEarthFeatures/VirtualFeatureSource>
+#include <osgEarthFeatures/FeatureCursor>
+#include <osgEarthFeatures/Filter>
 
 #define LC "[VirtualFeatureSource] "
 
@@ -105,6 +107,25 @@ namespace
 
 //------------------------------------------------------------------------
 
+FeatureSourceMapping::FeatureSourceMapping(FeatureSource* fs, FeaturePredicate* fp) :
+_source(fs), _predicate(fp)
+{
+    //nop
+}
+
+//------------------------------------------------------------------------
+
+VirtualFeatureSource::VirtualFeatureSource() :
+FeatureSource()
+{
+    //nop
+}
+
+VirtualFeatureSource::~VirtualFeatureSource()
+{
+    //nop
+}
+
 void
 VirtualFeatureSource::add( FeatureSource* source, FeaturePredicate* predicate )
 {
@@ -124,14 +145,11 @@ VirtualFeatureSource::createFeatureCursor( const Query& query )
 Status 
 VirtualFeatureSource::initialize( const osgDB::Options* readOptions )
 {
-    //FeatureSource::initialize( dbOptions );
-
     for( FeatureSourceMappingVector::iterator i = _sources.begin(); i != _sources.end(); ++i )
     {
         const Status& sourceStatus = i->_source->open(readOptions);
         if (sourceStatus.isError())
             return sourceStatus;
-        //i->_source->initialize( dbOptions );
     }
 
     return Status::OK();
diff --git a/src/osgEarthProcedural/CMakeLists.txt b/src/osgEarthProcedural/CMakeLists.txt
deleted file mode 100644
index 52a72d5..0000000
--- a/src/osgEarthProcedural/CMakeLists.txt
+++ /dev/null
@@ -1,67 +0,0 @@
-IF   (DYNAMIC_OSGEARTH)
-    ADD_DEFINITIONS(-DOSGEARTHPROCEDURAL_LIBRARY)
-ELSE (DYNAMIC_OSGEARTH)
-    ADD_DEFINITIONS(-DOSGEARTHPROCEDURAL_LIBRARY_STATIC)
-ENDIF(DYNAMIC_OSGEARTH)
-
-SET(LIB_NAME osgEarthProcedural)
-
-
-set(TARGET_GLSL )
-
-set(SHADERS_CPP "${CMAKE_CURRENT_BINARY_DIR}/AutoGenShaders.cpp")
-
-configure_shaders(
-    Shaders.cpp.in
-    ${SHADERS_CPP}
-    ${TARGET_GLSL} )
-
-SET(HEADER_PATH ${OSGEARTH_SOURCE_DIR}/include/${LIB_NAME})
-SET(LIB_PUBLIC_HEADERS
-#   header files go here
-    Common
-	Export
-	CoverageLegend
-	Shaders
-	SimplexNoise
-	SplatCatalog
-)
-
-ADD_LIBRARY(${LIB_NAME} ${OSGEARTH_USER_DEFINED_DYNAMIC_OR_STATIC}
-    ${LIB_PUBLIC_HEADERS}
-#  .cpp files go here
-	CoverageLegend.cpp
-	SplatCatalog.cpp
-	SimplexNoise.cpp
-	${SHADERS_CPP}
-)
-
-INCLUDE_DIRECTORIES(
-	${OSG_INCLUDE_DIR} 
-	${OSGEARTH_SOURCE_DIR} )
-
-LINK_EXTERNAL(
-	${LIB_NAME}
-	${TARGET_EXTERNAL_LIBRARIES}
-	${CMAKE_THREAD_LIBS_INIT}
-	${MATH_LIBRARY} )
-
-LINK_INTERNAL(
-	${LIB_NAME}
-    osgEarth
-)
-
-LINK_WITH_VARIABLES(
-	${LIB_NAME} 
-	OSG_LIBRARY
-	OSGDB_LIBRARY
-	OSGTEXT_LIBRARY
-	OSGUTIL_LIBRARY
-	OPENTHREADS_LIBRARY )
-
-LINK_CORELIB_DEFAULT(
-	${LIB_NAME}
-	${CMAKE_THREAD_LIBS_INIT}
-	${MATH_LIBRARY} )
-
-INCLUDE(ModuleInstall OPTIONAL)
diff --git a/src/osgEarthProcedural/Common b/src/osgEarthProcedural/Common
deleted file mode 100644
index 18bce16..0000000
--- a/src/osgEarthProcedural/Common
+++ /dev/null
@@ -1,32 +0,0 @@
-/* -*-c++-*- */
-/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
- * http://osgearth.org
- *
- * osgEarth is free software; you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>
- */
-
-#ifndef OSGEARTH_PROCEDURAL_COMMON_H
-#define OSGEARTH_PROCEDURAL_COMMON_H 1
-
-#include <osgEarth/Common>
-#include <osgEarthProcedural/Export>
-
-// common utilities
-namespace osgEarth { namespace Procedural
-{
-    
-} } // namespace osgEarth::Procedural
-
-#endif // OSGEARTH_PROCEDURAL_COMMON_H
diff --git a/src/osgEarthProcedural/CoverageLegend b/src/osgEarthProcedural/CoverageLegend
deleted file mode 100644
index 76e3a85..0000000
--- a/src/osgEarthProcedural/CoverageLegend
+++ /dev/null
@@ -1,99 +0,0 @@
-/* -*-c++-*- */
-/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
- * http://osgearth.org
- *
- * osgEarth is free software; you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>
- */
-#ifndef OSGEARTH_PROCEDURAL_COVERAGE_LEGEND
-#define OSGEARTH_PROCEDURAL_COVERAGE_LEGEND 1
-
-#include "Common"
-#include <osgEarth/Config>
-#include <osg/Referenced>
-#include <string>
-
-namespace osgEarth { namespace Procedural
-{
-    /**
-     * Associates a specific source data coverage value to the name
-     * of a splat class.
-     */
-    template<typename T>
-    class CoverageValuePredicateT : public osg::Referenced
-    {
-    public:
-        optional<T> _exactValue;
-        optional<T> _minValue;
-        optional<T> _maxValue;
-
-        optional<std::string> _description;
-        optional<std::string> _mappedClassName;
-
-        bool match(const T& testValue) const
-        {
-            if ( _exactValue.isSetTo(testValue) )
-                return true;
-            if ( !_minValue.isSet() && !_maxValue.isSet() )
-                return false;
-            if ( _minValue.isSet() && _minValue.get() > testValue )
-                return false;
-            if ( _maxValue.isSet() && _maxValue.get() < testValue )
-                return false;
-
-            return true;
-        }
-    };
-
-    typedef CoverageValuePredicateT<std::string> CoverageValuePredicate;
-    
-    /**
-     * Legend that maps coverage values (or value predicates) to splat
-     * catalog classes.
-     */
-    class CoverageLegend : public osg::Referenced
-    {
-    public:
-        CoverageLegend();
-
-    public:
-        typedef std::vector< osg::ref_ptr<CoverageValuePredicate> > Predicates;
-
-        /**
-         * The collection of value->class mapping predicates
-         */
-        const Predicates& getPredicates() const { return _predicates; }
-
-    public: // serialization
-
-        // populate this object from a Config
-        void fromConfig(const Config& conf);
-
-        // serialize this object to a Config.
-        Config getConfig() const;
-
-
-    protected:
-
-        virtual ~CoverageLegend() { }
-
-        optional<std::string> _name;
-        optional<std::string> _source;
-
-        Predicates _predicates;
-    };
-
-} } // namespace osgEarth::Procedural
-
-#endif // OSGEARTH_PROCEDURAL_COVERAGE_LEGEND
diff --git a/src/osgEarthProcedural/CoverageLegend.cpp b/src/osgEarthProcedural/CoverageLegend.cpp
deleted file mode 100644
index 5d193b0..0000000
--- a/src/osgEarthProcedural/CoverageLegend.cpp
+++ /dev/null
@@ -1,77 +0,0 @@
-/* -*-c++-*- */
-/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
- * http://osgearth.org
- *
- * osgEarth is free software; you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>
- */
-#include "CoverageLegend"
-#include <osgEarth/Config>
-
-using namespace osgEarth;
-using namespace osgEarth::Procedural;
-
-#define LC "[CoverageLegend] "
-
-//............................................................................
-
-CoverageLegend::CoverageLegend()
-{
-    //nop
-}
-
-void
-CoverageLegend::fromConfig(const Config& conf)
-{
-    conf.getIfSet("name",   _name);
-    conf.getIfSet("source", _source);
-
-    ConfigSet predicatesConf = conf.child("mappings").children();
-    for(ConfigSet::const_iterator i = predicatesConf.begin(); i != predicatesConf.end(); ++i)
-    {
-        osg::ref_ptr<CoverageValuePredicate> p = new CoverageValuePredicate();
-
-        i->getIfSet( "name",  p->_description );
-        i->getIfSet( "value", p->_exactValue );
-        i->getIfSet( "class", p->_mappedClassName );
-        
-        if ( p->_mappedClassName.isSet() )
-        {
-            _predicates.push_back( p.get() );
-        }
-    }
-}
-
-Config
-CoverageLegend::getConfig() const
-{
-    Config conf;
-    
-    conf.addIfSet("name",   _name);
-    conf.addIfSet("source", _source);
-
-    Config preds;
-    for(Predicates::const_iterator i = _predicates.begin(); i != _predicates.end(); ++i)
-    {
-        CoverageValuePredicate* p = i->get();
-        Config pred;
-        pred.addIfSet( "name",  p->_description );
-        pred.addIfSet( "value", p->_exactValue );
-        pred.addIfSet( "class", p->_mappedClassName );
-        preds.add(pred);
-    }
-    conf.add( "mappings", preds );
-
-    return conf;
-}
diff --git a/src/osgEarthProcedural/Export b/src/osgEarthProcedural/Export
deleted file mode 100644
index 83b2d60..0000000
--- a/src/osgEarthProcedural/Export
+++ /dev/null
@@ -1,74 +0,0 @@
-/* -*-c++-*- */
-/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
- * http://osgearth.org
- *
- * osgEarth is free software; you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>
- */
-
-/* -*-c++-*- 
- * Derived from osg/Export
- */
-
-#ifndef OSGEARTH_PROCEDURAL_EXPORT_H
-#define OSGEARTH_PROCEDURAL_EXPORT_H 1
-
-// define USE_DEPRECATED_API is used to include in API which is being fazed out
-// if you can compile your apps with this turned off you are
-// well placed for compatibility with future versions.
-#define USE_DEPRECATED_API
-
-#if defined(_MSC_VER)
-    #pragma warning( disable : 4244 )
-    #pragma warning( disable : 4251 )
-    #pragma warning( disable : 4267 )
-    #pragma warning( disable : 4275 )
-    #pragma warning( disable : 4290 )
-    #pragma warning( disable : 4786 )
-    #pragma warning( disable : 4305 )
-    #pragma warning( disable : 4996 )
-#endif
-
-#if defined(_MSC_VER) || defined(__CYGWIN__) || defined(__MINGW32__) || defined( __BCPLUSPLUS__)  || defined( __MWERKS__)
-    #  if defined( OSGEARTHPROCEDURAL_LIBRARY_STATIC )
-    #    define OSGEARTHPROCEDURAL_EXPORT
-    #  elif defined( OSGEARTHPROCEDURAL_LIBRARY )
-    #    define OSGEARTHPROCEDURAL_EXPORT   __declspec(dllexport)
-    #  else
-    #    define OSGEARTHPROCEDURAL_EXPORT   __declspec(dllimport)
-    #  endif
-#else
-    #  define OSGEARTHPROCEDURAL_EXPORT
-#endif  
-
-// set up define for whether member templates are supported by VisualStudio compilers.
-#ifdef _MSC_VER
-# if (_MSC_VER >= 1300)
-#  define __STL_MEMBER_TEMPLATES
-# endif
-#endif
-
-/* Define NULL pointer value */
-
-#ifndef NULL
-    #ifdef  __cplusplus
-        #define NULL    0
-    #else
-        #define NULL    ((void *)0)
-    #endif
-#endif
-
-
-#endif // OSGEARTH_PROCEDURAL_EXPORT_H
-
diff --git a/src/osgEarthProcedural/Shaders b/src/osgEarthProcedural/Shaders
deleted file mode 100644
index 90be788..0000000
--- a/src/osgEarthProcedural/Shaders
+++ /dev/null
@@ -1,31 +0,0 @@
-/* -*-c++-*- */
-/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2012 Pelican Mapping
-* http://osgearth.org
-*
-* osgEarth is free software; you can redistribute it and/or modify
-* it under the terms of the GNU Lesser General Public License as published by
-* the Free Software Foundation; either version 2 of the License, or
-* (at your option) any later version.
-*
-* This program is distributed in the hope that it will be useful,
-* but WITHOUT ANY WARRANTY; without even the implied warranty of
-* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-* GNU Lesser General Public License for more details.
-*
-* You should have received a copy of the GNU Lesser General Public License
-* along with this program.  If not, see <http://www.gnu.org/licenses/>
-*/
-#ifndef OSGEARTH_PROCEDURAL_SHADERS
-#define OSGEARTH_PROCEDURAL_SHADERS 1
-
-#include <osgEarthProcedural/Common>
-
-namespace osgEarth { namespace Procedural
-{
-    struct OSGEARTHPROCEDURAL_EXPORT Shaders
-	{
-	};
-} } // namespace osgEarth::Procedural
-
-#endif // OSGEARTH_PROCEDURAL_SHADERS
diff --git a/src/osgEarthProcedural/Shaders.cpp.in b/src/osgEarthProcedural/Shaders.cpp.in
deleted file mode 100644
index 25543db..0000000
--- a/src/osgEarthProcedural/Shaders.cpp.in
+++ /dev/null
@@ -1,9 +0,0 @@
-// ***DO NOT EDIT THIS FILE - IT IS AUTOMATICALLY GENERATED BY CMAKE***
-
-#include <osgEarthProcedural/Shaders>
-
-#define MULTILINE(...) #__VA_ARGS__
-
-using namespace osgEarth::Procedural;
-
-
diff --git a/src/osgEarthProcedural/SimplexNoise b/src/osgEarthProcedural/SimplexNoise
deleted file mode 100644
index 07b16ab..0000000
--- a/src/osgEarthProcedural/SimplexNoise
+++ /dev/null
@@ -1,159 +0,0 @@
-/* -*-c++-*- */
-/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
-* http://osgearth.org
-*
-* osgEarth is free software; you can redistribute it and/or modify
-* it under the terms of the GNU Lesser General Public License as published by
-* the Free Software Foundation; either version 2 of the License, or
-* (at your option) any later version.
-*
-* This program is distributed in the hope that it will be useful,
-* but WITHOUT ANY WARRANTY; without even the implied warranty of
-* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-* GNU Lesser General Public License for more details.
-*
-* You should have received a copy of the GNU Lesser General Public License
-* along with this program.  If not, see <http://www.gnu.org/licenses/>
-*/
-#ifndef OSGEARTH_PROCEDURAL_SIMPLEX_NOISE_H
-#define OSGEARTH_PROCEDURAL_SIMPLEX_NOISE_H 1
-
-#include <osgEarthProcedural/Common>
-
-namespace osgEarth { namespace Procedural
-{
-    /**
-     * Simplex Noise Generator.
-     * Adapted from https://github.com/Taywee/Noise
-     */
-    class OSGEARTHPROCEDURAL_EXPORT SimplexNoise
-    {
-    public:
-        SimplexNoise();
-        virtual ~SimplexNoise() { }
-
-        static const double DefaultFrequency;
-        static const double DefaultPersistence;
-        static const double DefaultLacunarity;
-        static const double DefaultRangeLow;
-        static const double DefaultRangeHigh;
-        static const unsigned DefaultOctaves;
-
-        /**
-         * Frequency is how often the noise pattern resets to zero across
-         * the input domain. For example, if your input range from 
-         * -100 < x < 100, a frequency of 1/200 will cause the noise function to
-         * reset every 200 units, i.e., the extent of the input domain. (It will
-         * be zero at -100 and zero again at 100. It may cross zero in between,
-         * but that is not guaranteed.)
-         *
-         * In mapping terms, say your map is 100000m wide. A frequency of 1/100000
-         * will result in a pattern that resets (and wraps around) every 100000m.
-         *
-         * Default = 1.0.
-         */ 
-        void setFrequency(double freq) { _freq = freq; }
-        double getFrequency() const { return _freq; }
-
-        /**
-         * Persietence is the factor by which the noise function's amplitude
-         * decreases with each successive octave.
-         *   i.e.: Amp(Oct2) = Amp(Oct1) * Persistance.
-         * Default = 0.5.
-         * Effect: Higher persistence: noisiness persists as you get more detail;
-         *         Lower persistence: data smooths out faster as you get more detail.
-         */
-        void setPersistence(double pers) { _pers = pers; }
-        double getPersistence() const { return _pers; }
-
-        /**
-         * Lacunarity is the factor by which the noise function's frequency
-         * increases with each successive octave.
-         *   i.e.: Freq(Oct2) = Freq(Oct1) * Lacunarity.
-         * Default = 2.0.
-         * Effect: Higher lacunarity: more "plateaus" in the output;
-         *         Lower lacunarity:  more "chopiness" in the output.
-         */
-        void setLacunarity(double lac) { _lacunarity = lac; }
-        double getLacunarity() const { return _lacunarity; }
-
-        /**
-         * The output range for noise data.
-         * Default = [-1..1].
-         */
-        void setRange(double low, double high) { _low = low, _high = high; }
-        double getRangeLow() const { return _low; }
-        double getRangeHigh() const { return _high; }
-
-        /**
-         * Number of iterations over which to generate noise. The frequency starts
-         * where you set it, and over each successive iteration, it is scaled by
-         * the persistence. This lets you control the rate at which the "noisiness"
-         * decreases with each octave.
-         * Default = 1.
-         */
-        void setOctaves(unsigned octaves) { _octaves = octaves; }
-        unsigned getOctaves() const { return _octaves; }
-
-        /**
-         * Generates 2D simplex Noise
-         */
-        double getValue(double xin, double yin) const;
-
-        /**
-         * Generates 3D simplex Noise
-         */
-        double getValue(double xin, double yin, double zin) const;
-
-        /**
-         * Generates 4D simplex Noise
-         */
-        double getValue(double x, double y, double z, double w) const;
-
-    private:
-        // Inner class to speed up gradient computations
-        // (array access is a lot slower than member access)
-        struct Grad
-        {
-            Grad(double x, double y, double z);
-            Grad(double x, double y, double z, double w);
-            double x, y, z, w;
-        };
-
-        const static Grad grad3[12];
-        const static Grad grad4[32];
-        const static unsigned char perm[512];
-
-        // Skewing and unskewing factors for 2, 3, and 4 dimensions
-        static double const F2;
-        static double const G2;
-        static double const F3;
-        static double const G3;
-        static double const F4;
-        static double const G4;
-
-        unsigned char permMod12[512];
-        bool permMod12Computed;
-        void ComputePermMod12();
-
-        // This method is a *lot* faster than using (int)Math.floor(x)
-        inline static int FastFloor(double x);
-        static double Dot(Grad const & g, double x, double y);
-        static double Dot(Grad const & g, double x, double y, double z);
-        static double Dot(Grad const & g, double x, double y, double z, double w);
-
-        double Noise(double x, double y) const;
-        double Noise(double x, double y, double z) const;
-        double Noise(double x, double y, double z, double w) const;
-
-        double _freq;
-        double _pers;
-        double _lacunarity;
-        double _low, _high;
-        unsigned _octaves;
-    };
-
-} } // namespace osgEarth::Procedural
-
-#endif //OSGEARTH_PROCEDURAL_SIMPLEX_NOISE_H
diff --git a/src/osgEarthProcedural/SimplexNoise.cpp b/src/osgEarthProcedural/SimplexNoise.cpp
deleted file mode 100644
index 1865838..0000000
--- a/src/osgEarthProcedural/SimplexNoise.cpp
+++ /dev/null
@@ -1,556 +0,0 @@
-/* -*-c++-*- */
-/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
- * http://osgearth.org
- *
- * osgEarth is free software; you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>
- */
-
-#include <osgEarthProcedural/SimplexNoise>
-#include <algorithm>
-
-#define POW2(x) ((double)(x==0 ? 1 : (2 << (x-1))))
-
-using namespace osgEarth::Procedural;
-
-const SimplexNoise::Grad SimplexNoise::grad3[12] = {
-    Grad(1, 1, 0), Grad(-1, 1, 0), Grad(1, -1, 0), Grad(-1, -1, 0),
-    Grad(1, 0, 1), Grad(-1, 0, 1), Grad(1, 0, -1), Grad(-1, 0, -1),
-    Grad(0, 1, 1), Grad(0, -1, 1), Grad(0, 1, -1), Grad(0, -1, -1)
-};
-
-const SimplexNoise::Grad SimplexNoise::grad4[32] =  {
-    Grad(0, 1, 1, 1), Grad(0, 1, 1, -1), Grad(0, 1, -1, 1), Grad(0, 1, -1, -1),
-    Grad(0, -1, 1, 1), Grad(0, -1, 1, -1), Grad(0, -1, -1, 1), Grad(0, -1, -1, -1),
-    Grad(1, 0, 1, 1), Grad(1, 0, 1, -1), Grad(1, 0, -1, 1), Grad(1, 0, -1, -1),
-    Grad(-1, 0, 1, 1), Grad(-1, 0, 1, -1), Grad(-1, 0, -1, 1), Grad(-1, 0, -1, -1),
-    Grad(1, 1, 0, 1), Grad(1, 1, 0, -1), Grad(1, -1, 0, 1), Grad(1, -1, 0, -1),
-    Grad(-1, 1, 0, 1), Grad(-1, 1, 0, -1), Grad(-1, -1, 0, 1), Grad(-1, -1, 0, -1),
-    Grad(1, 1, 1, 0), Grad(1, 1, -1, 0), Grad(1, -1, 1, 0), Grad(1, -1, -1, 0),
-    Grad(-1, 1, 1, 0), Grad(-1, 1, -1, 0), Grad(-1, -1, 1, 0), Grad(-1, -1, -1, 0)
-};
-
-const unsigned char SimplexNoise::perm[512] = {
-    151, 160, 137, 91, 90, 15,
-    131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 36, 103, 30, 69, 142, 8, 99, 37, 240, 21, 10, 23,
-    190, 6, 148, 247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219, 203, 117, 35, 11, 32, 57, 177, 33,
-    88, 237, 149, 56, 87, 174, 20, 125, 136, 171, 168, 68, 175, 74, 165, 71, 134, 139, 48, 27, 166,
-    77, 146, 158, 231, 83, 111, 229, 122, 60, 211, 133, 230, 220, 105, 92, 41, 55, 46, 245, 40, 244,
-    102, 143, 54, 65, 25, 63, 161, 1, 216, 80, 73, 209, 76, 132, 187, 208, 89, 18, 169, 200, 196,
-    135, 130, 116, 188, 159, 86, 164, 100, 109, 198, 173, 186, 3, 64, 52, 217, 226, 250, 124, 123,
-    5, 202, 38, 147, 118, 126, 255, 82, 85, 212, 207, 206, 59, 227, 47, 16, 58, 17, 182, 189, 28, 42,
-    223, 183, 170, 213, 119, 248, 152, 2, 44, 154, 163, 70, 221, 153, 101, 155, 167, 43, 172, 9,
-    129, 22, 39, 253, 19, 98, 108, 110, 79, 113, 224, 232, 178, 185, 112, 104, 218, 246, 97, 228,
-    251, 34, 242, 193, 238, 210, 144, 12, 191, 179, 162, 241, 81, 51, 145, 235, 249, 14, 239, 107,
-    49, 192, 214, 31, 181, 199, 106, 157, 184, 84, 204, 176, 115, 121, 50, 45, 127, 4, 150, 254,
-    138, 236, 205, 93, 222, 114, 67, 29, 24, 72, 243, 141, 128, 195, 78, 66, 215, 61, 156, 180,
-
-    151, 160, 137, 91, 90, 15,
-    131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 36, 103, 30, 69, 142, 8, 99, 37, 240, 21, 10, 23,
-    190, 6, 148, 247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219, 203, 117, 35, 11, 32, 57, 177, 33,
-    88, 237, 149, 56, 87, 174, 20, 125, 136, 171, 168, 68, 175, 74, 165, 71, 134, 139, 48, 27, 166,
-    77, 146, 158, 231, 83, 111, 229, 122, 60, 211, 133, 230, 220, 105, 92, 41, 55, 46, 245, 40, 244,
-    102, 143, 54, 65, 25, 63, 161, 1, 216, 80, 73, 209, 76, 132, 187, 208, 89, 18, 169, 200, 196,
-    135, 130, 116, 188, 159, 86, 164, 100, 109, 198, 173, 186, 3, 64, 52, 217, 226, 250, 124, 123,
-    5, 202, 38, 147, 118, 126, 255, 82, 85, 212, 207, 206, 59, 227, 47, 16, 58, 17, 182, 189, 28, 42,
-    223, 183, 170, 213, 119, 248, 152, 2, 44, 154, 163, 70, 221, 153, 101, 155, 167, 43, 172, 9,
-    129, 22, 39, 253, 19, 98, 108, 110, 79, 113, 224, 232, 178, 185, 112, 104, 218, 246, 97, 228,
-    251, 34, 242, 193, 238, 210, 144, 12, 191, 179, 162, 241, 81, 51, 145, 235, 249, 14, 239, 107,
-    49, 192, 214, 31, 181, 199, 106, 157, 184, 84, 204, 176, 115, 121, 50, 45, 127, 4, 150, 254,
-    138, 236, 205, 93, 222, 114, 67, 29, 24, 72, 243, 141, 128, 195, 78, 66, 215, 61, 156, 180
-};
-
-// Skewing and unskewing factors for 2, 3, and 4 dimensions
-double const SimplexNoise::F2 = 0.5*(sqrt(3.0)-1.0);
-double const SimplexNoise::G2 = (3.0-sqrt(3.0))/6.0;
-double const SimplexNoise::F3 = 1.0/3.0;
-double const SimplexNoise::G3 = 1.0/6.0;
-double const SimplexNoise::F4 = (sqrt(5.0)-1.0)/4.0;
-double const SimplexNoise::G4 = (5.0-sqrt(5.0))/20.0;
-
-SimplexNoise::Grad::Grad(double x, double y, double z)
-{
-    this->x = x;
-    this->y = y;
-    this->z = z;
-}
-
-SimplexNoise::Grad::Grad(double x, double y, double z, double w)
-{
-    this->x = x;
-    this->y = y;
-    this->z = z;
-    this->w = w;
-}
-
-// This method is a *lot* faster than using (int)Math.floor(x)
-int SimplexNoise::FastFloor(double x)
-{
-    int xi = (int)x;
-    return x<xi ? xi-1 : xi;
-}
-
-double SimplexNoise::Dot(Grad const & g, double x, double y)
-{
-    return g.x*x + g.y*y;
-}
-
-double SimplexNoise::Dot(Grad const & g, double x, double y, double z)
-{
-    return g.x*x + g.y*y + g.z*z;
-}
-
-double SimplexNoise::Dot(Grad const & g, double x, double y, double z, double w)
-{
-    return g.x*x + g.y*y + g.z*z + g.w*w;
-}
-
-
-const double   SimplexNoise::DefaultFrequency    =  1.0;
-const double   SimplexNoise::DefaultPersistence  =  0.5;
-const double   SimplexNoise::DefaultLacunarity   =  2.0;
-const double   SimplexNoise::DefaultRangeLow     = -1.0;
-const double   SimplexNoise::DefaultRangeHigh    =  1.0;
-const unsigned SimplexNoise::DefaultOctaves      =  10;
-
-
-SimplexNoise::SimplexNoise() :
-_octaves   ( DefaultOctaves ),
-_freq      ( DefaultFrequency ),
-_pers      ( DefaultPersistence ),
-_lacunarity( DefaultLacunarity ),
-_low       ( DefaultRangeLow ),
-_high      ( DefaultRangeHigh )
-{
-    for(unsigned int i=0; i<512; i++)
-    {
-        permMod12[i] = (unsigned char)(perm[i] % 12);
-    }
-}
-
-double SimplexNoise::getValue(double xin, double yin) const
-{
-    double freq = _freq;
-    double o = std::max(1u, _octaves);
-    double amp = 1.0;
-    double maxamp = 0.0;
-    double n = 0.0;
-
-    for(unsigned i=0; i<o; ++i)
-    {
-        n += Noise(xin*freq, yin*freq) * amp;
-        maxamp += amp;
-        amp *= _pers;
-        freq *= _lacunarity;
-    }
-    n /= maxamp;
-    n = n * (_high-_low)/2.0 + (_high+_low)/2.0;
-    return n;
-}
-
-double SimplexNoise::getValue(double xin, double yin, double zin) const
-{
-    double freq = _freq;
-    double o = std::max(1u, _octaves);
-    double amp = 1.0;
-    double maxamp = 0.0;
-    double n = 0.0;
-
-    for(unsigned i=0; i<o; ++i)
-    {
-        n += Noise(xin*freq, yin*freq, zin*freq) * amp;
-        maxamp += amp;
-        amp *= _pers;
-        freq *= _lacunarity;
-    }
-    n /= maxamp;
-    n = n * (_high-_low)/2.0 + (_high+_low)/2.0;
-    return n;
-}
-
-double SimplexNoise::getValue(double xin, double yin, double zin, double win) const
-{
-    double freq = _freq;
-    double o = std::max(1u, _octaves);
-    double amp = 1.0;
-    double maxamp = 0.0;
-    double n = 0.0;
-
-    for(unsigned i=0; i<o; ++i)
-    {
-        n += Noise(xin*freq, yin*freq, zin*freq, win*freq) * amp;
-        maxamp += amp;
-        amp *= _pers;
-        freq *= _lacunarity;
-    }
-    n /= maxamp;
-    n = n * (_high-_low)/2.0 + (_high+_low)/2.0;
-    return n;
-}
-
-
-// 2D simplex noise
-double SimplexNoise::Noise(double xin, double yin) const
-{
-    double n0, n1, n2; // Noise contributions from the three corners
-    // Skew the input space to determine which simplex cell we're in
-    double s = (xin+yin)*F2; // Hairy factor for 2D
-    int i = FastFloor(xin+s);
-    int j = FastFloor(yin+s);
-    double t = (i+j)*G2;
-    double X0 = i-t; // Unskew the cell origin back to (x,y) space
-    double Y0 = j-t;
-    double x0 = xin-X0; // The x,y distances from the cell origin
-    double y0 = yin-Y0;
-    // For the 2D case, the simplex shape is an equilateral triangle.
-    // Determine which simplex we are in.
-    int i1, j1; // Offsets for second (middle) corner of simplex in (i,j) coords
-    if(x0>y0)
-    {
-        i1=1;    // lower triangle, XY order: (0,0)->(1,0)->(1,1)
-        j1=0;
-    } else
-    {
-        i1=0;    // upper triangle, YX order: (0,0)->(0,1)->(1,1)
-        j1=1;
-    }
-    // A step of (1,0) in (i,j) means a step of (1-c,-c) in (x,y), and
-    // a step of (0,1) in (i,j) means a step of (-c,1-c) in (x,y), where
-    // c = (3-sqrt(3))/6
-    double x1 = x0 - i1 + G2; // Offsets for middle corner in (x,y) unskewed coords
-    double y1 = y0 - j1 + G2;
-    double x2 = x0 - 1.0 + 2.0 * G2; // Offsets for last corner in (x,y) unskewed coords
-    double y2 = y0 - 1.0 + 2.0 * G2;
-    // Work out the hashed gradient indices of the three simplex corners
-    int ii = i & 255;
-    int jj = j & 255;
-    int gi0 = permMod12[ii+perm[jj]];
-    int gi1 = permMod12[ii+i1+perm[jj+j1]];
-    int gi2 = permMod12[ii+1+perm[jj+1]];
-    // Calculate the contribution from the three corners
-    double t0 = 0.5 - x0*x0-y0*y0;
-    if(t0<0) n0 = 0.0;
-    else
-    {
-        t0 *= t0;
-        n0 = t0 * t0 * Dot(grad3[gi0], x0, y0);    // (x,y) of grad3 used for 2D gradient
-    }
-    double t1 = 0.5 - x1*x1-y1*y1;
-    if(t1<0) n1 = 0.0;
-    else
-    {
-        t1 *= t1;
-        n1 = t1 * t1 * Dot(grad3[gi1], x1, y1);
-    }
-    double t2 = 0.5 - x2*x2-y2*y2;
-    if(t2<0) n2 = 0.0;
-    else
-    {
-        t2 *= t2;
-        n2 = t2 * t2 * Dot(grad3[gi2], x2, y2);
-    }
-    // Add contributions from each corner to get the final noise value.
-    // The result is scaled to return values in the interval [-1,1].
-    return 70.0 * (n0 + n1 + n2);
-}
-
-
-// 3D simplex noise
-double SimplexNoise::Noise(double xin, double yin, double zin) const
-{
-    double n0, n1, n2, n3; // Noise contributions from the four corners
-    // Skew the input space to determine which simplex cell we're in
-    double s = (xin+yin+zin)*F3; // Very nice and simple skew factor for 3D
-    int i = FastFloor(xin+s);
-    int j = FastFloor(yin+s);
-    int k = FastFloor(zin+s);
-    double t = (i+j+k)*G3;
-    double X0 = i-t; // Unskew the cell origin back to (x,y,z) space
-    double Y0 = j-t;
-    double Z0 = k-t;
-    double x0 = xin-X0; // The x,y,z distances from the cell origin
-    double y0 = yin-Y0;
-    double z0 = zin-Z0;
-    // For the 3D case, the simplex shape is a slightly irregular tetrahedron.
-    // Determine which simplex we are in.
-    int i1, j1, k1; // Offsets for second corner of simplex in (i,j,k) coords
-    int i2, j2, k2; // Offsets for third corner of simplex in (i,j,k) coords
-    if(x0>=y0)
-    {
-        if(y0>=z0)
-        {
-            i1=1;    // X Y Z order
-            j1=0;
-            k1=0;
-            i2=1;
-            j2=1;
-            k2=0;
-        } else if(x0>=z0)
-        {
-            i1=1;    // X Z Y order
-            j1=0;
-            k1=0;
-            i2=1;
-            j2=0;
-            k2=1;
-        } else
-        {
-            i1=0;    // Z X Y order
-            j1=0;
-            k1=1;
-            i2=1;
-            j2=0;
-            k2=1;
-        }
-    } else     // x0<y0
-    {
-        if(y0<z0)
-        {
-            i1=0;    // Z Y X order
-            j1=0;
-            k1=1;
-            i2=0;
-            j2=1;
-            k2=1;
-        } else if(x0<z0)
-        {
-            i1=0;    // Y Z X order
-            j1=1;
-            k1=0;
-            i2=0;
-            j2=1;
-            k2=1;
-        } else
-        {
-            i1=0;    // Y X Z order
-            j1=1;
-            k1=0;
-            i2=1;
-            j2=1;
-            k2=0;
-        }
-    }
-    // A step of (1,0,0) in (i,j,k) means a step of (1-c,-c,-c) in (x,y,z),
-    // a step of (0,1,0) in (i,j,k) means a step of (-c,1-c,-c) in (x,y,z), and
-    // a step of (0,0,1) in (i,j,k) means a step of (-c,-c,1-c) in (x,y,z), where
-    // c = 1/6.
-    double x1 = x0 - i1 + G3; // Offsets for second corner in (x,y,z) coords
-    double y1 = y0 - j1 + G3;
-    double z1 = z0 - k1 + G3;
-    double x2 = x0 - i2 + 2.0*G3; // Offsets for third corner in (x,y,z) coords
-    double y2 = y0 - j2 + 2.0*G3;
-    double z2 = z0 - k2 + 2.0*G3;
-    double x3 = x0 - 1.0 + 3.0*G3; // Offsets for last corner in (x,y,z) coords
-    double y3 = y0 - 1.0 + 3.0*G3;
-    double z3 = z0 - 1.0 + 3.0*G3;
-    // Work out the hashed gradient indices of the four simplex corners
-    int ii = i & 255;
-    int jj = j & 255;
-    int kk = k & 255;
-    int gi0 = permMod12[ii+perm[jj+perm[kk]]];
-    int gi1 = permMod12[ii+i1+perm[jj+j1+perm[kk+k1]]];
-    int gi2 = permMod12[ii+i2+perm[jj+j2+perm[kk+k2]]];
-    int gi3 = permMod12[ii+1+perm[jj+1+perm[kk+1]]];
-    // Calculate the contribution from the four corners
-    double t0 = 0.6 - x0*x0 - y0*y0 - z0*z0;
-    if(t0<0) n0 = 0.0;
-    else
-    {
-        t0 *= t0;
-        n0 = t0 * t0 * Dot(grad3[gi0], x0, y0, z0);
-    }
-    double t1 = 0.6 - x1*x1 - y1*y1 - z1*z1;
-    if(t1<0) n1 = 0.0;
-    else
-    {
-        t1 *= t1;
-        n1 = t1 * t1 * Dot(grad3[gi1], x1, y1, z1);
-    }
-    double t2 = 0.6 - x2*x2 - y2*y2 - z2*z2;
-    if(t2<0) n2 = 0.0;
-    else
-    {
-        t2 *= t2;
-        n2 = t2 * t2 * Dot(grad3[gi2], x2, y2, z2);
-    }
-    double t3 = 0.6 - x3*x3 - y3*y3 - z3*z3;
-    if(t3<0) n3 = 0.0;
-    else
-    {
-        t3 *= t3;
-        n3 = t3 * t3 * Dot(grad3[gi3], x3, y3, z3);
-    }
-    // Add contributions from each corner to get the final noise value.
-    // The result is scaled to stay just inside [-1,1]
-    return 32.0*(n0 + n1 + n2 + n3);
-}
-
-double SimplexNoise::Noise(double x, double y, double z, double w) const
-{
-    double n0, n1, n2, n3, n4; // Noise contributions from the five corners
-    // Skew the (x,y,z,w) space to determine which cell of 24 simplices we're in
-    double s = (x + y + z + w) * F4; // Factor for 4D skewing
-    int i = FastFloor(x + s);
-    int j = FastFloor(y + s);
-    int k = FastFloor(z + s);
-    int l = FastFloor(w + s);
-    double t = (i + j + k + l) * G4; // Factor for 4D unskewing
-    double X0 = i - t; // Unskew the cell origin back to (x,y,z,w) space
-    double Y0 = j - t;
-    double Z0 = k - t;
-    double W0 = l - t;
-    double x0 = x - X0;    // The x,y,z,w distances from the cell origin
-    double y0 = y - Y0;
-    double z0 = z - Z0;
-    double w0 = w - W0;
-    // For the 4D case, the simplex is a 4D shape I won't even try to describe.
-    // To find out which of the 24 possible simplices we're in, we need to
-    // determine the magnitude ordering of x0, y0, z0 and w0.
-    // Six pair-wise comparisons are performed between each possible pair
-    // of the four coordinates, and the results are used to rank the numbers.
-    int rankx = 0;
-    int ranky = 0;
-    int rankz = 0;
-    int rankw = 0;
-    if(x0 > y0)
-    {
-        rankx++;
-    } else
-    {
-        ranky++;
-    }
-
-    if(x0 > z0)
-    {
-        rankx++;
-    } else
-    {
-        rankz++;
-    }
-
-    if(x0 > w0)
-    {
-        rankx++;
-    } else
-    {
-        rankw++;
-    }
-
-    if(y0 > z0)
-    {
-        ranky++;
-    } else
-    {
-        rankz++;
-    }
-
-    if(y0 > w0)
-    {
-        ranky++;
-    } else
-    {
-        rankw++;
-    }
-
-    if(z0 > w0)
-    {
-        rankz++;
-    } else
-    {
-        rankw++;
-    }
-
-    int i1, j1, k1, l1; // The integer offsets for the second simplex corner
-    int i2, j2, k2, l2; // The integer offsets for the third simplex corner
-    int i3, j3, k3, l3; // The integer offsets for the fourth simplex corner
-    // simplex[c] is a 4-vector with the numbers 0, 1, 2 and 3 in some order.
-    // Many values of c will never occur, since e.g. x>y>z>w makes x<z, y<w and x<w
-    // impossible. Only the 24 indices which have non-zero entries make any sense.
-    // We use a thresholding to set the coordinates in turn from the largest magnitude.
-    // Rank 3 denotes the largest coordinate.
-    i1 = rankx >= 3 ? 1 : 0;
-    j1 = ranky >= 3 ? 1 : 0;
-    k1 = rankz >= 3 ? 1 : 0;
-    l1 = rankw >= 3 ? 1 : 0;
-    // Rank 2 denotes the second largest coordinate.
-    i2 = rankx >= 2 ? 1 : 0;
-    j2 = ranky >= 2 ? 1 : 0;
-    k2 = rankz >= 2 ? 1 : 0;
-    l2 = rankw >= 2 ? 1 : 0;
-    // Rank 1 denotes the second smallest coordinate.
-    i3 = rankx >= 1 ? 1 : 0;
-    j3 = ranky >= 1 ? 1 : 0;
-    k3 = rankz >= 1 ? 1 : 0;
-    l3 = rankw >= 1 ? 1 : 0;
-    // The fifth corner has all coordinate offsets = 1, so no need to compute that.
-    double x1 = x0 - i1 + G4; // Offsets for second corner in (x,y,z,w) coords
-    double y1 = y0 - j1 + G4;
-    double z1 = z0 - k1 + G4;
-    double w1 = w0 - l1 + G4;
-    double x2 = x0 - i2 + 2.0*G4; // Offsets for third corner in (x,y,z,w) coords
-    double y2 = y0 - j2 + 2.0*G4;
-    double z2 = z0 - k2 + 2.0*G4;
-    double w2 = w0 - l2 + 2.0*G4;
-    double x3 = x0 - i3 + 3.0*G4; // Offsets for fourth corner in (x,y,z,w) coords
-    double y3 = y0 - j3 + 3.0*G4;
-    double z3 = z0 - k3 + 3.0*G4;
-    double w3 = w0 - l3 + 3.0*G4;
-    double x4 = x0 - 1.0 + 4.0*G4; // Offsets for last corner in (x,y,z,w) coords
-    double y4 = y0 - 1.0 + 4.0*G4;
-    double z4 = z0 - 1.0 + 4.0*G4;
-    double w4 = w0 - 1.0 + 4.0*G4;
-    // Work out the hashed gradient indices of the five simplex corners
-    int ii = i & 255;
-    int jj = j & 255;
-    int kk = k & 255;
-    int ll = l & 255;
-    int gi0 = perm[ii+perm[jj+perm[kk+perm[ll]]]] % 32;
-    int gi1 = perm[ii+i1+perm[jj+j1+perm[kk+k1+perm[ll+l1]]]] % 32;
-    int gi2 = perm[ii+i2+perm[jj+j2+perm[kk+k2+perm[ll+l2]]]] % 32;
-    int gi3 = perm[ii+i3+perm[jj+j3+perm[kk+k3+perm[ll+l3]]]] % 32;
-    int gi4 = perm[ii+1+perm[jj+1+perm[kk+1+perm[ll+1]]]] % 32;
-    // Calculate the contribution from the five corners
-    double t0 = 0.6 - x0*x0 - y0*y0 - z0*z0 - w0*w0;
-    if(t0<0) n0 = 0.0;
-    else {
-        t0 *= t0;
-        n0 = t0 * t0 * Dot(grad4[gi0], x0, y0, z0, w0);
-    }
-    double t1 = 0.6 - x1*x1 - y1*y1 - z1*z1 - w1*w1;
-    if(t1<0) n1 = 0.0;
-    else {
-        t1 *= t1;
-        n1 = t1 * t1 * Dot(grad4[gi1], x1, y1, z1, w1);
-    }
-    double t2 = 0.6 - x2*x2 - y2*y2 - z2*z2 - w2*w2;
-    if(t2<0) n2 = 0.0;
-    else {
-        t2 *= t2;
-        n2 = t2 * t2 * Dot(grad4[gi2], x2, y2, z2, w2);
-    }
-    double t3 = 0.6 - x3*x3 - y3*y3 - z3*z3 - w3*w3;
-    if(t3<0) n3 = 0.0;
-    else {
-        t3 *= t3;
-        n3 = t3 * t3 * Dot(grad4[gi3], x3, y3, z3, w3);
-    }
-    double t4 = 0.6 - x4*x4 - y4*y4 - z4*z4 - w4*w4;
-    if(t4<0) n4 = 0.0;
-    else {
-        t4 *= t4;
-        n4 = t4 * t4 * Dot(grad4[gi4], x4, y4, z4, w4);
-    }
-    // Sum up and scale the result to cover the range [-1,1]
-    return 27.0 * (n0 + n1 + n2 + n3 + n4);
-}
diff --git a/src/osgEarthProcedural/SplatCatalog b/src/osgEarthProcedural/SplatCatalog
deleted file mode 100644
index ff56066..0000000
--- a/src/osgEarthProcedural/SplatCatalog
+++ /dev/null
@@ -1,161 +0,0 @@
-/* -*-c++-*- */
-/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
- * http://osgearth.org
- *
- * osgEarth is free software; you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>
- */
-#ifndef OSGEARTH_PROCEDURAL_SPLAT_CATALOG
-#define OSGEARTH_PROCEDURAL_SPLAT_CATALOG 1
-
-#include "Common"
-#include <osg/Referenced>
-
-#include <osgEarth/Containers>
-#include <osgEarth/URI>
-
-// forward declarations
-namespace osg {
-    class Texture2DArray;
-}
-namespace osgDB {
-    class Options;
-}
-
-namespace osgEarth { namespace Procedural
-{
-    /**
-     * Defines the parameters for noise-generated detail splatting
-     * for a given class/range.
-     */
-    struct SplatDetailData
-    {
-        optional<URI>         _imageURI;
-        optional<float>       _saturation;
-        optional<float>       _threshold;
-        optional<float>       _slope;
-
-        // catalog will populate this value
-        int _textureIndex;
-
-        SplatDetailData();
-        SplatDetailData(const Config& conf);
-        Config getConfig() const;
-    };
-
-    /**
-     * Defines a single splatting appearance that will be used if its
-     * selector expression evaluates to true.
-     */
-    struct SplatRangeData
-    {
-        optional<float>           _minRange;
-        optional<URI>             _imageURI;
-        optional<URI>             _modelURI;
-        optional<int>             _modelCount;
-        optional<int>             _modelLevel;
-        optional<SplatDetailData> _detail;
-
-        // catalog will populate this value
-        int _textureIndex;
-
-        SplatRangeData();
-        SplatRangeData(const Config& conf);
-        Config getConfig() const;
-    };
-
-    typedef std::vector<SplatRangeData> SplatRangeDataVector;
-
-    /**
-     * A single splatting class. One class may have multiple
-     * data definitions, which are evaluated in order of appearance.
-     */
-    struct SplatClass
-    {
-        std::string          _name;
-        SplatRangeDataVector _ranges;
-
-        SplatClass();
-        SplatClass(const Config& conf);
-        Config getConfig() const;
-    };
-    
-    typedef osgEarth::fast_map<std::string, SplatClass> SplatClassMap;
-    
-    // Associates a selector expression that, if it evaluates to true, will
-    // select the specified splat texture
-    typedef std::pair<std::string /*expression*/, SplatRangeData> SplatSelector;
-
-    // Vector of selectors
-    typedef std::vector<SplatSelector> SplatSelectorVector;
-
-    // Maps splat class names to texture data renderers
-    typedef osgEarth::fast_map<std::string /*className*/, SplatSelectorVector> SplatLUT;
-
-    // Defines the splatting texture and associated lookup table.
-    struct SplatTextureDef
-    {
-        osg::ref_ptr<osg::Texture2DArray> _texture;
-        SplatLUT                          _splatLUT;
-    };
-
-
-    /**
-     * Catalog of texture-splatting classes. Usually these correspond
-     * to land use.
-     */
-    class SplatCatalog : public osg::Referenced
-    {
-    public:
-        /**
-         * Construct an empty catalog.
-         */
-        SplatCatalog();
-
-        /**
-         * The splatting classifications set.
-         */
-        const SplatClassMap& getClasses() const { return _classes; }
-
-        /**
-         * Create a texture array from the images in the catalog, along
-         * with a definition of how to map classifications to texture
-         * array indices.
-         */
-        bool createSplatTextureDef(const osgDB::Options* options,
-                                   SplatTextureDef&      out_def );
-
-
-    public: // serialization
-
-        // populate this object from a Config
-        void fromConfig(const Config& conf);
-
-        // serialize this object to a Config.
-        Config getConfig() const;
-
-
-    protected:
-
-        virtual ~SplatCatalog() { }
-
-        optional<int>         _version;
-        optional<std::string> _name;
-        optional<std::string> _description;
-        SplatClassMap         _classes;
-    };
-
-} } // namespace osgEarth::Procedural
-
-#endif // OSGEARTH_PROCEDURAL_SPLAT_CATALOG
diff --git a/src/osgEarthProcedural/SplatCatalog.cpp b/src/osgEarthProcedural/SplatCatalog.cpp
deleted file mode 100644
index 55fd3cb..0000000
--- a/src/osgEarthProcedural/SplatCatalog.cpp
+++ /dev/null
@@ -1,343 +0,0 @@
-/* -*-c++-*- */
-/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
- * http://osgearth.org
- *
- * osgEarth is free software; you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>
- */
-#include "SplatCatalog"
-#include <osgEarth/Config>
-#include <osgEarth/ImageUtils>
-#include <osg/Texture2DArray>
-
-using namespace osgEarth;
-using namespace osgEarth::Procedural;
-
-#define LC "[SplatCatalog] "
-
-#define SPLAT_CATALOG_CURRENT_VERSION 1
-
-
-//............................................................................
-
-SplatDetailData::SplatDetailData() :
-_textureIndex( -1 )
-{
-    //nop
-}
-
-SplatDetailData::SplatDetailData(const Config& conf) :
-_textureIndex( -1 )
-{
-    conf.getIfSet("image",      _imageURI);
-    conf.getIfSet("saturation", _saturation);
-    conf.getIfSet("threshold",  _threshold);
-    conf.getIfSet("slope",      _slope);
-}
-
-Config
-SplatDetailData::getConfig() const
-{
-    Config conf;
-    conf.addIfSet("image",      _imageURI);
-    conf.addIfSet("saturation", _saturation);
-    conf.addIfSet("threshold",  _threshold);
-    conf.addIfSet("slope",      _slope);
-    return conf;
-}
-
-//............................................................................
-
-SplatRangeData::SplatRangeData() :
-_textureIndex( -1 )
-{
-    //nop
-}
-
-SplatRangeData::SplatRangeData(const Config& conf) :
-_textureIndex( -1 )
-{
-    conf.getIfSet("image",      _imageURI);
-    conf.getIfSet("model",      _modelURI);
-    conf.getIfSet("modelCount", _modelCount);
-    conf.getIfSet("modelLevel", _modelLevel);
-
-    if ( conf.hasChild("detail") )
-        _detail = SplatDetailData(conf.child("detail"));
-}
-
-Config
-SplatRangeData::getConfig() const
-{
-    Config conf;
-    conf.addIfSet("image",      _imageURI);
-    conf.addIfSet("model",      _modelURI);
-    conf.addIfSet("modelCount", _modelCount);
-    conf.addIfSet("modelLevel", _modelLevel);
-    if ( _detail.isSet() )
-        conf.add( "detail", _detail->getConfig() );
-
-    return conf;
-}
-
-//............................................................................
-
-SplatClass::SplatClass()
-{
-    //nop
-}
-
-SplatClass::SplatClass(const Config& conf)
-{
-    _name = conf.key();
-
-    // read the data definitions in order:
-    for(ConfigSet::const_iterator i = conf.children().begin(); i != conf.children().end(); ++i)
-    {
-        if ( !i->empty() )
-        {
-            _ranges.push_back(SplatRangeData(*i));
-        }
-    }
-}
-
-Config
-SplatClass::getConfig() const
-{
-    Config conf( _name );
-    for(SplatRangeDataVector::const_iterator i = _ranges.begin(); i != _ranges.end(); ++i)
-    {
-        conf.add( i->getConfig() );
-    }
-    return conf;
-}
-
-//............................................................................
-
-SplatCatalog::SplatCatalog()
-{
-    _version = SPLAT_CATALOG_CURRENT_VERSION;
-}
-
-void
-SplatCatalog::fromConfig(const Config& conf)
-{
-    conf.getIfSet("version",     _version);
-    conf.getIfSet("name",        _name);
-    conf.getIfSet("description", _description);
-
-    Config classesConf = conf.child("classes");
-    if ( !classesConf.empty() )
-    {
-        for(ConfigSet::const_iterator i = classesConf.children().begin(); i != classesConf.children().end(); ++i)
-        {
-            if ( !i->key().empty() )
-            {
-                _classes[i->key()] = SplatClass(*i);
-            }
-        }
-    }
-}
-
-Config
-SplatCatalog::getConfig() const
-{
-    Config conf;
-    conf.addIfSet("version",     _version);
-    conf.addIfSet("name",        _name);
-    conf.addIfSet("description", _description);
-    
-    Config classes("classes");
-    {
-        for(SplatClassMap::const_iterator i = _classes.begin(); i != _classes.end(); ++i)
-        {
-            classes.add( i->second.getConfig() );
-        }
-    }    
-    conf.add( classes );
-
-    return conf;
-}
-
-namespace
-{
-    osg::Image* loadImage(const URI& uri, const osgDB::Options* dbOptions, osg::Image* firstImage)
-    {
-        // try to load the image:
-        ReadResult result = uri.readImage(dbOptions);
-        if ( result.succeeded() )
-        {
-            // if this is the first image loaded, remember it so we can ensure that
-            // all images are copatible.
-            if ( firstImage == 0L )
-            {
-                firstImage = result.getImage();
-            }
-            else
-            {
-                // ensure compatibility, a requirement for texture arrays.
-                // In the future perhaps we can resize/convert instead.
-                if ( !ImageUtils::textureArrayCompatible(result.getImage(), firstImage) )
-                {
-                    OE_WARN << LC << "Image " << uri.base()
-                        << " was found, but cannot be used because it is not compatible with "
-                        << "other splat images (same dimensions, pixel format, etc.)\n";
-
-                    return 0L;
-                }
-            }
-        }
-        else
-        {
-            OE_WARN << LC
-                << "Image in the splat catalog failed to load: "
-                << uri.full() << "; message = " << result.getResultCodeString()
-                << std::endl;
-        }
-
-        return result.releaseImage();
-    }
-}
-
-bool
-SplatCatalog::createSplatTextureDef(const osgDB::Options* dbOptions,
-                                    SplatTextureDef&      out      )
-{
-    // Reset all texture indices to default
-    for(SplatClassMap::iterator i = _classes.begin(); i != _classes.end(); ++i)
-    {
-        SplatClass& c = i->second;
-        for(SplatRangeDataVector::iterator range = c._ranges.begin(); range != c._ranges.end(); ++range)
-        {
-            range->_textureIndex = -1;
-            if ( range->_detail.isSet() )
-            {
-                range->_detail->_textureIndex = -1;
-            }
-        }
-    }
-
-    typedef osgEarth::fast_map<URI, int> ImageIndexTable; // track images to prevent dupes
-    ImageIndexTable imageIndices;
-    std::vector< osg::ref_ptr<osg::Image> > imagesInOrder;
-    int index = 0;
-    osg::Image* firstImage  = 0L;
-
-    // Load all referenced images in the catalog, and assign each a unique index.
-    for(SplatClassMap::iterator i = _classes.begin(); i != _classes.end(); ++i)
-    {
-        SplatClass& c = i->second;
-
-        for(SplatRangeDataVector::iterator range = c._ranges.begin(); range != c._ranges.end(); ++range)
-        {
-            // Load the main image and assign it an index:
-            if (range->_imageURI.isSet())
-            {
-                int texIndex = -1;
-                ImageIndexTable::iterator k = imageIndices.find(range->_imageURI.get());
-                if ( k == imageIndices.end() )
-                {
-                    osg::ref_ptr<osg::Image> image = loadImage( range->_imageURI.get(), dbOptions, firstImage );
-                    if ( image.valid() )
-                    {
-                        if ( !firstImage )
-                            firstImage = image.get();
-
-                        imageIndices[range->_imageURI.get()] = texIndex = index++;
-                        imagesInOrder.push_back( image.get() );
-                    }
-                }
-                else
-                {
-                    texIndex = k->second;
-                }
-                range->_textureIndex = texIndex;
-            }
-
-            // Load the detail texture if it exists:
-            if (range->_detail.isSet() &&
-                range->_detail->_imageURI.isSet())
-            {
-                int texIndex = -1;
-                ImageIndexTable::iterator k = imageIndices.find(range->_detail->_imageURI.get());
-                if ( k == imageIndices.end() )
-                {
-                    osg::ref_ptr<osg::Image> image = loadImage( range->_detail->_imageURI.get(), dbOptions, firstImage );
-                    if ( image.valid() )
-                    {
-                        if ( !firstImage )
-                            firstImage = image.get();
-            
-                        imageIndices[range->_detail->_imageURI.get()] = texIndex = index++;
-                        imagesInOrder.push_back( image.get() );
-                    }
-                }
-                else
-                {
-                    texIndex = k->second;
-                }
-                range->_detail->_textureIndex = texIndex;
-            }
-        }
-    }
-
-    // Next, go through the classes and build the splat lookup table.
-    for(SplatClassMap::const_iterator i = _classes.begin(); i != _classes.end(); ++i)
-    {
-        const SplatClass& c = i->second;
-
-        // selectors for this class (ordered):
-        SplatSelectorVector selectors;
-
-        // check each data element:
-        for(SplatRangeDataVector::const_iterator range = c._ranges.begin(); range != c._ranges.end(); ++range)
-        {
-            // If the primary image exists, look up its index and add it to the selector set.
-            ImageIndexTable::const_iterator k = imageIndices.find( range->_imageURI.get() );
-            if ( k != imageIndices.end() )
-            {
-                std::string expression;
-                if ( range->_minRange.isSet() )
-                {
-                    expression = Stringify()
-                        << "env.range >= float(" << range->_minRange.get() << ")";
-                }
-
-                // insert into the lookup table.
-                out._splatLUT[c._name].push_back( SplatSelector(expression, *range) );
-            }
-        }
-    }
-
-    // Create the texture array.
-    if ( imagesInOrder.size() > 0 )
-    {
-        out._texture = new osg::Texture2DArray();
-        out._texture->setTextureSize( firstImage->s(), firstImage->t(), imagesInOrder.size() );
-        out._texture->setWrap( osg::Texture::WRAP_S, osg::Texture::REPEAT );
-        out._texture->setWrap( osg::Texture::WRAP_T, osg::Texture::REPEAT );
-        out._texture->setFilter( osg::Texture::MIN_FILTER, osg::Texture::LINEAR_MIPMAP_LINEAR );
-        out._texture->setFilter( osg::Texture::MAG_FILTER, osg::Texture::LINEAR );
-        out._texture->setResizeNonPowerOfTwoHint( false );
-        out._texture->setMaxAnisotropy( 4.0f );
-
-        for(unsigned i=0; i<imagesInOrder.size(); ++i)
-        {
-            out._texture->setImage( i, imagesInOrder[i].get() );
-        }
-
-    }
-
-    return out._texture.valid();
-}
diff --git a/src/osgEarthQt/CMakeLists.txt b/src/osgEarthQt/CMakeLists.txt
index 2d07d74..7ab4c0c 100644
--- a/src/osgEarthQt/CMakeLists.txt
+++ b/src/osgEarthQt/CMakeLists.txt
@@ -1,171 +1,169 @@
-IF (Qt5Widgets_FOUND OR QT4_FOUND AND NOT ANDROID AND OSGEARTH_USE_QT)
-
-
-IF   (DYNAMIC_OSGEARTH)
-    ADD_DEFINITIONS(-DOSGEARTHQT_LIBRARY)
-ELSE (DYNAMIC_OSGEARTH)
-    ADD_DEFINITIONS(-DOSGEARTHQT_LIBRARY_STATIC)
-ENDIF(DYNAMIC_OSGEARTH)
-
-SET(LIB_NAME osgEarthQt)
-
-SET(HEADER_PATH ${OSGEARTH_SOURCE_DIR}/include/${LIB_NAME})
-
-if(OSGEARTH_QT_BUILD_LEGACY_WIDGETS)
-    # Header files that need moc'd
-    set(LIB_MOC_HDRS
-        #AnnotationDialogs
-        #AnnotationListWidget
-        #AnnotationToolbar
-        CollapsiblePairWidget
-        DataManager
-        LayerManagerWidget
-        LOSControlWidget
-        LOSCreationDialog
-        MapCatalogWidget
-        TerrainProfileGraph
-        TerrainProfileWidget
-        ViewerWidget
-    )
-
-    # Qt resource files
-    set(LIB_QT_RCS
-        images.qrc
-    )
-
-    # Qt UI files
-    set(LIB_QT_UIS
-        ui/LOSCreationDialog.ui
-    )
-else(OSGEARTH_QT_BUILD_LEGACY_WIDGETS)
-    # Header files that need moc'd
-    set(LIB_MOC_HDRS
-        ViewerWidget
-    )
-endif(OSGEARTH_QT_BUILD_LEGACY_WIDGETS)
-
-IF(Qt5Widgets_FOUND)
-    QT5_ADD_RESOURCES( LIB_RC_SRCS ${LIB_QT_RCS} )
-    QT5_WRAP_UI( LIB_UI_HDRS ${LIB_QT_UIS} )
-    QT5_WRAP_CPP( LIB_UI_SRCS ${LIB_UI_HDRS} )
-    SET(LIB_MOC_SRCS)
-    FOREACH( LIB_MOC_HDR ${LIB_MOC_HDRS} )
-        GET_FILENAME_COMPONENT( LIB_MOC_HDR_ABS ${LIB_MOC_HDR} ABSOLUTE )
-        QT5_WRAP_CPP( LIB_MOC_SRC ${LIB_MOC_HDR} OPTIONS "-f${LIB_MOC_HDR_ABS}" )
-        LIST( APPEND LIB_MOC_SRCS ${LIB_MOC_SRC} )
-    ENDFOREACH()
-ELSE()
-    INCLUDE( ${QT_USE_FILE} )
-    QT4_ADD_RESOURCES( LIB_RC_SRCS ${LIB_QT_RCS} )
-    QT4_WRAP_UI( LIB_UI_HDRS ${LIB_QT_UIS} )
-    QT4_WRAP_CPP( LIB_UI_SRCS ${LIB_UI_HDRS} OPTIONS "-f" )
-    QT4_WRAP_CPP( LIB_MOC_SRCS ${LIB_MOC_HDRS} OPTIONS "-f" )
-ENDIF()
-
-if(OSGEARTH_QT_BUILD_LEGACY_WIDGETS)
-    SET(LIB_PUBLIC_HEADERS
-    #   header files go here
-        Actions
-        #AnnotationDialogs
-        #AnnotationListWidget
-        #AnnotationToolbar
-        CollapsiblePairWidget
-        Common
-        DataManager
-        GuiActions
-        LayerManagerWidget
-        LOSControlWidget
-        LOSCreationDialog
-        MapCatalogWidget
-        TerrainProfileGraph
-        TerrainProfileWidget
-        ViewWidget
-        ViewerWidget
-        ${LIB_UI_HDRS}
-        ${LIB_QT_UIS}
-        ${LIB_QT_RCS}
-    )
-
-    ADD_LIBRARY(${LIB_NAME} SHARED
-        ${LIB_PUBLIC_HEADERS}
-    #  .cpp files go here
-        ${LIB_RC_SRCS}
-        ${LIB_UI_SRCS}
-        ${LIB_MOC_SRCS}
-        #AnnotationDialogs.cpp
-        #AnnotationListWidget.cpp
-        #AnnotationToolbar.cpp
-        CollapsiblePairWidget.cpp
-        DataManager.cpp
-        LayerManagerWidget.cpp
-        LOSControlWidget.cpp
-        LOSCreationDialog.cpp
-        MapCatalogWidget.cpp
-        TerrainProfileGraph.cpp
-        TerrainProfileWidget.cpp
-        ViewWidget.cpp
-        ViewerWidget.cpp
-    )
-else(OSGEARTH_QT_BUILD_LEGACY_WIDGETS)
-    SET(LIB_PUBLIC_HEADERS
-    #   header files go here
-        Common
-        ViewWidget
-        ViewerWidget
-        ${LIB_UI_HDRS}
-        ${LIB_QT_UIS}
-        ${LIB_QT_RCS}
-    )
-
-    ADD_LIBRARY(${LIB_NAME} SHARED
-        ${LIB_PUBLIC_HEADERS}
-    #  .cpp files go here
-        ${LIB_RC_SRCS}
-        ${LIB_UI_SRCS}
-        ${LIB_MOC_SRCS}
-        ViewWidget.cpp
-        ViewerWidget.cpp
-    )
-endif(OSGEARTH_QT_BUILD_LEGACY_WIDGETS)
-
-INCLUDE_DIRECTORIES(${OSG_INCLUDE_DIR} ${OSGEARTH_SOURCE_DIR} ${QT_INCLUDES} ${CMAKE_CURRENT_BINARY_DIR})
-
-IF (WIN32)
-  LINK_EXTERNAL(${LIB_NAME} ${TARGET_EXTERNAL_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} ${MATH_LIBRARY})
-ELSE(WIN32)
-  LINK_EXTERNAL(${LIB_NAME} ${TARGET_EXTERNAL_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} ${MATH_LIBRARY})
-ENDIF(WIN32)
-
-if(OSGEARTH_QT_BUILD_LEGACY_WIDGETS)
-    link_internal(${LIB_NAME}
-        osgEarth
-        osgEarthAnnotation
-        osgEarthUtil
-        ${QT_QTCORE_LIBRARY}
-        ${QT_QTGUI_LIBRARY}
-        ${QT_QTOPENGL_LIBRARY}
-    )
-else(OSGEARTH_QT_BUILD_LEGACY_WIDGETS)
-    link_internal(${LIB_NAME}
-        osgEarth
-        osgEarthUtil
-        ${QT_QTCORE_LIBRARY}
-        ${QT_QTGUI_LIBRARY}
-        ${QT_QTOPENGL_LIBRARY}
-    )
-endif(OSGEARTH_QT_BUILD_LEGACY_WIDGETS)
-
-LINK_WITH_VARIABLES(${LIB_NAME} OSG_LIBRARY OSGWIDGET_LIBRARY OSGUTIL_LIBRARY OSGSIM_LIBRARY OSGTERRAIN_LIBRARY OSGDB_LIBRARY OSGFX_LIBRARY OSGVIEWER_LIBRARY OSGTEXT_LIBRARY OSGGA_LIBRARY OSGQT_LIBRARY OPENTHREADS_LIBRARY)
-
-LINK_CORELIB_DEFAULT(${LIB_NAME} ${CMAKE_THREAD_LIBS_INIT} ${MATH_LIBRARY})
-
-IF ( Qt5Widgets_FOUND )
-    qt5_use_modules( ${LIB_NAME} Gui Widgets OpenGL )
-ENDIF( Qt5Widgets_FOUND )
-
-# Add this project to the NodeKits solution folder
-set_property( TARGET ${LIB_NAME} PROPERTY FOLDER "NodeKits" )
-
-INCLUDE(ModuleInstall OPTIONAL)
-
-ENDIF (Qt5Widgets_FOUND OR QT4_FOUND AND NOT ANDROID AND OSGEARTH_USE_QT)
\ No newline at end of file
+
+IF (Qt5Widgets_FOUND OR QT4_FOUND AND NOT ANDROID AND OSGEARTH_QT_BUILD)
+
+    if(DYNAMIC_OSGEARTH)
+        ADD_DEFINITIONS(-DOSGEARTHQT_LIBRARY)
+    else(DYNAMIC_OSGEARTH)
+        ADD_DEFINITIONS(-DOSGEARTHQT_LIBRARY_STATIC)
+    endif(DYNAMIC_OSGEARTH)
+
+    IF(Qt5Widgets_FOUND)
+      SET(LIB_NAME osgEarthQt5)
+      SET(HEADER_INSTALL_DIR osgEarthQt)
+    ELSE(Qt5Widgets_FOUND)
+      SET(LIB_NAME osgEarthQt)
+    ENDIF(Qt5Widgets_FOUND)
+
+    SET(HEADER_PATH ${OSGEARTH_SOURCE_DIR}/include/${LIB_NAME})
+
+    if(OSGEARTH_QT_BUILD_LEGACY_WIDGETS)
+        # Header files that need moc'd
+        set(LIB_MOC_HDRS
+            CollapsiblePairWidget
+            DataManager
+            LayerManagerWidget
+            LOSControlWidget
+            LOSCreationDialog
+            MapCatalogWidget
+            TerrainProfileGraph
+            TerrainProfileWidget
+            ViewerWidget
+        )
+
+        # Qt resource files
+        set(LIB_QT_RCS
+            images.qrc
+        )
+
+        # Qt UI files
+        set(LIB_QT_UIS
+            ui/LOSCreationDialog.ui
+        )
+    else(OSGEARTH_QT_BUILD_LEGACY_WIDGETS)
+        # Header files that need moc'd
+        set(LIB_MOC_HDRS
+            ViewerWidget
+        )
+    endif(OSGEARTH_QT_BUILD_LEGACY_WIDGETS)
+
+    IF(Qt5Widgets_FOUND)
+        QT5_ADD_RESOURCES( LIB_RC_SRCS ${LIB_QT_RCS} )
+        QT5_WRAP_UI( LIB_UI_HDRS ${LIB_QT_UIS} )
+        QT5_WRAP_CPP( LIB_UI_SRCS ${LIB_UI_HDRS} )
+        SET(LIB_MOC_SRCS)
+        FOREACH( LIB_MOC_HDR ${LIB_MOC_HDRS} )
+            GET_FILENAME_COMPONENT( LIB_MOC_HDR_ABS ${LIB_MOC_HDR} ABSOLUTE )
+            QT5_WRAP_CPP( LIB_MOC_SRC ${LIB_MOC_HDR} OPTIONS "-f${LIB_MOC_HDR_ABS}" )
+            LIST( APPEND LIB_MOC_SRCS ${LIB_MOC_SRC} )
+        ENDFOREACH()
+    ELSE()
+        INCLUDE( ${QT_USE_FILE} )
+        QT4_ADD_RESOURCES( LIB_RC_SRCS ${LIB_QT_RCS} )
+        QT4_WRAP_UI( LIB_UI_HDRS ${LIB_QT_UIS} )
+        QT4_WRAP_CPP( LIB_UI_SRCS ${LIB_UI_HDRS} OPTIONS "-f" )
+        QT4_WRAP_CPP( LIB_MOC_SRCS ${LIB_MOC_HDRS} OPTIONS "-f" )
+    ENDIF()
+
+    if(OSGEARTH_QT_BUILD_LEGACY_WIDGETS)
+        SET(LIB_PUBLIC_HEADERS
+        #   header files go here
+            Actions
+            CollapsiblePairWidget
+            Common
+            DataManager
+            GuiActions
+            LayerManagerWidget
+            LOSControlWidget
+            LOSCreationDialog
+            MapCatalogWidget
+            TerrainProfileGraph
+            TerrainProfileWidget
+            ViewWidget
+            ViewerWidget
+            ${LIB_UI_HDRS}
+            ${LIB_QT_UIS}
+            ${LIB_QT_RCS}
+        )
+
+        ADD_LIBRARY(${LIB_NAME} ${OSGEARTH_USER_DEFINED_DYNAMIC_OR_STATIC}
+            ${LIB_PUBLIC_HEADERS}
+        #  .cpp files go here
+            ${LIB_RC_SRCS}
+            ${LIB_UI_SRCS}
+            ${LIB_MOC_SRCS}
+            CollapsiblePairWidget.cpp
+            DataManager.cpp
+            LayerManagerWidget.cpp
+            LOSControlWidget.cpp
+            LOSCreationDialog.cpp
+            MapCatalogWidget.cpp
+            TerrainProfileGraph.cpp
+            TerrainProfileWidget.cpp
+            ViewWidget.cpp
+            ViewerWidget.cpp
+        )
+    else(OSGEARTH_QT_BUILD_LEGACY_WIDGETS)
+        SET(LIB_PUBLIC_HEADERS
+        #   header files go here
+            Common
+            ViewWidget
+            ViewerWidget
+            ${LIB_UI_HDRS}
+            ${LIB_QT_UIS}
+            ${LIB_QT_RCS}
+        )
+
+        ADD_LIBRARY(${LIB_NAME} ${OSGEARTH_USER_DEFINED_DYNAMIC_OR_STATIC}
+            ${LIB_PUBLIC_HEADERS}
+        #  .cpp files go here
+            ${LIB_RC_SRCS}
+            ${LIB_UI_SRCS}
+            ${LIB_MOC_SRCS}
+            ViewWidget.cpp
+            ViewerWidget.cpp
+        )
+    endif(OSGEARTH_QT_BUILD_LEGACY_WIDGETS)
+
+    INCLUDE_DIRECTORIES(${OSG_INCLUDE_DIR} ${OSGEARTH_SOURCE_DIR} ${QT_INCLUDES} ${CMAKE_CURRENT_BINARY_DIR})
+
+    IF (WIN32)
+      LINK_EXTERNAL(${LIB_NAME} ${TARGET_EXTERNAL_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} ${MATH_LIBRARY})
+    ELSE(WIN32)
+      LINK_EXTERNAL(${LIB_NAME} ${TARGET_EXTERNAL_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} ${MATH_LIBRARY})
+    ENDIF(WIN32)
+
+    if(OSGEARTH_QT_BUILD_LEGACY_WIDGETS)
+        link_internal(${LIB_NAME}
+            osgEarth
+            osgEarthAnnotation
+            osgEarthUtil
+            ${QT_QTCORE_LIBRARY}
+            ${QT_QTGUI_LIBRARY}
+            ${QT_QTOPENGL_LIBRARY}
+            ${QT_QTOPENGLEXTENSIONS_LIBRARY}
+        )
+    else(OSGEARTH_QT_BUILD_LEGACY_WIDGETS)
+        link_internal(${LIB_NAME}
+            osgEarth
+            osgEarthUtil
+            ${QT_QTCORE_LIBRARY}
+            ${QT_QTGUI_LIBRARY}
+            ${QT_QTOPENGL_LIBRARY}
+            ${QT_QTOPENGLEXTENSIONS_LIBRARY}
+        )
+    endif(OSGEARTH_QT_BUILD_LEGACY_WIDGETS)
+
+    LINK_WITH_VARIABLES(${LIB_NAME} OSG_LIBRARY OSGWIDGET_LIBRARY OSGUTIL_LIBRARY OSGSIM_LIBRARY OSGTERRAIN_LIBRARY OSGDB_LIBRARY OSGFX_LIBRARY OSGVIEWER_LIBRARY OSGTEXT_LIBRARY OSGGA_LIBRARY OSGQT_LIBRARY OPENTHREADS_LIBRARY)
+
+    LINK_CORELIB_DEFAULT(${LIB_NAME} ${CMAKE_THREAD_LIBS_INIT} ${MATH_LIBRARY})
+
+    IF ( Qt5Widgets_FOUND )
+        qt5_use_modules( ${LIB_NAME} Gui Widgets OpenGL )
+    ENDIF( Qt5Widgets_FOUND )
+
+    # Add this project to the NodeKits solution folder
+    set_property( TARGET ${LIB_NAME} PROPERTY FOLDER "NodeKits" )
+
+    INCLUDE(ModuleInstall OPTIONAL)
+
+ENDIF (Qt5Widgets_FOUND OR QT4_FOUND AND NOT ANDROID AND OSGEARTH_QT_BUILD)
diff --git a/src/osgEarthQt/DataManager b/src/osgEarthQt/DataManager
index eed02af..716b444 100644
--- a/src/osgEarthQt/DataManager
+++ b/src/osgEarthQt/DataManager
@@ -25,6 +25,10 @@
 #include <osgEarth/Map>
 #include <osgEarth/MapNode>
 #include <osgEarth/Viewpoint>
+#include <osgEarth/ImageLayer>
+#include <osgEarth/ElevationLayer>
+#include <osgEarth/ModelLayer>
+#include <osgEarth/MaskLayer>
 
 #include <osgEarthAnnotation/AnnotationNode>
 
@@ -155,11 +159,11 @@ namespace osgEarth { namespace QtGui
     {
       DataManagerImageLayerCallback(DataManager* dm) : _dm(dm) { }
 
-      void onOpacityChanged(ImageLayer* layer)
-      {
-        if (_dm.valid())
-          _dm->onMapChanged();
-      }
+      //void onOpacityChanged(ImageLayer* layer)
+      //{
+      //  if (_dm.valid())
+      //    _dm->onMapChanged();
+      //}
 
       void onEnabledChanged(TerrainLayer* layer)
       {
diff --git a/src/osgEarthQt/DataManager.cpp b/src/osgEarthQt/DataManager.cpp
index 52f7296..8f36909 100644
--- a/src/osgEarthQt/DataManager.cpp
+++ b/src/osgEarthQt/DataManager.cpp
@@ -24,6 +24,9 @@
 #include <osgEarth/Map>
 #include <osgEarth/MapModelChange>
 #include <osgEarth/MapNode>
+#include <osgEarth/ImageLayer>
+#include <osgEarth/ElevationLayer>
+#include <osgEarth/ModelLayer>
 
 using namespace osgEarth::QtGui;
 using namespace osgEarth::Annotation;
@@ -59,17 +62,17 @@ void DataManager::initialize()
   if (_map)
   {
     osgEarth::ElevationLayerVector elevLayers;
-    _map->getElevationLayers(elevLayers);
+    _map->getLayers(elevLayers);
     for (osgEarth::ElevationLayerVector::const_iterator it = elevLayers.begin(); it != elevLayers.end(); ++it)
       (*it)->addCallback(_elevationCallback);
 
     osgEarth::ImageLayerVector imageLayers;
-    _map->getImageLayers(imageLayers);
+    _map->getLayers(imageLayers);
     for (osgEarth::ImageLayerVector::const_iterator it = imageLayers.begin(); it != imageLayers.end(); ++it)
       (*it)->addCallback(_imageCallback);
 
     osgEarth::ModelLayerVector modelLayers;
-    _map->getModelLayers(modelLayers);
+    _map->getLayers(modelLayers);
     for (osgEarth::ModelLayerVector::const_iterator it = modelLayers.begin(); it != modelLayers.end(); ++it)
       (*it)->addCallback(_modelCallback);
 
@@ -253,19 +256,26 @@ void DataManager::onMapChanged(const osgEarth::MapModelChange& change)
 {
   switch( change.getAction() )
   {
-  case MapModelChange::ADD_ELEVATION_LAYER: 
-    change.getElevationLayer()->addCallback(_elevationCallback); break;
-  case MapModelChange::ADD_IMAGE_LAYER:
-    change.getImageLayer()->addCallback(_imageCallback); break;
-  case MapModelChange::ADD_MODEL_LAYER:
-		change.getModelLayer()->addCallback(_modelCallback); break;
-  case MapModelChange::REMOVE_ELEVATION_LAYER:
-    change.getElevationLayer()->removeCallback(_elevationCallback); break;
-  case MapModelChange::REMOVE_IMAGE_LAYER:
-    change.getImageLayer()->removeCallback(_imageCallback); break;
-  case MapModelChange::REMOVE_MODEL_LAYER:
-    change.getModelLayer()->removeCallback(_modelCallback); break;
-  default: break;
+  case MapModelChange::ADD_LAYER: 
+      if (change.getElevationLayer())
+        change.getElevationLayer()->addCallback(_elevationCallback);
+      else if (change.getImageLayer())
+        change.getImageLayer()->addCallback(_imageCallback);
+      else if (change.getModelLayer())
+          change.getModelLayer()->addCallback(_modelCallback);
+      break;
+
+  case MapModelChange::REMOVE_LAYER:
+      if (change.getElevationLayer())
+        change.getElevationLayer()->removeCallback(_elevationCallback);
+      else if (change.getImageLayer())
+        change.getImageLayer()->removeCallback(_imageCallback);
+      else if (change.getModelLayer())
+          change.getModelLayer()->removeCallback(_modelCallback);
+      break;
+
+  default:
+      break;
   }
 
   onMapChanged();
diff --git a/src/osgEarthQt/GuiActions b/src/osgEarthQt/GuiActions
index a9fd477..7185210 100644
--- a/src/osgEarthQt/GuiActions
+++ b/src/osgEarthQt/GuiActions
@@ -23,6 +23,7 @@
 #include <osgEarthQt/Actions>
 
 #include <osgEarth/Viewpoint>
+#include <osgEarth/ModelLayer>
 #include <osgEarthUtil/EarthManipulator>
 
 #include <osgViewer/Viewer>
diff --git a/src/osgEarthQt/LayerManagerWidget b/src/osgEarthQt/LayerManagerWidget
index 26179aa..f960fe3 100644
--- a/src/osgEarthQt/LayerManagerWidget
+++ b/src/osgEarthQt/LayerManagerWidget
@@ -24,6 +24,9 @@
 #include <osgEarthQt/DataManager>
 
 #include <osgEarth/Map>
+#include <osgEarth/ImageLayer>
+#include <osgEarth/ElevationLayer>
+#include <osgEarth/ModelLayer>
 
 #include <QCheckBox>
 #include <QDropEvent>
diff --git a/src/osgEarthQt/LayerManagerWidget.cpp b/src/osgEarthQt/LayerManagerWidget.cpp
index 63d2dcf..0d3f649 100644
--- a/src/osgEarthQt/LayerManagerWidget.cpp
+++ b/src/osgEarthQt/LayerManagerWidget.cpp
@@ -23,8 +23,8 @@
 #include <osgEarthQt/DataManager>
 #include <osgEarthQt/GuiActions>
 
-
 #include <osgEarth/Map>
+#include <osgEarth/ModelLayer>
 #include <osgEarth/Viewpoint>
 #include <osgEarthAnnotation/AnnotationNode>
 
@@ -72,11 +72,11 @@ namespace
   public:
     WidgetImageLayerCallback(ImageLayerControlWidget* widget) : _widget(widget) {}
 
-    void onOpacityChanged(ImageLayer* layer)
-    {
-      if (_widget)
-        _widget->setLayerOpacity(layer->getOpacity());
-    }
+    //void onOpacityChanged(ImageLayer* layer)
+    //{
+    //  if (_widget)
+    //    _widget->setLayerOpacity(layer->getOpacity());
+    //}
 
     void onEnabledChanged(TerrainLayer* layer)
     {
@@ -381,7 +381,7 @@ void ElevationLayerControlWidget::onEnabledCheckStateChanged(int state)
 void ElevationLayerControlWidget::onRemoveClicked(bool checked)
 {
   if (_parent && _parent->getMap())
-    _parent->getMap()->removeElevationLayer(_layer);
+    _parent->getMap()->removeLayer(_layer);
 }
 
 void ElevationLayerControlWidget::setLayerVisible(bool visible)
@@ -497,7 +497,7 @@ void ImageLayerControlWidget::onSliderValueChanged(int value)
 void ImageLayerControlWidget::onRemoveClicked(bool checked)
 {
   if (_parent && _parent->getMap())
-    _parent->getMap()->removeImageLayer(_layer);
+    _parent->getMap()->removeLayer(_layer);
 }
 
 void ImageLayerControlWidget::setLayerVisible(bool visible)
@@ -597,7 +597,7 @@ void ModelLayerControlWidget::onEnabledCheckStateChanged(int state)
 void ModelLayerControlWidget::onRemoveClicked(bool checked)
 {
   if (_parent && _parent->getMap())
-    _parent->getMap()->removeModelLayer(_layer);
+    _parent->getMap()->removeLayer(_layer);
 }
 
 void ModelLayerControlWidget::setLayerVisible(bool visible)
@@ -749,21 +749,21 @@ void LayerManagerWidget::refresh()
   if (_type == IMAGE_LAYERS)
   {
     osgEarth::ImageLayerVector layers;
-    _map->getImageLayers(layers);
+    _map->getLayers(layers);
     for (osgEarth::ImageLayerVector::const_iterator it = layers.begin(); it != layers.end(); ++it)
       addImageLayerItem(*it);
   }
   else if (_type == MODEL_LAYERS)
   {
     osgEarth::ModelLayerVector layers;
-    _map->getModelLayers(layers);
+    _map->getLayers(layers);
     for (osgEarth::ModelLayerVector::const_iterator it = layers.begin(); it != layers.end(); ++it)
       addModelLayerItem(*it);
   }
   else if (_type == ELEVATION_LAYERS)
   {
     osgEarth::ElevationLayerVector layers;
-    _map->getElevationLayers(layers);
+    _map->getLayers(layers);
     for (osgEarth::ElevationLayerVector::const_iterator it = layers.begin(); it != layers.end(); ++it)
       addElevationLayerItem(*it);
   }
@@ -896,19 +896,19 @@ void LayerManagerWidget::doLayerWidgetDrop(LayerControlWidgetBase* widget, Layer
     {
       ElevationLayerControlWidget* elevWidget = dynamic_cast<ElevationLayerControlWidget*>(widget);
       if (elevWidget)
-        _map->moveElevationLayer(elevWidget->layer(), newRow >= 0 ? newRow : _stack->count() - 1);
+        _map->moveLayer(elevWidget->layer(), newRow >= 0 ? newRow : _stack->count() - 1);
     }
     else if (_type == IMAGE_LAYERS)
     {
       ImageLayerControlWidget* imageWidget = dynamic_cast<ImageLayerControlWidget*>(widget);
       if (imageWidget)
-        _map->moveImageLayer(imageWidget->layer(), newRow >= 0 ? newRow : _stack->count() - 1);
+        _map->moveLayer(imageWidget->layer(), newRow >= 0 ? newRow : _stack->count() - 1);
     }
     else if (_type == MODEL_LAYERS)
     {
       ModelLayerControlWidget* modelWidget = dynamic_cast<ModelLayerControlWidget*>(widget);
       if (modelWidget)
-        _map->moveModelLayer(modelWidget->layer(), newRow >= 0 ? newRow : _stack->count() - 1);
+        _map->moveLayer(modelWidget->layer(), newRow >= 0 ? newRow : _stack->count() - 1);
     }
   }
 }
diff --git a/src/osgEarthQt/MapCatalogWidget.cpp b/src/osgEarthQt/MapCatalogWidget.cpp
index 32cf276..5d5c2cd 100644
--- a/src/osgEarthQt/MapCatalogWidget.cpp
+++ b/src/osgEarthQt/MapCatalogWidget.cpp
@@ -23,8 +23,11 @@
 #include <osgEarthQt/DataManager>
 #include <osgEarthQt/GuiActions>
 
-
 #include <osgEarth/Map>
+#include <osgEarth/ImageLayer>
+#include <osgEarth/ElevationLayer>
+#include <osgEarth/ModelLayer>
+#include <osgEarth/MaskLayer>
 #include <osgEarth/Viewpoint>
 #include <osgEarthAnnotation/AnnotationNode>
 
@@ -428,7 +431,7 @@ void MapCatalogWidget::refreshElevationLayers()
     _elevationsItem->takeChildren();
 	  
     osgEarth::ElevationLayerVector layers;
-    _map->getElevationLayers(layers);
+    _map->getLayers(layers);
     for (osgEarth::ElevationLayerVector::const_iterator it = layers.begin(); it != layers.end(); ++it)
     {
       LayerTreeItem* layerItem = new LayerTreeItem(*it, _map);
@@ -462,7 +465,7 @@ void MapCatalogWidget::refreshImageLayers()
     _imagesItem->takeChildren();
 
     osgEarth::ImageLayerVector layers;
-    _map->getImageLayers(layers);
+    _map->getLayers(layers);
     for (osgEarth::ImageLayerVector::const_iterator it = layers.begin(); it != layers.end(); ++it)
     {
       LayerTreeItem* layerItem = new LayerTreeItem(*it, _map);
@@ -496,7 +499,8 @@ void MapCatalogWidget::refreshModelLayers()
     _modelsItem->takeChildren();
 
     osgEarth::ModelLayerVector layers;
-    _map->getModelLayers(layers);
+    _map->getLayers(layers);
+
     for (osgEarth::ModelLayerVector::const_iterator it = layers.begin(); it != layers.end(); ++it)
     {
       LayerTreeItem* layerItem = new LayerTreeItem(*it, _map);
@@ -567,7 +571,8 @@ void MapCatalogWidget::refreshMaskLayers()
     _masksItem->takeChildren();
 
     osgEarth::MaskLayerVector layers;
-    _map->getTerrainMaskLayers(layers);
+    _map->getLayers(layers);
+
     for (osgEarth::MaskLayerVector::const_iterator it = layers.begin(); it != layers.end(); ++it)
     {
       CustomActionTreeItem* layerItem = new CustomActionTreeItem(*it);
diff --git a/src/osgEarthSilverLining/SilverLiningContextNode.cpp b/src/osgEarthSilverLining/SilverLiningContextNode.cpp
index 57c6c8a..4fbf9f8 100644
--- a/src/osgEarthSilverLining/SilverLiningContextNode.cpp
+++ b/src/osgEarthSilverLining/SilverLiningContextNode.cpp
@@ -27,6 +27,7 @@
 #include <osg/Light>
 #include <osg/LightSource>
 #include <osgEarth/CullingUtils>
+#include <osgEarth/NodeUtils>
 
 #undef  LC
 #define LC "[SilverLiningContextNode] "
diff --git a/src/osgEarthSilverLining/SilverLiningNode b/src/osgEarthSilverLining/SilverLiningNode
index 5c6c243..4547458 100644
--- a/src/osgEarthSilverLining/SilverLiningNode
+++ b/src/osgEarthSilverLining/SilverLiningNode
@@ -47,7 +47,7 @@ namespace osgEarth { namespace SilverLining
     public: // SkyNode
 
         /** The (sun) light that this node controls */
-        osg::Light* getSunLight() { return _light.get(); }
+        osg::Light* getSunLight() const { return _light.get(); }
 
         /** Attach to a view so that this node controls its light. */
         void attach(osg::View* view, int lightNum);
diff --git a/src/osgEarthSilverLining/SilverLiningNode.cpp b/src/osgEarthSilverLining/SilverLiningNode.cpp
index aa08b9a..14eb2ec 100644
--- a/src/osgEarthSilverLining/SilverLiningNode.cpp
+++ b/src/osgEarthSilverLining/SilverLiningNode.cpp
@@ -26,7 +26,10 @@
 
 #include <osg/Light>
 #include <osg/LightSource>
+
 #include <osgEarth/CullingUtils>
+#include <osgEarth/Lighting>
+#include <osgEarth/NodeUtils>
 
 #undef  LC
 #define LC "[SilverLiningNode] "
@@ -41,8 +44,7 @@ _mapSRS(mapSRS),
 _callback(callback)
 {
     // Create a new Light for the Sun.
-    _light = new osg::Light();
-    _light->setLightNum( 0 );
+    _light = new LightGL3(0);
     _light->setDiffuse( osg::Vec4(1,1,1,1) );
     _light->setAmbient( osg::Vec4(0.2f, 0.2f, 0.2f, 1) );
     _light->setPosition( osg::Vec4(1, 0, 0, 0) ); // w=0 means infinity
@@ -51,11 +53,13 @@ _callback(callback)
     _lightSource = new osg::LightSource();
     _lightSource->setLight( _light.get() );
     _lightSource->setReferenceFrame(osg::LightSource::RELATIVE_RF);
+    GenerateGL3LightingUniforms generateLightingVisitor;
+    _lightSource->accept(generateLightingVisitor);
 
     // scene lighting
     osg::StateSet* stateset = this->getOrCreateStateSet();
     _lighting = new PhongLightingEffect();
-    _lighting->setCreateLightingUniform( false );
+    //_lighting->setCreateLightingUniform( false );
     _lighting->attach( stateset );
 
     // need update traversal.
@@ -73,8 +77,9 @@ void
 SilverLiningNode::attach(osg::View* view, int lightNum)
 {
     _light->setLightNum( lightNum );
-    view->setLight( _light.get() );
-    view->setLightingMode( osg::View::SKY_LIGHT );
+    //view->setLight( _light.get() );
+    //view->setLightingMode( osg::View::SKY_LIGHT );
+    view->setLightingMode(osg::View::NO_LIGHT);
 }
 
 unsigned
@@ -167,7 +172,7 @@ SilverLiningNode::traverse(osg::NodeVisitor& nv)
             {
                 for (CameraSet::const_iterator i = _camerasToAdd.begin(); i != _camerasToAdd.end(); ++i)
                 {
-                    SilverLiningContextNode* newNode = new SilverLiningContextNode(this, i->get(), _light, _mapSRS, _options, _callback);
+                    SilverLiningContextNode* newNode = new SilverLiningContextNode(this, i->get(), _light.get(), _mapSRS, _options, _callback.get());
                     _contexts[i->get()] = newNode;
                     _contextList.push_back(newNode);
                 }
diff --git a/src/osgEarthSplat/CMakeLists.txt b/src/osgEarthSplat/CMakeLists.txt
index 8dd6f4b..1785b2c 100644
--- a/src/osgEarthSplat/CMakeLists.txt
+++ b/src/osgEarthSplat/CMakeLists.txt
@@ -16,12 +16,11 @@ set(TARGET_GLSL
     Splat.vert.model.glsl
     Splat.vert.view.glsl
     Splat.frag.glsl
-    Splat.frag.common.glsl
     Splat.util.glsl
-    LandCover.TCS.glsl
-    LandCover.TES.glsl
-    LandCover.GS.glsl
-    LandCover.FS.glsl )
+    GroundCover.TCS.glsl
+    GroundCover.TES.glsl
+    GroundCover.GS.glsl
+    GroundCover.FS.glsl )
 
 set(SHADERS_CPP "${CMAKE_CURRENT_BINARY_DIR}/AutoGenShaders.cpp")
 
@@ -35,14 +34,13 @@ set(TARGET_IN
 
 set(TARGET_SRC
     Coverage.cpp
-    LandCover.cpp
-    LandCoverTerrainEffect.cpp
-    LandCoverTilePatchCallback.cpp
+    GroundCover.cpp
+    GroundCoverLayer.cpp
     LandUseTileSource.cpp
     NoiseTextureFactory.cpp
-	SplatExtension.cpp
+    RoadSurfaceLayer.cpp
 	SplatCatalog.cpp
-	SplatTerrainEffect.cpp
+    SplatLayer.cpp
 	SplatCoverageLegend.cpp
     Surface.cpp
     Zone.cpp
@@ -51,17 +49,16 @@ set(TARGET_SRC
 set(LIB_PUBLIC_HEADERS
     Coverage
 	Export
-    LandCover
-    LandCoverTerrainEffect
-    LandCoverTilePatchCallback
+    GroundCover
+    GroundCoverLayer
     LandUseTileSource
     NoiseTextureFactory
+    RoadSurfaceLayer
 	SplatCoverageLegend
 	SplatCatalog
-	SplatExtension
 	SplatOptions
     SplatShaders
-	SplatTerrainEffect
+    SplatLayer
     Surface
     Zone)
 	
diff --git a/src/osgEarthSplat/Coverage b/src/osgEarthSplat/Coverage
index 04fdfb4..64e0874 100644
--- a/src/osgEarthSplat/Coverage
+++ b/src/osgEarthSplat/Coverage
@@ -66,8 +66,8 @@ namespace osgEarth { namespace Splat
         Config getConfig() const {
             Config conf = ConfigOptions::getConfig();
             conf.key() = "coverage";
-            conf.updateIfSet("layer", _coverageLayer);
-            conf.updateIfSet("legend", _legendURI);
+            conf.set("layer", _coverageLayer);
+            conf.set("legend", _legendURI);
             return conf;
         }
 
diff --git a/src/osgEarthSplat/Coverage.cpp b/src/osgEarthSplat/Coverage.cpp
index 7dc1f67..a587bb0 100644
--- a/src/osgEarthSplat/Coverage.cpp
+++ b/src/osgEarthSplat/Coverage.cpp
@@ -19,6 +19,7 @@
 #include "Coverage"
 #include "SplatCoverageLegend"
 #include <osgEarth/Map>
+#include <osgEarth/ImageLayer>
 #include <osgEarth/XmlUtils>
 #include <osgDB/Options>
 
@@ -68,7 +69,7 @@ Coverage::configure(const ConfigOptions& conf, const Map* map, const osgDB::Opti
     }
 
     // Find the classification layer in the map:
-    _layer = map->getImageLayerByName( in.layer().get() );
+    _layer = map->getLayerByName<ImageLayer>( in.layer().get() );
     if ( !_layer.valid() )
     {
         OE_WARN << LC << "Layer \"" << in.layer().get() << "\" not found in the map\n";
diff --git a/src/osgEarthSplat/Export b/src/osgEarthSplat/Export
index df6b48a..10ac9ac 100644
--- a/src/osgEarthSplat/Export
+++ b/src/osgEarthSplat/Export
@@ -42,7 +42,7 @@
 
 #if defined(_MSC_VER) || defined(__CYGWIN__) || defined(__MINGW32__) || defined( __BCPLUSPLUS__)  || defined( __MWERKS__)
     #  if defined( OSGEARTHSPLAT_LIBRARY_STATIC )
-    #    define OSGEARTH_SPLAT_EXPORT
+    #    define OSGEARTHSPLAT_EXPORT
     #  elif defined( OSGEARTHSPLAT_LIBRARY )
     #    define OSGEARTHSPLAT_EXPORT   __declspec(dllexport)
     #  else
diff --git a/src/osgEarthSplat/GroundCover b/src/osgEarthSplat/GroundCover
new file mode 100644
index 0000000..099cf64
--- /dev/null
+++ b/src/osgEarthSplat/GroundCover
@@ -0,0 +1,260 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+ * Copyright 2016 Pelican Mapping
+ * http://osgearth.org
+ *
+ * osgEarth is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+#ifndef OSGEARTH_PROCEDURAL_GroundCover
+#define OSGEARTH_PROCEDURAL_GroundCover 1
+
+#include "Export"
+#include <osgEarth/Config>
+#include <osgEarth/ImageLayer>
+#include <osgEarth/LandCoverLayer>
+#include <osgEarthSymbology/ResourceLibrary>
+#include <osgEarthSymbology/Symbol>
+#include <osg/BoundingBox>
+#include <osg/Shader>
+
+namespace osgDB {
+    class Options;
+}
+namespace osgEarth {
+    class Map;
+}
+
+namespace osgEarth { namespace Splat
+{
+    using namespace osgEarth;
+    using namespace osgEarth::Symbology;
+
+    class Coverage;
+    class SplatCoverageLegend;
+
+
+    class OSGEARTHSPLAT_EXPORT GroundCoverBiomeOptions : public ConfigOptions
+    {
+    public:
+        GroundCoverBiomeOptions(const ConfigOptions& conf = ConfigOptions()) : ConfigOptions(conf) {
+            fromConfig( _conf );
+        }
+
+        /** Name of the biome classes to use. This is one or more class names from the legend,
+          * separated by whitespace. e.g.: "forest grassland swamp" */
+        optional<std::string>& biomeClasses() { return _biomeClasses; }
+        const optional<std::string>& biomeClasses() const { return _biomeClasses; }
+
+        /** Symbology used to conigure rendering in this biome */
+        SymbolVector& symbols() { return _symbols; }
+        const SymbolVector& symbols() const { return _symbols; }
+
+    protected:
+        optional<std::string> _biomeClasses;
+        SymbolVector _symbols;
+
+    public:    
+        void fromConfig(const Config& conf);
+        Config getConfig() const;
+        virtual void mergeConfig( const Config& conf ) {
+            ConfigOptions::mergeConfig( conf );
+            fromConfig( conf );
+        }
+    };
+    typedef std::vector<GroundCoverBiomeOptions> GroundCoverBiomeOptionsVector;
+
+
+    class OSGEARTHSPLAT_EXPORT GroundCoverOptions : public ConfigOptions
+    {
+    public:
+        GroundCoverOptions(const ConfigOptions& conf = ConfigOptions());
+
+        /** Name of the land cover layer */
+        optional<std::string>& name() { return _name; }
+        const optional<std::string>& name() const { return _name; }
+
+        /** Equivalent terrain LOD at which this layer will render */
+        optional<unsigned>& lod() { return _lod; }
+        const optional<unsigned>& lod() const { return _lod; }
+
+        /** Maximum viewing distance from camera */
+        optional<float>& maxDistance() { return _maxDistance; }
+        const optional<float>& maxDistance() const { return _maxDistance; }
+
+        /** Wind speed [0..1] */
+        optional<float>& wind() { return _wind; }
+        const optional<float>& wind() const { return _wind; }
+
+        /** Metric that controls the number of land cover instances will render
+          * in a given area. [1..5] */
+        optional<float>& density() { return _density; }
+        const optional<float>& density() const { return _density; }
+
+        /** Percentage of land that this layer's instances will cover [0..1]. Lower
+          * values will result is more "patchiness" of placement. */
+        optional<float>& fill() { return _fill; }
+        const optional<float>& fill() const { return _fill; }
+
+        /** Brightness factor for rendering [1..], 1 = default */
+        optional<float>& brightness() { return _brightness; }
+        const optional<float>& brightness() const { return _brightness; }
+
+        /** Contrast factor for rendering [0..], 0 = default */
+        optional<float>& contrast() { return _contrast; }
+        const optional<float>& contrast() const { return _contrast; }
+
+        /** Biomes comprising this layer. */
+        GroundCoverBiomeOptionsVector& biomes() { return _biomes; }
+        const GroundCoverBiomeOptionsVector& biomes() const { return _biomes; }
+
+    public:
+        virtual Config getConfig() const;
+    protected:
+        virtual void mergeConfig(const Config& conf) {
+            ConfigOptions::mergeConfig(conf);
+            fromConfig(conf);
+        }
+        void fromConfig(const Config& conf);
+
+    protected:
+        optional<std::string> _name;
+        optional<unsigned> _lod;
+        optional<float> _maxDistance;
+        optional<float> _density;
+        optional<float> _fill;
+        optional<float> _wind;
+        optional<float> _brightness;
+        optional<float> _contrast;
+        GroundCoverBiomeOptionsVector _biomes;
+    };
+
+
+    class OSGEARTHSPLAT_EXPORT GroundCoverBillboard
+    {
+    public:
+        GroundCoverBillboard(osg::Image* image, float width, float height)
+            : _image(image), _width(width), _height(height) { }
+
+        osg::ref_ptr<osg::Image> _image;
+        float                    _width;
+        float                    _height;
+    };
+
+    typedef std::vector<GroundCoverBillboard> GroundCoverBillboards;
+
+
+    class OSGEARTHSPLAT_EXPORT GroundCoverBiome : public osg::Referenced
+    {
+    public:
+        GroundCoverBiome() : _code(0) { }
+
+        /** classification names for this biome (space separated) */
+        void setClasses( const std::string& value ) { _classNames = value; }
+        const std::string& getClasses() const { return _classNames; }
+
+        /** Resources that may be used to render land cover in this biome */
+        GroundCoverBillboards& getBillboards() { return _billboards; }
+        const GroundCoverBillboards& getBillboards() const { return _billboards; }
+
+    protected:
+        virtual ~GroundCoverBiome() { }
+
+        std::string    _classNames;
+        int            _code;
+        GroundCoverBillboards _billboards;
+
+    public:
+        bool configure(const ConfigOptions& conf, const osgDB::Options* dbo);
+    };
+
+    typedef std::vector< osg::ref_ptr<GroundCoverBiome> > GroundCoverBiomes;
+
+
+    /**
+     * Interface for controlling ground cover appearance.
+     */
+    class OSGEARTHSPLAT_EXPORT GroundCover : public osg::Referenced
+    {
+    public:
+        GroundCover(const GroundCoverOptions& options);
+        
+        GroundCoverOptions& options() { return _options; }
+        const GroundCoverOptions& options() const { return _options; }
+
+        /** Name of this layer */
+        //void setName(const std::string& name) { _name = name; }
+        const std::string& getName() const { return options().name().get(); }
+
+        /** Biomes comprising this layer. */
+        GroundCoverBiomes& getBiomes() { return _biomes; }
+        const GroundCoverBiomes& getBiomes() const { return _biomes; }
+
+        //! Wind factor
+        void setWind(float wind);
+        float getWind() const;
+
+        //! Density
+        void setDensity(float density);
+        float getDensity() const;
+
+        //! Fill
+        void setFill(float fill);
+        float getFill() const;
+
+        //! Max draw distance
+        void setMaxDistance(float distance);
+        float getMaxDistance() const;
+
+        //! Brightness
+        void setBrightness(float value);
+        float getBrightness() const;
+
+        //! Constrast
+        void setContrast(float value);
+        float getContrast() const;
+
+    public:
+
+        int getTotalNumBillboards() const;
+
+        /** Creates the shader that contains the GLSL APOI for accessing this layer's information */
+        osg::Shader* createShader() const;
+
+        /** Creates the shader that resolves land cover information into billboard data. */
+        //osg::Shader* createPredicateShader(const Coverage*) const;
+        osg::Shader* createPredicateShader(LandCoverDictionary*, LandCoverLayer*) const;
+
+        /** Builds the texture object containing all the data for this layer. */
+        osg::Texture* createTexture() const;
+
+        /** The stateset containing the shaders and state for rendering this layer. */
+        osg::StateSet* getOrCreateStateSet();
+
+        osg::StateSet* getStateSet() const { return _stateSet.get(); }
+
+    protected:
+        virtual ~GroundCover() { }
+
+        GroundCoverOptions _options;
+        GroundCoverBiomes _biomes;
+
+        osg::ref_ptr<osg::StateSet> _stateSet;
+
+    public:
+        bool configure(const osgDB::Options* readOptions);
+    };
+
+} } // namespace osgEarth::Splat
+
+#endif // OSGEARTH_PROCEDURAL_GroundCover
diff --git a/src/osgEarthSplat/GroundCover.FS.glsl b/src/osgEarthSplat/GroundCover.FS.glsl
new file mode 100644
index 0000000..fb37d2c
--- /dev/null
+++ b/src/osgEarthSplat/GroundCover.FS.glsl
@@ -0,0 +1,29 @@
+#version 330
+#pragma vp_name       Land cover billboard texture application
+#pragma vp_entryPoint oe_GroundCover_fragment
+#pragma vp_location   fragment_coloring
+
+#pragma import_defines(OE_GROUNDCOVER_HAS_MULTISAMPLES)
+
+
+uniform sampler2DArray oe_GroundCover_billboardTex;
+uniform float oe_GroundCover_exposure;
+in vec2 oe_GroundCover_texCoord;
+in vec4 oe_layer_tilec;
+
+flat in float oe_GroundCover_arrayIndex; // from GroundCover.GS.glsl
+
+void oe_GroundCover_fragment(inout vec4 color)
+{    
+    // modulate the texture
+    color = texture(oe_GroundCover_billboardTex, vec3(oe_GroundCover_texCoord, oe_GroundCover_arrayIndex)) * color;
+    color.rgb *= oe_GroundCover_exposure;
+    
+    // if multisampling is off, use alpha-discard.
+#ifndef OE_GROUNDCOVER_HAS_MULTISAMPLES
+    if (color.a < 0.15)
+        discard;
+#endif
+    //if ( !oe_terrain_hasMultiSamples && color.a < 0.15 )
+    //    discard;
+}
diff --git a/src/osgEarthSplat/LandCover.GS.glsl b/src/osgEarthSplat/GroundCover.GS.glsl
similarity index 51%
rename from src/osgEarthSplat/LandCover.GS.glsl
rename to src/osgEarthSplat/GroundCover.GS.glsl
index 806975d..8fa591b 100644
--- a/src/osgEarthSplat/LandCover.GS.glsl
+++ b/src/osgEarthSplat/GroundCover.GS.glsl
@@ -1,7 +1,10 @@
 #version $GLSL_VERSION_STR
-#pragma vp_name       LandCover geometry shader
-#pragma vp_entryPoint oe_landcover_geom
+$GLSL_DEFAULT_PRECISION_FLOAT
+#pragma vp_name       GroundCover geometry shader
+#pragma vp_entryPoint oe_GroundCover_geom
 #pragma vp_location   geometry
+
+#pragma import_defines(OE_IS_SHADOW_CAMERA, OE_GROUNDCOVER_MASK_SAMPLER, OE_GROUNDCOVER_MASK_MATRIX)
                 
 layout(triangles)        in;        // triangles from the TileDrawable
 layout(triangle_strip)   out;       // output a triangle-strip billboard
@@ -13,25 +16,23 @@ void VP_EmitViewVertex();
 
 uniform float osg_FrameTime;            // Frame time (seconds) used for wind animation
                 
-uniform float oe_landcover_width;           // width of each billboard
-uniform float oe_landcover_height;          // height of each billboard
-uniform float oe_landcover_ao;              // fake ambient occlusion of ground verts (0=full)
+uniform float oe_GroundCover_width;           // width of each billboard
+uniform float oe_GroundCover_height;          // height of each billboard
+uniform float oe_GroundCover_ao;              // fake ambient occlusion of ground verts (0=full)
 
-uniform float oe_landcover_fill;            // percentage of points that make it through, based on noise function
-uniform float oe_landcover_windFactor;      // wind blowing the foliage
-uniform float oe_landcover_maxDistance;     // distance at which flora disappears
+uniform float oe_GroundCover_fill;            // percentage of points that make it through, based on noise function
+uniform float oe_GroundCover_windFactor;      // wind blowing the foliage
+uniform float oe_GroundCover_maxDistance;     // distance at which flora disappears
 
-uniform float oe_landcover_contrast;
-uniform float oe_landcover_brightness;
+uniform float oe_GroundCover_contrast;
+uniform float oe_GroundCover_brightness;
 
 uniform sampler2D oe_tile_elevationTex;
 uniform mat4      oe_tile_elevationTexMatrix;
 uniform float     oe_tile_elevationSize;
 
-uniform bool oe_isShadowCamera;
-
 // Noise texture:
-uniform sampler2D oe_splat_noiseTex;
+uniform sampler2D oe_GroundCover_noiseTex;
 
 // different noise texture channels:
 #define NOISE_SMOOTH   0
@@ -43,29 +44,29 @@ uniform sampler2D oe_splat_noiseTex;
 in vec4 oe_layer_tilec;
 
 // Output grass texture coordinates to the fragment shader
-out vec2 oe_landcover_texCoord;
+out vec2 oe_GroundCover_texCoord;
 
 // Input from the TCS that 
-//flat in int oe_landcover_biomeIndex;
+//flat in int oe_GroundCover_biomeIndex;
 
 // Output that selects the land cover texture from the texture array (non interpolated)
-flat out float oe_landcover_arrayIndex;
+flat out float oe_GroundCover_arrayIndex;
 
-struct oe_landcover_Biome {
+struct oe_GroundCover_Biome {
     int firstBillboardIndex;
     int numBillboards;
     float density;
     float fill;
     vec2 maxWidthHeight;
 };
-void oe_landcover_getBiome(in int biomeIndex, out oe_landcover_Biome biome);
+void oe_GroundCover_getBiome(in int biomeIndex, out oe_GroundCover_Biome biome);
 
-struct oe_landcover_Billboard {
+struct oe_GroundCover_Billboard {
     int arrayIndex;
     float width;
     float height;
 };
-void oe_landcover_getBillboard(in int billboardIndex, out oe_landcover_Billboard bb);
+void oe_GroundCover_getBillboard(in int billboardIndex, out oe_GroundCover_Billboard bb);
 
 
 // Output colors/normals:
@@ -79,16 +80,16 @@ in vec3 oe_UpVectorView;
 float oe_terrain_getElevation(in vec2);
 
 // Generated in code
-int oe_landcover_getBiomeIndex(in vec4);
-
-uniform bool oe_landcover_useMask;
-uniform sampler2D MASK_SAMPLER;
-uniform mat4 MASK_TEXTURE;
+int oe_GroundCover_getBiomeIndex(in vec4);
 
+#ifdef OE_GROUNDCOVER_MASK_SAMPLER
+uniform sampler2D OE_GROUNDCOVER_MASK_SAMPLER;
+uniform mat4 OE_GROUNDCOVER_MASK_MATRIX;
+#endif
 
 // Sample the elevation texture and move the vertex accordingly.
 void
-oe_landcover_clamp(inout vec4 vert_view, in vec3 up, vec2 UV)
+oe_GroundCover_clamp(inout vec4 vert_view, in vec3 up, vec2 UV)
 {
     float elev = oe_terrain_getElevation( UV );
     vert_view.xyz += up*elev;
@@ -96,7 +97,7 @@ oe_landcover_clamp(inout vec4 vert_view, in vec3 up, vec2 UV)
 
 // Generate a pseudo-random value in the specified range:
 float
-oe_landcover_rangeRand(float minValue, float maxValue, vec2 co)
+oe_GroundCover_rangeRand(float minValue, float maxValue, vec2 co)
 {
     float t = fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453);
     return minValue + t*(maxValue-minValue);
@@ -104,18 +105,18 @@ oe_landcover_rangeRand(float minValue, float maxValue, vec2 co)
 
 // Generate a wind-perturbation value
 float
-oe_landcover_applyWind(float time, float factor, float randOffset)
+oe_GroundCover_applyWind(float time, float factor, float randOffset)
 {
    return sin(time + randOffset) * factor;
 }
 
 // Generate a pseudo-random barycentric point inside a triangle.
 vec3
-oe_landcover_getRandomBarycentricPoint(vec2 seed)
+oe_GroundCover_getRandomBarycentricPoint(vec2 seed)
 {
     vec3 b;
-    b[0] = oe_landcover_rangeRand(0.0, 1.0, seed.xy);
-    b[1] = oe_landcover_rangeRand(0.0, 1.0, seed.yx);
+    b[0] = oe_GroundCover_rangeRand(0.0, 1.0, seed.xy);
+    b[1] = oe_GroundCover_rangeRand(0.0, 1.0, seed.yx);
     if (b[0]+b[1] >= 1.0)
     {
         b[0] = 1.0 - b[0];
@@ -127,13 +128,13 @@ oe_landcover_getRandomBarycentricPoint(vec2 seed)
 
 // MAIN ENTRY POINT  
 void
-oe_landcover_geom()
+oe_GroundCover_geom()
 {    
     vec4 center = vec4(0,0,0,1);
     vec2 tileUV = vec2(0,0);
     
     // gen a random point within the input triangle
-    vec3 b = oe_landcover_getRandomBarycentricPoint(gl_in[0].gl_Position.xy);
+    vec3 b = oe_GroundCover_getRandomBarycentricPoint(gl_in[0].gl_Position.xy);
     
     // Load the triangle data and compute the new position and tile coords
     // using the barycentric coordinates.
@@ -150,7 +151,7 @@ oe_landcover_geom()
     }
    
     // Look up the biome at this point:
-    int biomeIndex = oe_landcover_getBiomeIndex(vec4(tileUV,0,1));
+    int biomeIndex = oe_GroundCover_getBiomeIndex(vec4(tileUV,0,1));
     if ( biomeIndex < 0 )
     {
         // No biome defined; bail out without emitting any geometry.
@@ -158,36 +159,35 @@ oe_landcover_geom()
     }
     
     // If we're using a mask texture, sample it now:
-    if ( oe_landcover_useMask )
+#ifdef OE_GROUNDCOVER_MASK_SAMPLER
+    float mask = texture(OE_GROUNDCOVER_MASK_SAMPLER, (OE_GROUNDCOVER_MASK_MATRIX*vec4(tileUV,0,1)).st).a;
+    if ( mask > 0.0 )
     {
-        float mask = texture(MASK_SAMPLER, (MASK_TEXTURE*vec4(tileUV,0,1)).st).a;
-        if ( mask > 0.0 )
-        {
-            // Failed to pass the mask; no geometry emitted.
-            return;
-        }
+        // Failed to pass the mask; no geometry emitted.
+        return;
     }
+#endif
     
     // Transform to view space.
     vec4 center_view = gl_ModelViewMatrix * center;
     vec3 up_view     = oe_UpVectorView;
     
     // Clamp the center point to the elevation.
-    oe_landcover_clamp(center_view, up_view, tileUV);
+    oe_GroundCover_clamp(center_view, up_view, tileUV);
 
     // Calculate the normalized camera range:
-    float nRange = clamp(-center_view.z/oe_landcover_maxDistance, 0.0, 1.0);
+    float nRange = clamp(-center_view.z/oe_GroundCover_maxDistance, 0.0, 1.0);
 
     // Distance culling:
     if ( nRange == 1.0 )
         return;
 
     // look up biome:
-    oe_landcover_Biome biome;
-    oe_landcover_getBiome(biomeIndex, biome);
+    oe_GroundCover_Biome biome;
+    oe_GroundCover_getBiome(biomeIndex, biome);
 
     // sample the noise texture.
-    vec4 noise = texture(oe_splat_noiseTex, tileUV);
+    vec4 noise = texture(oe_GroundCover_noiseTex, tileUV);
 
     // a pseudo-random scale factor to the width and height of a billboard
     float sizeScale = abs(1.0 + noise[NOISE_RANDOM_2]);
@@ -206,20 +206,20 @@ oe_landcover_geom()
 
     // discard instances based on noise value threshold (coverage). If it passes,
     // scale the noise value back up to [0..1]
-    if ( noise[NOISE_SMOOTH] > oe_landcover_fill )
+    if ( noise[NOISE_SMOOTH] > oe_GroundCover_fill )
         return;
     else
-        noise[NOISE_SMOOTH] /= oe_landcover_fill;
+        noise[NOISE_SMOOTH] /= oe_GroundCover_fill;
 
     // select a billboard seemingly at random. Need to scale n to account for the fill limit first though.
     int billboardIndex = biome.firstBillboardIndex + int( floor(noise[NOISE_RANDOM] * float(biome.numBillboards) ) );
     billboardIndex = min(billboardIndex, biome.firstBillboardIndex + biome.numBillboards - 1);
 
-    oe_landcover_Billboard billboard;
-    oe_landcover_getBillboard(billboardIndex, billboard);
+    oe_GroundCover_Billboard billboard;
+    oe_GroundCover_getBillboard(billboardIndex, billboard);
     
     // pass the billboard's array index along to the fragment shader.
-    oe_landcover_arrayIndex = float(billboard.arrayIndex);
+    oe_GroundCover_arrayIndex = float(billboard.arrayIndex);
     
 	
     // push the falloff closer to the max distance.
@@ -240,95 +240,104 @@ oe_landcover_geom()
 	// compute the billboard corners in view space.
     vec4 LL, LR, UL, UR;
     
-    if ( oe_isShadowCamera == false )
+
+#ifdef OE_IS_SHADOW_CAMERA
+    
+    vec3 tangentVector = gl_NormalMatrix * vec3(1,0,0); // vector pointing east-ish.
+    vec3 halfWidthTangentVector = cross(tangentVector, up_view) * 0.5 * width;
+    vec3 heightVector = up_view*height;
+
+    vp_Color = vec4(1,1,1,falloff);
+
+    for(int i=0; i<2; ++i)
     {
-        vec3 tangentVector = normalize(cross(vec3(0,0,-1), up_view));
-        vec3 halfWidthTangentVector = tangentVector * 0.5 * width;
-        vec3 heightVector = up_view*height;
-        
         LL = vec4(center_view.xyz - halfWidthTangentVector, 1.0);
         LR = vec4(center_view.xyz + halfWidthTangentVector, 1.0);
         UL = vec4(LL.xyz + heightVector, 1.0);
         UR = vec4(LR.xyz + heightVector, 1.0);
-                      
-        // TODO: animate based on wind parameters.
-        float nw = noise[NOISE_SMOOTH];
-        float wind = width*oe_landcover_windFactor*nw;
-        UL.x += oe_landcover_applyWind(osg_FrameTime*(1+nw), wind, UL.x);
-        UR.x += oe_landcover_applyWind(osg_FrameTime*(1-nw), wind, tileUV.t);
     
-        // Color variation, brightness, and contrast:
-        vec3 color = vec3( noise[NOISE_RANDOM_2] );
-        color = ( ((color - 0.5) * oe_landcover_contrast + 0.5) * oe_landcover_brightness);
-
-        vp_Color = vec4(color*oe_landcover_ao, falloff);
-
-        // calculates normals:
-        vec3 faceNormalVector = normalize(cross(tangentVector, heightVector));
-        float blend = 0.25 + (noise[NOISE_RANDOM_2]*0.25);
-        vec3 Lnormal = mix(-tangentVector, faceNormalVector, blend);
-        vec3 Rnormal = mix( tangentVector, faceNormalVector, blend);
-
+        // calculates normal:
+        vp_Normal = normalize(cross(tangentVector, heightVector));
+        
         gl_Position = LL;
-        oe_landcover_texCoord = vec2(0,0);
-        vp_Normal = Lnormal;
+        oe_GroundCover_texCoord = vec2(0,0);
         VP_EmitViewVertex();
     
         gl_Position = LR;
-        oe_landcover_texCoord = vec2(1,0);
-        vp_Normal = Rnormal;
-        VP_EmitViewVertex();
-
-        vp_Color = vec4(color, falloff);      
+        oe_GroundCover_texCoord = vec2(1,0);
+        VP_EmitViewVertex();    
 
         gl_Position = UL;
-        oe_landcover_texCoord = vec2(0,1);
-        vp_Normal = Lnormal;
+        oe_GroundCover_texCoord = vec2(0,1);
         VP_EmitViewVertex();
 
-        oe_landcover_texCoord = vec2(1,1);
-        vp_Normal = Rnormal;
+        oe_GroundCover_texCoord = vec2(1,1);
         gl_Position = UR;
         VP_EmitViewVertex();
                     
         EndPrimitive();
-    }
-    else
-    {
-        // generating cross-hatch geometry (for shadowing)
 
-        vec3 eastVector = gl_NormalMatrix * vec3(1,0,0);
-        vec3 halfWidthTangentVector = cross(eastVector, up_view) * 0.5 * width;
-        vec3 heightVector = up_view*height;
+        tangentVector = gl_NormalMatrix * vec3(0,1,0);
+        halfWidthTangentVector = cross(tangentVector, up_view) * 0.5 * width;
+    }
 
-        vp_Color = vec4(1,1,1,falloff);
+#else // normal render camera - draw as a billboard:
 
-        for(int i=0; i<2; ++i)
-        {
-            LL = vec4(center_view.xyz - halfWidthTangentVector, 1.0);
-            LR = vec4(center_view.xyz + halfWidthTangentVector, 1.0);
-            UL = vec4(LL.xyz + heightVector, 1.0);
-            UR = vec4(LR.xyz + heightVector, 1.0);
-    
-            gl_Position = LL;
-            oe_landcover_texCoord = vec2(0,0);
-            VP_EmitViewVertex();
+    vec3 tangentVector = normalize(cross(vec3(0,0,-1), up_view));
+    vec3 halfWidthTangentVector = tangentVector * 0.5 * width;
+    vec3 heightVector = up_view*height;
+        
+    LL = vec4(center_view.xyz - halfWidthTangentVector, 1.0);
+    LR = vec4(center_view.xyz + halfWidthTangentVector, 1.0);
+    UL = vec4(LL.xyz + heightVector, 1.0);
+    UR = vec4(LR.xyz + heightVector, 1.0);
+                      
+    // TODO: animate based on wind parameters.
+    float nw = noise[NOISE_SMOOTH];
+    float wind = width*oe_GroundCover_windFactor*nw;
+    UL.x += oe_GroundCover_applyWind(osg_FrameTime*(1+nw), wind, UL.x);
+    UR.x += oe_GroundCover_applyWind(osg_FrameTime*(1-nw), wind, tileUV.t);
     
-            gl_Position = LR;
-            oe_landcover_texCoord = vec2(1,0);
-            VP_EmitViewVertex();    
+    // Color variation, brightness, and contrast:
+    vec3 color = vec3( noise[NOISE_RANDOM_2] );
+    color = ( ((color - 0.5) * oe_GroundCover_contrast + 0.5) * oe_GroundCover_brightness);
 
-            gl_Position = UL;
-            oe_landcover_texCoord = vec2(0,1);
-            VP_EmitViewVertex();
+    vp_Color = vec4(color*oe_GroundCover_ao, falloff);
 
-            oe_landcover_texCoord = vec2(1,1);
-            gl_Position = UR;
-            VP_EmitViewVertex();
-                    
-            EndPrimitive();
+    // calculates normals:
+    vec3 faceNormalVector = normalize(cross(tangentVector, heightVector));
 
-            halfWidthTangentVector = cross(halfWidthTangentVector, up_view);
-        }
-    }
+    // if we are looking straight-ish down on the billboard, don't bother with it
+    if (abs(dot(normalize(center_view.xyz), faceNormalVector)) < 0.01)
+        return;
+
+    float blend = 0.25 + (noise[NOISE_RANDOM_2]*0.25);
+    vec3 Lnormal = mix(-tangentVector, faceNormalVector, blend);
+    vec3 Rnormal = mix( tangentVector, faceNormalVector, blend);
+
+    gl_Position = LL;
+    oe_GroundCover_texCoord = vec2(0,0);
+    vp_Normal = Lnormal;
+    VP_EmitViewVertex();
+    
+    gl_Position = LR;
+    oe_GroundCover_texCoord = vec2(1,0);
+    vp_Normal = Rnormal;
+    VP_EmitViewVertex();
+
+    vp_Color = vec4(color, falloff);      
+
+    gl_Position = UL;
+    oe_GroundCover_texCoord = vec2(0,1);
+    vp_Normal = Lnormal;
+    VP_EmitViewVertex();
+
+    oe_GroundCover_texCoord = vec2(1,1);
+    vp_Normal = Rnormal;
+    gl_Position = UR;
+    VP_EmitViewVertex();
+                    
+    EndPrimitive();
+    
+#endif // !OE_IS_SHADOW_CAMERA
 }
diff --git a/src/osgEarthSplat/LandCover.TCS.glsl b/src/osgEarthSplat/GroundCover.TCS.glsl
similarity index 55%
rename from src/osgEarthSplat/LandCover.TCS.glsl
rename to src/osgEarthSplat/GroundCover.TCS.glsl
index 933ebff..9b02374 100644
--- a/src/osgEarthSplat/LandCover.TCS.glsl
+++ b/src/osgEarthSplat/GroundCover.TCS.glsl
@@ -4,44 +4,44 @@
  * TCS that assigns a patch grid density.
  */
  
-#pragma vp_name       LandCover tessellation control shader
-#pragma vp_entryPoint oe_landcover_configureTess
+#pragma vp_name       GroundCover tessellation control shader
+#pragma vp_entryPoint oe_GroundCover_configureTess
 #pragma vp_location   tess_control
 
 layout(vertices=3) out;
 
-uniform float oe_landcover_density;
+uniform float oe_GroundCover_density;
 
 // per-vertex tile coordinates
 vec4 oe_layer_tilec;
 
 // SDK function to sample the coverage data
-int oe_landcover_getBiomeIndex(in vec4);
+int oe_GroundCover_getBiomeIndex(in vec4);
 
 // SDK function to load per-vertex data
 void VP_LoadVertex(in int);
 
 // MAIN ENTRY POINT                
-void oe_landcover_configureTess()
+void oe_GroundCover_configureTess()
 {
 	if (gl_InvocationID == 0)
 	{
-        float d = oe_landcover_density;
+        float d = oe_GroundCover_density;
 
         VP_LoadVertex(0);
-        if ( oe_landcover_getBiomeIndex(oe_layer_tilec) >= 0 ) {
-            d = oe_landcover_density;
+        if ( oe_GroundCover_getBiomeIndex(oe_layer_tilec) >= 0 ) {
+            d = oe_GroundCover_density;
         }
         else {
             VP_LoadVertex(1);
-            if ( oe_landcover_getBiomeIndex(oe_layer_tilec) >= 0 ) {
-                d = oe_landcover_density;
+            if ( oe_GroundCover_getBiomeIndex(oe_layer_tilec) >= 0 ) {
+                d = oe_GroundCover_density;
                 VP_LoadVertex(0);
             }
             else {
                 VP_LoadVertex(2);
-                if ( oe_landcover_getBiomeIndex(oe_layer_tilec) >= 0 ) {
-                    d = oe_landcover_density;
+                if ( oe_GroundCover_getBiomeIndex(oe_layer_tilec) >= 0 ) {
+                    d = oe_GroundCover_density;
                     VP_LoadVertex(0);
                 }
             }
diff --git a/src/osgEarthSplat/LandCover.TES.glsl b/src/osgEarthSplat/GroundCover.TES.glsl
similarity index 90%
rename from src/osgEarthSplat/LandCover.TES.glsl
rename to src/osgEarthSplat/GroundCover.TES.glsl
index 6f465e4..8ce2f78 100644
--- a/src/osgEarthSplat/LandCover.TES.glsl
+++ b/src/osgEarthSplat/GroundCover.TES.glsl
@@ -1,7 +1,7 @@
 #version 410
 
-#pragma vp_name       LandCover TES Shader
-#pragma vp_entryPoint oe_landcover_tessellate
+#pragma vp_name       GroundCover TES Shader
+#pragma vp_entryPoint oe_GroundCover_tessellate
 #pragma vp_location   tess_eval
 
 // osgEarth terrain is always CCW winding
@@ -41,7 +41,7 @@ vec4 VP_Interpolate3(vec4 a, vec4 b, vec4 c)
 vec3 vp_Normal;
 
 // simplest possible pass-though:
-void oe_landcover_tessellate()
+void oe_GroundCover_tessellate()
 {
     VP_Interpolate3();
     // Must re-normalize the normal vector since interpolation was linear?
diff --git a/src/osgEarthSplat/GroundCover.cpp b/src/osgEarthSplat/GroundCover.cpp
new file mode 100644
index 0000000..7a71373
--- /dev/null
+++ b/src/osgEarthSplat/GroundCover.cpp
@@ -0,0 +1,448 @@
+#include "GroundCover"
+#include "Coverage"
+#include "SplatCatalog"
+#include "SplatCoverageLegend"
+#include "Zone"
+#include "SplatShaders"
+
+#include <osgEarth/Map>
+#include <osgEarth/ImageLayer>
+#include <osgEarth/ImageUtils>
+#include <osgEarth/VirtualProgram>
+#include <osgEarthSymbology/BillboardSymbol>
+
+#include <osg/Texture2DArray>
+
+using namespace osgEarth;
+using namespace osgEarth::Splat;
+using namespace osgEarth::Symbology;
+
+#define LC "[GroundCover] "
+
+//........................................................................
+
+void
+GroundCoverBiomeOptions::fromConfig(const Config& conf) 
+{
+    conf.getIfSet("classes", _biomeClasses);
+    const ConfigSet& symbols = conf.children();
+    for (ConfigSet::const_iterator i = symbols.begin(); i != symbols.end(); ++i) {
+        Symbol* s = SymbolRegistry::instance()->create(*i);
+        if (s) {
+            _symbols.push_back(s);
+        }
+    }
+}
+
+Config
+GroundCoverBiomeOptions::getConfig() const 
+{
+    Config conf("biome");
+    conf.set("classes", _biomeClasses);
+    for (int i = 0; i < _symbols.size(); ++i) {
+        Config symbolConf = _symbols[i]->getConfig();
+        if (!symbolConf.empty()) {
+            conf.add(symbolConf);
+        }
+    }
+    return conf;
+}
+
+//........................................................................
+
+GroundCoverOptions::GroundCoverOptions(const ConfigOptions& co) :
+ConfigOptions(co),
+_lod(14),
+_maxDistance(1000.0f),
+_density(1.0f),
+_fill(1.0f),
+_wind(0.0f),
+_brightness(1.0f),
+_contrast(0.5f)
+{
+    fromConfig(_conf);
+}
+
+Config
+GroundCoverOptions::getConfig() const
+{
+    Config conf = ConfigOptions::getConfig();
+    conf.key() = "groundcover";
+    conf.set("name", _name);
+    conf.set("lod", _lod);
+    conf.set("max_distance", _maxDistance);
+    conf.set("density", _density);
+    conf.set("fill", _fill);
+    conf.set("wind", _wind);
+    conf.set("brightness", _brightness);
+    conf.set("contrast", _contrast);
+    if (_biomes.size() > 0) {
+        Config biomes("biomes");
+        for (int i = 0; i < _biomes.size(); ++i) {
+            biomes.add("biome", _biomes[i].getConfig());
+        }
+        conf.update(biomes);
+    }
+    return conf;
+}
+
+void
+GroundCoverOptions::fromConfig(const Config& conf)
+{
+    conf.getIfSet("name", _name);
+    conf.getIfSet("lod", _lod);
+    conf.getIfSet("max_distance", _maxDistance);
+    conf.getIfSet("density", _density);
+    conf.getIfSet("fill", _fill);
+    conf.getIfSet("wind", _wind);
+    conf.getIfSet("brightness", _brightness);
+    conf.getIfSet("contrast", _contrast);
+    const Config* biomes = conf.child_ptr("biomes");
+    if (biomes) {
+        const ConfigSet& biomesVec = biomes->children();
+        for (ConfigSet::const_iterator i = biomesVec.begin(); i != biomesVec.end(); ++i) {
+            _biomes.push_back(GroundCoverBiomeOptions(*i));
+        }
+    }
+}
+
+//............................................................................
+
+GroundCover::GroundCover(const GroundCoverOptions& in) :
+_options(in)
+{
+    //nop
+}
+
+bool
+GroundCover::configure(const osgDB::Options* readOptions)
+{
+    if ( options().biomes().size() == 0 )
+    {
+        OE_WARN << LC << "No biomes defined in layer \"" << getName() << "\"\n";
+        return false;
+    }
+
+    for(int i=0; i<_options.biomes().size(); ++i)
+    {
+        osg::ref_ptr<GroundCoverBiome> biome = new GroundCoverBiome();
+        _biomes.push_back( biome.get() );
+    }
+
+    for (int i = 0; i<_biomes.size(); ++i)
+    {
+        if ( _biomes[i]->configure( options().biomes()[i], readOptions ) == false )
+        {
+            OE_WARN << LC << "One of the biomes in layer \"" << getName() << "\" is improperly configured\n";
+            return false;
+        }
+    }
+
+    return true;
+}
+
+int
+GroundCover::getTotalNumBillboards() const
+{
+    int count = 0;
+    for(int i=0; i<_biomes.size(); ++i)
+    {
+        count += _biomes[i]->getBillboards().size();
+    }
+    return count;
+}
+
+osg::StateSet*
+GroundCover::getOrCreateStateSet()
+{
+    if ( !_stateSet.valid() )
+    {
+        _stateSet = new osg::StateSet();
+
+        _stateSet->addUniform(new osg::Uniform("oe_GroundCover_windFactor", options().wind().get()));
+        _stateSet->addUniform(new osg::Uniform("oe_GroundCover_noise", 1.0f));
+        _stateSet->addUniform(new osg::Uniform("oe_GroundCover_ao", 0.5f));
+        _stateSet->addUniform(new osg::Uniform("oe_GroundCover_exposure", 1.0f));
+
+        _stateSet->addUniform(new osg::Uniform("oe_GroundCover_density", options().density().get()));
+        _stateSet->addUniform(new osg::Uniform("oe_GroundCover_fill", options().fill().get()));
+        _stateSet->addUniform(new osg::Uniform("oe_GroundCover_maxDistance", options().maxDistance().get()));
+
+        _stateSet->addUniform(new osg::Uniform("oe_GroundCover_brightness", options().brightness().get()));
+        _stateSet->addUniform(new osg::Uniform("oe_GroundCover_contrast", options().contrast().get()));
+    }
+
+    return _stateSet.get();
+}
+
+#define SET_GET_UNIFORM(NAME, UNIFORM) \
+    void GroundCover::set##NAME (float value) { getOrCreateStateSet()->getUniform(UNIFORM)->set(value); } \
+    float GroundCover::get##NAME () const { float value = 0.0f; if (getStateSet()) getStateSet()->getUniform(UNIFORM)->get(value); return value; }
+
+SET_GET_UNIFORM(Wind, "oe_GroundCover_windFactor")
+SET_GET_UNIFORM(Density, "oe_GroundCover_density")
+SET_GET_UNIFORM(Fill, "oe_GroundCover_fill")
+SET_GET_UNIFORM(MaxDistance, "oe_GroundCover_maxDistance")
+SET_GET_UNIFORM(Brightness, "oe_GroundCover_brightness")
+SET_GET_UNIFORM(Contrast, "oe_GroundCover_contrast")
+
+
+osg::Shader*
+GroundCover::createShader() const
+{
+    std::stringstream biomeBuf;
+    std::stringstream billboardBuf;
+
+    int totalBillboards = getTotalNumBillboards();
+
+    // encode all the biome data.
+    biomeBuf << 
+        "struct oe_GroundCover_Biome { \n"
+        "    int firstBillboardIndex; \n"
+        "    int numBillboards; \n"
+        "    float density; \n"
+        "    float fill; \n"
+        "    vec2 maxWidthHeight; \n"
+        "}; \n"
+
+        "const oe_GroundCover_Biome oe_GroundCover_biomes[" << getBiomes().size() << "] = oe_GroundCover_Biome[" << getBiomes().size() << "] ( \n";
+
+    billboardBuf <<
+        "struct oe_GroundCover_Billboard { \n"
+        "    int arrayIndex; \n"
+        "    float width; \n"
+        "    float height; \n"
+        "}; \n"
+
+        "const oe_GroundCover_Billboard oe_GroundCover_billboards[" << totalBillboards << "] = oe_GroundCover_Billboard[" << totalBillboards << "](\n";
+    
+    int index = 0;
+    for(int i=0; i<getBiomes().size(); ++i)
+    {
+        const GroundCoverBiome* biome = getBiomes()[i].get();
+
+        float maxWidth = 0.0f, maxHeight = 0.0f;
+        
+        int firstIndex = index;
+
+        for(int j=0; j<biome->getBillboards().size(); ++j)
+        {
+            const GroundCoverBillboard& bb = biome->getBillboards()[j];
+
+            billboardBuf
+                << "    oe_GroundCover_Billboard("
+                << index 
+                << ", float(" << bb._width << ")"
+                << ", float(" << bb._height << ")"
+                << ")";
+            
+            ++index;
+            if ( index < totalBillboards )
+                billboardBuf << ",\n";
+
+            maxWidth = std::max(maxWidth, bb._width);
+            maxHeight = std::max(maxHeight, bb._height);
+        }
+
+        // We multiply the height x 2 below because billboards have their origin
+        // at the bottom center of the image. The GPU culling code considers the
+        // distance from this anchor point. For width, it's in the middle, but for
+        // height, it's at the bottom so we need to double it. It doubles in both
+        // directions, but that's OK since we are rarely if ever going to GPU-cull
+        // a billboard at the top of the viewport. -gw
+
+        biomeBuf << "    oe_GroundCover_Biome(" 
+            << firstIndex << ", "
+            << biome->getBillboards().size() 
+            << ", float(" << options().density().get() << ")"
+            << ", float(" << options().fill().get() << ")"
+            << ", vec2(float(" << maxWidth << "),float(" << maxHeight*2.0f << ")))";
+
+        if ( (i+1) < getBiomes().size() )
+            biomeBuf << ",\n";
+    }
+
+    biomeBuf
+        << "\n);\n";
+
+    billboardBuf
+        << "\n); \n";
+
+    biomeBuf 
+        << "void oe_GroundCover_getBiome(in int biomeIndex, out oe_GroundCover_Biome biome) { \n"
+        << "    biome = oe_GroundCover_biomes[biomeIndex]; \n"
+        << "} \n";
+        
+    billboardBuf
+        << "void oe_GroundCover_getBillboard(in int billboardIndex, out oe_GroundCover_Billboard billboard) { \n"
+        << "    billboard = oe_GroundCover_billboards[billboardIndex]; \n"
+        << "} \n";
+    
+    osg::ref_ptr<ImageLayer> layer;
+
+    osg::Shader* shader = new osg::Shader();
+    shader->setName( "GroundCoverLayer" );
+    shader->setShaderSource( Stringify() << "#version 330\n" << biomeBuf.str() << "\n" << billboardBuf.str() );
+    return shader;
+}
+
+osg::Shader*
+GroundCover::createPredicateShader(LandCoverDictionary* landCoverDict, LandCoverLayer* layer) const
+{
+    const char* defaultCode = "int oe_GroundCover_getBiomeIndex(in vec4 coords) { return -1; }\n";
+
+    std::stringstream buf;
+    buf << "#version 330\n";
+
+        if ( !landCoverDict )
+    {
+        buf << defaultCode;
+        OE_WARN << LC << "No land cover dictionary; generating default coverage predicate\n";
+    }
+    else if ( !layer )
+    {
+        buf << defaultCode;
+        OE_WARN << LC << "No classification layer; generating default coverage predicate\n";
+    }
+    else
+    {
+        const std::string& sampler = layer->shareTexUniformName().get();
+        const std::string& matrix  = layer->shareTexMatUniformName().get();
+
+        buf << "uniform sampler2D " << sampler << ";\n"
+            << "uniform mat4 " << matrix << ";\n"
+            << "int oe_GroundCover_getBiomeIndex(in vec4 coords) { \n"
+            << "    float value = textureLod(" << sampler << ", (" << matrix << " * coords).st, 0).r;\n";
+
+        for(int biomeIndex=0; biomeIndex<getBiomes().size(); ++biomeIndex)
+        {
+            const GroundCoverBiome* biome = getBiomes()[biomeIndex].get();
+
+            if ( !biome->getClasses().empty() )
+            {
+                StringVector classes;
+                StringTokenizer(biome->getClasses(), classes, " ", "\"", false);
+
+                for(int i=0; i<classes.size(); ++i)
+                {
+                    const LandCoverClass* lcClass = landCoverDict->getClassByName(classes[i]);
+                    if (lcClass)
+                    {
+                        buf << "    if (value == " << lcClass->getValue() << ") return " << biomeIndex << "; \n";
+                    }
+                    else
+                    {
+                        OE_WARN << LC << "Land cover class \"" << classes[i] << "\" was not found in the dictionary!\n";
+                    }
+                }
+            }
+        }
+        buf << "    return -1; \n";
+        buf << "}\n";
+    }
+    
+    osg::Shader* shader = new osg::Shader();
+    shader->setName("oe GroundCover predicate function");
+    shader->setShaderSource( buf.str() );
+
+    return shader;
+}
+
+osg::Texture*
+GroundCover::createTexture() const
+{
+    osg::Texture2DArray* tex = new osg::Texture2DArray();
+
+    int arrayIndex = 0;
+    float s = -1.0f, t = -1.0f;
+
+    for(int b=0; b<getBiomes().size(); ++b)
+    {
+        const GroundCoverBiome* biome = getBiomes()[b].get();
+
+        for(int i=0; i<biome->getBillboards().size(); ++i, ++arrayIndex)
+        {
+            const GroundCoverBillboard& bb = biome->getBillboards()[i];
+
+            osg::ref_ptr<osg::Image> im;
+
+            if ( s < 0 )
+            {
+                s  = bb._image->s();
+                t  = bb._image->t();
+                im = bb._image.get();
+                tex->setTextureSize(s, t, getTotalNumBillboards());                              
+            }
+            else
+            {
+                if ( bb._image->s() != s || bb._image->t() != t )
+                {
+                    ImageUtils::resizeImage( bb._image.get(), s, t, im );
+                }
+                else
+                {
+                    im = bb._image.get();
+                }
+            }
+
+            tex->setImage( arrayIndex, im.get() );
+        }
+    }
+
+    tex->setFilter(tex->MIN_FILTER, tex->NEAREST_MIPMAP_LINEAR);
+    tex->setFilter(tex->MAG_FILTER, tex->LINEAR);
+    tex->setWrap  (tex->WRAP_S, tex->CLAMP_TO_EDGE);
+    tex->setWrap  (tex->WRAP_T, tex->CLAMP_TO_EDGE);
+    tex->setUnRefImageDataAfterApply( true );
+    tex->setMaxAnisotropy( 4.0 );
+    tex->setResizeNonPowerOfTwoHint( false );
+
+    return tex;
+}
+
+//............................................................................
+
+bool
+GroundCoverBiome::configure(const ConfigOptions& conf, const osgDB::Options* dbo)
+{
+    GroundCoverBiomeOptions in( conf );
+
+    if ( in.biomeClasses().isSet() )
+        setClasses( in.biomeClasses().get() );
+
+    for(SymbolVector::const_iterator i = in.symbols().begin(); i != in.symbols().end(); ++i)
+    {
+        const BillboardSymbol* bs = dynamic_cast<BillboardSymbol*>( i->get() );
+        if ( bs )
+        {
+            URI imageURI = bs->url()->evalURI();
+
+            osg::Image* image = const_cast<osg::Image*>( bs->getImage() );
+            if ( !image )
+            {
+                image = imageURI.getImage(dbo);
+            }
+
+            if ( image )
+            {
+                getBillboards().push_back( GroundCoverBillboard(image, bs->width().get(), bs->height().get()) );
+            }
+            else
+            {
+                OE_WARN << LC << "Failed to load billboard image from \"" << imageURI.full() << "\"\n";
+            }
+        } 
+        else
+        {
+            OE_WARN << LC << "Unrecognized symbol in land cover biome\n";
+        }
+    }
+
+    if ( getBillboards().size() == 0 )
+    {
+        OE_WARN << LC << "A biome failed to install any billboards.\n";
+        return false;
+    }
+
+    return true;
+}
diff --git a/src/osgEarthSplat/GroundCoverLayer b/src/osgEarthSplat/GroundCoverLayer
new file mode 100644
index 0000000..6b41ecb
--- /dev/null
+++ b/src/osgEarthSplat/GroundCoverLayer
@@ -0,0 +1,163 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+* Copyright 2008-2012 Pelican Mapping
+* http://osgearth.org
+*
+* osgEarth is free software; you can redistribute it and/or modify
+* it under the terms of the GNU Lesser General Public License as published by
+* the Free Software Foundation; either version 2 of the License, or
+* (at your option) any later version.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+* IN THE SOFTWARE.
+*
+* You should have received a copy of the GNU Lesser General Public License
+* along with this program.  If not, see <http://www.gnu.org/licenses/>
+*/
+#ifndef OSGEARTH_SPLAT_GROUND_COVER_LAYER_H
+#define OSGEARTH_SPLAT_GROUND_COVER_LAYER_H
+
+#include "Export"
+#include "Coverage"
+#include "Zone"
+#include <osgEarth/PatchLayer>
+#include <osgEarth/LayerListener>
+#include <osgEarth/LandCoverLayer>
+
+namespace osgEarth { namespace Features {
+    class FeatureSource;
+    class FeatureSourceLayer;
+} }
+
+namespace osgEarth { namespace Splat
+{
+    using namespace osgEarth;
+    using namespace osgEarth::Features;
+
+    //! Serializable configuration for a GroundCoverLayer.
+    class OSGEARTHSPLAT_EXPORT GroundCoverLayerOptions : public PatchLayerOptions
+    {
+    public:
+        GroundCoverLayerOptions(const ConfigOptions& co = ConfigOptions()) :
+            PatchLayerOptions(co),
+            _lod(13u),
+            _castShadows(false)
+        {
+            fromConfig(_conf);
+        }
+
+        //! Required layer containing land cover data
+        optional<std::string>& landCoverLayer() { return _landCoverLayerName; }
+        const optional<std::string>& landCoverLayer() const { return _landCoverLayerName; }        
+        
+        //! Optional name of a map layer to use for masking ground cover
+        optional<std::string>& maskLayer() { return _maskLayerName; }
+        const optional<std::string>& maskLayer() const { return _maskLayerName; }
+
+        //! Geographic zones
+        std::vector<ZoneOptions>& zones() { return _zones; }
+        const std::vector<ZoneOptions>& zones() const { return _zones; }
+
+        //! Terrain LOD at which this layer should render
+        optional<unsigned>& lod() { return _lod; }
+        const optional<unsigned>& lod() const { return _lod; }
+
+        //! Whether objects in this layer should cast shadows.
+        optional<bool>& castShadows() { return _castShadows; }
+        const optional<bool>& castShadows() const { return _castShadows; }
+
+    public:
+        virtual Config getConfig() const;
+    protected:
+        virtual void mergeConfig(const Config& conf) {
+            VisibleLayerOptions::mergeConfig(conf);
+            fromConfig(conf);
+        }
+        void fromConfig(const Config& conf);
+
+    private:
+        optional<std::string> _landCoverLayerName;
+        optional<std::string> _maskLayerName;
+        ZoneOptionsVector _zones;
+        optional<unsigned> _lod;
+        optional<bool> _castShadows;
+    };
+
+
+    //! Layer that renders billboards on the ground using the GPU,
+    //! like trees, grass, rocks, etc.
+    class OSGEARTHSPLAT_EXPORT GroundCoverLayer : public PatchLayer
+    {
+    public:
+        META_Layer(osgEarthSplat, GroundCoverLayer, GroundCoverLayerOptions);
+
+        //! Construct a layer with default configuration
+        GroundCoverLayer();
+        
+        //! Construct a layer with configuration options
+        GroundCoverLayer(const GroundCoverLayerOptions& options);
+
+        //! Layer containing required coverage data
+        void setLandCoverLayer(LandCoverLayer* landCoverLayer);
+
+        //! Layer containing the land cover dictionary.
+        void setLandCoverDictionary(LandCoverDictionary* landCoverDict);
+
+        //! Masking layer (optional)
+        void setMaskLayer(ImageLayer* layer);
+
+        //! LOD at which to draw ground cover
+        unsigned getLOD() const;
+
+        //! Geogrphic zones; at least one is required
+        Zones& zones() { return _zones; }
+        const Zones& zones() const { return _zones; }
+
+    public: // Layer
+
+        //! Override
+        bool cull(const osgUtil::CullVisitor* cv, osg::State::StateSetStack& ssStack) const;
+
+    protected:
+
+        //! Override post-ctor init
+        virtual void init();
+
+        //! Override layer open
+        virtual const Status& open();
+
+    public:
+
+        //! Called when this layer is added to the map
+        virtual void addedToMap(const Map* map);
+        virtual void removedFromMap(const Map* map);
+        virtual void setTerrainResources(TerrainResources*);
+
+    protected:
+        virtual ~GroundCoverLayer() { }
+
+        osg::observer_ptr<LandCoverDictionary> _landCoverDict;
+        osg::observer_ptr<LandCoverLayer> _landCoverLayer;
+        osg::observer_ptr<ImageLayer> _maskLayer;
+
+        LayerListener<GroundCoverLayer, LandCoverDictionary> _landCoverDictListener;
+        LayerListener<GroundCoverLayer, LandCoverLayer> _landCoverListener;
+        LayerListener<GroundCoverLayer, ImageLayer> _maskLayerListener;
+
+        TextureImageUnitReservation _groundCoverTexBinding;
+        TextureImageUnitReservation _noiseBinding;
+
+        Zones _zones;
+        bool _zonesConfigured;
+
+        void buildStateSets();
+    };
+
+} } // namespace osgEarth::Splat
+
+#endif // OSGEARTH_SPLAT_GROUND_COVER_LAYER_H
diff --git a/src/osgEarthSplat/GroundCoverLayer.cpp b/src/osgEarthSplat/GroundCoverLayer.cpp
new file mode 100644
index 0000000..d28558d
--- /dev/null
+++ b/src/osgEarthSplat/GroundCoverLayer.cpp
@@ -0,0 +1,414 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+* Copyright 2008-2012 Pelican Mapping
+* http://osgearth.org
+*
+* osgEarth is free software; you can redistribute it and/or modify
+* it under the terms of the GNU Lesser General Public License as published by
+* the Free Software Foundation; either version 2 of the License, or
+* (at your option) any later version.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+* IN THE SOFTWARE.
+*
+* You should have received a copy of the GNU Lesser General Public License
+* along with this program.  If not, see <http://www.gnu.org/licenses/>
+*/
+#include "GroundCoverLayer"
+#include "SplatShaders"
+#include "NoiseTextureFactory"
+#include <osgEarth/VirtualProgram>
+#include <osgEarth/Shadowing>
+#include <osgEarthFeatures/FeatureSource>
+#include <osgEarthFeatures/FeatureSourceLayer>
+#include <osgUtil/CullVisitor>
+#include <osg/BlendFunc>
+#include <osg/Multisample>
+#include <osg/Texture2D>
+#include <cstdlib> // getenv
+
+#define LC "[GroundCoverLayer] " << getName() << ": "
+
+#define GCTEX_SAMPLER "oe_GroundCover_billboardTex"
+#define NOISE_SAMPLER "oe_GroundCover_noiseTex"
+
+using namespace osgEarth::Splat;
+
+namespace osgEarth { namespace Splat {
+    REGISTER_OSGEARTH_LAYER(splat_groundcover, GroundCoverLayer);
+} }
+
+//........................................................................
+
+Config
+GroundCoverLayerOptions::getConfig() const
+{
+    Config conf = PatchLayerOptions::getConfig();
+    conf.key() = "splat_groundcover";
+    conf.set("land_cover_layer", _landCoverLayerName);
+    conf.set("mask_layer", _maskLayerName);
+    conf.set("lod", _lod);
+    conf.set("cast_shadows", _castShadows);
+
+    Config zones("zones");
+    for (int i = 0; i < _zones.size(); ++i) {
+        Config zone = _zones[i].getConfig();
+        if (!zone.empty())
+            zones.add(zone);
+    }
+    if (!zones.empty())
+        conf.update(zones);
+    return conf;
+}
+
+void
+GroundCoverLayerOptions::fromConfig(const Config& conf)
+{
+    conf.getIfSet("land_cover_layer", _landCoverLayerName);
+    conf.getIfSet("mask_layer", _maskLayerName);
+    conf.getIfSet("lod", _lod);
+    conf.getIfSet("cast_shadows", _castShadows);
+
+    const Config* zones = conf.child_ptr("zones");
+    if (zones) {
+        const ConfigSet& children = zones->children();
+        for (ConfigSet::const_iterator i = children.begin(); i != children.end(); ++i) {
+            _zones.push_back(ZoneOptions(*i));
+        }
+    }
+}
+
+//........................................................................
+
+namespace
+{
+    struct GroundCoverLayerAcceptor : public PatchLayer::AcceptCallback
+    {
+        GroundCoverLayer* _layer;
+
+        GroundCoverLayerAcceptor(GroundCoverLayer* layer) : _layer(layer) { }
+
+        bool acceptLayer(osg::NodeVisitor& nv, const osg::Camera* camera) const
+        {
+            // if this is a shadow camera and the layer is configured to cast shadows, accept it.
+            if (osgEarth::Shadowing::isShadowCamera(camera))
+            {
+                bool use = (_layer->options().castShadows() == true);
+                return use;
+            }
+
+            // if this is a depth-pass camera (and not a shadow cam), reject it.
+            unsigned clearMask = camera->getClearMask();
+            bool isDepthCamera = ((clearMask & GL_COLOR_BUFFER_BIT) == 0u) && ((clearMask & GL_DEPTH_BUFFER_BIT) != 0u);
+            if (isDepthCamera)
+                return false;
+
+            // otherwise accept the layer.
+            return true;
+        }
+
+        bool acceptKey(const TileKey& key) const
+        {
+            return _layer->getLOD() == key.getLOD();
+        }
+
+    };
+}
+
+//........................................................................
+
+GroundCoverLayer::GroundCoverLayer() :
+PatchLayer(&_optionsConcrete),
+_options(&_optionsConcrete)
+{
+    init();
+}
+
+GroundCoverLayer::GroundCoverLayer(const GroundCoverLayerOptions& options) :
+PatchLayer(&_optionsConcrete),
+_options(&_optionsConcrete),
+_optionsConcrete(options)
+{
+    init();
+}
+
+void
+GroundCoverLayer::init()
+{
+    PatchLayer::init();
+
+    _zonesConfigured = false;
+
+    // deserialize zone data
+    for (std::vector<ZoneOptions>::const_iterator i = options().zones().begin();
+        i != options().zones().end();
+        ++i)
+    {
+        osg::ref_ptr<Zone> zone = new Zone(*i);
+        _zones.push_back(zone.get());
+    }
+
+    setAcceptCallback(new GroundCoverLayerAcceptor(this));
+}
+
+const Status&
+GroundCoverLayer::open()
+{
+    return PatchLayer::open();
+}
+
+void
+GroundCoverLayer::setLandCoverDictionary(LandCoverDictionary* layer)
+{
+    _landCoverDict = layer;
+    if (layer)
+        buildStateSets();
+}
+
+void
+GroundCoverLayer::setLandCoverLayer(LandCoverLayer* layer)
+{
+    _landCoverLayer = layer;
+    if (layer) {
+        OE_INFO << LC << "Land cover layer is \"" << layer->getName() << "\"\n";
+        buildStateSets();
+    }
+}
+
+void
+GroundCoverLayer::setMaskLayer(ImageLayer* layer)
+{
+    _maskLayer = layer;
+    if (layer)
+    {
+        OE_INFO << LC << "Mask layer is \"" << layer->getName() << "\"\n";
+        buildStateSets();
+    }
+}
+
+unsigned
+GroundCoverLayer::getLOD() const
+{
+    return options().lod().get();
+}
+
+void
+GroundCoverLayer::addedToMap(const Map* map)
+{
+    if (!_landCoverDict.valid())
+    {
+        _landCoverDictListener.listen(map, this, &GroundCoverLayer::setLandCoverDictionary);
+    }
+
+    if (!_landCoverLayer.valid() && options().landCoverLayer().isSet())
+    {
+        _landCoverListener.listen(map, options().landCoverLayer().get(), this, &GroundCoverLayer::setLandCoverLayer);
+    }
+
+    if (options().maskLayer().isSet())
+    {
+        _maskLayerListener.listen(map, options().maskLayer().get(), this, &GroundCoverLayer::setMaskLayer);
+    }
+
+    for (Zones::iterator zone = _zones.begin(); zone != _zones.end(); ++zone)
+    {
+        zone->get()->configure(map, getReadOptions());
+    }
+
+    _zonesConfigured = true;
+    
+    buildStateSets();
+}
+
+void
+GroundCoverLayer::removedFromMap(const Map* map)
+{
+    //NOP
+}
+
+void
+GroundCoverLayer::setTerrainResources(TerrainResources* res)
+{
+    if (res)
+    {
+        if (_groundCoverTexBinding.valid() == false)
+        {
+            if (res->reserveTextureImageUnitForLayer(_groundCoverTexBinding, this, "Ground cover texture catalog") == false)
+            {
+                OE_WARN << LC << "No texture unit available for ground cover texture catalog\n";
+            }
+        }
+
+        if (_noiseBinding.valid() == false)
+        {
+            if (res->reserveTextureImageUnitForLayer(_noiseBinding, this, "Ground cover noise sampler") == false)
+            {
+                OE_WARN << LC << "No texture unit available for Ground cover Noise function\n";
+            }
+        }
+
+        if (_groundCoverTexBinding.valid())
+        {
+            buildStateSets();
+        }
+    }
+}
+
+bool
+GroundCoverLayer::cull(const osgUtil::CullVisitor* cv, osg::State::StateSetStack& stateSetStack) const
+{
+    if (Layer::cull(cv, stateSetStack) == false)
+        return false;
+
+    // If we have zones, select the current one and apply its state set.
+    if (_zones.size() > 0)
+    {
+        int zoneIndex = 0;
+        osg::Vec3d vp = cv->getViewPoint();
+
+        for(int z=_zones.size()-1; z > 0 && zoneIndex == 0; --z)
+        {
+            if ( _zones[z]->contains(vp) )
+            {
+                zoneIndex = z;
+            }
+        }
+
+        osg::StateSet* zoneStateSet = 0L;
+        GroundCover* gc = _zones[zoneIndex]->getGroundCover();
+        if (gc)
+        {
+            zoneStateSet = gc->getStateSet();
+        }
+
+        //osg::StateSet* zoneStateSet = _zones[zoneIndex]->getGroundCover()getStateSet();
+
+        if (zoneStateSet == 0L)
+        {
+            OE_FATAL << LC << "ASSERTION FAILURE - zoneStateSet is null\n";
+            exit(-1);
+        }
+        
+        stateSetStack.push_back(zoneStateSet);
+    }
+    return true;
+}
+
+void
+GroundCoverLayer::buildStateSets()
+{
+    // assert we have the necessary TIUs:
+    if (_groundCoverTexBinding.valid() == false) {
+        OE_DEBUG << LC << "buildStateSets deferred.. bindings not reserved\n";
+        return;
+    }
+
+    if (!_zonesConfigured) {
+        OE_DEBUG << LC << "buildStateSets deferred.. zones not yet configured\n";
+        return;
+    }
+    
+    osg::ref_ptr<LandCoverDictionary> landCoverDict;
+    if (_landCoverDict.lock(landCoverDict) == false) {
+        OE_DEBUG << LC << "buildStateSets deferred.. land cover dictionary not available\n";
+        return;
+    }
+    
+    osg::ref_ptr<LandCoverLayer> landCoverLayer;
+    if (_landCoverLayer.lock(landCoverLayer) == false) {
+        OE_DEBUG << LC << "buildStateSets deferred.. land cover layer not available\n";
+        return;
+    }
+
+    NoiseTextureFactory noise;
+    osg::ref_ptr<osg::Texture> noiseTexture = noise.create(256u, 4u);
+
+    GroundCoverShaders shaders;
+
+    // Layer-wide stateset:
+    osg::StateSet* stateset = getOrCreateStateSet();
+
+    // bind the noise sampler.
+    stateset->setTextureAttribute(_noiseBinding.unit(), noiseTexture.get());
+    stateset->addUniform(new osg::Uniform(NOISE_SAMPLER, _noiseBinding.unit()));
+
+    if (_maskLayer.valid())
+    {
+        stateset->setDefine("OE_GROUNDCOVER_MASK_SAMPLER", _maskLayer->shareTexUniformName().get());
+        stateset->setDefine("OE_GROUNDCOVER_MASK_MATRIX", _maskLayer->shareTexMatUniformName().get());
+    }
+
+    // disable backface culling to support shadow/depth cameras,
+    // for which the geometry shader renders cross hatches instead of billboards.
+    stateset->setMode(GL_CULL_FACE, osg::StateAttribute::PROTECTED);
+
+    // enable alpha-to-coverage multisampling for vegetation.
+    stateset->setMode(GL_SAMPLE_ALPHA_TO_COVERAGE_ARB, 1);
+
+    // uniform that communicates the availability of multisampling.
+    if (osg::DisplaySettings::instance()->getMultiSamples())
+    {
+        stateset->setDefine("OE_GROUNDCOVER_HAS_MULTISAMPLES");
+    }
+
+    stateset->setAttributeAndModes(
+        new osg::BlendFunc(GL_ONE, GL_ZERO, GL_ONE, GL_ZERO),
+        osg::StateAttribute::OVERRIDE);
+
+
+    for (Zones::iterator z = _zones.begin(); z != _zones.end(); ++z)
+    {
+        Zone* zone = z->get();
+        GroundCover* groundCover = zone->getGroundCover();
+        if (groundCover)
+        {
+            if (!groundCover->getBiomes().empty() || groundCover->getTotalNumBillboards() > 0)
+            {
+                osg::StateSet* zoneStateSet = groundCover->getOrCreateStateSet();
+                            
+                // Install the land cover shaders on the state set
+                VirtualProgram* vp = VirtualProgram::getOrCreate(zoneStateSet);
+                vp->setName("Ground cover (" + groundCover->getName() + ")");
+                shaders.loadAll(vp, getReadOptions());
+
+                // Generate the coverage acceptor shader
+                osg::Shader* covTest = groundCover->createPredicateShader(_landCoverDict.get(), _landCoverLayer.get());
+                covTest->setName(covTest->getName() + "_GEOMETRY");
+                covTest->setType(osg::Shader::GEOMETRY);
+                vp->setShader(covTest);
+
+                osg::Shader* covTest2 = groundCover->createPredicateShader(_landCoverDict.get(), _landCoverLayer.get());
+                covTest->setName(covTest->getName() + "_TESSCONTROL");
+                covTest2->setType(osg::Shader::TESSCONTROL);
+                vp->setShader(covTest2);
+
+                osg::ref_ptr<osg::Shader> layerShader = groundCover->createShader();
+                layerShader->setType(osg::Shader::GEOMETRY);
+                vp->setShader(layerShader.get());
+
+                OE_INFO << LC << "Established zone \"" << zone->getName() << "\" at LOD " << getLOD() << "\n";
+
+                osg::Texture* tex = groundCover->createTexture();
+
+                zoneStateSet->setTextureAttribute(_groundCoverTexBinding.unit(), tex);
+                zoneStateSet->addUniform(new osg::Uniform(GCTEX_SAMPLER, _groundCoverTexBinding.unit()));
+
+                OE_DEBUG << LC << "buildStateSets completed!\n";
+            }
+            else
+            {
+                OE_WARN << LC << "ILLEGAL: ground cover layer with no biomes or no billboards defined\n";
+            }
+        }
+        else
+        {
+            // not an error.
+            OE_DEBUG << LC << "zone contains no ground cover information\n";
+        }
+    }
+}
diff --git a/src/osgEarthSplat/LandCover b/src/osgEarthSplat/LandCover
deleted file mode 100644
index 757ca3c..0000000
--- a/src/osgEarthSplat/LandCover
+++ /dev/null
@@ -1,445 +0,0 @@
-/* -*-c++-*- */
-/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2016 Pelican Mapping
- * http://osgearth.org
- *
- * osgEarth is free software; you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>
- */
-#ifndef OSGEARTH_PROCEDURAL_LANDCOVER
-#define OSGEARTH_PROCEDURAL_LANDCOVER 1
-
-#include "Export"
-#include <osgEarth/Config>
-#include <osgEarth/ImageLayer>
-#include <osgEarthSymbology/ResourceLibrary>
-#include <osgEarthSymbology/Symbol>
-#include <osg/BoundingBox>
-#include <osg/Shader>
-
-namespace osgDB {
-    class Options;
-}
-namespace osgEarth {
-    class Map;
-}
-
-namespace osgEarth { namespace Splat
-{
-    using namespace osgEarth;
-    using namespace osgEarth::Symbology;
-
-    class Coverage;
-    class SplatCoverageLegend;
-
-
-    class OSGEARTHSPLAT_EXPORT LandCoverBillboard
-    {
-    public:
-        LandCoverBillboard(osg::Image* image, float width, float height)
-            : _image(image), _width(width), _height(height) { }
-
-        osg::ref_ptr<osg::Image> _image;
-        float                    _width;
-        float                    _height;
-    };
-
-    typedef std::vector<LandCoverBillboard> LandCoverBillboards;
-
-
-    class OSGEARTHSPLAT_EXPORT LandCoverBiome : public osg::Referenced
-    {
-    public:
-        LandCoverBiome() : _code(0) { }
-
-        /** classification names for this biome (space separated) */
-        void setClasses( const std::string& value ) { _classNames = value; }
-        const std::string& getClasses() const { return _classNames; }
-
-        /** Resources that may be used to render land cover in this biome */
-        LandCoverBillboards& getBillboards() { return _billboards; }
-        const LandCoverBillboards& getBillboards() const { return _billboards; }
-
-    protected:
-        virtual ~LandCoverBiome() { }
-
-        std::string    _classNames;
-        int            _code;
-        LandCoverBillboards _billboards;
-
-    public:
-        bool configure(const ConfigOptions& conf, const osgDB::Options* dbo);
-    };
-
-    typedef std::vector< osg::ref_ptr<LandCoverBiome> > LandCoverBiomes;
-
-
-    /**
-     * A layer of land cover data.
-     */
-    class OSGEARTHSPLAT_EXPORT LandCoverLayer : public osg::Referenced
-    {
-    public:
-        LandCoverLayer() : _lod(14), _castShadows(false), _maxDistance(1000.0f), _density(1.0f), _fill(1.0f), _wind(0.0f), _brightness(1.0f), _contrast(0.5f) { }
-
-        /** Name of this layer */
-        void setName(const std::string& name) { _name = name; }
-        const std::string& getName() const { return _name; }
-
-        /** Bounds within which this layer should render (map coordinates) */
-        void setBounds(const Bounds& bounds) { _bounds = bounds; }
-        const Bounds& getBounds() const { return _bounds; }
-
-        /** Terrain LOD at which this layer should render */
-        void setLOD(unsigned lod) { _lod = lod; }
-        unsigned getLOD() const { return _lod; }
-
-        /** Whether this layer should cast shadows when enabled */
-        void setCastShadows(bool value) { _castShadows = value; }
-        bool getCastShadows() const { return _castShadows; }
-
-        /** Maximum visibility distance from camera */
-        void setMaxDistance(float value) { _maxDistance = value; }
-        float getMaxDistance() const { return _maxDistance; }
-
-        /** Density of land cover; i.e. instances per unit of area [1..5+] */
-        void setDensity(float value) { _density = value; }
-        float getDensity() const { return _density; }
-
-        /** Fill percentage of land cover instances [0..1] */
-        void setFill(float value) { _fill = value; }
-        float getFill() const { return _fill; }
-
-        /** Wind effect (0..1) */
-        void setWind(float value) { _wind = value; }
-        float getWind() const { return _wind; }
-
-        /** Brightness [0..2+] .. 1 = default */
-        void setBrightness(float value) { _brightness = value; }
-        float getBrightness() const { return _brightness; }
-
-        /** Contrast [0..1] .. 0 = default */
-        void setContrast(float value) { _contrast = value; }
-        float getContrast() const { return _contrast; }
-
-        /** Biomes comprising this layer. */
-        LandCoverBiomes& getBiomes() { return _biomes; }
-        const LandCoverBiomes& getBiomes() const { return _biomes; }
-
-    public:
-
-        int getTotalNumBillboards() const;
-
-        /** Creates the shader that contains the GLSL APOI for accessing this layer's information */
-        osg::Shader* createShader() const;
-
-        /** Creates the shader that resolves land cover information into billboard data. */
-        osg::Shader* createPredicateShader(const Coverage*) const;
-
-        /** Builds the texture object containing all the data for this layer. */
-        osg::Texture* createTexture() const;
-
-        /** The stateset containing the shaders and state for rendering this layer. */
-        osg::StateSet* getOrCreateStateSet();
-        osg::StateSet* getStateSet() const { return _stateSet.get(); }
-
-    protected:
-        virtual ~LandCoverLayer() { }
-
-        std::string     _name;
-        Bounds          _bounds;
-        unsigned        _lod;
-        bool            _castShadows;
-        float           _maxDistance;
-        float           _density;
-        float           _fill;
-        float           _wind;
-        float           _brightness;
-        float           _contrast;
-        LandCoverBiomes _biomes;
-
-        osg::ref_ptr<osg::StateSet> _stateSet;
-
-    public:
-        bool configure(const ConfigOptions& conf, const osgDB::Options* dbo);
-    };
-
-    typedef std::vector< osg::ref_ptr<LandCoverLayer> > LandCoverLayers;
-
-    /**
-     * Describes all land cover for the scene.
-     */
-    class OSGEARTHSPLAT_EXPORT LandCover : public osg::Referenced
-    {
-    public:
-        LandCover() { }
-
-        /** Resource library containing resources for the land cover assets */
-        void setLibrary(ResourceLibrary* lib) { _lib = lib; }
-        const ResourceLibrary* getLibrary() const { return _lib.get(); }
-
-        /** Land cover layers */
-        LandCoverLayers& getLayers() { return _layers; }
-        const LandCoverLayers& getLayers() const { return _layers; }
-
-        /** Layer that will mask out land cover */
-        void setMaskLayer(ImageLayer* layer) { _maskLayer = layer; }
-        ImageLayer* getMaskLayer() { return _maskLayer.get(); }
-        const ImageLayer* getMaskLayer() const { return _maskLayer.get(); }
-
-    protected:
-        virtual ~LandCover() { }
-
-        osg::ref_ptr<ResourceLibrary> _lib;
-        LandCoverLayers               _layers;
-        osg::ref_ptr<ImageLayer>      _maskLayer;
-        
-    public:
-        bool configure(const ConfigOptions& conf, const Map* map, const osgDB::Options* dbo);
-    };
-
-    typedef std::vector< osg::ref_ptr<LandCover> > LandCovers;
-
-
-    //........................................................................
-
-
-    /**
-     * Configures one biome within a land cover layer.
-     */
-    class LandCoverBiomeOptions : public ConfigOptions
-    {
-    public:
-        LandCoverBiomeOptions(const ConfigOptions& conf = ConfigOptions()) : ConfigOptions(conf) {
-            fromConfig( _conf );
-        }
-
-        /** Name of the biome classes to use. This is one or more class names from the legend,
-          * separated by whitespace. e.g.: "forest grassland swamp" */
-        optional<std::string>& biomeClasses() { return _biomeClasses; }
-        const optional<std::string>& biomeClasses() const { return _biomeClasses; }
-
-        /** Symbology used to conigure rendering in this biome */
-        SymbolVector& symbols() { return _symbols; }
-        const SymbolVector& symbols() const { return _symbols; }
-
-    protected:
-        optional<std::string> _biomeClasses;
-        SymbolVector _symbols;
-
-    public:    
-        void fromConfig(const Config& conf) {
-            conf.getIfSet( "classes", _biomeClasses );
-            const ConfigSet& symbols = conf.children();
-            for(ConfigSet::const_iterator i = symbols.begin(); i != symbols.end(); ++i) {
-                Symbol* s = SymbolRegistry::instance()->create(*i);
-                if ( s ) { 
-                    _symbols.push_back( s );
-                }
-            }
-        }
-
-        Config getConfig() const {
-            Config conf("biome");
-            conf.updateIfSet( "classes", _biomeClasses );
-            for(int i=0; i<_symbols.size(); ++i) {
-                Config symbolConf = _symbols[i]->getConfig();
-                if ( !symbolConf.empty() ) {
-                    conf.add( symbolConf );
-                }
-            }
-            return conf;
-        }
-
-        void mergeConfig( const Config& conf ) {
-            ConfigOptions::mergeConfig( conf );
-            fromConfig( conf );
-        }
-    };
-
-    /**
-     * Configures one layer of land cover data. 
-     */
-    class LandCoverLayerOptions : public ConfigOptions
-    {
-    public:
-        LandCoverLayerOptions(const ConfigOptions& conf = ConfigOptions()) : ConfigOptions(conf) {
-            fromConfig( _conf );
-        }
-
-        /** Name of the land cover layer */
-        optional<std::string>& name() { return _name; }
-        const optional<std::string>& name() const { return _name; }
-
-        /** Equivalent terrain LOD at which this layer will render */
-        optional<unsigned>& lod() { return _lod; }
-        const optional<unsigned>& lod() const { return _lod; }
-
-        /** Whether objects in this layer should cast shadows. */
-        optional<bool>& castShadows() { return _castShadows; }
-        const optional<bool>& castShadows() const { return _castShadows; }
-
-        /** Maximum viewing distance from camera */
-        optional<float>& maxDistance() { return _maxDistance; }
-        const optional<float>& maxDistance() const { return _maxDistance; }
-
-        /** Wind speed [0..1] */
-        optional<float>& wind() { return _wind; }
-        const optional<float>& wind() const { return _wind; }
-
-        /** Metric that controls the number of land cover instances will render
-          * in a given area. [1..5] */
-        optional<float>& density() { return _density; }
-        const optional<float>& density() const { return _density; }
-
-        /** Percentage of land that this layer's instances will cover [0..1]. Lower
-          * values will result is more "patchiness" of placement. */
-        optional<float>& fill() { return _fill; }
-        const optional<float>& fill() const { return _fill; }
-
-        /** Brightness factor for rendering [1..], 1 = default */
-        optional<float>& brightness() { return _brightness; }
-        const optional<float>& brightness() const { return _brightness; }
-
-        /** Contrast factor for rendering [0..], 0 = default */
-        optional<float>& contrast() { return _contrast; }
-        const optional<float>& contrast() const { return _contrast; }
-
-        /** Biomes comprising this layer. */
-        std::vector<LandCoverBiomeOptions>& biomes() { return _biomes; }
-        const std::vector<LandCoverBiomeOptions>& biomes() const { return _biomes; }
-
-    protected:
-        optional<std::string>              _name;
-        optional<unsigned>                 _lod;
-        optional<bool>                     _castShadows;
-        optional<float>                    _maxDistance;
-        optional<float>                    _density;
-        optional<float>                    _fill;
-        optional<float>                    _wind;
-        optional<float>                    _brightness;
-        optional<float>                    _contrast;
-        std::vector<LandCoverBiomeOptions> _biomes;
-
-    public:
-        void fromConfig(const Config& conf) {
-            conf.getIfSet( "name", _name );
-            conf.getIfSet( "lod",  _lod  );
-            conf.getIfSet( "cast_shadows", _castShadows );
-            conf.getIfSet( "max_distance", _maxDistance );
-            conf.getIfSet( "density", _density );
-            conf.getIfSet( "fill", _fill );
-            conf.getIfSet( "wind", _wind );
-            conf.getIfSet( "brightness", _brightness );
-            conf.getIfSet( "contrast", _contrast );
-            const Config* biomes = conf.child_ptr("biomes");
-            if ( biomes ) {
-                const ConfigSet& biomesVec = biomes->children();
-                for( ConfigSet::const_iterator i = biomesVec.begin(); i != biomesVec.end(); ++i ) {
-                    _biomes.push_back( LandCoverBiomeOptions(*i) );
-                }
-            }
-        }
-        
-        Config getConfig() const {
-            Config conf = ConfigOptions::getConfig();
-            conf.key() = "layer";
-            conf.updateIfSet( "name", _name );
-            conf.updateIfSet( "lod",  _lod );
-            conf.updateIfSet( "cast_shadows", _castShadows );
-            conf.updateIfSet( "max_distance", _maxDistance );
-            conf.updateIfSet( "density", _density );
-            conf.updateIfSet( "fill", _fill );
-            conf.updateIfSet( "wind", _wind );
-            conf.updateIfSet( "brightness", _brightness );
-            conf.updateIfSet( "contrast", _contrast );
-            if ( _biomes.size() > 0 ) {
-                Config biomes("biomes");
-                for(int i=0; i<_biomes.size(); ++i) {
-                    biomes.add( "biome", _biomes[i].getConfig() );
-                }
-                conf.update( biomes );
-            }
-            return conf;
-        }
-
-        void mergeConfig( const Config& conf ) {
-            ConfigOptions::mergeConfig( conf );
-            fromConfig( conf );
-        }
-    };
-
-    /**
-     * Options for rendering land cover (environmental features that sit on the terrain
-     * surface like trees, rocks, grass, etc.)
-     */
-    class LandCoverOptions : public ConfigOptions
-    {
-    public:
-        LandCoverOptions(const ConfigOptions& conf = ConfigOptions()) : ConfigOptions(conf) {
-            fromConfig( _conf );
-        }
-
-        /** URI of an resource library to use for land cover data */
-        optional<URI>& library() { return _resourceLibURI; }
-        const optional<URI>& library() const { return _resourceLibURI; }
-
-        /** Layers of land cover data */
-        std::vector<LandCoverLayerOptions>& layers() { return _layers; }
-        const std::vector<LandCoverLayerOptions>& layers() const { return _layers; }
-
-        /** Name of a map layer to use for masking out land cover. */
-        optional<std::string>& maskLayerName() { return _maskLayerName; }
-        const optional<std::string>& maskLayerName() const { return _maskLayerName; }
-
-    protected:        
-        optional<URI>                      _resourceLibURI; 
-        optional<std::string>              _maskLayerName;
-        std::vector<LandCoverLayerOptions> _layers;
-
-    public:
-        void fromConfig(const Config& conf) {
-            conf.getIfSet( "library", _resourceLibURI );
-            conf.getIfSet( "mask_layer", _maskLayerName );
-            const Config* layers = conf.child_ptr("layers");
-            if ( layers ) {
-                const ConfigSet& layerVec = layers->children();
-                for(ConfigSet::const_iterator i = layerVec.begin(); i != layerVec.end(); ++i ) {
-                    _layers.push_back( LandCoverLayerOptions(*i) );
-                }
-            }
-        }
-        
-        Config getConfig() const {
-            Config conf = ConfigOptions::getConfig();
-            conf.key() = "land_cover";
-            conf.updateIfSet( "library", _resourceLibURI );
-            conf.updateIfSet( "mask_layer", _maskLayerName );
-            if ( !_layers.empty() ) {
-                Config layers("layers");
-                for(int i=0; i<_layers.size(); ++i) {
-                    layers.add("layer", _layers[i].getConfig());
-                }
-            }
-            return conf;
-        }
-
-        void mergeConfig( const Config& conf ) {
-            ConfigOptions::mergeConfig( conf );
-            fromConfig( conf );
-        }
-    };
-
-} } // namespace osgEarth::Splat
-
-#endif // OSGEARTH_PROCEDURAL_LANDCOVER
diff --git a/src/osgEarthSplat/LandCover.FS.glsl b/src/osgEarthSplat/LandCover.FS.glsl
deleted file mode 100644
index 4e0cb8e..0000000
--- a/src/osgEarthSplat/LandCover.FS.glsl
+++ /dev/null
@@ -1,49 +0,0 @@
-#version 330
-#pragma vp_name       Land cover billboard texture application
-#pragma vp_entryPoint oe_landcover_fragment
-#pragma vp_location   fragment_coloring
-
-#define IS_ARRAY
-                     
-uniform bool oe_terrain_hasMultiSamples;
-
-#ifdef IS_ARRAY
-
-uniform sampler2DArray oe_landcover_texArray;
-uniform float oe_landcover_exposure;
-in vec2 oe_landcover_texCoord;
-in vec4 oe_layer_tilec;
-
-flat in float oe_landcover_arrayIndex; // from LandCover.GS.glsl
-
-void oe_landcover_fragment(inout vec4 color)
-{    
-    // modulate the texture
-    color = texture(oe_landcover_texArray, vec3(oe_landcover_texCoord, oe_landcover_arrayIndex)) * color;
-    color.rgb *= oe_landcover_exposure;
-    
-    // if multisampling is off, use alpha-discard.
-    if ( !oe_terrain_hasMultiSamples && color.a < 0.15 )
-        discard;
-}
-
-#else
-
-uniform sampler2D oe_landcover_tex;
-uniform float oe_landcover_exposure;
-
-in vec2 oe_landcover_texCoord;
-in vec4 oe_layer_tilec;
-
-void oe_landcover_fragment(inout vec4 color)
-{    
-    // modulate the texture
-    color = texture(oe_landcover_tex, oe_landcover_texCoord) * color;
-    color.rgb *= oe_landcover_exposure;
-    
-    // if multisampling is off, use alpha-discard.
-    if ( !oe_terrain_hasMultiSamples && color.a < 0.15 )
-        discard;
-}
-
-#endif
\ No newline at end of file
diff --git a/src/osgEarthSplat/LandCover.cpp b/src/osgEarthSplat/LandCover.cpp
deleted file mode 100644
index fa0f5f0..0000000
--- a/src/osgEarthSplat/LandCover.cpp
+++ /dev/null
@@ -1,438 +0,0 @@
-#include "LandCover"
-#include "Coverage"
-#include "SplatCatalog"
-#include "SplatCoverageLegend"
-
-#include <osgEarth/Map>
-#include <osgEarth/ImageLayer>
-#include <osgEarth/ImageUtils>
-#include <osgEarthSymbology/BillboardSymbol>
-#include <osgEarthSymbology/BillboardResource>
-
-#include <osg/Texture2DArray>
-
-using namespace osgEarth;
-using namespace osgEarth::Splat;
-using namespace osgEarth::Symbology;
-
-#define LC "[LandCover] "
-
-bool
-LandCover::configure(const ConfigOptions& conf, const Map* map, const osgDB::Options* dbo)
-{
-    LandCoverOptions in( conf );
-    
-    if ( in.library().isSet() )
-    {
-        _lib = new ResourceLibrary( "default", in.library().get() );
-        if ( !_lib->initialize( dbo ) )
-        {
-            OE_WARN << LC << "Failed to load resource library \"" << in.library()->full() << "\"\n";
-            return false;
-        }
-    }
-
-    if ( in.layers().empty() )
-    {
-        OE_WARN << LC << "No land cover layers defined\n";
-        return false;
-    }
-    else
-    {
-        for(int i=0; i<in.layers().size(); ++i)
-        {
-            osg::ref_ptr<LandCoverLayer> layer = new LandCoverLayer();
-
-            if ( layer->configure( in.layers().at(i), dbo ) )
-            {
-                _layers.push_back( layer.get() );
-                OE_INFO << LC << "Configured land cover layer \"" << layer->getName() << "\"\n";
-            }
-            else
-            {
-                OE_WARN << LC << "Land cover layer \"" << layer->getName() << "\" is improperly configured\n";
-                return false;
-            }
-        }
-    }
-
-    if ( in.maskLayerName().isSet() )
-    {
-        ImageLayer* layer = map->getImageLayerByName( in.maskLayerName().get() );
-        if ( layer )
-        {
-            setMaskLayer( layer );
-        }
-        else
-        {
-            OE_WARN << LC << "Masking layer \"" << in.maskLayerName().get() << "\" not found in the map." << std::endl;
-            return false;
-        }
-    }
-
-    return true;
-}
-
-
-//............................................................................
-
-bool
-LandCoverLayer::configure(const ConfigOptions& conf, const osgDB::Options* dbo)
-{
-    LandCoverLayerOptions in( conf );
-
-    if ( in.name().isSet() )
-        setName( in.name().get() );
-    if ( in.lod().isSet() )
-        setLOD( in.lod().get() );
-    if ( in.castShadows().isSet() )
-        setCastShadows( in.castShadows().get() );
-    if ( in.maxDistance().isSet() )
-        setMaxDistance( in.maxDistance().get() );
-    if ( in.density().isSet() )
-        setDensity( in.density().get() );
-    if ( in.fill().isSet() )
-        setFill( in.fill().get() );
-    if ( in.wind().isSet() )
-        setWind( in.wind().get() );
-    if ( in.brightness().isSet() )
-        setBrightness( in.brightness().get() );
-    if ( in.contrast().isSet() )
-        setContrast( in.contrast().get() );
-
-    if ( in.biomes().size() == 0 )
-    {
-        OE_WARN << LC << "No biomes defined in layer \"" << getName() << "\"\n";
-        return false;
-    }
-
-    for(int i=0; i<in.biomes().size(); ++i)
-    {
-        osg::ref_ptr<LandCoverBiome> biome = new LandCoverBiome();
-
-        if ( biome->configure( in.biomes().at(i), dbo ) )
-        {
-            _biomes.push_back( biome.get() );
-        }
-        else
-        {
-            OE_WARN << LC << "One of the biomes in layer \"" << getName() << "\" is improperly configured\n";
-            return false;
-        }
-    }
-
-    return true;
-}
-
-int
-LandCoverLayer::getTotalNumBillboards() const
-{
-    int count = 0;
-    for(int i=0; i<getBiomes().size(); ++i)
-    {
-        count += getBiomes().at(i)->getBillboards().size();
-    }
-    return count;
-}
-
-osg::StateSet*
-LandCoverLayer::getOrCreateStateSet()
-{
-    if ( !_stateSet.valid() )
-        _stateSet = new osg::StateSet();
-    return _stateSet.get();
-}
-
-osg::Shader*
-LandCoverLayer::createShader() const
-{
-    std::stringstream biomeBuf;
-    std::stringstream billboardBuf;
-
-    int totalBillboards = getTotalNumBillboards();
-
-    // encode all the biome data.
-    biomeBuf << 
-        "struct oe_landcover_Biome { \n"
-        "    int firstBillboardIndex; \n"
-        "    int numBillboards; \n"
-        "    float density; \n"
-        "    float fill; \n"
-        "    vec2 maxWidthHeight; \n"
-        "}; \n"
-
-        "const oe_landcover_Biome oe_landcover_biomes[" << getBiomes().size() << "] = oe_landcover_Biome[" << getBiomes().size() << "] ( \n";
-
-    billboardBuf <<
-        "struct oe_landcover_Billboard { \n"
-        "    int arrayIndex; \n"
-        "    float width; \n"
-        "    float height; \n"
-        "}; \n"
-
-        "const oe_landcover_Billboard oe_landcover_billboards[" << totalBillboards << "] = oe_landcover_Billboard[" << totalBillboards << "](\n";
-    
-    int index = 0;
-    for(int i=0; i<getBiomes().size(); ++i)
-    {
-        const LandCoverBiome* biome = getBiomes().at(i).get();
-
-        float maxWidth = 0.0f, maxHeight = 0.0f;
-        
-        int firstIndex = index;
-
-        for(int j=0; j<biome->getBillboards().size(); ++j)
-        {
-            const LandCoverBillboard& bb = biome->getBillboards().at(j);
-
-            billboardBuf
-                << "    oe_landcover_Billboard("
-                << index 
-                << ", float(" << bb._width << ")"
-                << ", float(" << bb._height << ")"
-                << ")";
-            
-            ++index;
-            if ( index < totalBillboards )
-                billboardBuf << ",\n";
-
-            maxWidth = std::max(maxWidth, bb._width);
-            maxHeight = std::max(maxHeight, bb._height);
-        }
-
-        // We multiply the height x 2 below because billboards have their origin
-        // at the bottom center of the image. The GPU culling code considers the
-        // distance from this anchor point. For width, it's in the middle, but for
-        // height, it's at the bottom so we need to double it. It doubles in both
-        // directions, but that's OK since we are rarely if ever going to GPU-cull
-        // a billboard at the top of the viewport. -gw
-
-        biomeBuf << "    oe_landcover_Biome(" 
-            << firstIndex << ", "
-            << biome->getBillboards().size() 
-            << ", float(" << getDensity() << ")"
-            << ", float(" << getFill() << ")"
-            << ", vec2(float(" << maxWidth << "),float(" << maxHeight*2.0f << ")))";
-
-        if ( (i+1) < getBiomes().size() )
-            biomeBuf << ",\n";
-    }
-
-    biomeBuf
-        << "\n);\n";
-
-    billboardBuf
-        << "\n); \n";
-
-    biomeBuf 
-        << "void oe_landcover_getBiome(in int biomeIndex, out oe_landcover_Biome biome) { \n"
-        << "    biome = oe_landcover_biomes[biomeIndex]; \n"
-        << "} \n";
-        
-    billboardBuf
-        << "void oe_landcover_getBillboard(in int billboardIndex, out oe_landcover_Billboard billboard) { \n"
-        << "    billboard = oe_landcover_billboards[billboardIndex]; \n"
-        << "} \n";
-    
-    osg::ref_ptr<ImageLayer> layer;
-
-    osg::Shader* shader = new osg::Shader();
-    shader->setName( "LandCoverLayer" );
-    shader->setShaderSource( Stringify() << "#version 330\n" << biomeBuf.str() << "\n" << billboardBuf.str() );
-    return shader;
-}
-
-osg::Shader*
-LandCoverLayer::createPredicateShader(const Coverage* coverage) const
-{
-    const char* defaultCode = "int oe_landcover_getBiomeIndex(in vec4 coords) { return -1; }\n";
-
-    std::stringstream buf;
-    buf << "#version 330\n";
-    
-    osg::ref_ptr<ImageLayer> layer;
-
-    if ( !coverage )
-    {
-        buf << defaultCode;
-        OE_INFO << LC << "No coverage; generating default coverage predicate\n";
-    }
-    else if ( !coverage->getLegend() )
-    {
-        buf << defaultCode;
-        OE_INFO << LC << "No legend; generating default coverage predicate\n";
-    }
-    else if ( !coverage->lockLayer(layer) )
-    {
-        buf << defaultCode;
-        OE_INFO << LC << "No classification layer; generating default coverage predicate\n";
-    }
-    else
-    {
-        const std::string& sampler = layer->shareTexUniformName().get();
-        const std::string& matrix  = layer->shareTexMatUniformName().get();
-
-        buf << "uniform sampler2D " << sampler << ";\n"
-            << "uniform mat4 " << matrix << ";\n"
-            << "int oe_landcover_getBiomeIndex(in vec4 coords) { \n"
-            << "    float value = textureLod(" << sampler << ", (" << matrix << " * coords).st, 0).r;\n";
-
-        for(int biomeIndex=0; biomeIndex<getBiomes().size(); ++biomeIndex)
-        {
-            const LandCoverBiome* biome = getBiomes().at(biomeIndex).get();
-
-            if ( !biome->getClasses().empty() )
-            {
-                StringVector classes;
-                StringTokenizer(biome->getClasses(), classes, " ", "\"", false);
-
-                for(int i=0; i<classes.size(); ++i)
-                {
-                    std::vector<const CoverageValuePredicate*> predicates;
-                    if ( coverage->getLegend()->getPredicatesForClass(classes[i], predicates) )
-                    {
-                        for(std::vector<const CoverageValuePredicate*>::const_iterator p = predicates.begin();
-                            p != predicates.end(); 
-                            ++p)
-                        {
-                            const CoverageValuePredicate* predicate = *p;
-
-                            if ( predicate->_exactValue.isSet() )
-                            {
-                                buf << "    if (value == " << predicate->_exactValue.get() << ") return " << biomeIndex << "; \n";
-                            }
-                            else if ( predicate->_minValue.isSet() && predicate->_maxValue.isSet() )
-                            {
-                                buf << "    if (value >= " << predicate->_minValue.get() << " && value <= " << predicate->_maxValue.get() << ") return " << biomeIndex << "; \n";
-                            }
-                            else if ( predicate->_minValue.isSet() )
-                            {
-                                buf << "    if (value >= " << predicate->_minValue.get() << ")  return " << biomeIndex << "; \n";
-                            }
-                            else if ( predicate->_maxValue.isSet() )
-                            {
-                                buf << "    if (value <= " << predicate->_maxValue.get() << ") return " << biomeIndex << "; \n";
-                            }
-
-                            else 
-                            {
-                                OE_WARN << LC << "Class \"" << classes[i] << "\" found, but no exact/min/max value was set in the legend\n";
-                            }
-                        }
-                    }
-                    else
-                    {
-                        OE_WARN << LC << "Class \"" << classes[i] << "\" not found in the legend!\n";
-                    }
-                }
-            }
-        }
-        buf << "    return -1; \n";
-        buf << "}\n";
-    }
-    
-    osg::Shader* shader = new osg::Shader();
-    shader->setName("oe Landcover predicate function");
-    shader->setShaderSource( buf.str() );
-
-    return shader;
-}
-
-//............................................................................
-
-bool
-LandCoverBiome::configure(const ConfigOptions& conf, const osgDB::Options* dbo)
-{
-    LandCoverBiomeOptions in( conf );
-
-    if ( in.biomeClasses().isSet() )
-        setClasses( in.biomeClasses().get() );
-
-    for(SymbolVector::const_iterator i = in.symbols().begin(); i != in.symbols().end(); ++i)
-    {
-        const BillboardSymbol* bs = dynamic_cast<BillboardSymbol*>( i->get() );
-        if ( bs )
-        {
-            URI imageURI = bs->url()->evalURI();
-
-            osg::Image* image = const_cast<osg::Image*>( bs->getImage() );
-            if ( !image )
-            {
-                image = imageURI.getImage(dbo);
-            }
-
-            if ( image )
-            {
-                getBillboards().push_back( LandCoverBillboard(image, bs->width().get(), bs->height().get()) );
-            }
-            else
-            {
-                OE_WARN << LC << "Failed to load billboard image from \"" << imageURI.full() << "\"\n";
-            }
-        } 
-        else
-        {
-            OE_WARN << LC << "Unrecognized symbol in land cover biome\n";
-        }
-    }
-
-    if ( getBillboards().size() == 0 )
-    {
-        OE_WARN << LC << "A biome failed to install any billboards.\n";
-        return false;
-    }
-
-    return true;
-}
-
-osg::Texture*
-LandCoverLayer::createTexture() const
-{
-    osg::Texture2DArray* tex = new osg::Texture2DArray();
-
-    int arrayIndex = 0;
-    float s = -1.0f, t = -1.0f;
-
-    for(int b=0; b<getBiomes().size(); ++b)
-    {
-        const LandCoverBiome* biome = getBiomes().at(b);
-
-        for(int i=0; i<biome->getBillboards().size(); ++i, ++arrayIndex)
-        {
-            const LandCoverBillboard& bb = biome->getBillboards().at(i);
-
-            osg::ref_ptr<osg::Image> im;
-
-            if ( s < 0 )
-            {
-                s  = bb._image->s();
-                t  = bb._image->t();
-                im = bb._image.get();
-                tex->setTextureSize(s, t, getTotalNumBillboards());                              
-            }
-            else
-            {
-                if ( bb._image->s() != s || bb._image->t() != t )
-                {
-                    ImageUtils::resizeImage( bb._image.get(), s, t, im );
-                }
-                else
-                {
-                    im = bb._image.get();
-                }
-            }
-
-            tex->setImage( arrayIndex, im.get() );
-        }
-    }
-
-    tex->setFilter(tex->MIN_FILTER, tex->NEAREST_MIPMAP_LINEAR);
-    tex->setFilter(tex->MAG_FILTER, tex->LINEAR);
-    tex->setWrap  (tex->WRAP_S, tex->CLAMP_TO_EDGE);
-    tex->setWrap  (tex->WRAP_T, tex->CLAMP_TO_EDGE);
-    tex->setUnRefImageDataAfterApply( true );
-    tex->setMaxAnisotropy( 4.0 );
-    tex->setResizeNonPowerOfTwoHint( false );
-
-    return tex;
-}
diff --git a/src/osgEarthSplat/LandCoverTerrainEffect b/src/osgEarthSplat/LandCoverTerrainEffect
deleted file mode 100644
index 0842f55..0000000
--- a/src/osgEarthSplat/LandCoverTerrainEffect
+++ /dev/null
@@ -1,87 +0,0 @@
-/* -*-c++-*- */
-/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2012 Pelican Mapping
-* http://osgearth.org
-*
-* osgEarth is free software; you can redistribute it and/or modify
-* it under the terms of the GNU Lesser General Public License as published by
-* the Free Software Foundation; either version 2 of the License, or
-* (at your option) any later version.
-*
-* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
-* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
-* IN THE SOFTWARE.
-*
-* You should have received a copy of the GNU Lesser General Public License
-* along with this program.  If not, see <http://www.gnu.org/licenses/>
-*/
-#ifndef OSGEARTH_SPLAT_LANDCOVER_TERRAIN_EFFECT_H
-#define OSGEARTH_SPLAT_LANDCOVER_TERRAIN_EFFECT_H
-
-#include "Export"
-#include "Coverage"
-#include "Zone"
-#include "LandCoverTilePatchCallback"
-
-#include <osgEarth/TerrainEffect>
-#include <osg/Uniform>
-#include <osgDB/Options>
-
-using namespace osgEarth;
-
-namespace osgEarth { namespace Splat
-{
-    /**
-     * Effect that renders land cover data (trees, grass, rocks, etc.)
-     */
-    class OSGEARTHSPLAT_EXPORT LandCoverTerrainEffect : public TerrainEffect
-    {
-    public:
-        /** constructor */
-        LandCoverTerrainEffect();
-
-        /**
-         * Sets the OSG DB options to use when performing I/O
-         */
-        void setDBOptions(const osgDB::Options* dbo);
-
-        /**
-         * Sets the coverage/surface zones.
-         */
-        void setZones(const Zones& zones) { _zones = zones; }
-        const Zones& getZones() const { return _zones; }
-
-        /**
-         * Sets the coverage source. This is optional, if you do not install
-         * a coverage source the land cover will render everywhere unconstrained.
-         */
-        void setCoverage(Coverage* coverage) { _coverage = coverage; }
-        Coverage* getCoverage() const { return _coverage.get(); }
-
-    public: // TerrainEffect interface
-
-        void onInstall(TerrainEngineNode* engine);
-
-        void onUninstall(TerrainEngineNode* engine);
-
-
-    protected:
-        virtual ~LandCoverTerrainEffect() { }
-
-        osg::ref_ptr<const osgDB::Options> _dbo;
-
-        osg::ref_ptr<Coverage> _coverage;
-        Zones _zones;
-        int _noiseTexUnit;
-        int _landCoverTexUnit;
-
-        osg::ref_ptr<LandCoverTilePatchCallback> _tilePatchCallback;
-    };
-
-} } // namespace osgEarth::Splat
-
-#endif // OSGEARTH_SPLAT_LANDCOVER_TERRAIN_EFFECT_H
diff --git a/src/osgEarthSplat/LandCoverTerrainEffect.cpp b/src/osgEarthSplat/LandCoverTerrainEffect.cpp
deleted file mode 100644
index 40e9b17..0000000
--- a/src/osgEarthSplat/LandCoverTerrainEffect.cpp
+++ /dev/null
@@ -1,238 +0,0 @@
-/* -*-c++-*- */
-/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2012 Pelican Mapping
-* http://osgearth.org
-*
-* osgEarth is free software; you can redistribute it and/or modify
-* it under the terms of the GNU Lesser General Public License as published by
-* the Free Software Foundation; either version 2 of the License, or
-* (at your option) any later version.
-*
-* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
-* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
-* IN THE SOFTWARE.
-*
-* You should have received a copy of the GNU Lesser General Public License
-* along with this program.  If not, see <http://www.gnu.org/licenses/>
-*/
-#include "LandCoverTerrainEffect"
-#include "SplatOptions"
-#include "NoiseTextureFactory"
-
-#include <osgEarth/Registry>
-#include <osgEarth/Capabilities>
-#include <osgEarth/VirtualProgram>
-#include <osgEarth/TerrainEngineNode>
-#include <osgEarth/URI>
-#include <osgEarth/ShaderLoader>
-#include <osgEarth/ImageUtils>
-
-#include <osg/Texture2D>
-#include <osg/BlendFunc>
-#include <osg/Multisample>
-#include <osgUtil/Optimizer>
-
-#include "SplatShaders"
-
-#define LC "[LandCoverTerrainEffect] "
-
-using namespace osgEarth;
-using namespace osgEarth::Splat;
-
-LandCoverTerrainEffect::LandCoverTerrainEffect() :
-_noiseTexUnit    ( -1 ),
-_landCoverTexUnit( -1 )
-{
-    //nop
-}
-
-void
-LandCoverTerrainEffect::setDBOptions(const osgDB::Options* dbo)
-{
-    _dbo = dbo;
-}
-
-void
-LandCoverTerrainEffect::onInstall(TerrainEngineNode* engine)
-{
-    if ( !engine )
-        return;
-
-    if ( engine )
-    {
-        // first make sure there is land cover data available:
-        bool landCoverActive = false;
-        for(Zones::const_iterator z = _zones.begin(); z != _zones.end(); ++z)
-        {
-            Zone* zone = z->get();
-            LandCover* landcover = zone->getLandCover();
-            if ( landcover )
-            {
-                landCoverActive = true;
-            }
-        }
-
-        if ( landCoverActive )
-        {
-            // install a noise texture if we haven't already.
-            osg::StateSet* terrainStateSet = engine->getOrCreateStateSet();
-
-            // check whether it's already installed by another extension:
-            if ( terrainStateSet->getUniform("oe_splat_noiseTex") == 0L )
-            {
-                // reserve a texture unit:
-                if (engine->getResources()->reserveTextureImageUnit(_noiseTexUnit, "Splat Noise"))
-                {
-                    NoiseTextureFactory noise;
-                    terrainStateSet->setTextureAttribute( _noiseTexUnit, noise.create(256u, 4u) );
-                    terrainStateSet->addUniform( new osg::Uniform("oe_splat_noiseTex", _noiseTexUnit) );
-                }
-                else
-                {
-                    OE_WARN << LC << "No texture image unit available for LandCover Noise. Aborting.\n";
-                    return;
-                }
-            }
-            
-            int _landCoverTexUnit;
-            if ( !engine->getResources()->reserveTextureImageUnit(_landCoverTexUnit, "LandCover") )
-            {
-                OE_WARN << LC << "No texture image unit available for LandCover." << std::endl;
-                return;
-            }
-
-            // The patch callback that will render the land cover:
-            _tilePatchCallback = new LandCoverTilePatchCallback();
-            _tilePatchCallback->_zones = _zones;
-
-            engine->addTilePatchCallback( _tilePatchCallback.get() );
-
-            for(Zones::iterator z = _zones.begin(); z != _zones.end(); ++z)
-            {
-                Zone* zone = z->get();
-                LandCover* landCover = zone->getLandCover();
-                if ( landCover )
-                {
-                    for(LandCoverLayers::iterator i = landCover->getLayers().begin();
-                        i != landCover->getLayers().end();
-                        ++i)
-                    {
-                        LandCoverLayer* layer = i->get();
-                        if ( layer )
-                        {
-                            if ( !layer->getBiomes().empty() || layer->getTotalNumBillboards() > 0 )
-                            {
-                                osg::StateSet* stateset = layer->getOrCreateStateSet();
-
-                                bool useMask = (landCover->getMaskLayer() != 0L);
-
-                                // Install the land cover shaders on the state set
-                                VirtualProgram* vp = VirtualProgram::getOrCreate(stateset);
-                                vp->setName("Land Cover (" + layer->getName() + ")");
-                                LandCoverShaders shaders;
-                                if ( useMask )
-                                {
-                                    shaders.replace("MASK_SAMPLER", landCover->getMaskLayer()->shareTexUniformName().get());
-                                    shaders.replace("MASK_TEXTURE", landCover->getMaskLayer()->shareTexMatUniformName().get());
-                                }
-                                shaders.loadAll( vp, _dbo.get() );
-
-                                // Generate the coverage acceptor shader
-                                osg::Shader* covTest = layer->createPredicateShader( getCoverage() );
-                                covTest->setName( covTest->getName() + "_GEOMETRY" );
-                                covTest->setType( osg::Shader::GEOMETRY );
-                                vp->setShader( covTest );
-
-                                osg::Shader* covTest2 = layer->createPredicateShader( getCoverage() );
-                                covTest->setName( covTest->getName() + "_TESSCONTROL" );
-                                covTest2->setType( osg::Shader::TESSCONTROL );
-                                vp->setShader( covTest2 );
-
-                                osg::ref_ptr<osg::Shader> layerShader = layer->createShader();
-                                layerShader->setType( osg::Shader::GEOMETRY );
-                                vp->setShader( layerShader );
-
-                                OE_INFO << LC << "Adding land cover layer: " << layer->getName() << " to Zone " << zone->getName() << " at LOD " << layer->getLOD() << "\n";
-
-                                // Install the uniforms
-                                stateset->addUniform( new osg::Uniform("oe_landcover_windFactor", layer->getWind()) );
-                                stateset->addUniform( new osg::Uniform("oe_landcover_noise", 1.0f) );
-                                stateset->addUniform( new osg::Uniform("oe_landcover_ao", 0.5f) );
-                                stateset->addUniform( new osg::Uniform("oe_landcover_exposure", 1.0f) );
-                    
-                                stateset->addUniform( new osg::Uniform("oe_landcover_density",     layer->getDensity()) );
-                                stateset->addUniform( new osg::Uniform("oe_landcover_fill",        layer->getFill()) );
-                                stateset->addUniform( new osg::Uniform("oe_landcover_maxDistance", layer->getMaxDistance()) );
-
-                                stateset->addUniform( new osg::Uniform("oe_landcover_brightness",  layer->getBrightness()) );
-                                stateset->addUniform( new osg::Uniform("oe_landcover_contrast",    layer->getContrast()) );
-
-                                stateset->addUniform( new osg::Uniform("oe_landcover_useMask", useMask) );
-
-                                // enable alpha-to-coverage multisampling for vegetation.
-                                stateset->setMode(GL_SAMPLE_ALPHA_TO_COVERAGE_ARB, 1);
-
-                                // uniform that communicates the availability of multisampling.
-                                stateset->addUniform( new osg::Uniform(
-                                    "oe_terrain_hasMultiSamples",
-                                    osg::DisplaySettings::instance()->getMultiSamples()) );
-
-                                stateset->setAttributeAndModes(
-                                    new osg::BlendFunc(GL_ONE, GL_ZERO, GL_ONE, GL_ZERO),
-                                    osg::StateAttribute::OVERRIDE );
-
-                                stateset->setRenderingHint(osg::StateSet::TRANSPARENT_BIN);
-
-                                osg::Texture* tex = layer->createTexture();
-
-                                stateset->setTextureAttribute(_landCoverTexUnit, tex);
-                                stateset->addUniform(new osg::Uniform("oe_landcover_texArray", _landCoverTexUnit) );
-                            }
-                            else
-                            {
-                                OE_WARN << LC << "ILLEGAL: land cover layer with no biomes or no billboards defined\n";
-                            }
-                        }
-                        else
-                        {
-                            OE_WARN << LC << "ILLEGAL: empty layer found in land cover layer list\n";
-                        }
-                    }
-                }
-                else
-                {
-                    // not an error.
-                    OE_DEBUG << LC << "zone contains no land cover information\n";
-                }
-            }
-        }
-        else
-        {
-            OE_DEBUG << LC << "No land cover information found\n";
-        }
-    }
-}
-
-
-
-void
-LandCoverTerrainEffect::onUninstall(TerrainEngineNode* engine)
-{
-    if ( engine )
-    {
-        if ( _tilePatchCallback.valid() )
-        {
-            engine->removeTilePatchCallback( _tilePatchCallback.get() );
-
-            if ( _landCoverTexUnit >= 0 )
-                engine->getResources()->releaseTextureImageUnit( _landCoverTexUnit );
-
-            if ( _noiseTexUnit >= 0 )
-                engine->getResources()->releaseTextureImageUnit( _noiseTexUnit );
-        }
-    }
-}
diff --git a/src/osgEarthSplat/LandCoverTilePatchCallback b/src/osgEarthSplat/LandCoverTilePatchCallback
deleted file mode 100644
index 1c367ff..0000000
--- a/src/osgEarthSplat/LandCoverTilePatchCallback
+++ /dev/null
@@ -1,58 +0,0 @@
-/* -*-c++-*- */
-/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2012 Pelican Mapping
-* http://osgearth.org
-*
-* osgEarth is free software; you can redistribute it and/or modify
-* it under the terms of the GNU Lesser General Public License as published by
-* the Free Software Foundation; either version 2 of the License, or
-* (at your option) any later version.
-*
-* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
-* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
-* IN THE SOFTWARE.
-*
-* You should have received a copy of the GNU Lesser General Public License
-* along with this program.  If not, see <http://www.gnu.org/licenses/>
-*/
-#ifndef OSGEARTH_SPLAT_LANDCOVER_TILE_PATCH_CALLBACK_H
-#define OSGEARTH_SPLAT_LANDCOVER_TILE_PATCH_CALLBACK_H
-
-#include "Export"
-#include "Zone"
-#include <osgEarth/TilePatchCallback>
-
-using namespace osgEarth;
-
-namespace osgEarth { namespace Splat
-{
-    /**
-     * Tile Patch callback that renders the land cover data.
-     */
-    class LandCoverTilePatchCallback : public TilePatchCallback
-    {
-    public:
-        /** constructor */
-        LandCoverTilePatchCallback() { }
-
-        Zones _zones;
-
-    public: // TilePatchCallback
-
-        void cull(
-            osgUtil::CullVisitor* cv,
-            const TileKey&        key,
-            osg::StateSet*        stateSet,
-            osg::Node*            tilePatch);
-
-        void release(
-            const TileKey& key);
-    };
-
-} } // namespace osgEarth::Splat
-
-#endif // OSGEARTH_SPLAT_LANDCOVER_TILE_PATCH_CALLBACK_H
diff --git a/src/osgEarthSplat/LandCoverTilePatchCallback.cpp b/src/osgEarthSplat/LandCoverTilePatchCallback.cpp
deleted file mode 100644
index 3a5bdbe..0000000
--- a/src/osgEarthSplat/LandCoverTilePatchCallback.cpp
+++ /dev/null
@@ -1,89 +0,0 @@
-/* -*-c++-*- */
-/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2012 Pelican Mapping
-* http://osgearth.org
-*
-* osgEarth is free software; you can redistribute it and/or modify
-* it under the terms of the GNU Lesser General Public License as published by
-* the Free Software Foundation; either version 2 of the License, or
-* (at your option) any later version.
-*
-* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
-* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
-* IN THE SOFTWARE.
-*
-* You should have received a copy of the GNU Lesser General Public License
-* along with this program.  If not, see <http://www.gnu.org/licenses/>
-*/
-#include "LandCoverTilePatchCallback"
-#include <osgEarth/TraversalData>
-#include <osgEarth/Shadowing>
-#include <osgUtil/CullVisitor>
-
-using namespace osgEarth;
-using namespace osgEarth::Splat;
-
-#define LC "[LandCoverTilePatchCallback] "
-
-void
-LandCoverTilePatchCallback::cull(osgUtil::CullVisitor* cv,
-                                 const TileKey&        key,
-                                 osg::StateSet*        tileStateSet,
-                                 osg::Node*            tilePatch)
-{
-    RefUID* zoneIndex = VisitorData::fetch<RefUID>(*cv, "oe.LandCover.zoneIndex");
-    if ( zoneIndex && (*zoneIndex) < _zones.size() )
-    {
-        // Figure out what type of camera this is:
-        unsigned clearMask = cv->getCurrentCamera()->getClearMask();
-        bool isDepthCamera = ((clearMask & GL_COLOR_BUFFER_BIT) == 0u) && ((clearMask & GL_DEPTH_BUFFER_BIT) != 0u);
-        bool isShadowCamera = osgEarth::Shadowing::isShadowCamera(cv->getCurrentCamera());
-
-        // only consider land cover if we are capturing color OR shadow map.
-        if ( isShadowCamera || !isDepthCamera )
-        {
-            bool pushedTileStateSet = false;
-
-            //ZoneData& zone = _zones[*zoneIndex];
-            Zone* zone = _zones[*zoneIndex].get();
-
-            LandCoverLayers& layers = zone->getLandCover()->getLayers();
-            for(int i=0; i<layers.size(); ++i)
-            {
-                //LayerData& layerData = zone._layers[i];
-                LandCoverLayer* layer = layers[i].get();
-
-                if ( layer->getLOD() == key.getLOD() &&
-                    (!isShadowCamera || layer->getCastShadows()) )
-                {
-                    if ( !pushedTileStateSet )
-                    {
-                        cv->pushStateSet( tileStateSet );
-                        pushedTileStateSet = true;
-                    }
-
-                    cv->pushStateSet( layer->getStateSet() );
-
-                    tilePatch->accept( *cv );
-
-                    cv->popStateSet();
-                }
-            }
-
-            if ( pushedTileStateSet )
-            {
-                cv->popStateSet();
-            }
-        }
-    }
-}
-
-void
-LandCoverTilePatchCallback::release(const TileKey& key)
-{
-    // nop - implementation is stateless.
-}
diff --git a/src/osgEarthSplat/LandUseTileSource b/src/osgEarthSplat/LandUseTileSource
index e4d5eac..f1a95be 100644
--- a/src/osgEarthSplat/LandUseTileSource
+++ b/src/osgEarthSplat/LandUseTileSource
@@ -21,7 +21,7 @@
 
 #include <osgEarth/TileSource>
 #include <osgEarth/ImageLayer>
-#include <osgEarthUtil/SimplexNoise>
+#include <osgEarth/SimplexNoise>
 #include <osgDB/FileNameUtils>
 #include "Export"
 
@@ -37,7 +37,7 @@ namespace osgEarth { namespace Splat
             : osgEarth::TileSourceOptions(options)
         {
             setDriver("landuse");
-            baseLOD().init( 32u );
+            baseLOD().init( 12u );
             warpFactor().init( 0.01f );
             fromConfig( _conf );
         }
@@ -149,7 +149,7 @@ namespace osgEarth { namespace Splat
         osg::ref_ptr<ImageLayer>     _imageLayer;
         ImageLayerVector             _imageLayers;
         std::vector<float>           _warps;
-        osgEarth::Util::SimplexNoise _noiseGen;
+        osgEarth::SimplexNoise _noiseGen;
     };
 
     /**
diff --git a/src/osgEarthSplat/LandUseTileSource.cpp b/src/osgEarthSplat/LandUseTileSource.cpp
index a5568dc..9f8d3c1 100644
--- a/src/osgEarthSplat/LandUseTileSource.cpp
+++ b/src/osgEarthSplat/LandUseTileSource.cpp
@@ -21,7 +21,7 @@
 #include <osgEarth/MapFrame>
 #include <osgEarth/Registry>
 #include <osgEarth/ImageUtils>
-#include <osgEarthUtil/SimplexNoise>
+#include <osgEarth/SimplexNoise>
 
 using namespace osgEarth;
 using namespace osgEarth::Splat;
@@ -67,7 +67,7 @@ namespace
             osg::clampBetween( covIn.y() + n1*warp, 0.0f, 1.0f ) );
     }
 
-    float getNoise(osgEarth::Util::SimplexNoise& noiseGen, const osg::Vec2& uv)
+    float getNoise(osgEarth::SimplexNoise& noiseGen, const osg::Vec2& uv)
     {
         // TODO: check that u and v are 0..s and not 0..s-1
         double n = noiseGen.getTiledValue(uv.x(), uv.y());
@@ -154,7 +154,7 @@ namespace
 
         void load(const TileKey& key, ImageLayer* sourceLayer, float sourceWarp, ProgressCallback* progress)
         {
-            if ( sourceLayer->getEnabled() && sourceLayer->getVisible() && sourceLayer->isKeyInRange(key) )
+            if ( sourceLayer->getEnabled() && sourceLayer->getVisible() && sourceLayer->isKeyInLegalRange(key) )
             {
                 for(TileKey k = key; k.valid() && !image.valid(); k = k.createParentKey())
                 {
@@ -246,7 +246,7 @@ LandUseTileSource::createImage(const TileKey&    key,
                     continue;
 
                 if ( !layer.image.valid() )
-                    layer.load(key, _imageLayers[L], _warps[L], progress);
+                    layer.load(key, _imageLayers[L].get(), _warps[L], progress);
 
                 if ( !layer.valid )
                     continue;
diff --git a/src/osgEarthSplat/NoiseTextureFactory.cpp b/src/osgEarthSplat/NoiseTextureFactory.cpp
index 457df95..56ddaaf 100644
--- a/src/osgEarthSplat/NoiseTextureFactory.cpp
+++ b/src/osgEarthSplat/NoiseTextureFactory.cpp
@@ -20,7 +20,7 @@
 
 #include <osgEarth/ImageUtils>
 #include <osgEarth/Random>
-#include <osgEarthUtil/SimplexNoise>
+#include <osgEarth/SimplexNoise>
 #include <osg/Texture2D>
 
 using namespace osgEarth;
@@ -52,7 +52,7 @@ NoiseTextureFactory::create(unsigned dim, unsigned chans) const
     for(int k=0; k<chans; ++k)
     {
         // Configure the noise function:
-        osgEarth::Util::SimplexNoise noise;
+        osgEarth::SimplexNoise noise;
         noise.setNormalize( true );
         noise.setRange( 0.0, 1.0 );
         noise.setFrequency( F[k] );
@@ -114,6 +114,7 @@ NoiseTextureFactory::create(unsigned dim, unsigned chans) const
     tex->setFilter(tex->MAG_FILTER, tex->LINEAR);
     tex->setMaxAnisotropy( 4.0f );
     tex->setUnRefImageDataAfterApply( true );
+    ImageUtils::activateMipMaps(tex);
 
     return tex;
 }
diff --git a/src/osgEarthSplat/RoadSurfaceLayer b/src/osgEarthSplat/RoadSurfaceLayer
new file mode 100644
index 0000000..59b2955
--- /dev/null
+++ b/src/osgEarthSplat/RoadSurfaceLayer
@@ -0,0 +1,160 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+ * Copyright 2016 Pelican Mapping
+ * http://osgearth.org
+ *
+ * osgEarth is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+#ifndef OSGEARTH_SPLAT_ROAD_SURFACE_LAYER
+#define OSGEARTH_SPLAT_ROAD_SURFACE_LAYER 1
+
+#include <osgEarth/ImageLayer>
+#include <osgEarth/TileRasterizer>
+#include <osgEarth/LayerListener>
+#include <osgEarthFeatures/FeatureSource>
+#include <osgEarthFeatures/FeatureSourceLayer>
+#include <osgEarthSymbology/StyleSheet>
+#include "Export"
+
+namespace osgEarth { namespace Splat
+{
+    using namespace osgEarth;
+    using namespace osgEarth::Features;
+
+    /**
+     * Configuration options for the land use tile source
+     */
+    class OSGEARTHSPLAT_EXPORT RoadSurfaceLayerOptions : public ImageLayerOptions    
+    {
+    public:
+        RoadSurfaceLayerOptions(const ConfigOptions& options = ConfigOptions())
+            : ImageLayerOptions(options)
+        {
+            fromConfig(_conf);
+        }
+
+    public:
+
+        //! Inline feature specification
+        optional<FeatureSourceOptions>& features() { return _featureSourceOptions; }
+        const optional<FeatureSourceOptions>& features() const { return _featureSourceOptions; }
+
+        //! Name of feature source layer containing features
+        optional<std::string>& featureSourceLayer() { return _featureSourceLayer; }
+        const optional<std::string>& featureSourceLayer() const { return _featureSourceLayer; }
+
+        //! Buffer around the road vector for querying linear data (should be at least road width/2)
+        optional<Distance>& featureBufferWidth() { return _bufferWidth; }
+        const optional<Distance>& featureBufferWidth() const { return _bufferWidth; }
+
+        //! Style for rendering the road
+        osg::ref_ptr<StyleSheet>& styles() { return _styles; }
+        const osg::ref_ptr<StyleSheet>& styles() const { return _styles; }
+
+    public:
+        Config getConfig() const
+        {
+            Config conf = ImageLayerOptions::getConfig();
+            conf.key() = "road_surface";
+            conf.addObjIfSet("features",  _featureSourceOptions);
+            conf.addIfSet("feature_source", _featureSourceLayer);
+            conf.addIfSet("buffer_width", _bufferWidth);
+            conf.addObjIfSet("styles", _styles);
+            return conf;
+        }
+
+    protected:
+        void mergeConfig( const Config& conf ) {
+            ImageLayerOptions::mergeConfig( conf );
+            fromConfig( conf );
+        }
+
+    private:
+        void fromConfig( const Config& conf )
+        {
+            conf.getObjIfSet("features",  _featureSourceOptions);
+            conf.getIfSet("feature_source", _featureSourceLayer);
+            conf.getIfSet("buffer_width", _bufferWidth);
+            conf.getObjIfSet("styles", _styles);
+        }
+        
+    private:
+        optional<FeatureSourceOptions> _featureSourceOptions;
+        optional<std::string> _featureSourceLayer;
+        optional<Distance> _bufferWidth;
+        osg::ref_ptr<StyleSheet> _styles;
+    };
+
+    /**
+     * Tile source that will read from ANOTHER tile source and perform
+     * various pre-processing syntheses operations like warping and detailing.
+     */
+    class OSGEARTHSPLAT_EXPORT RoadSurfaceLayer : public osgEarth::ImageLayer
+    {
+    public:
+        META_Layer(osgEarth, RoadSurfaceLayer, RoadSurfaceLayerOptions);
+
+        //! Create a blank layer to be configured with options()
+        RoadSurfaceLayer();
+
+        //! Create a layer from deserialized options
+        RoadSurfaceLayer(const RoadSurfaceLayerOptions& options);
+
+    public:
+
+        //! Sets the map layer from which to pull feature data. Call either
+        //! This or setFeatureSource
+        void setFeatureSourceLayer(FeatureSourceLayer* layer);
+
+        //! Sets the feature source to get road data from; call either this
+        //! or setFeatureSourceLayer
+        void setFeatureSource(FeatureSource* source);
+
+    public: // ImageLayer
+
+        // Opens the layer and returns a status
+        virtual const Status& open();
+
+        // Creates an image for a tile key
+        virtual GeoImage createImageImplementation(const TileKey& key, ProgressCallback* progress);
+
+    protected: // Layer
+
+        // Called by Map when it adds this layer
+        virtual void addedToMap(const class Map*);
+
+        // Called by Map when it removes this layer
+        virtual void removedFromMap(const class Map*);
+
+        // post-ctor initialization
+        virtual void init();
+
+        // A node to add to the scene graph for this layer.
+        virtual osg::Node* getOrCreateNode();
+
+    protected:
+
+        virtual ~RoadSurfaceLayer() { }
+
+    private:
+        osg::ref_ptr<FeatureSource> _features;
+        osg::ref_ptr<Session> _session;
+        osg::observer_ptr<TileRasterizer> _rasterizer;
+
+        LayerListener<RoadSurfaceLayer, FeatureSourceLayer> _layerListener;
+    };
+
+} } // namespace osgEarth::Splat
+
+#endif // OSGEARTH_SPLAT_ROAD_SURFACE_LAYER
diff --git a/src/osgEarthSplat/RoadSurfaceLayer.cpp b/src/osgEarthSplat/RoadSurfaceLayer.cpp
new file mode 100644
index 0000000..fb5b439
--- /dev/null
+++ b/src/osgEarthSplat/RoadSurfaceLayer.cpp
@@ -0,0 +1,407 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+ * Copyright 2016 Pelican Mapping
+ * http://osgearth.org
+ *
+ * osgEarth is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+#include "RoadSurfaceLayer"
+#include <osgEarth/Utils>
+#include <osgEarth/Map>
+#include <osgEarth/TileRasterizer>
+#include <osgEarth/VirtualProgram>
+#include <osgEarthFeatures/FilterContext>
+#include <osgEarthFeatures/GeometryCompiler>
+#include <osgDB/WriteFile>
+
+using namespace osgEarth;
+using namespace osgEarth::Splat;
+using namespace osgEarth::Features;
+using namespace osgEarth::Symbology;
+
+#define LC "[RoadSurfaceLayer] "
+
+
+REGISTER_OSGEARTH_LAYER(road_surface, RoadSurfaceLayer);
+
+RoadSurfaceLayer::RoadSurfaceLayer() :
+ImageLayer(&_optionsConcrete),
+_options(&_optionsConcrete)
+{
+    init();
+}
+
+RoadSurfaceLayer::RoadSurfaceLayer(const RoadSurfaceLayerOptions& options) :
+ImageLayer(&_optionsConcrete),
+_options(&_optionsConcrete),
+_optionsConcrete(options)
+{
+    init();
+}
+
+void
+RoadSurfaceLayer::init()
+{
+    setTileSourceExpected(false);
+
+    // Generate Mercator tiles by default.
+    setProfile(Profile::create("global-geodetic"));
+    //setProfile(Profile::create("spherical-mercator"));
+
+    // Create a rasterizer for rendering nodes to images.
+    _rasterizer = new TileRasterizer(); 
+
+    ImageLayer::init();
+
+    if (getName().empty())
+        setName("Road surface");
+}
+
+const Status&
+RoadSurfaceLayer::open()
+{
+    // assert a feature source:
+    if (!options().features().isSet() && !options().featureSourceLayer().isSet())
+    {
+        return setStatus(Status::Error(Status::ConfigurationError, "Missing required feature source"));
+    }
+
+    if (options().features().isSet())
+    {
+        // create and attempt to open that feature source:
+        osg::ref_ptr<FeatureSource> features = FeatureSourceFactory::create(options().features().get());
+        if (features.valid())
+        {
+            setStatus(features->open());
+
+            if (getStatus().isOK())
+            {
+                setFeatureSource(features.get());
+            }
+        }
+        else
+        {
+            return setStatus(Status::Error(Status::ServiceUnavailable, "Cannot load feature source"));
+        }
+    }
+
+
+    if (getStatus().isOK())
+        return ImageLayer::open();
+    else
+        return getStatus();
+}
+
+void
+RoadSurfaceLayer::addedToMap(const Map* map)
+{
+    // create a session for feature processing based in the Map,
+    // but don't set the feature source yet.
+    _session = new Session(map, options().styles().get(), 0L, getReadOptions());
+    _session->setResourceCache(new ResourceCache());
+
+    if (options().featureSourceLayer().isSet())
+    {
+        _layerListener.listen(
+            map,
+            options().featureSourceLayer().get(),
+            this,
+            &RoadSurfaceLayer::setFeatureSourceLayer);
+    }
+    else if (!_features.valid())
+    {
+        setStatus(Status::Error(Status::ConfigurationError, "No features"));
+    }
+}
+
+void
+RoadSurfaceLayer::removedFromMap(const Map* map)
+{
+    _session = 0L;
+}
+
+osg::Node*
+RoadSurfaceLayer::getOrCreateNode()
+{
+    // adds the Rasterizer to the scene graph so we can rasterize tiles
+    return _rasterizer.get();
+}
+
+void
+RoadSurfaceLayer::setFeatureSource(FeatureSource* fs)
+{
+    if (fs != _features.get())
+    {
+        _features = fs;
+        if (_features.valid())
+        {
+            setStatus(_features->getStatus());
+        }
+    }
+}
+
+void
+RoadSurfaceLayer::setFeatureSourceLayer(FeatureSourceLayer* layer)
+{
+    if (layer && layer->getStatus().isError())
+    {
+        setStatus(Status::Error(Status::ResourceUnavailable, "Feature source layer is unavailable; check for error"));
+        return;
+    }
+
+    if (layer)
+        OE_INFO << LC << "Feature source layer is \"" << layer->getName() << "\"\n";
+
+    setFeatureSource(layer ? layer->getFeatureSource() : 0L);
+}
+
+typedef std::vector< std::pair< Style, FeatureList > > StyleToFeatures;
+
+void addFeatureToMap(Feature* feature, const Style& style, StyleToFeatures& map)
+{
+    bool added = false;
+
+    if (!style.getName().empty())
+    {
+        // Try to find the style by name
+        for (int i = 0; i < map.size(); i++)
+        {
+            if (map[i].first.getName() == style.getName())
+            {
+                map[i].second.push_back(feature);
+                added = true;
+                break;
+            }
+        }
+    }
+
+    if (!added)
+    {
+        FeatureList list;
+        list.push_back( feature );
+        map.push_back(std::pair< Style, FeatureList>(style, list));
+    }                                
+}
+
+void sortFeaturesIntoStyleGroups(StyleSheet* styles, FeatureList& features, FilterContext &context, StyleToFeatures& map)
+{
+    if ( styles == 0L )
+        return;
+
+    if ( styles->selectors().size() > 0 )
+    {
+        for( StyleSelectorList::const_iterator i = styles->selectors().begin(); i != styles->selectors().end(); ++i )
+        {
+            const StyleSelector& sel = *i;
+
+            if ( sel.styleExpression().isSet() )
+            {
+                // establish the working bounds and a context:
+                StringExpression styleExprCopy(  sel.styleExpression().get() );
+
+                for (FeatureList::iterator itr = features.begin(); itr != features.end(); ++itr)
+                {
+                    Feature* feature = itr->get();
+
+                    const std::string& styleString = feature->eval( styleExprCopy, &context );
+                    if (!styleString.empty() && styleString != "null")
+                    {
+                        // resolve the style:
+                        Style combinedStyle;
+
+                        // if the style string begins with an open bracket, it's an inline style definition.
+                        if ( styleString.length() > 0 && styleString[0] == '{' )
+                        {
+                            Config conf( "style", styleString );
+                            conf.setReferrer( sel.styleExpression().get().uriContext().referrer() );
+                            conf.set( "type", "text/css" );
+                            combinedStyle = Style(conf);
+                        }
+
+                        // otherwise, look up the style in the stylesheet. Do NOT fall back on a default
+                        // style in this case: for style expressions, the user must be explicity about 
+                        // default styling; this is because there is no other way to exclude unwanted
+                        // features.
+                        else
+                        {
+                            const Style* selectedStyle = styles->getStyle(styleString, false);
+                            if ( selectedStyle )
+                                combinedStyle = *selectedStyle;
+                        }
+
+                        if (!combinedStyle.empty())
+                        {
+                            addFeatureToMap( feature, combinedStyle, map);
+                        }                                
+                    }
+                }
+            }
+        }
+    }
+    else
+    {
+        const Style* style = styles->getDefaultStyle();
+        for (FeatureList::iterator itr = features.begin(); itr != features.end(); ++itr)
+        {
+            Feature* feature = itr->get();
+            addFeatureToMap( feature, *style, map);
+        }        
+    }
+}
+
+GeoImage
+RoadSurfaceLayer::createImageImplementation(const TileKey& key, ProgressCallback* progress)
+{
+    if (getStatus().isError())    
+    {
+        return GeoImage::INVALID;
+    }
+    
+    if (!_features.valid())
+    {
+        setStatus(Status(Status::ServiceUnavailable, "No feature source"));
+        return GeoImage::INVALID;
+    }
+
+    const FeatureProfile* featureProfile = _features->getFeatureProfile();
+    if (!featureProfile)
+    {
+        setStatus(Status(Status::ConfigurationError, "Feature profile is missing"));
+        return GeoImage::INVALID;
+    }
+
+    const SpatialReference* featureSRS = featureProfile->getSRS();
+    if (!featureSRS)
+    {
+        setStatus(Status(Status::ConfigurationError, "Feature profile has no SRS"));
+        return GeoImage::INVALID;
+    }
+
+    // If the feature source has a tiling profile, we are going to have to map the incoming
+    // TileKey to a set of intersecting TileKeys in the feature source's tiling profile.
+    GeoExtent featureExtent = key.getExtent().transform(featureSRS);
+    GeoExtent queryExtent = featureExtent;
+
+    // Buffer the incoming extent, if requested.
+    if (options().featureBufferWidth().isSet())
+    {
+        GeoExtent geoExtent = queryExtent.transform(featureSRS->getGeographicSRS());
+        double latitude = geoExtent.getCentroid().y();
+        double buffer = SpatialReference::transformUnits(options().featureBufferWidth().get(), featureSRS, latitude);
+        queryExtent.expand(buffer, buffer);
+    }
+    
+
+    FeatureList features;
+
+    if (featureProfile->getProfile())
+    {
+        // Resolve the list of tile keys that intersect the incoming extent.
+        std::vector<TileKey> intersectingKeys;
+        featureProfile->getProfile()->getIntersectingTiles(queryExtent, key.getLOD(), intersectingKeys);
+
+        std::set<TileKey> featureKeys;
+        for (int i = 0; i < intersectingKeys.size(); ++i)
+        {        
+            if (intersectingKeys[i].getLOD() > featureProfile->getMaxLevel())
+                featureKeys.insert(intersectingKeys[i].createAncestorKey(featureProfile->getMaxLevel()));
+            else
+                featureKeys.insert(intersectingKeys[i]);
+        }
+
+        // Query and collect all the features we need for this tile.
+        for (std::set<TileKey>::const_iterator i = featureKeys.begin(); i != featureKeys.end(); ++i)
+        {
+            Query query;        
+            query.tileKey() = *i;
+
+            osg::ref_ptr<FeatureCursor> cursor = _features->createFeatureCursor(query);
+            if (cursor.valid())
+            {
+                cursor->fill(features);
+            }
+        }
+    }
+    else
+    {
+        // Set up the query; bounds must be in the feature SRS:
+        Query query;
+        query.bounds() = queryExtent.bounds();
+
+        // Run the query and fill the list.
+        osg::ref_ptr<FeatureCursor> cursor = _features->createFeatureCursor(query);
+        if (cursor.valid())
+        {
+            cursor->fill(features);
+        }
+    }
+
+    if (!features.empty())
+    {
+        // Create the output extent:
+        GeoExtent outputExtent = key.getExtent();
+
+        const SpatialReference* keySRS = outputExtent.getSRS();
+        osg::Vec3d pos(outputExtent.west(), outputExtent.south(), 0);
+        osg::ref_ptr<const SpatialReference> srs = keySRS->createTangentPlaneSRS(pos);
+        outputExtent = outputExtent.transform(srs.get());
+
+        FilterContext fc(_session.get(), featureProfile, featureExtent);
+        fc.setOutputSRS(outputExtent.getSRS());
+
+        // compile the features into a node.
+        GeometryCompiler compiler;
+
+        StyleToFeatures map;
+        sortFeaturesIntoStyleGroups(options().styles().get(), features, fc, map);
+        osg::ref_ptr< osg::Group > group;
+        if (!map.empty())
+        {
+            group = new osg::Group;
+            for (unsigned int i = 0; i < map.size(); i++)
+            {
+                osg::ref_ptr<osg::Node> node = compiler.compile(map[i].second, map[i].first, fc);
+                if (node.valid() && node->getBound().valid())
+                {
+                    group->addChild( node );
+                }
+            }
+        }
+
+        if (group && group->getBound().valid())
+        {
+            Threading::Future<osg::Image> imageFuture;
+
+            // Schedule the rasterization and get the future.
+            osg::ref_ptr<TileRasterizer> rasterizer;
+            if (_rasterizer.lock(rasterizer))
+            {
+                imageFuture = rasterizer->push(group.release(), getTileSize(), outputExtent);
+
+                // Immediately discard the temporary reference to the rasterizer, because
+                // otherwise a deadlock can occur if the application exits while the call
+                // to release() below is blocked.
+                rasterizer = 0L;
+            }
+
+            osg::Image* image = imageFuture.release();
+            if (image)
+            {
+                return GeoImage(image, key.getExtent());
+            }
+        }
+    }
+
+    return GeoImage::INVALID;
+}
diff --git a/src/osgEarthSplat/Splat.Noise.glsl b/src/osgEarthSplat/Splat.Noise.glsl
index fccd2d3..206d9aa 100644
--- a/src/osgEarthSplat/Splat.Noise.glsl
+++ b/src/osgEarthSplat/Splat.Noise.glsl
@@ -1,4 +1,5 @@
 #version $GLSL_VERSION_STR
+$GLSL_DEFAULT_PRECISION_FLOAT
 
 //
 // Description : Array and textureless GLSL 2D/3D/4D simplex 
diff --git a/src/osgEarthSplat/Splat.frag.common.glsl b/src/osgEarthSplat/Splat.frag.common.glsl
deleted file mode 100644
index 9a53f20..0000000
--- a/src/osgEarthSplat/Splat.frag.common.glsl
+++ /dev/null
@@ -1,35 +0,0 @@
-// begin: Splat.frag.common.glsl
-
-// TODO:
-// Encapsulate the "use normal map" define logic in the terrain SDK itself.
-
-#pragma vp_define OE_USE_NORMAL_MAP
-
-#ifdef OE_USE_NORMAL_MAP
-
-// import SDK
-vec4 oe_terrain_getNormalAndCurvature(in vec2);
-
-// normal map version:
-in vec2 oe_normalMapCoords;
-
-float oe_splat_getSlope()
-{
-    vec4 encodedNormal = oe_terrain_getNormalAndCurvature( oe_normalMapCoords );
-    vec3 normalTangent = normalize(encodedNormal.xyz*2.0-1.0);
-    return clamp((1.0-normalTangent.z)/0.8, 0.0, 1.0);
-}
-
-#else // !OE_USE_NORMAL_MAP
-
-// non- normal map version:
-in float oe_splat_slope;
-
-float oe_splat_getSlope()
-{
-    return oe_splat_slope;
-}
-
-#endif // OE_USE_NORMAL_MAP
-
-// end: Splat.frag.common.glsl
\ No newline at end of file
diff --git a/src/osgEarthSplat/Splat.frag.glsl b/src/osgEarthSplat/Splat.frag.glsl
index ebb2b2d..b92cb50 100644
--- a/src/osgEarthSplat/Splat.frag.glsl
+++ b/src/osgEarthSplat/Splat.frag.glsl
@@ -1,4 +1,5 @@
 #version $GLSL_VERSION_STR
+$GLSL_DEFAULT_PRECISION_FLOAT
 
 #if(__VERSION__ < 400)
 #extension GL_ARB_gpu_shader5 : enable      // textureGather
@@ -6,18 +7,12 @@
 
 #pragma vp_entryPoint oe_splat_complex
 #pragma vp_location   fragment_coloring
-#pragma vp_order      0.4
-
-// define to activate 'edit' mode in which uniforms control
-// the splatting parameters.
-#pragma vp_define SPLAT_EDIT
-
-// define to activate GPU-generated noise instead of a noise texture.
-#pragma vp_define SPLAT_GPU_NOISE
 
 // include files
 #pragma include Splat.types.glsl
-#pragma include Splat.frag.common.glsl
+
+// statset defines
+#pragma import_defines(OE_SPLAT_HAVE_NOISE_SAMPLER, OE_SPLAT_EDIT_MODE, OE_SPLAT_GPU_NOISE, OE_TERRAIN_RENDER_NORMAL_MAP, OE_TERRAIN_BLEND_IMAGERY)
 
 // from: Splat.util.glsl
 void oe_splat_getLodBlend(in float range, out float lod0, out float rangeOuter, out float rangeInner, out float clampedRange);
@@ -33,7 +28,7 @@ in vec2 oe_splat_covtc;                     // coverage texture coords
 in float oe_splat_range;                    // distance from camera to vertex
 flat in float oe_splat_coverageTexSize;     // size of coverage texture
 
-// from SplatTerrainEffect:
+// from SplatLayerFactory:
 uniform sampler2D oe_splat_coverageTex;
 uniform sampler2DArray oe_splatTex;
 uniform int oe_splat_scaleOffsetInt;
@@ -41,7 +36,7 @@ uniform int oe_splat_scaleOffsetInt;
 uniform float oe_splat_detailRange;
 uniform float oe_splat_noiseScale;
 
-#ifdef SPLAT_EDIT
+#ifdef OE_SPLAT_EDIT_MODE
 uniform float oe_splat_brightness;
 uniform float oe_splat_contrast;
 uniform float oe_splat_threshold;
@@ -51,43 +46,83 @@ uniform float oe_splat_minSlope;
 // lookup table containing the coverage value => texture index mappings
 uniform samplerBuffer oe_splat_coverageLUT;
 
+uniform int oe_layer_order;
+
+//............................................................................
+// Get the slope of the terrain
+
+#ifdef OE_TERRAIN_RENDER_NORMAL_MAP
+// import SDK
+vec4 oe_terrain_getNormalAndCurvature(in vec2);
+
+// normal map version:
+in vec2 oe_normalMapCoords;
 
+float oe_splat_getSlope()
+{
+    vec4 encodedNormal = oe_terrain_getNormalAndCurvature( oe_normalMapCoords );
+    vec3 normalTangent = normalize(encodedNormal.xyz*2.0-1.0);
+    return clamp((1.0-normalTangent.z)/0.8, 0.0, 1.0);
+}
+
+#else // !OE_TERRAIN_RENDER_NORMAL_MAP
+
+// non- normal map version:
+in float oe_splat_slope;
+
+float oe_splat_getSlope()
+{
+    return oe_splat_slope;
+}
+
+#endif // OE_TERRAIN_RENDER_NORMAL_MAP
+
+
+//............................................................................
 // reads the encoded splatting render information for a coverage value.
 // this data was encoded in Surface::createLUTBUffer().
+
 void oe_splat_getRenderInfo(in float value, in oe_SplatEnv env, out oe_SplatRenderInfo ri)
 {
     const int num_lods = 26;
-    const float inv255 = 0.00392156862;
 
-    int index = int(value)*num_lods + int(env.lod);
+    int lutIndex = int(value)*num_lods + int(env.lod);
 
     // fetch the splatting parameters:
-    vec4 t = texelFetch(oe_splat_coverageLUT, index);
+    vec4 t = texelFetch(oe_splat_coverageLUT, lutIndex);
 
     ri.primaryIndex = t[0];
     ri.detailIndex  = t[1];
 
     // brightness and contrast are packed into one float:
     ri.brightness   = trunc(t[2])/100.0;
-    ri.contrast     = fract(t[2])*100.0;
+    ri.contrast     = fract(t[2])*10.0;
 
     // threshold and slope are packed into one float:
     ri.threshold    = trunc(t[3])/100.0;
-    ri.minSlope     = fract(t[3])*100.0;
+    ri.minSlope     = fract(t[3])*10.0;
 }
 
+
+//............................................................................
+// Sample a texel from the splatting texture catalog
+
 vec4 oe_splat_getTexel(in float index, in vec2 tc)
 {
-    return texture(oe_splatTex, vec3(tc, index));
+    //return texture(oe_splatTex, vec3(tc, index));
+    return index >= 0.0 ? texture(oe_splatTex, vec3(tc, index)) : vec4(1,0,0,0);
 }
 
+
+//............................................................................
 // Samples a detail texel using its render info parameters.
 // Returns the weighting factor in the alpha channel.
+
 vec4 oe_splat_getDetailTexel(in oe_SplatRenderInfo ri, in vec2 tc, in oe_SplatEnv env)
 {
     float hasDetail = clamp(ri.detailIndex+1.0, 0.0, 1.0);
 
-#ifdef SPLAT_EDIT
+#ifdef OE_SPLAT_EDIT_MODE
     float brightness = oe_splat_brightness;
     float contrast = oe_splat_contrast;
     float threshold = oe_splat_threshold;
@@ -124,7 +159,9 @@ vec4 oe_splat_getDetailTexel(in oe_SplatRenderInfo ri, in vec2 tc, in oe_SplatEn
     return vec4(result.rgb, hasDetail*n);
 }
 
+//............................................................................
 // Generates a texel using nearest-neighbor coverage sampling.
+
 vec4 oe_splat_nearest(in vec2 splat_tc, inout oe_SplatEnv env)
 {
     float coverageValue = texture(oe_splat_coverageTex, oe_splat_covtc).r;
@@ -133,10 +170,12 @@ vec4 oe_splat_nearest(in vec2 splat_tc, inout oe_SplatEnv env)
     vec4 primary = oe_splat_getTexel(ri.primaryIndex, splat_tc);
     float detailToggle = ri.detailIndex >= 0 ? 1.0 : 0.0;
     vec4 detail  = oe_splat_getDetailTexel(ri, splat_tc, env) * detailToggle;    
-    return vec4( mix(primary.rgb, detail.rgb, detail.a), 1.0 );
+    return vec4( mix(primary.rgb, detail.rgb, detail.a), primary.a );
 }
 
+//............................................................................
 // Generates a texel using bilinear filtering on the coverage data.
+
 vec4 oe_splat_bilinear(in vec2 splat_tc, inout oe_SplatEnv env)
 {
     vec4 texel = vec4(0,0,0,1);
@@ -156,51 +195,38 @@ vec4 oe_splat_bilinear(in vec2 splat_tc, inout oe_SplatEnv env)
     oe_SplatRenderInfo ri_nw; oe_splat_getRenderInfo(value_nw, env, ri_nw);
 
     // Primary splat:
-    vec3 sw_primary = oe_splat_getTexel(ri_sw.primaryIndex, splat_tc).rgb;
-    vec3 se_primary = oe_splat_getTexel(ri_se.primaryIndex, splat_tc).rgb;
-    vec3 ne_primary = oe_splat_getTexel(ri_ne.primaryIndex, splat_tc).rgb;
-    vec3 nw_primary = oe_splat_getTexel(ri_nw.primaryIndex, splat_tc).rgb;
+    vec4 sw_primary = oe_splat_getTexel(ri_sw.primaryIndex, splat_tc);
+    vec4 se_primary = oe_splat_getTexel(ri_se.primaryIndex, splat_tc);
+    vec4 ne_primary = oe_splat_getTexel(ri_ne.primaryIndex, splat_tc);
+    vec4 nw_primary = oe_splat_getTexel(ri_nw.primaryIndex, splat_tc);
 
     // Detail splat - weighting is in the alpha channel
     // TODO: Pointless to have a detail range? -gw
     // TODO: If noise is a texture, just try to single-sample it instead
-    float detailToggle =env.range < oe_splat_detailRange ? 1.0 : 0.0;
+    float detailToggle = env.range < oe_splat_detailRange ? 1.0 : 0.0;
     vec4 sw_detail = detailToggle * oe_splat_getDetailTexel(ri_sw, splat_tc, env);
     vec4 se_detail = detailToggle * oe_splat_getDetailTexel(ri_se, splat_tc, env);
     vec4 ne_detail = detailToggle * oe_splat_getDetailTexel(ri_ne, splat_tc, env);
-    vec4 nw_detail = detailToggle * oe_splat_getDetailTexel(ri_nw, splat_tc, env);   
+    vec4 nw_detail = detailToggle * oe_splat_getDetailTexel(ri_nw, splat_tc, env); 
 
-#if 0
-    // Combine everything based on weighting:
-    texel.rgb =
-        sw_weight * mix(sw_primary, sw_detail.rgb, sw_detail.a) +
-        se_weight * mix(se_primary, se_detail.rgb, se_detail.a) +
-        ne_weight * mix(ne_primary, ne_detail.rgb, ne_detail.a) +
-        nw_weight * mix(nw_primary, nw_detail.rgb, nw_detail.a);
-
-#else
+    vec4 nw_mix = mix(nw_primary, nw_detail, nw_detail.a);
+    vec4 ne_mix = mix(ne_primary, ne_detail, ne_detail.a);
+    vec4 sw_mix = mix(sw_primary, sw_detail, sw_detail.a);
+    vec4 se_mix = mix(se_primary, se_detail, se_detail.a);
 
-    vec3 nw_mix = mix(nw_primary, nw_detail.rgb, nw_detail.a);
-    vec3 ne_mix = mix(ne_primary, ne_detail.rgb, ne_detail.a);
-    vec3 sw_mix = mix(sw_primary, sw_detail.rgb, sw_detail.a);
-    vec3 se_mix = mix(se_primary, se_detail.rgb, se_detail.a);
+    vec2 weight = fract( oe_splat_covtc*size - 0.5+(1.0/size) );
 
-    //float cellSize = 1.0/size;
-    //vec2 g1 = fract(oe_splat_covtc*size); //(size-1.0));
-    //vec2 g2 = fract(g1-0.5+pixelWidth);
-    vec2 weight = fract( oe_splat_covtc*size - 0.5+(1.0/size) ); //cellSize);
+    vec4 temp0 = mix(nw_mix, ne_mix, weight.x);
+    vec4 temp1 = mix(sw_mix, se_mix, weight.x);
 
-    vec3 temp0 = mix(nw_mix, ne_mix, weight.x);
-    vec3 temp1 = mix(sw_mix, se_mix, weight.x);
-
-    texel.rgb = mix(temp1, temp0, weight.y);
-
-#endif
+    texel = mix(temp1, temp0, weight.y);
 
     return texel;
 }
 
-#ifdef SPLAT_GPU_NOISE
+//............................................................................
+
+#ifdef OE_SPLAT_GPU_NOISE
 
 uniform float oe_splat_freq;
 uniform float oe_splat_pers;
@@ -217,15 +243,26 @@ vec4 oe_splat_getNoise(in vec2 tc)
 
 #else // !SPLAT_GPU_NOISE
 
+#ifdef OE_SPLAT_HAVE_NOISE_SAMPLER
 uniform sampler2D oe_splat_noiseTex;
 vec4 oe_splat_getNoise(in vec2 tc)
 {
     return texture(oe_splat_noiseTex, tc.st);
 }
+#else
+vec4 oe_splat_getNoise(in vec2 tc)
+{
+    return vec4(0.0);
+}
+#endif
 
 #endif // SPLAT_GPU_NOISE
 
+
+
+//............................................................................
 // Simplified entry point with does no filtering or range blending. (much faster.)
+
 void oe_splat_simple(inout vec4 color)
 {
     float noiseLOD = floor(oe_splat_noiseScale);
@@ -247,7 +284,9 @@ void oe_splat_simple(inout vec4 color)
     //color = mix(color, vec4(tc.s, tc.t, 0.0, 1.0), 0.5);
 }
 
+//............................................................................
 // Main entry point for fragment shader.
+
 void oe_splat_complex(inout vec4 color)
 {
     // Noise coords.
@@ -281,12 +320,29 @@ void oe_splat_complex(inout vec4 color)
     // recalcluate blending ratio
     float lodBlend = clamp((rangeOuter - env.range) / (rangeOuter - rangeInner), 0, 1);
        
-    // Blend:
+    // Blend the two samples based on LOD factor:
     vec4 texel = mix(texel0, texel1, lodBlend);
 
+#if 0
     color = mix(color, texel, texel.a);
-    color.a = 1.0;
+    color.a = oe_layer_order > 0 ? texel.a : 1.0;
+    
+#else
+
+#ifdef OE_TERRAIN_BLEND_IMAGERY
 
-    // uncomment to visualize slope.
-    //color.rgba = vec4(env.slope,0,0,1);
+    if (oe_layer_order == 0)
+    {
+        color.rgb = texel.rgb*texel.a + color.rgb*(1.0-texel.a);
+        color.a = max(color.a, texel.a);
+    }
+    else
+#endif
+    {
+        color = mix(color, texel, texel.a);
+        color.a = texel.a;
+    }
+#endif
+    // uncomment to visualize slope, noise, etc.
+    //color.rgba = vec4(env.noise.x,0,0,1);  
 }
diff --git a/src/osgEarthSplat/Splat.util.glsl b/src/osgEarthSplat/Splat.util.glsl
index 0dda668..ed1a272 100644
--- a/src/osgEarthSplat/Splat.util.glsl
+++ b/src/osgEarthSplat/Splat.util.glsl
@@ -1,4 +1,5 @@
 #version $GLSL_VERSION_STR
+$GLSL_DEFAULT_PRECISION_FLOAT
 
 #pragma vp_location fragment_coloring
 
diff --git a/src/osgEarthSplat/Splat.vert.model.glsl b/src/osgEarthSplat/Splat.vert.model.glsl
index 09f9dc2..151c8ae 100644
--- a/src/osgEarthSplat/Splat.vert.model.glsl
+++ b/src/osgEarthSplat/Splat.vert.model.glsl
@@ -1,9 +1,14 @@
 #version $GLSL_VERSION_STR
+$GLSL_DEFAULT_PRECISION_FLOAT
 
 #pragma vp_entryPoint oe_splat_vertex_model
 #pragma vp_location   vertex_model
 #pragma vp_order      0.5
+#pragma import_defines(OE_TERRAIN_RENDER_NORMAL_MAP)
 
+// Transmit the approximate terrain slope if we're not rendering normal maps
+// in the terrain engine.
+#ifndef OE_TERRAIN_RENDER_NORMAL_MAP
 vec3 vp_Normal; // stage global
 out float oe_splat_slope;
 
@@ -14,3 +19,12 @@ void oe_splat_vertex_model(inout vec4 VertexMODEL)
     // are not available, which is hopefully never :/
     oe_splat_slope = 1.0-vp_Normal.z;
 }
+
+#else
+
+void oe_splat_vertex_model(inout vec4 VertexMODEL)
+{
+    //nop
+}
+
+#endif
diff --git a/src/osgEarthSplat/Splat.vert.view.glsl b/src/osgEarthSplat/Splat.vert.view.glsl
index b13c1ab..97540d3 100644
--- a/src/osgEarthSplat/Splat.vert.view.glsl
+++ b/src/osgEarthSplat/Splat.vert.view.glsl
@@ -1,4 +1,5 @@
 #version $GLSL_VERSION_STR
+$GLSL_DEFAULT_PRECISION_FLOAT
 
 #pragma vp_entryPoint oe_splat_vertex_view
 #pragma vp_location   vertex_view
@@ -6,6 +7,8 @@
 
 #pragma include Splat.types.glsl
 
+#pragma import_defines(OE_SPLAT_COVERAGE_TEXMAT)
+
 out vec4 oe_layer_tilec;
 out float oe_splat_range;
 out vec2 oe_splat_covtc;
@@ -13,7 +16,7 @@ out vec2 oe_splat_covtc;
 uniform sampler2D oe_splat_coverageTex;
 flat out float oe_splat_coverageTexSize;
 
-uniform mat4 COVERAGE_TEXTURE_MATRIX;   // assigned at runtime
+uniform mat4 OE_SPLAT_COVERAGE_TEXMAT;   // assigned at runtime
 
 
 void oe_splat_vertex_view(inout vec4 VertexVIEW)
@@ -23,7 +26,7 @@ void oe_splat_vertex_view(inout vec4 VertexVIEW)
 
     // calculate the coverage sampling coordinates. The texture matrix accounts
     // for any super-sampling that might be in effect for the current LOD.
-    oe_splat_covtc = (COVERAGE_TEXTURE_MATRIX * oe_layer_tilec).st;
+    oe_splat_covtc = (OE_SPLAT_COVERAGE_TEXMAT * oe_layer_tilec).st;
 
     // Precalculate the size of the coverage texture. This is faster than
     // calling textureSize per pixel in the fragment shader.
diff --git a/src/osgEarthSplat/SplatCatalog b/src/osgEarthSplat/SplatCatalog
index ea360d4..801b7c3 100644
--- a/src/osgEarthSplat/SplatCatalog
+++ b/src/osgEarthSplat/SplatCatalog
@@ -58,7 +58,6 @@ namespace osgEarth { namespace Splat
     struct OSGEARTHSPLAT_EXPORT SplatRangeData
     {
         optional<unsigned>        _maxLOD;
-        //optional<float>           _minRange;
         optional<URI>             _imageURI;
         optional<URI>             _modelURI;
         optional<int>             _modelCount;
@@ -153,7 +152,7 @@ namespace osgEarth { namespace Splat
     public: // static utility
 
         /** Reads a splat catalog from a URI. */
-        static SplatCatalog* read(const URI& uri, const osgDB::Options* options);
+        static SplatCatalog* read(const URI& uri, const osgDB::Options* options =0L);
 
 
     protected:
diff --git a/src/osgEarthSplat/SplatExtension b/src/osgEarthSplat/SplatExtension
index ea93edc..1d9ab4d 100644
--- a/src/osgEarthSplat/SplatExtension
+++ b/src/osgEarthSplat/SplatExtension
@@ -16,13 +16,14 @@
  * You should have received a copy of the GNU Lesser General Public License
  * along with this program.  If not, see <http://www.gnu.org/licenses/>
  */
+#if 0
 #ifndef OSGEARTH_SPLAT_SPLAT_EXTENSION
 #define OSGEARTH_SPLAT_SPLAT_EXTENSION 1
 
 #include "Export"
 #include "SplatOptions"
-#include "SplatTerrainEffect"
-#include "LandCoverTerrainEffect"
+#include "SplatLayerFactory"
+#include "GroundCoverLayerFactory"
 #include "Zone"
 
 #include <osgEarth/Extension>
@@ -80,11 +81,14 @@ namespace osgEarth { namespace Splat
 
     private:
         osg::ref_ptr<const osgDB::Options>   _dbo;
-        osg::ref_ptr<SplatTerrainEffect>     _splatEffect;
-        osg::ref_ptr<LandCoverTerrainEffect> _landCoverEffect;
+        osg::ref_ptr<SplatLayerFactory>      _splatLayerFactory;
+        osg::ref_ptr<GroundCoverLayerFactory>  _GroundCoverLayerFactory;
         osg::ref_ptr<ZoneSwitcher>           _zoneSwitcher;
+        int                                  _noiseTexUnit;
     };
 
 } } // namespace osgEarth::Splat
 
 #endif // OSGEARTH_SPLAT_SPLAT_EXTENSION
+
+#endif
\ No newline at end of file
diff --git a/src/osgEarthSplat/SplatExtension.cpp b/src/osgEarthSplat/SplatExtension.cpp
index 4e69e29..75bdc99 100644
--- a/src/osgEarthSplat/SplatExtension.cpp
+++ b/src/osgEarthSplat/SplatExtension.cpp
@@ -16,10 +16,12 @@
  * You should have received a copy of the GNU Lesser General Public License
  * along with this program.  If not, see <http://www.gnu.org/licenses/>
  */
+#if 0
 #include "SplatExtension"
 #include "SplatCatalog"
 #include "SplatCoverageLegend"
-#include "SplatTerrainEffect"
+#include "SplatLayerFactory"
+#include "NoiseTextureFactory"
 
 #include <osgEarth/MapNode>
 #include <osgEarth/TerrainEngineNode>
@@ -32,7 +34,7 @@ using namespace osgEarth::Splat;
 
 //.........................................................................
 
-REGISTER_OSGEARTH_EXTENSION(osgearth_splat, SplatExtension);
+//REGISTER_OSGEARTH_EXTENSION(osgearth_splat, SplatExtension);
 
 
 SplatExtension::SplatExtension()
@@ -81,14 +83,14 @@ SplatExtension::connect(MapNode* mapNode)
     }
 
     bool enableSurfaceEffect = false;
-    bool enableLandCoverEffect = false;
+    bool enableGroundCoverEffect = false;
 
     // Zone definitions
     Zones myZones;
     for(int i=0; i<zones().size(); ++i)
     {
         osg::ref_ptr<Zone> zone = new Zone();
-        if ( zone->configure(zones().at(i), mapNode->getMap(), _dbo.get()) )
+        if ( zone->configure(zones()[i], mapNode->getMap(), _dbo.get()) )
         {
             myZones.push_back( zone.get() );
 
@@ -97,33 +99,45 @@ SplatExtension::connect(MapNode* mapNode)
                 enableSurfaceEffect = true;
             }
 
-            if ( zone->getLandCover() != 0L )
+            if ( zone->getGroundCover() != 0L )
             {
-                enableLandCoverEffect = true;
+                enableGroundCoverEffect = true;
             }
         }
     }
 
+    // Install the noise texture that's used by both effects.
+    if (enableSurfaceEffect || enableGroundCoverEffect)
+    {
+        osg::StateSet* terrainStateSet = mapNode->getTerrainEngine()->getOrCreateStateSet();
+
+        // reserve a texture unit:
+        if (mapNode->getTerrainEngine()->getResources()->reserveTextureImageUnit(_noiseTexUnit, "Splat Noise"))
+        {
+            NoiseTextureFactory noise;
+            terrainStateSet->setTextureAttribute(_noiseTexUnit, noise.create(256u, 4u));
+            terrainStateSet->addUniform(new osg::Uniform("oe_splat_noiseTex", _noiseTexUnit));
+        }
+    }
+
     if ( enableSurfaceEffect )
     {
         OE_INFO << LC << "Enabling the surface splatting effect\n";
-        _splatEffect = new SplatTerrainEffect();
-        _splatEffect->setDBOptions( _dbo.get() );
-        _splatEffect->setZones( myZones );
-        _splatEffect->setCoverage( myCoverage.get() );
-
-        mapNode->getTerrainEngine()->addEffect( _splatEffect.get() );
+        _splatLayerFactory = new SplatLayerFactory();
+        _splatLayerFactory->setDBOptions( _dbo.get() );
+        _splatLayerFactory->setZones( myZones );
+        _splatLayerFactory->setCoverage( myCoverage.get() );
+        _splatLayerFactory->install(mapNode);
     }
 
-    if ( enableLandCoverEffect )
+    if ( enableGroundCoverEffect )
     {
         OE_INFO << LC << "Enabling the land cover effect\n";
-        _landCoverEffect = new LandCoverTerrainEffect();
-        _landCoverEffect->setDBOptions( _dbo.get() );
-        _landCoverEffect->setZones( myZones );
-        _landCoverEffect->setCoverage( myCoverage.get() );
-        
-        mapNode->getTerrainEngine()->addEffect( _landCoverEffect.get() );
+        _GroundCoverLayerFactory = new GroundCoverLayerFactory();
+        _GroundCoverLayerFactory->setDBOptions( _dbo.get() );
+        _GroundCoverLayerFactory->setZones( myZones );
+        _GroundCoverLayerFactory->setCoverage( myCoverage.get() );
+        _GroundCoverLayerFactory->install(mapNode);
     }
 
     // Install the zone switcher; this will select the best zone based on
@@ -139,19 +153,24 @@ SplatExtension::disconnect(MapNode* mapNode)
 {
     if ( mapNode )
     {
-        if ( _splatEffect.valid() )
+        if ( _splatLayerFactory.valid() )
         {
-            mapNode->getTerrainEngine()->removeEffect( _splatEffect.get() );
-            _splatEffect = 0L;
+            _splatLayerFactory->uninstall(mapNode);
+            _splatLayerFactory = 0L;
         }
 
-        if ( _landCoverEffect.valid() )
+        if ( _GroundCoverLayerFactory.valid() )
         {
-            mapNode->getTerrainEngine()->removeEffect( _landCoverEffect.get() );
-            _landCoverEffect = 0L;
+            _GroundCoverLayerFactory->uninstall(mapNode);
+            _GroundCoverLayerFactory = 0L;
         }
 
         mapNode->getTerrainEngine()->removeCullCallback( _zoneSwitcher.get() );
+
+        if (_noiseTexUnit >= 0)
+        {
+            mapNode->getTerrainEngine()->getResources()->releaseTextureImageUnit(_noiseTexUnit);
+        }
     }
 
     return true;
@@ -175,3 +194,4 @@ SplatExtension::disconnect(Control* control)
     // NOP
     return true;
 }
+#endif
\ No newline at end of file
diff --git a/src/osgEarthSplat/SplatLayer b/src/osgEarthSplat/SplatLayer
new file mode 100644
index 0000000..3781ea8
--- /dev/null
+++ b/src/osgEarthSplat/SplatLayer
@@ -0,0 +1,139 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+* Copyright 2008-2012 Pelican Mapping
+* http://osgearth.org
+*
+* osgEarth is free software; you can redistribute it and/or modify
+* it under the terms of the GNU Lesser General Public License as published by
+* the Free Software Foundation; either version 2 of the License, or
+* (at your option) any later version.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+* IN THE SOFTWARE.
+*
+* You should have received a copy of the GNU Lesser General Public License
+* along with this program.  If not, see <http://www.gnu.org/licenses/>
+*/
+#ifndef OSGEARTH_SPLAT_SPLAT_LAYER_H
+#define OSGEARTH_SPLAT_SPLAT_LAYER_H
+
+#include "Export"
+#include "Coverage"
+#include "Zone"
+#include <osgEarth/VisibleLayer>
+#include <osgEarth/LayerListener>
+#include <osgEarth/LandCoverLayer>
+
+namespace osgEarth {
+    namespace Features {
+        class FeatureSource;
+        class FeatureSourceLayer;
+    }
+}
+namespace osgEarth { namespace Splat
+{
+    using namespace osgEarth;
+    using namespace osgEarth::Features;
+    
+    //! Options to configure a splat layer.
+    class OSGEARTHSPLAT_EXPORT SplatLayerOptions : public VisibleLayerOptions
+    {
+    public:
+        SplatLayerOptions(const ConfigOptions& co = ConfigOptions()) :
+            VisibleLayerOptions(co)
+        {
+            fromConfig(_conf);
+        }
+
+        //! Required layer containing land cover data
+        optional<std::string>& landCoverLayer() { return _landCoverLayerName; }
+        const optional<std::string>& landCoverLayer() const { return _landCoverLayerName; }
+
+        //! Splatting zones (one required, more optional)
+        std::vector<ZoneOptions>& zones() { return _zones; }
+        const std::vector<ZoneOptions>& zones() const { return _zones; }
+
+    public:
+        virtual Config getConfig() const;
+    protected:
+        virtual void mergeConfig(const Config& conf) {
+            VisibleLayerOptions::mergeConfig(conf);
+            fromConfig(conf);
+        }
+        void fromConfig(const Config& conf);
+
+    private:
+        optional<std::string> _landCoverLayerName;
+        std::vector<ZoneOptions> _zones;
+    };
+
+
+    //! Layer that renders geotypical textures on the terrain based on
+    //! classification data ("texture splatting").
+    class OSGEARTHSPLAT_EXPORT SplatLayer : public VisibleLayer
+    {
+    public:
+        META_Layer(osgEarthSplat, SplatLayer, SplatLayerOptions);
+
+        //! Construct a splat layer with default configuration
+        SplatLayer();
+        
+        //! Construct a splat layer with configuration options
+        SplatLayer(const SplatLayerOptions& options);
+
+        //! Layer containing required coverage data
+        void setLandCoverLayer(LandCoverLayer* landCoverLayer);
+
+        //! Layer containing the land cover dictionary.
+        void setLandCoverDictionary(LandCoverDictionary* landCoverDict);
+
+        //! Splatting zones
+        Zones& zones() { return _zones; }
+        const Zones& zones() const { return _zones; }
+
+    public: // Layer
+
+        //! Override
+        bool cull(const osgUtil::CullVisitor* cv, osg::State::StateSetStack& ssStack) const;
+
+    protected:
+
+        //! Override post-ctor init
+        virtual void init();
+
+    public:
+
+        //! Called when this layer is added to the map
+        virtual void addedToMap(const Map* map);
+        virtual void removedFromMap(const Map* map);
+        virtual void setTerrainResources(TerrainResources* res);
+
+    protected:
+        virtual ~SplatLayer() { }
+
+        osg::observer_ptr<LandCoverDictionary> _landCoverDict;
+        osg::observer_ptr<LandCoverLayer> _landCoverLayer;
+
+        LayerListener<SplatLayer, LandCoverDictionary> _landCoverDictListener;
+        LayerListener<SplatLayer, LandCoverLayer> _landCoverListener;
+
+        TextureImageUnitReservation _splatBinding;
+        TextureImageUnitReservation _lutBinding;
+        TextureImageUnitReservation _noiseBinding;
+
+        Zones _zones;
+        bool _zonesConfigured;
+        bool _editMode;
+        bool _gpuNoise;
+
+        void buildStateSets();
+    };
+
+} } // namespace osgEarth::Splat
+
+#endif // OSGEARTH_SPLAT_SPLAT_LAYER_FACTORY_H
diff --git a/src/osgEarthSplat/SplatLayer.cpp b/src/osgEarthSplat/SplatLayer.cpp
new file mode 100644
index 0000000..2974b29
--- /dev/null
+++ b/src/osgEarthSplat/SplatLayer.cpp
@@ -0,0 +1,352 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+* Copyright 2008-2012 Pelican Mapping
+* http://osgearth.org
+*
+* osgEarth is free software; you can redistribute it and/or modify
+* it under the terms of the GNU Lesser General Public License as published by
+* the Free Software Foundation; either version 2 of the License, or
+* (at your option) any later version.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+* IN THE SOFTWARE.
+*
+* You should have received a copy of the GNU Lesser General Public License
+* along with this program.  If not, see <http://www.gnu.org/licenses/>
+*/
+#include "SplatLayer"
+#include "SplatShaders"
+#include "NoiseTextureFactory"
+#include <osgEarth/VirtualProgram>
+#include <osgEarthFeatures/FeatureSource>
+#include <osgEarthFeatures/FeatureSourceLayer>
+#include <osgUtil/CullVisitor>
+#include <osg/BlendFunc>
+#include <cstdlib> // getenv
+
+#define LC "[SplatLayer] " << getName() << ": "
+
+#define COVERAGE_SAMPLER "oe_splat_coverageTex"
+#define SPLAT_SAMPLER    "oe_splatTex"
+#define NOISE_SAMPLER    "oe_splat_noiseTex"
+#define LUT_SAMPLER      "oe_splat_coverageLUT"
+
+using namespace osgEarth::Splat;
+
+namespace osgEarth { namespace Splat {
+    REGISTER_OSGEARTH_LAYER(splat_imagery, SplatLayer);
+} }
+
+//........................................................................
+
+Config
+SplatLayerOptions::getConfig() const
+{
+    Config conf = VisibleLayerOptions::getConfig();
+    conf.key() = "splat_imagery";
+    conf.set("land_cover_layer", _landCoverLayerName);
+
+    Config zones("zones");
+    for (int i = 0; i < _zones.size(); ++i) {
+        Config zone = _zones[i].getConfig();
+        if (!zone.empty())
+            zones.add(zone);
+    }
+    if (!zones.empty())
+        conf.update(zones);
+    return conf;
+}
+
+void
+SplatLayerOptions::fromConfig(const Config& conf)
+{
+    conf.getIfSet("land_cover_layer", _landCoverLayerName);
+
+    const Config* zones = conf.child_ptr("zones");
+    if (zones) {
+        const ConfigSet& children = zones->children();
+        for (ConfigSet::const_iterator i = children.begin(); i != children.end(); ++i) {
+            _zones.push_back(ZoneOptions(*i));
+        }
+    }
+}
+
+//........................................................................
+
+SplatLayer::SplatLayer() :
+VisibleLayer(&_optionsConcrete),
+_options(&_optionsConcrete)
+{
+    init();
+}
+
+SplatLayer::SplatLayer(const SplatLayerOptions& options) :
+VisibleLayer(&_optionsConcrete),
+_options(&_optionsConcrete),
+_optionsConcrete(options)
+{
+    init();
+}
+
+void
+SplatLayer::init()
+{
+    VisibleLayer::init();
+
+    _zonesConfigured = false;
+
+    _editMode = (::getenv("OSGEARTH_SPLAT_EDIT") != 0L); // TODO deprecate
+    _gpuNoise = (::getenv("OSGEARTH_SPLAT_GPU_NOISE") != 0L); // TODO deprecate
+
+    setRenderType(osgEarth::Layer::RENDERTYPE_TILE);
+
+    for (std::vector<ZoneOptions>::const_iterator i = options().zones().begin();
+        i != options().zones().end();
+        ++i)
+    {
+        osg::ref_ptr<Zone> zone = new Zone(*i);
+        _zones.push_back(zone.get());
+    }
+}
+
+void
+SplatLayer::setLandCoverDictionary(LandCoverDictionary* layer)
+{
+    _landCoverDict = layer;
+    if (layer)
+        buildStateSets();
+}
+
+void
+SplatLayer::setLandCoverLayer(LandCoverLayer* layer)
+{
+    _landCoverLayer = layer;
+    if (layer) {
+        buildStateSets();
+    }
+}
+
+void
+SplatLayer::addedToMap(const Map* map)
+{
+    if (!_landCoverDict.valid())
+    {
+        _landCoverDictListener.listen(map, this, &SplatLayer::setLandCoverDictionary);
+    }
+
+    if (!_landCoverLayer.valid() && options().landCoverLayer().isSet())
+    {
+        _landCoverListener.listen(map, options().landCoverLayer().get(), this, &SplatLayer::setLandCoverLayer);
+    }
+
+    for (Zones::iterator zone = _zones.begin(); zone != _zones.end(); ++zone)
+    {
+        zone->get()->configure(map, getReadOptions());
+    }
+
+    _zonesConfigured = true;
+    
+    buildStateSets();
+}
+
+void
+SplatLayer::removedFromMap(const Map* map)
+{
+    //NOP
+}
+
+void
+SplatLayer::setTerrainResources(TerrainResources* res)
+{
+    if (res)
+    {
+        // TODO.
+        // These reservations are Layer-specific, so we should add the
+        // capability to TerrainResources to support per-Layer reservations.
+        if (_splatBinding.valid() == false)
+        {
+            if (res->reserveTextureImageUnitForLayer(_splatBinding, this, "Splat texture") == false)
+            {
+                OE_WARN << LC << "No texture unit available for splatting texture\n";
+            }
+        }
+
+        if (_lutBinding.valid() == false)
+        {
+            if (res->reserveTextureImageUnitForLayer(_lutBinding, this, "Splatting LUT") == false)
+            {
+                OE_WARN << LC << "No texture unit available for splatting LUT\n";
+            }
+        }
+
+        if (_noiseBinding.valid() == false)
+        {
+            if (res->reserveTextureImageUnitForLayer(_noiseBinding, this, "Splat noise sampler") == false)
+            {
+                OE_WARN << LC << "No texture unit available for splatting Noise function\n";
+            }
+        }
+
+        if (_splatBinding.valid() && _lutBinding.valid())
+        {
+            buildStateSets();
+        }
+    }
+}
+
+bool
+SplatLayer::cull(const osgUtil::CullVisitor* cv,
+                 osg::State::StateSetStack& stateSetStack) const
+{
+    if (Layer::cull(cv, stateSetStack) == false)
+        return false;
+
+    // If we have zones, select the current one and apply its state set.
+    if (_zones.size() > 0)
+    {
+        int zoneIndex = 0;
+        osg::Vec3d vp = cv->getViewPoint();
+
+        for(int z=_zones.size()-1; z > 0 && zoneIndex == 0; --z)
+        {
+            if ( _zones[z]->contains(vp) )
+            {
+                zoneIndex = z;
+            }
+        }
+
+        osg::StateSet* zoneStateSet = 0L;
+        Surface* surface = _zones[zoneIndex]->getSurface();
+        if (surface)
+        {
+            zoneStateSet = surface->getStateSet();
+        }
+
+        if (zoneStateSet)
+        {
+            stateSetStack.push_back(zoneStateSet);
+        }
+    }
+    return true;
+}
+
+void
+SplatLayer::buildStateSets()
+{
+    // assert we have the necessary TIUs:
+    if (_splatBinding.valid() == false || _lutBinding.valid() == false) {
+        OE_DEBUG << LC << "buildStateSets deferred.. bindings not reserved\n";
+        return;
+    }
+
+    if (!_zonesConfigured) {
+        OE_DEBUG << LC << "buildStateSets deferred.. zones not yet configured\n";
+        return;
+    }
+    
+    osg::ref_ptr<LandCoverDictionary> landCoverDict;
+    if (_landCoverDict.lock(landCoverDict) == false) {
+        OE_DEBUG << LC << "buildStateSets deferred.. land cover dictionary not available\n";
+        return;
+    }
+    
+    osg::ref_ptr<LandCoverLayer> landCoverLayer;
+    if (_landCoverLayer.lock(landCoverLayer) == false) {
+        OE_DEBUG << LC << "buildStateSets deferred.. land cover layer not available\n";
+        return;
+    }
+
+    // Load all the splatting textures
+    for (Zones::iterator z = _zones.begin(); z != _zones.end(); ++z)
+    {
+        Zone* zone = z->get();
+        Surface* surface = z->get()->getSurface();
+        if (surface == 0L)
+        {
+            OE_WARN << LC << "No surface defined for zone " << zone->getName() << std::endl;
+            return;
+        }
+        if (surface->loadTextures(landCoverDict.get(), getReadOptions()) == false)
+        {
+            OE_WARN << LC << "Texture load failed for zone " << zone->getName() << "\n";
+            return;
+        }
+    }
+
+    // Set up the zone-specific elements:
+    for (Zones::iterator z = _zones.begin(); z != _zones.end(); ++z)
+    {
+        Zone* zone = z->get();
+
+        osg::StateSet* zoneStateset = zone->getSurface()->getOrCreateStateSet();
+        zoneStateset->setName("Splat Zone");
+
+        // The texture array for the zone:
+        const SplatTextureDef& texdef = zone->getSurface()->getTextureDef();
+
+        // apply the splatting texture catalog:
+        zoneStateset->setTextureAttribute(_splatBinding.unit(), texdef._texture.get());
+
+        // apply the buffer containing the coverage-to-splat LUT:
+        zoneStateset->setTextureAttribute(_lutBinding.unit(), texdef._splatLUTBuffer.get());
+
+        OE_DEBUG << LC << "Installed getRenderInfo for zone \"" << zone->getName() << "\" (uid=" << zone->getUID() << ")\n";
+    }
+
+    // Next set up the elements that apply to all zones:
+    osg::StateSet* stateset = this->getOrCreateStateSet();
+
+    // Bind the texture image unit:
+    stateset->addUniform(new osg::Uniform(SPLAT_SAMPLER, _splatBinding.unit()));
+
+    // install the uniform for the splat LUT.
+    stateset->addUniform(new osg::Uniform(LUT_SAMPLER, _lutBinding.unit()));
+        
+    if (_noiseBinding.valid())
+    {
+        NoiseTextureFactory noise;
+        osg::ref_ptr<osg::Texture> noiseTexture = noise.create(256u, 1u);
+        stateset->setTextureAttribute(_noiseBinding.unit(), noiseTexture.get());
+        stateset->addUniform(new osg::Uniform(NOISE_SAMPLER, _noiseBinding.unit()));
+        stateset->setDefine("OE_SPLAT_HAVE_NOISE_SAMPLER");
+    }
+
+    osg::Uniform* lcTexUniform = new osg::Uniform(COVERAGE_SAMPLER, landCoverLayer->shareImageUnit().get());
+    stateset->addUniform(lcTexUniform);
+
+    stateset->addUniform(new osg::Uniform("oe_splat_scaleOffsetInt", 0));
+    stateset->addUniform(new osg::Uniform("oe_splat_warp", 0.0f));
+    stateset->addUniform(new osg::Uniform("oe_splat_blur", 1.0f));
+    stateset->addUniform(new osg::Uniform("oe_splat_useBilinear", 1.0f));
+    stateset->addUniform(new osg::Uniform("oe_splat_noiseScale", 12.0f));
+
+    stateset->addUniform(new osg::Uniform("oe_splat_detailRange", 100000.0f));
+
+    if (_editMode)
+        stateset->setDefine("OE_SPLAT_EDIT_MODE");
+
+    if (_gpuNoise)
+        stateset->setDefine("OE_SPLAT_GPU_NOISE");
+
+    stateset->setDefine("OE_USE_NORMAL_MAP");
+
+    stateset->setDefine("OE_SPLAT_COVERAGE_TEXMAT", landCoverLayer->shareTexMatUniformName().get());
+    
+    //stateset->setAttributeAndModes(
+    //    new osg::BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ZERO),
+    //    osg::StateAttribute::OVERRIDE);
+
+    SplattingShaders splatting;
+    VirtualProgram* vp = VirtualProgram::getOrCreate(stateset);
+    splatting.load(vp, splatting.VertModel);
+    splatting.load(vp, splatting.VertView);
+    splatting.load(vp, splatting.Frag);
+    splatting.load(vp, splatting.Util);
+
+    OE_DEBUG << LC << "Statesets built!! Ready!\n";
+}
diff --git a/src/osgEarthSplat/SplatOptions b/src/osgEarthSplat/SplatOptions
index a872a46..43669b2 100644
--- a/src/osgEarthSplat/SplatOptions
+++ b/src/osgEarthSplat/SplatOptions
@@ -53,8 +53,8 @@ namespace osgEarth { namespace Splat
 
     public:
         Config getConfig() const {
-            Config conf = DriverConfigOptions::getConfig();
-            conf.updateObjIfSet( "coverage", _coverage );
+            Config conf("splat");
+            conf.setObj( "coverage", _coverage );
             Config zones("zones");
             for(int i=0; i<_zones.size(); ++i) {
                 Config zone = _zones[i].getConfig();
@@ -62,7 +62,7 @@ namespace osgEarth { namespace Splat
                     zones.add(zone);
             }
             if ( !zones.empty() )
-                conf.add(zones);
+                conf.update(zones);
             return conf;
         }
 
diff --git a/src/osgEarthSplat/SplatShaders b/src/osgEarthSplat/SplatShaders
index b419a8c..5f25d1e 100644
--- a/src/osgEarthSplat/SplatShaders
+++ b/src/osgEarthSplat/SplatShaders
@@ -41,15 +41,15 @@ namespace osgEarth { namespace Splat
             Util;
 	};
 
-    struct LandCoverShaders : public osgEarth::ShaderPackage
+    struct GroundCoverShaders : public osgEarth::ShaderPackage
 	{
-        LandCoverShaders();
+        GroundCoverShaders();
 
         std::string
-            LandCover_TCS,
-            LandCover_TES,
-            LandCover_GS,
-            LandCover_FS;
+            GroundCover_TCS,
+            GroundCover_TES,
+            GroundCover_GS,
+            GroundCover_FS;
 	};
 	
 } } // namespace osgEarth::Splat
diff --git a/src/osgEarthSplat/SplatShaders.cpp.in b/src/osgEarthSplat/SplatShaders.cpp.in
index 27e7637..1416574 100644
--- a/src/osgEarthSplat/SplatShaders.cpp.in
+++ b/src/osgEarthSplat/SplatShaders.cpp.in
@@ -23,25 +23,22 @@ SplattingShaders::SplattingShaders()
     Frag = "Splat.frag.glsl";
     _sources[Frag] = "@Splat.frag.glsl@";
 
-    FragCommon = "Splat.frag.common.glsl";
-    _sources[FragCommon] = "@Splat.frag.common.glsl@";
-
     Util = "Splat.util.glsl";
     _sources[Util] = "@Splat.util.glsl@";
 }
 
 
-LandCoverShaders::LandCoverShaders()
+GroundCoverShaders::GroundCoverShaders()
 {
-    LandCover_TCS = "LandCover.TCS.glsl";
-    _sources[LandCover_TCS] = "@LandCover.TCS.glsl@";
+    GroundCover_TCS = "GroundCover.TCS.glsl";
+    _sources[GroundCover_TCS] = "@GroundCover.TCS.glsl@";
 
-    LandCover_TES = "LandCover.TES.glsl";
-    _sources[LandCover_TES] = "@LandCover.TES.glsl@";
+    GroundCover_TES = "GroundCover.TES.glsl";
+    _sources[GroundCover_TES] = "@GroundCover.TES.glsl@";
 
-    LandCover_GS = "LandCover.GS.glsl";
-    _sources[LandCover_GS] = "@LandCover.GS.glsl@";
+    GroundCover_GS = "GroundCover.GS.glsl";
+    _sources[GroundCover_GS] = "@GroundCover.GS.glsl@";
 
-    LandCover_FS = "LandCover.FS.glsl";
-    _sources[LandCover_FS] = "@LandCover.FS.glsl@";
+    GroundCover_FS = "GroundCover.FS.glsl";
+    _sources[GroundCover_FS] = "@GroundCover.FS.glsl@";
 }
diff --git a/src/osgEarthSplat/SplatTerrainEffect b/src/osgEarthSplat/SplatTerrainEffect
deleted file mode 100644
index b1316f5..0000000
--- a/src/osgEarthSplat/SplatTerrainEffect
+++ /dev/null
@@ -1,111 +0,0 @@
-/* -*-c++-*- */
-/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2012 Pelican Mapping
-* http://osgearth.org
-*
-* osgEarth is free software; you can redistribute it and/or modify
-* it under the terms of the GNU Lesser General Public License as published by
-* the Free Software Foundation; either version 2 of the License, or
-* (at your option) any later version.
-*
-* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
-* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
-* IN THE SOFTWARE.
-*
-* You should have received a copy of the GNU Lesser General Public License
-* along with this program.  If not, see <http://www.gnu.org/licenses/>
-*/
-#ifndef OSGEARTH_SPLAT_SPLAT_TERRAIN_EFFECT_H
-#define OSGEARTH_SPLAT_SPLAT_TERRAIN_EFFECT_H
-
-#include "SplatCatalog"
-#include "SplatCoverageLegend"
-#include "SplatShaders"
-#include "Export"
-
-#include "Coverage"
-#include "Zone"
-
-#include <osgEarth/TerrainEffect>
-#include <osg/Image>
-#include <osg/Uniform>
-#include <osg/Texture2DArray>
-#include <osgDB/Options>
-
-using namespace osgEarth;
-
-namespace osgEarth { namespace Splat
-{
-    /**
-     * Effect that applies texture splatting to the terrain.
-     */
-    class OSGEARTHSPLAT_EXPORT SplatTerrainEffect : public TerrainEffect
-    {
-    public:
-        /** constructor */
-        SplatTerrainEffect();
-
-        /**
-         * Sets the OSG DB options to use when performing I/O
-         */
-        void setDBOptions(const osgDB::Options* dbo);
-
-        /**
-         * Sets the coverage source
-         */
-        void setCoverage(Coverage* coverage) { _coverage = coverage; }
-        Coverage* getCoverage() const { return _coverage.get(); }
-
-        /**
-         * Sets the splatting zones
-         */
-        void setZones(const Zones& zones) { _zones = zones; }
-        const Zones& getZones() const { return _zones; }
-
-
-    public: // TerrainEffect interface
-
-        void onInstall(TerrainEngineNode* engine);
-
-        void onUninstall(TerrainEngineNode* engine);
-
-
-    protected:
-        virtual ~SplatTerrainEffect() { }
-
-        // index-aligned with the surface biome region vector:
-        SplatTextureDefVector               _textureDefs;
-
-        int                                 _splatTexUnit;
-        osg::ref_ptr<osg::Uniform>          _splatTexUniform;
-        osg::ref_ptr<osg::Uniform>          _coverageTexUniform;
-        int                                 _lutTexUnit;
-        osg::ref_ptr<osg::Uniform>          _lutTexUniform;
-        osg::ref_ptr<osg::Uniform>          _scaleOffsetUniform;
-        osg::ref_ptr<osg::Uniform>          _warpUniform;
-        osg::ref_ptr<osg::Uniform>          _blurUniform;
-        float                               _renderOrder;
-        int                                 _noiseTexUnit;
-        osg::ref_ptr<osg::Texture>          _noiseTex;
-        osg::ref_ptr<osg::Uniform>          _noiseTexUniform;
-        osg::ref_ptr<osg::Uniform>          _noiseScaleUniform;
-        osg::ref_ptr<osg::Uniform>          _useBilinearUniform;
-
-        bool                                _editMode;
-        bool                                _gpuNoise;
-
-        osg::ref_ptr<const osgDB::Options>  _dbo;
-
-        osg::ref_ptr<Coverage>              _coverage;
-
-        Zones                               _zones;
-
-    };
-
-} } // namespace osgEarth::Splat
-
-#endif // OSGEARTH_SPLAT_SPLAT_TERRAIN_EFFECT_H
diff --git a/src/osgEarthSplat/SplatTerrainEffect.cpp b/src/osgEarthSplat/SplatTerrainEffect.cpp
deleted file mode 100644
index 87c2603..0000000
--- a/src/osgEarthSplat/SplatTerrainEffect.cpp
+++ /dev/null
@@ -1,232 +0,0 @@
-/* -*-c++-*- */
-/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2012 Pelican Mapping
-* http://osgearth.org
-*
-* osgEarth is free software; you can redistribute it and/or modify
-* it under the terms of the GNU Lesser General Public License as published by
-* the Free Software Foundation; either version 2 of the License, or
-* (at your option) any later version.
-*
-* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
-* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
-* IN THE SOFTWARE.
-*
-* You should have received a copy of the GNU Lesser General Public License
-* along with this program.  If not, see <http://www.gnu.org/licenses/>
-*/
-#include "SplatTerrainEffect"
-#include "SplatOptions"
-#include "NoiseTextureFactory"
-
-#include <osgEarth/Registry>
-#include <osgEarth/Capabilities>
-#include <osgEarth/VirtualProgram>
-#include <osgEarth/TerrainEngineNode>
-#include <osgEarth/ImageUtils>
-#include <osgEarth/URI>
-#include <osgEarth/ShaderLoader>
-#include <osgEarthUtil/SimplexNoise>
-
-#include <osg/Texture2D>
-#include <osg/TextureBuffer>
-#include <osgDB/WriteFile>
-
-#include "SplatShaders"
-
-#define LC "[Splat] "
-
-#define COVERAGE_SAMPLER "oe_splat_coverageTex"
-#define SPLAT_SAMPLER    "oe_splatTex"
-#define NOISE_SAMPLER    "oe_noise_tex"
-#define LUT_SAMPLER      "oe_splat_coverageLUT"
-
-using namespace osgEarth;
-using namespace osgEarth::Splat;
-
-SplatTerrainEffect::SplatTerrainEffect() :
-_renderOrder ( -1.0f ),
-_editMode    ( false ),
-_gpuNoise    ( false ),
-_splatTexUnit(-1),
-_lutTexUnit(-1),
-_noiseTexUnit(-1)
-{
-    _scaleOffsetUniform = new osg::Uniform("oe_splat_scaleOffsetInt", 0 );
-    _warpUniform        = new osg::Uniform("oe_splat_warp",           0.0f );
-    _blurUniform        = new osg::Uniform("oe_splat_blur",           1.0f );
-    _useBilinearUniform = new osg::Uniform("oe_splat_useBilinear",    1.0f );
-    _noiseScaleUniform  = new osg::Uniform("oe_splat_noiseScale",    12.0f );
-
-    _editMode = (::getenv("OSGEARTH_SPLAT_EDIT") != 0L);
-    _gpuNoise = (::getenv("OSGEARTH_SPLAT_GPU_NOISE") != 0L);
-}
-
-void
-SplatTerrainEffect::setDBOptions(const osgDB::Options* dbo)
-{
-    _dbo = dbo;
-}
-
-void
-SplatTerrainEffect::onInstall(TerrainEngineNode* engine)
-{
-    if ( engine )
-    {
-        // Check that we have coverage data (required for now - later masking data will be an option)
-        if ( !_coverage.valid() || !_coverage->hasLayer() )
-        {
-            OE_WARN << LC << "ILLEGAL: coverage data is required\n";
-            return;
-        }
-
-        bool splattingOK = false;
-
-        for(Zones::const_iterator z = _zones.begin(); z != _zones.end(); ++z)
-        {
-            Zone* zone = z->get();
-            Surface* surface = zone->getSurface();
-            if ( surface )
-            {
-                if ( surface->loadTextures(_coverage.get(), _dbo.get()) )
-                {
-                    splattingOK = true;
-                }
-            }
-        }
-
-        if ( splattingOK )
-        {
-            // First install a shared noise texture.
-            if ( _gpuNoise == false )
-            {
-                osg::StateSet* terrainStateSet = engine->getOrCreateStateSet();
-                if ( terrainStateSet->getUniform("oe_splat_noiseTex") == 0L )
-                {
-                    // reserve a texture unit:
-                    if (engine->getResources()->reserveTextureImageUnit(_noiseTexUnit, "Splat Noise"))
-                    {
-                        NoiseTextureFactory noise;
-                        terrainStateSet->setTextureAttribute( _noiseTexUnit, noise.create(256u, 4u) );
-                        terrainStateSet->addUniform( new osg::Uniform("oe_splat_noiseTex", _noiseTexUnit) );
-                    }
-                }
-            }
-
-            bool coverageOK =  engine->getResources()->reserveTextureImageUnit(_splatTexUnit, "Splat Coverage Data");
-
-            bool lutOK = engine->getResources()->reserveTextureImageUnit(_lutTexUnit, "Splat LUT");
-
-            // Set up surface splatting:
-            if ( coverageOK && lutOK )
-            {
-                // Set up the zone-specific elements:
-                for(Zones::iterator z = _zones.begin(); z != _zones.end(); ++z)
-                {
-                    Zone* zone = z->get();
-
-                    // The texture array for the zone:
-                    const SplatTextureDef& texdef = zone->getSurface()->getTextureDef();
-                    osg::StateSet* zoneStateset = zone->getOrCreateStateSet();
-
-                    // apply the splatting texture catalog:
-                    zoneStateset->setTextureAttribute( _splatTexUnit, texdef._texture.get() );
-
-                    // apply the buffer containing the coverage-to-splat LUT:
-                    zoneStateset->setTextureAttribute(_lutTexUnit, texdef._splatLUTBuffer.get());
-
-                    // The zone's sampling function:
-                    //VirtualProgram* vp = VirtualProgram::cloneOrCreate( zoneStateset );
-                    //osg::Shader* shader = new osg::Shader(osg::Shader::FRAGMENT, texdef._samplingFunction);
-                    //vp->setShader( "oe_splat_getRenderInfo", shader );
-
-                    OE_INFO << LC << "Installed getRenderInfo for zone \"" << zone->getName() << "\" (uid=" << zone->getUID() << ")\n";
-                }
-
-                // Next set up the elements that apply to all zones:
-                osg::StateSet* stateset = engine->getSurfaceStateSet();
-
-                // Bind the texture image unit:
-                _splatTexUniform = new osg::Uniform(SPLAT_SAMPLER, _splatTexUnit);
-                stateset->addUniform( _splatTexUniform.get() );
-
-                // install the uniform for the splat LUT.
-                _lutTexUniform = stateset->getOrCreateUniform( LUT_SAMPLER, osg::Uniform::SAMPLER_BUFFER );
-                _lutTexUniform->set(_lutTexUnit);
-
-                // coverage code sampler:
-                osg::ref_ptr<ImageLayer> coverageLayer;
-                _coverage->lockLayer( coverageLayer );
-
-                _coverageTexUniform = stateset->getOrCreateUniform( COVERAGE_SAMPLER, osg::Uniform::SAMPLER_2D );
-                _coverageTexUniform->set( coverageLayer->shareImageUnit().get() );
-
-                // control uniforms (TODO: simplify and deprecate unneeded uniforms)
-                stateset->addUniform( _scaleOffsetUniform.get() );
-                stateset->addUniform( _warpUniform.get() );
-                stateset->addUniform( _blurUniform.get() );
-                stateset->addUniform( _noiseScaleUniform.get() );
-                stateset->addUniform( _useBilinearUniform.get() );
-
-                stateset->addUniform(new osg::Uniform("oe_splat_detailRange",  1000000.0f));
-
-
-                SplattingShaders splatting;
-
-                splatting.define( "SPLAT_EDIT",        _editMode );
-                splatting.define( "SPLAT_GPU_NOISE",   _gpuNoise );
-                splatting.define( "OE_USE_NORMAL_MAP", engine->normalTexturesRequired() );
-
-                splatting.replace( "COVERAGE_TEXTURE_MATRIX", coverageLayer->shareTexMatUniformName().get() );
-            
-                VirtualProgram* vp = VirtualProgram::getOrCreate(stateset);
-                splatting.load( vp, splatting.VertModel );
-                splatting.load( vp, splatting.VertView );
-                splatting.load( vp, splatting.Frag );
-                splatting.load( vp, splatting.Util );
-
-                // GPU noise is expensive, so only use it to tweak noise function values that you
-                // can later bake into the noise texture generator.
-                if ( _gpuNoise )
-                {                
-                    //osgEarth::replaceIn( fragmentShader, "#undef SPLAT_GPU_NOISE", "#define SPLAT_GPU_NOISE" );
-
-                    // Use --uniform on the command line to tweak these values:
-                    stateset->addUniform(new osg::Uniform("oe_splat_freq",   32.0f));
-                    stateset->addUniform(new osg::Uniform("oe_splat_pers",    0.8f));
-                    stateset->addUniform(new osg::Uniform("oe_splat_lac",     2.2f));
-                    stateset->addUniform(new osg::Uniform("oe_splat_octaves", 8.0f));
-
-                    // support shaders
-                    std::string noiseShaderSource = ShaderLoader::load( splatting.Noise, splatting );
-                    osg::Shader* noiseShader = new osg::Shader(osg::Shader::FRAGMENT, noiseShaderSource);
-                    vp->setShader( "oe_splat_noiseshaders", noiseShader );
-                }
-            }
-        }
-    }
-}
-
-
-void
-SplatTerrainEffect::onUninstall(TerrainEngineNode* engine)
-{
-    if ( engine )
-    {
-        if ( _noiseTexUnit >= 0 )
-        {
-            engine->getResources()->releaseTextureImageUnit( _noiseTexUnit );
-            _noiseTexUnit = -1;
-        }
-    
-        if ( _splatTexUnit >= 0 )
-        {
-            engine->getResources()->releaseTextureImageUnit( _splatTexUnit );
-            _splatTexUnit = -1;
-        }
-    }
-}
diff --git a/src/osgEarthSplat/Surface b/src/osgEarthSplat/Surface
index ab8d92d..28ac251 100644
--- a/src/osgEarthSplat/Surface
+++ b/src/osgEarthSplat/Surface
@@ -25,6 +25,7 @@
 #include <osgEarth/Common>
 #include <osgEarth/Config>
 #include <osgEarth/URI>
+#include <osgEarth/LandCover>
 
 namespace osgEarth {
     class Map;
@@ -60,7 +61,7 @@ namespace osgEarth { namespace Splat
         Config getConfig() const {
             Config conf = ConfigOptions::getConfig();
             conf.key() = "surface";
-            conf.updateIfSet("catalog", _catalogURI);
+            conf.set("catalog", _catalogURI);
             return conf;
         }
 
@@ -88,21 +89,23 @@ namespace osgEarth { namespace Splat
          * Loads textures for splatting and generates a sampling function.
          * Returns false if something goes wrong
          */
-        bool loadTextures(const Coverage* coverage, const osgDB::Options* dbo);
+        bool loadTextures(const LandCoverDictionary* landCoverDict, const osgDB::Options* readOptions);
 
         /** Gets the texture definition creates by loadTextures */
         const SplatTextureDef& getTextureDef() const { return _textureDef; }
 
+        osg::StateSet* getOrCreateStateSet();
+        osg::StateSet* getStateSet() const { return _stateSet.get(); }
+
     protected:
         virtual ~Surface() { }
 
     protected:
         osg::ref_ptr<SplatCatalog> _catalog;
         SplatTextureDef            _textureDef;
+        osg::ref_ptr<osg::StateSet> _stateSet;
 
-        //bool createGLSLSamplingCode(const Coverage* coverage, std::string& output) const;
-
-        osg::Texture* createLUTBuffer(const Coverage* coverage) const;
+        osg::Texture* createLUTBuffer(const LandCoverDictionary* lcd) const;
 
     public:
 
diff --git a/src/osgEarthSplat/Surface.cpp b/src/osgEarthSplat/Surface.cpp
index 8306743..f16982e 100644
--- a/src/osgEarthSplat/Surface.cpp
+++ b/src/osgEarthSplat/Surface.cpp
@@ -39,8 +39,12 @@ Surface::configure(const ConfigOptions& conf, const Map* map, const osgDB::Optio
 {
     SurfaceOptions in(conf);
 
-    // Read in the catalog.
-    _catalog = SplatCatalog::read( in.catalogURI().get(), dbo );
+    if (_catalog.valid() == false && in.catalogURI().isSet())
+    {
+        // Read in the catalog.
+        _catalog = SplatCatalog::read( in.catalogURI().get(), dbo );
+    }
+
     if ( !_catalog.valid() )
     {
         OE_WARN << LC << "Failed to read catalog for surface\n";
@@ -50,28 +54,28 @@ Surface::configure(const ConfigOptions& conf, const Map* map, const osgDB::Optio
     return true;
 }
 
+osg::StateSet*
+Surface::getOrCreateStateSet()
+{
+    if ( !_stateSet.valid() )
+    {
+        _stateSet = new osg::StateSet();
+    }
+
+    return _stateSet.get();
+}
+
 bool
-Surface::loadTextures(const Coverage* coverage, const osgDB::Options* dbo)
+Surface::loadTextures(const LandCoverDictionary* landCoverDict, const osgDB::Options* dbo)
 {
     int numValidTextures = 0;
 
-    if ( coverage == 0L || !_catalog.valid() )
+    if ( landCoverDict == 0L || !_catalog.valid() )
         return false;
 
     if ( _catalog->createSplatTextureDef(dbo, _textureDef) )
     {
-        _textureDef._splatLUTBuffer = createLUTBuffer(coverage);
-#if 0
-        // loaded, now create a sampling function.
-        std::string code;
-        if ( !createGLSLSamplingCode(coverage, code) )
-        {
-            OE_WARN << LC << "Failed to generate sampling code\n";
-            return false;
-        }
-
-        _textureDef._samplingFunction = code;
-#endif
+        _textureDef._splatLUTBuffer = createLUTBuffer(landCoverDict);
     }
     else
     {
@@ -82,36 +86,6 @@ Surface::loadTextures(const Coverage* coverage, const osgDB::Options* dbo)
     return true;
 }
 
-namespace
-{
-#define INDENTATION 4
-    struct indent {
-        indent(int level) :_level(level){}
-        int _level;
-        friend std::ostream& operator<<(std::ostream& os, const indent& val) {
-            for (int i=0; i<val._level * INDENTATION; ++i) 
-                os << ' ';
-            return os;
-        }
-    };
-
-    void write(std::ostream& buf, const SplatRangeData* rangeData, int I)
-    {
-        buf << indent(I) << "primary = " << (rangeData->_textureIndex) << ".0;\n";
-        //if (rangeData->_detail.isSet()) {
-        //    buf << indent(I) << "detail = " << (rangeData->_detail->_textureIndex) << ".0;\n";
-        //    if (rangeData->_detail->_brightness.isSet())
-        //        buf << indent(I) << "brightness = " << rangeData->_detail->_brightness.get() << ";\n";
-        //    if (rangeData->_detail->_contrast.isSet())
-        //        buf << indent(I) << "contrast = " << rangeData->_detail->_contrast.get() << ";\n";
-        //    if (rangeData->_detail->_threshold.isSet())
-        //        buf << indent(I) << "threshold = " << rangeData->_detail->_threshold.get() << ";\n";
-        //    if (rangeData->_detail->_slope.isSet())
-        //        buf << indent(I) << "slope = " << rangeData->_detail->_slope.get() << ";\n";
-        //}
-    }
-}
-
 #define NUM_FLOATS_PER_LOD 6
 #define NUM_LODS 26
 #define NUM_CLASSES 256
@@ -142,7 +116,7 @@ namespace
 }
 
 osg::Texture*
-Surface::createLUTBuffer(const Coverage* coverage) const
+Surface::createLUTBuffer(const LandCoverDictionary* landCoverDict) const
 {
     typedef LOD CoverageClass[NUM_LODS];
 
@@ -150,35 +124,34 @@ Surface::createLUTBuffer(const Coverage* coverage) const
 
     LUT lut;
 
-    // Build the LUT!
-    const SplatCoverageLegend::Predicates& preds = coverage->getLegend()->getPredicates();
-    for (SplatCoverageLegend::Predicates::const_iterator p = preds.begin(); p != preds.end(); ++p)
+    for(LandCoverClassVector::const_iterator i = landCoverDict->getClasses().begin();
+        i != landCoverDict->getClasses().end();
+        ++i)
     {
-        const CoverageValuePredicate* pred = p->get();
-
-        if (pred->_exactValue.isSet())
+        const LandCoverClass* lcClass = i->get();
+        int coverageValue = lcClass->getValue();
+        if (coverageValue >= 0 && coverageValue < NUM_CLASSES)
         {
-            int coverageIndex = (int)(::atoi(pred->_exactValue.get().c_str()));
-            if (coverageIndex >= 0 && coverageIndex < NUM_CLASSES)
+            //OE_INFO << LC << "LUT: " << lcClass->getName() << " = " << coverageValue << std::endl;
+            CoverageClass& coverageClass = lut[coverageValue];
+            const std::string& className = lcClass->getName();
+            const SplatLUT::const_iterator k = _textureDef._splatLUT.find(className);
+            if (k != _textureDef._splatLUT.end())
             {
-                CoverageClass& coverageClass = lut[coverageIndex];
-            
-                // Look up by class name:
-                const std::string& className = pred->_mappedClassName.get();
-                const SplatLUT::const_iterator i = _textureDef._splatLUT.find(className);
-                if (i != _textureDef._splatLUT.end())
+                const SplatRangeDataVector& ranges = k->second;
+                unsigned r = 0;
+                for (unsigned lod = 0; lod < NUM_LODS; ++lod)
                 {
-                    const SplatRangeDataVector& ranges = i->second;
-                    unsigned r = 0;
-                    for (unsigned lod = 0; lod < NUM_LODS; ++lod)
-                    {
-                        const SplatRangeData& range = ranges[r];
-                        write(coverageClass[lod], range);
-                        if (range._maxLOD.isSet() && lod == range._maxLOD.get() && (r + 1) < ranges.size())
-                            ++r;
-                    }
+                    const SplatRangeData& range = ranges[r];
+                    write(coverageClass[lod], range);
+                    if (range._maxLOD.isSet() && lod == range._maxLOD.get() && (r + 1) < ranges.size())
+                        ++r;
                 }
             }
+            else
+            {
+                OE_WARN << LC << "No splat mapping for land cover class " << className << std::endl;
+            }
         }
     }
 
diff --git a/src/osgEarthSplat/Zone b/src/osgEarthSplat/Zone
index a98a0a3..8d8a61a 100644
--- a/src/osgEarthSplat/Zone
+++ b/src/osgEarthSplat/Zone
@@ -21,7 +21,7 @@
 
 #include "Export"
 #include "Surface"
-#include "LandCover"
+#include "GroundCover"
 #include <osgEarth/TerrainEngineNode>
 #include <osg/BoundingBox>
 #include <osg/Polytope>
@@ -37,6 +37,49 @@ namespace osgEarth { namespace Splat
 {
     using namespace osgEarth;
 
+
+    //........................................................................
+
+    /**
+     * Serializable options data for a Zone object
+     */
+    class OSGEARTHSPLAT_EXPORT ZoneOptions : public ConfigOptions
+    {
+    public:
+        ZoneOptions(const ConfigOptions& conf = ConfigOptions()) : ConfigOptions(conf) {
+            fromConfig(_conf);
+        }
+
+        //! Name of this zone (readable)
+        optional<std::string> name() { return _name; }
+        const optional<std::string> name() const { return _name; }
+
+        //! Boundary set for this zone (optional)
+        std::vector<osg::BoundingBox>& boundaries() { return _boundaries; }
+        const std::vector<osg::BoundingBox>& boundaries() const { return _boundaries; }
+
+        //! Surface rendering options
+        optional<SurfaceOptions>& surface() { return _surface; }
+        const optional<SurfaceOptions>& surface() const { return _surface; }
+
+        //! Ground cover rendering options        
+        optional<GroundCoverOptions>& groundCover() { return _groundCover; }
+        const optional<GroundCoverOptions>& groundCover() const { return _groundCover; }
+
+    protected:
+        optional<std::string> _name;
+        std::vector<osg::BoundingBox> _boundaries;
+        optional<SurfaceOptions> _surface;
+        optional<GroundCoverOptions> _groundCover;
+
+    public:
+        void fromConfig(const Config& conf);
+        Config getConfig() const;
+    };
+
+    typedef std::vector<ZoneOptions> ZoneOptionsVector;
+    
+
     /**
      * A zone limits a particular surface or land cover layer to a set of
      * geographic boundaries.
@@ -55,7 +98,9 @@ namespace osgEarth { namespace Splat
         typedef std::vector<Boundary> Boundaries;
 
     public:
-        Zone() : _uid(0) { }
+        Zone();
+
+        Zone(const ZoneOptions& options);
 
         void setName(const std::string& name) { _name = name; }
         const std::string& getName() const { return _name; }
@@ -63,13 +108,15 @@ namespace osgEarth { namespace Splat
         Boundaries& getBoundaries() { return _boundaries; }
         const Boundaries& getBoundaries() const { return _boundaries; }
 
+        void setSurface(Surface* surface) { _surface = surface; }
         Surface* getSurface() const { return _surface.get(); }
 
-        LandCover* getLandCover() const { return _landCover.get(); }
+        void setGroundCover(GroundCover* groundCover) { _groundCover = groundCover; }
+        GroundCover* getGroundCover() const { return _groundCover.get(); }
 
-        osg::StateSet* getStateSet() { return _stateSet.get(); }
+        //osg::StateSet* getStateSet() { return _stateSet.get(); }
 
-        osg::StateSet* getOrCreateStateSet();
+        //osg::StateSet* getOrCreateStateSet();
 
         bool contains(const osg::Vec3& points) const;
 
@@ -82,15 +129,18 @@ namespace osgEarth { namespace Splat
         UID                         _uid;
         Boundaries                  _boundaries;
         osg::ref_ptr<Surface>       _surface;
-        osg::ref_ptr<LandCover>     _landCover;
+        osg::ref_ptr<GroundCover>   _groundCover;
         osg::ref_ptr<osg::StateSet> _stateSet;
+        const ZoneOptions           _options;
 
     public:
-        bool configure(const ConfigOptions& conf, const Map* map, const osgDB::Options* dbo);
+        bool configure(const Map* map, const osgDB::Options* readOptions);
     };
 
     typedef std::vector<osg::ref_ptr<Zone> > Zones;
 
+
+    
     
     /**
      * Cull callback that will select the most appropriate Zone based on the camera position
@@ -108,48 +158,6 @@ namespace osgEarth { namespace Splat
         Zones _zones;
     };
 
-    //........................................................................
-
-    /**
-     * Serializable options data for a Zone object
-     */
-    class OSGEARTHSPLAT_EXPORT ZoneOptions : public ConfigOptions
-    {
-    public:
-        ZoneOptions(const ConfigOptions& conf = ConfigOptions()) : ConfigOptions(conf) {
-            fromConfig(_conf);
-        }
-
-        /** Name of this zone (readable) */
-        optional<std::string> name() { return _name; }
-        const optional<std::string> name() const { return _name; }
-
-        /** Boundary set for this zone (optional) */
-        std::vector<osg::BoundingBox>& boundaries() { return _boundaries; }
-        const std::vector<osg::BoundingBox>& boundaries() const { return _boundaries; }
-
-        /** Surface rendering options */
-        optional<SurfaceOptions>& surface() { return _surface; }
-        const optional<SurfaceOptions>& surface() const { return _surface; }
-
-        /** Land Cover rendering options */
-        optional<LandCoverOptions>& landCover() { return _landCover; }
-        const optional<LandCoverOptions>& landCover() const { return _landCover; }
-
-    protected:
-        optional<std::string>         _name;
-        std::vector<osg::BoundingBox> _boundaries;
-        optional<SurfaceOptions>      _surface;
-        optional<LandCoverOptions>    _landCover;
-
-    public:
-        void fromConfig(const Config& conf);
-        Config getConfig() const;
-    };
-
-    typedef std::vector<ZoneOptions> ZoneOptionsVector;
-
-
 } } // namespace osgEarth::Splat
 
 #endif // OSGEARTH_PROCEDURAL_ZONE
diff --git a/src/osgEarthSplat/Zone.cpp b/src/osgEarthSplat/Zone.cpp
index 2403a7e..a624014 100644
--- a/src/osgEarthSplat/Zone.cpp
+++ b/src/osgEarthSplat/Zone.cpp
@@ -25,22 +25,33 @@
 using namespace osgEarth;
 using namespace osgEarth::Splat;
 
-bool
-Zone::configure(const ConfigOptions& options, const Map* map, const osgDB::Options* dbo)
+Zone::Zone() :
+_uid(0)
 {
-    ZoneOptions in(options);
+    //nop
+}
 
-    if ( in.name().isSet() )
-        setName( in.name().get() );
+Zone::Zone(const ZoneOptions& options) :
+_options(options),
+_uid(0)
+{
+    //nop
+}
 
-    for(int i=0; i<in.boundaries().size(); ++i)
+bool
+Zone::configure(const Map* map, const osgDB::Options* readOptions)
+{
+    if ( _options.name().isSet() )
+        setName( _options.name().get() );
+
+    for(int i=0; i<_options.boundaries().size(); ++i)
     {
-        const osg::BoundingBox& box = in.boundaries().at(i);
+        const osg::BoundingBox& box = _options.boundaries()[i];
         _boundaries.push_back( Boundary() );
         Boundary& b = _boundaries.back();
         
         GeoExtent extent(
-            map->getSRS()->getGeographicSRS(),
+            SpatialReference::get("wgs84"),
             osg::clampBetween(box.xMin(), -180.0f, 180.0f),
             osg::clampBetween(box.yMin(),  -90.0f,  90.0f),
             osg::clampBetween(box.xMax(), -180.0f, 180.0f),
@@ -56,23 +67,35 @@ Zone::configure(const ConfigOptions& options, const Map* map, const osgDB::Optio
         b.meanRadius2 = meanRadius*meanRadius;
     }
     
-    if ( in.surface().isSet() )
+    if ( _options.surface().isSet() )
     {
         _surface = new Surface();
-        if ( !_surface->configure(in.surface().get(), map, dbo) )
+    }
+
+    if (_surface.valid())
+    {
+        if ( !_surface->configure(_options.surface().get(), map, readOptions) )
         {
             OE_WARN << LC << "Surface data is not properly configured; surface splatting disabled.\n";
             _surface = 0L;
         }
     }
 
-    if ( in.landCover().isSet() )
+    if( _options.groundCover().isSet() )
+    {
+        _groundCover = new GroundCover(_options.groundCover().get());
+    }
+
+    if (_groundCover.valid())
     {
-        _landCover = new LandCover();
-        if ( !_landCover->configure(in.landCover().get(), map, dbo) )
+        if (_groundCover->configure(readOptions))
+        {
+            OE_DEBUG << LC << "Configured land cover group \"" << _groundCover->getName() << "\"\n";
+        }
+        else
         {
-            OE_WARN << LC << "Land cover is not properly configured; land cover disabled.\n";
-            _landCover = 0L;
+            OE_WARN << LC << "Land cover group is improperly configured\n";
+            return false;
         }
     }
 
@@ -102,13 +125,15 @@ Zone::contains(const osg::Vec3& point) const
     return false;
 }
 
-osg::StateSet*
-Zone::getOrCreateStateSet() 
-{
-    if ( !_stateSet.valid() )
-        _stateSet = new osg::StateSet();
-    return _stateSet.get();
-}
+//osg::StateSet*
+//Zone::getOrCreateStateSet() 
+//{
+//    if ( !_stateSet.valid() )
+//        _stateSet = new osg::StateSet();
+//    return _stateSet.get();
+//}
+
+//........................................................................
 
 void
 ZoneOptions::fromConfig(const Config& conf)
@@ -123,7 +148,7 @@ ZoneOptions::fromConfig(const Config& conf)
         }
     }
     conf.getObjIfSet( "surface",    _surface );
-    conf.getObjIfSet( "land_cover", _landCover );
+    conf.getObjIfSet( "groundcover", _groundCover);
 }
 
 Config
@@ -145,15 +170,18 @@ ZoneOptions::getConfig() const
         }
         conf.add(regions);
     }
-    conf.updateObjIfSet( "surface",    _surface );
-    conf.updateObjIfSet( "land_cover", _landCover );
+    conf.setObj( "surface",    _surface );
+    conf.setObj( "groundcover", _groundCover );
     return conf;
 }
 
 void
 ZoneSwitcher::operator()(osg::Node* node, osg::NodeVisitor* nv)
 {
+#if 0
     osg::StateSet* stateset = 0L;
+    
+    Zone* finalZone = 0L;
 
     if ( _zones.size() > 0 )
     {
@@ -168,9 +196,10 @@ ZoneSwitcher::operator()(osg::Node* node, osg::NodeVisitor* nv)
             if ( _zones[z]->contains(vp) )
             {
                 stateset = _zones[z]->getStateSet();
-                finalZoneIndex      = zoneIndex;
+                finalZoneIndex = zoneIndex;
+                finalZone = _zones[z].get();
             }
-            if ( _zones[z]->getLandCover() )
+            if ( _zones[z]->getGroundCover() )
             {
                 zoneIndex++;
             }
@@ -182,8 +211,9 @@ ZoneSwitcher::operator()(osg::Node* node, osg::NodeVisitor* nv)
             finalZoneIndex = 0;
         }                
         
-        // Relays the zone index to the Patch callback.
-        VisitorData::store(*nv, "oe.LandCover.zoneIndex", new RefUID(finalZoneIndex));
+        // Relays the zone to the GroundCoverPatchLayer.
+        //VisitorData::store(*nv, "oe.GroundCover.zoneIndex", new RefUID(finalZoneIndex));
+        VisitorData::store(*nv, "oe.GroundCover.zone", finalZone );
     }
 
     if ( stateset )
@@ -193,4 +223,10 @@ ZoneSwitcher::operator()(osg::Node* node, osg::NodeVisitor* nv)
 
     if ( stateset )
         static_cast<osgUtil::CullVisitor*>(nv)->popStateSet();
+
+    if (finalZone)
+    {
+        VisitorData::remove(*nv, "oe.GroundCover.zone");
+    }
+#endif
 }
diff --git a/src/osgEarthSymbology/GEOS b/src/osgEarthSymbology/GEOS
index 9921e65..74471d2 100644
--- a/src/osgEarthSymbology/GEOS
+++ b/src/osgEarthSymbology/GEOS
@@ -25,7 +25,9 @@
 #include <osgEarthFeatures/Common>
 #include <osgEarthSymbology/Style>
 #include <osgEarthSymbology/Geometry>
+#include <geos/version.h>
 #include <geos/geom/Geometry.h>
+#include <geos/geom/GeometryFactory.h>
 
 namespace osgEarth { namespace Symbology
 {
@@ -45,7 +47,11 @@ namespace osgEarth { namespace Symbology
         void disposeGeometry(geos::geom::Geometry* input);
 
     protected:
+#if GEOS_VERSION_MAJOR >= 3 && GEOS_VERSION_MINOR >= 6
+        geos::geom::GeometryFactory::unique_ptr _factory;
+#else
         geos::geom::GeometryFactory* _factory;
+#endif
     };
 
 } } // namespace osgEarth::Features
diff --git a/src/osgEarthSymbology/GEOS.cpp b/src/osgEarthSymbology/GEOS.cpp
index 8941ae6..db42f65 100644
--- a/src/osgEarthSymbology/GEOS.cpp
+++ b/src/osgEarthSymbology/GEOS.cpp
@@ -216,7 +216,11 @@ GEOSContext::GEOSContext()
     geos::geom::PrecisionModel* pm = new geos::geom::PrecisionModel(geom::PrecisionModel::FLOATING);
 
     // Factory will clone the PM
+#if GEOS_VERSION_MAJOR >= 3 && GEOS_VERSION_MINOR >= 6
+    _factory = geos::geom::GeometryFactory::create( pm );
+#else
     _factory = new geos::geom::GeometryFactory( pm );
+#endif
 
     // Delete the template.
     delete pm;
@@ -224,7 +228,9 @@ GEOSContext::GEOSContext()
 
 GEOSContext::~GEOSContext()
 {
+#if !(GEOS_VERSION_MAJOR >= 3 && GEOS_VERSION_MINOR >= 6)
     delete _factory;
+#endif
 }
 
 geom::Geometry*
@@ -233,12 +239,16 @@ GEOSContext::importGeometry(const Symbology::Geometry* input)
     geom::Geometry* output = 0L;
     if ( input && input->isValid() )
     {
+#if GEOS_VERSION_MAJOR >= 3 && GEOS_VERSION_MINOR >= 6
+        output = import( input, _factory.get() );
+#else
         output = import( input, _factory );
 
         // if output is ok, it will have a pointer to f. this is probably a leak.
         // TODO: Check whether this is a leak!! -gw
         //if ( !output )
         //    delete f;
+#endif
     }
     return output;
 }
@@ -252,7 +262,11 @@ GEOSContext::exportGeometry(const geom::Geometry* input)
 
     if ( dynamic_cast<const geom::Point*>( input ) )
     {
-        OE_NOTICE << LC << "GEOS 'Point' NYI" << std::endl;        
+        const geom::Point* point = dynamic_cast< const geom::Point* >(input);
+        Symbology::PointSet* part = new Symbology::PointSet();
+        const geom::Coordinate* c = point->getCoordinate();
+        part->push_back(osg::Vec3d(c->x, c->y, c->z));
+        return part;
     }
     else if ( dynamic_cast<const geom::MultiPoint*>( input ) )
     {
@@ -331,10 +345,13 @@ GEOSContext::disposeGeometry(geom::Geometry* input)
 {
     if (input)
     {
-        geom::GeometryFactory* f = const_cast<geom::GeometryFactory*>(input->getFactory());
+#if GEOS_VERSION_MAJOR >= 3 && GEOS_VERSION_MINOR >= 6
         _factory->destroyGeometry(input);
+#else
+        geom::GeometryFactory* f = const_cast<geom::GeometryFactory*>(input->getFactory());
         if ( f != _factory )
             delete f;
+#endif
     }
 }
 
diff --git a/src/osgEarthSymbology/Geometry b/src/osgEarthSymbology/Geometry
index 234f083..0aba7f8 100644
--- a/src/osgEarthSymbology/Geometry
+++ b/src/osgEarthSymbology/Geometry
@@ -209,6 +209,11 @@ namespace osgEarth { namespace Symbology
         virtual void rewind( Orientation ori );
 
         /**
+         * Makes the last point the same as the first point. Suitable for rings and polygons.
+         */
+        virtual void close();
+
+        /**
          * Removes consecutive duplicates in the geometry to prepare for tesselation.
          */
         virtual void removeDuplicates();
@@ -257,6 +262,8 @@ namespace osgEarth { namespace Symbology
         /** dtor */
         virtual ~PointSet();
 
+        virtual void close();
+
     public:
         virtual Type getType() const { return Geometry::TYPE_POINTSET; }
     };
@@ -276,6 +283,8 @@ namespace osgEarth { namespace Symbology
 
         bool getSegment(double length, osg::Vec3d& start, osg::Vec3d& end);
 
+        virtual void close();
+
     public:
         virtual Type getType() const { return Geometry::TYPE_LINESTRING; }
         virtual bool isValid() const { return size() >= 2; }
@@ -397,6 +406,7 @@ namespace osgEarth { namespace Symbology
         virtual Bounds getBounds() const;
         virtual void rewind( Orientation ori );
         virtual void removeColinearPoints();
+        virtual void close();
 
     public:
         GeometryCollection& getComponents() { return _parts; }
diff --git a/src/osgEarthSymbology/Geometry.cpp b/src/osgEarthSymbology/Geometry.cpp
index 46b7c05..9e40b5c 100644
--- a/src/osgEarthSymbology/Geometry.cpp
+++ b/src/osgEarthSymbology/Geometry.cpp
@@ -37,7 +37,7 @@ using namespace geos;
 using namespace geos::operation;
 #endif
 
-#define GEOS_OUT OE_DEBUG
+#define GEOS_OUT OE_INFO
 
 #define LC "[Geometry] "
 
@@ -319,7 +319,7 @@ Geometry::crop( const Bounds& bounds, osg::ref_ptr<Geometry>& output ) const
     (*poly)[1].set(bounds.xMax(), bounds.yMin(), 0);
     (*poly)[2].set(bounds.xMax(), bounds.yMax(), 0);
     (*poly)[3].set(bounds.xMin(), bounds.yMax(), 0);
-    return crop(poly, output);
+    return crop(poly.get(), output);
 }
 
 bool
@@ -643,6 +643,14 @@ Geometry::getLength() const
     return length;
 }
 
+// ensures that the first and last points are idential.
+void 
+Geometry::close()
+{
+    if ( size() > 0 && front() != back() )
+        push_back( front() );
+}
+
 //----------------------------------------------------------------------------
 
 PointSet::PointSet( const PointSet& rhs ) :
@@ -655,6 +663,12 @@ PointSet::~PointSet()
 {
 }
 
+void
+PointSet::close()
+{
+    //NOP. Don't close point sets..
+}
+
 //----------------------------------------------------------------------------
 
 LineString::LineString( const LineString& rhs ) :
@@ -692,6 +706,12 @@ LineString::getSegment(double length, osg::Vec3d& start, osg::Vec3d& end)
     return false;
 }
 
+void
+LineString::close()
+{
+    //NOP - dont' close line strings.
+}
+
 //----------------------------------------------------------------------------
 
 Ring::Ring( const Ring& rhs ) :
@@ -742,12 +762,10 @@ Ring::open()
         erase( end()-1 );
 }
 
-// ensures that the first and last points are idential.
-void 
+void
 Ring::close()
 {
-    if ( size() > 0 && front() != back() )
-        push_back( front() );
+    Geometry::close();
 }
 
 // whether the ring is open.
@@ -900,8 +918,13 @@ MultiGeometry::~MultiGeometry()
 Geometry::Type
 MultiGeometry::getComponentType() const
 {
-    // dicey.
-    return _parts.size() > 0 ? _parts.front()->getType() : TYPE_UNKNOWN;
+    if (_parts.size() == 0)
+        return TYPE_UNKNOWN;
+
+    if (_parts.front()->getType() == TYPE_MULTI)
+        return _parts.front()->getComponentType();
+
+    return _parts.front()->getType();
 }
 
 int
@@ -969,6 +992,15 @@ MultiGeometry::isValid() const
     return valid;
 }
 
+void
+MultiGeometry::close()
+{
+    for( GeometryCollection::const_iterator i = _parts.begin(); i != _parts.end(); ++i )
+    {
+        i->get()->close();
+    }
+}
+
 // opens and rewinds the polygon to the specified orientation.
 void 
 MultiGeometry::rewind( Orientation orientation )
diff --git a/src/osgEarthSymbology/InstanceResource.cpp b/src/osgEarthSymbology/InstanceResource.cpp
index 10396fc..bef78b0 100644
--- a/src/osgEarthSymbology/InstanceResource.cpp
+++ b/src/osgEarthSymbology/InstanceResource.cpp
@@ -51,7 +51,7 @@ InstanceResource::getConfig() const
     Config conf = Resource::getConfig();
     conf.key() = "instance";
 
-    conf.updateIfSet( "url", _uri );
+    conf.set( "url", _uri );
 
     return conf;
 }
diff --git a/src/osgEarthSymbology/LineSymbol b/src/osgEarthSymbology/LineSymbol
index 371e351..60ab5cd 100644
--- a/src/osgEarthSymbology/LineSymbol
+++ b/src/osgEarthSymbology/LineSymbol
@@ -55,16 +55,21 @@ namespace osgEarth { namespace Symbology
         optional<float>& creaseAngle() { return _creaseAngle; }
         const optional<float>& creaseAngle() const { return _creaseAngle; }
 
+        /** URI of an image to load and use to texture lines */
+        optional<StringExpression>& imageURI() { return _imageURI; }
+        const optional<StringExpression>& imageURI() const { return _imageURI; }
+
     public:
         virtual Config getConfig() const;
         virtual void mergeConfig( const Config& conf );
         static void parseSLD(const Config& c, class Style& style);
 
     protected:
-        optional<Stroke>   _stroke;
-        optional<unsigned> _tessellation;
-        optional<float>    _creaseAngle;
-        optional<Distance> _tessellationSize;
+        optional<Stroke>           _stroke;
+        optional<unsigned>         _tessellation;
+        optional<float>            _creaseAngle;
+        optional<Distance>         _tessellationSize;
+        optional<StringExpression> _imageURI;
     };
 
 } } // namespace osgEarth::Symbology
diff --git a/src/osgEarthSymbology/LineSymbol.cpp b/src/osgEarthSymbology/LineSymbol.cpp
index 2e138af..aca1fff 100644
--- a/src/osgEarthSymbology/LineSymbol.cpp
+++ b/src/osgEarthSymbology/LineSymbol.cpp
@@ -22,6 +22,22 @@
 using namespace osgEarth;
 using namespace osgEarth::Symbology;
 
+namespace
+{
+    std::string stripQuotes(const std::string& s) {
+        bool q0 = (s.length() > 0 && (s[0] == '\"' || s[0] == '\''));
+        bool q1 = (s.length() > 1 && (s[s.length()-1] == '\"' || s[s.length()-1] == '\''));
+        if (q0 && q1) 
+            return s.substr(1, s.length()-2);
+        else if (q0)
+            return s.substr(1);
+        else if (q1)
+            return s.substr(0, s.length()-1);
+        else
+            return s;
+    }
+}
+
 OSGEARTH_REGISTER_SIMPLE_SYMBOL(line, LineSymbol);
 
 LineSymbol::LineSymbol( const Config& conf ) :
@@ -38,7 +54,8 @@ Symbol(rhs, copyop),
 _stroke          (rhs._stroke),
 _tessellation    (rhs._tessellation),
 _creaseAngle     (rhs._creaseAngle),
-_tessellationSize(rhs._tessellationSize)
+_tessellationSize(rhs._tessellationSize),
+_imageURI        (rhs._imageURI)
 {
     //nop
 }
@@ -52,6 +69,7 @@ LineSymbol::getConfig() const
     conf.addIfSet   ("tessellation", _tessellation);
     conf.addIfSet   ("crease_angle", _creaseAngle);
     conf.addObjIfSet("tessellation_size", _tessellationSize );
+    conf.addObjIfSet   ("image", _imageURI);
     return conf;
 }
 
@@ -62,6 +80,7 @@ LineSymbol::mergeConfig( const Config& conf )
     conf.getIfSet   ("tessellation", _tessellation);
     conf.getIfSet   ("crease_angle", _creaseAngle);
     conf.getObjIfSet("tessellation_size", _tessellationSize);
+    conf.getObjIfSet("image", _imageURI);
 }
 
 void
@@ -123,4 +142,7 @@ LineSymbol::parseSLD(const Config& c, Style& style)
     else if ( match(c.key(), "stroke-script") ) {
         style.getOrCreate<LineSymbol>()->script() = StringExpression(c.value());
     }
+    else if (match(c.key(), "stroke-image")) {
+        style.getOrCreate<LineSymbol>()->imageURI() = StringExpression(stripQuotes(c.value()), c.referrer());
+    }
 }
diff --git a/src/osgEarthSymbology/MarkerSymbolizer.cpp b/src/osgEarthSymbology/MarkerSymbolizer.cpp
index 2da38a1..27af6fe 100644
--- a/src/osgEarthSymbology/MarkerSymbolizer.cpp
+++ b/src/osgEarthSymbology/MarkerSymbolizer.cpp
@@ -39,8 +39,8 @@ static osg::Node* getNode(const std::string& str)
 #else
     osg::ref_ptr<osgDB::Options> options = new osgDB::Options;
     options->setObjectCacheHint(osgDB::Options::CACHE_ALL);
-    osg::Node* node = osgDB::readNodeFile(str, options.get());
-    return node;
+    ref_ptr<osg::Node> node = osgDB::readRefNodeFile(str, options.get());
+    return node.release();
 #endif
 }
 
diff --git a/src/osgEarthSymbology/MeshConsolidator.cpp b/src/osgEarthSymbology/MeshConsolidator.cpp
index 6422873..db07b0b 100644
--- a/src/osgEarthSymbology/MeshConsolidator.cpp
+++ b/src/osgEarthSymbology/MeshConsolidator.cpp
@@ -171,7 +171,6 @@ namespace
         case osg::Array::MatrixArrayType:
             return convertToBindPerVertex<osg::MatrixfArray>(static_cast<osg::MatrixfArray*>(array), numVerts);
 
-#if OSG_MIN_VERSION_REQUIRED(3,1,9)
         case osg::Array::Vec2iArrayType:
             return convertToBindPerVertex<osg::Vec2iArray>(static_cast<osg::Vec2iArray*>(array), numVerts);
 
@@ -207,7 +206,7 @@ namespace
 
         case osg::Array::MatrixdArrayType:
             return convertToBindPerVertex<osg::MatrixdArray>(static_cast<osg::MatrixdArray*>(array),  numVerts);
-#endif
+
         default:
             return array;
         }
@@ -359,7 +358,7 @@ MeshConsolidator::convertToTriangles( osg::Geometry& geom, bool force )
         }
         else
         {
-#ifdef OSG_GLES2_AVAILABLE
+#if defined(OSG_GLES2_AVAILABLE) || defined(OSG_GLES3_AVAILABLE)
             // GLES only supports UShort, not UInt
             osg::TriangleIndexFunctor< Collector<osg::DrawElementsUShort> > collector;
             collector._newPrimSets = &newPrimSets;
diff --git a/src/osgEarthSymbology/MeshSubdivider.cpp b/src/osgEarthSymbology/MeshSubdivider.cpp
index ea22b45..a3c5ac2 100644
--- a/src/osgEarthSymbology/MeshSubdivider.cpp
+++ b/src/osgEarthSymbology/MeshSubdivider.cpp
@@ -573,9 +573,11 @@ namespace
             osg::Vec3d v0_w = (*data._verts)[line._i0] * L2W;
             osg::Vec3d v1_w = (*data._verts)[line._i1] * L2W;
 
-            double g0 = angleBetween(v0_w, v1_w);
+            bool validLine = 
+                !osg::equivalent(v0_w.length2(), 0.0) &&
+                !osg::equivalent(v1_w.length2(), 0.0);
 
-            if ( g0 > granularity )
+            if ( validLine && angleBetween(v0_w, v1_w) > granularity )
             {
                 data._verts->push_back( geocentricMidpoint(v0_w, v1_w, interp) * W2L );
 
@@ -611,14 +613,14 @@ namespace
                 geom.removePrimitiveSet(0);
 
             // set the new VBO.
-            geom.setVertexArray( data._verts );
+            geom.setVertexArray( data._verts.get() );
             if ( geom.getVertexArray()->getVertexBufferObject() && data._verts->getVertexBufferObject() )
             {
                 data._verts->getVertexBufferObject()->setUsage( geom.getVertexArray()->getVertexBufferObject()->getUsage() );
             }
 
-            if ( data._colors )
-                geom.setColorArray( data._colors );
+            if ( data._colors.valid() )
+                geom.setColorArray( data._colors.get() );
 
 #ifdef STRIPIFY_LINES
             // detect and assemble line strips/loop
diff --git a/src/osgEarthSymbology/ModelResource b/src/osgEarthSymbology/ModelResource
index d918b76..d9f4b2e 100644
--- a/src/osgEarthSymbology/ModelResource
+++ b/src/osgEarthSymbology/ModelResource
@@ -22,6 +22,7 @@
 
 #include <osgEarthSymbology/Common>
 #include <osgEarthSymbology/InstanceResource>
+#include <osgEarth/Status>
 
 namespace osgEarth { namespace Symbology
 {
@@ -65,7 +66,6 @@ namespace osgEarth { namespace Symbology
         virtual osg::Node* createNodeFromURI(const URI& uri, const osgDB::Options* dbOptions) const;
 
         osg::BoundingBox _bbox;
-        Threading::Mutex _mutex;
         optional<bool>   _canScaleToFitXY;
         optional<bool>   _canScaleToFitZ;
     };
diff --git a/src/osgEarthSymbology/ModelResource.cpp b/src/osgEarthSymbology/ModelResource.cpp
index 632f779..596382b 100644
--- a/src/osgEarthSymbology/ModelResource.cpp
+++ b/src/osgEarthSymbology/ModelResource.cpp
@@ -60,7 +60,7 @@ ModelResource::getConfig() const
 const osg::BoundingBox&
 ModelResource::getBoundingBox(const osgDB::Options* dbo)
 {
-    if ( !_bbox.valid() )
+    if ( !_bbox.valid() && _status.isOK() )
     {
         Threading::ScopedMutexLock lock(_mutex);
         if ( !_bbox.valid() )
@@ -91,6 +91,9 @@ namespace
 osg::Node*
 ModelResource::createNodeFromURI( const URI& uri, const osgDB::Options* dbOptions ) const
 {
+    if (_status.isError())
+        return 0L;
+
     osg::ref_ptr< osgDB::Options > options = dbOptions ? new osgDB::Options( *dbOptions ) : 0L;
 
     // Explicitly cache images so that models that share images will only load one copy.
@@ -142,5 +145,14 @@ ModelResource::createNodeFromURI( const URI& uri, const osgDB::Options* dbOption
         }
     }
 
+    if (node == 0L && _status.isOK())
+    {
+        Threading::ScopedMutexLock lock(_mutex);
+        if (_status.isOK())
+        {
+            _status = Status::Error(Status::ServiceUnavailable, "Failed to load resource file");
+        }
+    }
+
     return node;
 }
diff --git a/src/osgEarthSymbology/PolygonSymbol b/src/osgEarthSymbology/PolygonSymbol
index 3415034..86809b1 100644
--- a/src/osgEarthSymbology/PolygonSymbol
+++ b/src/osgEarthSymbology/PolygonSymbol
@@ -43,6 +43,12 @@ namespace osgEarth { namespace Symbology
         optional<Fill>& fill() { return _fill; }
         const optional<Fill>& fill() const { return _fill; }
 
+        /** Whether to use a LineStyle (if one exists in the Style) as an outline.
+         * This defaults to true, but you set this to false to suppress outlining
+         * even when a LineStyle exists. */
+        optional<bool>& outline() { return _outline; }
+        const optional<bool>& outline() const { return _outline; }
+
     public:
         virtual Config getConfig() const;
         virtual void mergeConfig(const Config& conf);
@@ -50,6 +56,7 @@ namespace osgEarth { namespace Symbology
 
     protected:
         optional<Fill> _fill;
+        optional<bool> _outline;
     };
 
 
diff --git a/src/osgEarthSymbology/PolygonSymbol.cpp b/src/osgEarthSymbology/PolygonSymbol.cpp
index 47fad5f..b189778 100644
--- a/src/osgEarthSymbology/PolygonSymbol.cpp
+++ b/src/osgEarthSymbology/PolygonSymbol.cpp
@@ -26,13 +26,16 @@ OSGEARTH_REGISTER_SIMPLE_SYMBOL(polygon, PolygonSymbol);
 
 PolygonSymbol::PolygonSymbol(const PolygonSymbol& rhs,const osg::CopyOp& copyop):
 Symbol(rhs, copyop),
-_fill(rhs._fill)
+_fill(rhs._fill),
+_outline(rhs._outline)
 {
+    //nop
 }
 
 PolygonSymbol::PolygonSymbol( const Config& conf ) :
 Symbol( conf ),
-_fill ( Fill() )
+_fill ( Fill() ),
+_outline( true )
 {
     mergeConfig(conf);
 }
@@ -43,6 +46,7 @@ PolygonSymbol::getConfig() const
     Config conf = Symbol::getConfig();
     conf.key() = "polygon";
     conf.addObjIfSet( "fill", _fill );
+    conf.addIfSet("outline", _outline);
     return conf;
 }
 
@@ -50,6 +54,7 @@ void
 PolygonSymbol::mergeConfig(const Config& conf )
 {
     conf.getObjIfSet( "fill", _fill );
+    conf.getIfSet("outline", _outline);
 }
 
 void
diff --git a/src/osgEarthSymbology/Query b/src/osgEarthSymbology/Query
index 1fd899e..8f24b75 100644
--- a/src/osgEarthSymbology/Query
+++ b/src/osgEarthSymbology/Query
@@ -60,9 +60,14 @@ namespace osgEarth { namespace Symbology
         optional<osgEarth::TileKey>& tileKey() { return _tileKey; }
         const optional<osgEarth::TileKey>& tileKey() const { return _tileKey; }
 
-        const Map* getMap() const;
+        /** The maximum number of features to be returned by this Query.  This is driver specific . */
+        optional<int>& limit() { return _limit; }
+        const optional<int>& limit() const { return _limit; }
+
+        const MapFrame& getMap() const { return _mapFrame; }
 
         void setMap(const Map* map);
+        void setMap(const MapFrame& frame);
         
 
         /**
@@ -80,7 +85,8 @@ namespace osgEarth { namespace Symbology
         optional<std::string> _expression;
         optional<std::string> _orderby;
         optional<osgEarth::TileKey> _tileKey;
-        const Map* _map;
+        optional<int> _limit;
+        MapFrame _mapFrame;
     };
 
 } } // namespace osgEarth::Symbology
diff --git a/src/osgEarthSymbology/Query.cpp b/src/osgEarthSymbology/Query.cpp
index ccb70de..f9fb80a 100644
--- a/src/osgEarthSymbology/Query.cpp
+++ b/src/osgEarthSymbology/Query.cpp
@@ -21,8 +21,7 @@
 using namespace osgEarth;
 using namespace osgEarth::Symbology;
 
-Query::Query( const Config& conf ):
-_map(0)
+Query::Query( const Config& conf )
 {
     mergeConfig( conf );
 }
@@ -32,7 +31,8 @@ _bounds(rhs._bounds),
 _expression(rhs._expression),
 _orderby(rhs._orderby),
 _tileKey(rhs._tileKey),
-_map(rhs._map)
+_mapFrame(rhs._mapFrame),
+_limit(rhs._limit)
 {
     //nop
 }
@@ -56,6 +56,8 @@ Query::mergeConfig( const Config& conf )
             b.value<double>( "xmax", 0.0 ),
             b.value<double>( "ymax", 0.0 ) );
     }
+
+    conf.getIfSet("limit", _limit);
 }
 
 Config
@@ -64,6 +66,7 @@ Query::getConfig() const
     Config conf( "query" );
     conf.addIfSet( "expr", _expression );
     conf.addIfSet( "orderby", _orderby);
+    conf.addIfSet( "limit", _limit);
     if ( _bounds.isSet() ) {
         Config bc( "extent" );
         bc.add( "xmin", toString(_bounds->xMin()) );
@@ -129,12 +132,13 @@ Query::combineWith( const Query& rhs ) const
     return merged;
 }
 
-const Map* Query::getMap() const
+void Query::setMap(const Map* map)
 {
-    return _map;
+    _mapFrame.setMap(map);
 }
 
-void Query::setMap(const Map* map)
+void Query::setMap(const MapFrame& mapf)
 {
-    _map = map;
+    _mapFrame = mapf;
 }
+
diff --git a/src/osgEarthSymbology/RenderSymbol b/src/osgEarthSymbology/RenderSymbol
index 43c6c85..e846204 100644
--- a/src/osgEarthSymbology/RenderSymbol
+++ b/src/osgEarthSymbology/RenderSymbol
@@ -77,6 +77,14 @@ namespace osgEarth { namespace Symbology
         optional<std::string>& renderBin() { return _renderBin; }
         const optional<std::string>& renderBin() const { return _renderBin; }
 
+        /** enable decaling (lequal + polygonoffset) */
+        optional<bool>& decal() { return _decal; }
+        const optional<bool>& decal() const { return _decal; }
+
+        /** maximum crease angle for normal smoothing (default=0) */
+        optional<Angle>& maxCreaseAngle() { return _maxCreaseAngle; }
+        const optional<Angle>& maxCreaseAngle() const { return _maxCreaseAngle; }
+
     public:
         virtual Config getConfig() const;
         virtual void mergeConfig( const Config& conf );
@@ -92,6 +100,8 @@ namespace osgEarth { namespace Symbology
         optional<float>              _minAlpha;
         optional<std::string>        _renderBin;
         optional<bool>               _transparent;
+        optional<bool>               _decal;
+        optional<Angle>              _maxCreaseAngle;
         
         /** dtor */
         virtual ~RenderSymbol() { }
diff --git a/src/osgEarthSymbology/RenderSymbol.cpp b/src/osgEarthSymbology/RenderSymbol.cpp
index d029f16..a11f0d6 100644
--- a/src/osgEarthSymbology/RenderSymbol.cpp
+++ b/src/osgEarthSymbology/RenderSymbol.cpp
@@ -34,7 +34,9 @@ _order(rhs._order),
 _clipPlane(rhs._clipPlane),
 _minAlpha(rhs._minAlpha),
 _renderBin(rhs._renderBin),
-_transparent(rhs._transparent)
+_transparent(rhs._transparent),
+_decal(rhs._decal),
+_maxCreaseAngle(rhs._maxCreaseAngle)
 {
 }
 
@@ -46,7 +48,9 @@ _backfaceCulling( true ),
 _order          ( 0 ),
 _clipPlane      ( 0 ),
 _minAlpha       ( 0.0f ),
-_transparent    ( false )
+_transparent    ( false ),
+_decal          ( false ),
+_maxCreaseAngle ( 0.0 )
 {
     mergeConfig(conf);
 }
@@ -65,6 +69,8 @@ RenderSymbol::getConfig() const
     conf.addIfSet   ( "min_alpha",        _minAlpha );
     conf.addIfSet   ( "render_bin",       _renderBin );
     conf.addIfSet   ( "transparent",      _transparent );
+    conf.addIfSet   ( "decal",            _decal);
+    conf.addIfSet   ("max_crease_angle",  _maxCreaseAngle);
     return conf;
 }
 
@@ -80,6 +86,8 @@ RenderSymbol::mergeConfig( const Config& conf )
     conf.getIfSet   ( "min_alpha",        _minAlpha );
     conf.getIfSet   ( "render_bin",       _renderBin );
     conf.getIfSet   ( "transparent",      _transparent );
+    conf.getIfSet   ( "decal",            _decal);
+    conf.getIfSet   ("max_crease_angle",  _maxCreaseAngle);
 }
 
 void
@@ -97,17 +105,25 @@ RenderSymbol::parseSLD(const Config& c, Style& style)
         style.getOrCreate<RenderSymbol>()->depthOffset()->enabled() = as<bool>(c.value(), *defaults.depthOffset()->enabled() );
     }
     else if ( match(c.key(), "render-depth-offset-min-bias") ) {
-        style.getOrCreate<RenderSymbol>()->depthOffset()->minBias() = as<float>(c.value(), *defaults.depthOffset()->minBias() );
+        float value; Units units;
+        if (Units::parse(c.value(), value, units, Units::METERS))
+            style.getOrCreate<RenderSymbol>()->depthOffset()->minBias() = Distance(value, units);
         style.getOrCreate<RenderSymbol>()->depthOffset()->automatic() = false;
     }
     else if ( match(c.key(), "render-depth-offset-max-bias") ) {
-        style.getOrCreate<RenderSymbol>()->depthOffset()->maxBias() = as<float>(c.value(), *defaults.depthOffset()->maxBias() );
+        float value; Units units;
+        if (Units::parse(c.value(), value, units, Units::METERS))
+            style.getOrCreate<RenderSymbol>()->depthOffset()->maxBias() = Distance(value, units);
     }
     else if ( match(c.key(), "render-depth-offset-min-range") ) {
-        style.getOrCreate<RenderSymbol>()->depthOffset()->minRange() = as<float>(c.value(), *defaults.depthOffset()->minRange() );
+        float value; Units units;
+        if (Units::parse(c.value(), value, units, Units::METERS))
+            style.getOrCreate<RenderSymbol>()->depthOffset()->minRange() = Distance(value, units);
     }
     else if ( match(c.key(), "render-depth-offset-max-range") ) {
-        style.getOrCreate<RenderSymbol>()->depthOffset()->maxRange() = as<float>(c.value(), *defaults.depthOffset()->maxRange() );
+        float value; Units units;
+        if (Units::parse(c.value(), value, units, Units::METERS))
+            style.getOrCreate<RenderSymbol>()->depthOffset()->maxRange() = Distance(value, units);
     }
     else if ( match(c.key(), "render-depth-offset-auto") ) {
         style.getOrCreate<RenderSymbol>()->depthOffset()->automatic() = as<bool>(c.value(), *defaults.depthOffset()->automatic() );
@@ -130,4 +146,12 @@ RenderSymbol::parseSLD(const Config& c, Style& style)
     else if ( match(c.key(), "render-transparent") ) {
         style.getOrCreate<RenderSymbol>()->transparent() = as<bool>(c.value(), *defaults.transparent() );
     }
+    else if (match(c.key(), "render-decal")) {
+        style.getOrCreate<RenderSymbol>()->decal() = as<bool>(c.value(), *defaults.decal());
+    }
+    else if (match(c.key(), "render-max-crease-angle")) {
+        float value; Units units;
+        if (Units::parse(c.value(), value, units, Units::METERS))
+            style.getOrCreate<RenderSymbol>()->maxCreaseAngle() = Angle(value, units);
+    }
 }
diff --git a/src/osgEarthSymbology/Resource b/src/osgEarthSymbology/Resource
index 6269b6c..7c6f1af 100644
--- a/src/osgEarthSymbology/Resource
+++ b/src/osgEarthSymbology/Resource
@@ -23,6 +23,8 @@
 #include <osgEarthSymbology/Common>
 #include <osgEarthSymbology/Tags>
 #include <osgEarth/Config>
+#include <osgEarth/Status>
+#include <osgEarth/ThreadingUtils>
 #include <vector>
 
 namespace osgEarth { namespace Symbology
@@ -37,6 +39,8 @@ namespace osgEarth { namespace Symbology
         Resource(const Resource& rhs,const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY) {};
         Resource( const Config& config =Config() );
 
+        const Status& getStatus() const { return _status; }
+
         /** dtor */
         virtual ~Resource() { }
 
@@ -58,6 +62,11 @@ namespace osgEarth { namespace Symbology
         virtual Config getConfig() const;
         void mergeConfig( const Config& conf );
 
+    protected:
+
+        mutable Threading::Mutex _mutex;
+        mutable Status _status;
+
     private:
         std::string _name;
     };
diff --git a/src/osgEarthSymbology/ResourceCache b/src/osgEarthSymbology/ResourceCache
index 758c150..0b575b5 100644
--- a/src/osgEarthSymbology/ResourceCache
+++ b/src/osgEarthSymbology/ResourceCache
@@ -75,6 +75,8 @@ namespace osgEarth { namespace Symbology
          */
         bool getOrCreateStateSet( ResourceLibrary* library,  osg::ref_ptr<osg::StateSet>& output, const osgDB::Options* readOptions );
 
+        bool getOrCreateLineTexture(const URI& uri, osg::ref_ptr<osg::Texture>& output, const osgDB::Options* readOptions);
+
     protected:
         virtual ~ResourceCache() { }
 
@@ -85,6 +87,10 @@ namespace osgEarth { namespace Symbology
         SkinCache        _skinCache;
         Threading::Mutex _skinMutex;
 
+        typedef LRUCache<std::string, osg::ref_ptr<osg::Texture> > TextureCache;
+        TextureCache _texCache;
+        Threading::Mutex _texMutex;
+
         //typedef LRUCache<std::string, osg::observer_ptr<osg::Node> > InstanceCache;
         typedef LRUCache<std::string, osg::ref_ptr<osg::Node> > InstanceCache;
         InstanceCache    _instanceCache;
diff --git a/src/osgEarthSymbology/ResourceCache.cpp b/src/osgEarthSymbology/ResourceCache.cpp
index 2a5fb5c..f68c89e 100644
--- a/src/osgEarthSymbology/ResourceCache.cpp
+++ b/src/osgEarthSymbology/ResourceCache.cpp
@@ -17,6 +17,7 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>
  */
 #include <osgEarthSymbology/ResourceCache>
+#include <osg/Texture2D>
 
 using namespace osgEarth;
 using namespace osgEarth::Symbology;
@@ -33,6 +34,35 @@ _resourceLibraryCache( false )
 }
 
 bool
+ResourceCache::getOrCreateLineTexture(const URI& uri, osg::ref_ptr<osg::Texture>& output, const osgDB::Options* readOptions)
+{
+    Threading::ScopedMutexLock lock(_texMutex);
+    TextureCache::Record rec;
+    if (_texCache.get(uri.full(), rec) && rec.value().valid())
+    {
+        output = rec.value().get();
+    }
+    else
+    {
+        osg::ref_ptr<osg::Image> image = uri.getImage(readOptions);
+        if (image.valid())
+        {
+            osg::Texture2D* tex = new osg::Texture2D(image);
+            tex->setWrap( osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE );
+            tex->setWrap( osg::Texture::WRAP_T, osg::Texture::REPEAT );
+            tex->setFilter( osg::Texture::MIN_FILTER, osg::Texture::LINEAR_MIPMAP_LINEAR );
+            tex->setFilter( osg::Texture::MAG_FILTER, osg::Texture::LINEAR);
+            tex->setMaxAnisotropy( 4.0f );
+            tex->setResizeNonPowerOfTwoHint( false );
+            output = tex;
+            _texCache.insert(uri.full(), output.get());
+        }
+    }
+
+    return output.valid();
+}
+
+bool
 ResourceCache::getOrCreateStateSet(SkinResource*                skin,
                                    osg::ref_ptr<osg::StateSet>& output,
                                    const osgDB::Options*        readOptions)
diff --git a/src/osgEarthSymbology/ResourceLibrary b/src/osgEarthSymbology/ResourceLibrary
index 0c1c032..65f9dca 100644
--- a/src/osgEarthSymbology/ResourceLibrary
+++ b/src/osgEarthSymbology/ResourceLibrary
@@ -67,6 +67,7 @@ namespace osgEarth { namespace Symbology
          * Gets the name of the lib.
          */
         const std::string& getName() const { return _name; }
+        void setName(const std::string& name) { _name = name; }
 
         /**
          * Adds a resoure to the library.
diff --git a/src/osgEarthSymbology/ResourceLibrary.cpp b/src/osgEarthSymbology/ResourceLibrary.cpp
index 2718ed6..3476b27 100644
--- a/src/osgEarthSymbology/ResourceLibrary.cpp
+++ b/src/osgEarthSymbology/ResourceLibrary.cpp
@@ -52,7 +52,8 @@ _initialized( false )
 void
 ResourceLibrary::mergeConfig( const Config& conf )
 {
-    _name = conf.value( "name" );
+    if (_name.empty())
+        _name = conf.value( "name" );
 
     conf.getIfSet( "url", _uri );
 
@@ -180,6 +181,7 @@ ResourceLibrary::initialize( const osgDB::Options* dbOptions )
 
             if ( _uri.isSet() )
             {
+                OE_INFO << LC << "Loading library from " << _uri->full() << std::endl;
                 osg::ref_ptr<XmlDocument> xml = XmlDocument::load( *_uri, dbOptions );
                 if ( xml.valid() )
                 {
@@ -196,6 +198,12 @@ ResourceLibrary::initialize( const osgDB::Options* dbOptions )
                         if ( !child.empty() )
                             mergeConfig( child );
                     }
+
+                    OE_INFO << LC << "Found " << _skins.size() << " textures, " << _instances.size() << " models\n";
+                }
+                else
+                {
+                    OE_WARN << LC << "Failed to load library from XML\n";
                 }
             }
             _initialized = true;
diff --git a/src/osgEarthSymbology/Skins.cpp b/src/osgEarthSymbology/Skins.cpp
index 31a76a0..7f71e97 100644
--- a/src/osgEarthSymbology/Skins.cpp
+++ b/src/osgEarthSymbology/Skins.cpp
@@ -85,28 +85,28 @@ SkinResource::getConfig() const
     Config conf = Resource::getConfig();
     conf.key() = "skin";
 
-    conf.updateIfSet( "url",                 _imageURI );
-    conf.updateIfSet( "image_width",         _imageWidth );
-    conf.updateIfSet( "image_height",        _imageHeight );
-    conf.updateIfSet( "min_object_height",   _minObjHeight );
-    conf.updateIfSet( "max_object_height",   _maxObjHeight );
-    conf.updateIfSet( "tiled",               _isTiled );
-    conf.updateIfSet( "max_texture_span",    _maxTexSpan );
+    conf.set( "url",                 _imageURI );
+    conf.set( "image_width",         _imageWidth );
+    conf.set( "image_height",        _imageHeight );
+    conf.set( "min_object_height",   _minObjHeight );
+    conf.set( "max_object_height",   _maxObjHeight );
+    conf.set( "tiled",               _isTiled );
+    conf.set( "max_texture_span",    _maxTexSpan );
     
-    conf.updateIfSet( "texture_mode", "decal",    _texEnvMode, osg::TexEnv::DECAL );
-    conf.updateIfSet( "texture_mode", "modulate", _texEnvMode, osg::TexEnv::MODULATE );
-    conf.updateIfSet( "texture_mode", "replace",  _texEnvMode, osg::TexEnv::REPLACE );
-    conf.updateIfSet( "texture_mode", "blend",    _texEnvMode, osg::TexEnv::BLEND );
+    conf.set( "texture_mode", "decal",    _texEnvMode, osg::TexEnv::DECAL );
+    conf.set( "texture_mode", "modulate", _texEnvMode, osg::TexEnv::MODULATE );
+    conf.set( "texture_mode", "replace",  _texEnvMode, osg::TexEnv::REPLACE );
+    conf.set( "texture_mode", "blend",    _texEnvMode, osg::TexEnv::BLEND );
 
     // texture atlas support
-    conf.updateIfSet( "image_bias_s",        _imageBiasS );
-    conf.updateIfSet( "image_bias_t",        _imageBiasT );
-    conf.updateIfSet( "image_layer",         _imageLayer );
-    conf.updateIfSet( "image_scale_s",       _imageScaleS );
-    conf.updateIfSet( "image_scale_t",       _imageScaleT );
+    conf.set( "image_bias_s",        _imageBiasS );
+    conf.set( "image_bias_t",        _imageBiasT );
+    conf.set( "image_layer",         _imageLayer );
+    conf.set( "image_scale_s",       _imageScaleS );
+    conf.set( "image_scale_t",       _imageScaleT );
     
-    conf.updateIfSet( "atlas", _atlasHint );
-    conf.updateIfSet( "read_options", _readOptions );
+    conf.set( "atlas", _atlasHint );
+    conf.set( "read_options", _readOptions );
 
     return conf;
 }
@@ -167,6 +167,8 @@ SkinResource::createTexture(osg::Image* image) const
     // don't resize them, let it be
     tex->setResizeNonPowerOfTwoHint(false);
 
+    ImageUtils::activateMipMaps(tex);
+
     return tex;
 }
 
@@ -215,6 +217,9 @@ SkinResource::createStateSet( osg::Image* image ) const
 osg::ref_ptr<osg::Image>
 SkinResource::createImage( const osgDB::Options* dbOptions ) const
 {
+    if (getStatus().isError())
+        return 0L;
+
     ReadResult result;
     if (_readOptions.isSet())
     {
@@ -226,6 +231,13 @@ SkinResource::createImage( const osgDB::Options* dbOptions ) const
     {
         result = _imageURI->readImage(dbOptions);
     }
+
+    if (result.failed())
+    {
+        Threading::ScopedMutexLock lock(_mutex);
+        if (_status.isOK())
+            _status = Status::Error(Status::ServiceUnavailable, "Failed to load resource image\n");
+    }
     return result.releaseImage();
 }
 
diff --git a/src/osgEarthSymbology/Style b/src/osgEarthSymbology/Style
index 372ba39..ea402f3 100644
--- a/src/osgEarthSymbology/Style
+++ b/src/osgEarthSymbology/Style
@@ -34,6 +34,7 @@
 #include <osgEarthSymbology/Skins>
 #include <osgEarthSymbology/RenderSymbol>
 #include <osgEarthSymbology/CoverageSymbol>
+#include <osgEarthSymbology/BBoxSymbol>
 
 #include <osgEarth/Config>
 #include <osg/Object>
@@ -144,6 +145,9 @@ namespace osgEarth { namespace Symbology
 
         template<typename T> T* getOrCreate() { return getOrCreateSymbol<T>(); } // alias
 
+	/** Access to list of symbols currently defining the style */
+	SymbolList& symbols() { return _symbols; }
+	const SymbolList& symbols() const { return _symbols; }
 
         /** Serializes this object into a Config. */
         virtual Config getConfig( bool keepOrigType =true ) const;
diff --git a/src/osgEarthSymbology/StyleSheet b/src/osgEarthSymbology/StyleSheet
index 57ddda9..32d427c 100644
--- a/src/osgEarthSymbology/StyleSheet
+++ b/src/osgEarthSymbology/StyleSheet
@@ -75,6 +75,10 @@ namespace osgEarth { namespace Symbology
         Style* getDefaultStyle();
         const Style* getDefaultStyle() const;
 
+        /** Get access to styles for manual configuration */
+        StyleMap& styles() { return _styles; }
+        const StyleMap& styles() const { return _styles; }
+
         /** Selectors pick a style from the sheet based on some criteria. */
         StyleSelectorList& selectors() { return _selectors; }
         const StyleSelectorList& selectors() const { return _selectors; }
diff --git a/src/osgEarthSymbology/StyleSheet.cpp b/src/osgEarthSymbology/StyleSheet.cpp
index d8d6ce5..9f3a293 100644
--- a/src/osgEarthSymbology/StyleSheet.cpp
+++ b/src/osgEarthSymbology/StyleSheet.cpp
@@ -59,7 +59,7 @@ StyleSheet::getStyle( const std::string& name, bool fallBackOnDefault )
     if ( i != _styles.end() ) {
         return &i->second;
     }
-    else if ( name.length() > 1 && name.at(0) == '#' ) {
+    else if ( name.length() > 1 && name[0] == '#' ) {
         std::string nameWithoutHash = name.substr( 1 );
         return getStyle( nameWithoutHash, fallBackOnDefault );
     }
@@ -78,7 +78,7 @@ StyleSheet::getStyle( const std::string& name, bool fallBackOnDefault ) const
     if ( i != _styles.end() ) {
         return &i->second;
     }
-    else if ( name.length() > 1 && name.at(0) == '#' ) {
+    else if ( name.length() > 1 && name[0] == '#' ) {
         std::string nameWithoutHash = name.substr( 1 );
         return getStyle( nameWithoutHash, fallBackOnDefault );
     }
@@ -232,7 +232,11 @@ StyleSheet::mergeConfig( const Config& conf )
     ConfigSet libraries = conf.children( "library" );
     for( ConfigSet::iterator i = libraries.begin(); i != libraries.end(); ++i )
     {
-        ResourceLibrary* resLib = new ResourceLibrary( *i );
+        const Config& libConf = *i;
+        ResourceLibrary* resLib = new ResourceLibrary( libConf );
+        if (resLib && libConf.value("name").empty() == false)
+            resLib->setName(libConf.value("name"));
+
         _resLibs[resLib->getName()] = resLib;
     }
 
diff --git a/src/osgEarthSymbology/Symbol b/src/osgEarthSymbology/Symbol
index b0294da..7f6e1ec 100644
--- a/src/osgEarthSymbology/Symbol
+++ b/src/osgEarthSymbology/Symbol
@@ -45,6 +45,7 @@ namespace osgEarth { namespace Symbology
         const URIContext& uriContext() const { return _uriContext; }
 
         virtual Config getConfig() const;   
+	virtual void mergeConfig(const Config& conf);
 
     public:
         /* methods required by osg::Object */
@@ -60,8 +61,6 @@ namespace osgEarth { namespace Symbology
 
         Symbol( const Config& conf =Config() );
 
-        void mergeConfig(const Config& conf);
-
         virtual ~Symbol() { }
     };
 
@@ -140,10 +139,20 @@ namespace osgEarth { namespace Symbology
     };
 
 #define OSGEARTH_REGISTER_SYMBOL( CLASSNAME )\
+    extern "C" void osgearth_symbol_##CLASSNAME(void) {} \
     static osgEarth::Symbology::RegisterSymbolProxy<CLASSNAME> s_osgEarthRegisterSymbolProxy_##CLASSNAME;
+    
+#define USE_OSGEARTH_SYMBOL( CLASSNAME ) \
+    extern "C" void osgearth_symbol_##CLASSNAME(void); \
+    static osgDB::PluginFunctionProxy proxy_osgearth_symbol_##CLASSNAME(osgearth_symbol_##CLASSNAME);
 
 #define OSGEARTH_REGISTER_SIMPLE_SYMBOL( KEY, CLASSNAME)\
+    extern "C" void osgearth_simple_symbol_##KEY(void) {} \
     static osgEarth::Symbology::RegisterSymbolProxy< osgEarth::Symbology::SimpleSymbolFactory<CLASSNAME> > s_osgEarthRegisterSymbolProxy_##CLASSNAME##KEY(new osgEarth::Symbology::SimpleSymbolFactory<CLASSNAME>(#KEY));
+    
+#define USE_OSGEARTH_SIMPLE_SYMBOL( KEY ) \
+    extern "C" void osgearth_simple_symbol_##KEY(void); \
+    static osgDB::PluginFunctionProxy proxy_osgearth_simple_symbol_##KEY(osgearth_simple_symbol_##KEY);
 
 
 } } // namespace osgEarth::Symbology
diff --git a/src/osgEarthSymbology/TextSymbol b/src/osgEarthSymbology/TextSymbol
index 8bd1a39..1edda3b 100644
--- a/src/osgEarthSymbology/TextSymbol
+++ b/src/osgEarthSymbology/TextSymbol
@@ -136,10 +136,6 @@ namespace osgEarth { namespace Symbology
         optional<Layout>& layout() { return _layout; }
         const optional<Layout>& layout() const { return _layout; }
 
-        /** Whether to remove duplicates when drawing multiple labels. */
-        optional<bool>& removeDuplicateLabels() { return _removeDuplicateLabels; }
-        const optional<bool>& removeDuplicateLabels() const { return _removeDuplicateLabels; }
-
         /** Whether to enable decluttering on the text, if applicable */
         optional<bool>& declutter() { return _declutter; }
         const optional<bool>& declutter() const { return _declutter; }
@@ -175,7 +171,6 @@ namespace osgEarth { namespace Symbology
         optional<NumericExpression> _size;
         optional<StringExpression>  _content;
         optional<NumericExpression> _priority;
-        optional<bool>              _removeDuplicateLabels;
         optional<osg::Vec2s>        _pixelOffset;
         optional<NumericExpression> _onScreenRotation;
         optional<NumericExpression> _geographicCourse;
diff --git a/src/osgEarthSymbology/TextSymbol.cpp b/src/osgEarthSymbology/TextSymbol.cpp
index ed7291f..30059d2 100644
--- a/src/osgEarthSymbology/TextSymbol.cpp
+++ b/src/osgEarthSymbology/TextSymbol.cpp
@@ -38,7 +38,6 @@ _font(rhs._font),
 _size(rhs._size),
 _content(rhs._content),
 _priority(rhs._priority),
-_removeDuplicateLabels(rhs._removeDuplicateLabels),
 _pixelOffset(rhs._pixelOffset),
 _onScreenRotation(rhs._onScreenRotation),
 _geographicCourse(rhs._geographicCourse),
@@ -60,7 +59,6 @@ _haloOffset           ( 0.0625f ),
 _haloBackdropType     ( osgText::Text::OUTLINE ),
 _haloImplementation   ( osgText::Text::DELAYED_DEPTH_WRITES ),
 _size                 ( 16.0f ),
-_removeDuplicateLabels( false ),
 _alignment            ( ALIGN_BASE_LINE ),
 _layout               ( LAYOUT_LEFT_TO_RIGHT ),
 _provider             ( "annotation" ),
@@ -101,7 +99,6 @@ TextSymbol::getConfig() const
     conf.addObjIfSet( "size", _size );
     conf.addObjIfSet( "content", _content );
     conf.addObjIfSet( "priority", _priority );
-    conf.addIfSet( "remove_duplicate_labels", _removeDuplicateLabels );
 
     conf.addIfSet( "encoding", "ascii", _encoding, ENCODING_ASCII );
     conf.addIfSet( "encoding", "utf8",  _encoding, ENCODING_UTF8 );
@@ -171,7 +168,6 @@ TextSymbol::mergeConfig( const Config& conf )
     conf.getObjIfSet( "size", _size );
     conf.getObjIfSet( "content", _content );
     conf.getObjIfSet( "priority", _priority );
-    conf.getIfSet( "remove_duplicate_labels", _removeDuplicateLabels );
 
     conf.getIfSet( "encoding", "ascii", _encoding, ENCODING_ASCII );
     conf.getIfSet( "encoding", "utf8",  _encoding, ENCODING_UTF8 );
@@ -272,12 +268,6 @@ TextSymbol::parseSLD(const Config& c, Style& style)
         else if ( match(c.value(), "delayed-depth-writes") )
             style.getOrCreate<TextSymbol>()->haloImplementation() = osgText::Text::DELAYED_DEPTH_WRITES;
     }
-    else if ( match(c.key(), "text-remove-duplicate-labels") ) {
-        if ( c.value() == "true" )
-            style.getOrCreate<TextSymbol>()->removeDuplicateLabels() = true;
-        else if (c.value() == "false")
-            style.getOrCreate<TextSymbol>()->removeDuplicateLabels() = false;
-    }
     else if ( match(c.key(), "text-align") ) {
         if      ( match(c.value(), "left-top") )
             style.getOrCreate<TextSymbol>()->alignment() = TextSymbol::ALIGN_LEFT_TOP;
diff --git a/src/osgEarthTriton/CMakeLists.txt b/src/osgEarthTriton/CMakeLists.txt
index 97bae35..d07fcaa 100644
--- a/src/osgEarthTriton/CMakeLists.txt
+++ b/src/osgEarthTriton/CMakeLists.txt
@@ -20,6 +20,7 @@ SET(LIB_PUBLIC_HEADERS
     TritonDrawable
     TritonAPIWrapper
     TritonCallback
+    TritonLayer
 )
 
 ADD_LIBRARY(${LIB_NAME} ${OSGEARTH_USER_DEFINED_DYNAMIC_OR_STATIC}
@@ -29,6 +30,7 @@ ADD_LIBRARY(${LIB_NAME} ${OSGEARTH_USER_DEFINED_DYNAMIC_OR_STATIC}
     TritonContext.cpp
     TritonDrawable.cpp
     TritonAPIWrapper.cpp
+    TritonLayer.cpp
 )
 
 INCLUDE_DIRECTORIES( 
diff --git a/src/osgEarthTriton/TritonAPIWrapper b/src/osgEarthTriton/TritonAPIWrapper
index 94aaa12..6064026 100644
--- a/src/osgEarthTriton/TritonAPIWrapper
+++ b/src/osgEarthTriton/TritonAPIWrapper
@@ -23,6 +23,11 @@
 #include <osg/Vec3>
 #include <stdint.h> // for uintptr_t
 
+namespace osgEarth {
+    namespace Util {
+        class OceanNode;
+    }
+}
 
 namespace osgEarth { namespace Triton
 {
@@ -80,9 +85,6 @@ namespace osgEarth { namespace Triton
         void SetQuality(OceanQuality value);
         OceanQuality GetQuality() const;
 
-        //void SetMaximumWavePeriod(double value);
-        //double GetMaximumWavePeriod() const;
-
         void EnableSpray(bool enabled);
         bool SprayEnabled() const;
 
diff --git a/src/osgEarthTriton/TritonContext b/src/osgEarthTriton/TritonContext
index 9bd3015..4b3c534 100644
--- a/src/osgEarthTriton/TritonContext
+++ b/src/osgEarthTriton/TritonContext
@@ -19,6 +19,7 @@
 #ifndef OSGEARTH_TRITON_CONTEXT_H
 #define OSGEARTH_TRITON_CONTEXT_H
 
+#include <Triton.h>
 #include "Common"
 #include "TritonOptions"
 #include "TritonAPIWrapper"
@@ -26,7 +27,6 @@
 #include <osg/Referenced>
 #include <osg/Light>
 #include <osgEarth/ThreadingUtils>
-#include <Triton.h>
 
 namespace osgEarth {
     class SpatialReference;
@@ -37,9 +37,11 @@ namespace osgEarth { namespace Triton
     /**
      * Contains all the Triton SDK handles.
      */
-    class TritonContext : public osg::Referenced
+    class TritonContext : public osg::Object
     {
     public:
+        META_Object(osgEarth, TritonContext);
+
         TritonContext(const TritonOptions& options);
 
         /** Sets the spatial reference system of the map */
@@ -70,10 +72,22 @@ namespace osgEarth { namespace Triton
 
         int getHeightMapSize() const;
 
+        const std::string& getMaskLayerName() const;
+
+    public: // osg::Object
+
+        /** If State is non-zero, this function releases any associated OpenGL objects for
+           * the specified graphics context. Otherwise, releases OpenGL objects
+           * for all graphics contexts. */
+        void releaseGLObjects(osg::State* state) const;
+
     protected:
 
         virtual ~TritonContext();
 
+        // hidden ctors (for META_Object)
+        TritonContext() { }
+        TritonContext(const TritonContext&, const osg::CopyOp&) { }
 
     private:
         TritonOptions    _options;
@@ -84,9 +98,9 @@ namespace osgEarth { namespace Triton
 
         osg::ref_ptr<const osgEarth::SpatialReference> _srs;
 
-        ::Triton::ResourceLoader* _resourceLoader;
-        ::Triton::Environment*    _environment;
-        ::Triton::Ocean*          _ocean;
+        mutable ::Triton::ResourceLoader* _resourceLoader;
+        mutable ::Triton::Environment*    _environment;
+        mutable ::Triton::Ocean*          _ocean;
 
         Environment* _environmentWrapper;
         Ocean*       _oceanWrapper;
diff --git a/src/osgEarthTriton/TritonContext.cpp b/src/osgEarthTriton/TritonContext.cpp
index 095186f..9a171cb 100644
--- a/src/osgEarthTriton/TritonContext.cpp
+++ b/src/osgEarthTriton/TritonContext.cpp
@@ -16,7 +16,6 @@
  * You should have received a copy of the GNU Lesser General Public License
  * along with this program.  If not, see <http://www.gnu.org/licenses/>
  */
-#include <Triton.h>
 #include "TritonContext"
 #include <osg/GLExtensions>
 #include <osg/Math>
@@ -44,20 +43,11 @@ _oceanWrapper         ( 0L )
 
 TritonContext::~TritonContext()
 {
-    if ( _oceanWrapper )
+    if (_oceanWrapper)
         delete _oceanWrapper;
 
-    if ( _environmentWrapper )
+    if (_environmentWrapper)
         delete _environmentWrapper;
-
-    if ( _ocean )
-        delete _ocean;
-
-    if ( _environment )
-        delete _environment;
-
-    if ( _resourceLoader )
-        delete _resourceLoader;
 }
 
 void
@@ -84,6 +74,12 @@ TritonContext::getHeightMapSize() const
     return osg::clampBetween(_options.heightMapSize().get(), 64, 2048);
 }
 
+const std::string&
+TritonContext::getMaskLayerName() const
+{
+    return _options.maskLayer().get();
+}
+
 void
 TritonContext::initialize(osg::RenderInfo& renderInfo)
 {
@@ -183,3 +179,29 @@ TritonContext::update(double simTime)
         _ocean->UpdateSimulation( fmod(simTime, 86400.0) );
     }
 }
+
+void
+TritonContext::releaseGLObjects(osg::State* state) const
+{
+    OE_INFO << LC << "Triton shutting down - releasing GL resources\n";
+    if (state)
+    {
+        if ( _ocean )
+        {
+            delete _ocean;
+            _ocean = 0L;
+        }
+
+        if ( _environment )
+        {
+            delete _environment;
+            _environment = 0L;
+        }
+
+        if ( _resourceLoader )
+        {
+            delete _resourceLoader;
+            _resourceLoader = 0L;
+        }
+    }
+}
diff --git a/src/osgEarthTriton/TritonDrawable b/src/osgEarthTriton/TritonDrawable
index 79b8ac4..b83df40 100644
--- a/src/osgEarthTriton/TritonDrawable
+++ b/src/osgEarthTriton/TritonDrawable
@@ -30,6 +30,7 @@
 #include <osgEarth/MapNode>
 #include <osgEarth/Terrain>
 #include <osgEarth/NativeProgramAdapter>
+#include <osgEarth/ImageLayer>
 
 const unsigned int TRITON_OCEAN_MASK = 0x4; // 0100
 
@@ -46,6 +47,8 @@ namespace osgEarth { namespace Triton
         TritonDrawable(osgEarth::MapNode* mapNode=NULL, TritonContext* TRITON =0L);
         META_Object(Triton, TritonDrawable);
 
+        void setMaskLayer(const osgEarth::ImageLayer* maskLayer);
+
     public: // osg::Drawable
 
         // custom draw (called with an active GC)
@@ -67,17 +70,20 @@ namespace osgEarth { namespace Triton
         void SetPlanarReflectionMap(osg::Texture2D* map) {_planarReflectionMap = map;};
         void SetPlanarReflectionProjection(osg::RefMatrix * proj) {_planarReflectionProjection = proj;};
 
+        osg::Group* _heightCameraParent;
+
     protected:
         virtual ~TritonDrawable();
 
         osg::observer_ptr<TritonContext>  _TRITON;
         osg::observer_ptr<osgEarth::MapNode> _mapNode;
+        osg::observer_ptr<const osgEarth::ImageLayer> _maskLayer;
         osg::ref_ptr<osg::TextureCubeMap> _cubeMap;
         osg::BoundingBox                  _bbox;
         osg::ref_ptr<osg::Texture2D> _heightMap;
         osg::ref_ptr<osg::Camera> _heightCamera;
         osg::observer_ptr<osgEarth::TerrainCallback> _terrainChangedCallback;
-
+        
         osg::ref_ptr< osg::Texture2D >       _planarReflectionMap;
         osg::ref_ptr< osg::RefMatrix >       _planarReflectionProjection;
 
diff --git a/src/osgEarthTriton/TritonDrawable.cpp b/src/osgEarthTriton/TritonDrawable.cpp
index 4a47c29..1d046f5 100644
--- a/src/osgEarthTriton/TritonDrawable.cpp
+++ b/src/osgEarthTriton/TritonDrawable.cpp
@@ -16,10 +16,8 @@
  * You should have received a copy of the GNU Lesser General Public License
  * along with this program.  If not, see <http://www.gnu.org/licenses/>
  */
-#include <Triton.h>
-
-#include "TritonDrawable"
 #include "TritonContext"
+#include "TritonDrawable"
 #include <osg/MatrixTransform>
 #include <osg/FrameBufferObject>
 
@@ -246,7 +244,7 @@ static const size_t NUM_CONTEXTS = 64;
 
         // Called when the terrain engine loads a new tile (or new tile heightfield data).
         // When this happens we need to update the Triton height map.
-        void onTileAdded(const osgEarth::TileKey& tileKey, osg::Node* terrain, osgEarth::TerrainCallbackContext& context)
+        void onTileAdded(const osgEarth::TileKey& tileKey, osg::Node* graph, osgEarth::TerrainCallbackContext& context)
         {
             osg::ref_ptr<TritonDrawable> drawable;
             if ( _drawable.lock(drawable) )
@@ -262,14 +260,25 @@ static const size_t NUM_CONTEXTS = 64;
         "#version " GLSL_VERSION_STR "\n"
         GLSL_DEFAULT_PRECISION_FLOAT "\n"
 
+        "#pragma import_defines(OE_TRITON_MASK_MATRIX);\n"
+
         "// terrain SDK:\n"
         "float oe_terrain_getElevation(); \n"
 
-        "varying float oe_triton_elev;\n"
+        "out float oe_triton_elev;\n"
+        
+        "#ifdef OE_TRITON_MASK_MATRIX\n"
+        "out vec2 maskCoords;\n"
+        "uniform mat4 OE_TRITON_MASK_MATRIX;\n"
+        "vec4 oe_layer_tilec;\n"
+        "#endif\n"
 
-        "void setupContour(inout vec4 VertexModel) \n"
+        "void oe_triton_setupHeightMap(inout vec4 unused) \n"
         "{ \n"
         "    oe_triton_elev = oe_terrain_getElevation(); \n"
+        "#ifdef OE_TRITON_MASK_MATRIX\n"
+        "    maskCoords = (OE_TRITON_MASK_MATRIX * oe_layer_tilec).st;\n"
+        "#endif\n"
         "} \n";
 
     // The fragment shader simply takes the texture index that we generated
@@ -281,24 +290,41 @@ static const size_t NUM_CONTEXTS = 64;
         "#version " GLSL_VERSION_STR "\n"
         GLSL_DEFAULT_PRECISION_FLOAT "\n"
 
-        "varying float oe_triton_elev;\n"
+        "#pragma import_defines(OE_TRITON_MASK_SAMPLER);\n"
+
+        "in float oe_triton_elev;\n"
+
+        "#ifdef OE_TRITON_MASK_SAMPLER\n"
+        "in vec2 maskCoords;\n"
+        "uniform sampler2D OE_TRITON_MASK_SAMPLER;\n"
+        "uniform float DD;\n"
+        "#endif\n"
+
+        "out vec4 out_height; \n"
 
-        "void colorContour( inout vec4 color ) \n"
+        "void oe_triton_drawHeightMap(inout vec4 unused) \n"
         "{ \n"
 #ifdef DEBUG_HEIGHTMAP
           // Map to black = -500m, white = +500m
           "   float nHeight = clamp(oe_triton_elev / 1000.0 + 0.5, 0.0, 1.0);\n"
 #else
           "   float nHeight = oe_triton_elev;\n"
+
+          "#ifdef OE_TRITON_MASK_SAMPLER\n"
+          "    float mask = texture(OE_TRITON_MASK_SAMPLER, maskCoords).a;\n"
+          "    nHeight *= mask; \n"
+          "#endif\n"
+
 #endif
-        "    gl_FragColor = vec4( nHeight, 0.0, 0.0, 1.0 ); \n"
+        "    out_height = vec4( nHeight, 0.0, 0.0, 1.0 ); \n"
         "} \n";
 }
 
 
 TritonDrawable::TritonDrawable(osgEarth::MapNode* mapNode, TritonContext* TRITON) :
 _TRITON(TRITON),
-_mapNode(mapNode)
+_mapNode(mapNode),
+_heightCameraParent(0L)
 {
     // call this to ensure draw() gets called every frame.
     setSupportsDisplayList( false );
@@ -317,11 +343,17 @@ TritonDrawable::~TritonDrawable()
     osg::ref_ptr<osgEarth::TerrainCallback> callback;
     if ( _mapNode.lock(mapNode) && _terrainChangedCallback.lock(callback) && mapNode->getTerrain() )
     {
-        _mapNode->getTerrain()->removeTerrainCallback( callback );
+        _mapNode->getTerrain()->removeTerrainCallback( callback.get() );
     }
 }
 
 void
+TritonDrawable::setMaskLayer(const osgEarth::ImageLayer* layer)
+{
+    _maskLayer = layer;
+}
+
+void
 TritonDrawable::dirtyAllContexts()
 {
     _contextDirty.setAllElementsTo(1);
@@ -359,7 +391,7 @@ TritonDrawable::updateHeightMap(osg::RenderInfo& renderInfo) const
     double lookAtLat=0.0, lookAtLon=0.0, lookAtHeight=0.0;
     mapNode->getMap()->getSRS()->getEllipsoid()->convertXYZToLatLongHeight(center.x(), center.y(), center.z(), lookAtLat, lookAtLon, lookAtHeight);
 
-    // Calculate the distance to the horizon from the eyepoint
+    // Calculate the distance to the horizon from the eyepoint 
     double eyeLen = eye.length();
     double radE = mslEye.length();
     double hmax = radE + 8848.0;
@@ -410,7 +442,7 @@ TritonDrawable::updateHeightMap(osg::RenderInfo& renderInfo) const
 
 #ifdef DEBUG_HEIGHTMAP
     mapNode->getParent(0)->removeChild(0, 1);
-    mapNode->getParent(0)->insertChild(0, makeFrustumFromCamera(_heightCam));
+    mapNode->getParent(0)->insertChild(0, makeFrustumFromCamera(_heightCamera));
 #endif /* DEBUG_HEIGHTMAP */
 }
 
@@ -579,9 +611,14 @@ TritonDrawable::drawImplementation(osg::RenderInfo& renderInfo) const
     // Put GL back in a state that won't confuse the OSG state tracking:
     state->dirtyAllVertexArrays();
     state->dirtyAllAttributes();
+    state->dirtyAllModes();
     //osg::GL2Extensions* api = osg::GL2Extensions::Get(state->getContextID(), true);
     //api->glUseProgram((GLuint)0);
     //state->setLastAppliedProgramObject( 0L );
+
+    // Keep an eye on this.
+    // I had to remove something similar in another module (Rex engine) because it was causing
+    // positional attributes (like clip planes) to re-apply with an incorrect MVM. -gw
     state->apply();
 }
 
@@ -691,25 +728,51 @@ void TritonDrawable::setupHeightMap(osg::State& state)
     _heightCamera->setRenderOrder(osg::Camera::PRE_RENDER);
     _heightCamera->setRenderTargetImplementation( osg::Camera::FRAME_BUFFER_OBJECT );
     _heightCamera->setImplicitBufferAttachmentMask(0, 0);
-    _heightCamera->attach(osg::Camera::COLOR_BUFFER, _heightMap);
+    _heightCamera->attach(osg::Camera::COLOR_BUFFER, _heightMap.get());
     _heightCamera->setCullMask( ~TRITON_OCEAN_MASK );
     _heightCamera->setAllowEventFocus(false);
     _heightCamera->setFinalDrawCallback(new PassHeightMapToTritonCallback(_TRITON.get()));
 
     // Install the shaders. We also bind osgEarth's elevation data attribute, which the
-    // terrain engine automatically generates at the specified location.
+    // terrain engine automatically generates at the specified location. We need to set
+    // this VP as "abstract" because it cannot run without the terrain engine's SDK 
+    // shaders installed.
     osg::StateSet* stateSet = _heightCamera->getOrCreateStateSet();
     osgEarth::VirtualProgram* heightProgram = osgEarth::VirtualProgram::getOrCreate(stateSet);
-    heightProgram->setFunction( "setupContour", vertexShader,   osgEarth::ShaderComp::LOCATION_VERTEX_MODEL);
-    heightProgram->setFunction( "colorContour", fragmentShader, osgEarth::ShaderComp::LOCATION_FRAGMENT_OUTPUT);
+    heightProgram->setName("Triton Height Map");
+    heightProgram->setFunction( "oe_triton_setupHeightMap", vertexShader,   osgEarth::ShaderComp::LOCATION_VERTEX_MODEL);
+    heightProgram->setFunction( "oe_triton_drawHeightMap", fragmentShader, osgEarth::ShaderComp::LOCATION_FRAGMENT_OUTPUT);
+    heightProgram->setIsAbstract(true);
+
+    // If we're using a mask layer, enable that in the shader:
+    osg::ref_ptr<const ImageLayer> maskLayer;
+    _maskLayer.lock(maskLayer);
+
+    if (!maskLayer.valid() && !_TRITON->getMaskLayerName().empty())
+    {
+        maskLayer = _mapNode->getMap()->getLayerByName<ImageLayer>(_TRITON->getMaskLayerName());
+        if (!maskLayer)
+        {
+            OE_WARN << LC << "Mask Layer \"" << _TRITON->getMaskLayerName() << "\" not found in Map!\n";
+        }
+    }
+
+    if (maskLayer.valid())
+    {
+        stateSet->setDefine("OE_TRITON_MASK_SAMPLER", maskLayer->shareTexUniformName().get());
+        stateSet->setDefine("OE_TRITON_MASK_MATRIX", maskLayer->shareTexMatUniformName().get());
+        OE_INFO << LC << "Using mask layer \"" << maskLayer->getName() << "\", sampler=" << maskLayer->shareTexUniformName().get() << ", matrix=" << maskLayer->shareTexMatUniformName().get() << std::endl;
+    }
 
     _heightCamera->addChild( mapNode->getTerrainEngine() );
+
     _terrainChangedCallback = new OceanTerrainChangedCallback(this);
     if ( mapNode->getTerrain() )
         mapNode->getTerrain()->addTerrainCallback( _terrainChangedCallback.get() );
 
-    osg::Group* root = osgEarth::findTopMostNodeOfType<osg::Group>(mapNode);
-    root->addChild(_heightCamera.get());
+    _heightCameraParent->addChild(_heightCamera.get());
+    //osg::Group* root = osgEarth::findTopMostNodeOfType<osg::Group>(mapNode);
+    //root->addChild(_heightCamera.get());
 
 #ifdef DEBUG_HEIGHTMAP
     mapNode->getParent(0)->addChild(CreateTextureQuadOverlay(_heightMap, 0.65, 0.05, 0.3, 0.3));
diff --git a/src/osgEarthTriton/TritonOptions b/src/osgEarthTriton/TritonLayer
similarity index 60%
copy from src/osgEarthTriton/TritonOptions
copy to src/osgEarthTriton/TritonLayer
index f87635c..c6372eb 100644
--- a/src/osgEarthTriton/TritonOptions
+++ b/src/osgEarthTriton/TritonLayer
@@ -16,32 +16,33 @@
  * You should have received a copy of the GNU Lesser General Public License
  * along with this program.  If not, see <http://www.gnu.org/licenses/>
  */
-#ifndef OSGEARTH_TRITON_OPTIONS
-#define OSGEARTH_TRITON_OPTIONS 1
+#ifndef OSGEARTH_TRITON_LAYER
+#define OSGEARTH_TRITON_LAYER 1
 
 #include "Common"
-#include <osgEarthUtil/Ocean>
+#include "TritonOptions"
+#include "TritonNode"
+#include <osgEarth/VisibleLayer>
+#include <osgEarth/LayerListener>
+#include <osgEarth/ImageLayer>
 
 namespace osgEarth { namespace Triton
 {
     /**
-     * Options for controlling the ocean surface node.
+     * Serializable options for initializing the TritonLayer.
      */
-    class /*header-only*/ TritonOptions : public osgEarth::Util::OceanOptions
+    class OSGEARTHTRITON_EXPORT TritonLayerOptions : public osgEarth::VisibleLayerOptions
     {
     public:
-        TritonOptions(const osgEarth::Util::OceanOptions& conf = osgEarth::Util::OceanOptions()) :
-            osgEarth::Util::OceanOptions( conf )
+        TritonLayerOptions(const osgEarth::ConfigOptions& options = osgEarth::ConfigOptions()) :
+            osgEarth::VisibleLayerOptions( options )
         {
-            setDriver( "triton" );
             _useHeightMap.init( true );
             _heightMapSize.init( 1024 );
             _renderBinNumber.init( 12 );
             fromConfig( _conf );
         }
 
-        virtual ~TritonOptions() { }
-
         /* User name for license activation */
         osgEarth::optional<std::string>& user() { return _user; }
         const osgEarth::optional<std::string>& user() const { return _user; }
@@ -66,22 +67,31 @@ namespace osgEarth { namespace Triton
         optional<int>& renderBinNumber() { return _renderBinNumber; }
         const optional<int>& renderBinNumber() const { return _renderBinNumber; }
 
+        /** Name of optional land mask layer. A land mask layer is an image layer
+          * in the Map that has the alpha channel set to 1 over land and 0 over ocean.
+          * This is used in addition to the height map to prevent drawing the ocean
+          * over land areas.
+          */
+        optional<std::string>& maskLayer() { return _maskLayerName; }
+        const optional<std::string>& maskLayer() const { return _maskLayerName; }
 
     public:
         osgEarth::Config getConfig() const {
-            osgEarth::Config conf = osgEarth::Util::OceanOptions::newConfig();
+            osgEarth::Config conf = osgEarth::VisibleLayerOptions::getConfig();
+            conf.key() = "triton";
             conf.addIfSet("user", _user);
             conf.addIfSet("license_code", _licenseCode);
             conf.addIfSet("resource_path", _resourcePath);
             conf.addIfSet("use_height_map", _useHeightMap);
             conf.addIfSet("height_map_size", _heightMapSize);
-            conf.addIfSet("render_bin_number",   _renderBinNumber);
+            conf.addIfSet("render_bin_number", _renderBinNumber);
+            conf.addIfSet("mask_layer", _maskLayerName);
             return conf;
         }
 
     protected:
         void mergeConfig( const osgEarth::Config& conf ) {
-            osgEarth::Util::OceanOptions::mergeConfig( conf );
+            osgEarth::VisibleLayerOptions::mergeConfig( conf );
             fromConfig( conf );
         }
 
@@ -92,7 +102,8 @@ namespace osgEarth { namespace Triton
             conf.getIfSet("resource_path", _resourcePath);
             conf.getIfSet("use_height_map", _useHeightMap);
             conf.getIfSet("height_map_size", _heightMapSize);
-            conf.getIfSet("render_bin_number",   _renderBinNumber);
+            conf.getIfSet("render_bin_number", _renderBinNumber);
+            conf.getIfSet("mask_layer", _maskLayerName);
         }
 
     private:
@@ -102,8 +113,50 @@ namespace osgEarth { namespace Triton
         osgEarth::optional<bool>        _useHeightMap;
         osgEarth::optional<int>         _heightMapSize;
         osgEarth::optional<int>         _renderBinNumber;
+        osgEarth::optional<std::string> _maskLayerName;
+    };
+
+
+    /**
+     * Node that roots the Triton adapter.
+     */
+    class OSGEARTHTRITON_EXPORT TritonLayer : public osgEarth::VisibleLayer
+    {
+    public:
+        META_Layer(osgEarth, TritonLayer, TritonLayerOptions);
+
+        TritonLayer();
+
+        TritonLayer(const TritonLayerOptions& options);
+
+        /** Masking layer for the ocean */
+        void setMaskLayer(const osgEarth::ImageLayer* maskLayer);
+
+        /** Maximum visibility altitude */
+        void setMaxAltitude(float alt);
+        float getMaxAltitude() const;
+
+    public: // Layer
+
+        virtual osg::Node* getOrCreateNode();
+
+    protected: // Layer
+
+        virtual void init();
+
+        virtual void addedToMap(const class osgEarth::Map*);
+
+        virtual void removedFromMap(const class osgEarth::Map*);
+
+    private:
+
+        osg::ref_ptr<TritonNode> _tritonNode;
+
+    private:
+
+        LayerListener<TritonLayer, const osgEarth::ImageLayer> _layerListener;
     };
 
 } } // namespace osgEarth::Triton
 
-#endif // OSGEARTH_TRITON_OPTIONS
+#endif // OSGEARTH_TRITON_LAYER
diff --git a/src/osgEarthTriton/TritonLayer.cpp b/src/osgEarthTriton/TritonLayer.cpp
new file mode 100644
index 0000000..694ed7f
--- /dev/null
+++ b/src/osgEarthTriton/TritonLayer.cpp
@@ -0,0 +1,96 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+ * Copyright 2016 Pelican Mapping
+ * http://osgearth.org
+ *
+ * osgEarth is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+#include "TritonLayer"
+#include "TritonNode"
+#include "TritonContext"
+#include <osgEarth/ImageLayer>
+
+#define LC "[TritonLayer] "
+
+using namespace osgEarth::Triton;
+
+/** Register this layer so it can be used in an earth file */
+namespace osgEarth { namespace Triton
+{
+    REGISTER_OSGEARTH_LAYER(triton, TritonLayer);
+    REGISTER_OSGEARTH_LAYER(triton_ocean, TritonLayer);
+} }
+
+
+TritonLayer::TritonLayer() :
+osgEarth::VisibleLayer(&_optionsConcrete),
+_options(&_optionsConcrete)
+{
+    init();
+}
+
+TritonLayer::TritonLayer(const TritonLayerOptions& options) :
+osgEarth::VisibleLayer(&_optionsConcrete),
+_options(&_optionsConcrete),
+_optionsConcrete(options)
+{
+    init();
+}
+
+void
+TritonLayer::init()
+{
+    OE_INFO << LC << "Creating a TritonLayer\n";
+
+    osgEarth::VisibleLayer::init();
+
+    this->setName("Triton");
+    setRenderType(RENDERTYPE_NONE);
+
+    TritonOptions legacyOptions(options());
+    _tritonNode = new TritonNode(legacyOptions);
+}
+
+osg::Node*
+TritonLayer::getOrCreateNode()
+{
+    return _tritonNode.get();
+}
+
+void
+TritonLayer::setMaskLayer(const osgEarth::ImageLayer* maskLayer)
+{
+    _tritonNode->setMaskLayer(maskLayer);
+}
+
+void
+TritonLayer::addedToMap(const osgEarth::Map* map)
+{    
+    if (options().maskLayer().isSet())
+    {
+        // listen for the mask layer.
+        _layerListener.listen(map, options().maskLayer().get(), this, &TritonLayer::setMaskLayer);
+    }      
+}
+
+void
+TritonLayer::removedFromMap(const osgEarth::Map* map)
+{
+    if (options().maskLayer().isSet())
+    {
+        _layerListener.clear();
+        setMaskLayer(0L);
+    }
+}
+
diff --git a/src/osgEarthTriton/TritonNode b/src/osgEarthTriton/TritonNode
index 709a302..b304057 100644
--- a/src/osgEarthTriton/TritonNode
+++ b/src/osgEarthTriton/TritonNode
@@ -25,6 +25,8 @@
 #include "TritonCallback"
 #include <osgEarthUtil/Ocean>
 #include <osgEarth/MapNode>
+#include <osgEarth/ImageLayer>
+#include <osgEarth/ResourceReleaser>
 #include <osg/Drawable>
 
 namespace osgEarth { namespace Triton
@@ -37,10 +39,19 @@ namespace osgEarth { namespace Triton
     class OSGEARTHTRITON_EXPORT TritonNode : public osgEarth::Util::OceanNode
     {
     public:
-        TritonNode(
-            osgEarth::MapNode*   mapNode,
-            const TritonOptions& options,
-            Callback*            userCallback =0L);
+        /** Consruct a TritonNode */
+        TritonNode(const TritonOptions& options,
+                   Callback* userCallback =0L);
+
+        /** @deprecated - place TritonNode under MapNode, or call setMapNode
+         *  Construct a TritonNode */
+        TritonNode(osgEarth::MapNode* mapNode, const TritonOptions& options, Callback* callback =0L);
+
+        /** MapNode to use; will be discovered automatically if not set here */
+        void setMapNode(osgEarth::MapNode* mapNode);
+
+        /** Layer to use to mask the rendering of the ocean surface */
+        void setMaskLayer(const osgEarth::ImageLayer* layer);
 
     protected: // OceanNode
 
@@ -61,8 +72,13 @@ namespace osgEarth { namespace Triton
         TritonOptions               _options;
         osg::Drawable*              _drawable;
         osg::ref_ptr<osg::Uniform>  _alphaUniform;
+        osg::observer_ptr<ResourceReleaser> _releaser;
+        osg::observer_ptr<const ImageLayer> _maskLayer;
+        osg::observer_ptr<MapNode> _mapNode;
+        osg::ref_ptr<Callback> _callback;
+        bool _needsMapNode;
 
-        //Environment* _environmentWrapper;
+        void create();
     };
 
 } } // namespace osgEarth::Triton
diff --git a/src/osgEarthTriton/TritonNode.cpp b/src/osgEarthTriton/TritonNode.cpp
index 85ce60a..50fe1c8 100644
--- a/src/osgEarthTriton/TritonNode.cpp
+++ b/src/osgEarthTriton/TritonNode.cpp
@@ -16,55 +16,126 @@
  * You should have received a copy of the GNU Lesser General Public License
  * along with this program.  If not, see <http://www.gnu.org/licenses/>
  */
-
-#include <Triton.h>
 #include "TritonNode"
 #include "TritonContext"
 #include "TritonDrawable"
 #include <osgEarth/CullingUtils>
+#include <osgEarth/NodeUtils>
 
 #define LC "[TritonNode] "
 
 using namespace osgEarth::Triton;
 
-TritonNode::TritonNode(osgEarth::MapNode*   mapNode,
+
+TritonNode::TritonNode(const TritonOptions& options,
+                       Callback* callback) :
+OceanNode( options ),
+_options ( options ),
+_callback( callback ),
+_needsMapNode( true )
+{
+    // Triton requires a constant update traversal.
+    ADJUST_UPDATE_TRAV_COUNT(this, +1);
+}
+
+
+// @deprecated ctor
+TritonNode::TritonNode(osgEarth::MapNode* mapNode,
                        const TritonOptions& options,
-                       Callback*            callback) :
+                       Callback* callback) :
 OceanNode( options ),
-_options ( options )
+_options ( options ),
+_callback( callback )
+{
+    // Triton requires a constant update traversal.
+    ADJUST_UPDATE_TRAV_COUNT(this, +1);
+
+    setMapNode(mapNode);
+    _needsMapNode = (mapNode == 0L);
+}
+
+void
+TritonNode::setMaskLayer(const osgEarth::ImageLayer* maskLayer)
+{
+    _maskLayer = maskLayer;
+    create();
+}
+
+void
+TritonNode::setMapNode(osgEarth::MapNode* mapNode)
 {
+    if (!mapNode)
+    {
+        this->removeChildren(0, this->getNumChildren());
+        _drawable = 0L;
+        _TRITON = 0L;
+        setSRS(0L);
+        _needsMapNode = true;
+    }
+    else
+    {
+        _mapNode = mapNode;
+        create();
+    }
+}
+
+void
+TritonNode::create()
+{    
+    this->removeChildren(0, this->getNumChildren());
+    _drawable = 0L;
+
+    osg::ref_ptr<MapNode> mapNode;
+    if (!_mapNode.lock(mapNode))
+        return;
+
     const osgEarth::Map* map = mapNode->getMap();
     if ( map )
         setSRS( map->getSRS() );
 
-    _TRITON = new TritonContext( options );
+    // Remember the resource releaser so we can properly destroy 
+    // Triton objects in a graphics context.
+    _releaser = mapNode->getResourceReleaser();
+
+    // create an object to house Triton data and resources.
+    if (!_TRITON.valid())
+        _TRITON = new TritonContext(_options);
 
     if ( map )
         _TRITON->setSRS( map->getSRS() );
 
-    if ( callback )
-        _TRITON->setCallback( callback );
+    if ( _callback.valid() )
+        _TRITON->setCallback( _callback.get() );
 
-    _drawable = new TritonDrawable(mapNode, _TRITON);
+    TritonDrawable* drawable = new TritonDrawable(mapNode.get(), _TRITON.get());
+    _drawable = drawable;
     _alphaUniform = getOrCreateStateSet()->getOrCreateUniform("oe_ocean_alpha", osg::Uniform::FLOAT);
     _alphaUniform->set(getAlpha());
+    _drawable->setNodeMask( TRITON_OCEAN_MASK );
+    drawable->setMaskLayer(_maskLayer.get());
+    this->addChild(_drawable);
 
-    osg::Geode* geode = new osg::Geode();
-    geode->addDrawable( _drawable );
-    geode->setNodeMask( TRITON_OCEAN_MASK );
-
-    this->addChild( geode );
+    drawable->_heightCameraParent = this;
 
-    this->setNumChildrenRequiringUpdateTraversal(1);
+    //this->setNumChildrenRequiringUpdateTraversal(1);
 
     // Place in the depth-sorted bin and set a rendering order.
     // We want Triton to render after the terrain.
-    _drawable->getOrCreateStateSet()->setRenderBinDetails( options.renderBinNumber().get(), "DepthSortedBin" );
+    _drawable->getOrCreateStateSet()->setRenderBinDetails( _options.renderBinNumber().get(), "DepthSortedBin" );
 }
 
 TritonNode::~TritonNode()
 {
-    //nop
+    // submit the TRITON context to the releaser so it can shut down Triton
+    // objects in a valid graphics context.
+    if (_TRITON.valid())
+    {
+        osg::ref_ptr<ResourceReleaser> releaser;
+        if (_releaser.lock(releaser))
+        {
+            releaser->push(_TRITON.get());
+        }
+    }
 }
 
 void
@@ -92,9 +163,25 @@ TritonNode::computeBound() const
 void
 TritonNode::traverse(osg::NodeVisitor& nv)
 {
-    if ( nv.getVisitorType() == nv.UPDATE_VISITOR && _TRITON->ready() )
+    if ( nv.getVisitorType() == nv.UPDATE_VISITOR )
     {
-        _TRITON->update(nv.getFrameStamp()->getSimulationTime());
+        // Find a MapNode in the traversal path if necessary:
+        if (_needsMapNode)
+        {
+            MapNode* mapNode = osgEarth::findInNodePath<MapNode>(nv);
+            if (mapNode)
+            {
+                setMapNode(mapNode);
+                _needsMapNode = false;
+            }
+        }
+
+        // Tick Triton each frame:
+        if (_TRITON->ready())
+        {
+            _TRITON->update(nv.getFrameStamp()->getSimulationTime());
+        }
     }
+
     osgEarth::Util::OceanNode::traverse(nv);
 }
diff --git a/src/osgEarthTriton/TritonOptions b/src/osgEarthTriton/TritonOptions
index f87635c..599c1ca 100644
--- a/src/osgEarthTriton/TritonOptions
+++ b/src/osgEarthTriton/TritonOptions
@@ -66,16 +66,24 @@ namespace osgEarth { namespace Triton
         optional<int>& renderBinNumber() { return _renderBinNumber; }
         const optional<int>& renderBinNumber() const { return _renderBinNumber; }
 
+        /** Name of optional land mask layer. A land mask layer is an image layer
+          * in the Map that has the alpha channel set to 1 over land and 0 over ocean.
+          * This is used in addition to the height map to prevent drawing the ocean
+          * over land areas.
+          */
+        optional<std::string>& maskLayer() { return _maskLayerName; }
+        const optional<std::string>& maskLayer() const { return _maskLayerName; }
 
     public:
         osgEarth::Config getConfig() const {
-            osgEarth::Config conf = osgEarth::Util::OceanOptions::newConfig();
+            osgEarth::Config conf = osgEarth::Util::OceanOptions::getConfig();
             conf.addIfSet("user", _user);
             conf.addIfSet("license_code", _licenseCode);
             conf.addIfSet("resource_path", _resourcePath);
             conf.addIfSet("use_height_map", _useHeightMap);
             conf.addIfSet("height_map_size", _heightMapSize);
-            conf.addIfSet("render_bin_number",   _renderBinNumber);
+            conf.addIfSet("render_bin_number", _renderBinNumber);
+            conf.addIfSet("mask_layer", _maskLayerName);
             return conf;
         }
 
@@ -92,7 +100,8 @@ namespace osgEarth { namespace Triton
             conf.getIfSet("resource_path", _resourcePath);
             conf.getIfSet("use_height_map", _useHeightMap);
             conf.getIfSet("height_map_size", _heightMapSize);
-            conf.getIfSet("render_bin_number",   _renderBinNumber);
+            conf.getIfSet("render_bin_number", _renderBinNumber);
+            conf.getIfSet("mask_layer", _maskLayerName);
         }
 
     private:
@@ -102,6 +111,7 @@ namespace osgEarth { namespace Triton
         osgEarth::optional<bool>        _useHeightMap;
         osgEarth::optional<int>         _heightMapSize;
         osgEarth::optional<int>         _renderBinNumber;
+        osgEarth::optional<std::string> _maskLayerName;
     };
 
 } } // namespace osgEarth::Triton
diff --git a/src/osgEarthUtil/AnnotationEvents.cpp b/src/osgEarthUtil/AnnotationEvents.cpp
index 4937476..edbb71d 100644
--- a/src/osgEarthUtil/AnnotationEvents.cpp
+++ b/src/osgEarthUtil/AnnotationEvents.cpp
@@ -34,6 +34,7 @@ AnnotationEventCallback::AnnotationEventCallback( AnnotationEventHandler* handle
 _hoverEnabled( true ),
 _mouseDown   ( false )
 {
+    OE_DEPRECATED(AnnotationEventCallback, RTTPicker);
     if ( handler )
         addHandler( handler );
 }
diff --git a/src/osgEarthUtil/AtlasBuilder.cpp b/src/osgEarthUtil/AtlasBuilder.cpp
index 0ab4388..d07341d 100644
--- a/src/osgEarthUtil/AtlasBuilder.cpp
+++ b/src/osgEarthUtil/AtlasBuilder.cpp
@@ -118,7 +118,7 @@ AtlasBuilder::build(const ResourceLibrary* inputLib,
     // clone the Resource library so we can re-write the URIs and add 
     // texture matrix information.
     out._lib = new ResourceLibrary( inputLib->getConfig() );
-    out._lib->initialize( _options );
+    out._lib->initialize( _options.get() );
     out._lib->uri().unset();
 
     // store a mapping from atlasbuilder source to skin.
@@ -138,7 +138,7 @@ AtlasBuilder::build(const ResourceLibrary* inputLib,
             continue;
         }
 
-        osg::ref_ptr<osg::Image> image = skin->createImage( _options );
+        osg::ref_ptr<osg::Image> image = skin->createImage( _options.get() );
         if ( image.valid() )
         {
             OE_INFO << LC << "Loaded skin file: " << skin->imageURI()->full() << std::endl;
@@ -153,9 +153,9 @@ AtlasBuilder::build(const ResourceLibrary* inputLib,
             }
 
             // normalize to RGBA8
-            image = ImageUtils::convertToRGBA8(image);
+            image = ImageUtils::convertToRGBA8(image.get());
 
-            maintab->addSource( image );
+            maintab->addSource( image.get() );
 
             // for each aux pattern, either load and resize the aux image or create
             // an empty placeholder.
@@ -171,21 +171,20 @@ AtlasBuilder::build(const ResourceLibrary* inputLib,
                 std::string auxFile = base + "_" + pattern + "." + ext;
 
                 // read in the auxiliary image:
-                osg::ref_ptr<osg::Image> auxImage;
-                auxImage = osgDB::readImageFile( auxFile, _options.get() );
+                osg::ref_ptr<osg::Image> auxImage = osgDB::readRefImageFile( auxFile, _options.get() );
 
                 // if that didn't work, try alternate extensions:
                 const char* alternateExtensions[3] = {"png", "jpg", "osgb"};
                 for(int b = 0; b < 3 && !auxImage.valid(); ++b)
                 {
                     auxFile = base + "_" + pattern + "." + alternateExtensions[b];
-                    auxImage = osgDB::readImageFile( auxFile, _options.get() );
+                    auxImage = osgDB::readRefImageFile( auxFile, _options.get() );
                 }
 
                 if ( auxImage.valid() )
                 {
                     OE_INFO << LC << "  Found aux file: " << auxFile << std::endl;
-                    auxImage = ImageUtils::convertToRGBA8(auxImage);
+                    auxImage = ImageUtils::convertToRGBA8(auxImage.get());
                 }
                 else
                 {
@@ -205,7 +204,7 @@ AtlasBuilder::build(const ResourceLibrary* inputLib,
                     OE_INFO << "  ...resized " << auxFile << " to match atlas size" << std::endl;
                 }
 
-                if ( !ImageUtils::sameFormat(image, auxImage.get()) ) 
+                if ( !ImageUtils::sameFormat(image.get(), auxImage.get()) ) 
                 {
                     auxImage = ImageUtils::convertToRGBA8(auxImage.get());
                 }
diff --git a/src/osgEarthUtil/AutoClipPlaneHandler.cpp b/src/osgEarthUtil/AutoClipPlaneHandler.cpp
index 911c821..96eadc9 100644
--- a/src/osgEarthUtil/AutoClipPlaneHandler.cpp
+++ b/src/osgEarthUtil/AutoClipPlaneHandler.cpp
@@ -234,14 +234,10 @@ AutoClipPlaneCullCallback::operator()( osg::Node* node, osg::NodeVisitor* nv )
                     c->_maxFar = DBL_MAX;
                 }
 
-                // get the height-above-ellipsoid. If we need to be more accurate, we can use 
-                // ElevationQuery in the future..
-                //osg::Vec3d loc;
                 GeoPoint loc;
                 if ( map )
                 {
                     loc.fromWorld( map->getSRS(), eye );
-                    //map->worldPointToMapPoint( eye, loc );
                 }
                 else
                 {
diff --git a/src/osgEarthUtil/CMakeLists.txt b/src/osgEarthUtil/CMakeLists.txt
index 8fe8c01..07321a2 100644
--- a/src/osgEarthUtil/CMakeLists.txt
+++ b/src/osgEarthUtil/CMakeLists.txt
@@ -9,9 +9,9 @@ SET(LIB_NAME osgEarthUtil)
 SET(HEADER_PATH ${OSGEARTH_SOURCE_DIR}/include/${LIB_NAME})
 
 SET(HEADERS_ROOT
-	ActivityMonitorTool
+    ActivityMonitorTool
     AnnotationEvents
-    AutoClipPlaneHandler	
+    AutoClipPlaneHandler
     ArcGIS
     AtlasBuilder
     Common
@@ -20,35 +20,33 @@ SET(HEADERS_ROOT
     ClampCallback
     DataScanner
     EarthManipulator
-	Ephemeris
+    Ephemeris
     ExampleResources
     Export
-    FeatureQueryTool
+    FlatteningLayer
     Fog
     Formatter
+    FractalElevationLayer
+    GARSGraticule
     GeodeticGraticule
-    GraticuleExtension
-    GraticuleNode
-    GraticuleOptions
-    GraticuleTerrainEffect
     HTM
     LatLongFormatter
     LineOfSight
     LinearLineOfSight
     LODBlending
-	LogarithmicDepthBuffer
+    LogarithmicDepthBuffer
     MeasureTool
     MGRSFormatter
     MGRSGraticule
     MouseCoordsTool
-    ObjectLocator
+    MultiElevationLayer
     Ocean
     PolyhedralLineOfSight
     RadialLineOfSight
     RTTPicker
     Shaders
-	Shadowing
-	SimplexNoise
+    Shadowing
+    SimpleOceanLayer
     SimplePager
     Sky
     SpatialData
@@ -61,8 +59,11 @@ SET(HEADERS_ROOT
     TMS
     TMSBackFiller
     TMSPackager
+    TopologyGraph
     UTMGraticule
+    UTMLabelingEngine
     VerticalScale
+    ViewFitter
     WFS
     WMS
 )
@@ -81,22 +82,24 @@ set(TARGET_GLSL
     Graticule.vert.glsl
     Graticule.frag.glsl
     LogDepthBuffer.vert.glsl
-    LogDepthBuffer.VertOnly.vert.glsl    
+    LogDepthBuffer.VertOnly.vert.glsl
     LogDepthBuffer.frag.glsl
     Shadowing.vert.glsl
-    Shadowing.frag.glsl)
+    Shadowing.frag.glsl
+    SimpleOceanLayer.vert.glsl
+    SimpleOceanLayer.frag.glsl)
 
 set(TARGET_IN
     Shaders.cpp.in)
 
-configure_shaders(            
+configure_shaders(
     Shaders.cpp.in
     ${SHADERS_CPP}
     ${TARGET_GLSL} )
 
 
 SET(SOURCES_ROOT
-	ActivityMonitorTool.cpp
+    ActivityMonitorTool.cpp
     AnnotationEvents.cpp
     ArcGIS.cpp
     AtlasBuilder.cpp
@@ -106,30 +109,29 @@ SET(SOURCES_ROOT
     ContourMap.cpp
     DataScanner.cpp
     EarthManipulator.cpp
-	Ephemeris.cpp
+    Ephemeris.cpp
     ExampleResources.cpp
-    FeatureQueryTool.cpp
+    FlatteningLayer.cpp
     Fog.cpp
+    FractalElevationLayer.cpp
+    GARSGraticule.cpp
     GeodeticGraticule.cpp
-    GraticuleExtension.cpp
-    GraticuleTerrainEffect.cpp
-    GraticuleNode.cpp
     HTM.cpp
     LatLongFormatter.cpp
     LinearLineOfSight.cpp
-	LogarithmicDepthBuffer.cpp
+    LogarithmicDepthBuffer.cpp
     LODBlending.cpp
     MeasureTool.cpp
     MGRSFormatter.cpp
     MGRSGraticule.cpp
     MouseCoordsTool.cpp
-    ObjectLocator.cpp
+    MultiElevationLayer.cpp
     Ocean.cpp
     PolyhedralLineOfSight.cpp
     RadialLineOfSight.cpp
     RTTPicker.cpp
-	Shadowing.cpp
-	SimplexNoise.cpp
+    Shadowing.cpp
+    SimpleOceanLayer.cpp
     SimplePager.cpp
     SpatialData.cpp
     Sky.cpp
@@ -141,8 +143,11 @@ SET(SOURCES_ROOT
     TMS.cpp
     TMSBackFiller.cpp
     TMSPackager.cpp
+    TopologyGraph.cpp
     UTMGraticule.cpp
+    UTMLabelingEngine.cpp
     VerticalScale.cpp
+    ViewFitter.cpp
     WFS.cpp
     WMS.cpp
     ${SHADERS_CPP}
@@ -182,7 +187,7 @@ SET(LIB_PUBLIC_HEADERS
     ${HEADERS_COLORFILTER}
 )
 
-SET(LIB_COMMON_FILES 
+SET(LIB_COMMON_FILES
     ${SOURCES_ROOT} ${SOURCES_COLORFILTER}
 )
 
diff --git a/src/osgEarthUtil/ContourMap b/src/osgEarthUtil/ContourMap
index aa3a1b3..324d889 100644
--- a/src/osgEarthUtil/ContourMap
+++ b/src/osgEarthUtil/ContourMap
@@ -27,6 +27,7 @@
 #include <osgEarth/ImageLayer>
 #include <osgEarth/Extension>
 #include <osg/Texture1D>
+#include <osg/Texture2D>
 #include <osg/TransferFunction>
 
 namespace osgEarth {
@@ -76,6 +77,12 @@ namespace osgEarth { namespace Util
                                            public ContourMapOptions
     {
     public:
+#if defined(OSG_GLES2_AVAILABLE) || defined(OSG_GLES3_AVAILABLE)
+        typedef osg::Texture2D TextureType;
+#else
+        typedef osg::Texture1D TextureType;
+#endif
+    
         /** construct a new effect */
         ContourMap();
         ContourMap(const ConfigOptions& co);
@@ -98,7 +105,7 @@ namespace osgEarth { namespace Util
 
         int                                   _unit;
         osg::ref_ptr<osg::TransferFunction1D> _xfer;
-        osg::ref_ptr<osg::Texture1D>          _xferTexture;
+        osg::ref_ptr<TextureType>             _xferTexture;
         osg::ref_ptr<osg::Uniform>            _xferSampler;
         osg::ref_ptr<osg::Uniform>            _xferMin;
         osg::ref_ptr<osg::Uniform>            _xferRange;
diff --git a/src/osgEarthUtil/ContourMap.cpp b/src/osgEarthUtil/ContourMap.cpp
index 66bc67a..1613aba 100644
--- a/src/osgEarthUtil/ContourMap.cpp
+++ b/src/osgEarthUtil/ContourMap.cpp
@@ -55,12 +55,17 @@ ContourMap::init()
     // uniforms we'll need:
     _xferMin     = new osg::Uniform(osg::Uniform::FLOAT,      "oe_contour_min" );
     _xferRange   = new osg::Uniform(osg::Uniform::FLOAT,      "oe_contour_range" );
+#if defined(OSG_GLES2_AVAILABLE) || defined(OSG_GLES3_AVAILABLE)
+    _xferSampler = new osg::Uniform(osg::Uniform::SAMPLER_2D, "oe_contour_xfer" );
+#else
     _xferSampler = new osg::Uniform(osg::Uniform::SAMPLER_1D, "oe_contour_xfer" );
+#endif
     _opacityUniform = new osg::Uniform(osg::Uniform::FLOAT,   "oe_contour_opacity" );
 
     // Create a 1D texture from the transfer function's image.
-    _xferTexture = new osg::Texture1D();
+    _xferTexture = new TextureType();
     _xferTexture->setResizeNonPowerOfTwoHint( false );
+    _xferTexture->setUseHardwareMipMapGeneration( false );
     _xferTexture->setFilter( osg::Texture::MIN_FILTER, osg::Texture::LINEAR );
     _xferTexture->setFilter( osg::Texture::MAG_FILTER, osg::Texture::LINEAR );
     _xferTexture->setWrap( osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE );
diff --git a/src/osgEarthUtil/ContourMap.frag.glsl b/src/osgEarthUtil/ContourMap.frag.glsl
index c9ab5bf..cb0c086 100644
--- a/src/osgEarthUtil/ContourMap.frag.glsl
+++ b/src/osgEarthUtil/ContourMap.frag.glsl
@@ -5,7 +5,7 @@ $GLSL_DEFAULT_PRECISION_FLOAT
 #pragma vp_location   fragment_coloring
 #pragma vp_order      0.2
 
-varying vec4 oe_layer_tilec;
+in vec4 oe_layer_tilec;
 uniform sampler1D oe_contour_xfer;
 uniform float oe_contour_opacity;
 uniform float oe_contour_min;
@@ -18,6 +18,6 @@ void oe_contour_fragment( inout vec4 color )
     float height = oe_terrain_getElevation(oe_layer_tilec.st);
     float height_normalized = (height-oe_contour_min)/oe_contour_range;
     float lookup = clamp( height_normalized, 0.0, 1.0 );
-    vec4 texel = texture1D( oe_contour_xfer, lookup );
+    vec4 texel = texture( oe_contour_xfer, lookup );
     color.rgb = mix(color.rgb, texel.rgb, texel.a * oe_contour_opacity);
-}
\ No newline at end of file
+}
diff --git a/src/osgEarthUtil/ContourMap.vert.glsl b/src/osgEarthUtil/ContourMap.vert.glsl
index 5fefaa7..e80711a 100644
--- a/src/osgEarthUtil/ContourMap.vert.glsl
+++ b/src/osgEarthUtil/ContourMap.vert.glsl
@@ -7,10 +7,10 @@ $GLSL_DEFAULT_PRECISION_FLOAT
 #pragma vp_location   vertex_model
 #pragma vp_order      0.5
 
-varying vec4 oe_layer_tilec;
+out vec4 oe_layer_tilec;
+out float oe_contour_lookup;
 uniform float oe_contour_min;
 uniform float oe_contour_range;
-varying float oe_contour_lookup;
 
 float oe_terrain_getElevation(in vec2 uv);
 
@@ -19,4 +19,4 @@ void oe_contour_vertex(inout vec4 VertexModel)
     float height = oe_terrain_getElevation(oe_layer_tilec.st);
     float height_normalized = (height-oe_contour_min)/oe_contour_range;
     oe_contour_lookup = clamp( height_normalized, 0.0, 1.0 );
-}
\ No newline at end of file
+}
diff --git a/src/osgEarthUtil/Controls b/src/osgEarthUtil/Controls
index 98d42c6..744a698 100644
--- a/src/osgEarthUtil/Controls
+++ b/src/osgEarthUtil/Controls
@@ -22,7 +22,6 @@
 #include <osgEarthUtil/Common>
 #include <osgEarth/Common>
 #include <osgEarth/Units>
-#include <osgEarth/AlphaEffect>
 #include <osgEarth/Containers>
 #include <osg/Drawable>
 #include <osg/Geode>
@@ -41,7 +40,7 @@
  * Controls are 2D interface components that automatically layout to fit their containers.
  * The support layout containers, margins and padding, alignment/justification, and docking.
  * Controls are a quick and easy way to add "HUD" components to a scene. Just create a
- * ControlSurface and add it to a View's scene. Then create and add Controls to that
+ * ControlCanvas and add it to a View's scene. Then create and add Controls to that
  * surface.
  */
 namespace osgEarth { namespace Util { namespace Controls
@@ -55,7 +54,7 @@ namespace osgEarth { namespace Util { namespace Controls
     // internal state class
     struct ControlContext
     {
-        ControlContext() : _viewContextID(~0), _frameStamp(0L), _view(0L) { }
+        ControlContext() : _view(0L), _viewContextID(~0), _frameStamp(0L) { }
         osg::View* _view;
         osg::ref_ptr<const osg::Viewport> _vp;
         unsigned int _viewContextID;
@@ -266,8 +265,8 @@ namespace osgEarth { namespace Util { namespace Controls
         bool getAbsorbEvents() const { return _absorbEvents; }
 
         /** control opacity [0..1] */
-        void setOpacity(float value) { if ( _alphaEffect.valid() ) _alphaEffect->setAlpha(value); }
-        float getOpacity() const { return _alphaEffect.valid() ? _alphaEffect->getAlpha() : 1.0f; }
+        void setOpacity(float value);
+        float getOpacity() const { return _foreColor->a(); }
 
         void addEventHandler( ControlEventHandler* handler, bool fire =false );
 
@@ -327,7 +326,6 @@ namespace osgEarth { namespace Util { namespace Controls
         bool _absorbEvents;
         osg::Geode* _geode;
         osg::ref_ptr<osg::Geometry> _geom;
-        osg::ref_ptr<AlphaEffect> _alphaEffect;
     };
 
     typedef std::vector< osg::ref_ptr<Control> > ControlVector;
diff --git a/src/osgEarthUtil/Controls.cpp b/src/osgEarthUtil/Controls.cpp
index a32cb37..03902d1 100755
--- a/src/osgEarthUtil/Controls.cpp
+++ b/src/osgEarthUtil/Controls.cpp
@@ -191,10 +191,6 @@ Control::init()
 
     _geode = new osg::Geode();
     this->addChild( _geode );
-    
-#ifdef OSG_GLES2_AVAILABLE
-    _alphaEffect = new AlphaEffect(this->getOrCreateStateSet());
-#endif
 }
 
 void
@@ -423,6 +419,12 @@ Control::parentIsVisible() const
     return visible;
 }
 
+void
+Control::setOpacity(float a) {
+    osg::Vec4f c = _foreColor.get();
+    c.a() = a;
+    setForeColor(c);
+}
 
 void
 Control::setForeColor( const osg::Vec4f& value ) {
@@ -609,13 +611,16 @@ Control::draw(const ControlContext& cx)
                 float rx = _renderPos.x() - padding().left();
                 float ry = _renderPos.y() - padding().top();
 
-                osg::Vec3Array* verts = new osg::Vec3Array(4);
+                osg::Vec3Array* verts = new osg::Vec3Array(6);
                 _geom->setVertexArray( verts );
                 (*verts)[0].set( rx, vph - ry, 0 );
                 (*verts)[1].set( rx, vph - ry - _renderSize.y(), 0 );
                 (*verts)[2].set( rx + _renderSize.x(), vph - ry - _renderSize.y(), 0 );
-                (*verts)[3].set( rx + _renderSize.x(), vph - ry, 0 );
-                _geom->addPrimitiveSet( new osg::DrawArrays( GL_QUADS, 0, 4 ) );
+                (*verts)[3].set( (*verts)[2] );
+                (*verts)[4].set( rx + _renderSize.x(), vph - ry, 0 );
+                (*verts)[5].set( (*verts)[0] );
+                
+               _geom->addPrimitiveSet( new osg::DrawArrays( GL_TRIANGLES, 0, 6 ) );
 
                 osg::Vec4Array* colors = new osg::Vec4Array(1);
                 (*colors)[0] = _active && _activeColor.isSet() ? _activeColor.value() : _backColor.value();
@@ -650,7 +655,7 @@ Control::draw(const ControlContext& cx)
                 getGeode()->addDrawable( geom );
             }
         }
-
+        
         _dirty = false;
     }
 }
@@ -703,7 +708,13 @@ namespace
     {
         LabelText() : osgText::Text() { setDataVariance(osg::Object::DYNAMIC); }
         const osg::BoundingBox& getTextBB() const { return _textBB; }
-        const osg::Matrix& getATMatrix(int contextID) const { return _autoTransformCache[contextID]._matrix; }
+        const osg::Matrix& getATMatrix(int contextID) const { 
+        #if OSG_MIN_VERSION_REQUIRED(3,5,6)
+                return _matrix;
+        #else
+                return _autoTransformCache[contextID]._matrix;
+        #endif
+        }
     };
 
     // writes a value to a label
@@ -1095,7 +1106,7 @@ ImageControl::draw( const ControlContext& cx )
         float ry = osg::round( _renderPos.y() );
         float vph = cx._vp->height();
 
-        osg::Vec3Array* verts = new osg::Vec3Array(4);
+        osg::Vec3Array* verts = new osg::Vec3Array(6);
         g->setVertexArray( verts );
 
         if ( _rotation.as(Units::RADIANS) != 0.0f || _fixSizeForRot == true )
@@ -1109,17 +1120,21 @@ ImageControl::draw( const ControlContext& cx )
             rot( rx, vph-ry, rc, ra, (*verts)[0] );
             rot( rx, vph-ry-_image->t(), rc, ra, (*verts)[1] );
             rot( rx+_image->s(), vph-ry-_image->t(), rc, ra, (*verts)[2] );
-            rot( rx+_image->s(), vph-ry, rc, ra, (*verts)[3] );
+            (*verts)[3].set( (*verts)[2] );
+            rot( rx+_image->s(), vph-ry, rc, ra, (*verts)[4] );
+            (*verts)[5].set( (*verts)[0] );
         }
         else
         {
             (*verts)[0].set( rx, vph - ry, 0 );
             (*verts)[1].set( rx, vph - ry - _renderSize.y(), 0 );
             (*verts)[2].set( rx + _renderSize.x(), vph - ry - _renderSize.y(), 0 );
-            (*verts)[3].set( rx + _renderSize.x(), vph - ry, 0 );
+            (*verts)[3].set( (*verts)[2] );
+            (*verts)[4].set( rx + _renderSize.x(), vph - ry, 0 );
+            (*verts)[5].set( (*verts)[0] );
         }
 
-        g->addPrimitiveSet( new osg::DrawArrays( GL_QUADS, 0, 4 ) );
+        g->addPrimitiveSet( new osg::DrawArrays( GL_TRIANGLES, 0, 6 ) );
 
         osg::Vec4Array* c = new osg::Vec4Array(1);
         (*c)[0] = osg::Vec4f(1,1,1,1);
@@ -1128,14 +1143,16 @@ ImageControl::draw( const ControlContext& cx )
 
         bool flip = _image->getOrigin()==osg::Image::TOP_LEFT;
 
-        osg::Vec2Array* t = new osg::Vec2Array(4);
+        osg::Vec2Array* t = new osg::Vec2Array(6);
 
 #ifdef IMAGECONTROL_TEXRECT
 
         (*t)[0].set( 0, flip? 0: _image->t()-1 );
         (*t)[1].set( 0, flip? _image->t()-1: 0 );
         (*t)[2].set( _image->s()-1, flip? _image->t()-1: 0 );
-        (*t)[3].set( _image->s()-1, flip? 0: _image->t()-1 );
+        (*t)[3].set( (*t)[2]);
+        (*t)[4].set( _image->s()-1, flip? 0: _image->t()-1 );
+        (*t)[5].set( (*t)[0] );
         osg::TextureRectangle* tex = new osg::TextureRectangle( _image.get() );
 
 #else
@@ -1143,7 +1160,9 @@ ImageControl::draw( const ControlContext& cx )
         (*t)[0].set( 0, flip? 0 : 1 );
         (*t)[1].set( 0, flip? 1 : 0 );
         (*t)[2].set( 1, flip? 1 : 0 );
-        (*t)[3].set( 1, flip? 0 : 1 );
+        (*t)[3].set( (*t)[2]);
+        (*t)[4].set( 1, flip? 0 : 1 );
+        (*t)[5].set( (*t)[0] );
         osg::Texture2D* tex = new osg::Texture2D( _image.get() );
 #endif
 
@@ -1155,9 +1174,9 @@ ImageControl::draw( const ControlContext& cx )
         tex->setFilter( osg::Texture::MAG_FILTER, osg::Texture::LINEAR );
         g->getOrCreateStateSet()->setTextureAttributeAndModes( 0, tex, osg::StateAttribute::ON );
 
-        osg::TexEnv* texenv = new osg::TexEnv( osg::TexEnv::MODULATE );
+        /*osg::TexEnv* texenv = new osg::TexEnv( osg::TexEnv::MODULATE );
         g->getStateSet()->setTextureAttributeAndModes( 0, texenv, osg::StateAttribute::ON );
-
+         */
         getGeode()->addDrawable( g );
 
         _dirty = false;
@@ -1263,7 +1282,7 @@ HSliderControl::draw( const ControlContext& cx )
         {
             float vph = cx._vp->height();
 
-            osg::Vec3Array* verts = new osg::Vec3Array(8);
+            osg::Vec3Array* verts = new osg::Vec3Array(10);
             g->setVertexArray( verts );
 
             (*verts)[0].set( rx, vph - ry, 0 );
@@ -1277,8 +1296,11 @@ HSliderControl::draw( const ControlContext& cx )
             (*verts)[4].set( hx-4, vph - ry + 3, 0 );
             (*verts)[5].set( hx-4, vph - (ry + rh + 3), 0 );
             (*verts)[6].set( hx+4, vph - (ry + rh + 3), 0 );
-            (*verts)[7].set( hx+4, vph - ry + 3, 0 );
-            g->addPrimitiveSet( new osg::DrawArrays( GL_QUADS, 4, 4 ) );
+            (*verts)[7].set( (*verts)[6] );
+            (*verts)[8].set( hx+4, vph - ry + 3, 0 );
+            (*verts)[9].set( (*verts)[4] );
+            
+            g->addPrimitiveSet( new osg::DrawArrays( GL_TRIANGLES, 4, 6) );
 
             osg::Vec4Array* c = new osg::Vec4Array(1);
             (*c)[0] = *foreColor();
@@ -1668,11 +1690,11 @@ void Container::setVisible(bool visibility)
         getChildren(out); 
         for (int i = 0; i < (int) out.size(); i++) 
         { 
-                Container* container = dynamic_cast<Container*>( out.at(i) ); 
+                Container* container = dynamic_cast<Container*>( out[i] ); 
                 if (container) { 
                         container->setVisible(visibility); 
                 } else { 
-                        out.at(i)->setVisible(visibility); 
+                        out[i]->setVisible(visibility); 
                 } 
         } 
 }
@@ -2292,7 +2314,7 @@ ControlCanvas::EventCallback::handleResize(osg::View* view, ControlCanvas* canva
             if ( !gc && view->getNumSlaves() > 0 )
                 gc = view->getSlave(0)._camera->getGraphicsContext();
 
-            if ( gc )
+            if ( gc && gc->getState() )
                 cx._viewContextID = gc->getState()->getContextID();
             else
                 cx._viewContextID = ~0u;
@@ -2435,9 +2457,6 @@ ControlNodeBin::setFading( bool value )
 void
 ControlNodeBin::draw( const ControlContext& context, bool newContext, int bin )
 {
-    const osg::Viewport* vp = context._vp.get();
-    osg::Vec2f surfaceSize( context._vp->width(), context._vp->height() );
-
     // we don't really need to keep this list in the object, but that prevents it from having to
     // reallocate it each time
     _taken.clear();
@@ -2722,7 +2741,7 @@ ControlCanvas::init()
     _controlNodeBin = new ControlNodeBin();
     this->addChild( _controlNodeBin->getControlGroup() );
    
-#ifndef OSG_GLES2_AVAILABLE
+#if defined(OSG_GL_FIXED_FUNCTION_AVAILABLE)
     // don't use shaders unless we have to.
     this->getOrCreateStateSet()->setAttributeAndModes(
         new osg::Program(), 
@@ -2854,6 +2873,10 @@ ControlCanvas::update(const osg::FrameStamp* frameStamp)
             control->calcPos( _context, osg::Vec2f(0,0), surfaceSize );
 
             control->draw( _context );
+
+#if !defined(OSG_GL_FIXED_FUNCTION_AVAILABLE)
+            osgEarth::Registry::shaderGenerator().run(control);
+#endif
         }
     }
 
@@ -2862,7 +2885,7 @@ ControlCanvas::update(const osg::FrameStamp* frameStamp)
         _controlNodeBin->draw( _context, _contextDirty, bin );
     }
 
-#ifdef OSG_GLES2_AVAILABLE
+#if defined(OSG_GLES2_AVAILABLE) || defined(OSG_GLES3_AVAILABLE)
     // shaderize.
     // we don't really need to rebuild shaders on every dirty; we could probably
     // just do it on add/remove controls; but that's an optimization for later
diff --git a/src/osgEarthUtil/EarthManipulator b/src/osgEarthUtil/EarthManipulator
index d56ddd5..5e3c30e 100644
--- a/src/osgEarthUtil/EarthManipulator
+++ b/src/osgEarthUtil/EarthManipulator
@@ -737,6 +737,29 @@ namespace osgEarth { namespace Util
          */
         void  setFindNodeTraversalMask( const osg::Node::NodeMask & nodeMask ) { _findNodeTraversalMask = nodeMask; }
 
+        /**
+         * Expressly set the initial vertical FOV.
+         * If the manipulator detects that the camera has switched from persective 
+         * to orthographic projection, it will use the last know VFOV of the perspective
+         * projection to match the zoom level in orthographic mode. However, if you start
+         * in orthographic mode, it doesn't have this information; you can provide it
+         * with this method.
+         */
+        void setInitialVFOV(double vfov);
+
+        /**
+         * The last detected VFOV of a perspective camera (or the initial FOV if started in ortho)
+         */
+        double getLastKnownVFOV() const { return _lastKnownVFOV; }
+
+        /**
+         * Assigns a NodeVisitor to use when OSG calls updateCamera() at the end of
+         * the update traversal. This is useful if you have a Transform subclass that
+         * overrides Transform::computeLocalToWorldMatrix and needs access to a
+         * NodeVisitor.
+         */
+        void setUpdateCameraNodeVisitor(osg::NodeVisitor* nv);
+
     public: // osgGA::CameraManipulator
 
         virtual const char* className() const { return "EarthManipulator"; }
@@ -922,7 +945,6 @@ namespace osgEarth { namespace Util
         virtual bool handlePointAction( const Action& type, float mx, float my, osg::View* view );
         virtual void handleContinuousAction( const Action& action, osg::View* view );
         virtual void handleMovementAction( const ActionType& type, double dx, double dy, osg::View* view );
-        //virtual bool handleMultiTouchAction( const Action& action, const TouchEvent& te, osg::View* view );
 
         /**
          * Gets the viewpoint of the TetherNode.
@@ -1035,7 +1057,7 @@ namespace osgEarth { namespace Util
         EventType _last_event;
         double    _time_s_last_event;
 
-        double _vfov;
+        double _lastKnownVFOV;
 
         /** updates a camera to switch between prospective and ortho. */
         void updateProjection( osg::Camera* eventCamera );
@@ -1052,6 +1074,10 @@ namespace osgEarth { namespace Util
 
         osg::ref_ptr<TetherCallback> _tetherCallback;
 
+        bool _userWillCallUpdateCamera;
+
+        osg::observer_ptr<osg::NodeVisitor> _updateCameraNodeVisitor;
+
         void collisionDetect();
 
         void ctor_init();
diff --git a/src/osgEarthUtil/EarthManipulator.cpp b/src/osgEarthUtil/EarthManipulator.cpp
index b4c8f95..49a2215 100644
--- a/src/osgEarthUtil/EarthManipulator.cpp
+++ b/src/osgEarthUtil/EarthManipulator.cpp
@@ -27,7 +27,7 @@
 #include <osgUtil/LineSegmentIntersector>
 #include <osgViewer/View>
 #include <iomanip>
-#include <osgEarth/DPLineSegmentIntersector>
+#include <osgUtil/LineSegmentIntersector>
 
 #include <osg/io_utils>
 
@@ -103,6 +103,42 @@ namespace
         if( input > osg::PI ) input -= osg::PI*2.0;
         return input;
     }
+
+    // This replaces OSG's osg::computeLocalToWorld() function with one that
+    // passes your own NodeVisitor to the Transform::computeLocalToWorldMatrix()
+    // method. (We cannot subclass OSG's visitor because it's private.) This
+    // exists for users that have custom Transform subclasses that override
+    // Transform::computeLocalToWorldMatrix and need access to the NodeVisitor.
+    struct ComputeLocalToWorld : osg::NodeVisitor
+    {
+        osg::Matrix _matrix;
+        osg::NodeVisitor* _nv;
+        ComputeLocalToWorld(osg::NodeVisitor* nv) : _nv(nv) { }
+        void accumulate(const osg::NodePath& path)
+        {
+            if (path.empty()) return;
+            unsigned j = path.size();
+            for (osg::NodePath::const_reverse_iterator i = path.rbegin();
+                i != path.rend();
+                ++i, --j)
+            {
+                const osg::Camera* cam = dynamic_cast<const osg::Camera*>(*i);
+                if (cam)
+                {
+                    if( cam->getReferenceFrame() != osg::Transform::RELATIVE_RF || cam->getParents().empty())
+                        break;
+                }
+            }
+            for (; j<path.size(); ++j)
+            {
+                const_cast<osg::Node*>(path[j])->accept(*this);
+            }
+        }
+        void apply(osg::Transform& transform)
+        {
+            transform.computeLocalToWorldMatrix(_matrix, _nv);
+        }
+    };
 }
 
 
@@ -114,12 +150,12 @@ namespace
     struct ManipTerrainCallback : public TerrainCallback
     {
         ManipTerrainCallback(EarthManipulator* manip) : _manip(manip) { }
-        void onTileAdded(const TileKey& key, osg::Node* tile, TerrainCallbackContext& context)
+        void onTileAdded(const TileKey& key, osg::Node* graph, TerrainCallbackContext& context)
         {
             osg::ref_ptr<EarthManipulator> safe;
             if ( _manip.lock(safe) )
             {
-                safe->handleTileAdded(key, tile, context);
+                safe->handleTileAdded(key, graph, context);
             }
         }
         osg::observer_ptr<EarthManipulator> _manip;
@@ -512,6 +548,8 @@ EarthManipulator::ctor_init()
     _setVPAccel2 = 0;
     _lastTetherMode = TETHER_CENTER;
     _homeViewpointDuration = 0;
+    _lastKnownVFOV = 30.0;
+    _userWillCallUpdateCamera = false;
 }
 
 EarthManipulator::EarthManipulator() :
@@ -554,7 +592,7 @@ EarthManipulator::~EarthManipulator()
     osg::ref_ptr<MapNode> mapNode = _mapNode;
     if (mapNode.valid() && _terrainCallback && mapNode->getTerrain())
     {
-        mapNode->getTerrain()->removeTerrainCallback( _terrainCallback );
+        mapNode->getTerrain()->removeTerrainCallback( _terrainCallback.get() );
     }
 }
 
@@ -672,7 +710,6 @@ EarthManipulator::reinitialize()
     _setVP1.unset();
     _lastPointOnEarth.set(0.0, 0.0, 0.0);
     _setVPArcHeight = 0.0;
-    _vfov = 30.0;
 }
 
 
@@ -699,7 +736,7 @@ EarthManipulator::established()
     }
     _terrainCallback = new ManipTerrainCallback( this );
     if (_mapNode->getTerrain())
-        _mapNode->getTerrain()->addTerrainCallback( _terrainCallback );
+        _mapNode->getTerrain()->addTerrainCallback( _terrainCallback.get() );
 
     // Cache the SRS.
     _srs = _mapNode->getMapSRS();
@@ -725,9 +762,10 @@ EarthManipulator::established()
         else
         {
             Viewpoint vp;
-            vp.focalPoint() = GeoPoint(_srs.get(), safeNode->getBound().center(), ALTMODE_ABSOLUTE);
+            const Profile* profile = _mapNode->getMap()->getProfile();
+            vp.focalPoint() = GeoPoint(_srs.get(), profile->getExtent().getCentroid(), ALTMODE_ABSOLUTE);
             vp.heading()->set( 0.0, Units::DEGREES );
-            vp.pitch()->set( -89.0, Units::DEGREES );
+            vp.pitch()->set( -90.0, Units::DEGREES );
             vp.range()->set( safeNode->getBound().radius()*2.0, Units::METERS );
             vp.positionOffset()->set(0,0,0);
             setHomeViewpoint( vp );
@@ -751,7 +789,7 @@ EarthManipulator::established()
 
 
 void
-EarthManipulator::handleTileAdded(const TileKey& key, osg::Node* tile, TerrainCallbackContext& context)
+EarthManipulator::handleTileAdded(const TileKey& key, osg::Node* graph, TerrainCallbackContext& context)
 {
     // Only do collision avoidance if it's enabled, we're not tethering and
     // we're not in the middle of setting a viewpoint.
@@ -1073,7 +1111,7 @@ EarthManipulator::setViewpointFrame(double time_s)
         osg::Vec3d startWorld;
         osg::ref_ptr<osg::Node> startNode;
         if ( _setVP0->getNode(startNode) )
-            startWorld = computeWorld(startNode);
+            startWorld = computeWorld(startNode.get());
         else
             _setVP0->focalPoint()->transform( _srs.get() ).toWorld(startWorld);
 
@@ -1081,7 +1119,7 @@ EarthManipulator::setViewpointFrame(double time_s)
         osg::Vec3d endWorld;
         osg::ref_ptr<osg::Node> endNode;
         if ( _setVP1->getNode(endNode) )
-            endWorld = computeWorld(endNode);
+            endWorld = computeWorld(endNode.get());
         else
             _setVP1->focalPoint()->transform( _srs.get() ).toWorld(endWorld);
 
@@ -1348,7 +1386,7 @@ EarthManipulator::intersect(const osg::Vec3d& start, const osg::Vec3d& end, osg:
     {
 		osg::ref_ptr<osgUtil::LineSegmentIntersector> lsi = NULL;
 
-		lsi = new osgEarth::DPLineSegmentIntersector(start,end);
+		lsi = new osgUtil::LineSegmentIntersector(start,end);
 
         osgUtil::IntersectionVisitor iv(lsi.get());
         iv.setTraversalMask(_intersectTraversalMask);
@@ -1381,7 +1419,7 @@ EarthManipulator::intersectLookVector(osg::Vec3d& out_eye,
         osg::Vec3d look = out_target-out_eye;
 
 		osg::ref_ptr<osgUtil::LineSegmentIntersector> lsi =
-		    new osgEarth::DPLineSegmentIntersector(out_eye, out_eye+look*1e8);
+		    new osgUtil::LineSegmentIntersector(out_eye, out_eye+look*1e8);
 
         lsi->setIntersectionLimit(lsi->LIMIT_NEAREST);
 
@@ -1535,7 +1573,7 @@ EarthManipulator::updateProjection(osg::Camera* eventCamera)
                 double vfov, ar, zn, zf;
                 if (eventCamera->getProjectionMatrixAsPerspective(vfov, ar, zn, zf))
                 {
-                    _vfov = vfov;
+                    _lastKnownVFOV = vfov;
                 }
             }
 
@@ -1545,7 +1583,7 @@ EarthManipulator::updateProjection(osg::Camera* eventCamera)
             {
                 // need to update the ortho projection matrix to reflect the camera distance.
                 double ar = vp->width()/vp->height();
-                double y = _distance * tan(0.5*osg::DegreesToRadians(_vfov));
+                double y = _distance * tan(0.5*osg::DegreesToRadians(_lastKnownVFOV));
                 double x = y * ar;
 
 #if 0 // TODO: derive the pixel offsets and re-instate them.
@@ -1569,7 +1607,7 @@ EarthManipulator::updateProjection(osg::Camera* eventCamera)
 
                 OE_DEBUG << "ORTHO: "
                     << "ar = " << ar << ", width=" << vp->width() << ", height=" << vp->height()
-                    << ", dist = " << _distance << ", vfov=" << _vfov
+                    << ", dist = " << _distance << ", vfov=" << _lastKnownVFOV
                     << ", X = " << x << ", Y = " << y
                     << std::endl;
             }
@@ -1587,7 +1625,7 @@ EarthManipulator::handle(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapt
     if ( !established() )
         return false;
 
-    // make sure the camera callback is up to date:
+    // make sure the camera projection is up to date:
     osg::View* view = aa.asView();
     updateProjection( view->getCamera() );
 
@@ -1938,7 +1976,12 @@ EarthManipulator::updateTether()
         if ( nodePaths.empty() )
             return;
 
-        L2W = osg::computeLocalToWorld( nodePaths[0] );
+        osg::ref_ptr<osg::NodeVisitor> nv;
+        _updateCameraNodeVisitor.lock(nv);
+        ComputeLocalToWorld computeL2W(nv.get());
+        computeL2W.accumulate(nodePaths[0]);
+        L2W = computeL2W._matrix;
+
         if ( !L2W.valid() )
             return;
 
@@ -2319,17 +2362,20 @@ EarthManipulator::getInverseMatrix() const
 void
 EarthManipulator::updateCamera(osg::Camera& camera)
 {
-    // update the camera to reflect the current tether node
-    if ( isTethering() )
+    if (isTethering())
     {
         updateTether();
     }
-
-    // then update the camera as usual.
     osgGA::CameraManipulator::updateCamera(camera);
 }
 
 void
+EarthManipulator::setUpdateCameraNodeVisitor(osg::NodeVisitor* nv)
+{
+    _updateCameraNodeVisitor = nv;
+}
+
+void
 EarthManipulator::setByLookAt(const osg::Vec3d& eye,const osg::Vec3d& center,const osg::Vec3d& up)
 {
     osg::ref_ptr<osg::Node> safeNode;
@@ -2428,7 +2474,6 @@ EarthManipulator::recalculateCenter( const osg::CoordinateFrame& frame )
         osg::Vec3d ip1;
         osg::Vec3d ip2;
         osg::Vec3d normal;
-        // extend coordonate to fall on the edge of the boundingbox see http://www.osgearth.org/ticket/113
         bool hit_ip1 = intersect(_center - up * ilen * 0.1, _center + up * ilen, ip1, normal);
         bool hit_ip2 = intersect(_center + up * ilen * 0.1, _center - up * ilen, ip2, normal);
         if (hit_ip1)
@@ -2636,6 +2681,12 @@ EarthManipulator::setDistance( double distance )
 }
 
 void
+EarthManipulator::setInitialVFOV(double vfov)
+{
+    _lastKnownVFOV = vfov;
+}
+
+void
 EarthManipulator::dumpActionInfo( const EarthManipulator::Action& action, osg::NotifySeverity level ) const
 {
     osgEarth::notify(level) << "action: " << s_actionNames[action._type] << "; options: ";
diff --git a/src/osgEarthUtil/ExampleResources.cpp b/src/osgEarthUtil/ExampleResources.cpp
index 9763759..1d75a20 100644
--- a/src/osgEarthUtil/ExampleResources.cpp
+++ b/src/osgEarthUtil/ExampleResources.cpp
@@ -41,6 +41,8 @@
 #include <osgEarthAnnotation/AnnotationRegistry>
 #include <osgEarth/ScreenSpaceLayout>
 #include <osgEarth/TerrainEngineNode>
+#include <osgEarth/NodeUtils>
+#include <osgEarth/FileUtils>
 
 #include <osgEarth/XmlUtils>
 #include <osgEarth/StringUtils>
@@ -268,7 +270,7 @@ MapNodeHelper::load(osg::ArgumentParser&  args,
     myReadOptions->setPluginStringData("osgEarth.defaultOptions", defMNO.getConfig().toJSON());
 
     // read in the Earth file:
-    osg::Node* node = osgDB::readNodeFiles(args, myReadOptions.get());
+    osg::ref_ptr<osg::Node> node = osgDB::readNodeFiles(args, myReadOptions.get());
 
     osg::ref_ptr<MapNode> mapNode;
     if ( !node )
@@ -285,7 +287,7 @@ MapNodeHelper::load(osg::ArgumentParser&  args,
     }
     else
     {
-        mapNode = MapNode::get(node);
+        mapNode = MapNode::get(node.get());
         if ( !mapNode.valid() )
         {
             OE_WARN << LC << "Loaded scene graph does not contain a MapNode - aborting" << std::endl;
@@ -482,6 +484,14 @@ MapNodeHelper::parse(MapNode*             mapNode,
     // Configure for an ortho camera:
     if ( args.read("--ortho") )
     {
+        EarthManipulator* em = dynamic_cast<EarthManipulator*>(view->getCameraManipulator());
+        if (em)
+        {
+            double V, A, N, F;
+            view->getCamera()->getProjectionMatrixAsPerspective(V, A, N, F);
+            em->setInitialVFOV( V );
+        }
+
         view->getCamera()->setProjectionMatrixAsOrtho(-1, 1, -1, 1, 0, 1);
     }
 
@@ -538,7 +548,7 @@ MapNodeHelper::parse(MapNode*             mapNode,
             mapNode->getMap()->beginUpdate();
             for( ImageLayerVector::iterator i = imageLayers.begin(); i != imageLayers.end(); ++i )
             {
-                mapNode->getMap()->addImageLayer( i->get() );
+                mapNode->getMap()->addLayer( i->get() );
             }
             mapNode->getMap()->endUpdate();
         }
@@ -552,17 +562,6 @@ MapNodeHelper::parse(MapNode*             mapNode,
         mapNode->getTerrainEngine()->addEffect( new VerticalScale(vertScaleConf) );
     }
 
-    // Install a contour map effect.
-    if (args.read("--contourmap"))
-    {
-        mapNode->addExtension(Extension::create("contourmap", ConfigOptions()));
-
-        // with the cmdline switch, hids all the image layer so we can see the contour map.
-        for (unsigned i = 0; i < mapNode->getMap()->getNumImageLayers(); ++i) {
-            mapNode->getMap()->getImageLayerAt(i)->setVisible(false);
-        }
-    }
-
     // Generic named value uniform with min/max.
     VBox* uniformBox = 0L;
     while( args.find( "--uniform" ) >= 0 )
@@ -606,7 +605,8 @@ MapNodeHelper::parse(MapNode*             mapNode,
     // Simple sky model:
     if (args.read("--sky"))
     {
-        mapNode->addExtension(Extension::create("sky_simple", ConfigOptions()) );
+        std::string ext = mapNode->getMap()->isGeocentric() ? "sky_simple" : "sky_gl";
+        mapNode->addExtension(Extension::create(ext, ConfigOptions()) );
     }
 
     // Simple ocean model:
@@ -655,11 +655,10 @@ MapNodeHelper::parse(MapNode*             mapNode,
             caster->setTextureImageUnit( unit );
             caster->setLight( view->getLight() );
             caster->getShadowCastingGroup()->addChild( mapNode->getModelLayerGroup() );
-            //insertParent(caster, mapNode);
-            //root = findTopOfGraph(caster)->asGroup();
+            caster->getShadowCastingGroup()->addChild(mapNode->getTerrainEngine());
             if ( mapNode->getNumParents() > 0 )
             {
-                insertGroup(caster, mapNode->getParent(0));
+                osgEarth::insertGroup(caster, mapNode->getParent(0));
             }
             else
             {
@@ -802,7 +801,7 @@ ui::Control* SkyControlFactory::create(SkyNode* sky)
         skyYearSlider->addEventHandler( new SkyYearSlider(sky, yearLabel) );
 
         ++r;
-        grid->setControl(0, r, new ui::LabelControl("Min.Ambient: ", 16) );
+        grid->setControl(0, r, new ui::LabelControl("Ambient Light: ", 16) );
         ui::HSliderControl* ambient = grid->setControl(1, r, new ui::HSliderControl(0.0f, 1.0f, sky->getSunLight()->getAmbient().r()));
         ambient->addEventHandler( new AmbientBrightnessHandler(sky) );
         grid->setControl(2, r, new ui::LabelControl(ambient) );
diff --git a/src/osgEarthUtil/FeatureQueryTool b/src/osgEarthUtil/FeatureQueryTool
deleted file mode 100644
index b449ca9..0000000
--- a/src/osgEarthUtil/FeatureQueryTool
+++ /dev/null
@@ -1,57 +0,0 @@
-/* -*-c++-*- */
-/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2016 Pelican Mapping
- * http://osgearth.org
- *
- * osgEarth is free software; you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>
- */
-
-#ifndef OSGEARTHUTIL_FEATURE_QUERY_TOOL_H
-#define OSGEARTHUTIL_FEATURE_QUERY_TOOL_H 1
-
-#include <osgEarthUtil/Common>
-#include <osgEarth/MapNode>
-#include <osgEarth/MapNodeObserver>
-#include <osgEarthUtil/RTTPicker>
-#include <osgEarthFeatures/Feature>
-#include <osgEarthFeatures/FeatureSource>
-#include <osgEarthFeatures/FeatureSourceIndexNode>
-#include <osgGA/GUIEventHandler>
-
-namespace osgEarth { namespace Util
-{
-    using namespace osgEarth;
-    using namespace osgEarth::Features;
-
-    /**
-     * Tool that let you query the map for Features generated from a FeatureSource.
-     */
-    class OSGEARTHUTIL_EXPORT FeatureQueryTool : public RTTPicker
-    {
-    public:
-        /**
-         * Constructs a new feature query tool.
-         *
-         * @param callbackToAdd
-         *      (optional) convenience; calls addCallback with this parameter
-         */
-        FeatureQueryTool();
-
-        /** dtor */
-        virtual ~FeatureQueryTool() { }
-    };
-
-} } // namespace osgEarthUtil
-
-#endif // OSGEARTHUTIL_FEATURE_QUERY_TOOL_H
diff --git a/src/osgEarthUtil/FeatureQueryTool.cpp b/src/osgEarthUtil/FeatureQueryTool.cpp
deleted file mode 100644
index 8766d3b..0000000
--- a/src/osgEarthUtil/FeatureQueryTool.cpp
+++ /dev/null
@@ -1,35 +0,0 @@
-/* -*-c++-*- */
-/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2016 Pelican Mapping
- * http://osgearth.org
- *
- * osgEarth is free software; you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>
- */
-
-#include <osgEarthUtil/FeatureQueryTool>
-
-#define LC "[FeatureQueryTool] "
-
-using namespace osgEarth::Util;
-
-//#undef OE_DEBUG
-//#define OE_DEBUG OE_INFO
-
-//-----------------------------------------------------------------------
-
-FeatureQueryTool::FeatureQueryTool() :
-RTTPicker()
-{
-    //nop
-}
diff --git a/src/osgEarthUtil/FlatteningLayer b/src/osgEarthUtil/FlatteningLayer
new file mode 100644
index 0000000..786bc36
--- /dev/null
+++ b/src/osgEarthUtil/FlatteningLayer
@@ -0,0 +1,217 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+ * Copyright 2016 Pelican Mapping
+ * http://osgearth.org
+ *
+ * osgEarth is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+#ifndef OSGEARTH_UTIL_FLATTENING_LAYER
+#define OSGEARTH_UTIL_FLATTENING_LAYER 1
+
+#include <osgEarthUtil/Common>
+#include <osgEarth/TileSource>
+#include <osgEarth/ElevationLayer>
+#include <osgEarth/ElevationPool>
+#include <osgEarth/LayerListener>
+#include <osgEarthFeatures/FeatureSource>
+#include <osgEarthFeatures/FeatureSourceLayer>
+#include <osgEarthFeatures/ScriptEngine>
+#include <osgEarthSymbology/StyleSheet>
+#include <osgDB/FileNameUtils>
+
+namespace osgEarth { namespace Util
+{
+    using namespace osgEarth;
+    using namespace osgEarth::Features;
+    using namespace osgEarth::Symbology;
+
+    /**
+     * Serializable options to configure a FlatteningLayer.
+     */
+    class OSGEARTHUTIL_EXPORT FlatteningLayerOptions : public ElevationLayerOptions
+    {
+    public:
+        // constructor
+        FlatteningLayerOptions(const ConfigOptions& co = ConfigOptions()) :
+            ElevationLayerOptions(co)
+        {
+            _fill.init(false);
+            _lineWidth.init(40);
+            _bufferWidth.init(40);
+            mergeConfig(_conf);
+        }
+        
+        /** Name of the feature source layer to use for flattening. */
+        optional<std::string>& featureSourceLayer() { return _featureSourceLayer; }
+        const optional<std::string>& featureSourceLayer() const { return _featureSourceLayer; }
+
+        /** Features that will drive the flattening process. */
+        optional<FeatureSourceOptions>& featureSource() { return _featureSource; }
+        const optional<FeatureSourceOptions>& featureSource() const { return _featureSource; }
+
+        /** For line features, the width around the line to flatten. */
+        optional<NumericExpression>& lineWidth() { return _lineWidth; }
+        const optional<NumericExpression>& lineWidth() const { return _lineWidth; }
+
+        /** Width of the buffer between the flattened terrain and the natural terrain,
+            which will serve as a transition area. */
+        optional<NumericExpression>& bufferWidth() { return _bufferWidth; }
+        const optional<NumericExpression>& bufferWidth() const { return _bufferWidth; }
+
+        //! Whether to write all samples (default=false) with source elev instead of
+        //! writing NO_DATA_VALUE where no features exist
+        optional<bool>& fill() { return _fill; }
+        const optional<bool>& fill() const { return _fill; }
+
+        StyleSheet::ScriptDef* getScript() const { return _script.get(); }
+
+    public:
+        Config getConfig() const
+        {
+            Config conf = ElevationLayerOptions::getConfig();
+            conf.key() = "flattened_elevation";
+            conf.addObjIfSet("features",  _featureSource);
+            conf.addIfSet("feature_source", _featureSourceLayer);
+            conf.addObjIfSet("line_width", _lineWidth);
+            conf.addObjIfSet("buffer_width", _bufferWidth);
+            conf.addIfSet("fill", _fill);
+
+            if ( _script.valid() )
+            {
+                Config scriptConf("script");
+
+                if ( !_script->name.empty() )
+                    scriptConf.set( "name", _script->name );
+                if ( !_script->language.empty() )
+                    scriptConf.set( "language", _script->language );
+                if ( _script->uri.isSet() )
+                    scriptConf.set( "url", _script->uri->base() );
+                if ( !_script->profile.empty() )
+                    scriptConf.set( "profile", _script->profile );
+                else if ( !_script->code.empty() )
+                    scriptConf.value() = _script->code;
+
+                conf.add( scriptConf );
+            }
+
+            return conf;
+        }
+
+    protected:
+        void mergeConfig( const Config& conf )
+        {
+            URIContext uriContext = URIContext( conf.referrer() );
+
+            conf.getObjIfSet("features",  _featureSource);
+            conf.getIfSet("feature_source", _featureSourceLayer);
+            conf.getObjIfSet("line_width", _lineWidth);
+            conf.getObjIfSet("buffer_width", _bufferWidth);
+            conf.getIfSet("fill", _fill);
+
+            // TODO:  Separate out ScriptDef from Stylesheet and include it as a standalone class, along with this loading code.
+            ConfigSet scripts = conf.children( "script" );
+            for( ConfigSet::iterator i = scripts.begin(); i != scripts.end(); ++i )
+            {
+                _script = new StyleSheet::ScriptDef();
+
+                // load the code from a URI if there is one:
+                if ( i->hasValue("url") )
+                {
+                    _script->uri = URI( i->value("url"), uriContext );
+                    OE_INFO << "Loading script from \"" << _script->uri->full() << std::endl;
+                    _script->code = _script->uri->getString();
+                }
+                else
+                {
+                    _script->code = i->value();
+                }
+
+                // name is optional and unused at the moment
+                _script->name = i->value("name");
+
+                std::string lang = i->value("language");
+                _script->language = lang.empty() ? "javascript" : lang;
+
+                std::string profile = i->value("profile");
+                _script->profile = profile;
+            }
+        }
+        
+    private:
+        optional<FeatureSourceOptions>  _featureSource;
+        optional<std::string> _featureSourceLayer;
+        optional<NumericExpression> _lineWidth;
+        optional<NumericExpression> _bufferWidth;
+        optional<bool> _fill;
+        osg::ref_ptr< StyleSheet::ScriptDef > _script;
+    };
+
+    /**
+     * Elevation layer that overlays modified elevation samples intended to
+     * flatten the terrain around vector features. The use case is to make
+     * roads flat or prevent rivers and lakes from sloping with the terrain.
+     */
+    class OSGEARTHUTIL_EXPORT FlatteningLayer : public ElevationLayer
+    {
+    public:
+        META_Layer(osgEarth, FlatteningLayer, FlatteningLayerOptions);
+
+        // Create a layer with initial options.
+        FlatteningLayer(const FlatteningLayerOptions& options = FlatteningLayerOptions());
+
+        // Feature source layer to get a FeatureSource from
+        void setFeatureSourceLayer(FeatureSourceLayer* layer);    
+        
+        // the feature source from which to read flattening geometry
+        void setFeatureSource(FeatureSource* fs);
+
+    public: // ElevationLayer
+
+        virtual void init();
+
+        // opens the layer and returns the status
+        virtual const Status& open();
+
+    protected: // ElevationLayer
+
+        virtual void createImplementation(
+            const TileKey& key,
+            osg::ref_ptr<osg::HeightField>& hf,
+            osg::ref_ptr<NormalMap>& normalMap,
+            ProgressCallback* progres);
+
+        //! called by the map when this layer is added
+        virtual void addedToMap(const class Map*);
+
+        //! called by the map when this layer is removed
+        virtual void removedFromMap(const class Map*);
+
+    protected:
+
+        virtual ~FlatteningLayer();
+
+    private:
+
+        osg::ref_ptr<ElevationPool> _pool;
+        osg::ref_ptr<FeatureSource> _featureSource;
+        osg::ref_ptr<ScriptEngine> _scriptEngine;
+        LayerListener<FlatteningLayer, FeatureSourceLayer> _featureLayerListener;
+        osg::observer_ptr< const Map > _map;
+    };
+
+    REGISTER_OSGEARTH_LAYER(flattened_elevation, FlatteningLayer);
+
+} } // namespace osgEarth::Util
+
+#endif // OSGEARTH_UTIL_FLATTENING_LAYER
diff --git a/src/osgEarthUtil/FlatteningLayer.cpp b/src/osgEarthUtil/FlatteningLayer.cpp
new file mode 100644
index 0000000..d0d3fd9
--- /dev/null
+++ b/src/osgEarthUtil/FlatteningLayer.cpp
@@ -0,0 +1,985 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+ * Copyright 2016 Pelican Mapping
+ * http://osgearth.org
+ *
+ * osgEarth is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+#include <osgEarthUtil/FlatteningLayer>
+#include <osgEarth/Registry>
+#include <osgEarth/HeightFieldUtils>
+#include <osgEarth/Map>
+#include <osgEarth/Progress>
+#include <osgEarth/Utils>
+#include <osgEarthFeatures/FeatureCursor>
+#include <osgEarthFeatures/GeometryUtils>
+#include <osgEarthFeatures/FilterContext>
+#include <osgEarthSymbology/Query>
+
+using namespace osgEarth;
+using namespace osgEarth::Util;
+using namespace osgEarth::Features;
+using namespace osgEarth::Symbology;
+
+#define LC "[FlatteningLayer] "
+
+#define OE_TEST OE_DEBUG
+
+namespace
+{
+    // linear interpolation between a and b
+    double inline mix(double a, double b, double t)
+    {
+        return a + (b-a)*t;
+    }
+
+    // smoothstep (cos approx) interpolation between a and b
+    double inline smoothstep(double a, double b, double t)
+    {
+        // smoothstep (approximates cosine):
+        t = t*t*(3.0-2.0*t);
+        return a + (b-a)*t;
+    }
+
+    double inline smootherstep(double a, double b, double t)
+    {
+        t = t*t*t*(t*(t*6.0 - 15.0)+10.0);
+        return a + (b-a)*t;
+    }
+    
+    // clamp "a" to [lo..hi].
+    double inline clamp(double a, double lo, double hi)
+    {
+        return std::max(std::min(a, hi), lo);
+    }
+
+    typedef osg::Vec3d POINT;
+    typedef osg::Vec3d VECTOR;
+
+    // iterator that permits the use of looping indexes.
+    template<typename T>
+    struct CircleIterator {
+        CircleIterator(const std::vector<T>& v) : _v(v) { }
+        int size() const { return _v.size(); }
+        const T& operator[](int i) const { return _v[i % _v.size()]; }
+        const std::vector<T>& _v;
+    };
+    
+    // is P inside the CCW triangle ABC?
+    bool triangleContains(const POINT& A, const POINT& B, const POINT& C, const POINT& P)
+    {
+        VECTOR AB = B-A, BC = C-B, CA = A-C;
+        if (((P - A) ^ AB).z() > 0.0) return false;
+        if (((P - B) ^ BC).z() > 0.0) return false;
+        if (((P - C) ^ CA).z() > 0.0) return false;
+        return true;
+    }
+
+    // Find a point internal to the polygon.
+    // This will not always work with polygons that contain holes,
+    // so we need to come up with a different algorithm if this becomes a problem.
+    // Maybe try a random point generator and profile it.
+    osg::Vec3d inline getInternalPoint(const Polygon* p)
+    {
+        // Simple test: if the centroid is in the polygon, use it.
+        osg::Vec3d centroid = p->getBounds().center();
+        if (p->contains2D(centroid.x(), centroid.y()))
+            return centroid;
+
+        // Concave/holey polygon, so try the hard way.
+        // Ref: http://apodeline.free.fr/FAQ/CGAFAQ/CGAFAQ-3.html
+
+        CircleIterator<POINT> vi(p->asVector());
+
+        for (int i = 0; i < vi.size(); ++i)
+        {
+            const POINT& V = vi[i];
+            const POINT& A = vi[i-1];
+            const POINT& B = vi[i+1];
+
+            if (((V - A) ^ (B - V)).z() > 0.0) // Convex vertex? (assume CCW winding)
+            {
+                double minDistQV2 = DBL_MAX;   // shortest distance from test point to candidate point
+                int indexBestQ;                // index of best candidate point
+
+                // loop over all other verts (besides A, V, B):
+                for (int j = i + 2; j < i + 2 + vi.size() - 3; ++j)
+                {
+                    const POINT& Q = vi[j];
+
+                    if (triangleContains(A, V, B, Q))
+                    {
+                        double distQV2 = (Q - V).length2();
+                        if (distQV2 < minDistQV2)
+                        {
+                            minDistQV2 = distQV2;
+                            indexBestQ = j;
+                        }
+                    }
+                }
+
+                POINT result;
+
+                // If no inside point was found, return the midpoint of AB.
+                if (minDistQV2 == DBL_MAX)
+                {
+                    result = (A+B)*0.5;
+                }
+
+                // Otherwise, use the midpoint of QV.
+                else
+                {
+                    const POINT& Q = vi[indexBestQ];
+                    result = (Q+V)*0.5;
+                }
+
+                // make sure the resulting point doesn't fall within any of the
+                // polygon's holes.
+                if (p->contains2D(result.x(), result.y()))
+                {
+                    return result;
+                }
+            }
+        }
+
+        // Will only happen is holes prevent us from finding an internal point.
+        OE_WARN << LC << "getInternalPoint failed miserably\n";
+        return p->getBounds().center();
+    }
+
+    double getDistanceSquaredToClosestEdge(const osg::Vec3d& P, const Polygon* poly)
+    {        
+        double Dmin = DBL_MAX;
+        ConstSegmentIterator segIter(poly, true);
+        while (segIter.hasMore())
+        {
+            const Segment segment = segIter.next();
+            const POINT& A = segment.first;
+            const POINT& B = segment.second;
+            const VECTOR AP = P-A, AB = B-A;
+            double t = clamp((AP*AB)/AB.length2(), 0.0, 1.0);
+            VECTOR PROJ = A + AB*t;
+            double D = (P - PROJ).length2();
+            if (D < Dmin) Dmin = D;
+        }
+        return Dmin;
+    }
+
+
+    struct Widths {
+        Widths(const Widths& rhs) {
+            bufferWidth = rhs.bufferWidth;
+            lineWidth = rhs.lineWidth;
+        }
+
+        Widths(double bufferWidth, double lineWidth) {
+            this->bufferWidth = bufferWidth;
+            this->lineWidth = lineWidth;
+        }
+
+        double bufferWidth;
+        double lineWidth;
+    };
+
+    typedef std::vector<Widths> WidthsList;
+    
+    // Creates a heightfield that flattens an area intersecting the input polygon geometry.
+    // The height of the area is found by sampling a point internal to the polygon.
+    // bufferWidth = width of transition from flat area to natural terrain.
+    bool integratePolygons(const TileKey& key, osg::HeightField* hf, const MultiGeometry* geom, const SpatialReference* geomSRS,
+                           WidthsList& widths, ElevationEnvelope* envelope, 
+                           bool fillAllPixels, ProgressCallback* progress)
+    {
+        bool wroteChanges = false;
+
+        const GeoExtent& ex = key.getExtent();
+
+        double col_interval = ex.width() / (double)(hf->getNumColumns()-1);
+        double row_interval = ex.height() / (double)(hf->getNumRows()-1);
+
+        POINT Pex, P, internalP;
+
+        bool needsTransform = ex.getSRS() != geomSRS;
+        
+        for (unsigned col = 0; col < hf->getNumColumns(); ++col)
+        {
+            Pex.x() = ex.xMin() + (double)col * col_interval;
+
+            for (unsigned row = 0; row < hf->getNumRows(); ++row)
+            {
+                // check for cancelation periodically
+                //if (progress && progress->isCanceled())
+                //    return false;
+
+                Pex.y() = ex.yMin() + (double)row * row_interval;
+
+                if (needsTransform)
+                    ex.getSRS()->transform(Pex, geomSRS, P);
+                else
+                    P = Pex;
+                
+                bool done = false;
+                double minD2 = DBL_MAX;//bufferWidth * bufferWidth; // minimum distance(squared) to closest polygon edge
+                double bufferWidth = 0.0;
+
+                const Polygon* bestPoly = 0L;
+
+                for (unsigned int geomIndex = 0; geomIndex < geom->getNumComponents(); geomIndex++)
+                {
+                    Geometry* component = geom->getComponents()[geomIndex].get();
+                    Widths width = widths[geomIndex];
+                    ConstGeometryIterator giter(component, false);
+                    while (giter.hasMore() && !done)
+                    {
+                        const Polygon* polygon = dynamic_cast<const Polygon*>(giter.next());
+                        if (polygon)
+                        {
+                            // Does the point P fall within the polygon?
+                            if (polygon->contains2D(P.x(), P.y()))
+                            {
+                                // yes, flatten it to the polygon's centroid elevation;
+                                // and we're dont with this point.
+                                done = true;
+                                bestPoly = polygon;
+                                minD2 = -1.0;
+                                bufferWidth = width.bufferWidth;
+                            }
+
+                            // If not in the polygon, how far to the closest edge?
+                            else
+                            {
+                                double D2 = getDistanceSquaredToClosestEdge(P, polygon);
+                                if (D2 < minD2)
+                                {
+                                    minD2 = D2;
+                                    bestPoly = polygon;
+                                    bufferWidth = width.bufferWidth;
+                                }
+                            }
+                        }                    
+                    }
+                }
+
+                if (bestPoly && minD2 != 0.0)
+                {
+                    float h;
+                    POINT internalP = getInternalPoint(bestPoly);
+                    float elevInternal = envelope->getElevation(internalP.x(), internalP.y());
+
+                    if (minD2 < 0.0)
+                    {
+                        h = elevInternal;
+                    }
+                    else
+                    {
+                        float elevNatural = envelope->getElevation(P.x(), P.y());
+                        double blend = clamp(sqrt(minD2)/bufferWidth, 0.0, 1.0); // [0..1] 0=internal, 1=natural
+                        h = smootherstep(elevInternal, elevNatural, blend);
+                    }
+
+                    hf->setHeight(col, row, h);
+                    wroteChanges = true;
+                }
+
+                else if (fillAllPixels)
+                {
+                    float h = envelope->getElevation(P.x(), P.y());
+                    hf->setHeight(col, row, h);
+                    // do not set wroteChanges
+                }
+            }
+        }
+
+        return wroteChanges;
+    }
+
+
+
+    struct Sample {
+        double D2;      // distance to segment squared
+        osg::Vec3d A;   // endpoint of segment
+        osg::Vec3d B;   // other endpoint of segment;
+        double T;       // segment parameter of closest point
+
+        // used later:
+        float elevPROJ; // elevation at point on segment
+        float elev;     // flattened elevation
+        double D;       // distance
+
+        double innerRadius;
+        double outerRadius;
+
+        bool operator < (struct Sample& rhs) const { return D2 < rhs.D2; }
+    };
+
+    typedef std::vector<Sample> Samples;
+
+    bool EQ2(const osg::Vec3d& a, const osg::Vec3d& b) {
+        return osg::equivalent(a.x(), b.x()) && osg::equivalent(a.y(), b.y());
+    }
+
+    bool isSampleValid(const Sample* b1, Samples& samples)
+    {
+        for (unsigned i = 0; i < samples.size(); ++i)
+        {
+            Sample* b2 = &samples[i];
+            if (b1 == b2) continue;
+            if (b1->T == 0.0 && (EQ2(b1->A, b2->A) || EQ2(b1->A, b2->B))) return false;
+            if (b1->T == 1.0 && (EQ2(b1->B, b2->A) || EQ2(b1->B, b2->B))) return false;
+        }
+        return true;
+    }
+
+    // Inverse Direct Weighting (IDW) interpolation
+    float interpolateSamplesIDW(Samples& samples)
+    {
+        // higher powerParam corresponds to increased proximity favoring
+        const double powerParam = 2.5;
+
+        if (samples.size() == 0) return 0.0f;
+        if (samples.size() == 1) return samples[0].elev;
+
+        double numer = 0.0;
+        double denom = 0.0;
+        for (unsigned i = 0; i < samples.size(); ++i)
+        {
+            if (osg::equivalent(samples[i].D, 0.0)) {
+                numer = samples[i].elev, denom = 1.0;
+                break;
+            }
+            else {
+                double w = pow(1.0/samples[i].D, powerParam);
+                numer += w * samples[i].elev;
+                denom += w;
+            }
+        }
+
+        return numer / denom;
+    }
+
+    // Linear interpolation (simple mean)
+    float interpolateSamplesLinear(Samples& samples)
+    {
+        double numer = 0.0;
+        for (unsigned i = 0; i<samples.size(); ++i)
+            numer += samples[i].elev;
+        return samples.size() > 0 ? (numer / (double)(samples.size())) : FLT_MAX;
+    }
+
+    /**
+     * Create a heightfield that flattens the terrain around linear geometry.
+     * lineWidth = width of completely flat area
+     * bufferWidth = width of transition from flat area to natural terrain
+     *
+     * Note: this algorithm only samples elevation data from the source (elevation pool).
+     * As it progresses, however, it is creating new modified elevation data -- but later
+     * points will continue to derive their source data from the original data. This means
+     * there will be some discontinuities in the final data, especially along the edges of
+     * the flattening buffer.
+     *
+     * There is not perfect solution for this, but one improvement would be to copy the 
+     * source elevation into the heightfield as a starting point, and then sample that
+     * modifiable heightfield as we go along.
+     */
+    bool integrateLines(const TileKey& key, osg::HeightField* hf, const MultiGeometry* geom, const SpatialReference* geomSRS,
+                        WidthsList& widths, ElevationEnvelope* envelope,
+                        bool fillAllPixels, ProgressCallback* progress)
+    {
+        bool wroteChanges = false;
+
+        const GeoExtent& ex = key.getExtent();
+
+        double col_interval = ex.width() / (double)(hf->getNumColumns()-1);
+        double row_interval = ex.height() / (double)(hf->getNumRows()-1);
+
+        osg::Vec3d Pex, P, PROJ;
+
+        bool needsTransform = ex.getSRS() != geomSRS;
+        
+        // Loop over the new heightfield.
+        for (unsigned col = 0; col < hf->getNumColumns(); ++col)
+        {
+            Pex.x() = ex.xMin() + (double)col * col_interval;
+
+            for (unsigned row = 0; row < hf->getNumRows(); ++row)
+            {
+                // check for cancelation periodically
+                //if (progress && progress->isCanceled())
+                //    return false;
+
+                Pex.y() = ex.yMin() + (double)row * row_interval;
+
+                // Move the point into the working SRS if necessary
+                if (needsTransform)
+                    ex.getSRS()->transform(Pex, geomSRS, P);
+                else
+                    P = Pex;
+
+                // For each point, we need to find the closest line segments to that point
+                // because the elevation values on these line segments will be the flattening
+                // value. There may be more than one line segment that falls within the search
+                // radius; we will collect up to MaxSamples of these for each heightfield point.
+                static const unsigned Maxsamples = 4;
+                Samples samples;
+
+                for (unsigned int geomIndex = 0; geomIndex < geom->getNumComponents(); geomIndex++)
+                {
+                    Widths w = widths[geomIndex];
+                    double innerRadius = w.lineWidth * 0.5;
+                    double outerRadius = innerRadius + w.bufferWidth;
+                    double outerRadius2 = outerRadius * outerRadius;
+
+                    Geometry* component = geom->getComponents()[geomIndex].get();
+                    // Search for line segments.
+                    ConstGeometryIterator giter(component);
+                    while (giter.hasMore())
+                    {
+                        const Geometry* part = giter.next();                        
+
+                        for (int i = 0; i < part->size()-1; ++i)
+                        {
+                            // AB is a candidate line segment:
+                            const osg::Vec3d& A = (*part)[i];
+                            const osg::Vec3d& B = (*part)[i+1];
+
+                            osg::Vec3d AB = B - A;    // current segment AB
+
+                            double t;                 // parameter [0..1] on segment AB
+                            double D2;                // shortest distance from point P to segment AB, squared
+                            double L2 = AB.length2(); // length (squared) of segment AB
+                            osg::Vec3d AP = P - A;    // vector from endpoint A to point P
+
+                            if (L2 == 0.0)
+                            {
+                                // trivial case: zero-length segment
+                                t = 0.0;
+                                D2 = AP.length2();
+                            }
+                            else
+                            {
+                                // Calculate parameter "t" [0..1] which will yield the closest point on AB to P.
+                                // Clamping it means the closest point won't be beyond the endpoints of the segment.
+                                t = clamp((AP * AB)/L2, 0.0, 1.0);
+
+                                // project our point P onto segment AB:
+                                PROJ.set( A + AB*t );
+
+                                // measure the distance (squared) from P to the projected point on AB:
+                                D2 = (P - PROJ).length2();
+                            }
+
+                            // If the distance from our point to the line segment falls within
+                            // the maximum flattening distance, store it.
+                            if (D2 <= outerRadius2)
+                            {
+                                // see if P is a new sample.
+                                Sample* b;
+                                if (samples.size() < Maxsamples)
+                                {
+                                    // If we haven't collected the maximum number of samples yet,
+                                    // just add this to the list:
+                                    samples.push_back(Sample());
+                                    b = &samples.back();
+                                }
+                                else
+                                {
+                                    // If we are maxed out on samples, find the farthest one we have so far
+                                    // and replace it if the new point is closer:
+                                    unsigned max_i = 0;
+                                    for (unsigned i=1; i<samples.size(); ++i)
+                                        if (samples[i].D2 > samples[max_i].D2)
+                                            max_i = i;
+
+                                    b = &samples[max_i];
+
+                                    if (b->D2 < D2)
+                                        b = 0L;
+                                }
+
+                                if (b)
+                                {
+                                    b->D2 = D2;
+                                    b->A = A;
+                                    b->B = B;
+                                    b->T = t;
+                                    b->innerRadius = innerRadius;
+                                    b->outerRadius = outerRadius;
+                                }
+                            }
+                        }
+                    }
+                }
+
+                // Remove unnecessary sample points that lie on the endpoint of a segment
+                // that abuts another segment in our list.
+                for (unsigned i = 0; i < samples.size();) {
+                    if (!isSampleValid(&samples[i], samples)) {
+                        samples[i] = samples[samples.size() - 1];
+                        samples.resize(samples.size() - 1);
+                    }
+                    else ++i;
+                }
+
+                // Now that we are done searching for line segments close to our point,
+                // we will collect the elevations at our sample points and use them to 
+                // create a new elevation value for our point.
+                if (samples.size() > 0)
+                {
+                    // The original elevation at our point:
+                    float elevP = envelope->getElevation(P.x(), P.y());
+                    
+                    for (unsigned i = 0; i < samples.size(); ++i)
+                    {
+                        Sample& sample = samples[i];
+
+                        sample.D = sqrt(sample.D2);
+
+                        // Blend factor. 0 = distance is less than or equal to the inner radius;
+                        //               1 = distance is greater than or equal to the outer radius.
+                        double blend = clamp(
+                            (sample.D - sample.innerRadius) / (sample.outerRadius - sample.innerRadius),
+                            0.0, 1.0);
+                        
+                        if (sample.T == 0.0)
+                        {
+                            sample.elevPROJ = envelope->getElevation(sample.A.x(), sample.A.y());
+                            if (sample.elevPROJ == NO_DATA_VALUE)
+                                sample.elevPROJ = elevP;
+                        }
+                        else if (sample.T == 1.0)
+                        {
+                            sample.elevPROJ = envelope->getElevation(sample.B.x(), sample.B.y());
+                            if (sample.elevPROJ == NO_DATA_VALUE)
+                                sample.elevPROJ = elevP;
+                        }
+                        else
+                        {
+                            float elevA = envelope->getElevation(sample.A.x(), sample.A.y());
+                            if (elevA == NO_DATA_VALUE)
+                                elevA = elevP;
+
+                            float elevB = envelope->getElevation(sample.B.x(), sample.B.y());
+                            if (elevB == NO_DATA_VALUE)
+                                elevB = elevP;
+
+                            // linear interpolation of height from point A to point B on the segment:
+                            sample.elevPROJ = mix(elevA, elevB, sample.T);
+                        }
+
+                        // smoothstep interpolation of along the buffer (perpendicular to the segment)
+                        // will gently integrate the new value into the existing terrain.
+                        sample.elev = smootherstep(sample.elevPROJ, elevP, blend);
+                    }
+
+                    // Finally, combine our new elevation values and set the new value in the output.
+                    float finalElev = interpolateSamplesIDW(samples);
+                    if (finalElev < FLT_MAX)
+                        hf->setHeight(col, row, finalElev);
+                    else
+                        hf->setHeight(col, row, elevP);
+
+                    wroteChanges = true;
+                }
+
+                else if (fillAllPixels)
+                {
+                    // No close segments were found, so just copy over the source data.
+                    float h = envelope->getElevation(P.x(), P.y());
+                    hf->setHeight(col, row, h);
+
+                    // Note: do not set wroteChanges to true.
+                }
+            }
+        }
+
+        return wroteChanges;
+    }
+    
+
+    bool integrate(const TileKey& key, osg::HeightField* hf, const MultiGeometry* geom, const SpatialReference* geomSRS,
+                   WidthsList& widths, ElevationEnvelope* envelope,
+                   bool fillAllPixels, ProgressCallback* progress)
+    {
+        if (geom->isLinear())
+            return integrateLines(key, hf, geom, geomSRS, widths, envelope, fillAllPixels, progress);
+        else
+            return integratePolygons(key, hf, geom, geomSRS, widths, envelope, fillAllPixels, progress);
+    }
+}
+
+//........................................................................
+
+
+FlatteningLayer::FlatteningLayer(const FlatteningLayerOptions& options) :
+ElevationLayer(&_optionsConcrete),
+_options(&_optionsConcrete),
+_optionsConcrete(options)
+{
+    // Experiment with this and see what will work.
+    _pool = new ElevationPool();
+    _pool->setTileSize(257u);
+
+    init();
+}
+
+void
+FlatteningLayer::init()
+{
+    setTileSourceExpected(false);
+    ElevationLayer::init();
+}
+
+const Status&
+FlatteningLayer::open()
+{
+    // ensure the caller named a feature source:
+    if (!options().featureSource().isSet() &&
+        !options().featureSourceLayer().isSet())
+    {
+        return setStatus(Status::Error(Status::ConfigurationError, "Missing required feature source"));
+    }
+
+    // If the feature source is inline, open it now.
+    if (options().featureSource().isSet())
+    {
+        // Create the feature source instance:
+        FeatureSource* fs = FeatureSourceFactory::create(options().featureSource().get());
+        if (!fs)
+        {
+            return setStatus(Status::Error(Status::ServiceUnavailable, "Unable to create feature source as defined"));
+        }
+
+        // Open it:
+        setStatus(fs->open(getReadOptions()));
+        if (getStatus().isError())
+            return getStatus();
+        
+        setFeatureSource(fs);
+
+        if (getStatus().isError())
+            return getStatus();
+    }
+    
+    const Profile* profile = getProfile();
+    if ( !profile )
+    {
+        profile = Registry::instance()->getGlobalGeodeticProfile();
+        setProfile( profile );
+    }
+
+    return ElevationLayer::open();
+}
+
+void
+FlatteningLayer::setFeatureSource(FeatureSource* fs)
+{
+    if (fs)
+    {
+        _featureSource = fs;
+        if (_featureSource)
+        {
+            if (!_featureSource->getFeatureProfile())
+            {
+                setStatus(Status::Error(Status::ConfigurationError, "No feature profile (is the source open?)"));
+                _featureSource = 0L;
+                return;
+            }
+        }
+    }
+}
+
+FlatteningLayer::~FlatteningLayer()
+{
+    _featureLayerListener.clear();
+}
+
+void
+FlatteningLayer::setFeatureSourceLayer(FeatureSourceLayer* layer)
+{
+    if (layer)
+    {
+        if (layer->getStatus().isOK())
+            setFeatureSource(layer->getFeatureSource());
+    }
+    else
+    {
+        setFeatureSource(0L);
+    }
+}
+
+void
+FlatteningLayer::addedToMap(const Map* map)
+{   
+    // Initialize the elevation pool with our map:
+    OE_INFO << LC << "Attaching elevation pool to map\n";
+    _pool->setMap( map );
+
+        
+    // Listen for our feature source layer to arrive, if there is one.
+    if (options().featureSourceLayer().isSet())
+    {
+        _featureLayerListener.listen(
+            map,
+            options().featureSourceLayer().get(), 
+            this, &FlatteningLayer::setFeatureSourceLayer);
+    }
+        
+    // Collect all elevation layers preceding this one and use them for flattening.
+    ElevationLayerVector layers;
+    map->getLayers(layers);
+    for (ElevationLayerVector::iterator i = layers.begin(); i != layers.end(); ++i) {
+        if (i->get() == this) {
+            layers.erase(i);
+            break;
+        }
+        else {
+            OE_INFO << LC << "Using: " << i->get()->getName() << "\n";
+        }
+    }
+    if (!layers.empty())
+    {
+        _pool->setElevationLayers(layers);
+    }
+}
+
+void
+FlatteningLayer::removedFromMap(const Map* map)
+{
+    _featureLayerListener.clear();
+}
+
+void
+FlatteningLayer::createImplementation(const TileKey& key,
+                                      osg::ref_ptr<osg::HeightField>& hf,
+                                      osg::ref_ptr<NormalMap>& normalMap,
+                                      ProgressCallback* progress)
+{
+    if (getStatus().isError())    
+    {
+        return;
+    }
+    
+    if (!_featureSource.valid())
+    {
+        setStatus(Status(Status::ServiceUnavailable, "No feature source"));
+        return;
+    }
+
+    const FeatureProfile* featureProfile = _featureSource->getFeatureProfile();
+    if (!featureProfile)
+    {
+        setStatus(Status(Status::ConfigurationError, "Feature profile is missing"));
+        return;
+    }
+
+    const SpatialReference* featureSRS = featureProfile->getSRS();
+    if (!featureSRS)
+    {
+        setStatus(Status(Status::ConfigurationError, "Feature profile has no SRS"));
+        return;
+    }
+
+    if (_pool->getElevationLayers().empty())
+    {
+        OE_WARN << LC << "Internal error - Pool layer set is empty\n";
+        return;
+    }
+
+    OE_START_TIMER(create);
+
+    // If the feature source has a tiling profile, we are going to have to map the incoming
+    // TileKey to a set of intersecting TileKeys in the feature source's tiling profile.
+    GeoExtent queryExtent = key.getExtent().transform(featureSRS);
+
+    // Lat/Long extent:
+    GeoExtent geoExtent = queryExtent.transform(featureSRS->getGeographicSRS());
+
+    // Buffer the query extent to include the potentially flattened area.
+    /*
+    double linewidth = SpatialReference::transformUnits(
+        options().lineWidth().get(),
+        featureSRS,
+        geoExtent.getCentroid().y());
+
+    double bufferwidth = SpatialReference::transformUnits(
+        options().bufferWidth().get(),
+        featureSRS,
+        geoExtent.getCentroid().y());
+    */
+    // TODO:  JBFIX.  Add a "max" setting somewhere.
+    double linewidth = 10.0;
+    double bufferwidth = 10.0;
+    double queryBuffer = 0.5*linewidth + bufferwidth;
+    queryExtent.expand(queryBuffer, queryBuffer);
+
+    // We must do all the feature processing in a projected system since we're using vector math.
+#if 0
+    osg::ref_ptr<const SpatialReference> workingSRS = queryExtent.getSRS();
+    //if (workingSRS->isGeographic())
+    {
+        osg::Vec3d refPos = queryExtent.getCentroid();
+        workingSRS = workingSRS->createTangentPlaneSRS(refPos);
+    }
+#else
+    const SpatialReference* workingSRS = queryExtent.getSRS()->isGeographic() ? SpatialReference::get("spherical-mercator") :
+        queryExtent.getSRS();
+#endif
+
+    bool needsTransform = !featureSRS->isHorizEquivalentTo(workingSRS);
+
+    osg::ref_ptr< StyleSheet > styleSheet = new StyleSheet();
+    styleSheet->setScript(options().getScript());
+    osg::ref_ptr< Session > session = new Session( _map.get(), styleSheet.get());
+
+    // We will collection all the feature geometries in this multigeometry:
+    MultiGeometry geoms;
+    WidthsList widths;
+
+    if (featureProfile->getProfile())
+    {
+        // Tiled source, must resolve complete set of intersecting tiles:
+        std::vector<TileKey> intersectingKeys;
+        featureProfile->getProfile()->getIntersectingTiles(queryExtent, key.getLOD(), intersectingKeys);
+
+        std::set<TileKey> featureKeys;
+        for (int i = 0; i < intersectingKeys.size(); ++i)
+        {        
+            if (intersectingKeys[i].getLOD() > featureProfile->getMaxLevel())
+                featureKeys.insert(intersectingKeys[i].createAncestorKey(featureProfile->getMaxLevel()));
+            else
+                featureKeys.insert(intersectingKeys[i]);
+        }
+
+        // Query and collect all the features we need for this tile.
+        for (std::set<TileKey>::const_iterator i = featureKeys.begin(); i != featureKeys.end(); ++i)
+        {
+            Query query;        
+            query.tileKey() = *i;
+
+            osg::ref_ptr<FeatureCursor> cursor = _featureSource->createFeatureCursor(query);
+            while (cursor.valid() && cursor->hasMore())
+            {
+                Feature* feature = cursor->nextFeature();
+
+                double lineWidth = 0.0;
+                double bufferWidth = 0.0;
+                if (options().lineWidth().isSet())
+                {
+                    NumericExpression lineWidthExpr(options().lineWidth().get());
+                    lineWidth = feature->eval(lineWidthExpr, session.get());
+                }
+
+                if (options().bufferWidth().isSet())
+                {
+                    NumericExpression bufferWidthExpr(options().bufferWidth().get());
+                    bufferWidth = feature->eval(bufferWidthExpr, session.get());
+                }
+
+                // Transform the feature geometry to our working (projected) SRS.
+                if (needsTransform)
+                    feature->transform(workingSRS);
+
+                lineWidth = SpatialReference::transformUnits(
+                    Distance(lineWidth),
+                    featureSRS,
+                    geoExtent.getCentroid().y());
+
+                bufferWidth = SpatialReference::transformUnits(
+                    Distance(bufferWidth),
+                    featureSRS,
+                    geoExtent.getCentroid().y());
+
+                //TODO: optimization: test the geometry bounds against the expanded tilekey bounds
+                //      in order to discard geometries we don't care about
+                geoms.getComponents().push_back(feature->getGeometry());
+                widths.push_back(Widths(bufferWidth, lineWidth));
+            }
+        }
+    }
+    else
+    {
+        // Non-tiled feaure source, just query arbitrary extent:
+        // Set up the query; bounds must be in the feature SRS:
+        Query query;
+        query.bounds() = queryExtent.bounds();
+
+        // Run the query and fill the list.
+        osg::ref_ptr<FeatureCursor> cursor = _featureSource->createFeatureCursor(query);
+        while (cursor.valid() && cursor->hasMore())
+        {
+            Feature* feature = cursor->nextFeature();
+
+            double lineWidth = 0.0;
+            double bufferWidth = 0.0;
+            if (options().lineWidth().isSet())
+            {
+                NumericExpression lineWidthExpr(options().lineWidth().get());
+                lineWidth = feature->eval(lineWidthExpr, session.get());
+            }
+
+            if (options().bufferWidth().isSet())
+            {
+                NumericExpression bufferWidthExpr(options().bufferWidth().get());
+                bufferWidth = feature->eval(bufferWidthExpr, session.get());
+            }
+
+            // Transform the feature geometry to our working (projected) SRS.
+            if (needsTransform)
+                feature->transform(workingSRS);
+
+            lineWidth = SpatialReference::transformUnits(
+                Distance(lineWidth),
+                featureSRS,
+                geoExtent.getCentroid().y());
+
+            bufferWidth = SpatialReference::transformUnits(
+                Distance(bufferWidth),
+                featureSRS,
+                geoExtent.getCentroid().y());
+
+            // Transform the feature geometry to our working (projected) SRS.
+            if (needsTransform)
+                feature->transform(workingSRS);
+
+            //TODO: optimization: test the geometry bounds against the expanded tilekey bounds
+            //      in order to discard geometries we don't care about
+
+            geoms.getComponents().push_back(feature->getGeometry());
+            widths.push_back(Widths(bufferWidth, lineWidth));
+        }
+    }
+
+    if (!geoms.getComponents().empty())
+    {
+        if (!hf.valid())
+        {
+            // Make an empty heightfield to populate:
+            hf = HeightFieldUtils::createReferenceHeightField(
+                queryExtent,
+                257, 257,           // base tile size for elevation data
+                0u,                 // no border
+                true);              // initialize to HAE (0.0) heights
+
+            // Initialize to NO DATA.
+            hf->getFloatArray()->assign(hf->getNumColumns()*hf->getNumRows(), NO_DATA_VALUE);
+        }
+
+        // Create an elevation query envelope at the LOD we are creating
+        osg::ref_ptr<ElevationEnvelope> envelope = _pool->createEnvelope(workingSRS, key.getLOD());
+
+        bool fill = (options().fill() == true);     
+        
+        integrate(key, hf.get(), &geoms, workingSRS, widths, envelope.get(), fill, progress);
+    }
+}
diff --git a/src/osgEarthUtil/Fog.cpp b/src/osgEarthUtil/Fog.cpp
index c269883..ebdf7cb 100644
--- a/src/osgEarthUtil/Fog.cpp
+++ b/src/osgEarthUtil/Fog.cpp
@@ -93,7 +93,7 @@ void FogEffect::detach()
         osg::ref_ptr<osg::StateSet> stateset;
         if ( (*it).lock(stateset) )
         {
-            detach( stateset );
+            detach( stateset.get() );
             (*it) = 0L;
         }
     }
diff --git a/src/osgEarthUtil/Fog.frag.glsl b/src/osgEarthUtil/Fog.frag.glsl
index 64fdb27..3e4f0e5 100644
--- a/src/osgEarthUtil/Fog.frag.glsl
+++ b/src/osgEarthUtil/Fog.frag.glsl
@@ -5,9 +5,9 @@ $GLSL_DEFAULT_PRECISION_FLOAT
 #pragma vp_location   fragment_lighting
 #pragma vp_order      1.1
 
-varying float oe_fogFactor;
+in float oe_fogFactor;
 
 void oe_fog_frag(inout vec4 color)
 {        
     color.rgb = mix( gl_Fog.color.rgb, color.rgb, oe_fogFactor);
-}
\ No newline at end of file
+}
diff --git a/src/osgEarthUtil/Fog.vert.glsl b/src/osgEarthUtil/Fog.vert.glsl
index fcd86a3..0dfeaaf 100644
--- a/src/osgEarthUtil/Fog.vert.glsl
+++ b/src/osgEarthUtil/Fog.vert.glsl
@@ -6,7 +6,7 @@ $GLSL_DEFAULT_PRECISION_FLOAT
 
 uniform int oe_fog_algo;
 
-varying float oe_fogFactor;
+out float oe_fogFactor;
 
 void oe_fog_vertex(inout vec4 vertexVIEW)
 {
@@ -28,4 +28,4 @@ void oe_fog_vertex(inout vec4 vertexVIEW)
         const float LOG2 = 1.442695;
         oe_fogFactor = clamp(exp2( -gl_Fog.density * gl_Fog.density * z * z * LOG2 ), 0.0, 1.0);
 	}	
-}
\ No newline at end of file
+}
diff --git a/src/osgEarthUtil/FractalElevationLayer b/src/osgEarthUtil/FractalElevationLayer
new file mode 100644
index 0000000..b0c7420
--- /dev/null
+++ b/src/osgEarthUtil/FractalElevationLayer
@@ -0,0 +1,154 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+ * Copyright 2016 Pelican Mapping
+ * http://osgearth.org
+ *
+ * osgEarth is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+#ifndef OSGEARTH_UTIL_FRACTAL_ELEVATION_LAYER
+#define OSGEARTH_UTIL_FRACTAL_ELEVATION_LAYER 1
+
+#include <osgEarthUtil/Common>
+#include <osgEarth/TileSource>
+#include <osgEarth/ElevationLayer>
+#include <osgEarth/LayerListener>
+#include <osgEarth/URI>
+#include <osgEarth/LandCoverLayer>
+
+namespace osgEarth { namespace Util
+{
+    using namespace osgEarth;
+
+    struct FractalElevationLayerLandCoverMapping
+    {
+        std::string className;
+        optional<float> amplitude;
+    };
+
+    typedef std::map<std::string, FractalElevationLayerLandCoverMapping> FractalElevationLayerLandCoverMap;
+
+
+    /**
+     * Serializable options to configure a FractalElevationLayer.
+     */
+    class OSGEARTHUTIL_EXPORT FractalElevationLayerOptions : public ElevationLayerOptions
+    {
+
+    public:
+        // constructor
+        FractalElevationLayerOptions(const ConfigOptions& co = ConfigOptions());
+
+        //! Simplex noise frequency
+        optional<float>& frequency() { return _frequency; }
+        const optional<float>& frequency() const { return _frequency; }
+
+        //! Simplex noise persistence
+        optional<float>& persistence() { return _persistence; }
+        const optional<float>& persistence() const { return _persistence; }
+        
+        //! Simplex noise lacunarity
+        optional<float>& lacunarity() { return _lacunarity; }
+        const optional<float>& lacunarity() const { return _lacunarity; }
+        
+        //! Reference LOD
+        optional<unsigned>& baseLOD() { return _baseLOD; }
+        const optional<unsigned>& baseLOD() const { return _baseLOD; }
+
+        //! Maximum change in elevation (total)
+        optional<float>& amplitude() { return _amplitude; }
+        const optional<float>& amplitude() const { return _amplitude; }
+
+        //! Mappings from land cover classes to amplitude values (optional)
+        FractalElevationLayerLandCoverMap& landCoverMap() { return _lcMap; }
+        const FractalElevationLayerLandCoverMap& landCoverMap() const { return _lcMap; }
+        
+        //! URI of a noise texture to use to augment to simplex noise
+        optional<URI>& noiseImageURI() { return _noiseImageURI; }
+        const optional<URI>& noiseImageURI() const { return _noiseImageURI; }
+
+    public:
+        Config getConfig() const;
+
+    protected:
+        void mergeConfig(const Config& conf) {
+            ElevationLayerOptions::mergeConfig(conf);
+            fromConfig(conf);
+        }
+
+        void fromConfig(const Config& conf);
+        
+    private:
+        optional<float> _frequency;
+        optional<float> _persistence;
+        optional<float> _lacunarity;
+        optional<unsigned> _octaves;
+        optional<unsigned> _baseLOD;
+        optional<float> _amplitude;
+        optional<URI> _noiseImageURI;
+        FractalElevationLayerLandCoverMap _lcMap;
+
+    };
+
+
+    /**
+     * Elevation layer that adds fractal noise offset values.
+     */
+    class OSGEARTHUTIL_EXPORT FractalElevationLayer : public ElevationLayer
+    {
+    public:
+        META_Layer(osgEarth, FractalElevationLayer, FractalElevationLayerOptions);
+
+        //! Create a blank layer to be configurated through options().
+        FractalElevationLayer();
+
+        //! Create a layer with initial options.
+        FractalElevationLayer(const FractalElevationLayerOptions& options);
+
+    public: // ElevationLayer
+
+        // opens the layer and returns the status
+        virtual const Status& open();
+
+        virtual void init();
+
+    protected: // Layer
+
+        // called by the map when this layer is added/removed
+        virtual void addedToMap(const class Map*);
+
+        virtual void removedFromMap(const class Map*);
+
+        virtual void createImplementation(
+            const TileKey& key,
+            osg::ref_ptr<osg::HeightField>& out_hf,
+            osg::ref_ptr<NormalMap>& out_normalMap,
+            ProgressCallback* progress);
+
+    protected:
+
+        virtual ~FractalElevationLayer();
+
+        osg::observer_ptr<LandCoverLayer> _landCover;
+        osg::observer_ptr<LandCoverDictionary> _landCoverDict;
+
+        bool _debug;
+        osg::ref_ptr<osg::Image> _noiseImage1;
+        osg::ref_ptr<osg::Image> _noiseImage2;
+
+        const FractalElevationLayerLandCoverMapping* getMapping(const LandCoverClass*) const;
+    };
+
+} } // namespace osgEarth::Util
+
+#endif // OSGEARTH_UTIL_FRACTAL_ELEVATION_LAYER
diff --git a/src/osgEarthUtil/FractalElevationLayer.cpp b/src/osgEarthUtil/FractalElevationLayer.cpp
new file mode 100644
index 0000000..a34d34e
--- /dev/null
+++ b/src/osgEarthUtil/FractalElevationLayer.cpp
@@ -0,0 +1,378 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+ * Copyright 2016 Pelican Mapping
+ * http://osgearth.org
+ *
+ * osgEarth is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+#include <osgEarthUtil/FractalElevationLayer>
+#include <osgEarth/HeightFieldUtils>
+#include <osgEarth/Map>
+#include <osgEarth/ImageUtils>
+#include <osgEarth/SimplexNoise>
+#include <cstdlib> // for getenv
+
+using namespace osgEarth;
+using namespace osgEarth::Util;
+
+#define LC "[FractalElevationLayer] "
+
+#define OE_TEST OE_DEBUG
+
+REGISTER_OSGEARTH_LAYER(fractal_elevation, FractalElevationLayer);
+
+//............................................................................
+
+namespace
+{
+    void scaleCoordsToLOD(double& u, double& v, int baseLOD, const TileKey& key)
+    {
+        double dL = (double)((int)key.getLOD() - baseLOD);
+        double factor = pow(2.0, dL); //exp2(dL) .. exp2 not available on some platforms
+        double invFactor = 1.0 / factor;
+
+        u *= invFactor;
+        v *= invFactor;
+
+        if (factor >= 1.0)
+        {
+            unsigned nx, ny;
+            key.getProfile()->getNumTiles(key.getLOD(), nx, ny);
+
+            double tx = (double)key.getTileX();
+            double ty = (double)ny - (double)key.getTileY() - 1.0;
+
+            double ax = floor(tx * invFactor);
+            double ay = floor(ty * invFactor);
+
+            double bx = ax * factor;
+            double by = ay * factor;
+
+            double cx = bx + factor;
+            double cy = by + factor;
+
+            u += (tx - bx) / (cx - bx);
+            v += (ty - by) / (cy - by);
+        }
+    }
+}
+
+//............................................................................
+
+FractalElevationLayerOptions::FractalElevationLayerOptions(const ConfigOptions& co) :
+ElevationLayerOptions(co)
+{
+    _baseLOD.init(11u);
+    _amplitude.init(5.0f);
+    _frequency.init(64.0f);
+    _persistence.init(0.5f);
+    _lacunarity.init(2.0f);
+    fromConfig(_conf);
+}
+
+void
+FractalElevationLayerOptions::fromConfig(const Config& conf)
+{
+    conf.getIfSet("base_lod", _baseLOD);
+    conf.getIfSet("amplitude", _amplitude);
+    conf.getIfSet("frequency", _frequency);
+    conf.getIfSet("persistence", _persistence);
+    conf.getIfSet("lacunarity", _lacunarity);
+    conf.getIfSet("noise_image", _noiseImageURI);
+
+    const ConfigSet& lcmap = conf.child("land_cover_mappings").children();
+    for (ConfigSet::const_iterator i = lcmap.begin(); i != lcmap.end(); ++i)
+    {
+        const Config& mapping = *i;
+        FractalElevationLayerLandCoverMapping m;
+        m.className = mapping.value("class");
+        mapping.getIfSet("amplitude", m.amplitude);
+        if (!m.className.empty() && m.amplitude.isSet())
+            _lcMap[m.className] = m;
+    }
+}
+
+Config
+FractalElevationLayerOptions::getConfig() const
+{
+    Config conf = ElevationLayerOptions::getConfig();
+    conf.key() = "fractal_elevation";
+    conf.addIfSet("base_lod", _baseLOD);
+    conf.addIfSet("amplitude", _amplitude);
+    conf.addIfSet("frequency", _frequency);
+    conf.addIfSet("persistence", _persistence);
+    conf.addIfSet("lacunarity", _lacunarity);
+    conf.addIfSet("noise_image", _noiseImageURI);
+
+    if (!_lcMap.empty())
+    {
+        Config mappings("land_cover_mappings");
+        for (FractalElevationLayerLandCoverMap::const_iterator i = _lcMap.begin(); i != _lcMap.end(); ++i)
+        {
+            Config mapping("mapping");
+            mapping.set("class", i->first);
+            mapping.set("amplitude", i->second.amplitude.get());
+            mappings.add(mapping);
+        }
+        conf.add(mappings);
+    }
+
+    return conf;
+}
+
+//............................................................................
+
+FractalElevationLayer::FractalElevationLayer() :
+ElevationLayer(&_optionsConcrete),
+_options(&_optionsConcrete)
+{
+    init();
+}
+
+FractalElevationLayer::FractalElevationLayer(const FractalElevationLayerOptions& options) :
+ElevationLayer(&_optionsConcrete),
+_options(&_optionsConcrete),
+_optionsConcrete(options)
+{
+    init();
+}
+
+FractalElevationLayer::~FractalElevationLayer()
+{
+    //todo
+}
+
+void
+FractalElevationLayer::init()
+{
+    _debug = false;
+
+    // No tile source; we will override createImplementation
+    setTileSourceExpected(false);
+
+    // Global WGS84 profile
+    setProfile(Profile::create("global-geodetic"));
+    
+    // Default the tile size to 257
+    if (!options().tileSize().isSet())
+        options().tileSize().init(257u);
+
+    // If we try to use this layer 5 LODs beyond it's baseLOD, there is a 
+    // quantization that happens and the normaly become faceted. Need to track
+    // this down, but in the meantime, limit the output to baseLOD+5.
+    if (options().maxDataLevel().isSet())
+    {
+        if (options().maxDataLevel().get() - options().baseLOD().get() > 5)
+        {
+            options().maxDataLevel() = options().baseLOD().get() + 5;
+        }
+    }
+    else
+    {
+        options().maxDataLevel() = options().baseLOD().get() + 5;
+    }
+
+
+    // Build the first noise image in memory.
+    SimplexNoise noise;
+    noise.setFrequency(options().frequency().get());
+    noise.setPersistence(options().persistence().get());
+    noise.setLacunarity(options().lacunarity().get());
+    noise.setOctaves(12u);
+    _noiseImage1 = noise.createSeamlessImage(1024u);
+
+    // Try to load a secondary noise image:
+    if (options().noiseImageURI().isSet())
+    {
+        _noiseImage2 = options().noiseImageURI()->getImage(getReadOptions());
+        if (!_noiseImage2.valid())
+        {
+            //return Status::Error(Status::ServiceUnavailable, "Failed to load noise image");
+        }
+    }
+
+    // Print info about land cover mappings.
+    if (!options().landCoverMap().empty())
+    {
+        OE_INFO << LC << "Land cover to amplitude mappings:\n";
+        for(FractalElevationLayerLandCoverMap::const_iterator i = options().landCoverMap().begin();
+            i != options().landCoverMap().end();
+            ++i)
+        {
+            const FractalElevationLayerLandCoverMapping& mapping = i->second;
+            OE_INFO << LC << "   " << i->second.className << " => " << i->second.amplitude.get() << "\n";
+        }
+    }
+
+    ElevationLayer::init();
+}
+
+const Status&
+FractalElevationLayer::open()
+{
+    return ElevationLayer::open();
+}
+
+void
+FractalElevationLayer::addedToMap(const Map* map)
+{
+    if (map)
+    {
+        _landCover = map->getLayer<LandCoverLayer>();
+        if (_landCover.valid())
+            OE_INFO << LC << "Found land cover layer.\n";
+
+        _landCoverDict = map->getLayer<LandCoverDictionary>();
+        if (_landCoverDict.valid())
+            OE_INFO << LC << "Found land cover dictionary.\n";
+    }
+}
+
+void
+FractalElevationLayer::removedFromMap(const Map* map)
+{
+    _landCover = 0L;
+    _landCoverDict = 0L;
+}
+
+void
+FractalElevationLayer::createImplementation(const TileKey& key,
+                                            osg::ref_ptr<osg::HeightField>& out_hf,
+                                            osg::ref_ptr<NormalMap>& out_normalMap,
+                                            ProgressCallback* progress)
+{
+    double min_n = FLT_MAX, max_n = -FLT_MAX;
+    double min_h = FLT_MAX, max_h = -FLT_MAX;
+    double h_mean = 0.0;
+
+    ImageUtils::PixelReader noise1(_noiseImage1.get());
+    noise1.setBilinear(true);
+
+    ImageUtils::PixelReader noise2(_noiseImage2.get());
+    noise2.setBilinear(true);
+
+    osg::ref_ptr<osg::HeightField> hf = HeightFieldUtils::createReferenceHeightField(
+        key.getExtent(), getTileSize(), getTileSize(), 0u);
+
+    // land cover tile
+    GeoImage lcTile;
+
+    osg::ref_ptr<LandCoverLayer> lcLayer;
+    _landCover.lock(lcLayer);
+
+    if (lcLayer.valid())
+    {
+        lcTile = lcLayer->createImage(key, progress);
+    }
+
+    for (int s = 0; s < getTileSize(); ++s)
+    {
+        for (int t = 0; t < getTileSize(); ++t)
+        {
+            double u = (double)s / (double)(getTileSize() - 1);
+            double v = (double)t / (double)(getTileSize() - 1);
+
+            double n = 0.0;
+            double uScaled, vScaled;
+
+            double finalScale = 4.0;
+
+            // Step 1
+            if (_noiseImage1.valid())
+            {
+                uScaled = u, vScaled = v;
+                scaleCoordsToLOD(uScaled, vScaled, options().baseLOD().get(), key);
+
+                double uMod = fmod(uScaled, 1.0);
+                double vMod = fmod(vScaled, 1.0);
+
+                n += noise1(uMod, vMod).r() - 0.5;
+                finalScale *= 0.5;
+            }
+
+            if (_noiseImage2.valid())
+            {
+                uScaled = u, vScaled = v;
+                scaleCoordsToLOD(uScaled, vScaled, options().baseLOD().get() + 3, key);
+
+                double uMod = fmod(uScaled, 1.0);
+                double vMod = fmod(vScaled, 1.0);
+                n += noise2(uMod, vMod).r() - 0.5;
+                finalScale *= 0.5;
+            }
+
+            n *= finalScale;
+
+            // default amplitude:
+            float amp = options().amplitude().get();
+
+            // if we have land cover mappings, use them:
+            if (lcTile.valid())
+            {
+                const LandCoverClass* lcClass = lcLayer->getClassByUV(lcTile, u, v);
+                if (lcClass)
+                {
+                    const FractalElevationLayerLandCoverMapping* mapping = getMapping(lcClass);
+                    if (mapping)
+                    {
+                        amp = mapping->amplitude.getOrUse(amp);
+                    }
+                }
+            }
+
+            hf->setHeight(s, t, n * amp);
+
+            if (_debug)
+            {
+                h_mean += hf->getHeight(s, t);
+                min_n = std::min(min_n, n);
+                max_n = std::max(max_n, n);
+                min_h = std::min(min_h, (double)hf->getHeight(s, t));
+                max_h = std::max(max_h, (double)hf->getHeight(s, t));
+            }
+        }
+    }
+
+    if (_debug)
+    {
+        h_mean /= double(getTileSize()*getTileSize());
+        double q_mean = 0.0;
+
+        for (int s = 0; s < getTileSize(); ++s)
+        {
+            for (int t = 0; t < getTileSize(); ++t)
+            {
+                double q = hf->getHeight(s, t) - h_mean;
+                q_mean += q*q;
+            }
+        }
+
+        double stdev = sqrt(q_mean / double(getTileSize()*getTileSize()));
+
+        OE_INFO << LC << "Tile " << key.str() << " Hmean=" << h_mean
+            << ", stdev=" << stdev << ", n[" << min_n << ", " << max_n << "] "
+            << "h[" << min_h << ", " << max_h << "]\n";
+    }
+
+    out_hf = hf.release();
+    out_normalMap = 0L;
+}
+
+const FractalElevationLayerLandCoverMapping*
+FractalElevationLayer::getMapping(const LandCoverClass* lcc) const
+{
+    if (!lcc) return 0L;
+    FractalElevationLayerLandCoverMap::const_iterator i = options().landCoverMap().find(lcc->getName());
+    return i != options().landCoverMap().end() ? &(i->second) : 0L;
+}
diff --git a/src/osgEarthUtil/GARSGraticule b/src/osgEarthUtil/GARSGraticule
new file mode 100644
index 0000000..93ee410
--- /dev/null
+++ b/src/osgEarthUtil/GARSGraticule
@@ -0,0 +1,115 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+ * Copyright 2016 Pelican Mapping
+ * http://osgearth.org
+ *
+ * osgEarth is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+#ifndef OSGEARTHANNOTATION_GARS_GRATICLE
+#define OSGEARTHANNOTATION_GARS_GRATICLE
+
+#include <osgEarth/Extension>
+#include <osgEarth/VisibleLayer>
+#include <osgEarthUtil/Common>
+#include <osgEarthSymbology/Style>
+#include <osg/ClipPlane>
+
+namespace osgEarth { namespace Util
+{
+    using namespace osgEarth;
+    using namespace osgEarth::Symbology;
+
+    /**
+     * Configuration options for the geodetic graticule.
+     */
+    class GARSGraticuleOptions : public VisibleLayerOptions
+    {
+    public:
+        GARSGraticuleOptions(const ConfigOptions& conf =ConfigOptions()) : VisibleLayerOptions(conf) {
+            fromConfig(_conf);
+        }
+        
+    public:
+        //! Style for grid zone designator geometry and text
+        optional<Style>& style() { return _style; }
+        const optional<Style>& style() const { return _style; }
+
+    public:
+        virtual Config getConfig() const {
+            Config conf = VisibleLayerOptions::getConfig();
+            conf.key() = "gars_graticule";
+            conf.addObjIfSet("style", _style);
+            return conf;
+        }
+
+        virtual void fromConfig(const Config& conf) {
+            conf.getObjIfSet("style", _style);
+        }
+
+    protected:
+        optional<Style> _style;
+
+        void mergeConfig(const Config& conf) {
+            VisibleLayerOptions::mergeConfig(conf);
+            fromConfig(conf);
+        }
+    };
+
+    /**
+     * GARS (Global Area Reference System) Graticuler map layer.
+     * http://earth-info.nga.mil/GandG/coordsys/grids/gars.html
+     */
+    class OSGEARTHUTIL_EXPORT GARSGraticule : public VisibleLayer
+    {
+    public:
+        META_Layer(osgEarthUtil, GARSGraticule, GARSGraticuleOptions);
+
+        //! Construct a default GARS graticule
+        GARSGraticule();
+
+        //! Construct a graticule with custom options
+        GARSGraticule(const GARSGraticuleOptions& options);
+
+        //! Call to refresh after setting an option
+        void dirty();
+
+    public: // Layer
+
+        virtual void addedToMap(const Map* map);
+
+        virtual void removedFromMap(const Map* map);
+        
+        virtual osg::Node* getOrCreateNode();
+
+        virtual void init();
+
+    protected:
+
+        /** dtor */
+        virtual ~GARSGraticule() { }        
+
+    private:
+
+        void setUpDefaultStyles();
+        void rebuild();
+        void build30MinCells();
+
+        UID _uid;
+        osg::ref_ptr<const Profile> _profile;
+        osg::ref_ptr<osg::ClipPlane> _clipPlane;
+        osg::ref_ptr<osg::Group> _root;
+    };  
+} } // namespace osgEarth::Util
+
+#endif // OSGEARTHANNOTATION_GARS_GRATICLE
diff --git a/src/osgEarthUtil/GARSGraticule.cpp b/src/osgEarthUtil/GARSGraticule.cpp
new file mode 100644
index 0000000..87a389b
--- /dev/null
+++ b/src/osgEarthUtil/GARSGraticule.cpp
@@ -0,0 +1,382 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+ * Copyright 2016 Pelican Mapping
+ * http://osgearth.org
+ *
+ * osgEarth is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+#include <osgEarthUtil/GARSGraticule>
+#include <osgEarthAnnotation/FeatureNode>
+#include <osgEarthFeatures/Feature>
+#include <osgEarth/PagedNode>
+
+using namespace osgEarth;
+using namespace osgEarth::Util;
+using namespace osgEarth::Annotation;
+using namespace osgEarth::Features;
+using namespace osgEarth::Symbology;
+
+namespace
+{
+    osg::BoundingSphere getBounds(const GeoExtent& extent)
+    {
+        int samples = 6;
+
+        double xSample = extent.width() / (double)samples;
+        double ySample = extent.height() / (double)samples;
+
+        osg::BoundingSphere bs;
+        for (int c = 0; c < samples+1; c++)
+        {
+            double x = extent.xMin() + (double)c * xSample;
+            for (int r = 0; r < samples+1; r++)
+            {
+                double y = extent.yMin() + (double)r * ySample;
+                osg::Vec3d world;
+
+                GeoPoint samplePoint(extent.getSRS(), x, y, 0, ALTMODE_ABSOLUTE);
+
+                GeoPoint wgs84 = samplePoint.transform(osgEarth::SpatialReference::create("epsg:4326"));
+                wgs84.toWorld(world);
+                bs.expandBy(world);
+            }
+        }
+        return bs;
+    }
+
+    enum GARSLevel
+    {
+        GARS_30,
+        GARS_15,
+        GARS_5
+    };
+
+    std::string getGARSLabel(double lon, double lat, GARSLevel level)
+    {
+        int lonCell = floor((lon - -180.0) / 0.5);
+        int latCell = floor((lat - -90.0) / 0.5);
+     
+        // Format the lon cell
+        std::stringstream buf;
+        if (lonCell < 9)
+        {
+            buf << "00";
+        }
+        else if (lonCell < 99)
+        {
+            buf << "0";
+        }
+        buf << (lonCell + 1);
+
+        // Format the lat cell
+        std::string latIndices = "ABCDEFGHJKLMNPQRSTUVWXYZ";
+        int latPrimaryIndex = latCell / latIndices.size();
+        int latSecondaryIndex = latCell - (latPrimaryIndex * latIndices.size());
+        buf << latIndices[latPrimaryIndex] << latIndices[latSecondaryIndex];
+
+        if (level == GARS_15 || level == GARS_5)
+        {
+            // Figure out the quadrant of the 15 minute cell within the parent 30 minute cell.
+            int x15Cell = floor(fmod(lon - -180.0, 0.5) / 0.25);
+            int y15Cell = floor(fmod(lat - -90.0, 0.5) / 0.25);
+            int y15CellInverted = 2 - y15Cell - 1;
+            buf << x15Cell + y15CellInverted * 2 + 1;
+
+            if (level == GARS_5)
+            {
+                int x5Cell = floor((lon - -180.0 - (lonCell * 0.5 + x15Cell * 0.25)) / 0.08333333333);
+                int y5Cell = floor((lat - -90.0 - (latCell * 0.5 + y15Cell * 0.25)) / 0.08333333333);
+                int y5CellInverted = 3 - y5Cell - 1;
+                buf << x5Cell + y5CellInverted * 3 + 1;
+
+            }
+        }
+        return buf.str();
+    }
+
+    class GridNode : public PagedNode
+    {
+    public:
+        GridNode(GARSGraticule* graticule, const GeoExtent& extent, GARSLevel level);
+
+        virtual osg::Node* loadChild();
+
+        virtual void build();
+
+        virtual osg::BoundingSphere getChildBound() const;
+
+        virtual bool hasChild() const;
+
+        GeoExtent _extent;
+        GARSGraticule* _graticule;
+        GARSLevel _level;
+    };
+
+    GridNode::GridNode(GARSGraticule* graticule, const GeoExtent& extent, GARSLevel level):
+    _graticule(graticule),
+    _extent(extent),
+    _level(level),
+    PagedNode()
+    {
+        build();
+        setupPaging();
+    }
+
+    osg::Node* GridNode::loadChild()
+    {
+        GARSLevel childLevel;
+        unsigned dim = 2;
+        if (_level == GARS_30)
+        {
+            childLevel = GARS_15;
+            dim = 2;
+        }
+        else if (_level == GARS_15)
+        {
+            childLevel = GARS_5;
+            dim = 3;
+        }
+
+        double width = _extent.width() / (double)dim;
+        double height = _extent.height() / (double)dim;
+
+        osg::Group* group = new osg::Group;
+        for (unsigned int c = 0; c < dim; c++)
+        {
+            for (unsigned int r = 0; r < dim; r++)
+            {
+                double west = _extent.west() + (double)c * width;
+                double south= _extent.south() + (double)r * height;
+                double east = west + width;
+                double north = south + height;
+
+                group->addChild(new GridNode(_graticule, GeoExtent(_extent.getSRS(), west, south, east, north), childLevel));
+
+            }
+        }
+        return group;    
+    }
+
+    void GridNode::build()
+    { 
+        Feature* feature = new Feature(new LineString(5), SpatialReference::create("wgs84"));
+        feature->getGeometry()->push_back(_extent.west(), _extent.south(), 0.0);
+        feature->getGeometry()->push_back(_extent.east(), _extent.south(), 0.0);
+        feature->getGeometry()->push_back(_extent.east(), _extent.north(), 0.0);
+        feature->getGeometry()->push_back(_extent.west(), _extent.north(), 0.0);
+        feature->getGeometry()->push_back(_extent.west(), _extent.south(), 0.0);    
+        FeatureList features;
+        features.push_back(feature);
+
+        Style style = _graticule->options().style().get();
+
+        double lon, lat;
+        _extent.getCentroid(lon, lat);
+        std::string label = getGARSLabel(lon, lat, _level);
+    
+        FeatureNode* featureNode = new FeatureNode(0L, features, style);
+        // Add the node to the attachpoint.
+        _attachPoint->addChild(featureNode);
+       
+        GeoPoint centroid(_extent.getSRS(), lon, lat, 1000.0);
+        GeoPoint west(_extent.getSRS(), _extent.west(), lat, 0.0);
+        GeoPoint east(_extent.getSRS(), _extent.east(), lat, 0.0);   
+        double widthInMeters = west.distanceTo(east);
+
+        osgText::Text* text = new osgText::Text;
+        text->setFont(osgText::readRefFontFile("arial.ttf"));
+        text->setText(label);
+
+        text->setCharacterSize(widthInMeters / (double)label.size());
+        text->setAlignment(osgText::Text::CENTER_CENTER);
+        osg::Geode* textGeode = new osg::Geode;
+        textGeode->addDrawable(text);
+
+        osg::MatrixTransform* mt = new osg::MatrixTransform;
+        mt->addChild(textGeode);
+
+        osg::Matrixd local2World;
+        centroid.createLocalToWorld(local2World);
+        mt->setMatrix(local2World);
+
+       _attachPoint->addChild(mt);
+    }
+
+    osg::BoundingSphere GridNode::getChildBound() const
+    {
+        return getBounds(_extent);
+    }
+
+    bool GridNode::hasChild() const
+    {
+        return _level != GARS_5;
+    }
+
+
+    /*******/
+    class IndexNode : public PagedNode
+    {
+    public:
+        IndexNode(GARSGraticule* graticule, const GeoExtent& extent);
+
+        virtual osg::Node* loadChild();
+
+        virtual osg::BoundingSphere getChildBound() const;
+
+        virtual bool hasChild() const;
+
+        GeoExtent _extent;
+        GARSGraticule* _graticule;
+    };
+
+    IndexNode::IndexNode(GARSGraticule* graticule, const GeoExtent& extent):
+    _graticule(graticule),
+    _extent(extent),
+    PagedNode()
+    {
+        setupPaging();
+    }
+
+    osg::Node* IndexNode::loadChild()
+    {
+        // Load the 30 minute cells.
+        osg::Group* group = new osg::Group;
+
+        int numCols = ceil(_extent.width() / 0.5);
+        int numRows = ceil(_extent.height() / 0.5);
+
+        for (int c = 0; c < numCols; c++)
+        {    
+            for (int r = 0; r < numRows; r++)
+            {
+                double west = _extent.xMin() + 0.5 * (double)c;
+                double south = _extent.yMin() + 0.5 * r;
+                group->addChild(new GridNode(_graticule, GeoExtent(_extent.getSRS(), west, south, west + 0.5, south + 0.5), GARS_30));         
+            }
+        }        
+        return group;
+    }
+
+    osg::BoundingSphere IndexNode::getChildBound() const
+    {
+        return getBounds(_extent);
+    }
+
+    bool IndexNode::hasChild() const
+    {
+        return true;
+    }
+}
+
+/*******/
+
+REGISTER_OSGEARTH_LAYER(gars_graticule, GARSGraticule);
+
+
+GARSGraticule::GARSGraticule() :
+VisibleLayer(&_optionsConcrete),
+_options(&_optionsConcrete)
+{
+    init();
+}
+
+GARSGraticule::GARSGraticule(const GARSGraticuleOptions& options) :
+VisibleLayer(&_optionsConcrete),
+_options(&_optionsConcrete),
+_optionsConcrete(options)
+{
+    init();
+}
+
+void
+GARSGraticule::dirty()
+{
+    rebuild();
+}
+
+void
+GARSGraticule::init()
+{
+    VisibleLayer::init();
+
+    osg::StateSet* ss = this->getOrCreateStateSet();
+    ss->setMode( GL_DEPTH_TEST, 0 );
+    ss->setMode( GL_LIGHTING, 0 );
+    ss->setMode( GL_BLEND, 1 );
+
+    // force it to render after the terrain.
+    ss->setRenderBinDetails(1, "RenderBin");
+    
+    if (options().style().isSet() == false)
+    {
+        options().style()->getOrCreateSymbol<LineSymbol>()->stroke()->color() = Color::Blue;
+        options().style()->getOrCreateSymbol<LineSymbol>()->tessellation() = 20;
+    }
+
+    options().style()->getOrCreateSymbol<AltitudeSymbol>()->clamping() = AltitudeSymbol::CLAMP_TO_TERRAIN;
+    options().style()->getOrCreateSymbol<AltitudeSymbol>()->technique() = AltitudeSymbol::TECHNIQUE_DRAPE;
+}
+
+void
+GARSGraticule::addedToMap(const Map* map)
+{
+    rebuild();
+}
+
+void
+GARSGraticule::removedFromMap(const Map* map)
+{
+    //nop
+}
+
+osg::Node*
+GARSGraticule::getOrCreateNode()
+{
+    if (_root.valid() == false)
+    {
+        _root = new osg::Group();
+        _root->getOrCreateStateSet()->setAttribute(new osg::Program(), osg::StateAttribute::OFF);
+        rebuild();
+    }
+
+    return _root.get();
+}
+
+void
+GARSGraticule::rebuild()
+{
+    if (_root.valid() == false)
+        return;
+
+    _root->removeChildren(0, _root->getNumChildren());
+    build30MinCells();
+}
+
+
+void GARSGraticule::build30MinCells()
+{
+    double size = 3.0;
+    unsigned numCols = ceil(360.0 / size);
+    unsigned numRows = ceil(180.0 / size);
+
+    for (unsigned c = 0; c < numCols; c++)
+    {
+        for (unsigned r = 0; r < numRows; r++)
+        {
+            double west = -180.0 + (double)c * size;
+            double south = -90.0 + (double)r * size;
+            _root->addChild(new IndexNode(this, GeoExtent(SpatialReference::create("wgs84"), west, south, west + size, south + size)));
+        }
+    }
+}
diff --git a/src/osgEarthUtil/GeodeticGraticule b/src/osgEarthUtil/GeodeticGraticule
index c24ced4..7588bef 100644
--- a/src/osgEarthUtil/GeodeticGraticule
+++ b/src/osgEarthUtil/GeodeticGraticule
@@ -16,161 +16,197 @@
  * You should have received a copy of the GNU Lesser General Public License
  * along with this program.  If not, see <http://www.gnu.org/licenses/>
  */
-#ifndef OSGEARTHUTIL_GEODETIC_GRATICLE
-#define OSGEARTHUTIL_GEODETIC_GRATICLE
+#ifndef OSGEARTHUTIL_GEODETIC_GRATICULE_H
+#define OSGEARTHUTIL_GEODETIC_GRATICULE_H
 
 #include <osgEarthUtil/Common>
+#include <osgEarthUtil/LatLongFormatter>
+#include <osgEarth/VisibleLayer>
+#include <osgEarth/TerrainEffect>
 #include <osgEarth/MapNode>
-#include <osgEarth/MapNodeObserver>
 #include <osgEarthSymbology/Style>
-#include <osgEarthFeatures/Feature>
-#include <vector>
+#include <osgEarthAnnotation/LabelNode>
+#include <osg/ClipPlane>
 
 namespace osgEarth { namespace Util
 {
     using namespace osgEarth;
-    using namespace osgEarth::Features;
+    using namespace osgEarth::Annotation;
     using namespace osgEarth::Symbology;
 
-    /**
-     * Configuration options for the geodetic graticule.
-     */
-    class OSGEARTHUTIL_EXPORT GeodeticGraticuleOptions : public ConfigOptions
+
+    class GeodeticGraticuleOptions : public VisibleLayerOptions
     {
     public:
-        GeodeticGraticuleOptions( const Config& conf =Config() );
+        //! Grid line color
+        optional<Color>& color() { return _color; }
+        const optional<Color>& color() const { return _color; }
 
-        /** dtor */
-        virtual ~GeodeticGraticuleOptions() { }
+        //! Label color
+        optional<Color>& labelColor() { return _labelColor; }
+        const optional<Color>& labelColor() const { return _labelColor; }
+
+        //! Grid line width in pixels
+        optional<float>& lineWidth() { return _lineWidth; }
+        const optional<float>& lineWidth() const { return _lineWidth; }
+
+        //! A target number of grid lines to view on screen at once.
+        optional<int>& gridLines() { return _gridLines; }
+        const optional<int>& gridLines() const { return _gridLines; }
+
+        /** Resolutions for the graticule separated by spaces
+         *  Resolutions are in degrees listed from lowest to highest resolution
+         *  For example:  10 5 2.5 1.25 */
+        optional<std::string>& resolutions() { return _resolutions; }
+        const optional<std::string>& resolutions() const { return _resolutions; }
 
     public:
-        struct Level 
+        GeodeticGraticuleOptions(const ConfigOptions& opt =ConfigOptions()) : VisibleLayerOptions( opt )
         {
-            float    _minRange, _maxRange;
-            unsigned _subdivisionFactor;
-            optional<Style> _lineStyle;
-            optional<Style> _textStyle;
-        };
-
-        /** Default style for grid lines */
-        optional<Style>& lineStyle() { return _defaultLineStyle; }
-        const optional<Style>& lineStyle() const { return _defaultLineStyle; }
-
-        /** Default style for text labels */
-        optional<Style>& textStyle() { return _defaultTextStyle; }
-        const optional<Style>& textStyle() const { return _defaultTextStyle; }
-
-        /** Subdivision levels */
-        const std::vector<Level>& levels() const { return _levels; }
-
-        /** Clear out all the levels */
-        void clearLevels();
-
-        /**
-         * Adds a new level of detail. You should only add levels in descending order of 
-         * maxRange (farthest to closest).
-         *
-         * @param maxRange
-         *      Maximum camera range for this level.
-         * @param minRange
-         *      (optional) Minimum camera range for this level. Though you can set this at any level,
-         *      it typically only makes sense to set this at the deepest level in order to
-         *      disable the graticule when you zoom closer in.
-         * @param subdivisionFactor
-         *      (optional) Number of times to subdivide each tile
-         * @param lineStyle
-         *      (optional) Style to apply to the grid lines at this level
-         * @param textStyle
-         *      (optional) Style to apply to text labels at this level
-         */
-        void addLevel( 
-            float        maxRange, 
-            float        minRange          =0.0f, 
-            unsigned     subdivisionFactor =2u, 
-            const Style& lineStyle         =Style(),
-            const Style& textStyle         =Style() );
+            _lineWidth.init(2.0f);
+            _color.init(Color(Color::Yellow, 0.5f));
+            _labelColor.init(Color::White);
+            _gridLines.init(10);
+            fromConfig( _conf );
+        }
 
     public:
-        Config getConfig() const;
+        virtual Config getConfig() const {
+            Config conf = VisibleLayerOptions::getConfig();
+            conf.key() = "geodetic_graticule";
+            conf.addIfSet("line_width", _lineWidth);
+            conf.addIfSet("color",      _color);
+            conf.addIfSet("label_color", _labelColor );
+            conf.addIfSet("grid_lines", _gridLines);
+            conf.addIfSet("resolutions", _resolutions);
+            return conf;
+        }
 
     protected:
-        optional<Style>    _defaultLineStyle;
-        optional<Style>    _defaultTextStyle;
-        std::vector<Level> _levels;
-
-        void mergeConfig( const Config& conf );
+        virtual void mergeConfig( const Config& conf ) {
+            VisibleLayerOptions::mergeConfig( conf );
+            fromConfig( conf );
+        }
+
+        void fromConfig( const Config& conf ) {
+            conf.getIfSet("line_width", _lineWidth);
+            conf.getIfSet("color",      _color);
+            conf.getIfSet("label_color", _labelColor);
+            conf.getIfSet("grid_lines", _gridLines);
+            conf.getIfSet("resolutions", _resolutions);
+        }
+
+        optional<float>       _lineWidth;
+        optional<Color>       _color;
+        optional<Color>       _labelColor;
+        optional<int>         _gridLines;
+        optional<std::string> _resolutions;
     };
 
 
     /**
-     * Implements a map graticule. 
-     * 
-     * NOTE: So far, this only works for geocentric maps.
-     * TODO: Add projected support; add text label support
+     * Graticule that shows lat/long lines and automatically places labels.
      */
-    class OSGEARTHUTIL_EXPORT GeodeticGraticule : public osg::Group, public MapNodeObserver
+    class OSGEARTHUTIL_EXPORT GeodeticGraticule : public VisibleLayer
     {
     public:
+        META_Layer(osgEarthUtil, GeodeticGraticule, GeodeticGraticuleOptions);
 
-        /**
-         * Constructs a new graticule for use with the specified map. The graticule
-         * is created with several default levels. If you call addLevel(), the 
-         * default levels are deleted.
-         *
-         * @param map
-         *      Map with which you will use this graticule
-         * @param options
-         *      Optional "options" that configure the graticule. Defaults will be used
-         *      if you don't specify this.
-         */
-        GeodeticGraticule( MapNode* mapNode );
-        GeodeticGraticule( MapNode* mapNode, const GeodeticGraticuleOptions& options);
+        //! Construct a graticule with default settings.
+        GeodeticGraticule();
 
-        /** dtor */
-        virtual ~GeodeticGraticule() { }
+        //! Construct a graticule with custom settings.
+        GeodeticGraticule(const GeodeticGraticuleOptions& options);
 
-        /** 
-         * Applies a new set of options. The graticule will be rebuilt if necessary.
-         */
-        void setOptions( const GeodeticGraticuleOptions& options );
+        //! Rebuild the graticule after changing options.
+        void dirty();
 
-        /**
-         * Gets the options with which the graticule was built.
-         */
-        const GeodeticGraticuleOptions& getOptions() const { return _options.value(); }
+    public: // Layer
 
+        virtual void addedToMap(const Map* map);
 
-    public: // osg::Node
+        virtual void removedFromMap(const Map* map);
+        
+        virtual osg::Node* getOrCreateNode();
 
-        virtual void traverse(osg::NodeVisitor& nv);
+        virtual void init();
 
-    public: // MapNodeObserver
+    public: // VisibleLayer
 
-        virtual void setMapNode( MapNode* mapNode );
+        virtual void setVisible(bool value);
 
-        virtual MapNode* getMapNode() { return _mapNode.get(); }
+    protected:
 
+        /** dtor */
+        virtual ~GeodeticGraticule() { }      
 
     private:
+
+        void setUpDefaultStyles();
+
+        void rebuild();
+
+        UID _uid;
+
         osg::ref_ptr<const Profile> _profile;
-        osg::ref_ptr<const FeatureProfile> _featureProfile;
 
-        unsigned int               _id;
-        osg::observer_ptr<MapNode> _mapNode;
-        osg::Group*                _root;
+        osg::ref_ptr<osg::ClipPlane> _clipPlane;
 
-        optional<GeodeticGraticuleOptions> _options;
+        osg::ref_ptr<osg::Group> _root;
 
-    private:
-        unsigned int getID() const { return _id; }
-        void init();
-        void rebuild();
-        osg::Node* buildTile( const TileKey& key, Map* map ) const;
-        osg::Node* buildChildren( unsigned level, unsigned x, unsigned y ) const;
+        osg::observer_ptr<const Map> _map;
+        osg::ref_ptr<TerrainEffect> _effect;
+        osg::ref_ptr<osg::NodeCallback > _callback;
+        osg::ref_ptr<LatLongFormatter> _formatter;
+        float _defaultResolution;
+        
+        osg::Vec2f _centerOffset;
 
-        friend class GeodeticGraticuleFactory;
-    };
+        bool _visible;
+        
+
+        std::vector< double > _resolutions;
+
+
+        struct CameraData
+        {
+            osg::ref_ptr<osg::StateSet> _stateset;
+            osg::ref_ptr<osg::Uniform> _resolutionUniform;
+            osg::ref_ptr<osg::StateSet> _labelStateset;
+            std::vector< osg::ref_ptr<LabelNode> > _labelPool;
+            float _resolution;
+            osg::Matrixd _lastViewMatrix;     
+            GeoExtent _viewExtent;
+            double _lon;
+            double _lat;
+            double _metersPerPixel;
+        };
+        typedef std::map<osg::Camera*, CameraData> CameraDataMap;
+        mutable CameraDataMap _cameraDataMap;
+        mutable Threading::Mutex _cameraDataMapMutex;
+        
+        CameraData& getCameraData(osg::Camera*) const;
+
+        double getResolution() const;
+        void setResolution(double resolution);
+
+        void initLabelPool(CameraData&);
+
+        std::string getText(const GeoPoint& location, bool lat);
 
+        osg::ref_ptr<MapNode> _mapNode;
+
+        void installEffect();
+        void removeEffect();
+
+        GeoExtent getViewExtent(osgUtil::CullVisitor*) const;
+
+    public:
+        
+        void updateLabels();
+        void cull(osgUtil::CullVisitor*);
+        osg::StateSet* getStateSet(osgUtil::CullVisitor* cv);
+    };  
 } } // namespace osgEarth::Util
 
-#endif // OSGEARTHUTIL_GEODETIC_GRATICLE
+#endif // OSGEARTHUTIL_GEODETIC_GRATICULE_H
diff --git a/src/osgEarthUtil/GeodeticGraticule.cpp b/src/osgEarthUtil/GeodeticGraticule.cpp
index 627589e..30eab03 100644
--- a/src/osgEarthUtil/GeodeticGraticule.cpp
+++ b/src/osgEarthUtil/GeodeticGraticule.cpp
@@ -17,476 +17,666 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>
  */
 #include <osgEarthUtil/GeodeticGraticule>
-#include <osgEarthUtil/LatLongFormatter>
-
-#include <osgEarthFeatures/GeometryCompiler>
-#include <osgEarthSymbology/Geometry>
-#include <osgEarthAnnotation/LabelNode>
+#include <osgEarthUtil/Shaders>
 
 #include <osgEarth/Registry>
 #include <osgEarth/NodeUtils>
-#include <osgEarth/Utils>
-#include <osgEarth/CullingUtils>
-#include <osgEarth/DrapeableNode>
-#include <osgEarth/ThreadingUtils>
-
-#include <OpenThreads/Mutex>
-#include <OpenThreads/ScopedLock>
-#include <osg/PagedLOD>
+#include <osgEarth/TerrainEngineNode>
+#include <osgEarth/Terrain>
+
 #include <osg/Depth>
-#include <osg/Program>
-#include <osgDB/FileNameUtils>
+#include <osg/ClipNode>
+#include <osg/ClipPlane>
 
 #define LC "[GeodeticGraticule] "
 
+#define OE_TEST OE_NULL
+
 using namespace osgEarth;
 using namespace osgEarth::Util;
-using namespace osgEarth::Features;
 using namespace osgEarth::Symbology;
-using namespace osgEarth::Annotation;
 
-static Threading::Mutex s_graticuleMutex;
-typedef std::map<unsigned int, osg::ref_ptr<GeodeticGraticule> > GeodeticGraticuleRegistry;
-static GeodeticGraticuleRegistry s_graticuleRegistry;
+REGISTER_OSGEARTH_LAYER(geodetic_graticule, GeodeticGraticule);
 
-#define GRATICULE_EXTENSION "osgearthutil_geodetic_graticule"
-#define TEXT_MARKER "t"
-#define GRID_MARKER "g"
+namespace
+{
+    // Helper class to find a MapNode and set it in the graticule 
+    // so it can install the terrain effect.
+    struct MyGroup : public osg::Group
+    {
+        GeodeticGraticule* _grat;
+        osg::ref_ptr<MapNode>& _mapNode;
 
-//---------------------------------------------------------------------------
+        MyGroup(GeodeticGraticule* grat, osg::ref_ptr<MapNode>& mapNode)
+            : _grat(grat), _mapNode(mapNode)
+        {
+            // Require an update traversal to udpate the labels.
+            setNumChildrenRequiringUpdateTraversal(1);
+        }
 
-GeodeticGraticuleOptions::GeodeticGraticuleOptions( const Config& conf ) :
-ConfigOptions( conf )
-{
-    mergeConfig( _conf );
-}
+        void traverse(osg::NodeVisitor& nv)
+        {
+            if (nv.getVisitorType() == nv.UPDATE_VISITOR)
+            {
+                if (_mapNode.valid() == false)
+                {
+                    _mapNode = osgEarth::findInNodePath<MapNode>(nv);
+                    if (_mapNode.valid() == true)
+                    {
+                        _grat->dirty();
+                    }
+                }
+                _grat->updateLabels();
+            }
 
-void
-GeodeticGraticuleOptions::mergeConfig( const Config& conf )
-{
-    //todo
-}
+            else if (nv.getVisitorType() == nv.CULL_VISITOR)
+            {
+                _grat->cull(static_cast<osgUtil::CullVisitor*>(&nv));
+            }
 
-Config
-GeodeticGraticuleOptions::getConfig() const
-{
-    Config conf = ConfigOptions::newConfig();
-    conf.key() = "geodetic_graticule";
-    //todo
-    return conf;
-}
+            osg::Group::traverse(nv);
+        }
+    };
 
-void
-GeodeticGraticuleOptions::addLevel( float maxRange, float minRange, unsigned subFactor, const Style& lineStyle, const Style& textStyle )
-{
-    Level level;
-    level._maxRange = maxRange;
-    level._minRange = minRange;
-    level._subdivisionFactor = subFactor;
-    if ( !lineStyle.empty() )
-        level._lineStyle = lineStyle;
-    if ( !textStyle.empty() )
-        level._textStyle = textStyle;
-
-    _levels.push_back(level);
+    // Cull callback installed on the terrain that applies
+    // a stateset for the proper camera.
+    struct GraticuleTerrainCallback : public osg::NodeCallback
+    {
+        osg::observer_ptr<GeodeticGraticule> _g;
+        GraticuleTerrainCallback(GeodeticGraticule* g) : _g(g) { }
+        void operator()(osg::Node* node, osg::NodeVisitor* nv)
+        {
+            bool traversed = false;
+            osg::ref_ptr<GeodeticGraticule> grat;
+            if (_g.lock(grat))
+            {
+                osgUtil::CullVisitor* cv = dynamic_cast<osgUtil::CullVisitor*>(nv);
+                if (cv)
+                {
+                    osg::StateSet* stateset = grat->getStateSet(cv);
+                    if (stateset)
+                        cv->pushStateSet(stateset);
+
+                    traverse(node, nv);
+                    traversed = true;
+
+                    if (stateset)
+                        cv->popStateSet();
+                }
+            }
+
+            if (!traversed)
+                traverse(node, nv);
+        }
+    };
+
+    const char* textFadeFS =
+        "#version 330\n"
+        "uniform mat4 osg_ViewMatrixInverse;\n"
+        "void oe_GeodeticGraticule_text_frag(inout vec4 color) { \n"
+        "    const float maxHAE = 4000.0;\n"
+        "    vec3 eye = osg_ViewMatrixInverse[3].xyz;\n"
+        "    float hae = length(eye) - 6378137.0;\n"
+        "    float alpha = clamp(hae/maxHAE, 0.0, 1.0); \n"
+        "    color.a *= alpha;\n"
+        "}\n";
 }
 
-//---------------------------------------------------------------------------
 
-GeodeticGraticule::GeodeticGraticule( MapNode* mapNode ) :
-_mapNode   ( mapNode ),
-_root      ( 0L )
+GeodeticGraticule::GeodeticGraticule() :
+VisibleLayer(&_optionsConcrete),
+_options(&_optionsConcrete)
 {
     init();
 }
 
-GeodeticGraticule::GeodeticGraticule( MapNode* mapNode, const GeodeticGraticuleOptions& options ) :
-_mapNode   ( mapNode ),
-_root      ( 0L )
+GeodeticGraticule::GeodeticGraticule(const GeodeticGraticuleOptions& options) :
+VisibleLayer(&_optionsConcrete),
+_options(&_optionsConcrete),
+_optionsConcrete(options)
 {
-    _options = options;
     init();
 }
 
 void
-GeodeticGraticule::init()
+GeodeticGraticule::dirty()
 {
-    // safely generate a unique ID for this graticule:
-    _id = Registry::instance()->createUID();
-    {
-        Threading::ScopedMutexLock lock( s_graticuleMutex );
-        s_graticuleRegistry[_id] = this;
-    }
-
-    // this will intialize the graph.
     rebuild();
 }
 
 void
-GeodeticGraticule::setMapNode( MapNode* mapNode )
+GeodeticGraticule::init()
 {
-    _mapNode = mapNode;
-    rebuild();
+    VisibleLayer::init();
+
+    _defaultResolution = 10.0/180.0;
+
+    // make the shared depth attr:
+    this->getOrCreateStateSet()->setMode(GL_DEPTH_TEST, 0);
+
+    // Read the resolutions from the config.
+    if (options().resolutions().isSet())
+    {
+        StringTokenizer tok(" ");
+        StringVector tokens;
+        tok.tokenize(*options().resolutions(), tokens);
+        for (unsigned int i = 0; i < tokens.size(); i++)
+        {
+            double r = as<double>(tokens[i], -1.0);
+            if (r > 0) 
+            {
+                _resolutions.push_back( r );
+            }
+        }
+    }
+
+    if (_resolutions.empty())
+    {
+        // Initialize the resolutions
+        _resolutions.push_back( 10.0 );
+        _resolutions.push_back( 5.0 );
+        _resolutions.push_back( 2.5 );
+        _resolutions.push_back( 1.0 );
+        _resolutions.push_back( 0.5 );
+        _resolutions.push_back( 0.25 );
+        _resolutions.push_back( 0.125 );
+        _resolutions.push_back( 0.0625 );
+        _resolutions.push_back( 0.03125 );
+
+    }
+
+    // Divide all the resolutions by 180 so they match up with the terrain effects concept of resolutions
+    for (unsigned int i = 0; i < _resolutions.size(); i++)
+    {
+        _resolutions[i] /= 180.0;
+    }
+
+    // Initialize the formatter
+    _formatter = new LatLongFormatter(osgEarth::Util::LatLongFormatter::FORMAT_DEGREES_MINUTES_SECONDS_TERSE, LatLongFormatter::USE_SYMBOLS |LatLongFormatter::USE_PREFIXES);
 }
 
 void
-GeodeticGraticule::setOptions( const GeodeticGraticuleOptions& options )
+GeodeticGraticule::addedToMap(const Map* map)
 {
-    _options = options;
+    _map = map;
     rebuild();
 }
 
 void
-GeodeticGraticule::rebuild()
+GeodeticGraticule::removedFromMap(const Map* map)
 {
-    this->removeChildren( 0, this->getNumChildren() );
-
-    if ( !getMapNode() )
+    if (_mapNode.valid())
     {
-        OE_WARN << LC << "Illegal NULL map node" << std::endl;
-        return;
+        removeEffect();
+
+        if (_callback.valid())
+        {
+            _mapNode->getTerrainEngine()->removeCullCallback(_callback.get());
+            _callback = 0L;
+        }
     }
+    _map = 0L;
+}
 
-    if ( !getMapNode()->isGeocentric() )
+osg::Node*
+GeodeticGraticule::getOrCreateNode()
+{
+    if (_root.valid() == false)
     {
-        OE_WARN << LC << "Projected map mode is not yet supported" << std::endl;
-        return;
+        _root = new MyGroup(this, _mapNode);
+        rebuild();
     }
 
-    const Profile* mapProfile = _mapNode->getMap()->getProfile();
+    return _root.get();
+}
 
-    _profile = Profile::create(
-        mapProfile->getSRS(),
-        mapProfile->getExtent().xMin(),
-        mapProfile->getExtent().yMin(),
-        mapProfile->getExtent().xMax(),
-        mapProfile->getExtent().yMax(),
-        8, 4 );
+void
+GeodeticGraticule::setVisible(bool value)
+{
+    VisibleLayer::setVisible(value);
 
-    _featureProfile = new FeatureProfile(_profile->getSRS());
+    if (getVisible())
+        installEffect();
+    else
+        removeEffect();
+}
 
-    //todo: do this right..
-    osg::StateSet* set = this->getOrCreateStateSet();
-    set->setRenderBinDetails( 9999, "RenderBin" );
-    set->setMode( GL_LIGHTING, 0 );
+void
+GeodeticGraticule::rebuild()
+{
+    // clear everything out
+    if (!_root.valid())
+        return;
 
-    // set up default options if the caller did not supply them
-    if ( !_options.isSet() )
-    {
-        _options->lineStyle() = Style();
+    // we must have the map node, if not, wait
+    if (_mapNode.valid() == false)
+        return;
 
-        LineSymbol* line = _options->lineStyle()->getOrCreate<LineSymbol>();
-        line->stroke()->color() = Color::Gray;
-        line->stroke()->width() = 1.0;
+    // also need a map; if not, wait
+    osg::ref_ptr<const Map> map;
+    if (!_map.lock(map))
+        return;
 
-        AltitudeSymbol* alt = _options->lineStyle()->getOrCreate<AltitudeSymbol>();
-        alt->verticalOffset() = NumericExpression(5000.0);
+    // start from scratch
+    _root->removeChildren( 0, _root->getNumChildren() );
 
-        _options->textStyle() = Style();
-        TextSymbol* text = _options->textStyle()->getOrCreate<TextSymbol>();
-        text->alignment() = TextSymbol::ALIGN_CENTER_CENTER;
+    // requires a geocentric map
+    if ( !map->isGeocentric() )
+    {
+        OE_WARN << LC << "Projected map mode is not yet supported" << std::endl;
+        return;
+    }
 
-        if ( _mapNode->isGeocentric() )
-        {
-            double r = _mapNode->getMapSRS()->getEllipsoid()->getRadiusEquator();
-            _options->addLevel( FLT_MAX, 0.0f, 1u );
-            double d = 4.5*r;
-            for(int i=0; i<3; i++)
-            {
-                d *= 0.5;
-                _options->addLevel( d, d*0.25 );
-            }
-        }
-        else
-        {
-            //todo?
-        }
+    if (!_callback.valid())
+    {
+        _callback = new GraticuleTerrainCallback(this);
+        _mapNode->getTerrainEngine()->addCullCallback(_callback.get());
     }
 
-    DrapeableNode* drapeable = new DrapeableNode();
-    drapeable->setDrapingEnabled( false );
-    _root = drapeable;
-    this->addChild( _root );
+    setVisible(getVisible());
+}
 
-    // need at least one level
-    if ( _options->levels().size() < 1 )
+#define RESOLUTION_UNIFORM "oe_GeodeticGraticule_resolution"
+#define COLOR_UNIFORM "oe_GeodeticGraticule_color"
+#define WIDTH_UNIFORM "oe_GeodeticGraticule_lineWidth"
+
+void
+GeodeticGraticule::installEffect()
+{
+    if (_mapNode.valid() == false)
         return;
 
-    const GeodeticGraticuleOptions::Level& level0 = _options->levels()[0];
+    // shader components
+    osg::StateSet* stateset = _mapNode->getTerrainEngine()->getSurfaceStateSet();
+    VirtualProgram* vp = VirtualProgram::getOrCreate(stateset);
+
+    // configure shaders
+    Shaders package;
+    package.load(vp, package.Graticule_Vertex);
+    package.load(vp, package.Graticule_Fragment);
+    
+    stateset->addUniform(new osg::Uniform(COLOR_UNIFORM, options().color().get()));
+    stateset->addUniform(new osg::Uniform(WIDTH_UNIFORM, options().lineWidth().get()));
+}
 
-    // build the top level cell grid.
-    unsigned tilesX, tilesY;
-    _profile->getNumTiles( 0, tilesX, tilesY );
+void
+GeodeticGraticule::removeEffect()
+{
+    if (_mapNode.valid() == false)
+        return;
 
-    for( unsigned tx = 0; tx < tilesX; ++tx )
+    osg::StateSet* stateset = _mapNode->getTerrainEngine()->getSurfaceStateSet();
+    if ( stateset )
     {
-        for( unsigned ty = 0; ty < tilesY; ++ty )
+        VirtualProgram* vp = VirtualProgram::get(stateset);
+        if ( vp )
         {
-            TileKey key( 0, tx, ty, _profile.get() );
-            osg::Node* tile = buildTile( key, getMapNode()->getMap() );
-            if ( tile )
-                _root->addChild( tile );
+            Shaders package;
+            package.unload( vp, package.Graticule_Vertex );
+            package.unload( vp, package.Graticule_Fragment );
+
+            stateset->removeUniform( COLOR_UNIFORM );
+            stateset->removeUniform( WIDTH_UNIFORM );
         }
     }
 }
 
-
-osg::Node*
-GeodeticGraticule::buildTile( const TileKey& key, Map* map ) const
+void
+GeodeticGraticule::cull(osgUtil::CullVisitor* cv)
 {
-    if ( _options->levels().size() <= key.getLevelOfDetail() )
-    {
-        OE_WARN << LC << "Tried to create cell at non-existant level " << key.getLevelOfDetail() << std::endl;
-        return 0L;
-    }
-
-    const GeodeticGraticuleOptions::Level& level = _options->levels()[key.getLevelOfDetail()]; //_levels[key.getLevelOfDetail()];
-
+    osg::Matrixd viewMatrix = *cv->getModelViewMatrix();
 
-    // the "-2" here is because normal tile paging gives you one subdivision already,
-    // so we only need to account for > 1 subdivision factor.
-    unsigned cellsPerTile = level._subdivisionFactor <= 2u ? 1u : 1u << (level._subdivisionFactor-2u);
-    unsigned cellsPerTileX = std::max(1u, cellsPerTile);
-    unsigned cellsPerTileY = std::max(1u, cellsPerTile);
+    osg::Vec3d vp = cv->getViewPoint();
 
+    CameraData& cdata = getCameraData(cv->getCurrentCamera());
 
-    GeoExtent tileExtent = key.getExtent();
-
-    FeatureList latLines;
-    FeatureList lonLines;
-
-    static LatLongFormatter s_llf(LatLongFormatter::FORMAT_DECIMAL_DEGREES);
-    
-    double cellWidth = tileExtent.width() / cellsPerTileX;
-    double cellHeight = tileExtent.height() / cellsPerTileY;
+    // Only update if the view matrix has changed.
+    if (viewMatrix != cdata._lastViewMatrix && _mapNode.valid())
+    {
+        GeoPoint eyeGeo;
+        eyeGeo.fromWorld(_mapNode->getMapSRS(), vp);
+        cdata._lon = eyeGeo.x();
+        cdata._lat = eyeGeo.y();
 
-    const Style& lineStyle = level._lineStyle.isSet() ? *level._lineStyle : *_options->lineStyle();
-    const Style& textStyle = level._textStyle.isSet() ? *level._textStyle : *_options->textStyle();
+        osg::Viewport* viewport = cv->getViewport();
 
-    bool hasText = textStyle.get<TextSymbol>() != 0L;
+        float centerX = viewport->x() + viewport->width() / 2.0;
+        float centerY = viewport->y() + viewport->height() / 2.0;
 
-    osg::ref_ptr<osg::Group> labels;
-    if ( hasText )
-    {
-        labels = new osg::Group();
-    }
+        float offsetCenterX = centerX;
+        float offsetCenterY = centerY;
 
-    // spatial ref for features:
-    const SpatialReference* geoSRS = tileExtent.getSRS()->getGeographicSRS();
+        bool hitValid = false;
 
-    // longitude lines
-    for( unsigned cx = 0; cx < cellsPerTileX; ++cx )
-    {
-        double clon = tileExtent.xMin() + cellWidth * (double)cx;
-        LineString* lon = new LineString(2);
-        lon->push_back( osg::Vec3d(clon, tileExtent.yMin(), 0) );
-        lon->push_back( osg::Vec3d(clon, tileExtent.yMax(), 0) );
-        lonLines.push_back( new Feature(lon, geoSRS) );
+        // Try the center of the screen.
+        osg::Vec3d focalPoint;
+        if (_mapNode->getTerrain()->getWorldCoordsUnderMouse(cv->getCurrentCamera()->getView(), centerX, centerY, focalPoint))
+        {
+            hitValid = true;
+        }
 
-        if ( hasText )
+        if (hitValid)
         {
-            for( unsigned cy = 0; cy < cellsPerTileY; ++cy )
-            {
-                double clat = tileExtent.yMin() + (0.5*cellHeight) + cellHeight*(double)cy;
-                LabelNode* label = new LabelNode( 
-                    _mapNode.get(),
-                    GeoPoint(geoSRS, clon, clat),
-                    s_llf.format(clon, false),
-                    textStyle );
-                labels->addChild( label );
-            }
+            GeoPoint focalGeo;
+            focalGeo.fromWorld(_mapNode->getMapSRS(), focalPoint);
+            cdata._lon = focalGeo.x();
+            cdata._lat = focalGeo.y();
+            // We only store the previous view matrix if we actually got a hit.  Otherwise we still need to update.
+            cdata._lastViewMatrix = viewMatrix;
         }
-    }
 
-    // latitude lines
-    for( unsigned cy = 0; cy < cellsPerTileY; ++cy )
-    {
-        double clat = tileExtent.yMin() + cellHeight * (double)cy;
-        if ( clat == key.getProfile()->getExtent().yMin() )
-            continue;
+        // Get the view extent.
+        cdata._viewExtent = getViewExtent(cv);
 
-        LineString* lat = new LineString(2);
-        lat->push_back( osg::Vec3d(tileExtent.xMin(), clat, 0) );
-        lat->push_back( osg::Vec3d(tileExtent.xMax(), clat, 0) );
-        latLines.push_back( new Feature(lat, geoSRS) );
+        double targetResolution = (cdata._viewExtent.height() / 180.0) / options().gridLines().get();
 
-        if ( hasText )
+        double resolution = _resolutions[0];
+        for (unsigned int i = 0; i < _resolutions.size(); i++)
         {
-            for( unsigned cx = 0; cx < cellsPerTileX; ++cx )
+            resolution = _resolutions[i];
+            if (resolution <= targetResolution)
             {
-                double clon = tileExtent.xMin() + (0.5*cellWidth) + cellWidth*(double)cy;
-                LabelNode* label = new LabelNode( 
-                    _mapNode.get(), 
-                    GeoPoint(geoSRS, clon, clat),
-                    s_llf.format(clat, true),
-                    textStyle );
-                labels->addChild( label );
+                break;
             }
         }
-    }
 
-    osg::Group* group = new osg::Group();
+        // Trippy
+        //resolution = targetResolution;
 
-    GeometryCompiler compiler;
-    osg::ref_ptr<Session> session = new Session( map );
-    FilterContext context( session.get(), _featureProfile.get(), tileExtent );
+        // Try to compute an approximate meters to pixel value at this view.
+        double dist = osg::clampAbove(eyeGeo.z(), 1.0);
+        double halfWidth;
 
-    // make sure we get sufficient tessellation:
-    compiler.options().maxGranularity() = std::min(cellWidth, cellHeight) / 16.0;
+        const osg::Matrix& proj = *cv->getProjectionMatrix();
+        bool isOrtho = osg::equivalent(proj(3,3), 1.0);
 
-    compiler.options().geoInterp() = GEOINTERP_GREAT_CIRCLE;
-    osg::Node* lonNode = compiler.compile(lonLines, lineStyle, context);
-    if ( lonNode )
-        group->addChild( lonNode );
+        if (isOrtho)
+        {
+            double L, R, B, T, N, F;
+            proj.getOrtho(L, R, B, T, N, F);
+            halfWidth = 0.5*(R-L);
+        }
+        else // perspective
+        {
+            double fovy, aspectRatio, zNear, zFar;
+            cv->getProjectionMatrix()->getPerspective(fovy, aspectRatio, zNear, zFar);
+            halfWidth = osg::absolute(tan(osg::DegreesToRadians(fovy / 2.0)) * dist);
+        }
 
-    compiler.options().geoInterp() = GEOINTERP_RHUMB_LINE;
-    osg::Node* latNode = compiler.compile(latLines, lineStyle, context);
-    if ( latNode )
-        group->addChild( latNode );
+        cdata._metersPerPixel = (2.0 * halfWidth) / (double)viewport->height();
 
-    // add the labels.
-    if ( labels.valid() )
-        group->addChild( labels.get() );
+        if (cdata._resolution != resolution)
+        {
+            cdata._resolution = (float)resolution;
+            cdata._resolutionUniform->set(cdata._resolution);
+        }
 
-    // get the geocentric tile center:
-    osg::Vec3d tileCenter;
-    tileExtent.getCentroid( tileCenter.x(), tileCenter.y() );
+        OE_TEST << "EW=" << cdata._viewExtent.width() << ", ortho=" << isOrtho << ", hW=" << halfWidth << ", res=" << resolution << ", mPP=" << cdata._metersPerPixel << std::endl;
+    }
 
-    const SpatialReference* ecefSRS = tileExtent.getSRS()->getECEF();
-    osg::Vec3d centerECEF;
-    tileExtent.getSRS()->transform( tileCenter, ecefSRS, centerECEF );
-    //tileExtent.getSRS()->transformToECEF( tileCenter, centerECEF );
+    // traverse the label pool for this camera.
+    cv->pushStateSet(cdata._labelStateset.get());
 
-    osg::NodeCallback* ccc = 0L;
-    // set up cluster culling.
-    if ( tileExtent.getSRS()->isGeographic() && tileExtent.width() < 90.0 && tileExtent.height() < 90.0 )
+    for (std::vector< osg::ref_ptr< LabelNode > >::iterator i = cdata._labelPool.begin();
+        i != cdata._labelPool.end();
+        ++i)
     {
-        ccc = ClusterCullingFactory::create( group, centerECEF );
+        i->get()->accept(*cv);
     }
 
-    // add a paging node for higher LODs:
-    if ( key.getLevelOfDetail() + 1 < _options->levels().size() )
-    {
-        const GeodeticGraticuleOptions::Level& nextLevel = _options->levels()[key.getLevelOfDetail()+1];
+    cv->popStateSet();
+}
 
-        osg::BoundingSphere bs = group->getBound();
+GeoExtent
+GeodeticGraticule::getViewExtent(osgUtil::CullVisitor* cullVisitor) const
+{
+    // Get the corners of all points on the view frustum.  Mostly modified from osgthirdpersonview
+    osg::Matrixd proj = *cullVisitor->getProjectionMatrix();
+    osg::Matrixd mv = *cullVisitor->getModelViewMatrix();
+    osg::Matrixd invmv = osg::Matrixd::inverse( mv );
 
-        std::string uri = Stringify() << key.str() << "_" << getID() << "." << GRID_MARKER << "." << GRATICULE_EXTENSION;
+    // clamp the projection far plane so it's not on the other 
+    // side of the globe
+    osg::Vec3d eye = osg::Vec3d(0,0,0) * invmv;
 
-        osg::PagedLOD* plod = new osg::PagedLOD();
-        plod->setCenter( bs.center() );
-        plod->addChild( group, std::max(level._minRange,nextLevel._maxRange), FLT_MAX );
-        plod->setFileName( 1, uri );
-        plod->setRange( 1, 0, nextLevel._maxRange );
-        group = plod;
-    }
+    const osgEarth::SpatialReference* srs = osgEarth::SpatialReference::create("epsg:4326");
 
-    // or, if this is the deepest level and there's a minRange set, we need an LOD:
-    else if ( level._minRange > 0.0f )
+    double nearPlane, farPlane;
+    double nLeft, nRight, nTop, nBottom;
+    double fLeft, fRight, fTop, fBottom;
+    
+    if (osg::equivalent(proj(3,3), 1.0)) // ORTHOGRAPHIC
+    {
+        proj.getOrtho(nLeft, nRight, nBottom, nTop, nearPlane, farPlane);
+
+        fLeft = nLeft;
+        fRight = nRight;
+        fBottom = nBottom;
+        fTop = nTop;
+
+        // In an ortho projection the near plane can be negative;
+        // That will disrupt our extent calculation, so we want to clamp
+        // it to be between the eyepoint and the far plane.
+        nearPlane = osg::clampBetween(nearPlane, 0.0, farPlane);
+        farPlane = osg::clampBetween(farPlane, 1.0, eye.length() - srs->getEllipsoid()->getRadiusPolar());
+    }
+    else
     {
-        osg::LOD* lod = new osg::LOD();
-        lod->addChild( group, level._minRange, FLT_MAX );
-        group = lod;
+        double f, a, zn, zf;
+        proj.getPerspective(f,a,zn,zf);
+        zf = std::min(zf, eye.length()-1000.0);
+        proj.makePerspective(f, a, zn, zf);
+       
+        nearPlane = proj(3,2) / (proj(2,2)-1.0);
+        farPlane = proj(3,2) / (1.0+proj(2,2));
+
+        // Get the sides of the near plane.
+        nLeft = nearPlane * (proj(2,0)-1.0) / proj(0,0);
+        nRight = nearPlane * (1.0+proj(2,0)) / proj(0,0);
+        nTop = nearPlane * (1.0+proj(2,1)) / proj(1,1);
+        nBottom = nearPlane * (proj(2,1)-1.0) / proj(1,1);
+
+        // Get the sides of the far plane.
+        fLeft = farPlane * (proj(2,0)-1.0) / proj(0,0);
+        fRight = farPlane * (1.0+proj(2,0)) / proj(0,0);
+        fTop = farPlane * (1.0+proj(2,1)) / proj(1,1);
+        fBottom = farPlane * (proj(2,1)-1.0) / proj(1,1);
     }
 
-    if ( ccc )
+    double dist = farPlane - nearPlane;
+
+    std::vector< osg::Vec3d > verts;
+    verts.reserve(9);
+
+    // Include origin?
+    //verts.push_back(osg::Vec3d(0., 0., 0. ));
+    verts.push_back(osg::Vec3d( nLeft, nBottom, -nearPlane ));
+    verts.push_back(osg::Vec3d( nRight, nBottom, -nearPlane ));
+    verts.push_back(osg::Vec3d( nRight, nTop, -nearPlane ));
+    verts.push_back(osg::Vec3d( nLeft, nTop, -nearPlane ));
+    verts.push_back(osg::Vec3d( fLeft, fBottom, -farPlane ));
+    verts.push_back(osg::Vec3d( fRight, fBottom, -farPlane ));
+    verts.push_back(osg::Vec3d( fRight, fTop, -farPlane ));
+    verts.push_back(osg::Vec3d( fLeft, fTop, -farPlane ));
+
+    // Compute the bounding sphere of the frustum.
+    osg::BoundingSphered bs;
+    for (unsigned int i = 0; i < verts.size(); i++)
     {
-        osg::Group* cccGroup = new osg::Group();
-        cccGroup->addCullCallback( ccc );
-        cccGroup->addChild( group );
-        group = cccGroup;
+        osg::Vec3d world = verts[i] * invmv;
+        bs.expandBy( world );
     }
 
-    return group;
-}
+    // Get the center of the bounding sphere
+    osgEarth::GeoPoint center;
+    center.fromWorld(srs, bs.center());
+
+    double radiusDegrees = bs.radius() / 111000.0;
+    
+    // Try to clamp the maximum radius so far out views don't go wacky.
+    radiusDegrees = osg::minimum(radiusDegrees, 90.0);
 
+    double minLon = center.x() - radiusDegrees;
+    double minLat = osg::clampAbove(center.y() - radiusDegrees, -90.0);
+    double maxLon = center.x() + radiusDegrees;
+    double maxLat = osg::clampBelow(center.y() + radiusDegrees, 90.0);
 
-osg::Node*
-GeodeticGraticule::buildChildren( unsigned level, unsigned x, unsigned y ) const
-{
-    osg::ref_ptr<MapNode> mapNodeSafe = _mapNode.get();
-    if ( mapNodeSafe.valid() )
-    {
-        TileKey parent(level, x, y, _profile.get());
-        osg::Group* g = new osg::Group();
-        for( unsigned q=0; q<4; ++q )
-        {
-            TileKey child = parent.createChildKey( q );
-            osg::Node* n = buildTile(child, mapNodeSafe->getMap() );
-            if ( n )
-                g->addChild( n );
-        }
-        return g;
-    }
-    else return 0L;
-}
+    osgEarth::GeoExtent extent(srs, minLon, minLat, maxLon, maxLat);
 
-void
-GeodeticGraticule::traverse( osg::NodeVisitor& nv )
-{
-    if ( nv.getVisitorType() == osg::NodeVisitor::CULL_VISITOR )
-    {
-    }
-    osg::Group::traverse( nv );
+    return extent;
 }
 
-/**************************************************************************/
 
-namespace osgEarth { namespace Util
+void 
+GeodeticGraticule::updateLabels()
 {
-    // OSG Plugin for loading subsequent graticule levels
-    class GeodeticGraticuleFactory : public osgDB::ReaderWriter
+    const osgEarth::SpatialReference* srs = osgEarth::SpatialReference::create("wgs84");
+    
+    Threading::ScopedMutexLock lock(_cameraDataMapMutex);
+    for (CameraDataMap::iterator i = _cameraDataMap.begin(); i != _cameraDataMap.end(); ++i)
     {
-    public:
-        GeodeticGraticuleFactory()
+        CameraData& cdata = i->second;
+
+        std::vector< GeoExtent > extents;
+        if (cdata._viewExtent.crossesAntimeridian())
+        {
+            GeoExtent first, second;
+            cdata._viewExtent.splitAcrossAntimeridian(first, second);
+            extents.push_back(first);
+            extents.push_back(second);
+        }
+        else
         {
-            supportsExtension( GRATICULE_EXTENSION, "osgEarth graticule" );
+            extents.push_back( cdata._viewExtent );
         }
 
-    public: // osgDB::ReaderWriter
+        double resDegrees = cdata._resolution * 180.0;
+        // We want half the resolution so the labels don't appear as often as the grid lines
+        resDegrees *= 2.0;
 
-        const char* className() const
+    
+        // Hide all the labels
+        for (unsigned int i = 0; i < cdata._labelPool.size(); i++)
         {
-            return "osgEarth graticule LOD loader";
+            cdata._labelPool[i]->setNodeMask(0);
         }
 
-        bool acceptsExtension(const std::string& extension) const
+        // Approximate offset in degrees
+        double degOffset = cdata._metersPerPixel / 111000.0;
+     
+        unsigned int labelIndex = 0;
+
+
+        bool done = false;
+        for (unsigned int extentIndex = 0; extentIndex < extents.size() && !done; extentIndex++)
         {
-            return osgDB::equalCaseInsensitive(extension, GRATICULE_EXTENSION);
-        }
+            GeoExtent extent = extents[extentIndex];
 
-        ReadResult readNode(const std::string& uri, const Options* options) const
-        {        
-            std::string ext = osgDB::getFileExtension( uri );
-            if ( !acceptsExtension( ext ) )
-                return ReadResult::FILE_NOT_HANDLED;
+            int minLonIndex = floor(((extent.xMin() + 180.0)/resDegrees));
+            int maxLonIndex = ceil(((extent.xMax() + 180.0)/resDegrees));
 
-            // the graticule definition is formatted: LEVEL_ID.MARKER.EXTENSION
-            std::string def = osgDB::getNameLessExtension( uri );
-            
-            std::string marker = osgDB::getFileExtension( def );
-            def = osgDB::getNameLessExtension( def );
+            int minLatIndex = floor(((extent.yMin() + 90)/resDegrees));
+            int maxLatIndex = ceil(((extent.yMax() + 90)/resDegrees));
 
-            int levelNum, x, y, id;
-            sscanf( def.c_str(), "%d/%d/%d_%d", &levelNum, &x, &y, &id );
+            // Generate horizontal labels
+            for (int i = minLonIndex; i <= maxLonIndex && !done; i++)
+            {
+                GeoPoint point(srs, -180.0 + (double)i * resDegrees, cdata._lat + (_centerOffset.y() * degOffset), 0, ALTMODE_ABSOLUTE);
+                LabelNode* label = cdata._labelPool[labelIndex++].get();
+
+                label->setNodeMask(~0u);
+                label->setPosition(point);
+                std::string text = getText( point, false);
+                label->setText( text );
+                if (labelIndex == cdata._labelPool.size() - 1)
+                {
+                    done = true;
+                }
+            }
 
-            // look up the graticule referenced in the location name:
-            GeodeticGraticule* graticule = 0L;
+            // Generate the vertical labels
+            for (int i = minLatIndex; i <= maxLatIndex && !done; i++)
             {
-                Threading::ScopedMutexLock lock( s_graticuleMutex );
-                GeodeticGraticuleRegistry::iterator i = s_graticuleRegistry.find(id);
-                if ( i != s_graticuleRegistry.end() )
-                    graticule = i->second.get();
+                GeoPoint point(srs, cdata._lon + (_centerOffset.x() * degOffset), -90.0 + (double)i * resDegrees, 0, ALTMODE_ABSOLUTE);
+                // Skip drawing labels at the poles
+                if (osg::equivalent(osg::absolute( point.y()), 90.0, 0.1))
+                {
+                    continue;
+                }
+                LabelNode* label = cdata._labelPool[labelIndex++].get();
+                label->setNodeMask(~0u);
+                label->setPosition(point);
+                std::string text = getText( point, true);
+                label->setText( text );
+                if (labelIndex == cdata._labelPool.size() - 1)
+                {
+                    done = true;
+                }
             }
+        }
+    }
+}
 
-            osg::Node* result = 0L;
-            if (graticule)
-                result = graticule->buildChildren( levelNum, x, y );
+GeodeticGraticule::CameraData&
+GeodeticGraticule::getCameraData(osg::Camera* cam) const
+{
+    Threading::ScopedMutexLock lock(_cameraDataMapMutex);
+    CameraData& cdata = _cameraDataMap[cam];
 
-            return result ? ReadResult(result) : ReadResult::ERROR_IN_READING_FILE;
-        }
-    };
-    REGISTER_OSGPLUGIN(GRATICULE_EXTENSION, GeodeticGraticuleFactory)
+    // New camera data? Initialize:
+    if (cdata._labelPool.empty())
+    {
+        cdata._stateset = new osg::StateSet();
+        cdata._resolution = _defaultResolution;
+        cdata._resolutionUniform = cdata._stateset->getOrCreateUniform(RESOLUTION_UNIFORM, osg::Uniform::FLOAT);
+        cdata._resolutionUniform->set(cdata._resolution);
+        cdata._viewExtent = GeoExtent(osgEarth::SpatialReference::create("wgs84"), -180, -90, 180, 90);
+        cdata._lat = 0.0;
+        cdata._lon = 0.0;
+        const_cast<GeodeticGraticule*>(this)->initLabelPool(cdata);
+
+        cdata._labelStateset = new osg::StateSet();
+        VirtualProgram* vp = VirtualProgram::getOrCreate(cdata._labelStateset.get());
+        vp->setFunction("oe_GeodeticGraticule_text_frag", textFadeFS, ShaderComp::LOCATION_FRAGMENT_COLORING);
+    }
+
+    return cdata;
+}
+
+std::string
+GeodeticGraticule::getText(const GeoPoint& location, bool lat)
+{ 
+    double value = lat ? location.y() : location.x();
+    return _formatter->format(value, lat);
+}
+
+
+void
+GeodeticGraticule::initLabelPool(CameraData& cdata)
+{
+    const osgEarth::SpatialReference* srs = osgEarth::SpatialReference::create("wgs84");
 
-} } // namespace osgEarth::Util
+    Style style;
+    TextSymbol* text = style.getOrCreateSymbol<TextSymbol>();
+    text->alignment() = TextSymbol::ALIGN_CENTER_CENTER;
+    text->fill()->color() = options().labelColor().get();
+    AltitudeSymbol* alt = style.getOrCreateSymbol<AltitudeSymbol>();
+    alt->clamping() = AltitudeSymbol::CLAMP_TO_TERRAIN;
 
+    unsigned int labelPoolSize = 8 * options().gridLines().get();
+    for (unsigned int i = 0; i < labelPoolSize; i++)
+    {
+        GeoPoint pt(srs, 0,0,0);
+        LabelNode* label = new LabelNode(_mapNode.get(), pt, "0,0");
+        label->setDynamic(true);
+        label->setStyle(style);
+        cdata._labelPool.push_back(label);
+    }
+}
+
+osg::StateSet*
+GeodeticGraticule::getStateSet(osgUtil::CullVisitor* cv)
+{
+    CameraData& cdata = getCameraData(cv->getCurrentCamera());
+    return cdata._stateset.get();
+}
diff --git a/src/osgEarthUtil/Graticule.frag.glsl b/src/osgEarthUtil/Graticule.frag.glsl
index 33fdfc7..bf666ad 100644
--- a/src/osgEarthUtil/Graticule.frag.glsl
+++ b/src/osgEarthUtil/Graticule.frag.glsl
@@ -1,24 +1,25 @@
 #version $GLSL_VERSION_STR
+$GLSL_DEFAULT_PRECISION_FLOAT
 
-#pragma vp_entryPoint oe_graticule_fragment
+#pragma vp_entryPoint oe_GeodeticGraticule_fragment
 #pragma vp_location   fragment_lighting
 #pragma vp_order      1.1
 
-uniform float oe_graticule_lineWidth;
-uniform float oe_graticule_resolution;
-uniform vec4  oe_graticule_color;
+uniform float oe_GeodeticGraticule_lineWidth;
+uniform float oe_GeodeticGraticule_resolution;
+uniform vec4  oe_GeodeticGraticule_color;
 uniform mat4 osg_ViewMatrixInverse;
 
-in vec2 oe_graticule_coord;
+in vec2 oe_GeodeticGraticule_coord;
 
-void oe_graticule_fragment(inout vec4 color)
+void oe_GeodeticGraticule_fragment(inout vec4 color)
 {
     // double the effective res for longitude since it has twice the span
-    vec2 gr = vec2(0.5*oe_graticule_resolution, oe_graticule_resolution);
-    vec2 distanceToLine = mod(oe_graticule_coord, gr);
-    vec2 dx = abs(dFdx(oe_graticule_coord));
-    vec2 dy = abs(dFdy(oe_graticule_coord));
-    vec2 dF = vec2(max(dx.s, dy.s), max(dx.t, dy.t)) * oe_graticule_lineWidth;
+    vec2 gr = vec2(0.5*oe_GeodeticGraticule_resolution, oe_GeodeticGraticule_resolution);
+    vec2 distanceToLine = mod(oe_GeodeticGraticule_coord, gr);
+    vec2 dx = abs(dFdx(oe_GeodeticGraticule_coord));
+    vec2 dy = abs(dFdy(oe_GeodeticGraticule_coord));
+    vec2 dF = vec2(max(dx.s, dy.s), max(dx.t, dy.t)) * oe_GeodeticGraticule_lineWidth;
 
     if ( any(lessThan(distanceToLine, dF)))
     {
@@ -31,6 +32,6 @@ void oe_graticule_fragment(inout vec4 color)
         float hae = length(eye) - 6378137.0;
         float maxHAE = 2000.0;
         float alpha = clamp(hae / maxHAE, 0.0, 1.0) * antialias;
-        color.rgb = mix(color.rgb, oe_graticule_color.rgb, oe_graticule_color.a * alpha);
+        color.rgb = mix(color.rgb, oe_GeodeticGraticule_color.rgb, oe_GeodeticGraticule_color.a * alpha);
     }
-}
\ No newline at end of file
+}
diff --git a/src/osgEarthUtil/Graticule.vert.glsl b/src/osgEarthUtil/Graticule.vert.glsl
index 85f985d..6457dbc 100644
--- a/src/osgEarthUtil/Graticule.vert.glsl
+++ b/src/osgEarthUtil/Graticule.vert.glsl
@@ -1,17 +1,17 @@
 #version $GLSL_VERSION_STR
 
-#pragma vp_entryPoint oe_graticule_vertex
+#pragma vp_entryPoint oe_GeodeticGraticule_vertex
 #pragma vp_location   vertex_view
 #pragma vp_order      0.5
 
 uniform vec4 oe_tile_key;
 out vec4 oe_layer_tilec;
-out vec2 oe_graticule_coord;
+out vec2 oe_GeodeticGraticule_coord;
 
 
-void oe_graticule_vertex(inout vec4 vertex)
+void oe_GeodeticGraticule_vertex(inout vec4 vertex)
 {
     // calculate long and lat from [0..1] across the profile:
     vec2 r = (oe_tile_key.xy + oe_layer_tilec.xy)/exp2(oe_tile_key.z);
-    oe_graticule_coord = vec2(0.5*r.x, r.y);
+    oe_GeodeticGraticule_coord = vec2(0.5*r.x, r.y);
 }
diff --git a/src/osgEarthUtil/GraticuleExtension b/src/osgEarthUtil/GraticuleExtension
deleted file mode 100644
index be86b98..0000000
--- a/src/osgEarthUtil/GraticuleExtension
+++ /dev/null
@@ -1,75 +0,0 @@
-/* -*-c++-*- */
-/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2016 Pelican Mapping
- * http://osgearth.org
- *
- * osgEarth is free software; you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>
- */
-#ifndef OSGEARTH_GRATICULE_EXTENSION
-#define OSGEARTH_GRATICULE_EXTENSION 1
-
-#include "GraticuleOptions"
-#include "GraticuleTerrainEffect"
-#include "GraticuleNode"
-#include <osgEarth/Extension>
-#include <osgEarth/MapNode>
-#include <osgEarthUtil/Controls>
-
-namespace osgEarth { namespace Util
-{
-    using namespace osgEarth;
-  
-    /**
-     * Extension for loading the graticule node on demand.
-     */
-    class GraticuleExtension : public Extension,
-                               public ExtensionInterface<MapNode>,
-                               public GraticuleOptions
-    {
-    public:
-        META_Object(osgearth_ext_graticule, GraticuleExtension);
-
-        // CTORs
-        GraticuleExtension();
-        GraticuleExtension(const GraticuleOptions& options);
-
-        // DTOR
-        virtual ~GraticuleExtension();
-
-
-    public: // Extension
-
-        virtual void setDBOptions(const osgDB::Options* dbOptions);
-
-        virtual const ConfigOptions& getConfigOptions() const { return *this; }
-
-    public: // ExtensionInterface<MapNode>
-
-        bool connect(MapNode* mapNode);
-
-        bool disconnect(MapNode* mapNode);
-
-
-    protected: // Object
-        GraticuleExtension(const GraticuleExtension& rhs, const osg::CopyOp& op) { }
-
-    private:
-        osg::ref_ptr<const osgDB::Options>     _dbOptions;
-        osg::ref_ptr<GraticuleTerrainEffect>   _effect;
-        osg::ref_ptr<GraticuleNode>            _node;
-    };
-
-} } // namespace osgEarth::Util
-
-#endif // OSGEARTH_GRATICULE_EXTENSION
diff --git a/src/osgEarthUtil/GraticuleExtension.cpp b/src/osgEarthUtil/GraticuleExtension.cpp
deleted file mode 100644
index e6718aa..0000000
--- a/src/osgEarthUtil/GraticuleExtension.cpp
+++ /dev/null
@@ -1,83 +0,0 @@
-/* -*-c++-*- */
-/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2016 Pelican Mapping
- * http://osgearth.org
- *
- * osgEarth is free software; you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>
- */
-#include "GraticuleExtension"
-#include "GraticuleTerrainEffect"
-#include "GraticuleNode"
-
-#include <osgEarth/MapNode>
-#include <osgEarth/TerrainEngineNode>
-#include <osgDB/FileNameUtils>
-
-using namespace osgEarth;
-using namespace osgEarth::Util;
-
-#define LC "[GraticuleExtension] "
-
-REGISTER_OSGEARTH_EXTENSION( osgearth_graticule, GraticuleExtension );
-
-
-GraticuleExtension::GraticuleExtension()
-{
-    //nop
-}
-
-GraticuleExtension::GraticuleExtension(const GraticuleOptions& options) :
-GraticuleOptions( options )
-{
-    //nop
-}
-
-GraticuleExtension::~GraticuleExtension()
-{
-    //nop
-}
-
-void
-GraticuleExtension::setDBOptions(const osgDB::Options* dbOptions)
-{
-    _dbOptions = dbOptions;
-}
-
-bool
-GraticuleExtension::connect(MapNode* mapNode)
-{
-    if ( !mapNode )
-    {
-        OE_WARN << LC << "Illegal: MapNode cannot be null." << std::endl;
-        return false;
-    }
-
-   _node = new GraticuleNode(mapNode, *this);
-    mapNode->addChild(_node.get());
-    
-    OE_INFO << LC << "Installed!\n";
-
-    return true;
-}
-
-bool
-GraticuleExtension::disconnect(MapNode* mapNode)
-{
-    if ( mapNode )
-    {
-        mapNode->removeChild(_node.get());
-    }
-    _node = 0L;
-    return true;
-}
diff --git a/src/osgEarthUtil/GraticuleNode b/src/osgEarthUtil/GraticuleNode
deleted file mode 100644
index 7244149..0000000
--- a/src/osgEarthUtil/GraticuleNode
+++ /dev/null
@@ -1,118 +0,0 @@
-/* -*-c++-*- */
-/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2016 Pelican Mapping
-* http://osgearth.org
-*
-* osgEarth is free software; you can redistribute it and/or modify
-* it under the terms of the GNU Lesser General Public License as published by
-* the Free Software Foundation; either version 2 of the License, or
-* (at your option) any later version.
-*
-* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
-* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
-* IN THE SOFTWARE.
-*
-* You should have received a copy of the GNU Lesser General Public License
-* along with this program.  If not, see <http://www.gnu.org/licenses/>
-*/
-#ifndef OSGEARTH_GRATICULE_NODE
-#define OSGEARTH_GRATICULE_NODE 1
-
-#include "GraticuleOptions"
-#include "GraticuleTerrainEffect"
-#include "Export"
-#include <osgEarthUtil/LatLongFormatter>
-#include <osgEarth/MapNode>
-#include <osgEarthAnnotation/LabelNode>
-
-
-namespace osgEarth { namespace Util
-{
-    using namespace osgEarth;
-    using namespace osgEarth::Annotation;
-
-
-    /**
-    * Node that manges the graticule resolution and labels
-    */
-    class OSGEARTHUTIL_EXPORT GraticuleNode : public osg::Group
-    {
-    public:
-
-        GraticuleNode(MapNode* mapNode, const GraticuleOptions& options = GraticuleOptions() );
-
-        virtual void traverse(osg::NodeVisitor& nv);
-
-        /**
-         * Gets whether the graticule is visible.
-         */
-        bool getVisible() const;
-
-        /**
-         * Sets whether the graticule is visible or not.
-         */
-        void setVisible(bool visible);
-
-        /**
-         * Gets the center offset for the labels in pixels.
-         */
-        const osg::Vec2f& getCenterOffset() const;
-
-        /**
-         * Sets the center offset for the labels in pixels.  Use this to attempt to offset the label placement so it's not at the center of the screen.
-         */
-        void setCenterOffset(const osg::Vec2f& offset);
-
-        /**
-         * Gets the list of resolutions to display in the graticule
-         */
-        std::vector< double >& getResolutions();
-
-    protected:
-        // DTOR
-        virtual ~GraticuleNode();
-
-        
-    private:
-
-        osgEarth::GeoExtent getViewExtent(osgUtil::CullVisitor* cullVisitor);
-
-        double getResolution() const;
-        void setResolution(double resolution);
-
-        void initLabelPool();
-
-        void updateLabels();
-
-        std::string getText(const GeoPoint& location, bool lat);
-
-
-        osg::observer_ptr< MapNode > _mapNode;
-        osg::ref_ptr< GraticuleTerrainEffect > _effect;
-        osg::ref_ptr< LatLongFormatter > _formatter;
-        osg::ref_ptr< osg::Uniform > _resolutionUniform;
-        float _resolution;
-        double _lon;
-        double _lat;
-        
-        osg::Vec2f _centerOffset;
-
-        GeoExtent _viewExtent;
-        osg::Vec3d _focalPoint;
-        std::vector< osg::ref_ptr< LabelNode > > _labelPool;
-        const GraticuleOptions _options;
-        bool _visible;
-        double _metersPerPixel;
-        osg::Matrixd _viewMatrix;
-
-        std::vector< double > _resolutions;
-
-    };
-
-} } // namespace osgEarth::Util
-
-#endif // OSGEARTH_GRATICULE_NODE
diff --git a/src/osgEarthUtil/GraticuleNode.cpp b/src/osgEarthUtil/GraticuleNode.cpp
deleted file mode 100644
index a2094af..0000000
--- a/src/osgEarthUtil/GraticuleNode.cpp
+++ /dev/null
@@ -1,425 +0,0 @@
-/* -*-c++-*- */
-/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2016 Pelican Mapping
-* http://osgearth.org
-*
-* osgEarth is free software; you can redistribute it and/or modify
-* it under the terms of the GNU Lesser General Public License as published by
-* the Free Software Foundation; either version 2 of the License, or
-* (at your option) any later version.
-*
-* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
-* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
-* IN THE SOFTWARE.
-*
-* You should have received a copy of the GNU Lesser General Public License
-* along with this program.  If not, see <http://www.gnu.org/licenses/>
-*/
-#include "GraticuleNode"
-#include <osgEarth/TerrainEngineNode>
-
-using namespace osgEarth;
-using namespace osgEarth::Util;
-
-#define LC "[GraticuleNode] "
-
-
-GraticuleNode::GraticuleNode(MapNode* mapNode, const GraticuleOptions& options):
-_mapNode(mapNode),
-    _resolution(10.0/180.0),
-    _options(options),
-    _lat(0.0),
-    _lon(0.0),
-    _viewExtent(osgEarth::SpatialReference::create("wgs84"), -180, -90, 180, 90),
-    _visible(true),
-    _metersPerPixel(0.0)
-{
-    setNumChildrenRequiringUpdateTraversal(1);
-
-    // Read the resolutions from the config.
-    if (options.resolutions().isSet())
-    {
-        StringTokenizer tok(" ");
-        StringVector tokens;
-        tok.tokenize(*options.resolutions(), tokens);
-        for (unsigned int i = 0; i < tokens.size(); i++)
-        {
-            double r = as<double>(tokens[i], -1.0);
-            if (r > 0) 
-            {
-                _resolutions.push_back( r );
-            }
-        }
-    }
-
-    if (_resolutions.empty())
-    {
-        // Initialize the resolutions
-        _resolutions.push_back( 10.0 );
-        _resolutions.push_back( 5.0 );
-        _resolutions.push_back( 2.5 );
-        _resolutions.push_back( 1.0 );
-        _resolutions.push_back( 0.5 );
-        _resolutions.push_back( 0.25 );
-        _resolutions.push_back( 0.125 );
-        _resolutions.push_back( 0.0625 );
-        _resolutions.push_back( 0.03125 );
-
-    }
-
-    // Divide all the resolutions by 180 so they match up with the terrain effects concept of resolutions
-    for (unsigned int i = 0; i < _resolutions.size(); i++)
-    {
-        _resolutions[i] /= 180.0;
-    }
-
-    // Create the effect and add it to the MapNode.
-    _effect = new GraticuleTerrainEffect( options, 0 );
-    _mapNode->getTerrainEngine()->addEffect( _effect );
-
-    // Initialize the formatter
-    _formatter = new LatLongFormatter(osgEarth::Util::LatLongFormatter::FORMAT_DEGREES_MINUTES_SECONDS_TERSE, LatLongFormatter::USE_SYMBOLS |LatLongFormatter::USE_PREFIXES);
-
-    // Initialize the resolution uniform
-    _resolutionUniform = mapNode->getTerrainEngine()->getSurfaceStateSet()->getOrCreateUniform(GraticuleOptions::resolutionUniformName(), osg::Uniform::FLOAT);
-    _resolutionUniform->set((float)_resolution);
-
-    initLabelPool();
-}
-
-GraticuleNode::~GraticuleNode()
-{
-    osg::ref_ptr< MapNode > mapNode = _mapNode.get();
-    if ( mapNode.valid() )
-    {
-        mapNode->getTerrainEngine()->removeEffect( _effect );
-    }
-}
-
-bool GraticuleNode::getVisible() const
-{
-    return _visible;
-}
-
-void GraticuleNode::setVisible(bool visible)
-{
-    if (_visible != visible)
-    {
-        _visible = visible;
-        if (_visible)
-        {
-            setNodeMask(~0u);
-            _mapNode->getTerrainEngine()->addEffect(_effect.get());
-            // We need to re-initilize the uniform b/c the uniform may have been removed when the effect was removed.
-            _resolutionUniform = _mapNode->getTerrainEngine()->getSurfaceStateSet()->getOrCreateUniform(GraticuleOptions::resolutionUniformName(), osg::Uniform::FLOAT);
-            _resolutionUniform->set((float)_resolution);
-        }
-        else
-        {
-            setNodeMask(0);
-            _mapNode->getTerrainEngine()->removeEffect(_effect.get());
-        }
-    }
-}
-
-void GraticuleNode::initLabelPool()
-{
-    const osgEarth::SpatialReference* srs = osgEarth::SpatialReference::create("wgs84");
-
-    Style style;
-    TextSymbol* text = style.getOrCreateSymbol<TextSymbol>();
-    text->alignment() = TextSymbol::ALIGN_CENTER_CENTER;
-    text->fill()->color() = _options.labelColor().get();
-    AltitudeSymbol* alt = style.getOrCreateSymbol<AltitudeSymbol>();
-    alt->clamping() = AltitudeSymbol::CLAMP_TO_TERRAIN;
-
-    unsigned int labelPoolSize = 8 * _options.gridLines().get();
-    for (unsigned int i = 0; i < labelPoolSize; i++)
-    {
-        GeoPoint pt(srs, 0,0,0);
-        LabelNode* label = new LabelNode(_mapNode.get(), pt, "0,0");
-        label->setDynamic(true);
-        label->setStyle(style);
-        _labelPool.push_back(label);
-        addChild(label);
-    }
-}
-
-std::vector< double >& GraticuleNode::getResolutions()
-{
-    return _resolutions;
-}
-
-const osg::Vec2f& GraticuleNode::getCenterOffset() const
-{
-    return _centerOffset;
-}
-
-
-void GraticuleNode::setCenterOffset(const osg::Vec2f& offset)
-{
-    if (_centerOffset != offset)
-    {
-        _centerOffset = offset;
-        updateLabels();
-    }
-}
-
-void GraticuleNode::updateLabels()
-{
-    const osgEarth::SpatialReference* srs = osgEarth::SpatialReference::create("wgs84");
-
-    std::vector< GeoExtent > extents;
-    if (_viewExtent.crossesAntimeridian())
-    {
-        GeoExtent first, second;
-        _viewExtent.splitAcrossAntimeridian(first, second);
-        extents.push_back(first);
-        extents.push_back(second);
-    }
-    else
-    {
-        extents.push_back( _viewExtent );
-    }
-
-    double resDegrees = _resolution * 180.0;
-    // We want half the resolution so the labels don't appear as often as the grid lines
-    resDegrees *= 2.0;
-
-    
-    // Hide all the labels
-    for (unsigned int i = 0; i < _labelPool.size(); i++)
-    {
-        _labelPool[i]->setNodeMask(0);
-    }
-
-    // Approximate offset in degrees
-    double degOffset = _metersPerPixel / 111000.0;
-     
-    unsigned int labelIndex = 0;
-
-
-    for (unsigned int extentIndex = 0; extentIndex < extents.size(); extentIndex++)
-    {
-        GeoExtent extent = extents[extentIndex];
-
-        int minLonIndex = floor(((extent.xMin() + 180.0)/resDegrees));
-        int maxLonIndex = ceil(((extent.xMax() + 180.0)/resDegrees));
-
-        int minLatIndex = floor(((extent.yMin() + 90)/resDegrees));
-        int maxLatIndex = ceil(((extent.yMax() + 90)/resDegrees));
-
-        // Generate horizontal labels
-        for (int i = minLonIndex; i <= maxLonIndex; i++)
-        {
-            GeoPoint point(srs, -180.0 + (double)i * resDegrees, _lat + (_centerOffset.y() * degOffset), 0, ALTMODE_ABSOLUTE);
-            LabelNode* label = _labelPool[labelIndex++];
-
-            label->setNodeMask(~0u);
-            label->setPosition(point);
-            std::string text = getText( point, false);
-            label->setText( text );
-            if (labelIndex == _labelPool.size() - 1)
-            {
-                return;
-            }
-        }
-
-
-
-        // Generate the vertical labels
-        for (int i = minLatIndex; i <= maxLatIndex; i++)
-        {
-            GeoPoint point(srs, _lon + (_centerOffset.x() * degOffset), -90.0 + (double)i * resDegrees, 0, ALTMODE_ABSOLUTE);
-            // Skip drawing labels at the poles
-            if (osg::equivalent(osg::absolute( point.y()), 90.0, 0.1))
-            {
-                continue;
-            }
-            LabelNode* label = _labelPool[labelIndex++];
-            label->setNodeMask(~0u);
-            label->setPosition(point);
-            std::string text = getText( point, true);
-            label->setText( text );
-            if (labelIndex == _labelPool.size() - 1)
-            {
-                return;
-            }
-        }
-    }
-}
-
-void GraticuleNode::traverse(osg::NodeVisitor& nv)
-{
-    if (nv.getVisitorType() == osg::NodeVisitor::UPDATE_VISITOR)
-    {
-        updateLabels();
-    }
-    else if (nv.getVisitorType() == osg::NodeVisitor::CULL_VISITOR)
-    {
-        osgUtil::CullVisitor* cv = static_cast<osgUtil::CullVisitor*>(&nv);
-
-        osg::Vec3d vp = cv->getViewPoint();
-
-        osg::Matrixd viewMatrix = *cv->getModelViewMatrix();
-
-        // Only update if the view matrix has changed.
-        if (viewMatrix != _viewMatrix)
-        {            
-            GeoPoint eyeGeo;
-            eyeGeo.fromWorld( _mapNode->getMapSRS(), vp );
-            _lon = eyeGeo.x();
-            _lat = eyeGeo.y();            
-
-            osg::Viewport* viewport = cv->getViewport();
-
-            float centerX = viewport->x() + viewport->width() / 2.0;
-            float centerY = viewport->y() + viewport->height() / 2.0;
-
-            float offsetCenterX = centerX;
-            float offsetCenterY = centerY;
-
-            bool hitValid = false;
-
-            // Try the center of the screen.
-            if(_mapNode->getTerrain()->getWorldCoordsUnderMouse(cv->getCurrentCamera()->getView(), centerX, centerY, _focalPoint))
-            {
-                hitValid = true;
-            }
-
-            if (hitValid)
-            {
-                GeoPoint focalGeo;
-                focalGeo.fromWorld( _mapNode->getMapSRS(), _focalPoint );
-                _lon = focalGeo.x();
-                _lat = focalGeo.y();
-                // We only store the previous view matrix if we actually got a hit.  Otherwise we still need to update.
-                _viewMatrix = viewMatrix;
-            }
-
-
-            double targetResolution = (_viewExtent.height() / 180.0) / _options.gridLines().get();
-
-            double resolution = _resolutions[0];
-            for (unsigned int i = 0; i < _resolutions.size(); i++)
-            {
-                resolution = _resolutions[i];
-                if (resolution <= targetResolution)
-                {
-                    break;
-                }
-            }
-
-            // Trippy
-            //resolution = targetResolution;
-
-            _viewExtent = getViewExtent( cv );
-
-            // Try to compute an approximate meters to pixel value at this view.
-            double fovy, aspectRatio, zNear, zFar;
-            cv->getProjectionMatrix()->getPerspective(fovy, aspectRatio, zNear, zFar);
-            double dist = osg::clampAbove(eyeGeo.z(), 1.0);
-            double halfWidth = osg::absolute( tan(osg::DegreesToRadians(fovy/2.0)) * dist );
-            _metersPerPixel = (2.0 * halfWidth) / (double)viewport->height();
-
-            if (_resolution != resolution)
-            {
-                setResolution(resolution);
-            }
-        }
-    }
-    osg::Group::traverse(nv);
-}
-
-double GraticuleNode::getResolution() const
-{
-    return _resolution;
-}
-
-void GraticuleNode::setResolution(double resolution)
-{
-    if (_resolution != resolution)
-    {
-        _resolution = resolution;
-        _resolutionUniform->set((float)_resolution);
-    }
-}
-
-std::string GraticuleNode::getText(const GeoPoint& location, bool lat)
-{ 
-    double value = lat ? location.y() : location.x();
-    return _formatter->format(value, lat);
-}
-
-osgEarth::GeoExtent GraticuleNode::getViewExtent(osgUtil::CullVisitor* cullVisitor)
-{
-    // Get the corners of all points on the view frustum.  Mostly modified from osgthirdpersonview
-    osg::Matrixd proj = *cullVisitor->getProjectionMatrix();
-    osg::Matrixd mv = *cullVisitor->getModelViewMatrix();
-    osg::Matrixd invmv = osg::Matrixd::inverse( mv );
-
-    double nearPlane = proj(3,2) / (proj(2,2)-1.0);
-    double farPlane = proj(3,2) / (1.0+proj(2,2));
-
-    // Get the sides of the near plane.
-    double nLeft = nearPlane * (proj(2,0)-1.0) / proj(0,0);
-    double nRight = nearPlane * (1.0+proj(2,0)) / proj(0,0);
-    double nTop = nearPlane * (1.0+proj(2,1)) / proj(1,1);
-    double nBottom = nearPlane * (proj(2,1)-1.0) / proj(1,1);
-
-    // Get the sides of the far plane.
-    double fLeft = farPlane * (proj(2,0)-1.0) / proj(0,0);
-    double fRight = farPlane * (1.0+proj(2,0)) / proj(0,0);
-    double fTop = farPlane * (1.0+proj(2,1)) / proj(1,1);
-    double fBottom = farPlane * (proj(2,1)-1.0) / proj(1,1);
-
-    double dist = farPlane - nearPlane;
-
-    std::vector< osg::Vec3d > verts;
-    verts.reserve(9);
-
-
-    // Include origin?
-    //verts.push_back(osg::Vec3d(0., 0., 0. ));
-    verts.push_back(osg::Vec3d( nLeft, nBottom, -nearPlane ));
-    verts.push_back(osg::Vec3d( nRight, nBottom, -nearPlane ));
-    verts.push_back(osg::Vec3d( nRight, nTop, -nearPlane ));
-    verts.push_back(osg::Vec3d( nLeft, nTop, -nearPlane ));
-    verts.push_back(osg::Vec3d( fLeft, fBottom, -farPlane ));
-    verts.push_back(osg::Vec3d( fRight, fBottom, -farPlane ));
-    verts.push_back(osg::Vec3d( fRight, fTop, -farPlane ));
-    verts.push_back(osg::Vec3d( fLeft, fTop, -farPlane ));
-
-    const osgEarth::SpatialReference* srs = osgEarth::SpatialReference::create("epsg:4326");
-
-    // Compute the bounding sphere of the frustum.
-    osg::BoundingSphered bs;
-    for (unsigned int i = 0; i < verts.size(); i++)
-    {
-        osg::Vec3d world = verts[i] * invmv;
-        bs.expandBy( world );
-    }
-
-    // Get the center of the bounding sphere
-    osgEarth::GeoPoint center;
-    center.fromWorld(srs, bs.center());
-
-    double radiusDegrees = bs.radius() /= 111000.0;
-    
-    // Try to clamp the maximum radius so far out views don't go wacky.
-    radiusDegrees = osg::minimum(radiusDegrees, 90.0);
-
-    double minLon = center.x() - radiusDegrees;
-    double minLat = osg::clampAbove(center.y() - radiusDegrees, -90.0);
-    double maxLon = center.x() + radiusDegrees;
-    double maxLat = osg::clampBelow(center.y() + radiusDegrees, 90.0);
-
-    osgEarth::GeoExtent extent(srs, minLon, minLat, maxLon, maxLat);
-    extent.normalize();
-
-    return extent;
-}
\ No newline at end of file
diff --git a/src/osgEarthUtil/GraticuleOptions b/src/osgEarthUtil/GraticuleOptions
deleted file mode 100644
index f7c76f0..0000000
--- a/src/osgEarthUtil/GraticuleOptions
+++ /dev/null
@@ -1,114 +0,0 @@
-/* -*-c++-*- */
-/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2016 Pelican Mapping
- * http://osgearth.org
- *
- * osgEarth is free software; you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>
- */
-#ifndef OSGEARTH_DRIVER_GRATICULE_OPTIONS
-#define OSGEARTH_DRIVER_GRATICULE_OPTIONS 1
-
-#include <osgEarth/Common>
-#include <osgEarth/URI>
-#include <osgEarthSymbology/Color>
-
-namespace osgEarth { namespace Util
-{
-    using namespace osgEarth;
-    using namespace osgEarth::Symbology;
-
-    /**
-     * Options governing normal mapping of the terrain.
-     */
-    class GraticuleOptions : public DriverConfigOptions // NO EXPORT; header only
-    {
-    public:
-        /** Grid line color */
-        optional<Color>& color() { return _color; }
-        const optional<Color>& color() const { return _color; }
-
-        /** Label color */
-        optional<Color>& labelColor() { return _labelColor; }
-        const optional<Color>& labelColor() const { return _labelColor; }
-
-        /** Grid line width in pixels */
-        optional<float>& lineWidth() { return _lineWidth; }
-        const optional<float>& lineWidth() const { return _lineWidth; }
-
-        /** A target number of grid lines to view on screen at once. */
-        optional<int>& gridLines() { return _gridLines; }
-        const optional<int>& gridLines() const { return _gridLines; }
-
-        /** Resolutions for the graticule separated by spaces
-         *  Resolutions are in degrees listed from lowest to highest resolution
-         *  For example:  10 5 2.5 1.25
-        */
-        optional<std::string>& resolutions() { return _resolutions; }
-        const optional<std::string>& resolutions() const { return _resolutions; }
-
-    public: // uniform names
-        static const char* resolutionUniformName() { return "oe_graticule_resolution"; }
-        static const char* colorUniformName()      { return "oe_graticule_color"; }
-        static const char* lineWidthUniformName()  { return "oe_graticule_lineWidth"; }
-
-    public:
-        GraticuleOptions( const ConfigOptions& opt =ConfigOptions() ) : DriverConfigOptions( opt )
-        {
-            setDriver( "graticule" );
-            _lineWidth.init ( 2.0f );
-            _color.init     ( Color(Color::Yellow, 0.5f) );
-            _labelColor.init ( Color::White );
-            _gridLines.init(10);
-            fromConfig( _conf );
-        }
-
-        virtual ~GraticuleOptions() { }
-
-    public:
-        Config getConfig() const {
-            Config conf = DriverConfigOptions::getConfig();
-            conf.addIfSet("line_width", _lineWidth);
-            conf.addIfSet("color",      _color);
-            conf.addIfSet("label_color", _labelColor );
-            conf.addIfSet("grid_lines", _gridLines);
-            conf.addIfSet("resolutions", _resolutions);
-            return conf;
-        }
-
-    protected:
-        void mergeConfig( const Config& conf ) {
-            DriverConfigOptions::mergeConfig( conf );
-            fromConfig( conf );
-        }
-
-    private:
-        void fromConfig( const Config& conf ) {
-            conf.getIfSet("line_width", _lineWidth);
-            conf.getIfSet("color",      _color);
-            conf.getIfSet("label_color", _labelColor);
-            conf.getIfSet("grid_lines", _gridLines);
-            conf.getIfSet("resolutions", _resolutions);
-        }
-
-        optional<float>       _lineWidth;
-        optional<Color>       _color;
-        optional<Color>       _labelColor;
-        optional<int>         _gridLines;
-        optional<std::string> _resolutions;
-    };
-
-} } // namespace osgEarth::Util
-
-#endif // OSGEARTH_DRIVER_GRATICULE_OPTIONS
-
diff --git a/src/osgEarthUtil/GraticuleTerrainEffect b/src/osgEarthUtil/GraticuleTerrainEffect
deleted file mode 100644
index 3f72542..0000000
--- a/src/osgEarthUtil/GraticuleTerrainEffect
+++ /dev/null
@@ -1,61 +0,0 @@
-/* -*-c++-*- */
-/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2012 Pelican Mapping
-* http://osgearth.org
-*
-* osgEarth is free software; you can redistribute it and/or modify
-* it under the terms of the GNU Lesser General Public License as published by
-* the Free Software Foundation; either version 2 of the License, or
-* (at your option) any later version.
-*
-* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
-* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
-* IN THE SOFTWARE.
-*
-* You should have received a copy of the GNU Lesser General Public License
-* along with this program.  If not, see <http://www.gnu.org/licenses/>
-*/
-#ifndef OSGEARTH_GRATICULE_TERRAIN_EFFECT_H
-#define OSGEARTH_GRATICULE_TERRAIN_EFFECT_H
-
-#include "GraticuleOptions"
-#include "Export"
-
-#include <osgEarth/TerrainEffect>
-#include <osg/Uniform>
-
-namespace osgEarth { namespace Util
-{
-    using namespace osgEarth;
-
-    /**
-     * Effect that applies a graticule effect to the terrain.
-     */
-    class OSGEARTHUTIL_EXPORT GraticuleTerrainEffect : public TerrainEffect
-    {
-    public:
-        /** construct a new terrain effect. */
-        GraticuleTerrainEffect(
-            const GraticuleOptions& options,
-            const osgDB::Options*   dbOptions);
-
-
-    public: // TerrainEffect interface
-
-        void onInstall(TerrainEngineNode* engine);
-
-        void onUninstall(TerrainEngineNode* engine);
-
-
-    protected:
-        const GraticuleOptions _options;
-        virtual ~GraticuleTerrainEffect() { }
-    };
-
-} } // namespace osgEarth::Util
-
-#endif // OSGEARTH_GRATICULE_TERRAIN_EFFECT_H
diff --git a/src/osgEarthUtil/GraticuleTerrainEffect.cpp b/src/osgEarthUtil/GraticuleTerrainEffect.cpp
deleted file mode 100644
index 37dea57..0000000
--- a/src/osgEarthUtil/GraticuleTerrainEffect.cpp
+++ /dev/null
@@ -1,87 +0,0 @@
-/* -*-c++-*- */
-/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2012 Pelican Mapping
-* http://osgearth.org
-*
-* osgEarth is free software; you can redistribute it and/or modify
-* it under the terms of the GNU Lesser General Public License as published by
-* the Free Software Foundation; either version 2 of the License, or
-* (at your option) any later version.
-*
-* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
-* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
-* IN THE SOFTWARE.
-*
-* You should have received a copy of the GNU Lesser General Public License
-* along with this program.  If not, see <http://www.gnu.org/licenses/>
-*/
-#include "GraticuleTerrainEffect"
-
-#include <osgEarth/VirtualProgram>
-#include <osgEarth/TerrainEngineNode>
-#include <osgEarth/ShaderLoader>
-
-#include "Shaders"
-
-#define LC "[Graticule] "
-
-using namespace osgEarth;
-using namespace osgEarth::Util;
-
-
-GraticuleTerrainEffect::GraticuleTerrainEffect(const GraticuleOptions& options,
-                                               const osgDB::Options*   dbOptions) :
-_options( options )
-{
-    //nop
-}
-
-void
-GraticuleTerrainEffect::onInstall(TerrainEngineNode* engine)
-{
-    if ( engine )
-    {
-        // shader components
-        osg::StateSet* stateset = engine->getSurfaceStateSet();
-        VirtualProgram* vp = VirtualProgram::getOrCreate(stateset);
-
-        // configure shaders
-        Shaders package;
-        package.load( vp, package.Graticule_Vertex );
-        package.load( vp, package.Graticule_Fragment);
-
-        stateset->addUniform( new osg::Uniform(
-            GraticuleOptions::resolutionUniformName(), 10.0/180.0) );
-
-        stateset->addUniform( new osg::Uniform(
-            GraticuleOptions::colorUniformName(), _options.color().get()) );
-
-        stateset->addUniform( new osg::Uniform(
-            GraticuleOptions::lineWidthUniformName(), _options.lineWidth().get()) );
-    }
-}
-
-
-void
-GraticuleTerrainEffect::onUninstall(TerrainEngineNode* engine)
-{
-    osg::StateSet* stateset = engine->getSurfaceStateSet();
-    if ( stateset )
-    {
-        VirtualProgram* vp = VirtualProgram::get(stateset);
-        if ( vp )
-        {
-            Shaders package;
-            package.unload( vp, package.Graticule_Vertex );
-            package.unload( vp, package.Graticule_Fragment );
-
-            stateset->removeUniform( GraticuleOptions::resolutionUniformName() );
-            stateset->removeUniform( GraticuleOptions::colorUniformName() );
-            stateset->removeUniform( GraticuleOptions::lineWidthUniformName() );
-        }
-    }
-}
diff --git a/src/osgEarthUtil/HTM b/src/osgEarthUtil/HTM
index d3ad97a..cd78576 100644
--- a/src/osgEarthUtil/HTM
+++ b/src/osgEarthUtil/HTM
@@ -28,15 +28,23 @@
 #include <osg/Group>
 #include <osg/Polytope>
 #include <vector>
+#include <osgEarth/optional>
 
 namespace osgEarth { namespace Util
 {
+    using namespace osgEarth;
+
     struct HTMSettings
     {
-        unsigned _maxLeaves;
-        float _maxLeafRange;
+        unsigned _maxObjectsPerCell;
+        optional<float> _rangeFactor;
+        optional<float> _maxRange;
+        float _minCellSize;
+        float _maxCellSize;
+        bool _storeObjectsInLeavesOnly;
         int _debugCount;
         int _debugFrame;
+        bool _debugGeom;
     };
 
     /**
@@ -51,21 +59,31 @@ namespace osgEarth { namespace Util
     public:
         HTMGroup();
 
-        /**
-         * Sets the maximum number of leaf nodes that can exist at a
-         * given level in the graph before it subdivides into a more refined
-         * index. The default is 16.
-         */
-        void setMaxLeaves(unsigned value);
-        float getMaxLeaves() const { return _settings._maxLeaves; }
+        //! Sets the maximum number of objects that can live in a single cell.
+        void setMaximumObjectsPerCell(unsigned value) { _settings._maxObjectsPerCell = value; }
+        float getMaximumObjectsPerCell() const { return _settings._maxObjectsPerCell; }
+
+        //! Minimum size (bounding radius*2) of an HTM cell.
+        void setMinimumCellSize(double value) { _settings._minCellSize = value; }
+        double getMinimumCellSize() const { return _settings._minCellSize; }
+
+        //! Maximum size (bounding radius*2) of an HTM cell that can contain objects.
+        void setMaximumCellSize(double value) { _settings._maxCellSize = value; }
+        double getMaximumCellSize() const { return _settings._maxCellSize; }
+
+        //! Cells become visible at (cell bounding radius * range factor).
+        //! If this is set, the value in setRange is ignored.
+        void setRangeFactor(float value) { _settings._rangeFactor = value; }
+
+        //! Range at which objects become visible. This overrides range factor if set.
+        void setMaxRange(float value) { _settings._maxRange = value; }
 
-        /**
-         * Sets the maximum visibility range of any leaf node in this graph.
-         * @param[in ] range Maximum visibile range (meters); default is 50,000m.
-         */
-        void setMaxLeafRange(float range);
-        float getMaxLeafRange() const { return _settings._maxLeafRange; }
+        //! If true, only store objects in the leaf nodes (defaults to false)
+        void setStoreObjectsInLeavesOnly(bool value) { _settings._storeObjectsInLeavesOnly = value; }
 
+        //! Enable debugging geometry
+        void setDebug(bool value) { _settings._debugGeom = value; }
+        bool getDebug() const { return _settings._debugGeom; }
 
     public: // osg::Group
 
@@ -101,7 +119,8 @@ namespace osgEarth { namespace Util
     {
     public:
         HTMNode(HTMSettings& settings,
-                const osg::Vec3d& v0, const osg::Vec3d& v1, const osg::Vec3d& v2);
+                const osg::Vec3d& v0, const osg::Vec3d& v1, const osg::Vec3d& v2,
+                const std::string& id = "");
         
         bool contains(const osg::Vec3d& p) const {
             return _tri.contains(p);
@@ -149,6 +168,7 @@ namespace osgEarth { namespace Util
         Triangle _tri;
         bool     _isLeaf;
         HTMSettings& _settings;
+        osg::ref_ptr<osg::Node> _debug;
     };
 
 } } // namesapce osgEarth::Util
diff --git a/src/osgEarthUtil/HTM.cpp b/src/osgEarthUtil/HTM.cpp
index 6efbee1..1c8c455 100644
--- a/src/osgEarthUtil/HTM.cpp
+++ b/src/osgEarthUtil/HTM.cpp
@@ -26,6 +26,7 @@
 #include <osgEarthAnnotation/LabelNode>
 #include <osg/Geometry>
 #include <osgText/Text>
+#include <osgEarth/DrapeableNode>
 
 using namespace osgEarth;
 using namespace osgEarth::Util;
@@ -92,11 +93,53 @@ HTMNode::Triangle::getMidpoints(osg::Vec3d* w) const
 //-----------------------------------------------------------------------
 
 HTMNode::HTMNode(HTMSettings& settings,
-                 const osg::Vec3d& v0, const osg::Vec3d& v1, const osg::Vec3d& v2) :
+                 const osg::Vec3d& v0, const osg::Vec3d& v1, const osg::Vec3d& v2,
+                 const std::string& id) :
 _settings(settings)
 {
+    setName(id);
+
     _isLeaf = true;
     _tri.set( v0, v1, v2 );
+
+    if (settings._debugGeom)
+    {
+        const double R = SpatialReference::get("wgs84")->getEllipsoid()->getRadiusEquator();
+
+        osg::Geometry* geom = new osg::Geometry();
+        geom->setUseVertexBufferObjects(true);
+    
+        osg::Vec3Array* verts = new osg::Vec3Array();
+        for (double t=0.0; t<1.0; t+=0.05)
+            verts->push_back((v0 + (v1-v0)*t) * R);
+        for (double t=0.0; t<1.0; t+=0.05)
+            verts->push_back((v1 + (v2-v1)*t) * R);
+        for (double t=0.0; t<1.0; t+=0.05)
+            verts->push_back((v2 + (v0-v2)*t) * R);
+        geom->setVertexArray(verts);
+
+        osg::Vec4Array* color = new osg::Vec4Array();
+        color->push_back(osg::Vec4(1,1,0,1));
+        color->setBinding(color->BIND_OVERALL);
+        geom->setColorArray(color);
+
+        geom->addPrimitiveSet(new osg::DrawArrays(GL_LINE_LOOP, 0, verts->size()));
+        geom->getOrCreateStateSet()->setAttribute(new osg::Program(), osg::StateAttribute::PROTECTED);
+
+        osgText::Text* text = new osgText::Text();
+        text->setText(Stringify() << getName() << "\nS=" << geom->getBound().radius()*2.0);
+        text->setPosition((v0+v1+v2)/3 * R);
+        text->setCharacterSizeMode(text->SCREEN_COORDS);
+        text->setCharacterSize(48);
+        text->setAutoRotateToScreen(true);
+        text->setAlignment(text->CENTER_CENTER);
+        text->getOrCreateStateSet()->setAttribute(new osg::Program(), osg::StateAttribute::PROTECTED);
+
+        DrapeableNode* drape = new DrapeableNode();
+        drape->addChild(geom);
+        drape->addChild(text);
+        _debug = drape;
+    }
 }
 
 void
@@ -104,6 +147,7 @@ HTMNode::traverse(osg::NodeVisitor& nv)
 {
     if ( nv.getVisitorType() == nv.CULL_VISITOR )
     {
+        //OE_INFO << getName() << std::endl;
 #if 0
         if ( _isLeaf )
         {
@@ -119,14 +163,40 @@ HTMNode::traverse(osg::NodeVisitor& nv)
 
         const osg::BoundingSphere& bs = getBound();
 
-        if ( nv.getDistanceToViewPoint(bs.center(), true) <= (bs.radius() + _settings._maxLeafRange) )
+        float range = nv.getDistanceToViewPoint(bs.center(), true);
+        bool inRange = false;
+
+        if (_settings._maxRange.isSet() == false)
+        {
+            inRange = range < (bs.radius() * _settings._rangeFactor.get());
+        }
+        else
+        {
+            inRange = range < (bs.radius() + _settings._maxRange.get());
+        }
+
+        if ( inRange )
         {
             osg::Group::traverse( nv );
+            
+            if (_debug.valid() && _isLeaf)
+            {
+                _debug->accept(nv);
+            }
+        }
+        else if (_debug.valid())
+        {
+            _debug->accept(nv);
         }
     }
     else
     {
-        osg::Group::traverse( nv );
+        if (_debug.valid())
+        {
+            _debug->accept(nv);
+        }
+
+        osg::Group::traverse( nv );        
     }
 }
 
@@ -135,8 +205,11 @@ HTMNode::insert(osg::Node* node)
 {
     if ( _isLeaf )
     {
-        if ((getNumChildren() < _settings._maxLeaves) ||
-            (getBound().radius() < _settings._maxLeafRange))
+        bool roomForMoreObjects = (getNumChildren() < _settings._maxObjectsPerCell);
+        bool underMaxCellSize = getBound().radius()*2.0 < _settings._maxCellSize;
+        bool reachedMinCellSize = getBound().radius()*2.0 <= _settings._minCellSize;
+
+        if ((underMaxCellSize && roomForMoreObjects) || reachedMinCellSize)
         {
             addChild( node );
         }
@@ -152,7 +225,8 @@ HTMNode::insert(osg::Node* node)
     {
         const osg::Vec3d& p = node->getBound().center();
 
-        for(unsigned i=0; i<_children.size(); ++i)
+        // last four children are the subcells
+        for (int i = _children.size() - 1; i >= _children.size()-4; --i)
         {
             HTMNode* child = dynamic_cast<HTMNode*>(_children[i].get());
             if ( child && child->contains(p) )
@@ -176,34 +250,36 @@ HTMNode::split()
 
     // split into four children, each wound CCW
     HTMNode* c[4];
-    c[0] = new HTMNode(_settings, _tri._v[0], w[0], w[2]);
-    c[1] = new HTMNode(_settings, _tri._v[1], w[1], w[0]);
-    c[2] = new HTMNode(_settings, _tri._v[2], w[2], w[1]);
-    c[3] = new HTMNode(_settings, w[0], w[1], w[2]);
+    c[0] = new HTMNode(_settings, _tri._v[0], w[0], w[2], Stringify()<< getName() << "0");
+    c[1] = new HTMNode(_settings, _tri._v[1], w[1], w[0], Stringify() << getName() << "1");
+    c[2] = new HTMNode(_settings, _tri._v[2], w[2], w[1], Stringify() << getName() << "2");
+    c[3] = new HTMNode(_settings, w[0], w[1], w[2], Stringify() << getName() << "3");
     
-    // distibute the data amongst the children
-    for(osg::NodeList::iterator i = _children.begin(); i != _children.end(); ++i)
+    if (_settings._storeObjectsInLeavesOnly == true)
     {
-        osg::Node* node = i->get();        
-        const osg::Vec3d& p = node->getBound().center();
-
-        for(unsigned j=0; j<4; ++j)
+        // distibute the data amongst the children
+        for(osg::NodeList::iterator i = _children.begin(); i != _children.end(); ++i)
         {
-            if ( c[j]->contains(p) )
+            osg::Node* node = i->get();        
+            const osg::Vec3d& p = node->getBound().center();
+
+            for(unsigned j=0; j<4; ++j)
             {
-                c[j]->insert( node );
-                break;
+                if ( c[j]->contains(p) )
+                {
+                    c[j]->insert( node );
+                    break;
+                }
             }
         }
-    }
 
-    // remove the leaves from this node
-    osg::Group::removeChildren(0, getNumChildren());
+        // remove the leaves from this node
+        osg::Group::removeChildren(0, getNumChildren());
+    }
 
     // add the new subnodes to this node.
     for(unsigned i=0; i<4; ++i)
     {
-        c[i]->setName( Stringify() << getName() << i );
         osg::Group::addChild( c[i] );
     }
 
@@ -214,8 +290,12 @@ HTMNode::split()
 
 HTMGroup::HTMGroup()
 {
-    _settings._maxLeaves = 16;
-    _settings._maxLeafRange = 50000.0f;
+    _settings._maxObjectsPerCell = 128;
+    _settings._rangeFactor.init( 7.0f );
+    _settings._debugGeom = true;
+    _settings._minCellSize = 10000;
+    _settings._maxCellSize = 500000;
+    _settings._storeObjectsInLeavesOnly = false;
 
     // hopefully prevent the OSG optimizer from altering this graph:
     setDataVariance( osg::Object::DYNAMIC );
@@ -224,22 +304,6 @@ HTMGroup::HTMGroup()
 }
 
 void
-HTMGroup::setMaxLeaves(unsigned maxLeaves)
-{
-    _settings._maxLeaves = maxLeaves;
-
-    reinitialize();
-}
-
-void
-HTMGroup::setMaxLeafRange(float range)
-{
-    _settings._maxLeafRange = range;
-
-    reinitialize();
-}
-
-void
 HTMGroup::reinitialize()
 {
     _children.clear();
@@ -257,14 +321,14 @@ HTMGroup::reinitialize()
     osg::Vec3d v5( 0,   0,  -rz);     // lat=-90  long=  0
 
     // CCW triangles.
-    osg::Group::addChild( new HTMNode(_settings, v0, v1, v2) );
-    osg::Group::addChild( new HTMNode(_settings, v0, v2, v3) );
-    osg::Group::addChild( new HTMNode(_settings, v0, v3, v4) );
-    osg::Group::addChild( new HTMNode(_settings, v0, v4, v1) );
-    osg::Group::addChild( new HTMNode(_settings, v5, v1, v4) );
-    osg::Group::addChild( new HTMNode(_settings, v5, v4, v3) );
-    osg::Group::addChild( new HTMNode(_settings, v5, v3, v2) );
-    osg::Group::addChild( new HTMNode(_settings, v5, v2, v1) );
+    osg::Group::addChild( new HTMNode(_settings, v0, v1, v2, "0") );
+    osg::Group::addChild( new HTMNode(_settings, v0, v2, v3, "1") );
+    osg::Group::addChild( new HTMNode(_settings, v0, v3, v4, "2") );
+    osg::Group::addChild( new HTMNode(_settings, v0, v4, v1, "3") );
+    osg::Group::addChild( new HTMNode(_settings, v5, v1, v4, "4") );
+    osg::Group::addChild( new HTMNode(_settings, v5, v4, v3, "5") );
+    osg::Group::addChild( new HTMNode(_settings, v5, v3, v2, "6") );
+    osg::Group::addChild( new HTMNode(_settings, v5, v2, v1, "7") );
 }
 
 bool
diff --git a/src/osgEarthUtil/LODBlending.cpp b/src/osgEarthUtil/LODBlending.cpp
index b14d715..973aa18 100644
--- a/src/osgEarthUtil/LODBlending.cpp
+++ b/src/osgEarthUtil/LODBlending.cpp
@@ -61,11 +61,11 @@ namespace
         "uniform float oe_tile_birthtime; \n"
         "uniform float oe_lodblend_delay; \n"
         "uniform float oe_lodblend_duration; \n"
-
         "uniform mat4 oe_layer_parent_texmat; \n"
-        "varying vec4 oe_layer_texc; \n"
-        "varying vec4 oe_lodblend_texc; \n"
-        "varying float oe_lodblend_r; \n"
+
+        "out vec4 oe_layer_texc; \n"
+        "out vec4 oe_lodblend_texc; \n"
+        "out float oe_lodblend_r; \n"
 
         "void oe_lodblend_imagery_vertex(inout vec4 VertexVIEW) \n"
         "{ \n"
@@ -86,9 +86,10 @@ namespace
         "#version " GLSL_VERSION_STR "\n"
         GLSL_DEFAULT_PRECISION_FLOAT "\n"
 
-        "attribute vec4 oe_terrain_attr; \n"
-        "attribute vec4 oe_terrain_attr2; \n"
-        "varying vec3 vp_Normal; \n"
+        "in vec4 oe_terrain_attr; \n"
+        "in vec4 oe_terrain_attr2; \n"
+        
+        "vec3 vp_Normal; \n"
 
         "uniform float oe_min_tile_range_factor; \n"
         "uniform vec4 oe_tile_key; \n"
@@ -123,9 +124,10 @@ namespace
         "#version " GLSL_VERSION_STR "\n"
         GLSL_DEFAULT_PRECISION_FLOAT "\n"
 
-        "attribute vec4 oe_terrain_attr; \n"
-        "attribute vec4 oe_terrain_attr2; \n"
-        "varying vec3 vp_Normal; \n"
+        "in vec4 oe_terrain_attr; \n"
+        "in vec4 oe_terrain_attr2; \n"
+
+        "vec3 vp_Normal; \n"
 
         "uniform float oe_min_tile_range_factor; \n"
         "uniform vec4 oe_tile_key; \n"
@@ -134,11 +136,11 @@ namespace
         "uniform float oe_lodblend_delay; \n"
         "uniform float oe_lodblend_duration; \n"
         "uniform float oe_lodblend_vscale; \n"
-
         "uniform mat4 oe_layer_parent_texmat; \n"
-        "varying vec4 oe_layer_texc; \n"
-        "varying vec4 oe_lodblend_texc; \n"
-        "varying float oe_lodblend_r; \n"
+
+        "out vec4 oe_layer_texc; \n"
+        "out vec4 oe_lodblend_texc; \n"
+        "out float oe_lodblend_r; \n"
 
         "void oe_lodblend_combined_vertex(inout vec4 VertexMODEL) \n"
         "{ \n"
@@ -172,15 +174,16 @@ namespace
 
         "uniform vec4 oe_tile_key; \n"
         "uniform int oe_layer_uid; \n"
-        "varying vec4 oe_lodblend_texc; \n"
-        "varying float oe_lodblend_r; \n"
         "uniform sampler2D oe_layer_tex_parent; \n"
 
+        "in vec4 oe_lodblend_texc; \n"
+        "in float oe_lodblend_r; \n"
+
         "void oe_lodblend_imagery_fragment(inout vec4 color) \n"
         "{ \n"
         "    if ( oe_layer_uid >= 0 ) \n"
         "    { \n"
-        "        vec4 texel = texture2D(oe_layer_tex_parent, oe_lodblend_texc.st); \n"
+        "        vec4 texel = texture(oe_layer_tex_parent, oe_lodblend_texc.st); \n"
         "        float enable = step(0.09, texel.a); \n"          // did we get a parent texel?
         "        texel.rgb = mix(color.rgb, texel.rgb, enable); \n" // if not, use the incoming color for the blend
         "        texel.a = mix(0.0, color.a, enable); \n"           // ...and blend from alpha=0 for a fade-in effect.
@@ -189,6 +192,11 @@ namespace
         "} \n";
 }
 
+LODBlending::LODBlending()
+{
+    init();
+}
+
 
 LODBlending::LODBlending(const LODBlendingOptions& options) :
 LODBlendingOptions(options)
diff --git a/src/osgEarthUtil/LinearLineOfSight.cpp b/src/osgEarthUtil/LinearLineOfSight.cpp
index dc65500..f3b1a74 100644
--- a/src/osgEarthUtil/LinearLineOfSight.cpp
+++ b/src/osgEarthUtil/LinearLineOfSight.cpp
@@ -21,7 +21,7 @@
 */
 #include <osgEarthUtil/LinearLineOfSight>
 #include <osgEarth/TerrainEngineNode>
-#include <osgEarth/DPLineSegmentIntersector>
+#include <osgUtil/LineSegmentIntersector>
 #include <osgSim/LineOfSight>
 #include <osgUtil/IntersectionVisitor>
 #include <osgUtil/LineSegmentIntersector>
@@ -39,9 +39,9 @@ namespace
         {
         }
 
-        virtual void onTileAdded(const osgEarth::TileKey& tileKey, osg::Node* terrain, TerrainCallbackContext&)
+        virtual void onTileAdded(const osgEarth::TileKey& tileKey, osg::Node* graph, TerrainCallbackContext&)
         {
-            _los->terrainChanged( tileKey, terrain );
+            _los->terrainChanged( tileKey, graph );
         }
 
     private:
@@ -254,12 +254,12 @@ LinearLineOfSightNode::compute(osg::Node* node, bool backgroundThread)
       }
 
 
-      DPLineSegmentIntersector* lsi = new DPLineSegmentIntersector(_startWorld, _endWorld);
+      osgUtil::LineSegmentIntersector* lsi = new osgUtil::LineSegmentIntersector(_startWorld, _endWorld);
       osgUtil::IntersectionVisitor iv( lsi );
 
       node->accept( iv );
 
-      DPLineSegmentIntersector::Intersections& hits = lsi->getIntersections();
+      osgUtil::LineSegmentIntersector::Intersections& hits = lsi->getIntersections();
       if ( hits.size() > 0 )
       {
           _hasLOS = false;
@@ -442,7 +442,7 @@ LineOfSightTether::operator()(osg::Node* node, osg::NodeVisitor* nv)
         {
             if (_startNode.valid())
             {
-                osg::Vec3d worldStart = getNodeCenter(_startNode);
+                osg::Vec3d worldStart = getNodeCenter(_startNode.get());
 
                 //Convert to mappoint since that is what LOS expects
                 GeoPoint mapStart;
@@ -452,7 +452,7 @@ LineOfSightTether::operator()(osg::Node* node, osg::NodeVisitor* nv)
 
             if (_endNode.valid())
             {
-                osg::Vec3d worldEnd = getNodeCenter( _endNode );
+                osg::Vec3d worldEnd = getNodeCenter( _endNode.get() );
 
                 //Convert to mappoint since that is what LOS expects
                 GeoPoint mapEnd;
@@ -517,13 +517,13 @@ LinearLineOfSightEditor::LinearLineOfSightEditor(LinearLineOfSightNode* los):
 _los(los)
 {
     _startDragger  = new osgEarth::Annotation::SphereDragger( _los->getMapNode());
-    _startDragger->addPositionChangedCallback(new LOSDraggerCallback(_los, true ) );    
+    _startDragger->addPositionChangedCallback(new LOSDraggerCallback(_los.get(), true ) );    
     static_cast<osgEarth::Annotation::SphereDragger*>(_startDragger)->setColor(osg::Vec4(0,0,1,0));
     addChild(_startDragger);
 
     _endDragger = new osgEarth::Annotation::SphereDragger( _los->getMapNode());
     static_cast<osgEarth::Annotation::SphereDragger*>(_endDragger)->setColor(osg::Vec4(0,0,1,0));
-    _endDragger->addPositionChangedCallback(new LOSDraggerCallback(_los, false ) );
+    _endDragger->addPositionChangedCallback(new LOSDraggerCallback(_los.get(), false ) );
 
     addChild(_endDragger);
 
diff --git a/src/osgEarthUtil/LogDepthBuffer.VertOnly.vert.glsl b/src/osgEarthUtil/LogDepthBuffer.VertOnly.vert.glsl
index cc6089f..119f676 100644
--- a/src/osgEarthUtil/LogDepthBuffer.VertOnly.vert.glsl
+++ b/src/osgEarthUtil/LogDepthBuffer.VertOnly.vert.glsl
@@ -1,4 +1,4 @@
-#version 110
+#version $GLSL_VERSION_STR
 
 #pragma vp_entryPoint oe_logDepth_vert
 #pragma vp_location   vertex_clip
@@ -8,7 +8,7 @@ uniform float oe_logDepth_FC;
 
 void oe_logDepth_vert(inout vec4 clip)
 {
-    if ( oe_logDepth_FC > 0.0 )
+    if (gl_ProjectionMatrix[3][3] == 0.0) // perspective
     {
         clip.z = (log2(max(1e-6, clip.w+1.0))*oe_logDepth_FC - 1.0) * clip.w;
     }
diff --git a/src/osgEarthUtil/LogDepthBuffer.frag.glsl b/src/osgEarthUtil/LogDepthBuffer.frag.glsl
index a72414f..bbc2576 100644
--- a/src/osgEarthUtil/LogDepthBuffer.frag.glsl
+++ b/src/osgEarthUtil/LogDepthBuffer.frag.glsl
@@ -1,16 +1,22 @@
-#version 110
+#version $GLSL_VERSION_STR
+$GLSL_DEFAULT_PRECISION_FLOAT
 
 #pragma vp_entryPoint oe_logDepth_frag
 #pragma vp_location   fragment_lighting
 #pragma vp_order      0.99
 
 uniform float oe_logDepth_FC;
-varying float oe_logDepth_clipz;
+in float oe_logDepth_clipz;
 
 void oe_logDepth_frag(inout vec4 color)
 {
-    if ( oe_logDepth_FC > 0.0 )
+    if (gl_ProjectionMatrix[3][3] == 0.0) // perspective
     {
         gl_FragDepth = log2(oe_logDepth_clipz) * 0.5*oe_logDepth_FC;
     }
+    else
+    {
+        // must set gl_FragDepth is all branches even if it doesn't change
+        gl_FragDepth = gl_FragCoord.z;
+    }
 }
diff --git a/src/osgEarthUtil/LogDepthBuffer.vert.glsl b/src/osgEarthUtil/LogDepthBuffer.vert.glsl
index b219109..3353707 100644
--- a/src/osgEarthUtil/LogDepthBuffer.vert.glsl
+++ b/src/osgEarthUtil/LogDepthBuffer.vert.glsl
@@ -1,15 +1,16 @@
-#version 110
+#version $GLSL_VERSION_STR
+$GLSL_DEFAULT_PRECISION_FLOAT
 
 #pragma vp_entryPoint oe_logDepth_vert
 #pragma vp_location   vertex_clip
 #pragma vp_order      0.99
 
 uniform float oe_logDepth_FC;
-varying float oe_logDepth_clipz;
+out float oe_logDepth_clipz;
 
 void oe_logDepth_vert(inout vec4 clip)
 {
-    if ( oe_logDepth_FC > 0.0 )
+    if (gl_ProjectionMatrix[3][3] == 0.0) // perspective
     {
         clip.z = (log2(max(1e-6, 1.0 + clip.w)) * oe_logDepth_FC - 1.0) * clip.w;
         oe_logDepth_clipz = 1.0 + clip.w;
diff --git a/src/osgEarthUtil/LogarithmicDepthBuffer b/src/osgEarthUtil/LogarithmicDepthBuffer
index f20c697..b0c4fdb 100644
--- a/src/osgEarthUtil/LogarithmicDepthBuffer
+++ b/src/osgEarthUtil/LogarithmicDepthBuffer
@@ -32,6 +32,9 @@ namespace osgEarth { namespace Util
      * rendering of close and far objects in the same view without
      * z-fighting artifacts.
      *
+     * Note: Only works with perspective projections. For an orthographic
+     * projection it is a NOP.
+     *
      * Note: If you have any RTT cameras that deal with depth data,
      * they need to use a log buffer as well! (e.g., ClampingTechnique)
      *
diff --git a/src/osgEarthUtil/LogarithmicDepthBuffer.cpp b/src/osgEarthUtil/LogarithmicDepthBuffer.cpp
index f8eb7c0..a834f0a 100644
--- a/src/osgEarthUtil/LogarithmicDepthBuffer.cpp
+++ b/src/osgEarthUtil/LogarithmicDepthBuffer.cpp
@@ -65,18 +65,14 @@ namespace
         {
             const osg::Matrix& proj = renderInfo.getCurrentCamera()->getProjectionMatrix();
 
-            if ( osg::equivalent(proj(3,3), 0.0) ) // perspective
+            // perspective only. the LDB will automatically disable in orthograhpic.
+            if ( osg::equivalent(proj(3,3), 0.0) )
             {
                 float vfov, ar, n, f;
                 proj.getPerspective(vfov, ar, n, f);
                 float fc = (float)(2.0/LOG2(_coeff*f+1.0));
                 _uniform->set( fc );
             }
-            else // ortho
-            {
-                // Disable in ortho, because it just doesn't work.
-                _uniform->set( -1.0f );
-            }
 
             if ( _next.valid() )
             {
diff --git a/src/osgEarthUtil/MGRSGraticule b/src/osgEarthUtil/MGRSGraticule
index b3fc60c..2604f30 100644
--- a/src/osgEarthUtil/MGRSGraticule
+++ b/src/osgEarthUtil/MGRSGraticule
@@ -19,36 +19,64 @@
 #ifndef OSGEARTHUTIL_MGRS_GRATICLE
 #define OSGEARTHUTIL_MGRS_GRATICLE
 
-#include <osgEarthUtil/UTMGraticule>
+#include <osgEarthUtil/Common>
+#include <osgEarth/VisibleLayer>
+#include <osgEarthSymbology/StyleSheet>
+#include <osgEarthFeatures/Feature>
+#include <osg/ClipPlane>
+
 
 namespace osgEarth { namespace Util
 {
     using namespace osgEarth;
+    using namespace osgEarth::Features;
     using namespace osgEarth::Symbology;
 
     /**
      * Configuration options for the geodetic graticule.
      */
-    class OSGEARTHUTIL_EXPORT MGRSGraticuleOptions : public UTMGraticuleOptions
+    class OSGEARTHUTIL_EXPORT MGRSGraticuleOptions : public VisibleLayerOptions
     {
     public:
-        MGRSGraticuleOptions( const Config& conf =Config() );
-
-        /** dtor */
-        virtual ~MGRSGraticuleOptions() { }
-
-        /** Symbology for SQID lines and text */
-        optional<Style>& secondaryStyle() { return _secondaryStyle; }
-        const optional<Style>& secondaryStyle() const { return _secondaryStyle; }
+        //! Construct an MGRS options structure
+        MGRSGraticuleOptions(const ConfigOptions& conf =ConfigOptions());
+
+        //! Location of the SQID feature data
+        //! This is a prebuilt binary file that you should distribute 
+        //! with the application - typically names "mgrs_sqid.bin"
+        optional<URI>& sqidData() { return _sqidURI; }
+        const optional<URI>& sqidData() const { return _sqidURI; }
+
+        //! Styles for each display level. Each level should be names in the
+        //! following manner:
+        //! The top-level grid zone designator is called "gzd".
+        //! Each consecutive level is a power of 10, starting with "100000"
+        //! (for the SQID 100km grid) and going down from there (10000, 1000, etc.
+        //! down to level 1).
+        //! You can set only certain levels if you wish in order to override
+        //! the defaults.
+        osg::ref_ptr<StyleSheet>& styleSheet() { return _styleSheet; }
+        const osg::ref_ptr<StyleSheet>& styleSheet() const { return _styleSheet; }
+
+        //! Whether to apply default styles when you do not expressly
+        //! set them for all levels. Defaults to true.
+        optional<bool>& useDefaultStyles() { return _useDefaultStyles; }
+        const optional<bool>& useDefaultStyles() const { return _useDefaultStyles; }
 
     public:
-        Config getConfig() const;
+        virtual Config getConfig() const;
 
-    protected:
-
-        void mergeConfig( const Config& conf );
+        virtual void fromConfig(const Config& conf);
 
-        optional<Style> _secondaryStyle;
+    protected:
+        void mergeConfig(const Config& conf) {
+            VisibleLayerOptions::mergeConfig(conf);
+            fromConfig(conf);
+        }
+
+        optional<URI> _sqidURI;
+        osg::ref_ptr<StyleSheet> _styleSheet;
+        optional<bool> _useDefaultStyles;
     };
 
 
@@ -58,50 +86,63 @@ namespace osgEarth { namespace Util
      * NOTE: So far, this only works for geocentric maps.
      * TODO: Add projected support; add text label support
      */
-    class OSGEARTHUTIL_EXPORT MGRSGraticule : public UTMGraticule
+    class OSGEARTHUTIL_EXPORT MGRSGraticule : public VisibleLayer
     {
     public:
+        META_Layer(osgEarthUtil, MGRSGraticule, MGRSGraticuleOptions);
+
+        //! Constructs a default graticule
+        MGRSGraticule();
 
         /**
-         * Constructs a new graticule for use with the specified map. The graticule
-         * is created with several default levels. If you call addLevel(), the 
-         * default levels are deleted.
+         * Constructs a new graticule for use with the specified map.
          *
-         * @param map
-         *      Map with which you will use this graticule
          * @param options
          *      Optional "options" that configure the graticule. Defaults will be used
          *      if you don't specify this.
          */
-        MGRSGraticule( MapNode* mapNode );
-        MGRSGraticule( MapNode* mapNode, const MGRSGraticuleOptions& options);
-
-        /** dtor */
-        virtual ~MGRSGraticule() { }
-
-        /** 
-         * Applies a new set of options. The graticule will be rebuilt if necessary.
-         */
-        void setOptions( const MGRSGraticuleOptions& options );
+        MGRSGraticule(const MGRSGraticuleOptions& options);
 
         /**
-         * Gets the options with which the graticule was built.
+         * If you change any of the options, call this to refresh the display
+         * to refelct the new settings.
          */
-        const MGRSGraticuleOptions& getOptions() const { return _options.value(); }
+        void dirty();
 
-    public:
-        osg::Node* buildSQIDTiles( const std::string& gzd );
+    public: // Layer
 
-    protected:
-        optional<MGRSGraticuleOptions> _options;
-        osg::ref_ptr<osg::Uniform>     _minDepthOffset;
+        virtual void addedToMap(const Map* map);
+
+        virtual void removedFromMap(const Map* map);
+        
+        virtual osg::Node* getOrCreateNode();
+
+        virtual void init();
 
     protected:
-        virtual osg::Group* buildGZDChildren( osg::Group* node, const std::string& gzd );
+
+        /** dtor */
+        virtual ~MGRSGraticule() { }
+
+
+    private:
         
-        GeoExtent getExtent( const std::string& gzd, const std::string& sqid );
+        void setUpDefaultStyles();
+
+        void rebuild();
+
+        osg::ref_ptr<const Profile> _profile;
+
+        osg::ref_ptr<FeatureProfile> _featureProfile;
+
+        osg::ref_ptr<osg::ClipPlane> _clipPlane;
+
+        osg::ref_ptr<osg::Group> _root;
+
+        osg::observer_ptr<const Map> _map;
+
+        void loadGZDFeatures(const SpatialReference* srs, FeatureList& output) const;
 
-        friend class MGRSGraticuleFactory;
     };
 
 } } // namespace osgEarth::Util
diff --git a/src/osgEarthUtil/MGRSGraticule.cpp b/src/osgEarthUtil/MGRSGraticule.cpp
index af1b098..09ec511 100644
--- a/src/osgEarthUtil/MGRSGraticule.cpp
+++ b/src/osgEarthUtil/MGRSGraticule.cpp
@@ -18,22 +18,36 @@
  */
 #include <osgEarthUtil/MGRSGraticule>
 #include <osgEarthUtil/MGRSFormatter>
+#include <osgEarthUtil/UTMLabelingEngine>
 
 #include <osgEarthFeatures/GeometryCompiler>
 #include <osgEarthFeatures/TextSymbolizer>
+#include <osgEarthFeatures/FeatureSource>
+#include <osgEarthFeatures/TessellateOperator>
+
+#include <osgEarthAnnotation/FeatureNode>
 
-#include <osgEarth/ECEF>
-#include <osgEarth/DepthOffset>
 #include <osgEarth/Registry>
+#include <osgEarth/CullingUtils>
+#include <osgEarth/Utils>
+#include <osgEarth/PagedNode>
+#include <osgEarth/ShaderUtils>
+#include <osgEarth/Endian>
 
 #include <osg/BlendFunc>
 #include <osg/PagedLOD>
 #include <osg/Depth>
 #include <osg/LogicOp>
 #include <osg/MatrixTransform>
+#include <osg/ClipNode>
 #include <osgDB/FileNameUtils>
 #include <osgDB/ReaderWriter>
+//#include <osgDB/WriteFile>
+
+#include <osgEarthDrivers/feature_ogr/OGRFeatureOptions>
 
+#include <fstream>
+#include <sstream>
 
 #define LC "[MGRSGraticule] "
 
@@ -41,93 +55,1270 @@ using namespace osgEarth;
 using namespace osgEarth::Util;
 using namespace osgEarth::Features;
 using namespace osgEarth::Symbology;
+using namespace osgEarth::Annotation;
 
-#define MGRS_GRATICULE_EXTENSION "osgearthutil_mgrs_graticule"
+REGISTER_OSGEARTH_LAYER(mgrs_graticule, MGRSGraticule);
 
-//---------------------------------------------------------------------------
+//#define DEBUG_MODE
 
-MGRSGraticuleOptions::MGRSGraticuleOptions( const Config& conf ) :
-UTMGraticuleOptions( conf )
-{
-    mergeConfig( _conf );
-}
+//---------------------------------------------------------------------------
 
-void
-MGRSGraticuleOptions::mergeConfig( const Config& conf )
+MGRSGraticuleOptions::MGRSGraticuleOptions(const ConfigOptions& conf) :
+VisibleLayerOptions(conf)
 {
-    //todo
+    _sqidURI.init(URI("../data/mgrs_sqid.bin", conf.referrer()));
+    _styleSheet = new StyleSheet();
+    fromConfig(_conf);
 }
 
 Config
 MGRSGraticuleOptions::getConfig() const
 {
-    Config conf = UTMGraticuleOptions::newConfig();
+    Config conf = VisibleLayerOptions::getConfig();
     conf.key() = "mgrs_graticule";
-    //todo
+    conf.set("sqid_data", _sqidURI);
+    conf.setObj("styles", _styleSheet);
+    conf.set("use_default_styles", _useDefaultStyles);
     return conf;
 }
 
+void
+MGRSGraticuleOptions::fromConfig(const Config& conf)
+{
+    conf.getIfSet("sqid_data", _sqidURI);
+    conf.getObjIfSet("styles", _styleSheet);
+    conf.getIfSet("use_default_styles", _useDefaultStyles);
+}
+
 //---------------------------------------------------------------------------
 
+namespace
+{
+    void simplify(Vec3dVector& vec)
+    {
+        int count = vec.size();
+        for (int i = 1; i < vec.size()-1; ++i)
+        {
+            osg::Vec3d& p0 = vec[i-1];
+            osg::Vec3d& p1 = vec[i];
+            osg::Vec3d& p2 = vec[i+1];
+
+            osg::Vec3d a = p1 - p0; a.normalize();
+            osg::Vec3d b = p2 - p1; b.normalize();
+            if (a*b > 0.60)
+            {
+                vec.erase(vec.begin()+i);
+                --i;
+            }
+        }
+    }
+
+    //! Generates the binary SQID file.
+    void writeSQIDfile(const URI& uri)
+    {
+        osgEarth::Drivers::OGRFeatureOptions sqid_ogr;
+        sqid_ogr.url() = "H:/data/nga/mgrs/MGRS_100kmSQ_ID/WGS84/ALL_SQID.shp";
+        sqid_ogr.buildSpatialIndex() = false;
+
+        osg::ref_ptr<FeatureSource> sqid_fs = FeatureSourceFactory::create(sqid_ogr);
+        if (sqid_fs.valid() && sqid_fs->open().isOK())
+        {
+            // Read source data into an array:
+            FeatureList sqids;
+            osg::ref_ptr<FeatureCursor> sqid_cursor = sqid_fs->createFeatureCursor();
+            if (sqid_cursor.valid() && sqid_cursor->hasMore())
+                sqid_cursor->fill(sqids);
+
+            // Open the output stream:
+            std::ofstream out(uri.full().c_str(), std::ostream::out | std::ostream::binary);
+            out.imbue(std::locale::classic());
+
+            // We will need a local XY SRS for geometry simplification:
+            const SpatialReference* xysrs = SpatialReference::get("spherical-mercator");
+
+            u_long count = OE_ENCODE_LONG(sqids.size());
+            out.write(reinterpret_cast<const char*>(&count), sizeof(u_long));
+
+            for (FeatureList::iterator i = sqids.begin(); i != sqids.end(); ++i)
+            {
+                Feature* f = i->get();
+
+                std::string gzd = f->getString("GZD");
+                out.write(gzd.c_str(), 3);
+
+                std::string sqid = f->getString("100kmSQ_ID");
+                out.write(sqid.c_str(), 2);
+
+                char easting = (char)(f->getDouble("EASTING")/100000.0);
+                out.put(easting);
+
+                char northing = (char)(f->getDouble("NORTHING")/100000.0);
+                out.put(northing);
+
+                //GeoExtent extent(f->getSRS(), f->getGeometry()->getBounds());
+
+                // Transform into a local XY SRS for simplification:
+                if (f->getGeometry()->size() > 0)
+                {
+                    f->transform(xysrs);
+                    simplify(f->getGeometry()->asVector());
+                    f->transform(xysrs->getGeographicSRS());
+                }
+
+                // TODO: deal with MultiGeometries!
+
+                Geometry* g = f->getGeometry();
+
+                u_short numPoints = OE_ENCODE_SHORT((u_short)g->size());
+                out.write(reinterpret_cast<const char*>(&numPoints), sizeof(u_short));
+                                   
+                for (Geometry::const_iterator p = g->begin(); p != g->end(); ++p)
+                {
+                    uint64_t x = OE_ENCODE_DOUBLE(p->x());
+                    out.write(reinterpret_cast<const char*>(&x), sizeof(uint64_t));
+
+                    uint64_t y = OE_ENCODE_DOUBLE(p->y());
+                    out.write(reinterpret_cast<const char*>(&y), sizeof(uint64_t));
+                }
+            }
+            out.flush();
+            out.close();
+
+            OE_WARN << "Wrote SQIDs to " << uri.full() << std::endl;
+        }
+    }
+
+    bool readSQIDfile(const URI& uri, FeatureList& output)
+    {
+        output.clear();
+
+        std::ifstream fin(uri.full().c_str(), std::ostream::in | std::ostream::binary);
+        fin.imbue(std::locale::classic());
+
+        if (fin.eof() || fin.is_open() == false)
+            return false;
+
+        u_long count;
+        fin.read(reinterpret_cast<char*>(&count), sizeof(u_long));
+        count = OE_DECODE_LONG(count);
+
+        const SpatialReference* wgs84 = SpatialReference::get("wgs84");
+
+        for (u_long i = 0; i < count; ++i)
+        {
+            char gzd[4]; gzd[3] = 0;
+            fin.read(gzd, 3);
+
+            char sqid[3]; sqid[2] = 0;
+            fin.read(sqid, 2);
 
-MGRSGraticule::MGRSGraticule( MapNode* mapNode ) :
-UTMGraticule( 0L )
+            double easting = (double)fin.get() * 100000.0;
+
+            double northing = (double)fin.get() * 100000.0;
+
+            u_short numPoints;
+            fin.read(reinterpret_cast<char*>(&numPoints), sizeof(u_short));
+            numPoints = OE_DECODE_SHORT(numPoints);
+
+            if (numPoints > 16384)
+            {
+                OE_WARN << LC << "sqid bin file is corrupt.. abort!" << std::endl;
+                exit(-1);
+            }
+
+            osgEarth::Symbology::Ring* line = new osgEarth::Symbology::Ring();
+            for (u_short n = 0; n < numPoints; ++n)
+            {
+                uint64_t x;
+                fin.read(reinterpret_cast<char*>(&x), sizeof(uint64_t));
+
+                uint64_t y;
+                fin.read(reinterpret_cast<char*>(&y), sizeof(uint64_t));
+
+                line->push_back(osg::Vec3d(OE_DECODE_DOUBLE(x), OE_DECODE_DOUBLE(y), 0));
+            }
+
+            if (line->getTotalPointCount() > 0)
+            {
+                osg::ref_ptr<Feature> feature = new Feature(line, wgs84);
+                feature->set("gzd", std::string(gzd));
+                feature->set("sqid", std::string(sqid));
+                feature->set("easting", easting);
+                feature->set("northing", northing);
+                output.push_back(feature.get());
+            }
+            else
+            {
+                OE_INFO << LC << "Empty SQID geom at " << gzd << " " << sqid << std::endl;
+            }
+        }
+        return true;
+    }
+
+    struct LocalStats : public osg::Object
+    {
+        META_Object(osgEarth, LocalStats);
+        LocalStats() : osg::Object(), _gzdNode(0), _gzdText(0), _sqidText(0), _geomCell(0), _geomGrid(0) { }
+        unsigned _gzdNode, _gzdText, _sqidText, _geomCell, _geomGrid;
+        LocalStats(const LocalStats& rhs, const osg::CopyOp&) { }
+    };
+
+    struct LocalRoot : public osg::Group
+    {
+#ifdef DEBUG_MODE
+        void traverse(osg::NodeVisitor& nv)
+        {
+            if (nv.getVisitorType() == nv.CULL_VISITOR)
+            {
+                osg::UserDataContainer* udc = nv.getOrCreateUserDataContainer();
+                LocalStats* stats = new LocalStats();
+                stats->setName("stats");
+                unsigned index = udc->addUserObject(stats);
+
+                osg::Group::traverse(nv);
+
+                Registry::instance()->startActivity("GZDGeom", Stringify() << stats->_gzdNode);
+                Registry::instance()->startActivity("GZDText", Stringify() << stats->_gzdText);
+                Registry::instance()->startActivity("SQIDText", Stringify() << stats->_sqidText);
+                Registry::instance()->startActivity("GeomCell", Stringify() << stats->_geomCell);
+                Registry::instance()->startActivity("GeomGrid", Stringify() << stats->_geomGrid);
+
+                udc->removeUserObject(index);
+            }
+            else
+            {
+                osg::Group::traverse(nv);
+            }
+        }
+#endif
+    };
+
+#define STATS(nv) (dynamic_cast<LocalStats*>(nv.getUserDataContainer()->getUserObject("stats")))
+}
+
+//---------------------------------------------------------------------------
+
+MGRSGraticule::MGRSGraticule() :
+VisibleLayer(&_optionsConcrete),
+_options(&_optionsConcrete)
+{
+    init();
+}
+
+MGRSGraticule::MGRSGraticule(const MGRSGraticuleOptions& options) :
+VisibleLayer(&_optionsConcrete),
+_options(&_optionsConcrete),
+_optionsConcrete(options)
 {
-    _mapNode = mapNode;
     init();
+}
+
+void
+MGRSGraticule::dirty()
+{
+    rebuild();
+}
+
+void
+MGRSGraticule::init()
+{
+    VisibleLayer::init();
+
+    osg::StateSet* ss = this->getOrCreateStateSet();
+
+    // make the shared depth attr:
+    ss->setAttributeAndModes(
+        new osg::Depth(osg::Depth::ALWAYS, 0.f, 1.f, false),
+        osg::StateAttribute::ON);
+
+    ss->setMode( GL_LIGHTING, 0 );
+    ss->setMode( GL_BLEND, 1 );
+
+    // force it to render after the terrain.
+    ss->setRenderBinDetails(1, "RenderBin");
+}
 
-    if ( !_options->secondaryStyle().isSet() )
+void
+MGRSGraticule::addedToMap(const Map* map)
+{
+    _map = map;
+    rebuild();
+}
+
+void
+MGRSGraticule::removedFromMap(const Map* map)
+{
+    _map = 0L;
+}
+
+osg::Node*
+MGRSGraticule::getOrCreateNode()
+{
+    if (_root.valid() == false)
     {
-        LineSymbol* line = _options->secondaryStyle()->getOrCreate<LineSymbol>();
-        line->stroke()->color() = Color(Color::White, 0.5f);
-        line->stroke()->stipplePattern() = 0x1111;
+        _root = new LocalRoot();
+
+        // install the range callback for clip plane activation
+        _root->addCullCallback( new RangeUniformCullCallback() );
 
-        TextSymbol* text = _options->secondaryStyle()->getOrCreate<TextSymbol>();
-        text->fill()->color() = Color(Color::White, 0.3f);
-        text->halo()->color() = Color(Color::Black, 0.1f);
-        text->alignment() = TextSymbol::ALIGN_CENTER_CENTER;
+        rebuild();
     }
 
-//    _minDepthOffset = DepthOffsetUtils::createMinOffsetUniform();
-//    _minDepthOffset->set( 11000.0f );
+    return _root.get();
 }
 
-MGRSGraticule::MGRSGraticule( MapNode* mapNode, const MGRSGraticuleOptions& options ) :
-UTMGraticule( 0L, options )
+namespace
 {
-    _mapNode = mapNode;
-    init();
+    void findPointClosestTo(const Feature* f, const osg::Vec3d& p1, osg::Vec3d& out)
+    {
+        out = p1;
+        double minLen2 = DBL_MAX;
+        const Geometry* g = f->getGeometry();
+        ConstGeometryIterator iter(f->getGeometry(), false);
+        while (iter.hasMore())
+        {
+            const Geometry* part = iter.next();
+            for (Geometry::const_iterator i = part->begin(); i != part->end(); ++i)
+            {
+                osg::Vec3d p(i->x(), i->y(), 0);
+                double len2 = (p1 - p).length2();
+                if (len2 < minLen2)
+                {
+                    minLen2 = len2, out = *i;
+                }
+            }
+        }
+    }
+
+    struct GeomCell : public PagedNode
+    {
+        double _size;        
+        osg::ref_ptr<Feature> _feature;
+        Style _style;
+        bool _hasChild;
+        const MGRSGraticuleOptions* _options;
+    
+        GeomCell(double size);
+        void setupData(Feature* feature, const MGRSGraticuleOptions* options);
+        osg::Node* loadChild();
+        bool hasChild() const;
+        osg::BoundingSphere getChildBound() const;
+        osg::Node* build();
+
+        void traverse(osg::NodeVisitor&);
+    };
+
+
+    struct GeomGrid : public PagedNode
+    {
+        double _size;
+        const MGRSGraticuleOptions* _options;
+        osg::ref_ptr<Feature> _feature;
+        Style _style;
+        GeoExtent _extent;
+        osg::ref_ptr<const SpatialReference> _utm;
+
+        GeomGrid(double size)
+        {
+            _size = size;
+            setRangeMode(osg::LOD::PIXEL_SIZE_ON_SCREEN);
+            setRange(1600);
+            setAdditive(false);
+        }
+
+        void setupData(Feature* feature, const MGRSGraticuleOptions* options)
+        {
+            _feature = feature;
+            _options = options;
+            std::string styleName = Stringify() << (int)(_size*0.1);
+            _style = *options->styleSheet()->getStyle(styleName, true);
+            setNode(build());
+        }
+
+        osg::Node* loadChild()
+        {
+            osg::Group* group = new osg::Group();
+
+            double x0 = _feature->getDouble("easting");
+            double y0 = _feature->getDouble("northing");
+            double interval = _size * 0.1;
+
+            for (double x = x0; x < x0 + _size; x += interval)
+            {
+                for (double y = y0; y < y0 + _size; y += interval)
+                {
+                    osg::ref_ptr<LineString> polygon = new LineString();
+                    polygon->push_back(x, y);
+                    polygon->push_back(x+interval, y);
+                    polygon->push_back(x+interval, y+interval);
+                    polygon->push_back(x, y+interval);
+                    polygon->push_back(x, y);
+
+                    osg::ref_ptr<Feature> f = new Feature(polygon.get(), _utm.get());
+                    f->transform(_feature->getSRS());
+
+                    osg::ref_ptr<Geometry> croppedGeom;
+                    if (f->getGeometry()->crop(_extent.bounds(), croppedGeom))
+                    {
+                        f->setGeometry(croppedGeom.get());
+                        f->set("easting", x);
+                        f->set("northing", y);
+                        GeomCell* child = new GeomCell(interval);
+                        child->setupData(f.get(), _options);
+                        child->setupPaging();
+                        group->addChild(child);
+                    }                 
+                }
+            }
+            
+            return group;
+        }
+
+        osg::BoundingSphere getChildBound() const
+        {
+            return getChild(0)->getBound();
+        }
+
+        bool hasChild() const 
+        {
+            return true;
+        }
+
+        osg::Node* build()
+        {
+            _extent = GeoExtent(_feature->getSRS(), _feature->getGeometry()->getBounds());
+            double lon, lat;
+            _extent.getCentroid(lon, lat);
+            _utm = _feature->getSRS()->createUTMFromLonLat(lon, lat);
+
+            double x0 = _feature->getDouble("easting");
+            double y0 = _feature->getDouble("northing");
+
+            double interval = _size * 0.1;
+
+            osg::ref_ptr<MultiGeometry> grid = new MultiGeometry();
+
+            // south-north lines:
+            for (double x=x0; x<=x0+_size; x += interval)
+            {
+                LineString* ls = new LineString();
+                ls->push_back(osg::Vec3d(x, y0, 0));
+                ls->push_back(osg::Vec3d(x, y0+_size, 0));
+                grid->getComponents().push_back(ls);
+            }
+            
+            // west-east lines:
+            for (double y=y0; y<=y0+_size; y+=interval)
+            {
+                LineString* ls = new LineString();
+                ls->push_back(osg::Vec3d(x0, y, 0));
+                ls->push_back(osg::Vec3d(x0+_size, y, 0));
+                grid->getComponents().push_back(ls);
+            }
+
+            osg::ref_ptr<Feature> f = new Feature(grid.get(), _utm.get());
+            f->transform(_feature->getSRS());
+
+            osg::ref_ptr<Geometry> croppedGeom;
+            if (f->getGeometry()->crop(_extent.bounds(), croppedGeom))
+            {
+                f->setGeometry(croppedGeom.get());
+            }
+
+            GeometryCompilerOptions gco;
+            gco.shaderPolicy() = SHADERPOLICY_INHERIT;
+            FeatureNode* node = new FeatureNode(f.get(), _style, gco);
+            
+            return node;
+        }
+        
+#ifdef DEBUG_MODE
+        void traverse(osg::NodeVisitor& nv)
+        {
+            if (nv.getVisitorType() == nv.CULL_VISITOR)
+                STATS(nv)->_geomGrid++;
+            PagedNode::traverse(nv);
+        }
+#endif
+    };
+    
+
+
+    GeomCell::GeomCell(double size) : _size(size)
+    {
+        setRangeMode(osg::LOD::PIXEL_SIZE_ON_SCREEN);
+        setRange(880);
+        setAdditive(true);
+    }
+
+    void GeomCell::setupData(Feature* feature, const MGRSGraticuleOptions* options)
+    {
+        _feature = feature;
+        _options = options;
+        std::string styleName = Stringify() << (int)(_size);
+        _style = *options->styleSheet()->getStyle(styleName, true);
+        setNode( build() );
+    }
+
+    osg::Node* GeomCell::loadChild()
+    {
+        GeomGrid* child = new GeomGrid(_size);
+        child->setupData(_feature.get(), _options);
+        child->setupPaging();
+        return child;
+    }
+
+    bool GeomCell::hasChild() const
+    {
+        std::string sizeStr = Stringify() << (int)(_size/10);
+        return _options->styleSheet()->getStyle(sizeStr, false) != 0L;
+    }
+
+    osg::BoundingSphere GeomCell::getChildBound() const
+    {
+        osg::ref_ptr<GeomGrid> child = new GeomGrid(_size);
+        child->setupData(_feature.get(), _options);
+        return child->getBound();
+    }
+
+    osg::Node* GeomCell::build()
+    {
+        GeometryCompilerOptions gco;
+        gco.shaderPolicy() = SHADERPOLICY_INHERIT;
+        FeatureNode* node = new FeatureNode(_feature.get(), _style, gco);
+        return node;
+    }
+
+    void GeomCell::traverse(osg::NodeVisitor& nv)
+    {
+#ifdef DEBUG_MODE
+        if (nv.getVisitorType() == nv.CULL_VISITOR)
+            STATS(nv)->_geomCell++;
+#endif
+        PagedNode::traverse(nv);
+    }
+
+
+    //! Geometry for a single SQID 100km cell and its children
+    struct SQID100kmCell : public PagedNode
+    {
+        osg::ref_ptr<Feature> _feature;
+        const MGRSGraticuleOptions* _options;
+        Style _style;
+
+        SQID100kmCell(const std::string& name)
+        {
+            setName(name);
+            setRangeMode(osg::LOD::PIXEL_SIZE_ON_SCREEN);
+            setRange(880);
+            setAdditive(true);
+        }
+
+        void setupData(Feature* feature, const MGRSGraticuleOptions* options)
+        {
+            _feature = feature;
+            _options = options;
+            std::string styleName("100000");
+            _style = *options->styleSheet()->getStyle(styleName, true);
+            setNode( build() );
+        }
+
+        osg::Node* loadChild()
+        {
+            GeomGrid* child = new GeomGrid(100000.0);
+            child->setupData(_feature.get(), _options);
+            child->setupPaging();
+            return child;
+        }
+
+        bool hasChild() const
+        {
+            return _options->styleSheet()->getStyle("10000", false) != 0L;
+        }
+
+        osg::BoundingSphere getChildBound() const
+        {
+            GeomGrid* child = new GeomGrid(100000.0);
+            child->setupData(_feature.get(), _options);
+            return child->getBound();
+        }
+
+        osg::Node* build()
+        {
+            GeometryCompilerOptions gco;
+            gco.shaderPolicy() = SHADERPOLICY_INHERIT;
+            FeatureNode* node = new FeatureNode(_feature.get(), _style, gco);
+            return node;
+        }
+    };
+
+
+    //! All SQID 100km goemetry from a single UTM GZD cell combined into one geometry
+    struct SQID100kmGrid : public PagedNode
+    {
+        osg::BoundingSphere _bs;
+        FeatureList _sqidFeatures;
+        Style _style;
+        const MGRSGraticuleOptions* _options;
+
+        SQID100kmGrid(const std::string& name, const osg::BoundingSphere& bs)
+        {
+            setName(name);
+            _bs = bs;
+            setAdditive(false);
+            setRangeMode(osg::LOD::PIXEL_SIZE_ON_SCREEN);
+            setRange(3200);
+        }
+
+        void setupData(const FeatureList& sqidFeatures, const MGRSGraticuleOptions* options)
+        {
+            _sqidFeatures = sqidFeatures;
+            _options = options;
+
+            std::string styleName("100000");
+            _style = *options->styleSheet()->getStyle(styleName, true);
+
+            // remove any text symbology; that gets built elsewhere.
+            _style.remove<TextSymbol>();
+           
+            setNode(build());
+        }
+
+        osg::Node* loadChild()
+        {
+            osg::Group* group = new osg::Group();
+
+            for (FeatureList::const_iterator f = _sqidFeatures.begin(); f != _sqidFeatures.end(); ++f)
+            {
+                Feature* feature = f->get();
+                SQID100kmCell* geom = new SQID100kmCell(feature->getString("sqid"));
+                geom->setupData(feature, _options);
+                geom->setupPaging();
+                group->addChild(geom);
+                
+                GeoExtent extent(feature->getSRS(), feature->getGeometry()->getBounds());
+            }
+
+            return group;
+        }
+
+        osg::BoundingSphere getChildBound() const
+        {
+            return getChild(0)->getBound();
+        }
+
+        osg::Node* build()
+        {
+            GeometryCompilerOptions gco;
+            gco.shaderPolicy() = SHADERPOLICY_INHERIT;
+            return new FeatureNode(0L, _sqidFeatures, _style, gco);
+        }
+    };
+
+
+    struct GZDGeom : public PagedNode
+    {
+        Style _sqidStyle;
+        FeatureList _sqidFeatures;
+        const MGRSGraticuleOptions* _options;
+
+        GZDGeom(const std::string& name)
+        {
+            setName(name);     
+            setRangeMode(osg::LOD::PIXEL_SIZE_ON_SCREEN);
+            setRange(640);
+            setAdditive(false);
+        }
+
+        bool hasChild() const
+        {
+            return _options->styleSheet()->getStyle("100000", false) != 0L;
+        }
+
+        osg::Node* loadChild()
+        {
+            SQID100kmGrid* child = new SQID100kmGrid(getName(), getBound());
+            child->setupData(_sqidFeatures, _options);
+            child->setupPaging();
+            return child;
+        }
+
+        osg::BoundingSphere getChildBound() const
+        {
+            return getChild(0)->getBound();
+        }
+
+        void setupData(const Feature* gzdFeature,
+                       const FeatureList& sqidFeatures, 
+                       const MGRSGraticuleOptions* options,
+                       const FeatureProfile* prof, 
+                       const Map* map)
+        {
+            _options = options;
+            setNode(build(gzdFeature, prof, map));
+            _sqidFeatures = sqidFeatures;
+        }
+
+        osg::Node* build(const Feature* f, const FeatureProfile* prof, const Map* map)
+        {
+            osg::Group* group = new osg::Group();
+
+            // Extract just the line and altitude symbols:
+            const Style& gzdStyle = *_options->styleSheet()->getStyle("gzd");
+            Style lineStyle;
+            lineStyle.add( const_cast<LineSymbol*>(gzdStyle.get<LineSymbol>()) );
+            lineStyle.add( const_cast<AltitudeSymbol*>(gzdStyle.get<AltitudeSymbol>()) );
+            
+            GeoExtent extent(f->getSRS(), f->getGeometry()->getBounds());
+
+            GeometryCompiler compiler;
+            osg::ref_ptr<Session> session = new Session(map);
+            FilterContext context( session.get(), prof, extent );
+
+            // make sure we get sufficient tessellation:
+            compiler.options().maxGranularity() = 1.0;
+
+            FeatureList features;
+
+            // longitudinal line:
+            LineString* lon = new LineString(2);
+            lon->push_back( osg::Vec3d(extent.xMin(), extent.yMax(), 0) );
+            lon->push_back( osg::Vec3d(extent.xMin(), extent.yMin(), 0) );
+            Feature* lonFeature = new Feature(lon, extent.getSRS());
+            lonFeature->geoInterp() = GEOINTERP_GREAT_CIRCLE;
+            features.push_back( lonFeature );
+
+            // latitudinal line:
+            LineString* lat = new LineString(2);
+            lat->push_back( osg::Vec3d(extent.xMin(), extent.yMin(), 0) );
+            lat->push_back( osg::Vec3d(extent.xMax(), extent.yMin(), 0) );
+            Feature* latFeature = new Feature(lat, extent.getSRS());
+            latFeature->geoInterp() = GEOINTERP_RHUMB_LINE;
+            features.push_back( latFeature );
+
+            // top lat line at 84N
+            if ( extent.yMax() == 84.0 )
+            {
+                LineString* lat = new LineString(2);
+                lat->push_back( osg::Vec3d(extent.xMin(), extent.yMax(), 0) );
+                lat->push_back( osg::Vec3d(extent.xMax(), extent.yMax(), 0) );
+                Feature* latFeature = new Feature(lat, extent.getSRS());
+                latFeature->geoInterp() = GEOINTERP_RHUMB_LINE;
+                features.push_back( latFeature );
+            }
+
+            osg::Node* geomNode = compiler.compile(features, lineStyle, context);
+            if ( geomNode ) 
+                group->addChild( geomNode );
+
+            // get the geocentric tile center:
+            osg::Vec3d tileCenter;
+            extent.getCentroid( tileCenter.x(), tileCenter.y() );
+
+            const SpatialReference* ecefSRS = extent.getSRS()->getECEF();
+    
+            osg::Vec3d centerECEF;
+            extent.getSRS()->transform( tileCenter, ecefSRS, centerECEF );
+
+            Registry::shaderGenerator().run(group, Registry::stateSetCache());
+    
+            return ClusterCullingFactory::createAndInstall(group, centerECEF);
+        }
+        
+#ifdef DEBUG_MODE
+        void traverse(osg::NodeVisitor& nv)
+        {
+            if (nv.getVisitorType() == nv.CULL_VISITOR)
+                STATS(nv)->_gzdNode++;
+            PagedNode::traverse(nv);
+        }
+#endif
+    };
+
+
+    struct SQIDTextGrid : public osg::Group
+    {
+        SQIDTextGrid(const std::string& name, FeatureList& features, const MGRSGraticuleOptions* options)
+        {
+            setName(name);
+
+            const Style& style = *options->styleSheet()->getStyle("100000", true);
+            if (style.has<TextSymbol>() == false)
+                return;
+
+            const TextSymbol* textSymPrototype = style.get<TextSymbol>();
+            osg::ref_ptr<TextSymbol> textSym = new TextSymbol(*style.get<TextSymbol>());
+
+            if (textSym->size().isSet() == false)
+                textSym->size() = 24.0f;
+
+            if (textSym->alignment().isSet() == false)
+                textSym->alignment() = textSym->ALIGN_LEFT_BASE_LINE;
+        
+            TextSymbolizer symbolizer( textSym.get() );
+            
+            GeoExtent fullExtent;
+
+            for (FeatureList::const_iterator f = features.begin(); f != features.end(); ++f)
+            {
+                const Feature* feature = f->get();
+                std::string sqid = feature->getString("sqid");
+                osgText::Text* drawable = symbolizer.create(sqid);
+                drawable->setCharacterSizeMode(drawable->SCREEN_COORDS);
+                drawable->getOrCreateStateSet()->setRenderBinToInherit();
+            
+                GeoExtent extent(feature->getSRS(), feature->getGeometry()->getBounds());
+
+                const SpatialReference* ecef = feature->getSRS()->getECEF();
+
+                osg::Vec3d LL;
+                findPointClosestTo(feature, osg::Vec3d(extent.xMin(), extent.yMin(), 0), LL);
+                osg::Vec3d positionECEF;
+                extent.getSRS()->transform(LL, ecef, positionECEF );
+        
+                osg::Matrixd L2W;
+                ecef->createLocalToWorld( positionECEF, L2W );
+                osg::MatrixTransform* mt = new osg::MatrixTransform(L2W);
+                mt->addChild(drawable);
+
+                addChild(mt);
+
+                fullExtent.expandToInclude(extent);
+            }
+
+            OE_DEBUG << LC << "Created " << features.size() << " text elements for " << getName() << std::endl;
+            
+            Registry::shaderGenerator().run(this, Registry::stateSetCache());
+        }
+        
+#ifdef DEBUG_MODE
+        void traverse(osg::NodeVisitor& nv)
+        {
+            if (nv.getVisitorType() == nv.CULL_VISITOR)
+                STATS(nv)->_sqidText++;
+            osg::Group::traverse(nv);
+        }
+#endif
+    };
+
+
+    struct GZDText : public PagedNode
+    {
+        const MGRSGraticuleOptions* _options;
+        FeatureList  _sqidFeatures;
+        osg::BoundingSphere _bs;
+
+        GZDText(const std::string& name, const osg::BoundingSphere& bs)
+        {
+            setName(name);     
+            _bs = bs;
+            _additive = false;    
+            setRangeMode(osg::LOD::PIXEL_SIZE_ON_SCREEN);
+            setRange(880);
+        }
+
+        bool hasChild() const
+        {
+            const Style* s = _options->styleSheet()->getStyle("100000", false);
+            return s && s->has<TextSymbol>();
+        }
+
+        osg::Node* loadChild()
+        {
+            return new SQIDTextGrid(getName(), _sqidFeatures, _options);
+        }
+
+        osg::BoundingSphere getChildBound() const
+        {
+            return _bs;
+        }
+
+        void setupData(const Feature* gzdFeature, const FeatureList& sqidFeatures, const MGRSGraticuleOptions* options)
+        {
+            _options = options;
+            setNode(buildGZD(gzdFeature));
+            _sqidFeatures = sqidFeatures;
+        }
+
+        osg::Node* buildGZD(const Feature* f)
+        {
+            Style style = *_options->styleSheet()->getStyle("gzd", true);
+
+            const TextSymbol* textSymPrototype = style.get<TextSymbol>();
+
+            GeoExtent extent(f->getSRS(), f->getGeometry()->getBounds());
+
+            osg::ref_ptr<TextSymbol> textSym = textSymPrototype ? new TextSymbol(*textSymPrototype) : new TextSymbol();
+
+            if (textSym->size().isSet() == false)
+                textSym->size() = 32.0f;
+            if (textSym->alignment().isSet() == false)
+                textSym->alignment() = textSym->ALIGN_LEFT_BASE_LINE;
+        
+            TextSymbolizer symbolizer( textSym.get() );
+            osgText::Text* drawable = symbolizer.create(getName());
+            drawable->setCharacterSizeMode(osgText::Text::SCREEN_COORDS);
+            drawable->getOrCreateStateSet()->setRenderBinToInherit();
+
+            const SpatialReference* ecef = f->getSRS()->getECEF();
+            osg::Vec3d positionECEF;
+            extent.getSRS()->transform( osg::Vec3d(extent.xMin(),extent.yMin(),0), ecef, positionECEF );
+        
+            osg::Matrixd L2W;
+            ecef->createLocalToWorld( positionECEF, L2W );
+            osg::MatrixTransform* mt = new osg::MatrixTransform(L2W);
+            mt->addChild(drawable); 
+
+            Registry::shaderGenerator().run(drawable, Registry::stateSetCache());
+
+            return ClusterCullingFactory::createAndInstall(mt, positionECEF);
+        }
+
+#ifdef DEBUG_MODE
+        void traverse(osg::NodeVisitor& nv)
+        {
+            if (nv.getVisitorType() == nv.CULL_VISITOR)
+                STATS(nv)->_gzdText++;
+            PagedNode::traverse(nv);
+        }
+#endif
+    };
+}
+
+void
+MGRSGraticule::rebuild()
+{
+    if (_root.valid() == false)
+        return;
+
+    osg::ref_ptr<const Map> map;
+    if (!_map.lock(map))
+        return;
+
+    // Set up some reasonable default styling for a caller that did not
+    // set styles in the options.
+    if (options().useDefaultStyles() == true)
+    {
+        setUpDefaultStyles();
+    }
+    
+    // clear everything out and start over
+    _root->removeChildren( 0, _root->getNumChildren() );
+
+    // requires a geocentric map
+    if ( !map->isGeocentric() )
+    {
+        OE_WARN << LC << "Projected map mode is not supported" << std::endl;
+        return;
+    }
+
+    const Profile* mapProfile = map->getProfile();
+
+    _profile = Profile::create(
+        mapProfile->getSRS(),
+        mapProfile->getExtent().xMin(),
+        mapProfile->getExtent().yMin(),
+        mapProfile->getExtent().xMax(),
+        mapProfile->getExtent().yMax(),
+        8, 4 );
+
+    _featureProfile = new FeatureProfile(_profile->getSRS());
+
+
+    // rebuild the graph:
+    osg::Group* top = _root.get();
+
+    // Horizon clipping plane.
+    osg::ClipPlane* cp = _clipPlane.get();
+    if ( cp == 0L )
+    {
+        osg::ClipNode* clipNode = new osg::ClipNode();
+        osgEarth::Registry::shaderGenerator().run( clipNode );
+        cp = new osg::ClipPlane( 0 );
+        clipNode->addClipPlane( cp );
+        _root->addChild(clipNode);
+        top = clipNode;
+    }
+    top->addCullCallback( new ClipToGeocentricHorizon(_profile->getSRS(), cp) );
+
+#if 0
+    // Uncomment to write out a SQID data file
+    writeSQIDfile(options().sqidData().get());
+#endif
+    
+    FeatureList sqids;
+    if (readSQIDfile(options().sqidData().get(), sqids))
+    {        
+        typedef std::map<std::string, FeatureList> Table;
+        Table table;
+
+        for (FeatureList::iterator i = sqids.begin(); i != sqids.end(); ++i)
+        {
+            table[i->get()->getString("gzd")].push_back(i->get());
+        }
+
+        osg::Group* geomTop = new osg::Group();
+        top->addChild(geomTop);
+
+        osg::Group* textTop = new osg::Group();
+        top->addChild(textTop);
+
+        // build the GZD feature set
+        FeatureList gzdFeatures;
+        loadGZDFeatures(map->getSRS()->getGeographicSRS(), gzdFeatures);
+        osg::ref_ptr<FeatureListCursor> gzd_cursor = new FeatureListCursor(gzdFeatures);
+
+        unsigned count = 0u;
+
+        while (gzd_cursor.valid() && gzd_cursor->hasMore())
+        {
+            osg::ref_ptr<Feature> feature = gzd_cursor->nextFeature();
+            std::string gzd = feature->getString("gzd");
+            if (!gzd.empty())
+            {
+                GZDGeom* geom = new GZDGeom(gzd);
+                geom->setupData(feature.get(), table[gzd], &options(), _featureProfile.get(), map.get());
+                geom->setupPaging();
+                geomTop->addChild(geom);
+
+                GZDText* text = new GZDText(gzd, geom->getBound());
+                text->setupData(feature.get(), table[gzd], &options());
+                text->setupPaging();
+                textTop->addChild(text);
+
+                ++count;
+            }
+            else
+            {
+                OE_WARN << LC << "INTERNAL ERROR: GZD empty!" << std::endl;
+            }
+        }
+
+        // Install the UTM grid labeler
+        UTMLabelingEngine* labeler = new UTMLabelingEngine(_map->getSRS());
+        _root->addChild(labeler);
+
+        // Figure out the maximum labeling resolution
+        StyleSheet* ss = _options->styleSheet().get();
+        double maxRes = 100000.0;
+        if (ss->getStyle("10000", false)) maxRes = 10000.0;
+        if (ss->getStyle("1000", false)) maxRes = 1000.0;
+        if (ss->getStyle("100", false)) maxRes = 100.0;
+        if (ss->getStyle("10", false)) maxRes = 10.0;
+        if (ss->getStyle("1", false)) maxRes = 1.0;
+        labeler->setMaxResolution(maxRes);
+
+        osg::ref_ptr<StateSetCache> sscache = new StateSetCache();
+        sscache->optimize(geomTop);
+        sscache->optimize(textTop);
+    }
+    else
+    {
+        OE_WARN << LC << "SQID data file not opened" << std::endl;
+    }
 }
 
-osg::Group*
-MGRSGraticule::buildGZDChildren( osg::Group* parent, const std::string& gzd )
+// Algorithmically builds the world GZD cells
+void
+MGRSGraticule::loadGZDFeatures(const SpatialReference* geosrs, FeatureList& output) const
 {
-    osg::BoundingSphere bs = parent->getBound();
+    std::map<std::string, GeoExtent> _gzd;
+
+    // build the base Grid Zone Designator (GZD) loolup table. This is a table
+    // that maps the GZD string to its extent.
+    static std::string s_gzdRows( "CDEFGHJKLMNPQRSTUVWX" );
+
+    // build the lateral zones:
+    for( unsigned zone = 0; zone < 60; ++zone )
+    {
+        for( unsigned row = 0; row < s_gzdRows.size(); ++row )
+        {
+            double yMaxExtra = row == s_gzdRows.size()-1 ? 4.0 : 0.0; // extra 4 deg for row X
+
+            GeoExtent cellExtent(
+                geosrs,
+                -180.0 + double(zone)*6.0,
+                -80.0  + row*8.0,
+                -180.0 + double(zone+1)*6.0,
+                -80.0  + double(row+1)*8.0 + yMaxExtra );
+
+            _gzd[ Stringify() << std::setfill('0') << std::setw(2) << (zone+1) << s_gzdRows[row] ] = cellExtent;
+        }        
+    }
+
+    // the polar zones (UPS):
+    _gzd["01Y"] = GeoExtent( geosrs, -180.0,  84.0,   0.0,  90.0 );
+    _gzd["01Z"] = GeoExtent( geosrs,    0.0,  84.0, 180.0,  90.0 );
+    _gzd["01A"] = GeoExtent( geosrs, -180.0, -90.0,   0.0, -80.0 );
+    _gzd["01B"] = GeoExtent( geosrs,    0.0, -90.0, 180.0, -80.0 );
+
+    // replace the "exception" zones in Norway and Svalbard
+    _gzd["31V"] = GeoExtent( geosrs, 0.0, 56.0, 3.0, 64.0 );
+    _gzd["32V"] = GeoExtent( geosrs, 3.0, 56.0, 12.0, 64.0 );
+    _gzd["31X"] = GeoExtent( geosrs, 0.0, 72.0, 9.0, 84.0 );
+    _gzd["33X"] = GeoExtent( geosrs, 9.0, 72.0, 21.0, 84.0 );
+    _gzd["35X"] = GeoExtent( geosrs, 21.0, 72.0, 33.0, 84.0 );
+    _gzd["37X"] = GeoExtent( geosrs, 33.0, 72.0, 42.0, 84.0 );
+
+    // ..and remove the non-existant zones:
+    _gzd.erase( "32X" );
+    _gzd.erase( "34X" );
+    _gzd.erase( "36X" );
+
+    // Now go through the table and create features for these things
+    for (std::map<std::string, GeoExtent>::const_iterator i = _gzd.begin(); i != _gzd.end(); ++i)
+    {
+        const std::string& gzd = i->first;
+        const GeoExtent& extent = i->second;
+
+        Vec3dVector points;
+
+        TessellateOperator::tessellateGeo(
+            osg::Vec3d(extent.west(), extent.south(), 0),
+            osg::Vec3d(extent.east(), extent.south(), 0), 
+            20, GEOINTERP_RHUMB_LINE, points);
+        points.resize(points.size()-1);
+
+        TessellateOperator::tessellateGeo(
+            osg::Vec3d(extent.east(), extent.south(), 0),
+            osg::Vec3d(extent.east(), extent.north(), 0), 
+            20, GEOINTERP_GREAT_CIRCLE, points);
+        points.resize(points.size()-1);
+
+        TessellateOperator::tessellateGeo(
+            osg::Vec3d(extent.east(), extent.north(), 0),
+            osg::Vec3d(extent.west(), extent.north(), 0), 
+            20, GEOINTERP_RHUMB_LINE, points);
+        points.resize(points.size()-1);
+
+        TessellateOperator::tessellateGeo(
+            osg::Vec3d(extent.west(), extent.north(), 0),
+            osg::Vec3d(extent.west(), extent.south(), 0), 
+            20, GEOINTERP_GREAT_CIRCLE, points);
+
+        osg::ref_ptr<LineString> line = new LineString(&points);
+        Feature* feature = new Feature(line.get(), geosrs);
+        std::string gzd_padded = gzd.length() < 3 ? ("0" + gzd) : gzd;
+        feature->set("gzd", gzd_padded);
+        output.push_back(feature);
+    }
+}
+
+void
+MGRSGraticule::setUpDefaultStyles()
+{
+    float alpha = 0.35f;
+
+    StyleSheet* styles = options().styleSheet().get();
+    if (styles)
+    {
+        // GZD
+        if (styles->getStyle("gzd", false) == 0L)
+        {
+            Style style("gzd");
+            LineSymbol* line = style.getOrCreate<LineSymbol>();
+            line->stroke()->color().set(1, 0, 0, 0.25);
+            line->stroke()->width() = 4.0;
+            line->tessellation() = 20;
+            TextSymbol* text = style.getOrCreate<TextSymbol>();
+            text->fill()->color() = Color::Gray;
+            text->halo()->color() = Color::Black;
+            text->alignment() = TextSymbol::ALIGN_LEFT_BOTTOM;
+            styles->addStyle(style);
+        }
+
+        // SQID 100km (support "sqid" as an alias for "100000")
+        const Style* sqid = styles->getStyle("sqid", false);
+        if (sqid)
+        {
+            Style alias(*sqid);
+            alias.setName("100000");
+            styles->addStyle(alias);
+        }
+
+        if (styles->getStyle("100000", false) == 0L)
+        {
+            Style style("100000");
+            LineSymbol* line = style.getOrCreate<LineSymbol>();
+            line->stroke()->color().set(1,1,0,alpha);
+            line->stroke()->width() = 3;
+            TextSymbol* text = style.getOrCreate<TextSymbol>();
+            text->fill()->color() = Color::Gray;
+            text->halo()->color() = Color::Black;
+            text->alignment() = TextSymbol::ALIGN_LEFT_BOTTOM;
+            styles->addStyle(style);
+        }
+
+        // 10km
+        if (styles->getStyle("10000", false) == 0L)
+        {
+            Style style("10000");
+            LineSymbol* line = style.getOrCreate<LineSymbol>();
+            line->stroke()->color().set(0,1,0,alpha);
+            line->stroke()->width() = 2;
+            styles->addStyle(style);
+        }
 
-    std::string uri = Stringify() << gzd << "." << getID() << "." << MGRS_GRATICULE_EXTENSION;
+        // 1km
+        if (styles->getStyle("1000", false) == 0L)
+        {
+            Style style("1000");
+            LineSymbol* line = style.getOrCreate<LineSymbol>();
+            line->stroke()->color().set(.5,.5,1,alpha);
+            line->stroke()->width() = 2;
+            styles->addStyle(style);
+        }
 
-    osg::PagedLOD* plod = new osg::PagedLOD();
-    plod->setCenter( bs.center() );
-    plod->addChild( parent, 0.0, FLT_MAX );
-    plod->setFileName( 1, uri );
-    plod->setRange( 1, 0, bs.radius() * 10.0 );
+        // 100m
+        if (styles->getStyle("100", false) == 0L)
+        {
+            Style style("100");
+            LineSymbol* line = style.getOrCreate<LineSymbol>();
+            line->stroke()->color().set(1,1,1,alpha);
+            line->stroke()->width() = 1;
+            styles->addStyle(style);
+        }
+
+        // 10m
+        if (styles->getStyle("10", false) == 0L)
+        {
+            Style style("10");
+            LineSymbol* line = style.getOrCreate<LineSymbol>();
+            line->stroke()->color().set(1,1,1,alpha);
+            line->stroke()->width() = 1;
+            styles->addStyle(style);
+        }
 
-    return plod;
+        // 1m
+        if (styles->getStyle("1", false) == 0L)
+        {
+            Style style("1");
+            LineSymbol* line = style.getOrCreate<LineSymbol>();
+            line->stroke()->color().set(1,1,1,alpha);
+            line->stroke()->width() = 0.5;
+            styles->addStyle(style);
+        }
+    }
 }
 
+// ------ OLD CODE --------------------------------------------------------
+
+
+#if 0
+
 osg::Node*
 MGRSGraticule::buildSQIDTiles( const std::string& gzd )
 {
-    const GeoExtent& extent = _gzd[gzd];
+    const GeoExtent& extent = _utmData.sectorTable()[gzd];
 
     // parse the GZD into its components:
     unsigned zone;
     char letter;
     sscanf( gzd.c_str(), "%u%c", &zone, &letter );
     
-    const TextSymbol* textSymFromOptions = _options->secondaryStyle()->get<TextSymbol>();
+    const TextSymbol* textSymFromOptions = options().sqidStyle()->get<TextSymbol>();
     if ( !textSymFromOptions )
-        textSymFromOptions = _options->primaryStyle()->get<TextSymbol>();
+        textSymFromOptions = options().sqidStyle()->get<TextSymbol>();
 
     // copy it since we intend to alter it
     osg::ref_ptr<TextSymbol> textSym = 
@@ -146,7 +1337,7 @@ MGRSGraticule::buildSQIDTiles( const std::string& gzd )
     extent.getSRS()->transform(centerMap, ecefSRS, centerECEF);
 
     osg::Matrix local2world;
-    ecefSRS->createLocalToWorld( centerECEF, local2world ); //= ECEF::createLocalToWorld(centerECEF);
+    ecefSRS->createLocalToWorld( centerECEF, local2world );
     osg::Matrix world2local;
     world2local.invert(local2world);
 
@@ -526,45 +1717,44 @@ MGRSGraticule::buildSQIDTiles( const std::string& gzd )
 
     osg::Group* group = new osg::Group();
 
-    Style lineStyle;
-    lineStyle.add( _options->secondaryStyle()->get<LineSymbol>() );
-
-    GeometryCompiler compiler;
-    osg::ref_ptr<Session> session = new Session( getMapNode()->getMap() );
-    FilterContext context( session.get(), _featureProfile.get(), extent );
+    osg::ref_ptr<const Map> map;
+    if (_map.lock(map))
+    {
+        Style lineStyle;
+        lineStyle.add( options().sqidStyle()->get<LineSymbol>() );
 
-    // make sure we get sufficient tessellation:
-    compiler.options().maxGranularity() = 0.25;
+        GeometryCompiler compiler;
+        osg::ref_ptr<Session> session = new Session(map.get());
+        FilterContext context( session.get(), _featureProfile.get(), extent );
 
-    osg::Node* geomNode = compiler.compile(features, lineStyle, context);
-    if ( geomNode ) 
-        group->addChild( geomNode );
+        // make sure we get sufficient tessellation:
+        compiler.options().maxGranularity() = 0.25;
 
-    osg::MatrixTransform* mt = new osg::MatrixTransform(local2world);
-    mt->addChild(textGeode);
-    group->addChild( mt );
+        osg::Node* geomNode = compiler.compile(features, lineStyle, context);
+        if ( geomNode ) 
+            group->addChild( geomNode );
 
-    Registry::shaderGenerator().run(textGeode, Registry::stateSetCache());
+        osg::MatrixTransform* mt = new osg::MatrixTransform(local2world);
+        mt->addChild(textGeode);
+        group->addChild( mt );
 
-    // prep for depth offset:
-    //DepthOffsetUtils::prepareGraph( group );
-    //group->getOrCreateStateSet()->addUniform( _minDepthOffset.get() );
+        Registry::shaderGenerator().run(textGeode, Registry::stateSetCache());
+    }
 
     return group;
 }
 
-
 //---------------------------------------------------------------------------
 
 namespace osgEarth { namespace Util
 {
     // OSG Plugin for loading subsequent graticule levels
-    class MGRSGraticuleFactory : public osgDB::ReaderWriter
+    class MGRSGraticulePseudoLoader : public osgDB::ReaderWriter
     {
     public:
-        MGRSGraticuleFactory()
+        MGRSGraticulePseudoLoader()
         {
-            supportsExtension( MGRS_GRATICULE_EXTENSION, "osgEarth MGRS graticule" );
+            supportsExtension( MGRS_GRATICULE_PSEUDOLOADER_EXTENSION, "osgEarth MGRS graticule" );
         }
 
         const char* className() const
@@ -574,7 +1764,7 @@ namespace osgEarth { namespace Util
 
         bool acceptsExtension(const std::string& extension) const
         {
-            return osgDB::equalCaseInsensitive(extension, MGRS_GRATICULE_EXTENSION);
+            return osgDB::equalCaseInsensitive(extension, MGRS_GRATICULE_PSEUDOLOADER_EXTENSION);
         }
 
         ReadResult readNode(const std::string& uri, const Options* options) const
@@ -583,30 +1773,29 @@ namespace osgEarth { namespace Util
             if ( !acceptsExtension( ext ) )
                 return ReadResult::FILE_NOT_HANDLED;
 
-            // the graticule definition is formatted: LEVEL_ID.MARKER.EXTENSION
-            std::string def = osgDB::getNameLessExtension( uri );
-            
-            std::string idStr = osgDB::getFileExtension(def);
-            unsigned id;
-            sscanf(idStr.c_str(), "%u", &id);
-
-            std::string gzd = osgDB::getNameLessExtension(def);
+            if ( !options )
+            {
+                OE_WARN << LC << "INTERNAL ERROR: MGRSGraticule object not present in Options (1)\n";
+                return ReadResult::ERROR_IN_READING_FILE;
+            }
 
-            // look up the graticule referenced in the location name:
-            MGRSGraticule* graticule = 0L;
+            osg::ref_ptr<MGRSGraticule> graticule;
+            if (!OptionsData<MGRSGraticule>::lock(options, "osgEarth.MGRSGraticule", graticule))
             {
-                Threading::ScopedMutexLock lock( UTMGraticule::s_graticuleMutex );
-                UTMGraticule::UTMGraticuleRegistry::iterator i = UTMGraticule::s_graticuleRegistry.find(id);
-                if ( i != UTMGraticule::s_graticuleRegistry.end() )
-                    graticule = dynamic_cast<MGRSGraticule*>( i->second.get() );
+                OE_WARN << LC << "INTERNAL ERROR: MGRSGraticule object not present in Options (2)\n";
+                return ReadResult::ERROR_IN_READING_FILE;
             }
 
+            std::string def = osgDB::getNameLessExtension(uri);
+            std::string gzd = osgDB::getNameLessExtension(def);
+            
             osg::Node* result = graticule->buildSQIDTiles( gzd );
+
             return result ? ReadResult(result) : ReadResult::ERROR_IN_READING_FILE;
         }
     };
-    REGISTER_OSGPLUGIN(MGRS_GRATICULE_EXTENSION, MGRSGraticuleFactory)
+    REGISTER_OSGPLUGIN(MGRS_GRATICULE_PSEUDOLOADER_EXTENSION, MGRSGraticulePseudoLoader);
 
 } } // namespace osgEarth::Util
 
-
+#endif
\ No newline at end of file
diff --git a/src/osgEarthUtil/MouseCoordsTool.cpp b/src/osgEarthUtil/MouseCoordsTool.cpp
index b39c652..5b2d348 100644
--- a/src/osgEarthUtil/MouseCoordsTool.cpp
+++ b/src/osgEarthUtil/MouseCoordsTool.cpp
@@ -23,7 +23,8 @@
 #include <osgEarth/Terrain>
 #include <osgEarth/TerrainEngineNode>
 #include <osgViewer/View>
-#include <osgEarth/DPLineSegmentIntersector>
+#include <osgUtil/LineSegmentIntersector>
+#include <osgEarth/Registry>
 
 using namespace osgEarth;
 using namespace osgEarth::Util;
@@ -67,19 +68,22 @@ MouseCoordsTool::handle( const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapt
                 i->get()->reset( aa.asView(), _mapNode );
         }
 
-#if 0 // testing AGL
+#if 1 // testing AGL, Dist to Point
         osg::Vec3d eye, center, up;
         aa.asView()->getCamera()->getViewMatrixAsLookAt(eye, center, up);
-        DPLineSegmentIntersector* lsi = new DPLineSegmentIntersector(eye, osg::Vec3d(0,0,0));
+        osgUtil::LineSegmentIntersector* lsi = new osgUtil::LineSegmentIntersector(eye, osg::Vec3d(0,0,0));
         osgUtil::IntersectionVisitor iv(lsi);
         lsi->setIntersectionLimit(lsi->LIMIT_NEAREST);
-        iv.setUserData( new Map() );
+        //iv.setUserData( new Map() );
         _mapNode->accept(iv);
 
         if ( !lsi->getIntersections().empty() )
         {            
             double agl = (eye - lsi->getFirstIntersection().getWorldIntersectPoint()).length();
-            OE_NOTICE << "AGL = " << agl << "m" << std::endl;
+            double dtp = (eye - world).length();
+            //OE_NOTICE << "AGL = " << agl << "m; DPT = " << dtp << "m" << std::endl;
+            Registry::instance()->startActivity("AGL", Stringify() << agl << " m");
+            Registry::instance()->startActivity("Range", Stringify() << dtp << " m");
         }
 #endif
     }
@@ -112,7 +116,9 @@ MouseCoordsLabelCallback::set( const GeoPoint& mapCoords, osg::View* view, MapNo
         {
             _label->setText( Stringify()
                 <<  _formatter->format( mapCoords )
-                << ", " << mapCoords.z() );
+                << ", " << mapCoords.z() 
+                << "  |  "
+                << mapCoords.getSRS()->getName() );
         }
         else
         {
@@ -120,7 +126,9 @@ MouseCoordsLabelCallback::set( const GeoPoint& mapCoords, osg::View* view, MapNo
                 << std::fixed
                 << mapCoords.x()
                 << ", " << mapCoords.y()
-                << ", " << mapCoords.z() );
+                << ", " << mapCoords.z()
+                << "  |  "
+                << mapCoords.getSRS()->getName() );
         }
     }
 }
diff --git a/src/osgEarthUtil/MultiElevationLayer b/src/osgEarthUtil/MultiElevationLayer
new file mode 100644
index 0000000..6fac9a7
--- /dev/null
+++ b/src/osgEarthUtil/MultiElevationLayer
@@ -0,0 +1,100 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+ * Copyright 2016 Pelican Mapping
+ * http://osgearth.org
+ *
+ * osgEarth is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+#ifndef OSGEARTH_UTIL_MULTI_ELEVATION_LAYER
+#define OSGEARTH_UTIL_MULTI_ELEVATION_LAYER 1
+
+#include <osgEarthUtil/Common>
+#include <osgEarth/ElevationLayer>
+
+namespace osgEarth { namespace Util
+{
+    using namespace osgEarth;
+
+    /**
+     * Combines multiple elevation layers into one.
+     */
+    class OSGEARTHUTIL_EXPORT MultiElevationLayerOptions : public ElevationLayerOptions
+    {
+    public:
+        // constructor
+        MultiElevationLayerOptions(const ConfigOptions& co = ConfigOptions());
+        
+        std::vector<ConfigOptions>& layers() { return _layers; }
+        const std::vector<ConfigOptions>& layers() const { return _layers; }
+
+    public:
+        virtual Config getConfig() const;
+
+    protected:
+        virtual void mergeConfig(const Config& conf) {
+            ElevationLayerOptions::mergeConfig(conf);
+            fromConfig(conf);
+        }
+
+        void fromConfig(const Config& conf);
+        
+    private:
+        std::vector<ConfigOptions> _layers;
+    };
+
+
+    /**
+     * Elevation layer that...
+     */
+    class OSGEARTHUTIL_EXPORT MultiElevationLayer : public ElevationLayer
+    {
+    public:
+        META_Layer(osgEarth, MultiElevationLayer, MultiElevationLayerOptions);
+
+        //! Create a blank layer to be configurated through options().
+        MultiElevationLayer();
+
+        //! Create a layer with initial options.
+        MultiElevationLayer(const MultiElevationLayerOptions& options);
+
+    public: // ElevationLayer
+
+        // override to generate custom heightfield
+        virtual void createImplementation(
+            const TileKey& key,
+            osg::ref_ptr<osg::HeightField>& out_heightField,
+            osg::ref_ptr<NormalMap>& out_normalMap,
+            ProgressCallback* progress);
+
+    protected: // Layer
+
+        // opens the layer and returns the status
+        virtual const Status& open();
+
+        virtual void init();
+
+        virtual void addedToMap(const class Map*);
+
+        virtual void removedFromMap(const class Map*);
+
+    protected:
+
+        virtual ~MultiElevationLayer();
+
+        ElevationLayerVector _layers;
+    };
+
+} } // namespace osgEarth::Util
+
+#endif // OSGEARTH_UTIL_FRACTAL_ELEVATION_LAYER
diff --git a/src/osgEarthUtil/MultiElevationLayer.cpp b/src/osgEarthUtil/MultiElevationLayer.cpp
new file mode 100644
index 0000000..2c245a7
--- /dev/null
+++ b/src/osgEarthUtil/MultiElevationLayer.cpp
@@ -0,0 +1,179 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+ * Copyright 2016 Pelican Mapping
+ * http://osgearth.org
+ *
+ * osgEarth is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+#include <osgEarthUtil/MultiElevationLayer>
+
+using namespace osgEarth;
+using namespace osgEarth::Util;
+
+#define LC "[MultiElevationLayer] "
+
+#define OE_TEST OE_DEBUG
+
+REGISTER_OSGEARTH_LAYER(multi_elevation, MultiElevationLayer);
+
+//............................................................................
+
+MultiElevationLayerOptions::MultiElevationLayerOptions(const ConfigOptions& co) :
+ElevationLayerOptions(co)
+{
+    fromConfig(_conf);
+}
+
+void
+MultiElevationLayerOptions::fromConfig(const Config& conf)
+{
+    const ConfigSet layers = conf.child("layers").children();
+    for (ConfigSet::const_iterator i = layers.begin(); i != layers.end(); ++i)
+        _layers.push_back(*i);
+}
+
+Config
+MultiElevationLayerOptions::getConfig() const
+{
+    Config conf = ElevationLayerOptions::getConfig();
+    conf.key() = "multi_elevation";
+    Config layers("layers");
+    for (std::vector<ConfigOptions>::const_iterator i = _layers.begin(); i != _layers.end(); ++i)
+        layers.add(i->getConfig());
+    conf.add(layers);
+    return conf;
+}
+
+//............................................................................
+
+MultiElevationLayer::MultiElevationLayer() :
+ElevationLayer(&_optionsConcrete),
+_options(&_optionsConcrete)
+{
+    init();
+}
+
+MultiElevationLayer::MultiElevationLayer(const MultiElevationLayerOptions& options) :
+ElevationLayer(&_optionsConcrete),
+_options(&_optionsConcrete),
+_optionsConcrete(options)
+{
+    init();
+}
+
+MultiElevationLayer::~MultiElevationLayer()
+{
+    //todo
+}
+
+void
+MultiElevationLayer::init()
+{
+    setTileSourceExpected(false);
+    ElevationLayer::init();
+}
+
+const Status&
+MultiElevationLayer::open()
+{
+    for (std::vector<ConfigOptions>::const_iterator i = options().layers().begin();
+        i != options().layers().end();
+        ++i)
+    {
+        const ConfigOptions& co = *i;
+        osg::ref_ptr<Layer> layer = Layer::create(co);
+        if (layer.valid())
+        {
+            OE_INFO << LC << "Adding layer \"" << layer->getName() << "\"...\n";
+            ElevationLayer* elayer = dynamic_cast<ElevationLayer*>(layer.get());
+            if (elayer)
+            {
+                elayer->setReadOptions(getReadOptions());
+                const Status& s = elayer->open();
+                if (!s.isOK())
+                {
+                    // fail.
+                    return setStatus(s);
+                }
+
+                // Take profile from the first successfully opened layer.
+                if (!getProfile())
+                {
+                    setProfile(elayer->getProfile());
+                }
+
+                _layers.push_back(elayer);
+
+                //TODO: establish the data extents
+            }
+            else
+            {
+                OE_WARN << LC << "Illegal to add a non-elevation layer\n";
+                return setStatus(Status::Error(Status::ConfigurationError, "Only elevation layers are allowed"));
+            }
+        }
+    }
+
+    return ElevationLayer::open();
+}
+
+void
+MultiElevationLayer::addedToMap(const Map* map)
+{
+    for (ElevationLayerVector::iterator i = _layers.begin(); i != _layers.end(); ++i)
+    {
+        i->get()->addedToMap(map);
+    }
+}
+
+void
+MultiElevationLayer::removedFromMap(const Map* map)
+{
+    for (ElevationLayerVector::iterator i = _layers.begin(); i != _layers.end(); ++i)
+    {
+        i->get()->removedFromMap(map);
+    }
+}
+
+void
+MultiElevationLayer::createImplementation(const TileKey& key,
+                                          osg::ref_ptr<osg::HeightField>& out_heightField,
+                                          osg::ref_ptr<NormalMap>& out_normalMap,
+                                          ProgressCallback* progress)
+{
+    if (!out_heightField.valid())
+    {
+        out_heightField = new osg::HeightField();
+        out_heightField->allocate(257, 257);
+        out_heightField->getFloatArray()->assign(out_heightField->getFloatArray()->size(), NO_DATA_VALUE);
+
+        out_normalMap = new NormalMap(257, 257);
+    }
+
+    // Populate the heightfield and return it if it's valid
+    bool realData = _layers.populateHeightFieldAndNormalMap(
+        out_heightField.get(),
+        out_normalMap.get(),
+        key,
+        0L,
+        INTERP_BILINEAR,
+        progress);
+
+    if (!realData)
+    {
+        // Didn't get any real data? Discard the results.
+        out_heightField = 0L;
+        out_normalMap = 0L;
+    }
+}
diff --git a/src/osgEarthUtil/NightColorFilter.cpp b/src/osgEarthUtil/NightColorFilter.cpp
index 00706f4..bfdcaf8 100644
--- a/src/osgEarthUtil/NightColorFilter.cpp
+++ b/src/osgEarthUtil/NightColorFilter.cpp
@@ -35,10 +35,11 @@ namespace
     static OpenThreads::Atomic s_uniformNameGen;
 
     static const char* s_localShaderSource =
-        "#version 110\n"
+        "#version " GLSL_VERSION_STR "\n"
+        GLSL_DEFAULT_PRECISION_FLOAT "\n"
 
-        "varying vec3 atmos_lightDir;\n"    // light direction (view coords)
-        "varying vec3 atmos_up;\n"          // earth up vector at fragment (in view coords)
+        "in vec3 atmos_lightDir;\n"    // light direction (view coords)
+        "in vec3 atmos_up;\n"          // earth up vector at fragment (in view coords)
 
         "void __ENTRY_POINT__(inout vec4 color)\n"
         "{\n"
diff --git a/src/osgEarthUtil/ObjectLocator b/src/osgEarthUtil/ObjectLocator
deleted file mode 100644
index 71f01fb..0000000
--- a/src/osgEarthUtil/ObjectLocator
+++ /dev/null
@@ -1,204 +0,0 @@
-/* -*-c++-*- */
-/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2016 Pelican Mapping
- * http://osgearth.org
- *
- * osgEarth is free software; you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>
- */
-#ifndef OSGEARTH_UTIL_OBJECT_LOCATOR_H
-#define OSGEARTH_UTIL_OBJECT_LOCATOR_H
-
-#include <osgEarthUtil/Common>
-#include <osgEarth/Map>
-#include <osgEarth/Revisioning>
-#include <osg/MatrixTransform>
-
-namespace osgEarth { namespace Util
-{
-    using namespace osgEarth;
-
-    /**
-     * @deprecated - slated for removal after Version 2.7
-     * ObjectLocator - a revisioned object that generates a positional matrix for a node.
-     */
-    class OSGEARTHUTIL_EXPORT ObjectLocator : public osg::Referenced, public Revisioned
-    {
-    public:
-
-        /** Flags that represent separable location components. */
-        enum Components {
-            COMP_NONE           = 0x00,
-            COMP_POSITION       = 0x01,
-            COMP_HEADING        = 0x02,
-            COMP_PITCH          = 0x04,
-            COMP_ROLL           = 0x08,
-            COMP_ORIENTATION    = COMP_HEADING | COMP_PITCH | COMP_ROLL,
-            COMP_ALL            = COMP_POSITION | COMP_ORIENTATION
-        };
-
-        /** The order in which rotation are calculated */
-        enum RotationOrder {
-            HPR,
-            RPH
-        };
-
-    public:
-
-        /**
-         * Constructs a new locator that will generate positional matricies based on
-         * the specified SRS and rotation order.
-         */
-        ObjectLocator( const osgEarth::Map* map );
-
-        /**
-         * Constucts a new relative locator that inherits the mask of specified
-         * components from a parent locator.
-         */
-        ObjectLocator(ObjectLocator* parent, unsigned int compsToInherit =COMP_ALL );
-
-        /** dtor */
-        virtual ~ObjectLocator() { }
-
-        /**
-         * Sets the absolute OR relative positioning of this locator (depending on whether
-         * this locator has a parent). Units conform to this Locator's SRS.
-         */
-        void setPosition( const osg::Vec3d& pos );
-        const osg::Vec3d& getPosition() const { return _pos; }
-
-        /**
-         * Sets the absolute OR relative orientation of this locator (depending on whether
-         * this locator has a parent). Units are Euler angle degrees.
-         */
-        void setOrientation( const osg::Vec3d& hpr_deg );
-        const osg::Vec3d& getOrientation() const { return _hpr; }
-
-        /**
-         * The timestamp associated with this locator's position information.
-         * (Note: setting the time does not "dirty" the locator)
-         */
-        void setTime( double t ) { _timestamp = t; }
-        double getTime() const { return _timestamp; }
-
-        /**
-         * The order in which to calculate heading, pitch, and roll rotations
-         */
-        void setRotationOrder( RotationOrder value ) { _rotOrder = value; }
-        RotationOrder getRotationOrder() const { return _rotOrder; }
-
-        /**
-         * The optional parent locator. If a Locator has a parent, it inherits position and
-         * orientation from that parent as prescribed by the Components flags. Otherwise,
-         * the Locator is absolute.
-         */
-        void setParentLocator( ObjectLocator* parent, unsigned int componentsToInherit =COMP_ALL );
-        ObjectLocator* getParentLocator() { return _parentLoc.get(); }
-        const ObjectLocator* getParentLocator() const { return _parentLoc.get(); }
-
-        /** Policy for inheriting parent locator's components */
-        void setComponentsToInherit( unsigned int compMask );
-        unsigned int getComponentsToInherit() const { return _componentsToInherit; }
-
-        /** Gets the map associated with this locator. */
-        const Map* getMap() const { return _map.get(); }
-
-        /** Whether the locator contains a valid position/orientation. */
-        bool isEmpty() const;
-
-        /** Whether this location contains valid data */
-        bool isValid() const { return !_isEmpty && _map.valid(); }
-
-    public:
-
-        /**
-         * Gets the aggregate position represented by this locator,
-         * returning true upon success.
-         */
-        bool getLocatorPosition( osg::Vec3d& output ) const;
-
-        /**
-         * Gets the aggregate positioning matrix for this locator,
-         * returning true upon success.
-         */
-        bool getPositionMatrix( osg::Matrixd& output ) const;
-
-        /**
-         * Gets the aggregate orientation (HPR degrees) represented by this locator,
-         * returning true upon success.
-         */
-        bool getLocatorOrientation( osg::Vec3d& output ) const;
-
-        /**
-         * Gets the aggregate orientation matrix for this locator,
-         * returning true upon success.
-         */
-        bool getOrientationMatrix( osg::Matrixd& output, unsigned inherit =COMP_ALL ) const;
-
-        /**
-         * Gets a matrix that can be used to position and orient an object corresponding
-         * to this locator, returning true upon success.
-         */
-        bool getLocatorMatrix( osg::Matrixd& output, unsigned components =COMP_ALL ) const;
-
-    public:
-        //override
-        /** Override Revisioned::inSync to track with the parent locator's revision. */
-        virtual bool inSyncWith( int exRev ) const;
-
-    private:
-        osg::observer_ptr<const osgEarth::Map> _map;
-        osg::ref_ptr<ObjectLocator> _parentLoc;
-        unsigned int _componentsToInherit; // Locator::Components mask
-        RotationOrder _rotOrder;
-        osg::Vec3d _pos;
-        osg::Vec3d _hpr;
-        double _timestamp;
-        bool _isEmpty;
-    };
-
-
-    /**
-     * A transform node that tracks the position/orientation information in an ObjectLocator.
-     */
-    class OSGEARTHUTIL_EXPORT ObjectLocatorNode : public osg::MatrixTransform
-    {
-    public:
-        ObjectLocatorNode();
-        ObjectLocatorNode( ObjectLocator* locator );
-        ObjectLocatorNode( const Map* map );
-        ObjectLocatorNode( const ObjectLocatorNode& rhs, const osg::CopyOp& =osg::CopyOp::SHALLOW_COPY );
-        META_Node(osgEarthUtil, ObjectLocatorNode);
-
-    public:
-        /** 
-         * The locator creates the positioning matrix for the component.
-         */
-        void setLocator( ObjectLocator* locator );
-        ObjectLocator* getLocator() { return _locator.get(); }
-        const ObjectLocator* getLocator() const { return _locator.get(); }
-
-    public:
-        /** Synchronizes the transform matrix with the locator. */
-        virtual void update();
-
-        virtual void traverse(osg::NodeVisitor &nv);
-
-    private:
-        osg::ref_ptr<ObjectLocator> _locator;
-        osgEarth::Revision _matrixRevision;
-    };
-
-} } // namespace osgEarth::Util
-
-#endif // OSGEARTH_UTIL_OBJECT_LOCATOR_H
diff --git a/src/osgEarthUtil/ObjectLocator.cpp b/src/osgEarthUtil/ObjectLocator.cpp
deleted file mode 100644
index 6f88baa..0000000
--- a/src/osgEarthUtil/ObjectLocator.cpp
+++ /dev/null
@@ -1,288 +0,0 @@
-#include <osgEarthUtil/ObjectLocator>
-
-using namespace osgEarth;
-using namespace osgEarth::Util;
-
-#define LC "[ObjectLocator] "
-
-/***************************************************************************/
-
-namespace
-{
-    void getHPRFromQuat(const osg::Quat& q, double& h, double& p, double& r)
-    {
-        osg::Matrixd rot(q);
-        p = asin(rot(1,2));
-        if( osg::equivalent(osg::absolute(p), osg::PI_2) )
-        {
-            r = 0.0;
-            h = atan2( rot(0,1), rot(0,0) );
-        }
-        else
-        {
-            r = atan2( rot(0,2), rot(2,2) );
-            h = atan2( rot(1,0), rot(1,1) );
-        }
-        h = osg::RadiansToDegrees(h);
-        p = osg::RadiansToDegrees(p);
-        r = osg::RadiansToDegrees(r);
-    }
-}
-
-/***************************************************************************/
-
-ObjectLocator::ObjectLocator(const osgEarth::Map* map) :
-_map                ( map ),
-_componentsToInherit( COMP_ALL ),
-_timestamp          ( 0.0 ),
-_isEmpty            ( true ),
-_rotOrder           ( HPR )
-{
-    if ( !_map.valid() )
-        OE_WARN << LC << "Illegal: cannot create an ObjectLocator with a NULL Map." << std::endl;
-}
-
-ObjectLocator::ObjectLocator(ObjectLocator* parentLoc, unsigned int inheritMask ) :
-_timestamp( 0.0 ),
-_isEmpty  ( false ),
-_rotOrder ( HPR )
-{
-    setParentLocator( parentLoc, inheritMask );
-    _map = parentLoc->_map.get();
-}
-
-bool
-ObjectLocator::isEmpty() const
-{
-    return _parentLoc.valid() ? _parentLoc->isEmpty() : _isEmpty;
-}
-
-void
-ObjectLocator::setParentLocator( ObjectLocator* newParent, unsigned int inheritMask )
-{
-    if ( newParent == this )
-    {
-        OE_WARN << LC << "Illegal state, locator cannot be its own parent." << std::endl;
-        return;
-    }
-
-    _parentLoc = newParent;
-    _componentsToInherit = inheritMask;
-
-    if ( newParent )
-    {
-        _map = newParent->_map.get();
-    }
-
-    if ( !_map.valid() )
-    {
-        OE_WARN << "Illegal state, cannot create a Locator with a NULL srs" << std::endl;
-    }
-
-    dirty();
-}
-
-void
-ObjectLocator::setPosition( const osg::Vec3d& pos )
-{
-    _pos = pos;
-    _isEmpty = false;
-    dirty();
-}
-
-void
-ObjectLocator::setOrientation( const osg::Vec3d& hpr )
-{
-    _hpr = hpr;
-    _isEmpty = false;
-    dirty();
-}
-
-bool
-ObjectLocator::getLocatorPosition( osg::Vec3d& output ) const
-{
-    if ( !isValid() )
-        return false;
-
-    output = _pos;
-
-    if ( _parentLoc.valid() && (_componentsToInherit & COMP_POSITION) != 0 )
-    {
-        osg::Vec3d parentPos;
-        _parentLoc->getLocatorPosition( parentPos );
-        output += parentPos;
-    }
-
-    return true;
-}
-
-bool
-ObjectLocator::getPositionMatrix( osg::Matrixd& output ) const
-{
-    osg::Vec3d pos;
-    if ( !getLocatorPosition(pos) )
-        return false;
-
-    if ( _map->isGeocentric() )
-    {
-        _map->getProfile()->getSRS()->getEllipsoid()->computeLocalToWorldTransformFromLatLongHeight(
-            osg::DegreesToRadians(pos.y()),
-            osg::DegreesToRadians(pos.x()),
-            pos.z(),
-            output );
-    }
-    else
-    {
-        output.makeTranslate(pos);
-    }
-
-    return true;
-}
-
-bool
-ObjectLocator::getLocatorOrientation( osg::Vec3d& output ) const
-{
-    if ( !isValid() )
-        return false;
-
-    output = _hpr;
-
-    if ( _parentLoc.valid() && (_componentsToInherit & COMP_ORIENTATION) != 0 )
-    {
-        osg::Vec3d parentHPR;
-        _parentLoc->getLocatorOrientation( parentHPR );
-        output += parentHPR;
-    }
-
-    return true;
-}
-
-bool
-ObjectLocator::getOrientationMatrix( osg::Matrixd& output, unsigned inherit ) const
-{
-    if ( !isValid() )
-        return false;
-
-    if ( (inherit & COMP_ORIENTATION) == 0 )
-        return true;
-
-    if ( _hpr[0] != 0.0 || _hpr[1] != 0.0 || _hpr[2] != 0.0 )
-    {
-        // first figure out the orientation
-        osg::Quat azim_q;
-        if ( inherit & COMP_HEADING )
-            azim_q = osg::Quat( osg::DegreesToRadians(_hpr[0]), osg::Vec3d(0,0,1) );
-
-        osg::Quat pitch_q;
-        if ( inherit & COMP_PITCH )
-            pitch_q = osg::Quat( -osg::DegreesToRadians(_hpr[1]), osg::Vec3d(1,0,0) );
-
-        osg::Quat roll_q;
-        if ( inherit & COMP_ROLL )
-            roll_q = osg::Quat( osg::DegreesToRadians(_hpr[2]), osg::Vec3d(0,1,0) );
-
-        // these look backwards, but it's actually a fast way to avoid inverting a matrix
-        if ( _rotOrder == HPR )
-            output.set( roll_q.conj() * pitch_q.conj() * azim_q.conj() );
-        else if ( _rotOrder == RPH )
-            output.set( azim_q.conj() * pitch_q.conj() * roll_q.conj() );
-
-    }
-
-    if ( _parentLoc.valid() && (_componentsToInherit * COMP_ORIENTATION) != 0 )
-    {
-        osg::Matrixd parentRot;
-        if ( _parentLoc->getOrientationMatrix(parentRot, _componentsToInherit) )
-            output = output * parentRot;
-    }
-
-    return true;
-}
-
-bool
-ObjectLocator::getLocatorMatrix( osg::Matrixd& output, unsigned comps ) const
-{
-    bool ok = true;
-    osg::Matrixd pos, rot;
-
-    if ( comps & COMP_POSITION )
-        if ( !getPositionMatrix(pos) )
-            ok = false;
-
-    if ( comps & COMP_ORIENTATION )
-        if ( !getOrientationMatrix(rot, comps) )
-            ok = false;
-
-    output = rot * pos;
-    return ok;
-}
-
-bool
-ObjectLocator::inSyncWith( int exRev ) const
-{
-    return _parentLoc.valid() ? _parentLoc->inSyncWith( exRev ) :
-        osgEarth::Revisioned::inSyncWith( exRev );
-}
-
-/***************************************************************************/
-
-ObjectLocatorNode::ObjectLocatorNode()
-{
-    setNumChildrenRequiringUpdateTraversal( 1 );
-}
-
-ObjectLocatorNode::ObjectLocatorNode( const ObjectLocatorNode& rhs, const osg::CopyOp& op ) :
-osg::MatrixTransform( rhs, op ),
-_matrixRevision( rhs._matrixRevision ),
-_locator( rhs._locator.get() )
-{
-    setNumChildrenRequiringUpdateTraversal( 1 );
-    setLocator( _locator.get() ); // to update the trav count
-}
-
-ObjectLocatorNode::ObjectLocatorNode( ObjectLocator* locator ) :
-_matrixRevision( -1 )
-{
-    setNumChildrenRequiringUpdateTraversal( 1 );
-    setLocator( locator );
-}
-
-ObjectLocatorNode::ObjectLocatorNode( const Map* map ) :
-_matrixRevision( -1 )
-{
-    setNumChildrenRequiringUpdateTraversal( 1 );
-    setLocator( new ObjectLocator(map) );
-}
-
-void
-ObjectLocatorNode::setLocator( ObjectLocator* locator )
-{
-    _locator = locator;
-    _matrixRevision = -1;    
-}
-
-
-void
-ObjectLocatorNode::traverse(osg::NodeVisitor &nv)
-{
-    if (nv.getVisitorType() == osg::NodeVisitor::UPDATE_VISITOR)
-    {
-        update();
-    }
-    osg::MatrixTransform::traverse( nv );
-}
-
-void
-ObjectLocatorNode::update()
-{
-    if ( _locator.valid() && _locator->outOfSyncWith( _matrixRevision ) )
-    {
-        osg::Matrix mat;
-        if ( _locator->getLocatorMatrix( mat ) )
-        {
-            this->setMatrix( mat );
-            this->dirtyBound();
-            _locator->sync( _matrixRevision );
-        }
-    }
-}
diff --git a/src/osgEarthUtil/PolyhedralLineOfSight.cpp b/src/osgEarthUtil/PolyhedralLineOfSight.cpp
index 6976c4b..d518d26 100644
--- a/src/osgEarthUtil/PolyhedralLineOfSight.cpp
+++ b/src/osgEarthUtil/PolyhedralLineOfSight.cpp
@@ -38,8 +38,8 @@ namespace
     struct TerrainChangedCallback : public osgEarth::TerrainCallback
     {
         TerrainChangedCallback( PolyhedralLineOfSightNode* los ) : _los(los) { }
-        void onTileAdded(const osgEarth::TileKey& tileKey, osg::Node* terrain, TerrainCallbackContext& ) {
-            _los->terrainChanged( tileKey, terrain );
+        void onTileAdded(const osgEarth::TileKey& tileKey, osg::Node* graph, TerrainCallbackContext& ) {
+            _los->terrainChanged( tileKey, graph );
         }
         PolyhedralLineOfSightNode* _los;
     };
@@ -70,7 +70,7 @@ _distance    ( Distance(50000.0, Units::METERS) )
     _terrainCallback = new TerrainChangedCallback(this);
     
     if ( mapNode )
-        mapNode->getTerrain()->addTerrainCallback( _terrainCallback );
+        mapNode->getTerrain()->addTerrainCallback( _terrainCallback.get() );
 
     osg::StateSet* stateSet = this->getOrCreateStateSet();
     stateSet->setMode( GL_BLEND, 1 );
diff --git a/src/osgEarthUtil/RTTPicker b/src/osgEarthUtil/RTTPicker
index 8801ac1..aa499d1 100644
--- a/src/osgEarthUtil/RTTPicker
+++ b/src/osgEarthUtil/RTTPicker
@@ -27,11 +27,16 @@
 #include <osg/Image>
 #include <osg/Texture2D>
 #include <queue>
+#include <list>
 
 namespace osgEarth { namespace Util
 {
     /**
      * Picks objects using an RTT camera and Vertex Attributes.
+     *
+     * Note. The Picker will change the View Slave configuration in OSG,
+     * so you should call Viewer::stopThreading() before adding or
+     * removing a picker, and Viewer::startThreading when you're done.
      */
     class OSGEARTHUTIL_EXPORT RTTPicker : public osgEarth::Picker
     {
@@ -70,7 +75,7 @@ namespace osgEarth { namespace Util
          * do NOT need to call this method directly; the Picker will call it upon handling
          * a pick event (i.e., when Callback::accept returns true).
          *
-         * Returns true if the pick was succesfully queued; false if not.
+         * Returns true if the pick was successfully queued; false if not.
          */
         virtual bool pick(osg::View* view, float mouseX, float mouseY, Callback* callback);
 
@@ -87,6 +92,13 @@ namespace osgEarth { namespace Util
         virtual bool removeChild(osg::Node* child);
         virtual bool replaceChild(osg::Node* oldChild, osg::Node* newChild);
 
+    public: // simulate osg::Camera cull mask methods
+
+        /** Specifies the cull mask for the RTT camera */
+        void setCullMask(osg::Node::NodeMask nm);
+        osg::Node::NodeMask getCullMask() const { return _cullMask; }
+
+
     public: // for debugging
 
         /** For debugging only - creates (if nec.) and returns a texture that captures
@@ -103,6 +115,7 @@ namespace osgEarth { namespace Util
         
         int                    _rttSize;     // size of the RTT image (pixels per side)
         int                    _buffer;      // buffer around pick point to check (pixels)
+        osg::Node::NodeMask    _cullMask;    // cull mask applied to the camera
         osg::ref_ptr<Callback> _defaultCallback;
 
         // Associates a view and a pick camera for that view.
@@ -112,9 +125,12 @@ namespace osgEarth { namespace Util
             osg::ref_ptr<osg::Camera>    _pickCamera;
             osg::ref_ptr<osg::Image>     _image;
             osg::ref_ptr<osg::Texture2D> _tex;
+            int _numPicks;
         };
-        typedef std::vector<PickContext> PickContextVector;
-        PickContextVector _pickContexts;
+        // use a container that does not invalidate iters on insertion, since we hold
+        // pointers to PickContext objects in the _picks member
+        typedef std::list<PickContext> PickContexts;
+        PickContexts _pickContexts;
         
         // Creates a new pick context on demand.
         PickContext& getOrCreatePickContext(osg::View* view);
@@ -127,13 +143,13 @@ namespace osgEarth { namespace Util
             unsigned               _frame;
             PickContext*           _context;
         };
-        std::queue<Pick> _picks;
+        std::vector<Pick> _picks;
 
         // Runs the queue of picks given a frame number.
         void runPicks(unsigned frameNumber);
 
-        // Checks to see if a pick succeeded and fires approprate callback.
-        void checkForPickResult(Pick& pick);
+        // Checks to see if a pick succeeded and fires appropriate callback.
+        bool checkForPickResult(Pick& pick, unsigned frameNumber);
 
         // container for common RTT pick camera children (see addChild et al.)
         osg::ref_ptr<osg::Group> _group;
diff --git a/src/osgEarthUtil/RTTPicker.cpp b/src/osgEarthUtil/RTTPicker.cpp
index fe0b644..facf1ba 100644
--- a/src/osgEarthUtil/RTTPicker.cpp
+++ b/src/osgEarthUtil/RTTPicker.cpp
@@ -22,6 +22,7 @@
 #include <osgEarth/Registry>
 #include <osgEarth/ShaderLoader>
 #include <osgEarth/ObjectIndex>
+#include <osgEarth/Utils>
 
 #include <osgDB/WriteFile>
 #include <osg/BlendFunc>
@@ -33,10 +34,33 @@ using namespace osgEarth::Util;
 
 namespace
 {
+    // Callback to set the "far plane" uniform just before drawing.
+    struct CallHostCameraPreDrawCallback : public osg::Camera::DrawCallback
+    {
+        osg::observer_ptr<osg::Camera> _hostCamera;
+
+        CallHostCameraPreDrawCallback( osg::Camera* hostCamera ) :
+            _hostCamera( hostCamera )
+        {
+        }
+
+        void operator () (osg::RenderInfo& renderInfo) const
+        {
+            osg::ref_ptr<osg::Camera> hostCamera;
+            if ( _hostCamera.lock(hostCamera) )
+            {
+                const osg::Camera::DrawCallback* hostCallback = hostCamera->getPreDrawCallback();
+                if ( hostCallback )
+                    hostCallback->operator()( renderInfo );
+            }
+        }
+    };
+
     // SHADERS for the RTT pick camera.
 
     const char* pickVertexEncode =
         "#version " GLSL_VERSION_STR "\n"
+        GLSL_DEFAULT_PRECISION_FLOAT "\n"
 
         "#pragma vp_entryPoint oe_pick_encodeObjectID\n"
         "#pragma vp_location   vertex_clip\n"
@@ -61,6 +85,7 @@ namespace
 
     const char* pickFragment =
         "#version " GLSL_VERSION_STR "\n"
+        GLSL_DEFAULT_PRECISION_FLOAT "\n"
 
         "#pragma vp_entryPoint oe_pick_renderEncodedObjectID\n"
         "#pragma vp_location   fragment_output\n"
@@ -108,17 +133,33 @@ RTTPicker::RTTPicker(int cameraSize)
 
     // pixels around the click to test
     _buffer = 2;
+    
+    // Cull mask for RTT cameras
+    _cullMask = ~0;
 }
 
 RTTPicker::~RTTPicker()
 {
     // remove the RTT camera from all views
-    for(int i=0; i<_pickContexts.size(); ++i)
+    for (PickContexts::iterator i = _pickContexts.begin(); i != _pickContexts.end(); ++i)
     {
-        PickContext& pc = _pickContexts[i];
-        while( pc._pickCamera->getNumParents() > 0 )
+        PickContext& pc = *i;
+
+        osg::ref_ptr<osg::View> view;
+        if (pc._view.lock(view))
         {
-            pc._pickCamera->getParent(0)->removeChild( pc._pickCamera.get() );
+            unsigned numSlaves = pc._view->getNumSlaves();
+            for (unsigned k = 0; k < numSlaves; ++k)
+            {
+                if (pc._view->getSlave(k)._camera.get() == pc._pickCamera.get())
+                {
+                    // Remove children of the pick camera so GL objects don't get released when
+                    // we remove the slave
+                    pc._pickCamera->removeChildren(0, pc._pickCamera->getNumChildren());
+                    pc._view->removeSlave(k);
+                    break;
+                }
+            }
         }
     }
 }
@@ -139,10 +180,37 @@ RTTPicker::getOrCreateTexture(osg::View* view)
     return pc._tex.get();
 }
 
+void
+RTTPicker::setCullMask(osg::Node::NodeMask nm)
+{
+    if ( _cullMask == nm )
+        return;
+    _cullMask = nm;
+    for(PickContexts::const_iterator i = _pickContexts.begin(); i != _pickContexts.end(); ++i)
+    {
+        i->_pickCamera->setCullMask( _cullMask );
+    }
+}
+
+namespace
+{
+    struct MyUpdateSlave : public osg::View::Slave::UpdateSlaveCallback
+    {
+        void updateSlave(osg::View& view, osg::View::Slave& slave)
+        {
+            osg::Camera* cam = slave._camera.get();
+            cam->setProjectionResizePolicy(view.getCamera()->getProjectionResizePolicy());
+            cam->setProjectionMatrix(view.getCamera()->getProjectionMatrix());
+            cam->setViewMatrix(view.getCamera()->getViewMatrix());
+            cam->inheritCullSettings(*(view.getCamera()), cam->getInheritanceMask());            
+        }
+    };
+}
+
 RTTPicker::PickContext&
 RTTPicker::getOrCreatePickContext(osg::View* view)
 {
-    for(PickContextVector::iterator i = _pickContexts.begin(); i != _pickContexts.end(); ++i)
+    for(PickContexts::iterator i = _pickContexts.begin(); i != _pickContexts.end(); ++i)
     {
         if ( i->_view.get() == view )
         {
@@ -159,49 +227,74 @@ RTTPicker::getOrCreatePickContext(osg::View* view)
     c._image = new osg::Image();
     c._image->allocateImage(_rttSize, _rttSize, 1, GL_RGBA, GL_UNSIGNED_BYTE);    
     
-    // make an RTT camera and bind it to our imag:
+    // Make an RTT camera and bind it to our image.
+    // Note: don't use RF_INHERIT_VIEWPOINT because it's unnecessary and
+    //       doesn't work with a slave camera anyway
+    // Note: NESTED_RENDER mode makes the RTT camera track the clip planes
+    //       etc. of the master camera; since the master renderes first,
+    //       the setup should always be in place for the slave
     c._pickCamera = new osg::Camera();
     c._pickCamera->setName( "osgEarth::RTTPicker" );
     c._pickCamera->addChild( _group.get() );
     c._pickCamera->setClearColor( osg::Vec4(0,0,0,0) );
     c._pickCamera->setClearMask( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
-    c._pickCamera->setReferenceFrame( osg::Camera::ABSOLUTE_RF_INHERIT_VIEWPOINT ); 
     c._pickCamera->setViewport( 0, 0, _rttSize, _rttSize );
-    c._pickCamera->setRenderOrder( osg::Camera::PRE_RENDER, 1 );
+    c._pickCamera->setRenderOrder( osg::Camera::NESTED_RENDER );
     c._pickCamera->setRenderTargetImplementation( osg::Camera::FRAME_BUFFER_OBJECT );
     c._pickCamera->attach( osg::Camera::COLOR_BUFFER0, c._image.get() );
     c._pickCamera->setSmallFeatureCullingPixelSize( -1.0f );
+    c._pickCamera->setCullMask( _cullMask );
+
+    // Necessary to connect the slave to the master (why is this not automatic?)
+    c._pickCamera->setGraphicsContext(view->getCamera()->getGraphicsContext());
     
     osg::StateSet* rttSS = c._pickCamera->getOrCreateStateSet();
 
     // disable all the things that break ObjectID picking:
-    osg::StateAttribute::GLModeValue disable = osg::StateAttribute::OFF | osg::StateAttribute::OVERRIDE | osg::StateAttribute::PROTECTED;
+    osg::StateAttribute::GLModeValue disable = 
+        osg::StateAttribute::OFF | osg::StateAttribute::OVERRIDE | osg::StateAttribute::PROTECTED;
 
-    //rttSS->setMode(GL_BLEND,     disable );    
     rttSS->setMode(GL_LIGHTING,  disable );
     rttSS->setMode(GL_CULL_FACE, disable );
     rttSS->setMode(GL_ALPHA_TEST, disable );
+
+#if !(defined(OSG_GLES2_AVAILABLE) || defined(OSG_GLES3_AVAILABLE) || defined(OSG_GL3_AVAILABLE) )
+    rttSS->setMode(GL_POINT_SMOOTH, disable );
+    rttSS->setMode(GL_LINE_SMOOTH, disable );
+#endif
     
     // Disabling GL_BLEND is not enough, because osg::Text re-enables it
     // without regard for the OVERRIDE.
-    rttSS->setAttributeAndModes(new osg::BlendFunc(GL_ONE, GL_ZERO, GL_ONE, GL_ZERO), osg::StateAttribute::OVERRIDE | osg::StateAttribute::PROTECTED);
+    rttSS->setAttributeAndModes(
+        new osg::BlendFunc(GL_ONE, GL_ZERO, GL_ONE, GL_ZERO),
+        osg::StateAttribute::OVERRIDE | osg::StateAttribute::PROTECTED);
 
     // install the picking shaders:
     VirtualProgram* vp = createRTTProgram();
     rttSS->setAttribute( vp );
 
-    // designate this as a pick camera, overriding any defaults below
-    rttSS->addUniform( new osg::Uniform("oe_isPickCamera", true), osg::StateAttribute::OVERRIDE );
+    // designate this as a pick camera
+    rttSS->setDefine("OE_IS_PICK_CAMERA");
+    rttSS->setDefine("OE_LIGHTING", osg::StateAttribute::OFF | osg::StateAttribute::OVERRIDE);
 
     // default value for the objectid override uniform:
     rttSS->addUniform( new osg::Uniform(Registry::objectIndex()->getObjectIDUniformName().c_str(), 0u) );
     
-    // install the pick camera on the main camera.
-    view->getCamera()->addChild( c._pickCamera.get() );
-
-    // associate the RTT camara with the view's camera.
-    // (e.g., decluttering uses this to find the "true" viewport)
-    c._pickCamera->setUserData( view->getCamera() );
+    // install the pick camera as a slave of the view's camera so it will
+    // duplicate the view matrix and projection matrix during the update traversal
+    // The "false" means the pick camera has its own separate subgraph.
+    view->addSlave(c._pickCamera.get(), false);
+    osg::View::Slave& slave = view->getSlave(view->getNumSlaves()-1);
+    slave._updateSlaveCallback = new MyUpdateSlave();
+
+    // Pick camera starts out deactivated.
+    c._numPicks = 0;
+    c._pickCamera->setNodeMask(0);
+
+    // Add a pre-draw callback that calls the view camera's pre-draw callback.  This
+    // is better than assigning the same pre-draw callback, because the callback can
+    // change over time (such as installing or uninstalling a Logarithmic Depth Buffer)
+    c._pickCamera->setPreDrawCallback( new CallHostCameraPreDrawCallback(view->getCamera()) );
 
     return c;
 }
@@ -218,22 +311,13 @@ RTTPicker::handle(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa)
         }
 
         // if there are picks in the queue, need to continuing rendering:
-        if ( !_picks.empty() )
+        if ( _picks.empty() == false )
         {
             aa.requestRedraw();
         }
-
-        // synchronize the pick camera associated with this view
-        osg::Camera* cam = aa.asView()->getCamera();
-        if (cam)
-        {
-            PickContext& context = getOrCreatePickContext( aa.asView() );
-            context._pickCamera->setViewMatrix( cam->getViewMatrix() );
-            context._pickCamera->setProjectionMatrix( cam->getProjectionMatrix() );
-        }
     }
 
-    else if ( _defaultCallback.valid() && _defaultCallback->accept(ea, aa) )
+    if ( _defaultCallback.valid() && _defaultCallback->accept(ea, aa) )
     {        
         pick( aa.asView(), ea.getX(), ea.getY(), _defaultCallback.get() );
         aa.requestRedraw();
@@ -284,9 +368,16 @@ RTTPicker::pick(osg::View* view, float mouseX, float mouseY, Callback* callback)
     pick._v        = v;
     pick._callback = callbackToUse;
     pick._frame    = view->getFrameStamp() ? view->getFrameStamp()->getFrameNumber() : 0u;
-    
+   
     // Queue it up.
-    _picks.push( pick );
+    _picks.push_back( pick );
+    
+    // Activate the pick camera if necessary:
+    pick._context->_numPicks++;
+    if (pick._context->_numPicks == 1)
+    {
+        pick._context->_pickCamera->setNodeMask(~0);
+    }
     
     return true;
 }
@@ -294,17 +385,34 @@ RTTPicker::pick(osg::View* view, float mouseX, float mouseY, Callback* callback)
 void
 RTTPicker::runPicks(unsigned frameNumber)
 {
-    while( _picks.size() > 0 )
+    if (_picks.size() > 0)
     {
-        Pick& pick = _picks.front();
-        if ( frameNumber > pick._frame )
+        for (std::vector<Pick>::iterator i = _picks.begin(); i != _picks.end(); )
         {
-            checkForPickResult(pick);
-            _picks.pop();
-        }
-        else
-        {
-            break;
+            bool pickExpired = false;
+            Pick& pick = *i;
+            if (frameNumber > pick._frame)
+            {
+                pickExpired = checkForPickResult(pick, frameNumber);
+                if (pickExpired)
+                {
+                    // Decrement the pick count for this pick's camera. If it reaches zero,
+                    // disable the camera.
+                    pick._context->_numPicks--;
+                    if (pick._context->_numPicks == 0)
+                    {
+                        pick._context->_pickCamera->setNodeMask(0);
+                    }
+
+                    // Remove the pick.
+                    i = _picks.erase(i);
+                }
+            }
+
+            if (pickExpired == false)
+            {
+                ++i;
+            }
         }
     }
 }
@@ -358,8 +466,8 @@ namespace
     };
 }
 
-void
-RTTPicker::checkForPickResult(Pick& pick)
+bool
+RTTPicker::checkForPickResult(Pick& pick, unsigned frameNumber)
 {
     // decode the results
     osg::Image* image = pick._context->_image.get();
@@ -369,9 +477,10 @@ RTTPicker::checkForPickResult(Pick& pick)
     //osg::ref_ptr<osgDB::Options> o = new osgDB::Options();
     //osgDB::writeImageFile(*image, "out.tif", o.get());
 
+    bool hit = false;
     osg::Vec4f value;
     SpiralIterator iter(image->s(), image->t(), std::max(_buffer,1), pick._u, pick._v);
-    while(iter.next())
+    while (iter.next() && (hit == false))
     {
         value = read(iter.s(), iter.t());
 
@@ -384,11 +493,25 @@ RTTPicker::checkForPickResult(Pick& pick)
         if ( id > 0 )
         {
             pick._callback->onHit( id );
-            return;
+            hit = true;
         }
     }
 
-    pick._callback->onMiss();
+    // A pick expires if (a) it registers a hit, or (b) is registers a miss
+    // for 2 frames in a row. Why 2? Because the RTT picker itself delays 
+    // pick results by one frame, and the osgEarth draping/clamping systems
+    // also delay drawing by one frame. So we need 2 frames to positively
+    // register a hit on draped/clamped geometry.
+    bool pickExpired =
+        hit == true ||
+        frameNumber - pick._frame >= 2u;
+
+    if ((hit == false) && (pickExpired == true))
+    {
+        pick._callback->onMiss();
+    }
+
+    return pickExpired;
 }
 
 bool
diff --git a/src/osgEarthUtil/RadialLineOfSight.cpp b/src/osgEarthUtil/RadialLineOfSight.cpp
index 11ad5c0..4bc2fb7 100644
--- a/src/osgEarthUtil/RadialLineOfSight.cpp
+++ b/src/osgEarthUtil/RadialLineOfSight.cpp
@@ -21,7 +21,7 @@
 */
 #include <osgEarthUtil/RadialLineOfSight>
 #include <osgEarth/TerrainEngineNode>
-#include <osgEarth/DPLineSegmentIntersector>
+#include <osgUtil/LineSegmentIntersector>
 #include <osgSim/LineOfSight>
 #include <osgUtil/IntersectionVisitor>
 #include <osgUtil/LineSegmentIntersector>
@@ -67,9 +67,9 @@ namespace
         {
         }
 
-        virtual void onTileAdded(const osgEarth::TileKey& tileKey, osg::Node* terrain, TerrainCallbackContext& )
+        virtual void onTileAdded(const osgEarth::TileKey& tileKey, osg::Node* graph, TerrainCallbackContext& )
         {
-            _los->terrainChanged( tileKey, terrain );
+            _los->terrainChanged( tileKey, graph );
         }
 
     private:
@@ -286,7 +286,7 @@ RadialLineOfSightNode::compute_line(osg::Node* node)
         osg::Quat quat(angle, up );
         osg::Vec3d spoke = quat * (side * _radius);
         osg::Vec3d end = _centerWorld + spoke;
-        osg::ref_ptr<DPLineSegmentIntersector> dplsi = new DPLineSegmentIntersector( _centerWorld, end );
+        osg::ref_ptr<osgUtil::LineSegmentIntersector> dplsi = new osgUtil::LineSegmentIntersector( _centerWorld, end );
         ivGroup->addIntersector( dplsi.get() );
     }
 
@@ -297,11 +297,11 @@ RadialLineOfSightNode::compute_line(osg::Node* node)
 
     for (unsigned int i = 0; i < (unsigned int)_numSpokes; i++)
     {
-        DPLineSegmentIntersector* los = dynamic_cast<DPLineSegmentIntersector*>(ivGroup->getIntersectors()[i].get());
+        osgUtil::LineSegmentIntersector* los = dynamic_cast<osgUtil::LineSegmentIntersector*>(ivGroup->getIntersectors()[i].get());
         if ( !los )
             continue;
 
-        DPLineSegmentIntersector::Intersections& hits = los->getIntersections();
+        osgUtil::LineSegmentIntersector::Intersections& hits = los->getIntersections();
 
         osg::Vec3d start = los->getStart();
         osg::Vec3d end = los->getEnd();
@@ -429,7 +429,7 @@ RadialLineOfSightNode::compute_fill(osg::Node* node)
         osg::Quat quat(angle, up );
         osg::Vec3d spoke = quat * (side * _radius);
         osg::Vec3d end = _centerWorld + spoke;        
-        osg::ref_ptr<DPLineSegmentIntersector> dplsi = new DPLineSegmentIntersector( _centerWorld, end );
+        osg::ref_ptr<osgUtil::LineSegmentIntersector> dplsi = new osgUtil::LineSegmentIntersector( _centerWorld, end );
         if (dplsi)
             ivGroup->addIntersector( dplsi.get() );
     }
@@ -442,11 +442,11 @@ RadialLineOfSightNode::compute_fill(osg::Node* node)
     for (unsigned int i = 0; i < (unsigned int)_numSpokes; i++)
     {
         //Get the current hit
-        DPLineSegmentIntersector* los = dynamic_cast<DPLineSegmentIntersector*>(ivGroup->getIntersectors()[i].get());
+        osgUtil::LineSegmentIntersector* los = dynamic_cast<osgUtil::LineSegmentIntersector*>(ivGroup->getIntersectors()[i].get());
         if ( !los )
             continue;
 
-        DPLineSegmentIntersector::Intersections& hits = los->getIntersections();
+        osgUtil::LineSegmentIntersector::Intersections& hits = los->getIntersections();
 
         osg::Vec3d currEnd = los->getEnd();
         bool currHasLOS = hits.empty();
@@ -455,8 +455,8 @@ RadialLineOfSightNode::compute_fill(osg::Node* node)
         //Get the next hit
         unsigned int nextIndex = i + 1;
         if (nextIndex == _numSpokes) nextIndex = 0;
-        DPLineSegmentIntersector* losNext = static_cast<DPLineSegmentIntersector*>(ivGroup->getIntersectors()[nextIndex].get());
-        DPLineSegmentIntersector::Intersections& hitsNext = losNext->getIntersections();
+        osgUtil::LineSegmentIntersector* losNext = static_cast<osgUtil::LineSegmentIntersector*>(ivGroup->getIntersectors()[nextIndex].get());
+        osgUtil::LineSegmentIntersector::Intersections& hitsNext = losNext->getIntersections();
 
         osg::Vec3d nextEnd = losNext->getEnd();
         bool nextHasLOS = hitsNext.empty();
@@ -677,7 +677,7 @@ RadialLineOfSightTether::operator()(osg::Node* node, osg::NodeVisitor* nv)
 
         if ( los->getMapNode() )
         {
-            osg::Vec3d worldCenter = getNodeCenter( _node );
+            osg::Vec3d worldCenter = getNodeCenter( _node.get() );
 
             //Convert center to mappoint since that is what LOS expects
             GeoPoint mapCenter;
@@ -747,7 +747,7 @@ _los(los)
 {
 
     _dragger  = new osgEarth::Annotation::SphereDragger(_los->getMapNode());
-    _dragger->addPositionChangedCallback(new RadialLOSDraggerCallback(_los ) );    
+    _dragger->addPositionChangedCallback(new RadialLOSDraggerCallback(_los.get() ) );    
     static_cast<osgEarth::Annotation::SphereDragger*>(_dragger)->setColor(osg::Vec4(0,0,1,0));
     addChild(_dragger);    
 
diff --git a/src/osgEarthUtil/Shaders b/src/osgEarthUtil/Shaders
index d41551d..d4a60a7 100644
--- a/src/osgEarthUtil/Shaders
+++ b/src/osgEarthUtil/Shaders
@@ -46,7 +46,10 @@ namespace osgEarth { namespace Util
             LogDepthBuffer_VertOnly_VertFile,
 
             Shadowing_Vertex,
-            Shadowing_Fragment;
+            Shadowing_Fragment,
+
+            SimpleOceanLayer_Vertex,
+            SimpleOceanLayer_Fragment;
 	};	
 } } // namespace osgEarth::Util
 
diff --git a/src/osgEarthUtil/Shaders.cpp.in b/src/osgEarthUtil/Shaders.cpp.in
index 9545d3f..4adf753 100644
--- a/src/osgEarthUtil/Shaders.cpp.in
+++ b/src/osgEarthUtil/Shaders.cpp.in
@@ -38,4 +38,10 @@ Shaders::Shaders()
 
     Shadowing_Fragment = "Shadowing.frag.glsl";
     _sources[Shadowing_Fragment] = "@Shadowing.frag.glsl@";
+
+    SimpleOceanLayer_Vertex = "SimpleOceanLayer.vert.glsl";
+    _sources[SimpleOceanLayer_Vertex] = "@SimpleOceanLayer.vert.glsl@";
+
+    SimpleOceanLayer_Fragment = "SimpleOceanLayer.frag.glsl";
+    _sources[SimpleOceanLayer_Fragment] = "@SimpleOceanLayer.frag.glsl@";
 }
diff --git a/src/osgEarthUtil/Shadowing.cpp b/src/osgEarthUtil/Shadowing.cpp
index bc3333c..a4cb697 100644
--- a/src/osgEarthUtil/Shadowing.cpp
+++ b/src/osgEarthUtil/Shadowing.cpp
@@ -150,8 +150,9 @@ ShadowCaster::reinitialize()
         new osg::CullFace(osg::CullFace::FRONT),
         osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE);
 
-    _rttStateSet->addUniform(new osg::Uniform("oe_isShadowCamera", true), osg::StateAttribute::OVERRIDE);
+    //_rttStateSet->addUniform(new osg::Uniform("oe_isShadowCamera", true), osg::StateAttribute::OVERRIDE);
 
+    _rttStateSet->setDefine("OE_IS_SHADOW_CAMERA");
 
     _renderStateSet = new osg::StateSet();
     
diff --git a/src/osgEarthUtil/Shadowing.frag.glsl b/src/osgEarthUtil/Shadowing.frag.glsl
index dc9e8c4..f5f307e 100644
--- a/src/osgEarthUtil/Shadowing.frag.glsl
+++ b/src/osgEarthUtil/Shadowing.frag.glsl
@@ -1,10 +1,13 @@
 #version $GLSL_VERSION_STR
+$GLSL_DEFAULT_PRECISION_FLOAT
 
 #pragma vp_name       Shadowing Fragment Shader
 #pragma vp_entryPoint oe_shadow_fragment
 #pragma vp_location   fragment_lighting
 #pragma vp_order      0.9
 
+#pragma import_defines(OE_LIGHTING, OE_NUM_LIGHTS)
+
 uniform sampler2DArray oe_shadow_map;
 uniform float          oe_shadow_color;
 uniform float          oe_shadow_blur;
@@ -12,6 +15,25 @@ uniform float          oe_shadow_blur;
 in vec3 vp_Normal; // stage global
 in vec4 oe_shadow_coord[$OE_SHADOW_NUM_SLICES];
 
+// Parameters of each light:
+struct osg_LightSourceParameters 
+{   
+   vec4 ambient;
+   vec4 diffuse;
+   vec4 specular;
+   vec4 position;
+   vec3 spotDirection;
+   float spotExponent;
+   float spotCutoff;
+   float spotCosCutoff;
+   float constantAttenuation;
+   float linearAttenuation;
+   float quadraticAttenuation;
+
+   bool enabled;
+};  
+uniform osg_LightSourceParameters osg_LightSource[OE_NUM_LIGHTS];
+
 
 #define OE_SHADOW_NUM_SAMPLES 16
 
@@ -60,7 +82,7 @@ void oe_shadow_fragment(inout vec4 color)
     // pre-pixel biasing to reduce moire/acne
     const float b0 = 0.001;
     const float b1 = 0.01;
-    vec3 L = normalize(gl_LightSource[0].position.xyz);
+    vec3 L = normalize(osg_LightSource[0].position.xyz);
     vec3 N = normalize(vp_Normal);
     float costheta = clamp(dot(L,N), 0.0, 1.0);
     float bias = b0*tan(acos(costheta));
diff --git a/src/osgEarthUtil/Shadowing.vert.glsl b/src/osgEarthUtil/Shadowing.vert.glsl
index 16e16bd..c0aa056 100644
--- a/src/osgEarthUtil/Shadowing.vert.glsl
+++ b/src/osgEarthUtil/Shadowing.vert.glsl
@@ -1,4 +1,5 @@
 #version $GLSL_VERSION_STR
+$GLSL_DEFAULT_PRECISION_FLOAT
 
 #pragma vp_name       Shadowing Vertex Shader
 #pragma vp_entryPoint oe_shadow_vertex
diff --git a/src/osgEarthUtil/SimpleOceanLayer b/src/osgEarthUtil/SimpleOceanLayer
new file mode 100644
index 0000000..cc0b5c6
--- /dev/null
+++ b/src/osgEarthUtil/SimpleOceanLayer
@@ -0,0 +1,146 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+ * Copyright 2016 Pelican Mapping
+ * http://osgearth.org
+ *
+ * osgEarth is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+#ifndef OSGEARTH_UTIL_SIMPLE_OCEAN_LAYER
+#define OSGEARTH_UTIL_SIMPLE_OCEAN_LAYER 1
+
+#include <osgEarthUtil/Common>
+#include <osgEarth/Layer>
+#include <osgEarth/LayerListener>
+#include <osgEarthSymbology/Color>
+
+namespace osgEarth {
+    class ImageLayer;
+}
+
+namespace osgEarth { namespace Util
+{
+    using namespace osgEarth::Symbology;
+
+    /**
+     * Serializable configuation options for the SimpleOceanLayer.
+     */
+    class OSGEARTHUTIL_EXPORT SimpleOceanLayerOptions : public VisibleLayerOptions
+    {
+    public:
+        /** Color of the ocean surface */
+        optional<Color>& color() { return _color; }
+        const optional<Color>& color() const { return _color; }
+
+        /** Maximum altitude at which the ocean is visible */
+        optional<float>& maxAltitude() { return _maxAltitude; }
+        const optional<float>& maxAltitude() const { return _maxAltitude; }
+
+        /** Name of a Map Layer to use as an ocean mask. */
+        optional<std::string>& maskLayer() { return _maskLayer; }
+        const optional<std::string>& maskLayer() const { return _maskLayer; }
+
+        /** Whether to sample the terrain bathymetry and only draw the ocean
+        where it's negative (default = true) */
+        optional<bool>& useBathymetry() { return _useBathymetry; }
+        const optional<bool>& useBathymetry() const { return _useBathymetry; }
+
+    public:
+        SimpleOceanLayerOptions(const ConfigOptions& op =ConfigOptions()) : VisibleLayerOptions(op) {
+            _color.init(Color("#1D2C4FE7"));
+            _maxAltitude.init(500000.f);
+            _useBathymetry.init(false);
+            mergeConfig(_conf);
+        }
+
+        void mergeConfig(const Config& conf) {
+            conf.getIfSet("color", _color);
+            conf.getIfSet("max_altitude", _maxAltitude);
+            conf.getIfSet("mask_layer", _maskLayer);
+            conf.getIfSet("use_bathymetry", _useBathymetry);
+        }
+
+        Config getConfig() const {
+            Config conf;
+            conf.addIfSet("color", _color);
+            conf.addIfSet("max_altitude", _maxAltitude);
+            conf.addIfSet("mask_layer", _maskLayer);
+            conf.addIfSet("use_bathymetry", _useBathymetry);
+            return conf;
+        }
+
+    private:
+        optional<Color> _color;
+        optional<float> _maxAltitude;
+        optional<std::string> _maskLayer;
+        optional<bool> _useBathymetry;
+    };
+
+
+    /**
+     * A Rex map layer that renders a simple ocean surface.
+     * This layer requires that the map include bathymetric data (ocean floor).
+     */
+    class OSGEARTHUTIL_EXPORT SimpleOceanLayer : public VisibleLayer
+    {
+    public:
+        META_Layer(osgEarth, SimpleOceanLayer, SimpleOceanLayerOptions);
+
+        /** Constructs a new ocean layer */
+        SimpleOceanLayer();
+
+        /** Constructs a new layer from an options structure. */
+        SimpleOceanLayer(const SimpleOceanLayerOptions& options);
+
+    public:
+
+        /** Ocean surface color (including transparency in the alpha channel) */
+        void setColor(const Color& color);
+        const Color& getColor() const;
+
+        /** Maximum altitude at which the ocean layer is visible */
+        void setMaxAltitude(float altitude_m);
+        float getMaxAltitude() const;
+
+        /** Sets a masking layer, or pass NULL to clear the masking layer. Returns true upon success */
+        void setMaskLayer(const ImageLayer* layer);
+
+    public: // Layer
+
+        /** serialize */
+        virtual Config getConfig() const;
+
+        /** callback that ensures proper culling */
+        void modifyTileBoundingBox(const TileKey& key, osg::BoundingBox& box) const;
+
+    protected: // Layer
+
+        virtual void addedToMap(const class Map*);
+
+        virtual void removedFromMap(const class Map*);
+
+        virtual void init();
+
+    protected:
+
+        virtual ~SimpleOceanLayer() { }
+
+    private:
+
+        LayerListener<SimpleOceanLayer, const ImageLayer> _layerListener;
+    };
+    
+
+} } // namespace osgEarth::Util
+
+#endif // OSGEARTH_UTIL_SIMPLE_OCEAN_LAYER
diff --git a/src/osgEarthUtil/SimpleOceanLayer.cpp b/src/osgEarthUtil/SimpleOceanLayer.cpp
new file mode 100644
index 0000000..60f7a51
--- /dev/null
+++ b/src/osgEarthUtil/SimpleOceanLayer.cpp
@@ -0,0 +1,202 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+* Copyright 2016 Pelican Mapping
+* http://osgearth.org
+*
+* osgEarth is free software; you can redistribute it and/or modify
+* it under the terms of the GNU Lesser General Public License as published by
+* the Free Software Foundation; either version 2 of the License, or
+* (at your option) any later version.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+* IN THE SOFTWARE.
+*
+* You should have received a copy of the GNU Lesser General Public License
+* along with this program.  If not, see <http://www.gnu.org/licenses/>
+*/
+#include <osgEarthUtil/SimpleOceanLayer>
+#include <osgEarthUtil/Shaders>
+#include <osgEarth/VirtualProgram>
+#include <osgEarth/Lighting>
+#include <osgEarth/ImageLayer>
+#include <osgEarth/Map>
+#include <osg/CullFace>
+#include <osg/Material>
+
+
+using namespace osgEarth;
+using namespace osgEarth::Util;
+
+#define LC "[SimpleOceanLayer] "
+
+
+/** Register this layer so it can be used in an earth file */
+REGISTER_OSGEARTH_LAYER(simple_ocean, SimpleOceanLayer);
+
+
+
+SimpleOceanLayer::SimpleOceanLayer() :
+VisibleLayer(&_optionsConcrete),
+_options(&_optionsConcrete)
+{
+    init();
+}
+
+SimpleOceanLayer::SimpleOceanLayer(const SimpleOceanLayerOptions& options) :
+VisibleLayer(&_optionsConcrete),
+_options(&_optionsConcrete),
+_optionsConcrete(options)
+{
+    init();
+}
+
+void
+SimpleOceanLayer::init()
+{
+    OE_INFO << LC << "Creating a Simple Ocean Layer\n";
+
+    VisibleLayer::init();
+
+    this->setName("Simple Ocean");
+    setRenderType(RENDERTYPE_TILE);
+
+    osg::StateSet* ss = getOrCreateStateSet();
+    ss->setDataVariance(ss->DYNAMIC);
+    
+    VirtualProgram* vp = VirtualProgram::getOrCreate(ss);
+    Shaders shaders;
+    shaders.load(vp, shaders.SimpleOceanLayer_Vertex);
+    shaders.load(vp, shaders.SimpleOceanLayer_Fragment);
+
+    ss->setDefine("OE_TERRAIN_RENDER_ELEVATION", osg::StateAttribute::OFF);
+    ss->setDefine("OE_TERRAIN_RENDER_NORMAL_MAP", osg::StateAttribute::OFF);
+
+    if (options().useBathymetry() == true)
+    {
+        ss->setDefine("OE_OCEAN_USE_BATHYMETRY");
+    }
+
+    // remove backface culling so we can see underwater
+    // (use OVERRIDE since the terrain engine sets back face culling.)
+    ss->setAttributeAndModes(
+        new osg::CullFace(),
+        osg::StateAttribute::OFF | osg::StateAttribute::OVERRIDE);
+
+    // Material.
+#if 0
+    osg::Material* m = new MaterialGL3();
+    m->setAmbient(m->FRONT, osg::Vec4(.5, .5, .5, 1));
+    m->setDiffuse(m->FRONT, osg::Vec4(1, 1, 1, 1));
+    m->setSpecular(m->FRONT, osg::Vec4(1, 1, 1, 1)); //0.2, 0.2, 0.2, 1));
+    m->setEmission(m->FRONT, osg::Vec4(0, 0, 0, 1));
+    m->setShininess(m->FRONT, 100.0);
+    ss->setAttributeAndModes(m, 1); //osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE);
+    m->setUpdateCallback(new MaterialCallback());
+#endif
+    
+    setColor(options().color().get());
+    setMaxAltitude(options().maxAltitude().get());
+}
+
+void
+SimpleOceanLayer::setMaskLayer(const ImageLayer* maskLayer)
+{
+    if (maskLayer)
+    {
+        if (!maskLayer->getEnabled())
+        {
+            OE_WARN << LC << "Mask layer \"" << maskLayer->getName() << "\" disabled\n";
+            return;
+        }
+
+        if (!maskLayer->isShared())
+        {
+            OE_WARN << LC << "Mask layer \"" << maskLayer->getName() << "\" is not a shared\n";
+            return;
+        }
+
+        // activate the mask.
+        osg::StateSet* ss = getOrCreateStateSet();
+        ss->setDefine("OE_OCEAN_MASK", maskLayer->shareTexUniformName().get());
+        ss->setDefine("OE_OCEAN_MASK_MATRIX", maskLayer->shareTexMatUniformName().get());
+
+        OE_INFO << LC << "Installed \"" << maskLayer->getName() << "\" as mask layer\n";
+    }
+
+    else
+    {
+        osg::StateSet* ss = getOrCreateStateSet();
+        ss->removeDefine("OE_OCEAN_MASK");
+        ss->removeDefine("OE_OCEAN_MASK_MATRIX");
+
+        OE_INFO << LC << "Uninstalled mask layer\n";
+    }
+}
+
+void
+SimpleOceanLayer::addedToMap(const Map* map)
+{    
+    if (options().maskLayer().isSet())
+    {
+        // listen for the mask layer.
+        _layerListener.listen(map, options().maskLayer().get(), this, &SimpleOceanLayer::setMaskLayer);
+    }      
+}
+
+void
+SimpleOceanLayer::removedFromMap(const Map* map)
+{
+    if (options().maskLayer().isSet())
+    {
+        _layerListener.clear();
+        setMaskLayer(0L);
+    }
+}
+
+void
+SimpleOceanLayer::setColor(const Color& color)
+{
+    options().color() = color;
+    getOrCreateStateSet()->getOrCreateUniform(
+        "ocean_color", osg::Uniform::FLOAT_VEC4)->set(color);
+}
+
+const Color&
+SimpleOceanLayer::getColor() const
+{
+    return options().color().get();
+}
+
+void
+SimpleOceanLayer::setMaxAltitude(float alt)
+{
+    options().maxAltitude() = alt;
+    getOrCreateStateSet()->getOrCreateUniform(
+        "ocean_maxAltitude", osg::Uniform::FLOAT)->set(alt);
+}
+
+float
+SimpleOceanLayer::getMaxAltitude() const
+{
+    return options().maxAltitude().get();
+}
+
+void 
+SimpleOceanLayer::modifyTileBoundingBox(const TileKey& key, osg::BoundingBox& box) const
+{
+    // Force the max Z to be at least sea level, to satisfy the culling pass
+    box.zMax() = std::max(box.zMax(), (osg::BoundingBox::value_type)0.0);
+}
+
+Config
+SimpleOceanLayer::getConfig() const
+{
+    Config conf = options().getConfig();
+    conf.key() = "simple_ocean";
+    return conf;
+}
diff --git a/src/osgEarthUtil/SimpleOceanLayer.frag.glsl b/src/osgEarthUtil/SimpleOceanLayer.frag.glsl
new file mode 100644
index 0000000..e26a36d
--- /dev/null
+++ b/src/osgEarthUtil/SimpleOceanLayer.frag.glsl
@@ -0,0 +1,46 @@
+#version $GLSL_VERSION_STR
+$GLSL_DEFAULT_PRECISION_FLOAT
+
+#pragma vp_entryPoint ocean_FS
+#pragma vp_location fragment_coloring
+#pragma import_defines(OE_OCEAN_MASK, OE_OCEAN_BATHYMETRY)
+
+float oe_terrain_getElevation();
+
+in float ocean_visibility; // [0..1] => [invisible..visible]
+
+uniform vec4 ocean_color;
+
+#ifdef OE_OCEAN_MASK
+in vec2 ocean_maskCoord;
+uniform sampler2D OE_OCEAN_MASK ;
+#endif
+
+// remaps a value from [vmin..vmax] to [0..1] clamped
+float ocean_remap(float val, float vmin, float vmax, float r0, float r1)
+{
+    float vr = (clamp(val, vmin, vmax)-vmin)/(vmax-vmin);
+    return r0 + vr * (r1-r0);
+}
+
+// entry point.
+void ocean_FS(inout vec4 color)
+{
+    float alpha = 1.0;
+
+#ifdef OE_OCEAN_BATHYMETRY
+    const float lowF = -100.0;
+    const float hiF = -10.0;
+    const float seaLevel = 0.0;
+
+    float elevation = oe_terrain_getElevation();
+    float alpha = ocean_remap(elevation, seaLevel+lowF, seaLevel+hiF, 1.0, 0.0);
+#endif
+
+#ifdef OE_OCEAN_MASK
+    float mask = texture(OE_OCEAN_MASK, ocean_maskCoord).a;
+    alpha *= mask;
+#endif
+
+    color = vec4(ocean_color.rgb, alpha*ocean_visibility*ocean_color.a);
+}
diff --git a/src/osgEarthUtil/SimpleOceanLayer.vert.glsl b/src/osgEarthUtil/SimpleOceanLayer.vert.glsl
new file mode 100644
index 0000000..8897a03
--- /dev/null
+++ b/src/osgEarthUtil/SimpleOceanLayer.vert.glsl
@@ -0,0 +1,33 @@
+#version $GLSL_VERSION_STR
+$GLSL_DEFAULT_PRECISION_FLOAT
+
+#pragma vp_entryPoint ocean_VS
+#pragma vp_location vertex_view
+
+#pragma import_defines(OE_OCEAN_MASK_MATRIX)
+
+uniform float ocean_maxAltitude;
+uniform mat4 osg_ViewMatrixInverse;
+
+out float ocean_visibility; // [0..1]
+
+#ifdef OE_OCEAN_MASK_MATRIX
+out vec2 ocean_maskCoord;
+vec4 oe_layer_tilec;
+uniform mat4 OE_OCEAN_MASK_MATRIX ;
+#endif
+
+void ocean_VS(inout vec4 vertexView)
+{
+    // calculate the visibility based on the max altitude:
+    vec3 eye = osg_ViewMatrixInverse[3].xyz;
+    float eyeAlt = max(length(eye) - 6371000.0, 0.0);
+    float lowAlt = ocean_maxAltitude;
+    float highAlt = lowAlt*1.5;
+    ocean_visibility = clamp((highAlt-eyeAlt) / (highAlt-lowAlt), 0.0, 1.0);
+
+    // if masking, calculate the mask coordinates
+#ifdef OE_OCEAN_MASK_MATRIX
+    ocean_maskCoord = (OE_OCEAN_MASK_MATRIX * oe_layer_tilec).st;
+#endif
+}
diff --git a/src/osgEarthUtil/SimplePager b/src/osgEarthUtil/SimplePager
index d93ae5c..36c86e1 100644
--- a/src/osgEarthUtil/SimplePager
+++ b/src/osgEarthUtil/SimplePager
@@ -64,6 +64,9 @@ namespace osgEarth { namespace Util {
         void setFileLocationCallback(osgDB::FileLocationCallback* cb) { _fileLocationCallback = cb; }
         osgDB::FileLocationCallback* getFileLocationCallback() const  { return _fileLocationCallback.get(); }
 
+        void setEnableCancelation(bool value);
+        bool getEnableCancalation() const;
+
         /**
          * Gets the profile for the SimplePager.
          */
@@ -131,6 +134,7 @@ namespace osgEarth { namespace Util {
         osg::ref_ptr< osgDB::FileLocationCallback > _fileLocationCallback;
         float _priorityScale;
         float _priorityOffset;
+        bool _canCancel;
         
         mutable Threading::Mutex _mutex;
         typedef std::vector< osg::ref_ptr<Callback> > Callbacks;
diff --git a/src/osgEarthUtil/SimplePager.cpp b/src/osgEarthUtil/SimplePager.cpp
index 2bae6a9..9a18e40 100644
--- a/src/osgEarthUtil/SimplePager.cpp
+++ b/src/osgEarthUtil/SimplePager.cpp
@@ -1,5 +1,6 @@
 #include <osgEarthUtil/SimplePager> 
 #include <osgEarth/TileKey>
+#include <osgEarth/Utils>
 #include <osgDB/Registry>
 #include <osgDB/FileNameUtils>
 #include <osgDB/Options>
@@ -21,6 +22,7 @@ namespace
     struct ProgressMaster : public osg::NodeCallback
     {
         unsigned _frame;
+        bool _canCancel;
 
         void operator()(osg::Node* node, osg::NodeVisitor* nv)
         {
@@ -45,7 +47,10 @@ namespace
         // override from ProgressCallback
         bool isCanceled()
         {
-            return (!_master.valid()) || (_master->_frame - _lastFrame > 1u);
+            osg::ref_ptr<ProgressMaster> master;
+            if (!_master.lock(master)) return true;
+            if (!_master->_canCancel) return false;
+            return (master->_frame - _lastFrame > 1u);
         }
 
         // called by ProgressUpdater
@@ -83,25 +88,23 @@ namespace
             unsigned lod, x, y;
             sscanf( uri.c_str(), "%d_%d_%d.%*s", &lod, &x, &y );
 
+            osg::ref_ptr<SimplePager> pager;
+            if (!OptionsData<SimplePager>::lock(options, "osgEarth.SimplePager", pager))
+            {
+                OE_WARN << LC << "Internal error - no SimplePager object in OptionsData\n";
+                return ReadResult::ERROR_IN_READING_FILE;
+            }
 
-            SimplePager* pager =
-                dynamic_cast<SimplePager*>(
-                    const_cast<osg::Object*>(
-                        options->getUserDataContainer()->getUserObject("osgEarth::Util::SimplerPager::this")));
-            
-            if (pager)
+            osg::ref_ptr<SimplePager::ProgressTracker> tracker;
+            if (!OptionsData<SimplePager::ProgressTracker>::lock(options, "osgEarth.SimplePager.ProgressTracker", tracker))
             {
-                SimplePager::ProgressTracker* tracker =
-                    dynamic_cast<SimplePager::ProgressTracker*>(
-                        const_cast<osg::Object*>(
-                            options->getUserDataContainer()->getUserObject("osgEarth::Util::SimplerPager::ProgressTracker")));
-
-                return pager->loadKey(
-                    TileKey(lod, x, y, pager->getProfile()),
-                    tracker);
+                OE_WARN << LC << "Internal error - no ProgressTracker object in OptionsData\n";
+                return ReadResult::ERROR_IN_READING_FILE;
             }
 
-            return ReadResult::ERROR_IN_READING_FILE;
+            return pager->loadKey(
+                TileKey(lod, x, y, pager->getProfile()),
+                tracker.get());
         }
     };
 
@@ -131,7 +134,8 @@ _additive(false),
 _minLevel(0),
 _maxLevel(30),
 _priorityScale(1.0f),
-_priorityOffset(0.0f)
+_priorityOffset(0.0f),
+_canCancel(true)
 {
     // required in order to pass our "this" pointer to the pseudo loader:
     this->setName( "osgEarth::Util::SimplerPager::this" );
@@ -141,6 +145,16 @@ _priorityOffset(0.0f)
     addCullCallback( _progressMaster.get() );
 }
 
+void SimplePager::setEnableCancelation(bool value)
+{
+    static_cast<ProgressMaster*>(_progressMaster.get())->_canCancel = value;
+}
+
+bool SimplePager::getEnableCancalation() const
+{
+    return static_cast<ProgressMaster*>(_progressMaster.get())->_canCancel;
+}
+
 void SimplePager::build()
 {
     addChild( buildRootNode() );
@@ -266,8 +280,8 @@ osg::Node* SimplePager::createPagedNode(const TileKey& key, ProgressCallback* pr
 
         // assemble data to pass to the pseudoloader
         osgDB::Options* options = new osgDB::Options();
-        options->getOrCreateUserDataContainer()->addUserObject( this );
-        options->getOrCreateUserDataContainer()->addUserObject( tracker );
+        OptionsData<SimplePager>::set(options, "osgEarth.SimplePager", this);
+        OptionsData<ProgressTracker>::set(options, "osgEarth.SimplePager.ProgressTracker", tracker);
         plod->setDatabaseOptions( options );
         
         // Install an FLC if the caller provided one
diff --git a/src/osgEarthUtil/Sky b/src/osgEarthUtil/Sky
index b2c7b71..a0ae949 100644
--- a/src/osgEarthUtil/Sky
+++ b/src/osgEarthUtil/Sky
@@ -35,13 +35,12 @@
 
 namespace osgEarth {
     class MapNode;
-    class UpdateLightingUniformsHelper;
 }
 namespace osgDB {
     class Options;
 }
 
-namespace osgEarth { namespace Util 
+namespace osgEarth { namespace Util
 {
     using namespace osgEarth;
 
@@ -79,20 +78,20 @@ namespace osgEarth { namespace Util
             DriverConfigOptions::mergeConfig( conf );
             fromConfig( conf );
         }
-        
+
     private:
         void fromConfig( const Config& conf ) {
             conf.getIfSet("hours", _hours);
             conf.getIfSet("ambient", _ambient);
         }
-        
+
         optional<float> _hours;
         optional<float> _ambient;
     };
 
 
     /**
-    * Interface for classes that provide sky, lighting, and other 
+    * Interface for classes that provide sky, lighting, and other
     * environmental effect.
     */
     class OSGEARTHUTIL_EXPORT SkyNode : public osg::Group
@@ -104,14 +103,14 @@ namespace osgEarth { namespace Util
          */
         static SkyNode* create(
             osgEarth::MapNode* mapNode );
-        
+
         /**
          * Creates a new SkyNode with custom options.
          */
         static SkyNode* create(
             const SkyOptions&  options,
             osgEarth::MapNode* mapNode );
-        
+
         /**
          * Creates a new SkyNode with a named driver.
          */
@@ -141,7 +140,7 @@ namespace osgEarth { namespace Util
          */
         void setReferencePoint(const GeoPoint& point);
         const GeoPoint& getReferencePoint() const { return *_refpoint; }
-       
+
         /**
          * Whether the sky lights its subgraph.
          */
@@ -173,22 +172,20 @@ namespace osgEarth { namespace Util
         bool getAtmosphereVisible() const { return _atmosphereVisible; }
 
         /** Access the osg::Light representing the sun */
-        virtual osg::Light* getSunLight() = 0;
+        virtual osg::Light* getSunLight() const = 0;
+        //virtual const osg::Light* getSunLight() const = 0;
 
-        /** Sets a minimum ambient lighting value. */
-        virtual void setMinimumAmbient(const osg::Vec4f& ambient);
-        const osg::Vec4& getMinimumAmbient() const { return _minimumAmbient; }
+        /** @deprecated. use getSunLight()->setAmbient instead */
+        void setMinimumAmbient(const osg::Vec4f& ambient) { if (getSunLight()) getSunLight()->setAmbient(ambient); }
+        /** @deprecated; use getSunLight()->getAmbient instead */
+        const osg::Vec4 getMinimumAmbient() const { if (!getSunLight()) return osg::Vec4(0,0,0,0); return getSunLight()->getAmbient(); }
 
     public:
-        
+
         /** Attaches this sky node to a view (placing a sky light). Optional */
         virtual void attach(osg::View* view, int lightNum) { }
         void attach(osg::View* view) { attach(view, 0); }
 
-    public: // osg::Node
-
-        virtual void traverse(osg::NodeVisitor&);
-
     protected:
 
         // impl class can override these events.
@@ -199,7 +196,6 @@ namespace osgEarth { namespace Util
         virtual void onSetStarsVisible() { }
         virtual void onSetSunVisible() { }
         virtual void onSetAtmosphereVisible() { }
-        virtual void onSetMinimumAmbient() { }
 
     private:
 
@@ -215,8 +211,6 @@ namespace osgEarth { namespace Util
         osg::StateAttribute::OverrideValue _lightingValue;
         osg::ref_ptr<osg::Uniform>         _lightingUniform;
 
-        osg::ref_ptr<UpdateLightingUniformsHelper> _lightingUniformsHelper;
-
         void baseInit(const SkyOptions&);
     };
 
diff --git a/src/osgEarthUtil/Sky.cpp b/src/osgEarthUtil/Sky.cpp
index 3721f18..88d9a2c 100644
--- a/src/osgEarthUtil/Sky.cpp
+++ b/src/osgEarthUtil/Sky.cpp
@@ -26,6 +26,7 @@
 #include <osgEarth/ShaderUtils>
 #include <osgEarth/Extension>
 #include <osgEarth/MapNode>
+#include <osgEarth/Lighting>
 #include <osgDB/ReadFile>
 
 using namespace osgEarth;
@@ -58,9 +59,7 @@ SkyNode::baseInit(const SkyOptions& options)
     _moonVisible = true;
     _starsVisible = true;
     _atmosphereVisible = true;
-    _minimumAmbient.set(0.0f, 0.0f, 0.0f, 0.0f);
-
-    _lightingUniformsHelper = new UpdateLightingUniformsHelper();
+    //_minimumAmbient.set(0.0f, 0.0f, 0.0f, 0.0f);
 
     setLighting( osg::StateAttribute::ON );
 
@@ -70,6 +69,8 @@ SkyNode::baseInit(const SkyOptions& options)
         _dateTime = DateTime(_dateTime.year(), _dateTime.month(), _dateTime.day(), (double)hours);
         // (don't call setDateTime since we are called from the CTOR)
     }
+
+    this->getOrCreateStateSet()->setDefine("OE_NUM_LIGHTS", "1");
 }
 
 void
@@ -105,10 +106,14 @@ void
 SkyNode::setLighting(osg::StateAttribute::OverrideValue value)
 {
     _lightingValue = value;
-    _lightingUniform = Registry::shaderFactory()->createUniformForGLMode(
-        GL_LIGHTING, value );
+    //_lightingUniform = Registry::shaderFactory()->createUniformForGLMode(
+    //    GL_LIGHTING, value );
+    //this->getOrCreateStateSet()->addUniform( _lightingUniform.get(), value );
 
-    this->getOrCreateStateSet()->addUniform( _lightingUniform.get(), value );
+    if (value & osg::StateAttribute::INHERIT)
+        this->getOrCreateStateSet()->removeDefine(OE_LIGHTING_DEFINE);
+    else
+        this->getOrCreateStateSet()->setDefine(OE_LIGHTING_DEFINE, value);
 }
 
 void
@@ -139,27 +144,6 @@ SkyNode::setAtmosphereVisible(bool value)
     onSetAtmosphereVisible();
 }
 
-void
-SkyNode::setMinimumAmbient(const osg::Vec4f& value)
-{
-    _minimumAmbient = value;
-    onSetMinimumAmbient();
-}
-
-void
-SkyNode::traverse(osg::NodeVisitor& nv)
-{
-    if ( nv.getVisitorType() == nv.CULL_VISITOR )
-    {
-        // update the light model uniforms.
-        if ( _lightingUniformsHelper.valid() )
-        {
-            _lightingUniformsHelper->cullTraverse( this, &nv );
-        }
-    }
-    osg::Group::traverse(nv);
-}
-
 //------------------------------------------------------------------------
 
 #define MAPNODE_TAG     "__osgEarth::MapNode"
diff --git a/src/osgEarthUtil/SpatialData b/src/osgEarthUtil/SpatialData
index ef36921..fb38046 100644
--- a/src/osgEarthUtil/SpatialData
+++ b/src/osgEarthUtil/SpatialData
@@ -97,7 +97,6 @@ namespace osgEarth { namespace Util
         void split();
         void merge();
         void adjustCount( int delta );
-        bool intersects( const class osg::Polytope& tope ) const;
         void generateBoundaries();
         void generateBoundaryGeometry();
 
diff --git a/src/osgEarthUtil/SpatialData.cpp b/src/osgEarthUtil/SpatialData.cpp
index 5ebb8d2..640fab1 100644
--- a/src/osgEarthUtil/SpatialData.cpp
+++ b/src/osgEarthUtil/SpatialData.cpp
@@ -45,7 +45,7 @@ namespace
 
         return row*xdim + col;
     }
-    
+
 
     osg::Geode* makeClusterGeode( const GeoExtent& cellExtent, unsigned num )
     {
@@ -53,11 +53,11 @@ namespace
 
         double clat, clon;
         cellExtent.getCentroid( clon, clat );
-        osg::Vec3d xyz;        
+        osg::Vec3d xyz;
         cellExtent.getSRS()->getEllipsoid()->convertLatLongHeightToXYZ(
             osg::DegreesToRadians( clat ), osg::DegreesToRadians( clon ), 0, xyz.x(), xyz.y(), xyz.z() );
         t->setPosition( xyz );
-        
+
         std::stringstream buf;
         buf << num;
         std::string str;
@@ -66,7 +66,7 @@ namespace
         t->setCharacterSizeMode( osgText::TextBase::SCREEN_COORDS );
         t->setCharacterSize( 22.0f );
         t->setAutoRotateToScreen( true );
-        
+
         t->setFont( osgEarth::Registry::instance()->getDefaultFont() );
 
         t->setBackdropType( osgText::Text::OUTLINE );
@@ -144,7 +144,7 @@ GeoCell( extent, maxRange, maxObjects, splitDim, splitRangeFactor, 0 )
 
                 this->addChild( child, 0, maxRange ); //FLT_MAX );
             }
-        }                    
+        }
     }
 }
 
@@ -222,8 +222,8 @@ GeoCell::generateBoundaries()
             corner[i].x(), corner[i].y(), corner[i].z() );
         cornerVec[i] = corner[i];
         cornerVec[i].normalize();
-    }   
-    
+    }
+
     // now extrude the center and corners up and down to get the boundary points:
     unsigned p = 0;
     _boundaryPoints[p++] = center + centerVec*hae;
@@ -256,12 +256,13 @@ GeoCell::generateBoundaryGeometry()
     g->setVertexArray( v );
 
     osg::DrawElementsUByte* el = new osg::DrawElementsUByte( GL_QUADS );
+    // Faces are inward
     el->push_back( 7 ); el->push_back( 5 ); el->push_back( 4 ); el->push_back( 6 );
     el->push_back( 9 ); el->push_back( 7 ); el->push_back( 6 ); el->push_back( 8 );
-    el->push_back( 3 ); el->push_back( 9 ); el->push_back( 8 ); el->push_back( 2 );
-    el->push_back( 5 ); el->push_back( 3 ); el->push_back( 2 ); el->push_back( 4 );
-    //el->push_back( 2 ); el->push_back( 8 ); el->push_back( 6 ); el->push_back( 4 ); //top
-    //el->push_back( 9 ); el->push_back( 3 ); el->push_back( 5 ); el->push_back( 7 ); // bottom
+    el->push_back( 3 ); el->push_back( 2 ); el->push_back( 8 ); el->push_back( 9 );
+    el->push_back( 5 ); el->push_back( 4 ); el->push_back( 2 ); el->push_back( 3 );
+    //el->push_back( 2 ); el->push_back( 4 ); el->push_back( 6 ); el->push_back( 8 ); //top
+    //el->push_back( 9 ); el->push_back( 7 ); el->push_back( 5 ); el->push_back( 3 ); // bottom
     g->addPrimitiveSet( el );
 
     osg::Vec4Array* c = new osg::Vec4Array(1);
@@ -277,6 +278,7 @@ GeoCell::generateBoundaryGeometry()
 
     osg::StateSet* set = g->getOrCreateStateSet();
     set->setMode( GL_BLEND, 1 );
+    set->setMode( GL_CULL_FACE, 1 );
     set->setRenderingHint( osg::StateSet::TRANSPARENT_BIN );
     set->setAttribute( new osg::PolygonOffset(1,1), 1 );
 
@@ -284,18 +286,6 @@ GeoCell::generateBoundaryGeometry()
     _boundaryGeode->addDrawable( g );
 }
 
-bool
-GeoCell::intersects( const osg::Polytope& tope ) const
-{
-    const osg::Polytope::PlaneList& planes = tope.getPlaneList();
-    for( osg::Polytope::PlaneList::const_iterator i = planes.begin(); i != planes.end(); ++i )
-    {
-        if ( i->intersect( _boundaryPoints ) < 0 )
-            return false;
-    }
-    return true;
-}
-
 void
 GeoCell::traverse( osg::NodeVisitor& nv )
 {
@@ -306,7 +296,7 @@ GeoCell::traverse( osg::NodeVisitor& nv )
         if ( isCull )
         {
             // process boundary geometry, if present.
-            if ( _boundaryGeode.valid() ) 
+            if ( _boundaryGeode.valid() )
             {
                 if ( _count > 0 )
                     (*_boundaryColor)[0].set(1,0,0,0.35);
@@ -317,16 +307,9 @@ GeoCell::traverse( osg::NodeVisitor& nv )
                 _boundaryGeode->accept( nv );
             }
 
-            // custom BSP culling function. this checks that the set of boundary points
-            // for this cell intersects the viewing frustum.
             osgUtil::CullVisitor* cv = Culling::asCullVisitor(nv);
             if ( cv )
             {
-                if (!intersects( cv->getCurrentCullingSet().getFrustum() ) )
-                {
-                    return;
-                }
-
                 // passed cull, so record the framestamp.
                 _frameStamp = cv->getFrameStamp()->getFrameNumber();
             }
@@ -379,7 +362,7 @@ GeoCell::insertObject( GeoObject* object )
         {
             GeoObjectCollection::iterator low = _objects.begin();
             GeoObject* lowPriObject = low->second.get();
-            
+
             if ( getNumChildren() == 0 )
                 split();
 
@@ -427,8 +410,10 @@ GeoCell::split()
                 _extent.xMin() + xInterval*(double)(col+1),
                 _extent.yMin() + yInterval*(double)(row+1) );
 
+            GeoCell* cell = new GeoCell(cellExtent, newRange, _maxObjects, _splitDim, _splitRangeFactor, _depth+1);
+            cell->addCullCallback(new osgEarth::HorizonCullCallback);
             this->addChild(
-                new GeoCell(cellExtent, newRange, _maxObjects, _splitDim, _splitRangeFactor, _depth+1),
+                cell,
                 0.0f,
                 newRange );
         }
diff --git a/src/osgEarthUtil/TFS b/src/osgEarthUtil/TFS
index d88f0e8..6f740b5 100644
--- a/src/osgEarthUtil/TFS
+++ b/src/osgEarthUtil/TFS
@@ -61,7 +61,7 @@ namespace osgEarth { namespace Util
         unsigned int getFirstLevel() const { return _firstLevel;}
         void setFirstLevel(unsigned int value) { _firstLevel = value;}
 
-        const SpatialReference* getSRS() const { return _srs;}
+        const SpatialReference* getSRS() const { return _srs.get();}
         void setSRS( const SpatialReference* srs ) { _srs = srs;}
 
     private:
diff --git a/src/osgEarthUtil/TFSPackager.cpp b/src/osgEarthUtil/TFSPackager.cpp
index 919284e..c8a055b 100644
--- a/src/osgEarthUtil/TFSPackager.cpp
+++ b/src/osgEarthUtil/TFSPackager.cpp
@@ -22,9 +22,13 @@
 #include <osgEarthUtil/TFSPackager>
 
 #include <osgEarth/Registry>
+#include <osgEarth/FileUtils>
+
+#include <osgEarthFeatures/FilterContext>
+#include <osgEarthFeatures/FeatureCursor>
+
 #include <osgDB/FileNameUtils>
 #include <osgDB/FileUtils>
-#include <osgEarth/FileUtils>
 
 #define LC "[TFSPackager] "
 
@@ -236,9 +240,9 @@ public:
                   if (f)
                   {
                       //Reproject the feature to the dest SRS if it's not already
-                      if (!f->getSRS()->isEquivalentTo( _srs ) )
+                      if (!f->getSRS()->isEquivalentTo( _srs.get() ) )
                       {
-                          f->transform( _srs );
+                          f->transform( _srs.get() );
                       }
                       features.push_back( f );
                   }
@@ -323,7 +327,7 @@ void
     osg::ref_ptr< const osgEarth::Profile > profile = osgEarth::Profile::create(extent.getSRS(), extent.xMin(), extent.yMin(), extent.xMax(), extent.yMax(), 1, 1);
 
 
-    TileKey rootKey = TileKey(0, 0, 0, profile );    
+    TileKey rootKey = TileKey(0, 0, 0, profile.get() );    
 
 
     osg::ref_ptr< FeatureTile > root = new FeatureTile( rootKey );
@@ -339,9 +343,9 @@ void
         osg::ref_ptr< Feature > feature = cursor->nextFeature();
 
         //Reproject the feature to the dest SRS if it's not already
-        if (!feature->getSRS()->isEquivalentTo( _srs ) )
+        if (!feature->getSRS()->isEquivalentTo( _srs.get() ) )
         {
-            feature->transform( _srs );
+            feature->transform( _srs.get() );
         }
 
         if (feature->getGeometry() && feature->getGeometry()->getBounds().valid() && feature->getGeometry()->isValid())
@@ -376,13 +380,13 @@ void
     // Print the width of tiles at each level
     for (int i = 0; i <= highestLevel; ++i)
     {
-        TileKey tileKey(i, 0, 0, profile);
+        TileKey tileKey(i, 0, 0, profile.get());
         GeoExtent tileExtent = tileKey.getExtent();
         OE_NOTICE << "Level " << i << " tile size: " << tileExtent.width() << std::endl;
     }
 #endif
 
-    WriteFeaturesVisitor write(features, destination, _method, _srs);
+    WriteFeaturesVisitor write(features, destination, _method, _srs.get());
     root->accept( &write );
 
     //Write out the meta doc
diff --git a/src/osgEarthUtil/TMS.cpp b/src/osgEarthUtil/TMS.cpp
index aabbf91..355e675 100644
--- a/src/osgEarthUtil/TMS.cpp
+++ b/src/osgEarthUtil/TMS.cpp
@@ -454,14 +454,15 @@ TileMap::create(const std::string& url,
 
 
 TileMap* 
-TileMapReaderWriter::read( const std::string& location, const osgDB::ReaderWriter::Options* options )
+TileMapReaderWriter::read( const std::string& location, const osgDB::Options* options )
 {
     TileMap* tileMap = NULL;
 
-    ReadResult r = URI(location).readString();
+    ReadResult r = URI(location).readString(options);
     if ( r.failed() )
     {
-        OE_DEBUG << LC << "Failed to read TMS tile map file from " << location << std::endl;
+        OE_DEBUG << LC << "Failed to read TMS tile map file from " << location
+            << " ... " << r.errorDetail() << std::endl;
         return 0L;
     }
     
@@ -604,6 +605,8 @@ TileMapReaderWriter::read( const Config& conf )
     return tileMap;
 }
 
+#define DOUBLE_PRECISION 25
+
 static XmlDocument*
 tileMapToXmlDocument(const TileMap* tileMap)
 {
@@ -621,15 +624,15 @@ tileMapToXmlDocument(const TileMap* tileMap)
     osg::ref_ptr<XmlElement> e_bounding_box = new XmlElement( ELEM_BOUNDINGBOX );
     double minX, minY, maxX, maxY;
     tileMap->getExtents( minX, minY, maxX, maxY );
-    e_bounding_box->getAttrs()[ATTR_MINX] = toString(minX);
-    e_bounding_box->getAttrs()[ATTR_MINY] = toString(minY);
-    e_bounding_box->getAttrs()[ATTR_MAXX] = toString(maxX);
-    e_bounding_box->getAttrs()[ATTR_MAXY] = toString(maxY);
+    e_bounding_box->getAttrs()[ATTR_MINX] = toString(minX, DOUBLE_PRECISION);
+    e_bounding_box->getAttrs()[ATTR_MINY] = toString(minY, DOUBLE_PRECISION);
+    e_bounding_box->getAttrs()[ATTR_MAXX] = toString(maxX, DOUBLE_PRECISION);
+    e_bounding_box->getAttrs()[ATTR_MAXY] = toString(maxY, DOUBLE_PRECISION);
     doc->getChildren().push_back(e_bounding_box.get() );
 
     osg::ref_ptr<XmlElement> e_origin = new XmlElement( ELEM_ORIGIN );
-    e_origin->getAttrs()[ATTR_X] = toString(tileMap->getOriginX());
-    e_origin->getAttrs()[ATTR_Y] = toString(tileMap->getOriginY());
+    e_origin->getAttrs()[ATTR_X] = toString(tileMap->getOriginX(), DOUBLE_PRECISION);
+    e_origin->getAttrs()[ATTR_Y] = toString(tileMap->getOriginY(), DOUBLE_PRECISION);
     doc->getChildren().push_back(e_origin.get());
 
     osg::ref_ptr<XmlElement> e_tile_format = new XmlElement( ELEM_TILE_FORMAT );
@@ -663,7 +666,7 @@ tileMapToXmlDocument(const TileMap* tileMap)
         osg::ref_ptr<XmlElement> e_tile_set = new XmlElement( ELEM_TILESET );
         e_tile_set->getAttrs()[ATTR_HREF] = itr->getHref();
         e_tile_set->getAttrs()[ATTR_ORDER] = toString<unsigned int>(itr->getOrder());
-        e_tile_set->getAttrs()[ATTR_UNITSPERPIXEL] = toString(itr->getUnitsPerPixel());
+        e_tile_set->getAttrs()[ATTR_UNITSPERPIXEL] = toString(itr->getUnitsPerPixel(), DOUBLE_PRECISION);
         e_tile_sets->getChildren().push_back( e_tile_set.get() );
     }
     doc->getChildren().push_back(e_tile_sets.get());
@@ -675,10 +678,10 @@ tileMapToXmlDocument(const TileMap* tileMap)
         for (DataExtentList::const_iterator itr = tileMap->getDataExtents().begin(); itr != tileMap->getDataExtents().end(); ++itr)
         {
             osg::ref_ptr<XmlElement> e_data_extent = new XmlElement( ELEM_DATA_EXTENT );
-            e_data_extent->getAttrs()[ATTR_MINX] = toString(itr->xMin());
-            e_data_extent->getAttrs()[ATTR_MINY] = toString(itr->yMin());
-            e_data_extent->getAttrs()[ATTR_MAXX] = toString(itr->xMax());
-            e_data_extent->getAttrs()[ATTR_MAXY] = toString(itr->yMax());
+            e_data_extent->getAttrs()[ATTR_MINX] = toString(itr->xMin(), DOUBLE_PRECISION);
+            e_data_extent->getAttrs()[ATTR_MINY] = toString(itr->yMin(), DOUBLE_PRECISION);
+            e_data_extent->getAttrs()[ATTR_MAXX] = toString(itr->xMax(), DOUBLE_PRECISION);
+            e_data_extent->getAttrs()[ATTR_MAXY] = toString(itr->yMax(), DOUBLE_PRECISION);
             if ( itr->minLevel().isSet() )
                 e_data_extent->getAttrs()[ATTR_MIN_LEVEL] = toString<unsigned int>(*itr->minLevel());
             if ( itr->maxLevel().isSet() )
diff --git a/src/osgEarthUtil/TMSBackFiller.cpp b/src/osgEarthUtil/TMSBackFiller.cpp
index b22df3e..b7fdeb2 100644
--- a/src/osgEarthUtil/TMSBackFiller.cpp
+++ b/src/osgEarthUtil/TMSBackFiller.cpp
@@ -134,8 +134,9 @@ std::string TMSBackFiller::getFilename( const TileKey& key )
 
 osg::Image* TMSBackFiller::readTile( const TileKey& key )
 {
-    std::string filename = getFilename( key );        
-    return osgDB::readImageFile( filename );        
+    std::string filename = getFilename( key );
+    osg::ref_ptr< osg::Image> image = osgDB::readRefImageFile( filename );
+    return image.release();
 }
 
 void TMSBackFiller::writeTile( const TileKey& key, osg::Image* image )
diff --git a/src/osgEarthUtil/TMSPackager.cpp b/src/osgEarthUtil/TMSPackager.cpp
index 7a525fb..fd8029f 100644
--- a/src/osgEarthUtil/TMSPackager.cpp
+++ b/src/osgEarthUtil/TMSPackager.cpp
@@ -23,6 +23,8 @@
 #include <osgEarth/TaskService>
 #include <osgEarth/FileUtils>
 #include <osgEarth/CacheEstimator>
+#include <osgEarth/ImageLayer>
+#include <osgEarth/ElevationLayer>
 #include <osgDB/FileUtils>
 #include <osgDB/FileNameUtils>
 #include <osgDB/WriteFile>
@@ -42,22 +44,22 @@ WriteTMSTileHandler::WriteTMSTileHandler(TerrainLayer* layer,  Map* map, TMSPack
 
 std::string WriteTMSTileHandler::getPathForTile( const TileKey &key )
 {
-    std::string layerFolder = toLegalFileName( _packager->getLayerName() );         
+    std::string layerFolder = toLegalFileName( _packager->getLayerName() );
     unsigned w, h;
-    key.getProfile()->getNumTiles( key.getLevelOfDetail(), w, h );         
+    key.getProfile()->getNumTiles( key.getLevelOfDetail(), w, h );
 
-    return Stringify() 
+    return Stringify()
         << _packager->getDestination()
         << "/" << layerFolder
-        << "/" << key.getLevelOfDetail() 
-        << "/" << key.getTileX() 
+        << "/" << key.getLevelOfDetail()
+        << "/" << key.getTileX()
         << "/" << h - key.getTileY() - 1
         << "." << _packager->getExtension();
 }
 
 
 bool WriteTMSTileHandler::handleTile(const TileKey& key, const TileVisitor& tv)
-{    
+{
     ImageLayer* imageLayer = dynamic_cast< ImageLayer* >( _layer.get() );
     ElevationLayer* elevationLayer = dynamic_cast< ElevationLayer* >( _layer.get() );
 
@@ -73,19 +75,12 @@ bool WriteTMSTileHandler::handleTile(const TileKey& key, const TileVisitor& tv)
         return true;
     }
 
-    if (!tileSource)
-    {
-        // attempt to create the output folder:        
-        osgEarth::makeDirectoryForFile( path );       
-    }
-
-
     if (imageLayer)
-    {                        
+    {
         GeoImage geoImage = imageLayer->createImage( key );
 
         if (geoImage.valid())
-        {                             
+        {
             if (!_packager->getKeepEmpties() && ImageUtils::isEmptyImage(geoImage.getImage()))
             {
                 OE_INFO << "Not writing completely transparent image for key " << key.str() << std::endl;
@@ -94,6 +89,13 @@ bool WriteTMSTileHandler::handleTile(const TileKey& key, const TileVisitor& tv)
 
             if (_packager->getApplyAlphaMask())
             {
+                // Convert the image to RGBA if necessary
+                if (!ImageUtils::hasAlphaChannel(geoImage.getImage()))
+                {
+                    osg::ref_ptr< osg::Image > rgba = ImageUtils::convertToRGBA8(geoImage.getImage());
+                    geoImage = GeoImage(rgba.get(), geoImage.getExtent());
+                }
+
                 // mask out areas not included in the request:
                 for(std::vector<GeoExtent>::const_iterator g = tv.getExtents().begin();
                     g != tv.getExtents().end();
@@ -104,13 +106,13 @@ bool WriteTMSTileHandler::handleTile(const TileKey& key, const TileVisitor& tv)
             }
 
             // OE_NOTICE << "Created image for " << key.str() << std::endl;
-            osg::ref_ptr< osg::Image > final = geoImage.getImage();                        
+            osg::ref_ptr< osg::Image > final = geoImage.getImage();
 
-            // convert to RGB if necessary            
+            // convert to RGB if necessary
             if ( _packager->getExtension() == "jpg" && final->getPixelFormat() != GL_RGB )
             {
-                final = ImageUtils::convertToRGB8( final );
-            }            
+                final = ImageUtils::convertToRGB8( final.get() );
+            }
 
             // use the TileSource provided if set, else use writeImageFile
             if (tileSource)
@@ -120,18 +122,20 @@ bool WriteTMSTileHandler::handleTile(const TileKey& key, const TileVisitor& tv)
             }
             else
             {
-                return osgDB::writeImageFile(*final, path, _packager->getOptions());
+                // attempt to create the output folder:
+                osgEarth::makeDirectoryForFile( path );
+                return osgDB::writeImageFile(*final.get(), path, _packager->getOptions());
             }
-        }            
+        }
     }
     else if (elevationLayer )
     {
-        GeoHeightField hf = elevationLayer->createHeightField( key );
+        GeoHeightField hf = elevationLayer->createHeightField(key, NULL);
         if (hf.valid())
         {
             // convert the HF to an image
             ImageToHeightFieldConverter conv;
-            osg::ref_ptr< osg::Image > image = conv.convert( hf.getHeightField(), _packager->getElevationPixelDepth() );	
+            osg::ref_ptr< osg::Image > image = conv.convert( hf.getHeightField(), _packager->getElevationPixelDepth() );
 
             // use the TileSource provided if set, else use writeImageFile
             if (tileSource)
@@ -141,42 +145,48 @@ bool WriteTMSTileHandler::handleTile(const TileKey& key, const TileVisitor& tv)
             }
             else
             {
+                // attempt to create the output folder:
+                osgEarth::makeDirectoryForFile( path );
                 return osgDB::writeImageFile(*image.get(), path, _packager->getOptions());
             }
-        }            
+        }
     }
-        
-    // If we didn't produce a result but the key isn't within range then we should continue to 
+
+    // If we didn't produce a result but the key isn't within range then we should continue to
     // traverse the children b/c a min level was set.
-    if (!_layer->isKeyInRange(key))
+    if (!_layer->isKeyInLegalRange(key))
     {
         return true;
     }
-    return false;        
-} 
+    return false;
+}
 
 bool WriteTMSTileHandler::hasData( const TileKey& key ) const
 {
-    TileSource* ts = _layer->getTileSource();
-    if (ts)
-    {
-        return ts->hasData(key);
-    }
-    return true;
+    return _layer->mayHaveData(key);
+    //TileSource* ts = _layer->getTileSource();
+    //if (ts)
+    //{
+    //    return ts->hasDataInExtent(key.getExtent());
+    //}
+    //return true;
 }
 
 std::string WriteTMSTileHandler::getProcessString() const
 {
     ImageLayer* imageLayer = dynamic_cast< ImageLayer* >( _layer.get() );
-    ElevationLayer* elevationLayer = dynamic_cast< ElevationLayer* >( _layer.get() );    
+    ElevationLayer* elevationLayer = dynamic_cast< ElevationLayer* >( _layer.get() );
 
     std::stringstream buf;
     buf << "osgearth_package --tms ";
     if (imageLayer)
-    {        
-        for (int i = 0; i < _map->getNumImageLayers(); i++)
+    {
+        ImageLayerVector imageLayers;
+        _map->getLayers(imageLayers);
+
+        for (int i = 0; i < imageLayers.size(); i++)
         {
-            if (imageLayer == _map->getImageLayerAt(i))
+            if (imageLayer == imageLayers[i].get())
             {
                 buf << " --image " << i << " ";
                 break;
@@ -185,9 +195,12 @@ std::string WriteTMSTileHandler::getProcessString() const
     }
     else if (elevationLayer)
     {
-        for (int i = 0; i < _map->getNumElevationLayers(); i++)
+        ElevationLayerVector elevationLayers;
+        _map->getLayers(elevationLayers);
+
+        for (int i = 0; i < elevationLayers.size(); i++)
         {
-            if (elevationLayer == _map->getElevationLayerAt(i))
+            if (elevationLayer == elevationLayers[i].get())
             {
                 buf << " --elevation " << i << " ";
                 break;
@@ -201,12 +214,12 @@ std::string WriteTMSTileHandler::getProcessString() const
     buf << " --elevation-pixel-depth " << _packager->getElevationPixelDepth() << " ";
     if (_packager->getOptions())
     {
-        buf << " --db-options " << _packager->getOptions()->getOptionString() << " ";    
+        buf << " --db-options " << _packager->getOptions()->getOptionString() << " ";
     }
     if (_packager->getOverwrite())
     {
         buf << " --overwrite ";
-    }            
+    }
     if (_packager->getApplyAlphaMask())
     {
         buf << " --alpha-mask ";
@@ -323,16 +336,16 @@ void TMSPackager::setApplyAlphaMask(bool applyAlphaMask)
 
 TileVisitor* TMSPackager::getTileVisitor() const
 {
-    return _visitor;
+    return _visitor.get();
 }
 
 void TMSPackager::setVisitor(TileVisitor* visitor)
 {
     _visitor = visitor;
-}    
+}
 
 void TMSPackager::run( TerrainLayer* layer,  Map* map  )
-{    
+{
     // fetch one tile to see what the image size should be
     ImageLayer* imageLayer = dynamic_cast<ImageLayer*>(layer);
     ElevationLayer* elevationLayer = dynamic_cast<ElevationLayer*>(layer);
@@ -344,25 +357,31 @@ void TMSPackager::run( TerrainLayer* layer,  Map* map  )
 
         unsigned int index = 0;
         if (imageLayer)
-        {            
+        {
+            ImageLayerVector imageLayers;
+            map->getLayers(imageLayers);
+
             layerName << "image";
             // Get the index of the layer
-            for (int i = 0; i < map->getNumImageLayers(); i++)
+            for (int i = 0; i < imageLayers.size(); i++)
             {
-                if (map->getImageLayerAt(i) == imageLayer)
+                if (imageLayers[i].get() == imageLayer)
                 {
                     index = i;
                     break;
                 }
-            }            
+            }
         }
         else if (elevationLayer)
         {
+            ElevationLayerVector elevationLayers;
+            map->getLayers(elevationLayers);
+
             layerName << "elevation";
             // Get the index of the layer
-            for (int i = 0; i < map->getNumElevationLayers(); i++)
+            for (int i = 0; i < elevationLayers.size(); i++)
             {
-                if (map->getElevationLayerAt(i) == elevationLayer)
+                if (elevationLayers[i].get() == elevationLayer)
                 {
                     index = i;
                     break;
@@ -412,19 +431,19 @@ void TMSPackager::run( TerrainLayer* layer,  Map* map  )
     }
 
 
-    _handler = new WriteTMSTileHandler(layer, map, this);    
-    _visitor->setTileHandler( _handler );    
-    _visitor->run( map->getProfile() );    
+    _handler = new WriteTMSTileHandler(layer, map, this);
+    _visitor->setTileHandler( _handler.get() );
+    _visitor->run( map->getProfile() );
 }
 
-void TMSPackager::writeXML( TerrainLayer* layer, Map* map)
+void TMSPackager::writeXML(TerrainLayer* layer, Map* map)
 {
-    DataExtentList dataExtents;
-    layer->getDataExtents(dataExtents);
+    const DataExtentList& dataExtents = layer->getDataExtents();
+
      // create the tile map metadata:
     osg::ref_ptr<TMS::TileMap> tileMap = TMS::TileMap::create(
         "",
-        map->getProfile(),        
+        map->getProfile(),
         dataExtents,
         _extension,
         _width,
@@ -449,7 +468,7 @@ void TMSPackager::writeXML( TerrainLayer* layer, Map* map)
     tileMap->setVersion( "1.0.0" );
     tileMap->getFormat().setMimeType( mimeType );
     tileMap->generateTileSets( std::min(23u, maxLevel+1) );
-    
+
 
     // write out the tilemap catalog:
     std::string tileMapFilename = osgDB::concatPaths( osgDB::concatPaths(_destination, toLegalFileName( _layerName )), "tms.xml");
diff --git a/src/osgEarthUtil/TerrainProfile b/src/osgEarthUtil/TerrainProfile
index 2d26c0a..2ac50c9 100644
--- a/src/osgEarthUtil/TerrainProfile
+++ b/src/osgEarthUtil/TerrainProfile
@@ -190,7 +190,7 @@ namespace osgEarth { namespace Util {
          */
         void setStartEnd(const osgEarth::GeoPoint& start, const osgEarth::GeoPoint& end);
 
-        virtual void onTileAdded(const osgEarth::TileKey& tileKey, osg::Node* terrain, TerrainCallbackContext&);
+        virtual void onTileAdded(const osgEarth::TileKey& tileKey, osg::Node* graph, TerrainCallbackContext&);
 
         /**
          * Recomputes the terrain profile
diff --git a/src/osgEarthUtil/TerrainProfile.cpp b/src/osgEarthUtil/TerrainProfile.cpp
index c56d8c6..c19a573 100644
--- a/src/osgEarthUtil/TerrainProfile.cpp
+++ b/src/osgEarthUtil/TerrainProfile.cpp
@@ -185,7 +185,7 @@ void TerrainProfileCalculator::setStartEnd(const GeoPoint& start, const GeoPoint
     }
 }
 
-void TerrainProfileCalculator::onTileAdded(const osgEarth::TileKey& tileKey, osg::Node* terrain, TerrainCallbackContext&)
+void TerrainProfileCalculator::onTileAdded(const osgEarth::TileKey& tileKey, osg::Node* graph, TerrainCallbackContext&)
 {
     if (_start.isValid() && _end.isValid())
     {
diff --git a/src/osgEarthUtil/TileIndex.cpp b/src/osgEarthUtil/TileIndex.cpp
index da0b540..b293b14 100644
--- a/src/osgEarthUtil/TileIndex.cpp
+++ b/src/osgEarthUtil/TileIndex.cpp
@@ -22,10 +22,15 @@
 
 #include <osgEarth/Registry>
 #include <osgEarth/FileUtils>
+
 #include <osgEarthUtil/TileIndex>
+
+#include <osgEarthFeatures/OgrUtils>
+#include <osgEarthFeatures/FeatureCursor>
+
 #include <osgEarthDrivers/feature_ogr/OGRFeatureOptions>
+
 #include <ogr_api.h>
-#include <osgEarthFeatures/OgrUtils>
 #include <osgDB/FileUtils>
 
 using namespace osgEarth;
@@ -139,7 +144,7 @@ bool TileIndex::add( const std::string& filename, const GeoExtent& extent )
     polygon->push_back( osg::Vec3d(extent.bounds().xMin(), extent.bounds().yMax(), 0) );
     polygon->push_back( osg::Vec3d(extent.bounds().xMin(), extent.bounds().yMin(), 0) );
    
-    osg::ref_ptr< Feature > feature = new Feature( polygon, extent.getSRS()  );
+    osg::ref_ptr< Feature > feature = new Feature( polygon.get(), extent.getSRS()  );
     feature->set("location", filename );
     
     const SpatialReference* wgs84 = SpatialReference::create("epsg:4326");
diff --git a/src/osgEarthUtil/TileIndexBuilder.cpp b/src/osgEarthUtil/TileIndexBuilder.cpp
index 1697441..26d3baa 100644
--- a/src/osgEarthUtil/TileIndexBuilder.cpp
+++ b/src/osgEarthUtil/TileIndexBuilder.cpp
@@ -24,6 +24,7 @@
 #include <osgEarth/Registry>
 #include <osgEarth/FileUtils>
 #include <osgEarth/Progress>
+#include <osgEarth/ImageLayer>
 #include <osgEarthDrivers/gdal/GDALOptions>
 #include <osgDB/FileUtils>
 #include <osgDB/FileNameUtils>
diff --git a/src/osgEarthUtil/TopologyGraph b/src/osgEarthUtil/TopologyGraph
new file mode 100644
index 0000000..0022704
--- /dev/null
+++ b/src/osgEarthUtil/TopologyGraph
@@ -0,0 +1,147 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+* Copyright 2016 Pelican Mapping
+* http://osgearth.org
+*
+* osgEarth is free software; you can redistribute it and/or modify
+* it under the terms of the GNU Lesser General Public License as published by
+* the Free Software Foundation; either version 2 of the License, or
+* (at your option) any later version.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+* IN THE SOFTWARE.
+*
+* You should have received a copy of the GNU Lesser General Public License
+* along with this program.  If not, see <http://www.gnu.org/licenses/>
+*/
+
+#ifndef OSGEARTHUTIL_TOPOLOGY_GRAPH_H
+#define OSGEARTHUTIL_TOPOLOGY_GRAPH_H 1
+
+#include <osgEarthUtil/Common>
+#include <osgEarth/SpatialReference>
+#include <osg/NodeVisitor>
+#include <osg/Vec3d>
+#include <osg/Geometry>
+#include <vector>
+#include <map>
+#include <set>
+
+namespace osgEarth { namespace Util
+{
+#define TOPOLOGY_TOLERANCE 0.0
+
+    //! Stores the noded topology of a model with unique verticies and edge definitions.
+    //! The verticies are stored rotated into the XY plane so that we can properly find
+    //! the "bottom-most" vert and walk the boundary.
+    class OSGEARTHUTIL_EXPORT TopologyGraph
+    {
+    public:
+
+        struct Vertex {
+            Vertex(const Vertex& rhs) : _drawable(rhs._drawable), _verts(rhs._verts), _index(rhs._index) { }
+            Vertex(osg::Drawable* drawable, osg::Vec3Array* verts, unsigned index) : _drawable(drawable), _verts(verts), _index(index) { }
+            osg::Drawable* _drawable;
+            osg::Vec3Array* _verts;
+            unsigned _index;
+            const osg::Vec3& vertex() const { return (*_verts)[_index]; }
+            float x() const { return (*_verts)[_index].x(); }
+            float y() const { return (*_verts)[_index].y(); }
+            bool operator < (const Vertex& rhs) const {
+                double dx = x() - rhs.x();
+                if (dx < 0.0) return true;
+                if (dx > 0.0) return false;
+                double dy = y() - rhs.y();
+                return dy < 0.0;
+            }
+        };
+
+        typedef std::set<Vertex> VertexSet;
+
+        typedef VertexSet::iterator Index;
+
+        // custom comparator for Index so we can use it at a std::map key
+        struct IndexLess : public std::less<Index> {
+            bool operator()(const Index& lhs, const Index& rhs) const {
+                return (*lhs) < (*rhs);
+            }
+        };
+
+        typedef std::set<Index, IndexLess> IndexSet;
+
+        typedef std::map<Index, IndexSet, IndexLess>  EdgeMap;
+
+        typedef std::vector<Index> IndexVector;
+
+    public:
+
+        TopologyGraph();
+
+        void createBoundary(IndexVector& output) const;
+
+    public:
+        unsigned     _totalVerts;  // total number of verts encountered
+        //VertexSet    _vertsWorld;  // 
+        VertexSet    _verts;       // set of unique verts in the topology (rotated into XY plane)
+        EdgeMap      _edgeMap;     // maps each vert to all the verts with which it shares an edge
+        Index        _minY;        // points to the vert with the minimum Y coordinate (in XY plane)
+        osg::Matrixd _world2plane; // matrix that transforms into a localized XY plane
+        const osgEarth::SpatialReference* _srs;
+
+        friend class TopologyBuilder;
+        friend class BuildTopologyVisitor;
+
+        void dumpBoundary(const std::string& filename);
+    };
+
+
+    // A TriangleIndexFunctor that traverses a stream of triangles and builds a
+    // topology graph from their points and edges.
+    class OSGEARTHUTIL_EXPORT TopologyBuilder
+    {
+    public:
+        TopologyBuilder();
+
+        typedef std::map<unsigned, TopologyGraph::Index> UniqueMap;
+
+        TopologyGraph*  _topology;     // topology to which to append point and edge data
+        osg::Drawable*  _drawable;     // source geometry
+        osg::Vec3Array* _verts;        // source vertex list
+        osg::Matrix     _local2world;  // transforms source verts into world coordinates
+        UniqueMap       _uniqueMap;    // prevents duplicates
+
+        void operator()( unsigned v0, unsigned v1, unsigned v2 );
+
+        TopologyGraph::Index add( unsigned v );
+
+        friend class TopologyBuilderVisitor;
+    };
+
+
+    // Visits a scene graph and builds a topology graph from the verts and edges
+    // found within.
+    class OSGEARTHUTIL_EXPORT BuildTopologyVisitor : public osg::NodeVisitor
+    {
+    public:
+        BuildTopologyVisitor( TopologyGraph& topology );
+
+        // track local transforms so we can build a topology in world coords
+        void apply( osg::Transform& xform );
+
+        // add the contents of a Geometry to the topology
+        void apply( osg::Drawable& drawable );
+
+        void apply( osg::Drawable* drawable, osg::Vec3Array* verts );
+
+        std::vector<osg::Matrixd> _matrixStack;
+        TopologyGraph&            _topology;
+    };
+
+} }
+
+#endif // OSGEARTHUTIL_TOPOLOGY_GRAPH_H
diff --git a/src/applications/osgearth_boundarygen/BoundaryUtil.cpp b/src/osgEarthUtil/TopologyGraph.cpp
similarity index 69%
copy from src/applications/osgearth_boundarygen/BoundaryUtil.cpp
copy to src/osgEarthUtil/TopologyGraph.cpp
index 66fb5e7..6110c16 100644
--- a/src/applications/osgearth_boundarygen/BoundaryUtil.cpp
+++ b/src/osgEarthUtil/TopologyGraph.cpp
@@ -19,9 +19,10 @@
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
 */
+#include <osgEarthUtil/TopologyGraph>
+
+using namespace osgEarth::Util;
 
-#include "BoundaryUtil"
-#include "VertexCollectionVisitor"
 #include <algorithm>
 #include <osg/Geode>
 #include <osg/Geometry>
@@ -35,6 +36,333 @@
 #include <vector>
 #include <set>
 
+
+TopologyGraph::TopologyGraph() :
+_minY(_verts.end()),
+_totalVerts(0),
+_srs(0L)
+{
+    //nop
+}
+
+
+void
+TopologyGraph::createBoundary(TopologyGraph::IndexVector& output) const
+{
+    // the normal defines the XY plane in which to search for a boundary
+    osg::Vec3d normal(0,0,1);
+    osg::Vec3d center;
+
+#if 0
+    if ( geocentric )
+    {
+        // define the XY plane based on the normal to the center of the dataset:
+        osg::BoundingSphere bs = node->getBound();
+        center = bs.center();
+        normal = center;
+        normal.normalize();
+        OE_DEBUG << "Normal = " << normal.x() << ", " << normal.y() << ", " << normal.z() << std::endl;
+    }
+#endif
+
+
+#if 0
+    // set up a transform that will localize geometry into an XY plane
+    if ( geocentric )
+    {
+        topology._world2plane.makeRotate(normal, osg::Vec3d(0,0,1));
+        topology._world2plane.preMultTranslate(-center);
+
+        // if this is set, use mercator projection instead of a simple geolocation
+        //topology._srs = osgEarth::SpatialReference::get("spherical-mercator");
+    }
+#endif
+
+    // build the topology
+    //BuildTopologyVisitor buildTopoVisitor(topology);
+    //node->accept( buildTopoVisitor );
+
+    //OE_DEBUG << "Found " << topology._verts.size() << " unique verts" << std::endl;
+    //dumpPointCloud(topology);
+
+    // starting with the minimum-Y vertex (which is guaranteed to be in the boundary)
+    // traverse the outside of the point set. Do this by sorting all the edges by
+    // their angle relative to the vector to the previous point. The vector with the
+    // smallest angle represents the edge connecting the current point to the next
+    // boundary point. Walk the edge until we return to the beginning.
+    
+    Index vptr      = _minY;
+    Index vptr_prev = _verts.end();
+
+    IndexSet visited;
+
+    while( true )
+    {
+        // store this vertex in the result set:
+        output.push_back( vptr );
+
+        // pull up the next 2D vertex (XY plane):
+        osg::Vec2d vert ( vptr->x(), vptr->y() );
+
+        // construct the "base" vector that points from the previous 
+        // point to the current point; or to -X in the initial case
+        osg::Vec2d base;
+        if ( vptr_prev == _verts.end() )
+            base.set( -1, 0 );
+        else
+            base = vert - osg::Vec2d( vptr_prev->x(), vptr_prev->y() );
+            
+        // pull up the edge set for this vertex:
+        EdgeMap::const_iterator ei = _edgeMap.find(vptr);
+        if (ei == _edgeMap.end())
+            continue; // should be impossible
+
+        const IndexSet& edges = ei->second; //_edgeMap[vptr];
+
+        // find the edge with the minimun delta angle to the base vector
+        double bestScore = DBL_MAX;
+        Index  bestEdge  = _verts.end();
+        
+        //OE_DEBUG << "VERTEX (" << 
+        //    vptr->x() << ", " << vptr->y() << ", " << vptr->z() 
+        //    << ") has " << edges.size() << " edges..."
+        //    << std::endl;
+
+        for( IndexSet::iterator e = edges.begin(); e != edges.end(); ++e )
+        {
+            // don't go back from whence we just came
+            if ( *e == vptr_prev )
+                continue;
+
+            // never return to a vert we've already visited
+            if ( visited.find(*e) != visited.end() )
+                continue;
+
+            // calculate the angle between the base vector and the current edge:
+            osg::Vec2d edgeVert( (*e)->x(), (*e)->y() );
+            osg::Vec2d edge = edgeVert - vert;
+
+            base.normalize();
+            edge.normalize();
+            double cross = base.x()*edge.y() - base.y()*edge.x();
+            double dot   = base * edge;
+            double score = dot;
+
+            if ( cross < 0.0 )
+            {
+                double diff = 2.0-(score+1.0);
+                score = 1.0 + diff;
+            }
+
+            //OE_DEBUG << "   check: " << (*e)->x() << ", " << (*e)->y() << ", " << (*e)->z() << std::endl;
+            //OE_DEBUG << "   base = " << base.x() << ", " << base.y() << std::endl;
+            //OE_DEBUG << "   edge = " << edge.x() << ", " << edge.y() << std::endl;
+            //OE_DEBUG << "   crs = " << cross << ", dot = " << dot << ", score = " << score << std::endl;
+            
+            if ( score < bestScore )
+            {
+                bestScore = score;
+                bestEdge = *e;
+            }
+        }
+
+        if ( bestEdge == _verts.end() )
+        {
+            // this will probably never happen
+            osg::notify(osg::WARN) << "Illegal state - reached a dead end!" << std::endl;
+            break;
+        }
+
+        // store the previous:
+        vptr_prev = vptr;
+
+        // follow the chosen edge around the outside of the geometry:
+        //OE_DEBUG << "   BEST SCORE = " << bestScore << std::endl;
+        vptr = bestEdge;
+
+        // record this vert so we don't visit it again.
+        visited.insert( vptr );
+
+        // once we make it all the way around, we're done:
+        if ( vptr == _minY )
+            break;
+    }
+
+#if 0
+    // un-rotate the results from the XY plane back to their original frame:
+    if ( _srs )
+    {
+        const osgEarth::SpatialReference* ecef = _srs->getECEF();
+        topology._srs->transform(_result->asVector(), ecef);
+    }
+    else
+    {
+        osg::Matrix plane2world;
+        plane2world.invert( _world2plane );
+        for( osg::Vec3dArray::iterator i = _result->begin(); i != _result->end(); ++i )
+            (*i) = (*i) * plane2world;
+    }
+
+    return _result.release();
+#endif
+}
+
+
+void
+TopologyGraph::dumpBoundary(const std::string& filename)
+{
+    IndexVector boundary;
+    createBoundary(boundary);
+    
+    osg::Vec3Array* v = new osg::Vec3Array();
+
+    for (IndexVector::const_iterator i = boundary.begin(); i != boundary.end(); ++i)
+    {
+        const Index& index = *i;
+        v->push_back(osg::Vec3(index->x(), index->y(), 0));
+    }
+
+    osg::ref_ptr<osg::Geometry> g = new osg::Geometry();
+    g->setVertexArray(v);
+    g->addPrimitiveSet(new osg::DrawArrays(GL_LINE_STRIP, 0, v->size()));
+    g->addPrimitiveSet(new osg::DrawArrays(GL_POINTS, 0, v->size()));
+    g->getOrCreateStateSet()->setAttributeAndModes(new osg::Point(3));
+    osgDB::writeNodeFile(*(g.get()), filename);
+}
+
+
+TopologyBuilder::TopologyBuilder()
+{
+    //nop
+}
+
+void
+TopologyBuilder::operator()(unsigned v0, unsigned v1, unsigned v2)
+{
+    TopologyGraph::Index i0 = add(v0);
+    TopologyGraph::Index i1 = add(v1);
+    TopologyGraph::Index i2 = add(v2);
+
+    // add to the edge list for each of these verts
+    if (i0 != i1) _topology->_edgeMap[i0].insert(i1);
+    if (i0 != i2) _topology->_edgeMap[i0].insert(i2);
+    if (i1 != i0) _topology->_edgeMap[i1].insert(i0);
+    if (i1 != i2) _topology->_edgeMap[i1].insert(i2);
+    if (i2 != i0) _topology->_edgeMap[i2].insert(i0);
+    if (i2 != i1) _topology->_edgeMap[i2].insert(i1);
+}
+
+TopologyGraph::Index
+TopologyBuilder::add(unsigned v)
+{
+    // first see if we already added the vert at this index.
+    UniqueMap::iterator i = _uniqueMap.find(v);
+    if (i == _uniqueMap.end())
+    {
+#if 0
+        // no, so transform it into world coordinates, and rotate it into the XY plane
+        osg::Vec3d vert = (*_verts)[v];
+        osg::Vec3d world = vert * _local2world;
+        osg::Vec3d plane = world;
+
+        if (_topology->_srs)
+        {
+            const osgEarth::SpatialReference* ecef = _topology->_srs->getECEF();
+            ecef->transform(world, _topology->_srs, plane);
+        }
+        else
+        {
+            plane = world * _topology->_world2plane;
+        }
+#endif
+
+        TopologyGraph::Vertex vertex(_drawable, _verts, v);
+        std::pair<TopologyGraph::Index, bool> f = _topology->_verts.insert(vertex);
+        if (f.second == true) // insert succeeded
+        {
+            // this is a new location, so check it to see if it is the new "southernmost" point:
+            if (_topology->_minY == _topology->_verts.end() || vertex.y() < _topology->_minY->y())
+            {
+                _topology->_minY = f.first;
+            }
+        }
+
+#if 0
+        // insert it into the unique vert list
+        std::pair<TopologyGraph::VertexSet::iterator, bool> f = _topology->_verts.insert(plane);
+        if (f.second) // insert succedded
+        {
+            // this is a new location, so check it to see if it is the new "southernmost" point:
+            if (_topology->_minY == _topology->_verts.end() || plane.y() < _topology->_minY->y())
+            {
+                _topology->_minY = f.first;
+            }
+        }
+#endif
+
+        // store in the uniqueness map so we don't process the same index again
+        _uniqueMap[v] = f.first;
+
+        // return the index of the vert.
+        return f.first;
+    }
+    else
+    {
+        return i->second;
+    }
+}
+
+
+
+
+BuildTopologyVisitor::BuildTopologyVisitor(TopologyGraph& graph) :
+osg::NodeVisitor(),
+_topology(graph)
+{
+    setTraversalMode(TRAVERSE_ALL_CHILDREN);
+    setNodeMaskOverride(~0);
+}
+
+void
+BuildTopologyVisitor::apply(osg::Transform& xform)
+{
+    osg::Matrix matrix;
+    if (!_matrixStack.empty()) matrix = _matrixStack.back();
+    xform.computeLocalToWorldMatrix(matrix, this);
+    _matrixStack.push_back(matrix);
+    traverse(xform);
+    _matrixStack.pop_back();
+}
+
+void
+BuildTopologyVisitor::apply(osg::Drawable& drawable)
+{
+    osg::Geometry* geom = drawable.asGeometry();
+    if (geom)
+    {
+        osg::Vec3Array* verts = dynamic_cast<osg::Vec3Array*>(geom->getVertexArray());
+        if (verts)
+        {
+            apply(&drawable, verts);
+        }
+    }
+}
+
+void
+BuildTopologyVisitor::apply(osg::Drawable* drawable, osg::Vec3Array* verts)
+{
+    osg::TriangleIndexFunctor<TopologyBuilder> builder;
+    builder._topology = &_topology;
+    builder._drawable = drawable;
+    builder._verts = verts;
+    if (!_matrixStack.empty())
+        builder._local2world = _matrixStack.back();
+    _topology._totalVerts += verts->size();
+    drawable->accept(builder);
+}
+
+#if 0
+
 /* Comparator used to sort osg::Vec3d's first by x and then by y */
 bool presortCompare (osg::Vec3d i, osg::Vec3d j)
 {
@@ -649,3 +977,4 @@ BoundaryUtil::simpleBoundaryTest(const osg::Vec3dArray& boundary)
   osg::ref_ptr<osgEarth::Symbology::Geometry> outPoly;
   return outterPoly->difference(boundsPoly, outPoly);
 }
+#endif
diff --git a/src/osgEarthUtil/UTMGraticule b/src/osgEarthUtil/UTMGraticule
index cf94881..637fa05 100644
--- a/src/osgEarthUtil/UTMGraticule
+++ b/src/osgEarthUtil/UTMGraticule
@@ -22,6 +22,7 @@
 #include <osgEarthUtil/Common>
 #include <osgEarth/MapNode>
 #include <osgEarth/MapNodeObserver>
+#include <osgEarth/ModelLayer>
 #include <osgEarthSymbology/Style>
 #include <osgEarthFeatures/Feature>
 #include <osg/ClipPlane>
@@ -36,119 +37,134 @@ namespace osgEarth { namespace Util
     /**
      * Configuration options for the geodetic graticule.
      */
-    class OSGEARTHUTIL_EXPORT UTMGraticuleOptions : public ConfigOptions
+    class UTMGraticuleOptions : public VisibleLayerOptions
     {
     public:
-        UTMGraticuleOptions( const Config& conf =Config() );
-
-        /** dtor */
-        virtual ~UTMGraticuleOptions() { }
-
-
+        UTMGraticuleOptions(const ConfigOptions& conf =ConfigOptions()) : VisibleLayerOptions(conf) {
+            fromConfig(_conf);
+        }
+        
     public:
 
-        /** Default style for grid lines and text */
-        optional<Style>& primaryStyle() { return _primaryStyle; }
-        const optional<Style>& primaryStyle() const { return _primaryStyle; }
+        /** Style for grid zone designator geometry and text */
+        optional<Style>& gzdStyle() { return _gzdStyle; }
+        const optional<Style>& gzdStyle() const { return _gzdStyle; }
 
         /** Text scale factor (default = 1) */
         optional<float>& textScale() { return _textScale; }
         const optional<float>& textScale() const { return _textScale; }
 
     public:
-        Config getConfig() const;
+        virtual Config getConfig() const {
+            Config conf = VisibleLayerOptions::getConfig();
+            conf.key() = "utm_graticule";
+            conf.addObjIfSet("gzd_style", _gzdStyle);
+            conf.addIfSet("text_scale", _textScale);
+            return conf;
+        }
+
+        virtual void fromConfig(const Config& conf) {
+            conf.getObjIfSet("gzd_style", _gzdStyle);
+            conf.getIfSet("text_scale", _textScale);
+        }
 
     protected:
-        optional<Style>    _primaryStyle;
-        optional<float>    _textScale;
+        optional<Style> _gzdStyle;
+        optional<float> _textScale;
+
+        void mergeConfig(const Config& conf) {
+            VisibleLayerOptions::mergeConfig(conf);
+            fromConfig(conf);
+        }
+    };
+
+
+    /**
+     * UTM data (used by the UTM Graticule and the MGRS Graticule).
+     */
+    class OSGEARTHUTIL_EXPORT UTMData
+    {
+    public:
+        typedef std::map<std::string, GeoExtent> SectorTable;
+
+    public:
+        UTMData() { }
+
+        void rebuild(const Profile* profile);
 
-        void mergeConfig( const Config& conf );
+        osg::Group* buildGZDTile(const std::string& name, const GeoExtent& extent, const Style& style, const FeatureProfile* featureProfile, const Map* map);
+
+        SectorTable& sectorTable() { return _gzd; }
+        const SectorTable& sectorTable() const { return _gzd; }
+
+    private:
+        SectorTable _gzd;
     };
 
 
     /**
      * Implements a UTM map graticule. 
-     * 
-     * NOTE: So far, this only works for geocentric maps.
-     * TODO: Add projected support; add text label support
+     * This only works for geocentric maps.
      */
-    class OSGEARTHUTIL_EXPORT UTMGraticule : public osg::Group, public MapNodeObserver
+    class OSGEARTHUTIL_EXPORT UTMGraticule : public VisibleLayer
     {
     public:
+        META_Layer(osgEarthUtil, UTMGraticule, UTMGraticuleOptions);
 
         /**
-         * Constructs a new graticule for use with the specified map. The graticule
-         * is created with several default levels. If you call addLevel(), the 
-         * default levels are deleted.
+         * Construct a new UTM graticule with default settings.
+         */
+        UTMGraticule();
+
+        /**
+         * Constructs a new graticule for use with the specified map.
          *
-         * @param map
-         *      Map with which you will use this graticule
          * @param options
          *      Optional "options" that configure the graticule. Defaults will be used
          *      if you don't specify this.
          */
-        UTMGraticule( MapNode* mapNode );
-        UTMGraticule( MapNode* mapNode, const UTMGraticuleOptions& options);
-
-        /** dtor */
-        virtual ~UTMGraticule() { }
-
-        /** 
-         * Applies a new set of options. The graticule will be rebuilt if necessary.
-         */
-        void setOptions( const UTMGraticuleOptions& options );
+        UTMGraticule(const UTMGraticuleOptions& options);
 
         /**
-         * Gets the options with which the graticule was built.
+         * If you change any of the options, call this to refresh the display
+         * to refelct the new settings.
          */
-        const UTMGraticuleOptions& getOptions() const { return _options.value(); }
+        void dirty();
 
-        /**
-         * Sets the clip plane to use. If you don't set one, the object will
-         * create both a ClipNode and ClipPlane for geocentric horizon clipping.
-         * If you do set a clip plane, this object will update it automatically,
-         * and we expect that you have the plane registered with an osg::ClipNode
-         * elsewhere in the scene graph.
-         */
-        void setClipPlane(osg::ClipPlane* clipPlane);
-        osg::ClipPlane* getClipPlane() const { return _clipPlane.get(); }
+    public: // Layer
+
+        virtual void addedToMap(const Map* map);
 
+        virtual void removedFromMap(const Map* map);
         
-    public: // MapNodeObserver
+        virtual osg::Node* getOrCreateNode();
 
-        virtual void setMapNode( MapNode* mapNode );
+        virtual void init();
 
-        virtual MapNode* getMapNode() { return _mapNode.get(); }
+    protected:
 
+        /** dtor */
+        virtual ~UTMGraticule() { }
 
-    protected:
-        osg::ref_ptr<const Profile>        _profile;
-        osg::ref_ptr<const FeatureProfile> _featureProfile;
+    private:
 
-        unsigned int               _id;
-        osg::observer_ptr<MapNode> _mapNode;
-        osg::Group*                _root;
+        void setUpDefaultStyles();
 
-        optional<UTMGraticuleOptions> _options;
+        void rebuild();
 
-        typedef std::map<std::string, GeoExtent> SectorTable;
-        SectorTable _gzd;
+        UID _uid;
+
+        osg::ref_ptr<const Profile> _profile;
+
+        osg::ref_ptr<FeatureProfile> _featureProfile;
 
         osg::ref_ptr<osg::ClipPlane> _clipPlane;
 
-    protected:
-        unsigned int getID() const { return _id; }
-        void init();
-        void rebuild();
-        osg::Node* buildGZDTile( const std::string& name, const GeoExtent& extent );
+        osg::ref_ptr<osg::Group> _root;
 
-        virtual osg::Group* buildGZDChildren( osg::Group* node, const std::string& gzd ) {
-            return node; }
+        osg::observer_ptr<const Map> _map;
 
-    public:
-        static Threading::Mutex s_graticuleMutex;
-        typedef std::map<unsigned, osg::ref_ptr<UTMGraticule> > UTMGraticuleRegistry;
-        static UTMGraticuleRegistry s_graticuleRegistry;
+        UTMData _utmData;
     };
 
 } } // namespace osgEarth::Util
diff --git a/src/osgEarthUtil/UTMGraticule.cpp b/src/osgEarthUtil/UTMGraticule.cpp
index 52c1ad2..f96d174 100644
--- a/src/osgEarthUtil/UTMGraticule.cpp
+++ b/src/osgEarthUtil/UTMGraticule.cpp
@@ -17,21 +17,14 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>
  */
 #include <osgEarthUtil/UTMGraticule>
-#include <osgEarthUtil/MGRSFormatter>
 
 #include <osgEarthFeatures/GeometryCompiler>
 #include <osgEarthFeatures/TextSymbolizer>
 
-#include <osgEarthSymbology/Geometry>
-#include <osgEarthAnnotation/LabelNode>
-
 #include <osgEarth/Registry>
-#include <osgEarth/DepthOffset>
-#include <osgEarth/ECEF>
 #include <osgEarth/NodeUtils>
 #include <osgEarth/Utils>
 #include <osgEarth/CullingUtils>
-#include <osgEarth/DrapeableNode>
 #include <osgEarth/ThreadingUtils>
 
 #include <OpenThreads/Mutex>
@@ -49,175 +42,18 @@ using namespace osgEarth;
 using namespace osgEarth::Util;
 using namespace osgEarth::Features;
 using namespace osgEarth::Symbology;
-using namespace osgEarth::Annotation;
-
-Threading::Mutex UTMGraticule::s_graticuleMutex;
-UTMGraticule::UTMGraticuleRegistry UTMGraticule::s_graticuleRegistry;
-
-//---------------------------------------------------------------------------
-
-UTMGraticuleOptions::UTMGraticuleOptions( const Config& conf ) :
-ConfigOptions( conf ),
-_textScale   ( 1.0f )
-{
-    mergeConfig( _conf );
-}
 
-void
-UTMGraticuleOptions::mergeConfig( const Config& conf )
-{
-    //todo
-}
-
-Config
-UTMGraticuleOptions::getConfig() const
-{
-    Config conf = ConfigOptions::newConfig();
-    conf.key() = "utm_graticule";
-    //todo
-    return conf;
-}
+REGISTER_OSGEARTH_LAYER(utm_graticule, UTMGraticule);
 
 //---------------------------------------------------------------------------
 
-
-UTMGraticule::UTMGraticule( MapNode* mapNode ) :
-_mapNode   ( mapNode ),
-_root      ( 0L )
-{
-    init();
-}
-
-UTMGraticule::UTMGraticule( MapNode* mapNode, const UTMGraticuleOptions& options ) :
-_mapNode   ( mapNode ),
-_root      ( 0L )
-{
-    _options = options;
-    init();
-}
-
-void
-UTMGraticule::init()
-{
-    // safely generate a unique ID for this graticule:
-    _id = Registry::instance()->createUID();
-    {
-        Threading::ScopedMutexLock lock( s_graticuleMutex );
-        s_graticuleRegistry[_id] = this;
-    }
-
-    // make the shared depth attr:
-    this->getOrCreateStateSet()->setMode(GL_DEPTH_TEST, 0);
-
-    // force it to render after the terrain.
-    this->getOrCreateStateSet()->setRenderBinDetails(1, "RenderBin");
-
-    // install the range callback for clip plane activation
-    this->addCullCallback( new RangeUniformCullCallback() );
-
-    // this will intialize the graph.
-    rebuild();
-}
-
-void
-UTMGraticule::setClipPlane(osg::ClipPlane* value)
-{
-    _clipPlane = value;
-    rebuild();
-}
-
-void
-UTMGraticule::setMapNode( MapNode* mapNode )
-{
-    _mapNode = mapNode;
-    rebuild();
-}
-
-void
-UTMGraticule::setOptions( const UTMGraticuleOptions& options )
-{
-    _options = options;
-    rebuild();
-}
-
 void
-UTMGraticule::rebuild()
+UTMData::rebuild(const Profile* profile)
 {
-    // clear everything out
-    this->removeChildren( 0, this->getNumChildren() );
-
-    // requires a map node
-    if ( !getMapNode() )
-    {
-        return;
-    }
-
-    // requires a geocentric map
-    if ( !getMapNode()->isGeocentric() )
-    {
-        OE_WARN << LC << "Projected map mode is not yet supported" << std::endl;
-        return;
-    }
-
-    const Profile* mapProfile = getMapNode()->getMap()->getProfile();
-
-    _profile = Profile::create(
-        mapProfile->getSRS(),
-        mapProfile->getExtent().xMin(),
-        mapProfile->getExtent().yMin(),
-        mapProfile->getExtent().xMax(),
-        mapProfile->getExtent().yMax(),
-        8, 4 );
-
-    _featureProfile = new FeatureProfile(_profile->getSRS());
-
-    //todo: do this right..
-    osg::StateSet* set = this->getOrCreateStateSet();
-    set->setMode( GL_LIGHTING, 0 );
-    set->setMode( GL_BLEND, 1 );
-
-    // set up default options if the caller did not supply them
-    if ( !_options.isSet() )
-    {
-        _options->primaryStyle() = Style();
-
-        LineSymbol* line = _options->primaryStyle()->getOrCreate<LineSymbol>();
-        line->stroke()->color() = Color::Gray;
-        line->stroke()->width() = 1.0;
-        line->tessellation() = 20;
-
-        TextSymbol* text = _options->primaryStyle()->getOrCreate<TextSymbol>();
-        text->fill()->color() = Color(Color::White, 0.3f);
-        text->halo()->color() = Color(Color::Black, 0.2f);
-        text->alignment() = TextSymbol::ALIGN_CENTER_CENTER;
-    }
-
-
-    // rebuild the graph:
-
-    // Horizon clipping plane.
-    osg::ClipPlane* cp = _clipPlane.get();
-    if ( cp )
-    {
-        _root = this;
-    }
-    else
-    {
-        osg::ClipNode* clipNode = new osg::ClipNode();
-        osgEarth::Registry::shaderGenerator().run( clipNode );
-        cp = new osg::ClipPlane( 0 );
-        clipNode->addClipPlane( cp );
-        _root = clipNode;
-    }
-    _root->addCullCallback( new ClipToGeocentricHorizon(_profile->getSRS(), cp) );
-
-
-    this->addChild( _root );
-
     // build the base Grid Zone Designator (GZD) loolup table. This is a table
     // that maps the GZD string to its extent.
     static std::string s_gzdRows( "CDEFGHJKLMNPQRSTUVWX" );
-    const SpatialReference* geosrs = _profile->getSRS()->getGeographicSRS();
+    const SpatialReference* geosrs = profile->getSRS()->getGeographicSRS();
 
     // build the lateral zones:
     for( unsigned zone = 0; zone < 60; ++zone )
@@ -255,31 +91,22 @@ UTMGraticule::rebuild()
     _gzd.erase( "32X" );
     _gzd.erase( "34X" );
     _gzd.erase( "36X" );
-
-    // now build the lateral tiles for the GZD level.
-    for( SectorTable::iterator i = _gzd.begin(); i != _gzd.end(); ++i )
-    {
-        osg::Node* tile = buildGZDTile( i->first, i->second );
-        if ( tile )
-            _root->addChild( tile );
-    }
 }
 
-
-osg::Node*
-UTMGraticule::buildGZDTile( const std::string& name, const GeoExtent& extent )
+osg::Group*
+UTMData::buildGZDTile(const std::string& name, const GeoExtent& extent, const Style& style, const FeatureProfile* featureProfile, const Map* map)
 {
     osg::Group* group = new osg::Group();
 
     Style lineStyle;
-    lineStyle.add( _options->primaryStyle()->get<LineSymbol>() );
-    lineStyle.add( _options->primaryStyle()->get<AltitudeSymbol>() );
+    lineStyle.add( const_cast<LineSymbol*>(style.get<LineSymbol>()) );
+    lineStyle.add( const_cast<AltitudeSymbol*>(style.get<AltitudeSymbol>()) );
 
-    bool hasText = _options->primaryStyle()->get<TextSymbol>() != 0L;
+    bool hasText = style.get<TextSymbol>() != 0L;
 
     GeometryCompiler compiler;
-    osg::ref_ptr<Session> session = new Session( getMapNode()->getMap() );
-    FilterContext context( session.get(), _featureProfile.get(), extent );
+    osg::ref_ptr<Session> session = new Session(map);
+    FilterContext context( session.get(), featureProfile, extent );
 
     // make sure we get sufficient tessellation:
     compiler.options().maxGranularity() = 1.0;
@@ -332,10 +159,11 @@ UTMGraticule::buildGZDTile( const std::string& name, const GeoExtent& extent )
         extent.getSRS()->transform( osg::Vec3d(extent.xMin(),tileCenter.y(),0), ecefSRS, west );
         extent.getSRS()->transform( osg::Vec3d(extent.xMax(),tileCenter.y(),0), ecefSRS, east );
 
-        TextSymbol* textSym = _options->primaryStyle()->getOrCreate<TextSymbol>();
+        const TextSymbol* textSym_in = style.get<TextSymbol>();
+        osg::ref_ptr<TextSymbol> textSym = textSym_in ? new TextSymbol(*textSym_in) : new TextSymbol();
         textSym->size() = (west-east).length() / 3.0;
 
-        TextSymbolizer ts( textSym );
+        TextSymbolizer ts(textSym.get());
         
         osg::Geode* textGeode = new osg::Geode();        
         osg::Drawable* d = ts.create(name);
@@ -351,11 +179,155 @@ UTMGraticule::buildGZDTile( const std::string& name, const GeoExtent& extent )
         group->addChild(mt);
     }
 
-    group = buildGZDChildren( group, name );
+    //group = buildGZDChildren( group, name );
 
     group = ClusterCullingFactory::createAndInstall( group, centerECEF )->asGroup();
 
     return group;
 }
 
+//---------------------------------------------------------------------------
+
+
+UTMGraticule::UTMGraticule() :
+VisibleLayer(&_optionsConcrete),
+_options(&_optionsConcrete)
+{
+    init();
+}
+
+UTMGraticule::UTMGraticule(const UTMGraticuleOptions& options) :
+VisibleLayer(&_optionsConcrete),
+_options(&_optionsConcrete),
+_optionsConcrete(options)
+{
+    init();
+}
+
+void
+UTMGraticule::dirty()
+{
+    rebuild();
+}
+
+void
+UTMGraticule::init()
+{
+    VisibleLayer::init();
+
+    // make the shared depth attr:
+    this->getOrCreateStateSet()->setMode(GL_DEPTH_TEST, 0);
+
+    // force it to render after the terrain.
+    this->getOrCreateStateSet()->setRenderBinDetails(1, "RenderBin");
+}
+
+void
+UTMGraticule::addedToMap(const Map* map)
+{
+    _map = map;
+    rebuild();
+}
+
+void
+UTMGraticule::removedFromMap(const Map* map)
+{
+    _map = 0L;
+}
+
+osg::Node*
+UTMGraticule::getOrCreateNode()
+{
+    if (_root.valid() == false)
+    {
+        _root = new osg::Group();
+
+        // install the range callback for clip plane activation
+        _root->addCullCallback( new RangeUniformCullCallback() );
+
+        rebuild();
+    }
+
+    return _root.get();
+}
 
+void
+UTMGraticule::rebuild()
+{
+    if (_root.valid() == false)
+        return;
+
+    osg::ref_ptr<const Map> map;
+    if (!_map.lock(map))
+        return;
+
+    // clear everything out
+    _root->removeChildren( 0, _root->getNumChildren() );
+
+    // requires a geocentric map
+    if ( !map->isGeocentric() )
+    {
+        OE_WARN << LC << "Projected map mode is not yet supported" << std::endl;
+        return;
+    }
+
+    const Profile* mapProfile = map->getProfile();
+
+    _profile = Profile::create(
+        mapProfile->getSRS(),
+        mapProfile->getExtent().xMin(),
+        mapProfile->getExtent().yMin(),
+        mapProfile->getExtent().xMax(),
+        mapProfile->getExtent().yMax(),
+        8, 4 );
+
+    _featureProfile = new FeatureProfile(_profile->getSRS());
+
+    //todo: do this right..
+    osg::StateSet* set = this->getOrCreateStateSet();
+    set->setMode( GL_LIGHTING, 0 );
+    set->setMode( GL_BLEND, 1 );
+
+    // set up default options if the caller did not supply them
+    if ( !options().gzdStyle().isSet() )
+    {
+        options().gzdStyle() = Style();
+
+        LineSymbol* line = options().gzdStyle()->getOrCreate<LineSymbol>();
+        line->stroke()->color() = Color::Gray;
+        line->stroke()->width() = 1.0;
+        line->tessellation() = 20;
+
+        TextSymbol* text = options().gzdStyle()->getOrCreate<TextSymbol>();
+        text->fill()->color() = Color(Color::White, 0.3f);
+        text->halo()->color() = Color(Color::Black, 0.2f);
+        text->alignment() = TextSymbol::ALIGN_CENTER_CENTER;
+    }
+    
+    // rebuild the graph:
+    osg::Group* top = _root.get();
+
+    // Horizon clipping plane.
+    osg::ClipPlane* cp = _clipPlane.get();
+    if ( cp == 0L )
+    {
+        osg::ClipNode* clipNode = new osg::ClipNode();
+        osgEarth::Registry::shaderGenerator().run( clipNode );
+        cp = new osg::ClipPlane( 0 );
+        clipNode->addClipPlane( cp );
+        _root->addChild(clipNode);
+        top = clipNode;
+    }
+    top->addCullCallback( new ClipToGeocentricHorizon(_profile->getSRS(), cp) );
+    
+    // intialize the UTM sector tables for this profile.
+    _utmData.rebuild(_profile.get());
+
+    // now build the lateral tiles for the GZD level.
+    for( UTMData::SectorTable::iterator i = _utmData.sectorTable().begin(); i != _utmData.sectorTable().end(); ++i )
+    {
+        osg::Node* tile = _utmData.buildGZDTile(i->first, i->second, options().gzdStyle().get(), _featureProfile.get(), map.get());
+        if ( tile )
+            _root->addChild( tile );
+    }
+}
diff --git a/src/osgEarthUtil/UTMLabelingEngine b/src/osgEarthUtil/UTMLabelingEngine
new file mode 100644
index 0000000..7ead458
--- /dev/null
+++ b/src/osgEarthUtil/UTMLabelingEngine
@@ -0,0 +1,79 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+* Copyright 2016 Pelican Mapping
+* http://osgearth.org
+*
+* osgEarth is free software; you can redistribute it and/or modify
+* it under the terms of the GNU Lesser General Public License as published by
+* the Free Software Foundation; either version 2 of the License, or
+* (at your option) any later version.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+* IN THE SOFTWARE.
+*
+* You should have received a copy of the GNU Lesser General Public License
+* along with this program.  If not, see <http://www.gnu.org/licenses/>
+*/
+#ifndef OSGEARTH_UTIL_UTM_LABELING_ENGINE_H
+#define OSGEARTH_UTIL_UTM_LABELING_ENGINE_H 1
+
+#include <osgEarthUtil/Common>
+#include <osgEarth/GeoData>
+#include <osgEarth/MapNode>
+#include <osgEarth/Containers>
+#include <osgEarthAnnotation/LabelNode>
+
+namespace osgEarth { namespace Util
+{
+    using namespace osgEarth;
+    using namespace osgEarth::Annotation;
+
+    /**
+     * Node that plots UTM coordinats labels along the edge of the
+     * viewport when you are looking straight down on a zoomed-in area.
+     */
+    class UTMLabelingEngine : public osg::Group
+    {
+    public:
+        //! Construct a new labeling engine with the map's SRS
+        UTMLabelingEngine(const SpatialReference* srs);
+
+        //! Sets the maximum resolution (meters) at which to render labels
+        void setMaxResolution(double value);
+
+    public: // osg::Node
+        void traverse(osg::NodeVisitor& nv);
+
+    protected:
+        typedef std::vector< osg::ref_ptr<LabelNode> > LabelNodeVector;
+
+        struct CameraData
+        {
+            LabelNodeVector xLabels;
+            LabelNodeVector yLabels;
+        };
+
+        bool cullTraverse(osgUtil::CullVisitor& nv, CameraData& data);
+        
+        typedef PerObjectFastMap<osg::Camera*, CameraData> CameraDataMap;
+        CameraDataMap _cameraDataMap;
+
+        struct AcceptCameraData : public CameraDataMap::Functor {
+            AcceptCameraData(osg::NodeVisitor& nv) : _nv(nv) { }
+            void operator()(CameraData& data);
+            osg::NodeVisitor& _nv;
+        };
+
+        osg::ref_ptr<const SpatialReference> _srs;
+        Style _xLabelStyle, _yLabelStyle;
+        double _maxRes;
+    };
+
+} } // namespace osgEarth::Util
+
+#endif // OSGEARTH_UTIL_UTM_LABELING_ENGINE_H
diff --git a/src/osgEarthUtil/UTMLabelingEngine.cpp b/src/osgEarthUtil/UTMLabelingEngine.cpp
new file mode 100644
index 0000000..81d3906
--- /dev/null
+++ b/src/osgEarthUtil/UTMLabelingEngine.cpp
@@ -0,0 +1,511 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+* Copyright 2016 Pelican Mapping
+* http://osgearth.org
+*
+* osgEarth is free software; you can redistribute it and/or modify
+* it under the terms of the GNU Lesser General Public License as published by
+* the Free Software Foundation; either version 2 of the License, or
+* (at your option) any later version.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+* IN THE SOFTWARE.
+*
+* You should have received a copy of the GNU Lesser General Public License
+* along with this program.  If not, see <http://www.gnu.org/licenses/>
+*/
+#include "UTMLabelingEngine"
+#include <osgEarth/GeoData>
+#include <osgEarth/TerrainEngineNode>
+#include <osg/CoordinateSystemNode>
+
+#define LC "[UTMLabelingEngine] "
+
+using namespace osgEarth;
+using namespace osgEarth::Util;
+
+#define MAX_LABELS 60
+
+namespace
+{
+    /**
+     * Utility class to perform ellipsoid intersections
+     * (Used by the UTMLabelingEngine)
+     * TODO: At some point this class can graduate to the core if generally useful,
+     * possibly extending osg::EllipsoidModel.
+     */
+    class EllipsoidIntersector
+    {
+    public:
+        //! Construct a new ellipsoid intersector
+        EllipsoidIntersector(const osg::EllipsoidModel* em)
+        {
+            _ellipsoidToUnitSphere.makeScale(
+                1.0 / em->getRadiusEquator(),
+                1.0 / em->getRadiusEquator(),
+                1.0 / em->getRadiusPolar());
+
+            _unitSphereToEllipsoid.makeScale(
+                em->getRadiusEquator(),
+                em->getRadiusEquator(),
+                em->getRadiusPolar());
+        }
+
+        //! Interects a line (world space) with an ellipsoid.
+        //! @param p0 First point on the line
+        //! @param p1 Second point on the line
+        //! @param out_world Output world coordinates of closest intersection
+        bool intersectLine(const osg::Vec3d& p0_world, const osg::Vec3d& p1_world, osg::Vec3d& out_world)
+        {
+            double dist2 = 0.0;
+            osg::Vec3d v;
+            osg::Vec3d p0 = p0_world * _ellipsoidToUnitSphere;
+            osg::Vec3d p1 = p1_world * _ellipsoidToUnitSphere;
+
+            const double R = 1.0; // for unit sphere.
+
+            // http://paulbourke.net/geometry/circlesphere/index.html#linesphere
+
+            osg::Vec3d d = p1 - p0;
+
+            double A = d * d;
+            double B = 2.0 * (d * p0);
+            double C = (p0 * p0) - R*R;
+
+            // now solve the quadratic A + B*t + C*t^2 = 0.
+            double D = B*B - 4.0*A*C;
+            if (D > 0)
+            {
+                // two roots (line passes through sphere twice)
+                // find the closer of the two.
+                double sqrtD = sqrt(D);
+                double t0 = (-B + sqrtD) / (2.0*A);
+                double t1 = (-B - sqrtD) / (2.0*A);
+
+                //seg; pick closest:
+                if (fabs(t0) < fabs(t1))
+                    v = d*t0;
+                else
+                    v = d*t1;
+            }
+            else if (D == 0.0)
+            {
+                // one root (line is tangent to sphere?)
+                double t = -B / (2.0*A);
+                v = d*t;
+            }
+
+            dist2 = v.length2();
+
+            if (dist2 > 0.0)
+            {
+                out_world = (p0 + v) * _unitSphereToEllipsoid;
+                return true;
+            }
+            else
+            {
+                // either no intersection, or the distance was not the max.
+                return false;
+            }
+        }
+
+    private:
+        osg::Matrix _ellipsoidToUnitSphere;
+        osg::Matrix _unitSphereToEllipsoid;
+        osg::Matrix _clipToWorld;
+    };
+
+
+    /**
+     * Utility class for perform operations in clip space
+     * (Used by the UTMLabelingEngine)
+     */
+    class ClipSpace
+    {
+    public:
+        osg::Matrix _worldToClip, _clipToWorld;
+
+        ClipSpace(const osg::Matrix& MVP, const osg::Matrix& MVPinv)
+            : _worldToClip(MVP),
+            _clipToWorld(MVPinv)
+        {
+            //nop
+        }
+
+        // Moves the input point to the bottom edge of the viewport.
+        void clampToBottom(GeoPoint& p)
+        {
+            p.transformInPlace(p.getSRS()->getGeographicSRS());
+            osg::Vec3d world;
+            p.toWorld(world);
+            osg::Vec3d clip = world * _worldToClip;
+            clip.y() = -1.0;
+            world = clip * _clipToWorld;
+            p.fromWorld(p.getSRS(), world);
+        }
+
+        // Moves the input point to left edge of the viewport.
+        void clampToLeft(GeoPoint& p)
+        {
+            p.transformInPlace(p.getSRS()->getGeographicSRS());
+            osg::Vec3d world;
+            p.toWorld(world);
+            osg::Vec3d clip = world * _worldToClip;
+            clip.x() = -1.0;
+            world = clip * _clipToWorld;
+            p.fromWorld(p.getSRS(), world);
+        }
+    };
+
+
+    // Information for a single UTM zone. The labeling engine supports
+    // two UTM zones (left and right) at a time.
+    struct UTMZone
+    {
+        osg::ref_ptr<const SpatialReference> utmSRS;
+        GeoPoint UL_geo, LL_geo, LR_geo;
+        GeoPoint UL_utm, LL_utm, LR_utm;
+    };
+
+
+    // Given a view matrix, return the heading of the camera relative to North;
+    // this works for geocentric maps.
+    // TODO: graduate to a utilities class somewhere in the core if generally useful
+    double getCameraHeading(const osg::Matrix& VM)
+    {
+        osg::Matrixd VMinverse;
+        VMinverse.invert(VM);
+
+        osg::Vec3d N(0, 0, 6356752); // north pole, more or less
+        osg::Vec3d b(-VM(0, 2), -VM(1, 2), -VM(2, 2)); // look vector
+        osg::Vec3d E = osg::Vec3d(0, 0, 0)*VMinverse;
+        osg::Vec3d u = E; u.normalize();
+
+        // account for looking straight downish
+        if (osg::equivalent(b*u, -1.0, 1e-4))
+        {
+            // up vec becomes the look vec.
+            b = osg::Matrixd::transform3x3(VM, osg::Vec3f(0.0, 1.0, 0.0));
+            b.normalize();
+        }
+
+        osg::Vec3d proj_d = b - u*(b*u);
+        osg::Vec3d n = N - E;
+        osg::Vec3d proj_n = n - u*(n*u);
+        osg::Vec3d proj_e = proj_n^u;
+
+        double cameraHeading = atan2(proj_e*proj_d, proj_n*proj_d);
+        return cameraHeading;
+    }
+}
+
+//........................................................................
+
+UTMLabelingEngine::UTMLabelingEngine(const SpatialReference* srs) :
+_maxRes(1.0)
+{
+    _srs = srs;
+
+    // Set up the symbology for x-axis labels
+    TextSymbol* xText = _xLabelStyle.getOrCreate<TextSymbol>();
+    xText->alignment() = TextSymbol::ALIGN_CENTER_BOTTOM;
+    xText->halo()->color().set(0, 0, 0, 1);
+    xText->declutter() = false;
+
+    // Set up the symbology for y-axis labels
+    TextSymbol* yText = _yLabelStyle.getOrCreate<TextSymbol>();
+    yText->alignment() = TextSymbol::ALIGN_LEFT_BOTTOM;
+    yText->halo()->color().set(0, 0, 0, 1);
+    yText->declutter() = false;
+}
+
+void
+UTMLabelingEngine::setMaxResolution(double value)
+{
+    _maxRes = std::max(value, 1.0);
+    OE_INFO << LC << "Max resolution = " << _maxRes << std::endl;
+}
+
+void
+UTMLabelingEngine::AcceptCameraData::operator()(UTMLabelingEngine::CameraData& data)
+{
+    for (LabelNodeVector::iterator i = data.xLabels.begin(); i != data.xLabels.end(); ++i)
+        i->get()->accept(_nv);
+
+    for (LabelNodeVector::iterator i = data.yLabels.begin(); i != data.yLabels.end(); ++i)
+        i->get()->accept(_nv);
+}
+
+void
+UTMLabelingEngine::traverse(osg::NodeVisitor& nv)
+{
+    if (nv.getVisitorType() == nv.CULL_VISITOR)
+    {
+        osgUtil::CullVisitor* cv = dynamic_cast<osgUtil::CullVisitor*>(&nv);
+        if (cv)
+        {
+            // Find the data corresponding to this camera:
+            CameraData& data = _cameraDataMap.get(cv->getCurrentCamera());
+            bool visible = cullTraverse(*cv, data);
+            if (visible)
+            {
+                // traverse all the labels for this camera:
+                AcceptCameraData accept(nv);
+                accept(data);
+            }
+        }
+    }
+    else
+    {
+        AcceptCameraData accept(nv);
+        _cameraDataMap.forEach(accept);
+    }
+    
+    osg::Group::traverse(nv);
+}
+
+bool
+UTMLabelingEngine::cullTraverse(osgUtil::CullVisitor& nv, CameraData& data)
+{
+    osg::Camera* cam = nv.getCurrentCamera();
+
+    // Don't draw the labels if we are too far from North-Up:
+    double heading = getCameraHeading(cam->getViewMatrix());
+    if (osg::RadiansToDegrees(fabs(heading)) > 7.0)
+        return false;
+
+    // Initialize the label pool for this camera if we have not done so:
+    if (data.xLabels.empty())
+    {
+        for (unsigned i = 0; i < MAX_LABELS; ++i)
+        {
+            LabelNode* label = new LabelNode();
+            label->setDynamic(true);
+            label->setStyle(_xLabelStyle);
+            label->setHorizonCulling(false);
+            label->setOcclusionCulling(false);
+            data.xLabels.push_back(label);
+        }
+
+        for (unsigned i = 0; i < MAX_LABELS; ++i)
+        {
+            LabelNode* label = new LabelNode();
+            label->setDynamic(true);
+            label->setStyle(_yLabelStyle);
+            label->setHorizonCulling(false);
+            label->setOcclusionCulling(false);
+            data.yLabels.push_back(label);
+        }
+    }
+
+    // Start out with all labels off. We will then turn back on the ones we use:
+    for (unsigned i = 0; i < MAX_LABELS; ++i)
+    {
+        data.xLabels[i]->setNodeMask(0);
+        data.yLabels[i]->setNodeMask(0);
+    }
+
+    if (_maxRes > 10000.0)
+        return false;
+
+    // Intersect the corners of the view frustum with the ellipsoid.
+    // This will yeild the approximate geo-extent of the view.
+    // TODO: graduate this to the core if generally useful - could be helpful
+    // for displaying the extent of the current view.
+
+    // Calculate the "clip to world" matrix = MVPinv.
+    osg::Matrix MVP = (*nv.getModelViewMatrix()) * cam->getProjectionMatrix();
+    osg::Matrix MVPinv;
+    MVPinv.invert(MVP);
+
+    EllipsoidIntersector ellipsoid(_srs->getEllipsoid());
+
+    // For each corner, transform the clip coordinates at the near and far
+    // planes into world space and intersect that line with the ellipsoid:
+    osg::Vec3d p0, p1;
+
+    // find the lower-left corner of the frustum:
+    osg::Vec3d LL_world;
+    p0 = osg::Vec3d(-1, -1, -1) * MVPinv;
+    p1 = osg::Vec3d(-1, -1, +1) * MVPinv;
+    bool LL_ok = ellipsoid.intersectLine(p0, p1, LL_world);   
+    if (!LL_ok)
+        return false;
+
+    // find the upper-left corner of the frustum:
+    osg::Vec3d UL_world;
+    p0 = osg::Vec3d(-1, +1, -1) * MVPinv;
+    p1 = osg::Vec3d(-1, +1, +1) * MVPinv;
+    bool UL_ok = ellipsoid.intersectLine(p0, p1, UL_world);
+    if (!UL_ok)
+        return false;
+
+    // find the lower-right corner of the frustum:
+    osg::Vec3d LR_world;
+    p0 = osg::Vec3d(+1, -1, -1) * MVPinv;
+    p1 = osg::Vec3d(+1, -1, +1) * MVPinv;
+    bool LR_ok = ellipsoid.intersectLine(p0, p1, LR_world);
+    if (!LR_ok)
+        return false;
+
+    // Split the view into (at most) 2 UTM zones. 
+    UTMZone left, right;
+
+    left.LL_geo.fromWorld(_srs.get(), LL_world);
+    left.utmSRS = _srs->createUTMFromLonLat(left.LL_geo.x(), left.LL_geo.y());
+    if (left.utmSRS.valid() == false)
+        return false;
+
+    left.UL_geo.fromWorld(_srs.get(), UL_world);
+
+    right.LR_geo.fromWorld(_srs.get(), LR_world);
+    right.utmSRS = _srs->createUTMFromLonLat(right.LR_geo.x(), right.LR_geo.y());
+    if (right.utmSRS.valid() == false)
+        return false;
+
+    bool split = left.utmSRS->isHorizEquivalentTo(right.utmSRS.get()) == false;
+
+    if (split)
+    {
+        // Calculate the longitude of the on-screen zone boundary and fill in the UTMZone values.
+        double splitLon = (::floor(left.LL_geo.x() / 6.0) + 1.0) * 6.0;
+        left.LR_geo.set(_srs.get(), splitLon, left.LL_geo.y(), 0, ALTMODE_ABSOLUTE);
+        right.LL_geo = left.LR_geo;
+        right.UL_geo.set(_srs.get(), splitLon, left.UL_geo.y(), 0, ALTMODE_ABSOLUTE);
+    }
+    else
+    {
+        left.LR_geo = right.LR_geo;
+    }
+
+    left.LR_utm = left.LR_geo.transform(left.utmSRS.get());
+    left.LL_utm = left.LL_geo.transform(left.utmSRS.get());
+    left.UL_utm = left.UL_geo.transform(left.utmSRS.get());
+
+    if (left.LR_utm.isValid() == false ||
+        left.LL_utm.isValid() == false ||
+        left.UL_utm.isValid() == false)
+    {
+        OE_WARN << "Bail: left has invalid coords" << std::endl;
+        return false;
+    }
+
+    if (split)
+    {
+        right.UL_utm = right.UL_geo.transform(right.utmSRS.get());
+        right.LL_utm = right.LL_geo.transform(right.utmSRS.get());
+        right.LR_utm = right.LR_geo.transform(right.utmSRS.get());
+
+        //OE_NOTICE << "Right LL = " << right.LL_utm.toString() << std::endl;
+        if (right.UL_utm.isValid() == false ||
+            right.LL_utm.isValid() == false ||
+            right.LR_utm.isValid() == false)
+        {
+            split = false;
+        }
+    }
+
+    // Vertical extent of the frustum in meters:
+    double utmDiff = left.LL_utm.distanceTo(left.UL_utm);
+
+    // Determine the label interval based on the extent.
+    // These numbers are from trial-and-error.
+    double utmInterval;
+    if (utmDiff > 150000) return false;
+    else if (utmDiff > 18500) utmInterval = std::max(10000.0, _maxRes);
+    else if (utmDiff > 1750) utmInterval = std::max(1000.0, _maxRes);
+    else if (utmDiff > 170) utmInterval = std::max(100.0, _maxRes);
+    else utmInterval = std::max(10.0, _maxRes);
+
+    //OE_NOTICE << "utmDiff=" << utmDiff << ", utmInterval=" << utmInterval << std::endl;
+    
+    // Use this for clamping geopoints to the edges of the frustum:
+    ClipSpace window(MVP, MVPinv); 
+
+    // Indicies into the label pool
+    unsigned xi = 0, yi = 0;
+
+    // Finally, calculate all label positions and update them.
+    // NOTE: It is safe to do this in the CULL traversal since all labels are
+    // dynamic variance AND since all labels are children of this node.
+
+    // LEFT zone:
+    {
+        // Quantize the start location(s) to the interval:
+        double xStart = utmInterval * ::ceil(left.LL_utm.x() / utmInterval);
+
+        unsigned numLabels = left.LL_utm.distanceTo(left.LR_utm) / utmInterval;
+        if (numLabels < 2) numLabels = 2;
+
+        // For now lets just draw 10 labels. Later we'll figure out the proper scale
+        for (unsigned i = 0; i < numLabels && xi < MAX_LABELS; ++i, ++xi)
+        {
+            double t = (double)i / (double)(numLabels - 1);
+            double x = xStart + utmInterval * i;
+            double y = left.LL_utm.y();
+            GeoPoint p(left.utmSRS.get(), x, y, 0, ALTMODE_ABSOLUTE);
+            int xx = ((int)x % 100000) / utmInterval;
+            window.clampToBottom(p); // also xforms to geographic
+            if (p.y() < 84.0 && p.y() > -80.0)
+            {
+                data.xLabels[xi]->setPosition(p);
+                data.xLabels[xi]->setText(Stringify() << std::setprecision(8) << xx);
+                data.xLabels[xi]->setNodeMask(~0);
+            }
+        }
+        
+        double yStart = utmInterval * ::ceil(left.LL_utm.y() / utmInterval);
+
+        numLabels = left.LL_utm.distanceTo(left.UL_utm) / utmInterval;
+        if (numLabels < 2) numLabels = 2;
+
+        for (unsigned i = 0; i < numLabels && yi < MAX_LABELS; ++i, ++yi)
+        {
+            double t = (double)i / (double)(numLabels - 1);
+            double x = left.LL_utm.x();
+            double y = yStart + utmInterval * i;
+            int yy = ((10000000 + (int)y) % 100000) / utmInterval;
+            GeoPoint p(left.utmSRS.get(), x, y, 0, ALTMODE_ABSOLUTE);
+            window.clampToLeft(p); // also xforms to geographic
+            if (p.y() < 84.0 && p.y() > -80.0)
+            {
+                data.yLabels[yi]->setPosition(p);
+                data.yLabels[yi]->setText(Stringify() << std::setprecision(8) << yy);
+                data.yLabels[yi]->setNodeMask(~0);
+            }
+        }
+    }
+
+    // RIGHT zone, if we are split:
+    if (split)
+    {
+        double xStart = utmInterval * ::ceil(right.LL_utm.x() / utmInterval);
+        //double yStart = utmInterval * ::ceil(right.LL_utm.y() / utmInterval);
+
+        unsigned numLabels = right.LL_utm.distanceTo(right.LR_utm) / utmInterval;
+        if (numLabels < 2) numLabels = 2;
+
+        for (unsigned i = 0; i < numLabels && xi < MAX_LABELS; ++i, ++xi)
+        {
+            double t = (double)i / (double)(numLabels - 1);
+            double x = xStart + utmInterval * i;
+            double y = right.LL_utm.y();
+            GeoPoint p(right.utmSRS.get(), x, y, 0, ALTMODE_ABSOLUTE);
+            int xx = ((int)x % 100000) / utmInterval;
+            window.clampToBottom(p); // also xforms to geographic
+            if (p.y() < 84.0 && p.y() > -80.0)
+            {
+                data.xLabels[xi]->setPosition(p);
+                data.xLabels[xi]->setText(Stringify() << std::setprecision(8) << xx);
+                data.xLabels[xi]->setNodeMask(~0);
+            }
+        }
+    }
+
+    return true;
+}
diff --git a/src/osgEarthUtil/ViewFitter b/src/osgEarthUtil/ViewFitter
new file mode 100644
index 0000000..a5d357b
--- /dev/null
+++ b/src/osgEarthUtil/ViewFitter
@@ -0,0 +1,65 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+ * Copyright 2016 Pelican Mapping
+ * http://osgearth.org
+ *
+ * osgEarth is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+#ifndef OSGEARTH_UTIL_VIEW_FITTER_H
+#define OSGEARTH_UTIL_VIEW_FITTER_H
+
+#include <osgEarthUtil/Common>
+#include <osgEarth/GeoData>
+#include <osgEarth/Viewpoint>
+#include <osg/Camera>
+
+namespace osgEarth { namespace Util
+{
+    using namespace osgEarth;
+
+    /**
+     * Creates Viewpoints that fit a camera's view frustum to encompass
+     * a set of geospatial points as tightly as possible.
+     */
+    class OSGEARTHUTIL_EXPORT ViewFitter
+    {
+    public:
+        //! Construct a ViewFitter with a Map SRS and a camera.
+        ViewFitter(const SpatialReference* mapSRS, const osg::Camera* camera);
+
+        //! Creates a Viewpoint that looks straight down on the map and
+        //! encompasses the provided set of points.
+        //! Returns true upon success, or false if there is missing data or if
+        //! the camera is orthographic.
+        bool createViewpoint(const std::vector<GeoPoint>& points, Viewpoint& out) const;
+
+        //! Sets a buffer (in meters) to apply to the view fit. Applying a buffer will
+        //! expand the view so that the points are at least "buffer" meters inside the
+        //! edge of the fitted view.
+        void setBuffer(double value_meters) { _buffer_m = value_meters; }
+        double getBuffer() const { return _buffer_m; }
+
+        //! Sets the reference VFOV when using an orthographic camera.
+        void setReferenceVFOV(float vfov_degrees);
+
+    protected:
+        osg::ref_ptr<const osg::Camera> _camera;
+        osg::ref_ptr<const SpatialReference> _mapSRS;
+        float _vfov;
+        double _buffer_m;
+    };
+
+} } // namespace osgEarth::Util
+
+#endif // OSGEARTH_UTIL_VIEW_FITTER_H
diff --git a/src/osgEarthUtil/ViewFitter.cpp b/src/osgEarthUtil/ViewFitter.cpp
new file mode 100644
index 0000000..02c5ca0
--- /dev/null
+++ b/src/osgEarthUtil/ViewFitter.cpp
@@ -0,0 +1,230 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+ * Copyright 2016 Pelican Mapping
+ * http://osgearth.org
+ *
+ * osgEarth is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+#include <osgEarthUtil/ViewFitter>
+
+#define LC "[ViewFitter] "
+
+using namespace osgEarth;
+using namespace osgEarth::Util;
+
+namespace
+{
+    // Projected the point Pview (in camera view space) onto the far clip plane of
+    // a projection matrix.
+    void projectToFarPlane(osg::Vec3d& Pview, const osg::Matrix& projMatrix, const osg::Matrix& projMatrixInv)
+    {
+        osg::Vec4d Pclip = osg::Vec4d(Pview.x(), Pview.y(), Pview.z(), 1.0)* projMatrix;
+        Pclip.z() = Pclip.w();
+        osg::Vec4d Ptemp = Pclip * projMatrixInv;
+        Pview.set(Ptemp.x() / Ptemp.w(), Ptemp.y() / Ptemp.w(), Ptemp.z() / Ptemp.w());
+    }
+
+    double mix(double a, double b, double t)
+    {
+        return a*(1.0-t) + b*t;
+    }
+}
+
+ViewFitter::ViewFitter(const SpatialReference* mapSRS, const osg::Camera* camera) :
+_mapSRS(mapSRS),
+_camera(camera),
+_vfov(30.0f),
+_buffer_m(0.0)
+{
+    //nop
+}
+
+bool
+ViewFitter::createViewpoint(const std::vector<GeoPoint>& points, Viewpoint& outVP) const
+{
+    if (points.empty() || _mapSRS.valid() == false || _camera.valid() == false)
+        return false;
+
+    osg::Matrix projMatrix = _camera->getProjectionMatrix();
+    osg::Matrix viewMatrix = _camera->getViewMatrix();
+
+    bool isPerspective = !osg::equivalent(projMatrix(3,3), 1.0);
+
+    // Convert the point set to world space:
+    std::vector<osg::Vec3d> world(points.size());
+
+    // Collect the extent so we can calculate the centroid.
+    GeoExtent extent(_mapSRS.get());
+
+    for (int i = 0; i < points.size(); ++i)
+    {
+        // force absolute altitude mode - we don't care about clamping here
+        GeoPoint p = points[i];
+        p.z() = 0;
+        p.altitudeMode() = ALTMODE_ABSOLUTE;
+
+        // transform to the map's srs and then to world coords.
+        p = p.transform(_mapSRS.get());
+        p.toWorld(world[i]);
+
+        extent.expandToInclude(p.x(), p.y());
+    }
+    
+    double eyeDist;
+    double fovy_deg, ar;
+    double zfar;
+
+    // Calculate the centroid, which will become the focal point of the view:
+    GeoPoint centroidMap;
+    extent.getCentroid(centroidMap);
+
+    osg::Vec3d centroid;
+    centroidMap.toWorld(centroid);
+
+    if (isPerspective)
+    {
+        // For a perspective matrix, rewrite the projection matrix so 
+        // the far plane is the radius of the ellipsoid. We do this so
+        // we can project our control points onto a common plane.
+        double znear;
+        projMatrix.getPerspective(fovy_deg, ar, znear, zfar);
+        znear = 1.0;
+
+        if (_mapSRS->isGeographic())
+        {
+            osg::Vec3d C = centroid;
+            C.normalize();
+            C.z() = fabs(C.z());
+            double t = C * osg::Vec3d(0,0,1); // dot product
+
+            zfar = mix(_mapSRS->getEllipsoid()->getRadiusEquator(),
+                       _mapSRS->getEllipsoid()->getRadiusPolar(),
+                       t);
+            eyeDist = zfar * 2.0;
+        }
+        else
+        {
+            osg::Vec3d eye, center, up2;
+            viewMatrix.getLookAt(eye, center, up2);
+            eyeDist = eye.length();
+            zfar = eyeDist;
+        }
+
+        projMatrix.makePerspective(fovy_deg, ar, znear, zfar);
+    }
+
+    else // isOrtho
+    {
+        fovy_deg = _vfov;
+        double L, R, B, T, N, F;
+        projMatrix.getOrtho(L, R, B, T, N, F);
+        ar = (R - L) / (T - B);
+
+        if (_mapSRS->isGeographic())
+        {
+            osg::Vec3d C = centroid;
+            C.normalize();
+            C.z() = fabs(C.z());
+            double t = C * osg::Vec3d(0,0,1); // dot product
+
+            zfar = mix(_mapSRS->getEllipsoid()->getRadiusEquator(),
+                       _mapSRS->getEllipsoid()->getRadiusPolar(),
+                       t);
+            eyeDist = zfar * 2.0;
+        }
+        else
+        {
+            osg::Vec3d eye, center, up2;
+            viewMatrix.getLookAt(eye, center, up2);
+            eyeDist = eye.length();
+            zfar = eyeDist;
+        }
+    }
+
+    // Set up a new view matrix to look down on that centroid:
+    osg::Vec3d lookAt, up;
+
+    osg::Vec3d lookFrom = centroid;
+
+    if (_mapSRS->isGeographic())
+    {
+        lookFrom.normalize();
+        lookFrom *= eyeDist;
+        lookAt = centroid;
+        up.set(0,0,1);
+    }
+    else
+    {
+        lookFrom.z() = eyeDist;
+        lookAt.set(lookFrom.x(), lookFrom.y(), 0);
+        up.set(0,1,0);
+    }
+    viewMatrix.makeLookAt(lookFrom, lookAt, up);
+
+    // Transform our control points into view space, and then project each one
+    // onto our common view plane (tangent to the ellispoid).
+    osg::Matrix projMatrixInv;
+    projMatrixInv.invert(projMatrix);
+
+    double Mx = -DBL_MAX, My = -DBL_MAX;
+    std::vector<osg::Vec3d> view(world.size());
+    for (int i = 0; i < world.size(); ++i)
+    {
+        // Transform into view space (camera-relative):
+        view[i] = world[i] * viewMatrix;
+
+        // For a perspective projection, we have to project each point
+        // on to the far clipping plane. No need to do this in orthographic
+        // since the X and Y would not change.
+        if (isPerspective)
+            projectToFarPlane(view[i], projMatrix, projMatrixInv);
+
+        Mx = osg::maximum(Mx, osg::absolute(view[i].x()));
+        My = osg::maximum(My, osg::absolute(view[i].y()));
+    }
+
+    // Apply the edge buffer:
+    Mx += _buffer_m;
+    My += _buffer_m;
+
+    // Calculate optimal new Z (distance from view plane)
+    double half_fovy_rad = osg::DegreesToRadians(fovy_deg) * 0.5;
+    double half_fovx_rad = half_fovy_rad * ar;
+    double Zx = Mx / tan(half_fovx_rad);
+    double Zy = My / tan(half_fovy_rad);
+    double Zbest = std::max(Zx, Zy);
+
+    // Calcluate the new viewpoint.
+    //osg::Vec3d FPworld = centroid;
+
+    //if (_mapSRS->isGeographic())
+    //{
+    //    FPworld.normalize();
+    //    FPworld *= zfar;
+    //}
+    //else
+    //{
+    //    FPworld.z() = 0.0;
+    //}
+
+    // Convert to a geopoint
+    GeoPoint FP;
+    FP.fromWorld(_mapSRS.get(), centroid);
+    outVP = Viewpoint();
+    outVP.focalPoint() = FP;
+    outVP.pitch() = -90;
+    outVP.range() = Zbest;
+
+    return true;
+}
\ No newline at end of file
diff --git a/src/osgEarthUtil/WMS.cpp b/src/osgEarthUtil/WMS.cpp
index eb6407a..e945861 100644
--- a/src/osgEarthUtil/WMS.cpp
+++ b/src/osgEarthUtil/WMS.cpp
@@ -23,6 +23,8 @@
 #include <osgDB/FileNameUtils>
 #include <osgDB/FileUtils>
 
+#include <algorithm>
+
 using namespace osgEarth;
 using namespace osgEarth::Util;
 using namespace std;
@@ -221,6 +223,20 @@ readLayers(XmlElement* e, WMSLayer* parentLayer, WMSLayer::LayerList& layers)
             layer->getSpatialReferences().push_back(crs);
         }
 
+        if (parentLayer)
+        {
+            // Also add in any SRS that is defined in the parent layer.  Some servers, like GeoExpress from LizardTech will publish top level SRS's that also apply to the child layers
+            for (WMSLayer::SRSList::iterator itr = parentLayer->getSpatialReferences().begin(); itr != parentLayer->getSpatialReferences().end(); itr++)
+            {
+                std::string parentSRS = *itr;
+                // Only add the SRS if it's not already present in the SRS list.
+                if ( std::find(layer->getSpatialReferences().begin(), layer->getSpatialReferences().end(), parentSRS) == layer->getSpatialReferences().end() )
+                {
+                    layer->getSpatialReferences().push_back( parentSRS );
+                }
+            }
+        }
+
         osg::ref_ptr<XmlElement> e_bb = e_layer->getSubElement( ELEM_LATLONBOUNDINGBOX );
         if (e_bb.valid())
         {
diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt
new file mode 100644
index 0000000..b823eba
--- /dev/null
+++ b/src/tests/CMakeLists.txt
@@ -0,0 +1,31 @@
+PROJECT(OSGEARTH_TESTS)
+
+SET(OSGCORE_BUNDLED TRUE)
+
+IF(NOT OSGCORE_BUNDLED)
+	FIND_PACKAGE(OSGCORE)
+	IF(OSGCORE_FOUND)
+		INCLUDE(${OSGCORE_USE_FILE})
+	ELSE(OSGCORE_FOUND)
+		MESSAGE(ERROR "OSGCORE neeeded but NOT FOUND")
+	ENDIF(OSGCORE_FOUND)
+	SET(CMAKE_MODULE_PATH  ${PROJECT_SOURCE_DIR}/../../Macros)
+ENDIF(NOT OSGCORE_BUNDLED)
+SET(OPENSCENEGRAPH_APPLICATION_DIR ${PROJECT_SOURCE_DIR})
+
+
+#OpenThreads, osg, osgDB and osgUtil are included elsewhere.
+SET(TARGET_COMMON_LIBRARIES
+    osgEarth
+    osgEarthFeatures
+    osgEarthUtil
+    osgEarthSymbology
+    osgEarthAnnotation
+)
+
+SET(TARGET_DEFAULT_PREFIX "test_")
+SET(TARGET_DEFAULT_APPLICATION_FOLDER "Tests")
+
+
+enable_testing()
+ADD_SUBDIRECTORY(osgEarth_tests)
\ No newline at end of file
diff --git a/src/tests/osgEarth_tests/CMakeLists.txt b/src/tests/osgEarth_tests/CMakeLists.txt
new file mode 100644
index 0000000..b1b79ec
--- /dev/null
+++ b/src/tests/osgEarth_tests/CMakeLists.txt
@@ -0,0 +1,17 @@
+INCLUDE_DIRECTORIES(${OSG_INCLUDE_DIRS} )
+SET(TARGET_LIBRARIES_VARS OSG_LIBRARY OSGDB_LIBRARY OSGUTIL_LIBRARY OSGVIEWER_LIBRARY OPENTHREADS_LIBRARY)
+
+SET(TARGET_SRC
+    main.cpp
+    EndianTests.cpp
+    GeoExtentTests.cpp
+    FeatureTests.cpp
+    ImageLayerTests.cpp
+    SpatialReferenceTests.cpp
+    ThreadingTests.cpp
+    )
+
+#### end var setup  ###
+SETUP_APPLICATION(osgEarth_tests)
+
+add_test(NAME osgEarth_tests COMMAND osgEarth_tests)
\ No newline at end of file
diff --git a/src/tests/osgEarth_tests/EndianTests.cpp b/src/tests/osgEarth_tests/EndianTests.cpp
new file mode 100644
index 0000000..3fe471d
--- /dev/null
+++ b/src/tests/osgEarth_tests/EndianTests.cpp
@@ -0,0 +1,95 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+* Copyright 2016 Pelican Mapping
+* http://osgearth.org
+*
+* osgEarth is free software; you can redistribute it and/or modify
+* it under the terms of the GNU Lesser General Public License as published by
+* the Free Software Foundation; either version 2 of the License, or
+* (at your option) any later version.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+* IN THE SOFTWARE.
+*
+* You should have received a copy of the GNU Lesser General Public License
+* along with this program.  If not, see <http://www.gnu.org/licenses/>
+*/
+#include <cmath>
+#include <osgEarth/catch.hpp>
+#include <osgEarth/Endian>
+
+namespace {
+
+/** Returns true if two floats are equal within a given epsilon. */
+bool floatAreEqual(float v1, float v2, double epsilon)
+{
+  return static_cast<double>(fabs(v2 - v1)) <= epsilon;
+}
+
+/** Returns true if two doubles are equal within a given epsilon. */
+bool doubleAreEqual(double v1, double v2, double epsilon)
+{
+  return fabs(v2 - v1) <= epsilon;
+}
+
+/** Returns true if the system is detected as little endian */
+bool systemIsLittleEndian()
+{
+  // Source: https://stackoverflow.com/questions/4181951
+  int n = 1;
+  return (*(char*)&n == 1);
+}
+
+}
+
+TEST_CASE( "OE_ENCODE_FLOAT equality tests" ) {
+    // Test for big endian systems has not been developed.
+    if (!systemIsLittleEndian())
+        return;
+    REQUIRE(OE_ENCODE_FLOAT(0.f) == 0);
+    REQUIRE(OE_ENCODE_FLOAT(1.f) == 32831);
+    REQUIRE(OE_ENCODE_FLOAT(-1.f) == 32959);
+    REQUIRE(OE_ENCODE_FLOAT(100.5f) == 51522);
+    REQUIRE(OE_ENCODE_FLOAT(1.83e11f) == 2590911058);
+}
+
+TEST_CASE( "OE_ENCODE_DOUBLE equality tests" ) {
+    // Test for big endian systems has not been developed.
+    if (!systemIsLittleEndian())
+        return;
+    REQUIRE(OE_ENCODE_DOUBLE(0.) == 0);
+    REQUIRE(OE_ENCODE_DOUBLE(1.) == 61503);
+    REQUIRE(OE_ENCODE_DOUBLE(-1.) == 61631);
+    REQUIRE(OE_ENCODE_DOUBLE(100.5) == 2120000);
+    REQUIRE(OE_ENCODE_DOUBLE(1.83e11) == 222588388674);
+}
+
+TEST_CASE( "OE_DECODE_FLOAT equality tests" ) {
+    // Test for big endian systems has not been developed.
+    if (!systemIsLittleEndian())
+        return;
+    REQUIRE(floatAreEqual(OE_DECODE_FLOAT(0), 0.f, 1e-038));
+    REQUIRE(floatAreEqual(OE_DECODE_FLOAT(1), 2.35099e-038f, 1e-042));
+    REQUIRE(floatAreEqual(OE_DECODE_FLOAT(13784), -7.96046e+014f, 1e+10));
+    REQUIRE(floatAreEqual(OE_DECODE_FLOAT(0x12345678), 1.73782e+034f, 1e+29));
+    REQUIRE(floatAreEqual(OE_DECODE_FLOAT(0x78563412), 5.69046e-028f, 1e-32));
+    REQUIRE(floatAreEqual(OE_DECODE_FLOAT(0xf8393841), 11.5142f, 1e-04));
+}
+
+TEST_CASE( "OE_DECODE_DOUBLE equality tests" ) {
+    // Test for big endian systems has not been developed.
+    if (!systemIsLittleEndian())
+        return;
+    REQUIRE(doubleAreEqual(OE_DECODE_DOUBLE(0), 0.f, 1e-308));
+    REQUIRE(doubleAreEqual(OE_DECODE_DOUBLE(1), 7.29112e-304, 1e-308));
+    REQUIRE(doubleAreEqual(OE_DECODE_DOUBLE(13784), -8.27442e+116, 1e+112));
+    REQUIRE(doubleAreEqual(OE_DECODE_DOUBLE(0x12345678), 4.69197e+271, 1e+267));
+    REQUIRE(doubleAreEqual(OE_DECODE_DOUBLE(0x78563412), 5.62635e-221, 1e-225));
+    REQUIRE(doubleAreEqual(OE_DECODE_DOUBLE(0x123456789abcdef0), -4.88646e+235, 1e+231));
+    REQUIRE(doubleAreEqual(OE_DECODE_DOUBLE(0xf23456789abcde01), 1.14742e-299, 1e-304));
+}
diff --git a/src/tests/osgEarth_tests/FeatureTests.cpp b/src/tests/osgEarth_tests/FeatureTests.cpp
new file mode 100644
index 0000000..a769a1d
--- /dev/null
+++ b/src/tests/osgEarth_tests/FeatureTests.cpp
@@ -0,0 +1,53 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+* Copyright 2016 Pelican Mapping
+* http://osgearth.org
+*
+* osgEarth is free software; you can redistribute it and/or modify
+* it under the terms of the GNU Lesser General Public License as published by
+* the Free Software Foundation; either version 2 of the License, or
+* (at your option) any later version.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+* IN THE SOFTWARE.
+*
+* You should have received a copy of the GNU Lesser General Public License
+* along with this program.  If not, see <http://www.gnu.org/licenses/>
+*/
+
+#include <osgEarth/catch.hpp>
+
+#include <osgEarthFeatures/Feature>
+#include <osgEarthFeatures/GeometryUtils>
+
+using namespace osgEarth;
+using namespace osgEarth::Symbology;
+using namespace osgEarth::Features;
+
+TEST_CASE("Feature::splitAcrossDateLine doesn't modify features that don't cross the dateline") {
+    osg::ref_ptr< Feature > feature = new Feature(GeometryUtils::geometryFromWKT("POLYGON((-81 26, -40.5 45, -40.5 75.5, -81 60))"), osgEarth::SpatialReference::create("wgs84"));
+    FeatureList features;
+    feature->splitAcrossDateLine(features);
+    // We only have one feature in the list.
+    REQUIRE( features.size() == 1 );
+    // The feature is exactly the same feature that was passed in
+    REQUIRE(features.front().get() == feature.get());
+}
+
+TEST_CASE("Feature::splitAcrossDateLine works") {
+    osg::ref_ptr< Feature > feature = new Feature(GeometryUtils::geometryFromWKT("POLYGON((170 26, 190 26, 190 56, 170 56))"), osgEarth::SpatialReference::create("wgs84"));
+    FeatureList features;
+    feature->splitAcrossDateLine(features);
+    // We have two features in the list
+    REQUIRE(features.size() == 2);
+    // The features don't cross the anti-meridian
+    for (FeatureList::iterator itr = features.begin(); itr != features.end(); ++itr)
+    {
+        REQUIRE_FALSE(itr->get()->getExtent().crossesAntimeridian());
+    }    
+}
diff --git a/src/tests/osgEarth_tests/GeoExtentTests.cpp b/src/tests/osgEarth_tests/GeoExtentTests.cpp
new file mode 100644
index 0000000..23afd1b
--- /dev/null
+++ b/src/tests/osgEarth_tests/GeoExtentTests.cpp
@@ -0,0 +1,238 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+* Copyright 2016 Pelican Mapping
+* http://osgearth.org
+*
+* osgEarth is free software; you can redistribute it and/or modify
+* it under the terms of the GNU Lesser General Public License as published by
+* the Free Software Foundation; either version 2 of the License, or
+* (at your option) any later version.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+* IN THE SOFTWARE.
+*
+* You should have received a copy of the GNU Lesser General Public License
+* along with this program.  If not, see <http://www.gnu.org/licenses/>
+*/
+
+#include <osgEarth/catch.hpp>
+
+#include <osgEarth/GeoData>
+
+using namespace osgEarth;
+
+TEST_CASE( "GeoExtent" ) {
+    
+    const SpatialReference* WGS84 = SpatialReference::get("wgs84");
+    const SpatialReference* CUBE = SpatialReference::get("unified-cube");
+    
+    SECTION("Create an extent that crosses the antimeridian") {
+        GeoExtent ext(SpatialReference::create("wgs84"), 178, 30, 183.4, 34.5);
+        REQUIRE(ext.crossesAntimeridian());
+    
+        // Transform it and make sure it still crosses the antimeridian.
+        GeoExtent transformed;
+        ext.transform(SpatialReference::create("wgs84"), transformed);
+        REQUIRE(transformed.crossesAntimeridian());
+    }
+
+    SECTION("GeoExtent contains simple") {
+        GeoExtent ext(WGS84, -10.0, -10.0, 10.0, 10.0);
+        REQUIRE(ext.contains(5.0, 5.0));
+    }
+
+    SECTION( "GeoExtent contains works when the extent cross the antimeridian" ) {
+        GeoExtent ext(WGS84, -180.001, -90.0, 179.995, 90.0);
+        REQUIRE(ext.contains(5.0, 0.0));
+    }
+
+    SECTION("GeoExtent contains a point on the boundary") {
+        GeoExtent ext(WGS84, -100, -80, 100, 80);
+        REQUIRE(ext.contains(-100, -80));
+    }
+
+    SECTION("expandToInclude is a noop when point is within the extent") {
+        GeoExtent ext(WGS84, -10.0, -10.0, 10.0, 10.0);          
+        ext.expandToInclude(0.0, 0.0);
+        REQUIRE(ext == GeoExtent(WGS84, -10.0, -10.0, 10.0, 10.0));
+    }
+
+    SECTION("expandToInclude expand to the east and north") {
+        GeoExtent ext(WGS84, -10.0, -10.0, 10.0, 10.0);          
+        ext.expandToInclude(15.0, 15.0);
+        REQUIRE(ext == GeoExtent(WGS84, -10.0, -10.0, 15.0, 15.0));
+    }
+
+    SECTION("expandToInclude expand to the west and south") {
+        GeoExtent ext(WGS84, -10.0, -10.0, 10.0, 10.0);          
+        ext.expandToInclude(-15.0, -15.0);
+        REQUIRE(ext == GeoExtent(WGS84, -15.0, -15.0, 10.0, 10.0));
+    }
+
+    SECTION("expandToInclude to include the antimeridian to the east") {
+        GeoExtent ext(WGS84, 160.0, -10.0, 170.0, 10.0);
+        ext.expandToInclude(-160.0, 0.0);
+        REQUIRE(ext == GeoExtent(WGS84, 160.0, -10.0, -160.0, 10.0));
+    }
+
+    SECTION("expandToInclude to include the antimeridian to the west") {
+        GeoExtent ext(WGS84, -170.0, -10.0, -160.0, 10.0);
+        ext.expandToInclude(160.0, 0.0);
+        REQUIRE(ext == GeoExtent(WGS84, 160.0, -10.0, -160.0, 10.0));
+    }
+
+    SECTION("expandToInclude across the antimeridian to the east") {
+        GeoExtent ext(WGS84, 160.0, -10.0, -170.0, 10.0);
+        ext.expandToInclude(-160.0, 0.0);
+        REQUIRE(ext == GeoExtent(WGS84, 160.0, -10.0, -160.0, 10.0));
+    }
+
+    SECTION("expandToInclude across the antimeridian to the west") {
+        GeoExtent ext(WGS84, 170.0, -10.0, -160.0, 10.0);
+        ext.expandToInclude(160.0, 0.0);
+        REQUIRE(ext == GeoExtent(WGS84, 160.0, -10.0, -160.0, 10.0));
+    }
+
+    SECTION("multiple expandToIncludes") {
+        GeoExtent ext(WGS84, -10.0, -10.0, 10.0, 10.0);
+        ext.expandToInclude(-15.0, -15.0);
+        ext.expandToInclude(15.0, 15.0);
+        REQUIRE(ext == GeoExtent(WGS84, -15.0, -15.0, 15.0, 15.0));
+    }
+
+    SECTION("calling expandToInclude on an invalid extent just takes the incoming value.") {
+        GeoExtent invalid(WGS84);
+        invalid.expandToInclude(-15.0, -15.0);        
+        REQUIRE(invalid == GeoExtent(WGS84, -15.0, -15.0, -15.0, -15.0));
+    }
+
+    //SECTION("expandToInclude expands to the full extent") {
+    //    GeoExtent full(WGS84);
+    //    full.expandToInclude(-180.0, -90);
+    //    // First point should result in zero width
+    //    REQUIRE(full.width() == 0.0);
+    //    full.expandToInclude(180.0, 90);
+    //    // Seond point should result in full width
+    //    REQUIRE(full.width() == 360.0);
+    //}
+
+    SECTION("Intersect 2 non-overlapping extents") {
+        GeoExtent e1(WGS84, -10, -10, 10, 10);
+        GeoExtent e2(WGS84, 20, 20, 30, 30);
+        REQUIRE(e1.intersects(e2)==false);
+        REQUIRE(e1.intersectionSameSRS(e2).isInvalid());
+    }
+
+    SECTION("Intersect 2 simple overlapping extents") {
+        GeoExtent e1(WGS84, -10, -10, 10, 10);
+        GeoExtent e2(WGS84, 5, 5, 20, 20);
+        REQUIRE(e1.intersects(e2)==true);
+        REQUIRE(e1.intersectionSameSRS(e2) == GeoExtent(WGS84, 5, 5, 10, 10));
+    }
+
+    SECTION("Intersect non-overlapping anti-meridian extent with a simple extent") {
+        GeoExtent e1(WGS84, 170, -10, -170, 10);
+        GeoExtent e2(WGS84, 20, 20, 30, 30);
+        REQUIRE(e1.intersects(e2)==false);
+        REQUIRE(e1.intersectionSameSRS(e2).isInvalid());
+    }
+
+    SECTION("Intersect overlapping anti-meridian extent with a simple extent") {
+        GeoExtent e1(WGS84, 170, -10, -170, 10);
+        GeoExtent e2(WGS84, -175, -60, -165, 60);
+        REQUIRE(e1.intersects(e2)==true);
+        REQUIRE(e1.intersectionSameSRS(e2) == GeoExtent(WGS84, -175, -10, -170, 10));
+    }
+
+    SECTION("Intersect 2 non-overlapping anti-meridian extents") {
+        GeoExtent e1(WGS84, 170, -10, -170, 10);
+        GeoExtent e2(WGS84, 130, -50, -120, -40);
+        REQUIRE(e1.intersects(e2)==false);
+        REQUIRE(e1.intersectionSameSRS(e2).isInvalid());
+    }
+
+    SECTION("Intersect 2 overlapping anti-meridian extents") {
+        GeoExtent e1(WGS84, 170, -10, -170, 10);
+        GeoExtent e2(WGS84, 130, -50, -120, 5);
+        REQUIRE(e1.intersects(e2)==true);
+        REQUIRE(e1.intersectionSameSRS(e2) == GeoExtent(WGS84, 170, -10, -170, 5));
+    }
+
+    SECTION("2 extents tht abut do not intersect") {
+        GeoExtent e1(WGS84, 0, 0, 10, 10);
+        GeoExtent e2(WGS84, 10, 0, 20, 10);
+        REQUIRE(e1.intersects(e2)==false);
+        REQUIRE(e1.intersectionSameSRS(e2).isInvalid());
+    }
+    
+    SECTION("Scaling") {
+        GeoExtent e1(WGS84, -10, -10, 10, 10);
+        e1.scale(2, 2);
+        REQUIRE(e1 == GeoExtent(WGS84, -20, -20, 20, 20));
+    }
+
+    SECTION("ExpandBy") {
+        GeoExtent e1(WGS84, -10, -10, 10, 10);
+        e1.expand(5, 5);
+        REQUIRE(e1 == GeoExtent(WGS84, -12.5, -12.5, 12.5, 12.5));
+    }
+
+
+    // Older ones
+    SECTION("Normalization across the antimeridian") {
+        GeoExtent ext(SpatialReference::create("wgs84"), 175.0, -10.0, 185.0, 10.0);
+        REQUIRE(ext.crossesAntimeridian());
+        REQUIRE(ext.east() == -175.0);
+    }
+
+    SECTION("expandToInclude is a noop when point is within the extent") {
+        GeoExtent ext(SpatialReference::create("wgs84"), 175.0, -10.0, 185.0, 10.0);
+        ext.expandToInclude(180.0, 0.0);
+        REQUIRE(ext == GeoExtent(SpatialReference::create("wgs84"), 175.0, -10.0, 185.0, 10.0));
+    }
+
+    SECTION("expandToInclude expand to the west") {
+        GeoExtent ext(SpatialReference::create("wgs84"), 175.0, -10.0, 185.0, 10.0);
+        ext.expandToInclude(170.0, 0.0);        
+        REQUIRE(ext == GeoExtent(SpatialReference::create("wgs84"), 170.0, -10.0, 185.0, 10.0));
+        REQUIRE(ext.crossesAntimeridian());
+    }
+
+    SECTION("expandToInclude expand to the east") {
+        GeoExtent ext(SpatialReference::create("wgs84"), 175.0, -10.0, 185.0, 10.0);
+        ext.expandToInclude(186.0, 0.0);        
+        REQUIRE(ext == GeoExtent(SpatialReference::create("wgs84"), 175.0, -10.0, 186.0, 10.0));
+        REQUIRE(ext.crossesAntimeridian());
+    }
+
+    SECTION("expandToInclude expands to the closest side of the bounds") {
+        // This seems like it would expand to the east, but b/c of wrapping the final point is actually closer to the 
+        // west side, so it will expand westward.
+        GeoExtent ext(SpatialReference::create("wgs84"), 175.0, -10.0, 185.0, 10.0);
+        ext.expandToInclude(525.0, 0.0);        
+        REQUIRE(ext == GeoExtent(SpatialReference::create("wgs84"), 165.0, -10.0, 185.0, 10.0));
+        REQUIRE(ext.crossesAntimeridian());
+    }
+
+    SECTION("Validate input") {
+        REQUIRE(GeoExtent(WGS84, DBL_MAX, DBL_MAX, -DBL_MAX, -DBL_MAX).isInvalid());
+        REQUIRE(GeoExtent(WGS84, 0, 0, 1, DBL_MAX).isInvalid());
+        REQUIRE(GeoExtent(WGS84, 0.0, 0.0, 10.0, -10.0).isInvalid());
+        REQUIRE(GeoExtent(WGS84, sqrt(-1.0), 0.0, 10.0, 10.0).isInvalid());
+    }
+
+    SECTION("Cube 1") {
+        GeoExtent e1(CUBE, 0.0, 0.0, 6.0, 1.0);
+        GeoExtent e2(WGS84, -180.0, -90.0, 180.0, 90.0);
+        REQUIRE(e1.isValid());
+        REQUIRE(e1.intersects(e2));
+        REQUIRE(e2.intersects(e1));
+        REQUIRE(e1.contains(e2));
+        REQUIRE(e2.contains(e1));
+    }
+}
diff --git a/src/tests/osgEarth_tests/ImageLayerTests.cpp b/src/tests/osgEarth_tests/ImageLayerTests.cpp
new file mode 100644
index 0000000..142a077
--- /dev/null
+++ b/src/tests/osgEarth_tests/ImageLayerTests.cpp
@@ -0,0 +1,59 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+* Copyright 2016 Pelican Mapping
+* http://osgearth.org
+*
+* osgEarth is free software; you can redistribute it and/or modify
+* it under the terms of the GNU Lesser General Public License as published by
+* the Free Software Foundation; either version 2 of the License, or
+* (at your option) any later version.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+* IN THE SOFTWARE.
+*
+* You should have received a copy of the GNU Lesser General Public License
+* along with this program.  If not, see <http://www.gnu.org/licenses/>
+*/
+
+#include <osgEarth/catch.hpp>
+
+#include <osgEarth/ImageLayer>
+#include <osgEarth/Registry>
+
+#include <osgEarthDrivers/gdal/GDALOptions>
+
+using namespace osgEarth;
+using namespace osgEarth::Drivers;
+
+TEST_CASE( "ImageLayers can be created from TileSourceOptions" ) {
+
+    GDALOptions opt;
+    opt.url() = "../data/world.tif";
+    osg::ref_ptr< ImageLayer > layer = new ImageLayer( ImageLayerOptions("world", opt) );
+
+    Status status = layer->open();
+    REQUIRE( status.isOK() ); 
+
+    SECTION("Profiles are correct") {
+        const Profile* profile = layer->getProfile();
+        REQUIRE(profile != NULL);
+
+        // This doesn't actually work without a change to the gdal driver.
+        //REQUIRE(profile->isEquivalentTo(osgEarth::Registry::instance()->getGlobalGeodeticProfile()));
+        //REQUIRE(profile->isHorizEquivalentTo(globalGeodetic));
+    }
+
+    SECTION("Images are read correctly") {
+        TileKey key(0,0,0,layer->getProfile());
+        GeoImage image = layer->createImage( key );
+        REQUIRE(image.valid());
+        REQUIRE(image.getImage()->s() == 256);
+        REQUIRE(image.getImage()->t() == 256);
+        REQUIRE(image.getExtent() == key.getExtent());
+    }
+}
diff --git a/src/tests/osgEarth_tests/SpatialReferenceTests.cpp b/src/tests/osgEarth_tests/SpatialReferenceTests.cpp
new file mode 100644
index 0000000..058a40d
--- /dev/null
+++ b/src/tests/osgEarth_tests/SpatialReferenceTests.cpp
@@ -0,0 +1,92 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+* Copyright 2016 Pelican Mapping
+* http://osgearth.org
+*
+* osgEarth is free software; you can redistribute it and/or modify
+* it under the terms of the GNU Lesser General Public License as published by
+* the Free Software Foundation; either version 2 of the License, or
+* (at your option) any later version.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+* IN THE SOFTWARE.
+*
+* You should have received a copy of the GNU Lesser General Public License
+* along with this program.  If not, see <http://www.gnu.org/licenses/>
+*/
+
+#include <osgEarth/catch.hpp>
+
+#include <osgEarth/SpatialReference>
+
+using namespace osgEarth;
+
+TEST_CASE( "SpatialReferences are cached" ) {
+    osg::ref_ptr< const SpatialReference > srs1 = SpatialReference::create("spherical-mercator");
+    REQUIRE(srs1.valid());
+
+    osg::ref_ptr< const SpatialReference > srs2 = SpatialReference::create("spherical-mercator");
+    REQUIRE(srs2.valid());
+
+    REQUIRE(srs1.get() == srs2.get());
+}
+
+TEST_CASE( "Spherical Mercator SpatialReferences can be created" ) {
+    // Create a spherical mercator SRS.
+    osg::ref_ptr< const SpatialReference > mercSRS = SpatialReference::create("spherical-mercator");
+
+    REQUIRE( mercSRS.valid() );
+    REQUIRE( mercSRS->isSphericalMercator() );    
+    REQUIRE( mercSRS->isProjected() );    
+    REQUIRE( !mercSRS->isGeodetic() );    
+    REQUIRE( !mercSRS->isGeographic() );    
+    
+    SECTION("epsg:900913 is equivalent") {
+        osg::ref_ptr< const SpatialReference > epsg900913 = SpatialReference::create("epsg:900913");
+        REQUIRE(epsg900913.valid());
+        REQUIRE(epsg900913->isEquivalentTo(mercSRS.get()));
+    }
+
+    SECTION("epsg:3785 is equivalent") {
+        osg::ref_ptr< const SpatialReference > epsg3785 = SpatialReference::create("epsg:3785");
+        REQUIRE(epsg3785.valid());
+        REQUIRE(epsg3785->isEquivalentTo(mercSRS.get()));
+    }
+
+    SECTION("epsg:102113 is equivalent") {
+        osg::ref_ptr< const SpatialReference > epsg102113 = SpatialReference::create("epsg:102113");
+        REQUIRE(epsg102113.valid());
+        REQUIRE(epsg102113->isEquivalentTo(mercSRS.get()));
+    }    
+}
+
+TEST_CASE( "WGS84 SpatialReferences can be created" ) {
+    // Create a wgs84 mercator SRS.
+    osg::ref_ptr< const SpatialReference > wgs84 = SpatialReference::create("wgs84");     
+    REQUIRE( wgs84.valid() );
+
+    REQUIRE(wgs84->isGeographic());
+    REQUIRE(wgs84->isGeodetic());
+    REQUIRE(!wgs84->isMercator());
+    REQUIRE(!wgs84->isProjected());
+    
+    SECTION("epsg:4326 is equivalent") {
+        osg::ref_ptr< const SpatialReference > epsg4326 = SpatialReference::create("epsg:4326");
+        REQUIRE(epsg4326.valid());
+        REQUIRE(epsg4326->isEquivalentTo(wgs84.get()));
+    }
+}
+
+TEST_CASE("Plate Carre SpatialReferences can be created") {
+    osg::ref_ptr< const SpatialReference > plateCarre = SpatialReference::create("plate-carre");
+    REQUIRE(plateCarre.valid());
+    REQUIRE(!plateCarre->isGeographic());
+    REQUIRE(!plateCarre->isMercator());
+    REQUIRE(!plateCarre->isGeodetic());
+    REQUIRE(plateCarre->isProjected());
+}
diff --git a/src/tests/osgEarth_tests/ThreadingTests.cpp b/src/tests/osgEarth_tests/ThreadingTests.cpp
new file mode 100644
index 0000000..bf1520d
--- /dev/null
+++ b/src/tests/osgEarth_tests/ThreadingTests.cpp
@@ -0,0 +1,135 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+* Copyright 2016 Pelican Mapping
+* http://osgearth.org
+*
+* osgEarth is free software; you can redistribute it and/or modify
+* it under the terms of the GNU Lesser General Public License as published by
+* the Free Software Foundation; either version 2 of the License, or
+* (at your option) any later version.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+* IN THE SOFTWARE.
+*
+* You should have received a copy of the GNU Lesser General Public License
+* along with this program.  If not, see <http://www.gnu.org/licenses/>
+*/
+
+#include <osgEarth/catch.hpp>
+#include <osgEarth/ThreadingUtils>
+
+using namespace osgEarth;
+
+
+namespace ReadWriteMutexTest
+{
+    osgEarth::Threading::ReadWriteMutex mutex;
+    bool readLock1 = false;
+    bool readLock2 = false;
+    bool attemptingWriteLock = false;
+    bool writeLock = false;
+
+
+    class Thread1 : public OpenThreads::Thread
+    {
+    public:
+        Thread1()
+        {
+
+        }
+
+        void run()
+        {
+            // Thread one takes the first read lock.
+            mutex.readLock();
+            readLock1 = true;
+
+            // Wait for the write lock to happen in thread 2
+            while (!attemptingWriteLock)
+            {
+                OpenThreads::Thread::YieldCurrentThread();
+            }
+
+            // The write lock is being attempted, sleep for awhile to make sure it actually tries to get the write lock.
+            OpenThreads::Thread::microSleep(2e6);
+
+            // Take a second read lock
+            mutex.readLock();            
+            readLock2 = true;
+
+            // Unlock both of our read locks
+            mutex.readUnlock();
+            mutex.readUnlock();
+        }
+    };
+
+
+
+    class Thread2 : public osg::Referenced, public OpenThreads::Thread
+    {
+    public:
+        Thread2()
+        {
+
+        }
+
+        void run()
+        {
+            // Wait for thread1 to grab the read lock.
+            while (!readLock1)
+            {
+                OpenThreads::Thread::YieldCurrentThread();
+            }
+
+            // Tell the first thread we are attempting a write lock so it can try to grab it's second read lock.
+            attemptingWriteLock = true;
+
+            // Try to get the write lock
+            mutex.writeLock();
+            writeLock = true;
+            mutex.writeUnlock();            
+        }
+    };
+}
+
+// Disabled temporarily b/c it's breaking the Travis build for some reason.  Works fine on regular machines.
+/*
+TEST_CASE( "ReadWriteMutex can handle mulitple read locks from the same thread while a writer is trying to lock" ) {    
+
+    ReadWriteMutexTest::Thread1 thread1;
+    ReadWriteMutexTest::Thread2 thread2;
+
+    // Start both threads
+    thread1.start();
+    thread2.start();
+
+    // Wait a couple of seconds for the threads to actually start.
+    OpenThreads::Thread::microSleep(2e6);
+   
+    // Let the threads go for up to 5 seconds.  If they don't finish in that amount of time they are deadlocked.
+    double maxTimeSeconds = 5.0;
+    double elapsedTime = 0.0;
+    
+    osg::Timer_t startTime = osg::Timer::instance()->tick();
+    while (thread1.isRunning() && thread2.isRunning())
+    {
+        OpenThreads::Thread::YieldCurrentThread();
+        elapsedTime = osg::Timer::instance()->delta_s(startTime, osg::Timer::instance()->tick());
+        if (elapsedTime >= maxTimeSeconds)
+        {
+            OE_NOTICE << "Threads failed to complete in " << elapsedTime << " seconds" << std::endl;
+            break;
+        }
+    }
+
+    OE_NOTICE << "Elapsed time = " << elapsedTime << std::endl;
+    REQUIRE(!thread1.isRunning());
+    REQUIRE(!thread2.isRunning());
+    REQUIRE(elapsedTime < maxTimeSeconds);
+}
+*/
\ No newline at end of file
diff --git a/src/osgEarth/AutoScale b/src/tests/osgEarth_tests/main.cpp
similarity index 61%
rename from src/osgEarth/AutoScale
rename to src/tests/osgEarth_tests/main.cpp
index 1bdbe4c..8d35b62 100644
--- a/src/osgEarth/AutoScale
+++ b/src/tests/osgEarth_tests/main.cpp
@@ -20,30 +20,5 @@
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
 */
 
-#ifndef OSGEARTH_AUTO_SCALE_H
-#define OSGEARTH_AUTO_SCALE_H 1
-
-#include <osgEarth/Common>
-
-namespace osgEarth
-{
-    /**
-     * @deprecated
-     * Consider GeoPositionNodeAutoScaler instead
-     *
-     * Usage: set your render bin to AUTO_SCALE_BIN and your
-     * geometry will automatically draw at one pixel per meter.
-     *
-     * To enable:
-     *
-     *    node->getOrCreateStateSet()->setRenderBinDetails(0, osgEarth::AUTO_SCALE_BIN);
-     *
-     * And to disable:
-     *
-     *    node->getOrCreateStateSet()->setRenderBinToInherit();
-     */
-    extern OSGEARTH_EXPORT const std::string AUTO_SCALE_BIN;
-}
-
-
-#endif // OSGEARTH_AUTO_SCALE_H
+#define CATCH_CONFIG_MAIN  // This tells Catch to provide a main() - only do this in one cpp file
+#include <osgEarth/catch.hpp>
\ No newline at end of file
diff --git a/tests/aeqd.earth b/tests/aeqd.earth
new file mode 100644
index 0000000..474fca0
--- /dev/null
+++ b/tests/aeqd.earth
@@ -0,0 +1,22 @@
+<map name="Azimuthal Equidistant" type="projected">    
+    
+    <options>
+        <profile>
+            <srs>+proj=aeqd +lat_0=90 +lon_0=0 +x_0=0 +y_0=0 +ellps=WGS84 +datum=WGS84 +units=m +no_defs</srs>
+            <xmin>-17500000</xmin>
+            <xmax> 17500000</xmax>
+            <ymin>-17500000</ymin>
+            <ymax> 17500000</ymax>
+        </profile>
+        <terrain driver="rex" color="#ffffff00" tile_size="17"/>
+    </options>
+    
+    <image name="readymap_imagery" driver="tms">
+        <url>http://readymap.org/readymap/tiles/1.0.0/7/</url>
+    </image>
+    
+    <elevation name="readymap_elevation" driver="tms" enabled="false">
+        <url>http://readymap.org/readymap/tiles.1.0.0/116/</url>
+    </elevation>
+
+</map>
diff --git a/tests/annotation.earth b/tests/annotation.earth
index 53535bd..a94b084 100644
--- a/tests/annotation.earth
+++ b/tests/annotation.earth
@@ -17,7 +17,7 @@ osgEarth Sample - Annotations
                    heading="35.27" pitch="-35" />
     </viewpoints>
     
-    <annotations>
+    <annotations name="Annotations Group A">
     
         <label text="Label">
             <position lat="34" long="-120" />
@@ -78,6 +78,7 @@ osgEarth Sample - Annotations
                 stroke-width:       2px;
                 altitude-clamping:  terrain-scene;
                 altitude-binding:   vertex;
+                render-depth-offset-auto: true;
             </style>
         </circle>
         <label text="scene-clamped circle" lat="22.074" long="-159.606"/>
@@ -92,6 +93,9 @@ osgEarth Sample - Annotations
             </style>
         </ellipse>
         <label text="HAT Ellipse" lat="40" long="-100.0"/>
+    </annotations>
+    
+    <annotations name="Annotations Group B">
         
         <ellipse name="ellipse extruded">
             <position lat="32" long="-100.0"/>
@@ -147,7 +151,7 @@ osgEarth Sample - Annotations
         <feature name="Draped Polygon">
             <srs>wgs84</srs>
             <geometry>
-                POLYGON((-100 47, -100 49, -95 48, -96 45 -98 42))
+                POLYGON((-100 47, -100 49, -95 48, -96 45, -98 42))
             </geometry>
             <style type="text/css">
                 fill:     #ffff007f;
@@ -197,9 +201,9 @@ osgEarth Sample - Annotations
             <style type="text/css">
                 stroke:              #ffff00;
                 stroke-width:        3;
-                stroke-tessellation-size: 10km;
+                stroke-tessellation-size: 1km;
                 altitude-clamping:   terrain;
-                altitude-technique:  scene;
+                altitude-technique:  gpu;
                 render-lighting:     false;
             </style>
         </feature>
diff --git a/tests/annotation_dateline.earth b/tests/annotation_dateline.earth
new file mode 100644
index 0000000..a8a6753
--- /dev/null
+++ b/tests/annotation_dateline.earth
@@ -0,0 +1,27 @@
+<!--
+osgEarth Sample - Annotation going across the dateline.  Should be one continuous line.
+-->
+<map name="readymap.org" type="geocentric" version="2">
+
+    <image name="ReadyMap.org - Imagery" driver="tms">
+        <url>http://readymap.org/readymap/tiles/1.0.0/22/</url>
+    </image>
+
+    <annotations>
+
+        <feature name="Flight Path">
+            <srs>wgs84</srs>
+            <geometry>
+LINESTRING(140.385 35.765 0, 141.1 35.4917 3944.77, 142.163 35.1617 9645.92, 142.665 35.38 12496.8, 143.872 35.8933 12496.8, 145.667 37.1967 12496.8, 149.825 39.1533 12496.8, 155.668 42.9817 12496.8, 162.31 46.415 13716, 168.783 48.675 13716, 180 50 13716, 190 50 13716, 200 49 13716, 210 48 13716, 220 46 13716, 233 40.625 14935.2, 236.725 39.0533 14935.2, 238.828 37.8333 14935.2, 240.842 36.0417 14935.2, 240.98 35.9133 13819.4, 241.125 35.5133 11087.5, 241.245 35.1833 8832.26, 241.298 35 [...]
+            </geometry>
+
+            <style type="text/css">
+                stroke:              #ffff00;
+                stroke-width:        3;
+                render-lighting:     false;
+            </style>
+        </feature>
+
+
+    </annotations>
+</map>
diff --git a/tests/annotation_dateline_projected.earth b/tests/annotation_dateline_projected.earth
new file mode 100644
index 0000000..adeab88
--- /dev/null
+++ b/tests/annotation_dateline_projected.earth
@@ -0,0 +1,45 @@
+<!--
+osgEarth Sample - Annotation going across the dateline.  Should split into multiple features on either side of the earth.
+-->
+<map name="readymap.org" type="projected" version="2">
+
+    <options>
+        <profile>plate-carre</profile>
+    </options>
+
+
+    <image name="ReadyMap.org - Imagery" driver="tms">
+        <url>http://readymap.org/readymap/tiles/1.0.0/22/</url>
+    </image>
+
+    <annotations>
+
+        <feature name="Flight Path">
+            <srs>wgs84</srs>
+            <geometry>
+LINESTRING(140.385 35.765 0, 141.1 35.4917 3944.77, 142.163 35.1617 9645.92, 142.665 35.38 12496.8, 143.872 35.8933 12496.8, 145.667 37.1967 12496.8, 149.825 39.1533 12496.8, 155.668 42.9817 12496.8, 162.31 46.415 13716, 168.783 48.675 13716, 180 50 13716, 190 50 13716, 200 49 13716, 210 48 13716, 220 46 13716, 233 40.625 14935.2, 236.725 39.0533 14935.2, 238.828 37.8333 14935.2, 240.842 36.0417 14935.2, 240.98 35.9133 13819.4, 241.125 35.5133 11087.5, 241.245 35.1833 8832.26, 241.298 35 [...]
+            </geometry>
+
+            <style type="text/css">
+                stroke:              #ffff00;
+                stroke-width:        3;
+                render-lighting:     false;
+            </style>
+        </feature>
+
+
+       <!--An image overlay that crosses the date line and should be split into two chunks-->
+        <imageoverlay>
+            <url>../data/flag_us.png</url>
+            <geometry>POLYGON((170 26, 190 26, 190 56, 170 56))</geometry>
+        </imageoverlay>
+
+        <!--An image overlay with non-axis aligned corners-->
+        <imageoverlay>
+            <url>../data/flag_us.png</url>
+            <geometry>POLYGON((-81 26, -40.5 45, -40.5 75.5, -81 60))</geometry>
+        </imageoverlay>
+
+
+    </annotations>
+</map>
diff --git a/tests/boston-gpu.earth b/tests/boston-gpu.earth
index 4b38cb9..cb3bcba 100644
--- a/tests/boston-gpu.earth
+++ b/tests/boston-gpu.earth
@@ -15,7 +15,7 @@ to extruded buildings.
         <url>http://readymap.org/readymap/tiles/1.0.0/116/</url>
     </elevation>
       
-    <model name="buildings" driver="feature_geom">
+    <feature_model name="buildings">
              
         <features name="buildings" driver="ogr">
             <url>../data/boston_buildings_utm19.shp</url>
@@ -23,9 +23,6 @@ to extruded buildings.
 			<resample min_length="2.5"/>
         </features>
         
-		<!-- Uncomment this for the featuremanip or featurequery demo. -->
-        <!-- <feature_indexing/> -->
-        
         <!--
            The "layout" element activates tiling and paging of the feature set. If you
            omit the layout element, the entire feature set will render as one pre-loaded
@@ -40,7 +37,9 @@ to extruded buildings.
               level of detail. The default is 15. tile size = max range / tile size factor.
         -->
         
-        <layout tile_size_factor="52">
+        <layout>
+            <tile_size>500</tile_size>
+            <paged>true</paged>
             <level name="default" max_range="20000" style="buildings"/>
         </layout>
         
@@ -71,10 +70,10 @@ to extruded buildings.
                 }
             </style>
         </styles>   
-    </model>
+    </feature_model>
     
     
-    <model name="Streets" driver="feature_geom" enabled="true">
+    <feature_model name="Streets" enabled="true">
         <features name="streets" driver="ogr" build_spatial_index="true">
             <url>../data/boston-scl-utm19n-meters.shp</url>
 			<resample min_length="25" max_length="25"/>
@@ -94,10 +93,10 @@ to extruded buildings.
                 }
             </style>
         </styles>        
-    </model>
+    </feature_model>
 	
 	
-	<model name="streetlamps" driver="feature_geom">
+	<feature_model name="streetlamps">
 
         <features name="street centerlines" driver="ogr" build_spatial_index="true">
             <url>../data/boston-scl-utm19n-meters.shp</url>
@@ -125,10 +124,10 @@ to extruded buildings.
 				<url>../data/scripts/createLineOffsetPoints.js</url>
 			</script>
         </styles>   
-    </model>
+    </feature_model>
     
     
-    <model name="Parks" driver="feature_geom" enabled="true">
+    <feature_model name="Parks" enabled="true">
         <features name="parks" driver="ogr" build_spatial_index="true">
             <url>../data/boston-parks.shp</url>
         </features>
@@ -154,16 +153,13 @@ to extruded buildings.
                 }
             </style>
         </styles>        
-    </model>
-    
-    
+    </feature_model>
+        
     <viewpoints>
         <viewpoint name="Boston Overview" heading="24.261" height="0" lat="42.34425" long="-71.076262" pitch="-21.6" range="3450"/>
         <viewpoint name="Boston Downtown 1" heading="117" lat="42.3568" long="-71.0585" height="0" pitch="-20.4" range="1500" />
         <viewpoint name="Boston Downtown 2" heading="-128.5" lat="42.3582" long="-71.0546" height="0" pitch="-19" range="1620" />
         <viewpoint name="Boston Street Level" heading="-145.64081" lat="42.364015" long="-71.054149" pitch="-9.701" range="144.95"/>
     </viewpoints>
-
-    <sky driver="simple" hours="14.0"/>
   
 </map>
diff --git a/tests/boston.earth b/tests/boston.earth
index 299b6c2..6ae90bd 100644
--- a/tests/boston.earth
+++ b/tests/boston.earth
@@ -7,23 +7,18 @@ to extruded buildings.
 
 <map name="Boston Demo" type="geocentric" version="2">
     
-    <options>
-        <terrain driver="mp"/>
-    </options>
     
     <image name="readymap_imagery" driver="tms">
         <url>http://readymap.org/readymap/tiles/1.0.0/22/</url>
     </image>
     
     <elevation name="readymap_elevation" driver="tms">
-        <url>http://readymap.org/readymap/tiles/1.0.0/9/</url>
+        <url>http://readymap.org/readymap/tiles/1.0.0/116/</url>
     </elevation>
       
-    <model name="buildings" driver="feature_geom">
-             
+    <feature_model name="buildings">
         <features name="buildings" driver="ogr">
             <url>../data/boston_buildings_utm19.shp</url>
-            <build_spatial_index>true</build_spatial_index>
 			<resample min_length="2.5"/>
         </features>
         
@@ -75,22 +70,16 @@ to extruded buildings.
                 }
             </style>
         </styles>   
-    </model>
+    </feature_model>
     
     
-    <model name="Streets" driver="feature_geom" enabled="true">
-        <features name="streets" driver="ogr" build_spatial_index="true">
-            <url>../data/boston-scl-utm19n-meters.shp</url>
-			<resample min_length="25" max_length="25"/>
-        </features>
-        
+    <feature_model name="Streets" feature_source="streets-data">        
         <layout crop_features="true" tile_size_factor="7.5">
             <level max_range="5000"/>
-        </layout>
-        
+        </layout>        
         <styles>
             <style type="text/css">
-                streets {
+                default {
                     stroke:                       #ffff7f7f;
                     stroke-width:                 7.5m;
                     altitude-clamping:            terrain;
@@ -99,16 +88,10 @@ to extruded buildings.
                 }
             </style>
         </styles>        
-    </model>
+    </feature_model>
 	
 	
-	<model name="streetlamps" driver="feature_geom">
-
-        <features name="street centerlines" driver="ogr" build_spatial_index="true">
-            <url>../data/boston-scl-utm19n-meters.shp</url>
-			<resample min_length="25" max_length="25"/>
-		</features>
-
+	<feature_model name="streetlamps" feature_source="streets-data">
         <layout tile_size_factor="5" crop_features="true">
             <level max_range="1000" style="default"/>
         </layout>
@@ -129,13 +112,13 @@ to extruded buildings.
 				<url>../data/scripts/createLineOffsetPoints.js</url>
 			</script>
         </styles>   
-    </model>
+    </feature_model>
     
     
-    <model name="Parks" driver="feature_geom" enabled="true">
-        <features name="parks" driver="ogr" build_spatial_index="true">
+    <feature_model name="Parks" enabled="true">
+        <features name="parks" driver="ogr">
             <url>../data/boston-parks.shp</url>
-        </features>
+        </features>        
         
         <layout tile_size_factor="3">
             <level max_range="2000"/>
@@ -143,10 +126,11 @@ to extruded buildings.
         
         <instancing>true</instancing>
 		<cluster_culling>false</cluster_culling>
+        <feature_indexing enabled="false"/>
         
         <styles>
             <style type="text/css">
-                parks {
+                default {
                    model:                  "../data/loopix/tree4.osgb";
 				   model-scale:            0.15 + 0.1*Math.random();
                    model-placement:        random;
@@ -158,13 +142,21 @@ to extruded buildings.
                 }
             </style>
         </styles>        
-    </model>
+    </feature_model>
     
-    <viewpoints>
+    <viewpoints time="1.0">
         <viewpoint name="Boston Overview" heading="24.261" height="0" lat="42.34425" long="-71.076262" pitch="-21.6" range="3450"/>
         <viewpoint name="Boston Downtown 1" heading="117" lat="42.3568" long="-71.0585" height="0" pitch="-20.4" range="1500" />
         <viewpoint name="Boston Downtown 2" heading="-128.5" lat="42.3582" long="-71.0546" height="0" pitch="-19" range="1620" />
         <viewpoint name="Boston Street Level" heading="-145.64081" lat="42.364015" long="-71.054149" pitch="-9.701" range="144.95"/>
     </viewpoints>
   
+    
+    <feature_source name="streets-data" driver="ogr">
+        <url>../data/boston-scl-utm19n-meters.shp</url>
+        <filters>
+            <resample min_length="25" max_length="25"/>
+        </filters>
+    </feature_source>
+    
 </map>
diff --git a/tests/boston_tfs.earth b/tests/boston_tfs.earth
deleted file mode 100644
index 802a118..0000000
--- a/tests/boston_tfs.earth
+++ /dev/null
@@ -1,50 +0,0 @@
-<!--
-osgEarth Sample.
-
-Demonstrates TFS feature source.
--->
-
-<map name="Boston Demo" type="geocentric" version="2">
-    
-    <image name="ReadyMap.org - Imagery" driver="tms">
-        <url>http://readymap.org/readymap/tiles/1.0.0/22/</url>
-    </image>
-      
-    <model name="buildings" driver="feature_geom">
-             
-        <features name="buildings" driver="tfs">
-            <url>../data/tfs_boston.zip/layer/tfs.xml</url>
-            <format>json</format>
-        </features>
-        
-        <layout>
-            <tile_size_factor>10</tile_size_factor>
-        </layout>
-        
-        <styles>            
-            <style type="text/css">
-                buildings {
-                    extrusion-height:      3.5 * max([story_ht_], 1);
-                    extrusion-flatten:     true;
-                    fill:                  #7f7f7f;
-                    stroke:                #4f4f4f;
-                }
-            </style>
-        </styles>
-
-        <lighting>true</lighting>        
-    </model>
-    
-    
-    <options>
-        <lighting>false</lighting>
-    </options> 
-    
-    <external>
-        <viewpoints>
-            <viewpoint name="Boston Overview" heading="24.261" height="0" lat="42.34425" long="-71.076262" pitch="-21.6" range="3450"/>
-        </viewpoints>
-        <sky hours="21.0"/>
-    </external>
-  
-</map>
diff --git a/tests/city_labels.xml b/tests/city_labels.xml
new file mode 100644
index 0000000..87b434f
--- /dev/null
+++ b/tests/city_labels.xml
@@ -0,0 +1,25 @@
+<feature_model name="City Labels">
+    <features driver="ogr">
+        <url>../data/cities.shp</url>
+    </features>
+    <styles>
+        <selector class="default">
+            <query>
+                <expr><![CDATA[scalerank < 5]]></expr>
+            </query>
+        </selector>
+        <style type="text/css">
+            default {
+                icon:           "../data/downarrow.png";
+                icon-placement: centroid;
+                icon-declutter: true;
+                icon-align:     center-bottom;
+                text-content:   [name];
+                text-priority:  10-[scalerank];
+                text-align:     center-bottom;
+                text-halo:      #1f1f1f;
+                text-size:      6+2*(10-[scalerank]);
+            }
+        </style>
+    </styles>
+</feature_model>
diff --git a/tests/clouds.earth b/tests/clouds.earth
index 0c772ad..01288d5 100644
--- a/tests/clouds.earth
+++ b/tests/clouds.earth
@@ -1,39 +1,28 @@
-<map>
+<!--
+osgEarth Sample - Rendering a cloud layer with a layer shader
+-->
+<map name="Imagery with clouds" type="geocentric">
+
     <options>
-        <profile
-            srs="+proj=longlat +a=6441918.37 +b=6420319.837342"
-            xmin="-180" xmax="180" ymin="-90" ymax="90">
-        </profile>
-        <terrain driver="rex" color="#ffffff00" bin_number="2" skirt_ratio="0" tile_size="7"/>
+        <terrain driver="rex"/>
     </options>
-        
-    <image name="Clouds" driver="osg" url="../data/cloud_combined_2048.jpg">
-        <profile>global-geodetic</profile>
-        <cache_policy usage="none"/>
-        
-        <color_filters>
-            <glsl>
-                color.a = color.r;
-            </glsl>
-        </color_filters>  
-        
-        <!-- concept: not yet implemented -->
-        <shader name="cloudAlpha"/>
+    
+    <image name="readymap_imagery" driver="tms">
+        <url>http://readymap.org/readymap/tiles/1.0.0/7/</url>
     </image>
     
-    <!-- concept: not yet implemented -->
-    <shaders>
-        <shader name="cloudAlpha">
-            <code>
-              <![CDATA[
-                #pragma vp_location   fragment_coloring
-                #pragma vp_entryPoint cloudAlpha
-                
-                void cloudAlpha(inout vec4 color) {
-                    color.a = color.r;
-                }
-              ]]>
-            </code>
+    <image name="clouds" driver="osg">
+        <url>../data/cloud_combined_2048.jpg</url>
+        <profile>global-geodetic</profile>
+        <shader>
+          <![CDATA[
+            #version 330
+            #pragma vp_entryPoint alphaOut
+            #pragma vp_location fragment_coloring
+            void alphaOut(inout vec4 color) {
+                color.a *= clamp(distance(color.rgb, vec3(0.0,0.0,0.0)), 0.0, 1.0);
+            }
+          ]]>
         </shader>
-    </shaders>
-</map>
\ No newline at end of file
+    </image>
+</map>
diff --git a/tests/datum_override.earth b/tests/datum_override.earth
index 3fb1235..ceaffb8 100644
--- a/tests/datum_override.earth
+++ b/tests/datum_override.earth
@@ -12,10 +12,9 @@ See this site for SRS codes and PROJ4 initialization strings:
 http://spatialreference.org
 -->
 
-<map name="datum override example" type="geocentric" version="2">
+<map name="datum override example">
     
     <options>
-        <lighting>false</lighting>
         <profile srs="+proj=longlat +ellps=clrk66 +datum=NAD27 +no_defs"/>
     </options> 
         
diff --git a/tests/night.earth b/tests/day_night_mp.earth
similarity index 100%
rename from tests/night.earth
rename to tests/day_night_mp.earth
diff --git a/tests/day_night_rex.earth b/tests/day_night_rex.earth
new file mode 100644
index 0000000..08fee7c
--- /dev/null
+++ b/tests/day_night_rex.earth
@@ -0,0 +1,61 @@
+<!--
+ osgEarth example - use a layer shader to simulate day/night transition
+ 
+ Earth at night imagery: 
+ https://earthobservatory.nasa.gov/Features/NightLights/page3.php
+-->
+<map name="Day/Night Transition">
+    
+    <options>
+        <terrain driver="rex"/>
+    </options>
+    
+    <image name="readymap_imagery" driver="tms">
+        <url>http://readymap.org/readymap/tiles/1.0.0/7/</url>
+    </image>
+
+    <image name="EarthAtNight" driver="tms">
+        <url>http://readymap.org/readymap/tiles/1.0.0/26/</url>
+        <shader>
+          <![CDATA[
+            #pragma vp_entryPoint dayNight
+            #pragma vp_location fragment_coloring
+            #pragma import_defines(OE_NUM_LIGHTS)
+
+            // stage global - interpolated UP vector at fragment
+            vec3 vp_Normal;
+
+            struct osg_LightSourceParameters 
+            {   
+               vec4 ambient;
+               vec4 diffuse;
+               vec4 specular;
+               vec4 position;
+               vec3 spotDirection;
+               float spotExponent;
+               float spotCutoff;
+               float spotCosCutoff;
+               float constantAttenuation;
+               float linearAttenuation;
+               float quadraticAttenuation;
+               bool enabled;
+            };  
+            uniform osg_LightSourceParameters osg_LightSource[OE_NUM_LIGHTS];
+
+            void dayNight(inout vec4 color)
+            {
+                vec3 L = normalize(osg_LightSource[0].position.xyz);
+                vec3 N = normalize(vp_Normal);
+                float NdotL = dot(N,L);
+                float vmin = -0.55;
+                float vmax = 0.0;
+                float day = (clamp(NdotL, vmin, vmax) - vmin)/(vmax-vmin);
+                color.a *= (1.0 - day);
+            }
+          ]]>
+        </shader>
+    </image>      
+
+    <sky driver="simple" hours="14.0" ambient="0.2"/>
+    
+</map>
diff --git a/tests/detail_texture.earth b/tests/detail_texture.earth
index 2826653..3a5b622 100644
--- a/tests/detail_texture.earth
+++ b/tests/detail_texture.earth
@@ -16,5 +16,10 @@
         <url>http://readymap.org/readymap/tiles/1.0.0/116/</url>
     </elevation>
     
-    <detail image="../data/noise3.png" lod="21" alpha="0.5" max_range="6000" attenuation="2000"/>        
+    <detail>
+        <image>../data/noise3.png</image>
+        <lod>21</lod>
+        <max_range>6000</max_range>
+        <attenuation>2000</attenuation>
+    </detail>
 </map>
diff --git a/tests/fade_elevation.earth b/tests/fade_elevation.earth
deleted file mode 100644
index cf95402..0000000
--- a/tests/fade_elevation.earth
+++ /dev/null
@@ -1,59 +0,0 @@
-<!--
-osgEarth Sample - Fading out terrain based on the elevation.
-
-Run:
-
-  osgearth_viewer fade_elevation.earth --uniform min_elevation 0 100 --uniform fade_distance 1 1000
-
--->
-
-<map name="readymap.org" type="geocentric">
-
-    <image name="readymap_imagery" driver="tms">
-        <url>http://readymap.org/readymap/tiles/1.0.0/22/</url>
-    </image>
-
-    <elevation name="readymap_elevation" driver="tms">
-        <url>http://readymap.org/readymap/tiles/1.0.0/116/</url>
-    </elevation>    
-
-
-    <terrainshader>
-        <code><![CDATA[
-        
-            #version 330 compatibility
-            #pragma vp_entryPoint "fadeTerrain"
-            #pragma vp_location   vertex_view
-
-            attribute vec4 oe_terrain_attr;
-
-            out float elevation;
-            
-            void fadeTerrain(inout vec4 VertexVIEW)
-            {
-                elevation = oe_terrain_attr.w;
-            }
-            
-        ]]></code>
-    </terrainshader>
-
-    <terrainshader>
-        <code><![CDATA[
-        
-            #version 330 compatibility
-            #pragma vp_entryPoint "fade"
-            #pragma vp_location   "fragment_coloring"
-
-            in float elevation;
-            uniform float min_elevation;
-            uniform float fade_distance;
-            
-            void fade(inout vec4 color)
-            {
-                color.a = 1.0 - clamp((min_elevation - elevation)/fade_distance, 0.0, 1.0);
-            }
-            
-        ]]></code>
-    </terrainshader>
-
-</map>
diff --git a/tests/feature_clip_plane.earth b/tests/feature_clip_plane.earth
index 87bcb0d..6664dc0 100644
--- a/tests/feature_clip_plane.earth
+++ b/tests/feature_clip_plane.earth
@@ -8,12 +8,12 @@ way to mitigate z-fighting issues.
 -->
 
 <map name="Feature Geometry Demo" type="geocentric" version="2">
-    
+
     <image name="world" driver="gdal">
         <url>../data/world.tif</url>
     </image>
-    
-    <model name="boundaries" driver="feature_geom">
+
+    <feature_model name="boundaries">
         <features name="world" driver="ogr">
             <url>../data/world.shp</url>
         </features>        
@@ -21,12 +21,12 @@ way to mitigate z-fighting issues.
             <style type="text/css">
                 states {
                    stroke:            #ffff00;
-				   render-depth-test: false;
-				   render-clip-plane: 0;
+                   render-depth-test: false;
+                   render-clip-plane: 0;
                    render-order:      1;
                 }                    
             </style>
         </styles>        
-    </model>
-  
+    </feature_model>
+
 </map>
diff --git a/tests/feature_country_boundaries.earth b/tests/feature_country_boundaries.earth
index a403b72..29ec466 100644
--- a/tests/feature_country_boundaries.earth
+++ b/tests/feature_country_boundaries.earth
@@ -2,41 +2,40 @@
 osgEarth Sample
 -->
 
-<map name="Wordwide Line Vectors" type="geocentric">
-  
-    <options>
-        <lighting>false</lighting>
-        <terrain min_lod="16"/>
-    </options>
+<map name="Worldwide Line Vectors">
 
-    <image name="world" driver="gdal">
-        <url>../data/world.tif</url>
-        <cache_policy usage="no_cache"/>
+    <image name="readymap_imagery" driver="tms">
+        <url>http://readymap.org/readymap/tiles/1.0.0/7/</url>
     </image>
-    
-    <model name="world_boundaries" driver="feature_geom">
+
+    <elevation name="readymap_elevation" driver="tms" vdatum="egm96">
+        <url>http://readymap.org/readymap/tiles/1.0.0/116/</url>
+    </elevation>
             
-        <features name="world" driver="ogr">
-            <url>../data/world.shp</url>
-            <build_spatial_index>true</build_spatial_index>
-            <convert type="line"/>
-        </features>
+    <feature_source name="world-data" driver="ogr">
+        <url>../data/world.shp</url>
+        <convert type="line"/>
+    </feature_source>
+    
+    <feature_model name="world_boundaries" feature_source="world-data">
         
-        <layout tile_size="500000" crop_features="true">
+        <layout tile_size="500000" crop_features="true" paged="true">
             <level max_range="1e10"/>
         </layout>
                 
         <styles>
             <style type="text/css">
                 world {
-                   stroke:                       #ffff00;
-                   stroke-width:                 2px;
-                   stroke-tessellation-size:     100km;
-                   render-depth-offset-min-bias: 1000;
+                   stroke:                   #ffff00;
+                   stroke-width:             3px;
+                   stroke-tessellation-size: 1km;
+                   render-lighting:          false;
+                   altitude-clamping:        terrain-gpu;
+                   render-depth-offset:      true;                   
                 }            
             </style>
         </styles>
         
-    </model>
+    </feature_model>
   
 </map>
diff --git a/tests/feature_custom_filters.earth b/tests/feature_custom_filters.earth
index 348e26f..2b63b89 100644
--- a/tests/feature_custom_filters.earth
+++ b/tests/feature_custom_filters.earth
@@ -41,14 +41,12 @@ Shows how you can use a custom FeatureFilter, even one that is defined in an Ear
     
     <options lighting="false"/>
     
-    <external>
-        <decluttering>
-            <out_animation_time>  0.0  </out_animation_time>
-            <in_animation_time>   0.25 </in_animation_time>
-            <min_animation_scale> 0.45 </min_animation_scale>
-            <min_animation_alpha> 0.35 </min_animation_alpha>
-            <sort_by_priority>    true </sort_by_priority>
-        </decluttering>
-    </external>
+    <screen_space_layout>
+        <out_animation_time>  0.0  </out_animation_time>
+        <in_animation_time>   0.25 </in_animation_time>
+        <min_animation_scale> 0.45 </min_animation_scale>
+        <min_animation_alpha> 0.35 </min_animation_alpha>
+        <sort_by_priority>    true </sort_by_priority>
+    </screen_space_layout>
   
 </map>
diff --git a/tests/feature_draped_lines.earth b/tests/feature_draped_lines.earth
index 529fb51..a2c93fa 100644
--- a/tests/feature_draped_lines.earth
+++ b/tests/feature_draped_lines.earth
@@ -5,19 +5,14 @@ Demonstrates feature draping using projective texturing,
 i.e. "altitude-clamping: terrain-drape".
 -->
 
-<map name="Geometry Rasterizer Demo" type="round" version="2">
-  
-    <options>
-        <lighting>false</lighting>
-        <terrain min_lod="16"/>
-    </options>
+<map name="Draped Lines Demo">
 
     <image name="world" driver="gdal">
         <url>../data/world.tif</url>
         <cache_policy usage="no_cache"/>
     </image>
     
-    <model name="world_boundaries" driver="feature_geom">
+    <feature_model name="world_boundaries">
             
         <features name="world" driver="ogr">
             <url>../data/world.shp</url>
@@ -28,12 +23,12 @@ i.e. "altitude-clamping: terrain-drape".
             <style type="text/css">
                 world {
                    stroke:             #ffff00;
-                   stroke-width:       5px;
+                   stroke-width:       4px;
                    altitude-clamping:  terrain-drape;
                 }            
             </style>
         </styles>
         
-    </model>
+    </feature_model>
   
 </map>
diff --git a/tests/feature_draped_polygons.earth b/tests/feature_draped_polygons.earth
index 6937f2a..52d1cab 100644
--- a/tests/feature_draped_polygons.earth
+++ b/tests/feature_draped_polygons.earth
@@ -16,20 +16,11 @@ the feature data.
         <terrain min_lod="8"/>
     </options>
 
-    <image name="world" driver="gdal">
+    <image name="Imagery" driver="gdal">
         <url>../data/world.tif</url>
     </image>
     
-    <model name="countries" driver="feature_geom">
-                          
-        <features name="states" driver="ogr">
-            <url>../data/world.shp</url>
-            <filters>
-                <resample max_length="5.0"/>
-                <buffer distance="-0.05"/>
-            </filters>
-        </features>
-        
+    <feature_model name="Countries" feature_source="world-data">
         <styles>        
             <style type="text/css">
                 p1 {
@@ -91,5 +82,13 @@ the feature data.
             
         </styles>
         
-    </model>
+    </feature_model>
+    
+    <feature_source name="world-data" driver="ogr">
+        <url>../data/world.shp</url>
+        <filters>
+            <resample max_length="5.0"/>
+            <buffer distance="-0.05"/>
+        </filters>
+    </feature_source>
 </map>
diff --git a/tests/feature_extrude.earth b/tests/feature_extrude.earth
index 2886f20..5b38f97 100644
--- a/tests/feature_extrude.earth
+++ b/tests/feature_extrude.earth
@@ -6,31 +6,24 @@ Footprint Extrusion using the "feature_geom" driver.
 
 <map name="Extrusion Demo" type="geocentric" version="2">
       
-	
-    <!-- Our features layer. The "feature_geom" driver will analyze the
-         style sheet and determine how to render the feature data. -->
-         
-    <model name="buildings" driver="feature_geom">
-          
-        <!-- Feature data set to load. This is a set of polygons representing
-             some buildings in Washington DC -->
-             
-        <features name="buildings" driver="ogr">
-            <url>../data/dcbuildings.shp</url>
-            <build_spatial_index>true</build_spatial_index>
-        </features>
-             
+    <feature_source name="buildings-data" driver="ogr">
+        <url>../data/dcbuildings.shp</url>
+        <build_spatial_index>true</build_spatial_index>
+    </feature_source>
+	         
+    <feature_model name="buildings" feature_source="buildings-data">
         <styles>
             <style type="text/css">
                 buildings {
                     fill:              #ff7f2f;
+                    stroke:            #cf5f00;
                     extrusion-height:  15;
                     extrusion-flatten: true;
                     altitude-clamping: terrain;
-                }            
+                }
             </style>
         </styles>
-    </model>
+    </feature_model>
     
     <image name="ReadyMap.org - Imagery" driver="tms">
         <url>http://readymap.org/readymap/tiles/1.0.0/7/</url>
@@ -39,16 +32,9 @@ Footprint Extrusion using the "feature_geom" driver.
     <elevation name="ReadyMap.org - Elevation" driver="tms">
         <url>http://readymap.org/readymap/tiles/1.0.0/116/</url>
     </elevation>
-	
-	<options>
-		<terrain normalize_edges="true"/>
-	</options>
     
-    <external>
-		<viewpoints>
-			<viewpoint name="Zoom to Buildings" heading="12.4" lat="38.8982" long="-77.0365" height="20.67" pitch="-51.4" range="4500" />
-		</viewpoints>
-        <sky driver="gl" hours="20.0"/>
-    </external>
+    <viewpoints>
+        <viewpoint name="Zoom to Buildings" heading="12.4" lat="38.8982" long="-77.0365" height="20.67" pitch="-51.4" range="4500" />
+    </viewpoints>
   
 </map>
diff --git a/tests/feature_geom.earth b/tests/feature_geom.earth
index 0c799f9..fa8b9d6 100644
--- a/tests/feature_geom.earth
+++ b/tests/feature_geom.earth
@@ -5,30 +5,24 @@ Basic example of how to read feature data from a shapefile and build
 OSG geometry out of it.
 -->
 
-<map name="Feature Geometry Demo" type="geocentric" version="2">
+<map name="Feature Geometry Demo">
     	
-    <image name="world" driver="gdal">
-        <url>../data/world.tif</url>
+    <image name="readymap_imagery" driver="tms">
+        <url>http://readymap.org/readymap/tiles/1.0.0/7/</url>
     </image>
     
-    <model name="states" driver="feature_geom">
-
-        <!-- Configure the OGR feature driver to read the shapefile -->
-        <features name="world" driver="ogr">
+    <feature_model name="states">
+        <features name="states" driver="ogr">
             <url>../data/usa.shp</url>
-        </features>
-        
-        <!-- Appearance details -->
+        </features>        
         <styles>
             <style type="text/css">
                 states {
-                   stroke:          #ffff00;
-                   altitude-offset: 1000;      
-				   render-lighting: false;
+                   stroke:          #ffff00; 
+                   stroke-width:    2px;
+                   render-depth-offset: true;
                 }                    
             </style>
-        </styles>
-        
-    </model>
-  
+        </styles>        
+    </feature_model>
 </map>
diff --git a/tests/feature_gpx.earth b/tests/feature_gpx.earth
index 9e52a02..515dd69 100644
--- a/tests/feature_gpx.earth
+++ b/tests/feature_gpx.earth
@@ -26,7 +26,8 @@ Note:  You must have GDAL built with Expat support to read GPX files
         <styles>
             <style type="text/css">
                 routes {
-                   stroke: #ffff00;                   
+                   stroke: #ffff00; 
+                   render-depth-offset: true;
                 }                    
             </style>
         </styles>
@@ -35,10 +36,8 @@ Note:  You must have GDAL built with Expat support to read GPX files
         
     </model>
 	
-	<external>
-        <viewpoints>
-            <viewpoint name="Fells Loop" heading="0" height="0" lat="42.43095" long="-71.1076" pitch="-90" range="5000"/>
-        </viewpoints>
-    </external>
+    <viewpoints>
+        <viewpoint name="Fells Loop" heading="0" height="0" lat="42.43095" long="-71.1076" pitch="-90" range="5000"/>
+    </viewpoints>
   
 </map>
diff --git a/tests/feature_inline_geometry.earth b/tests/feature_inline_geometry.earth
index 31abb2c..1a8f2bb 100644
--- a/tests/feature_inline_geometry.earth
+++ b/tests/feature_inline_geometry.earth
@@ -24,7 +24,7 @@ display. The options are:
     </image>
     
     
-    <model name="great_circle" driver="feature_geom">
+    <feature_model name="great_circle">
 	    <features driver="ogr">
 			<geometry>
 				POLYGON((-120 30, -120 50, -70 50, -70 30))
@@ -37,14 +37,15 @@ display. The options are:
             <style type="text/css">
                 default {
                    fill: #ffff006f;
+                   stroke: #ffff00;
+                   stroke-width: 3px;
 				   altitude-clamping: terrain-drape;
                 }                    
             </style>
-        </styles>
-        
-    </model>
+        </styles>        
+    </feature_model>
     
-    <model name="rhumb_line" driver="feature_geom">
+    <feature_model name="rhumb_line">
 	    <features driver="ogr">
 			<geometry>
 				POLYGON((-68 30, -68 50, -20 50, -20 30))
@@ -57,11 +58,12 @@ display. The options are:
             <style type="text/css">
                 default {
                    fill: #ff00ff6f;
+                   stroke: #ff00ff;
+                   stroke-width: 3px;
 				   altitude-clamping: terrain-drape;
                 }                    
             </style>
-        </styles>
-        
-    </model>
+        </styles>        
+    </feature_model>
   
 </map>
diff --git a/tests/feature_labels.earth b/tests/feature_labels.earth
index b52a10f..d4446ad 100644
--- a/tests/feature_labels.earth
+++ b/tests/feature_labels.earth
@@ -5,22 +5,19 @@ This shows how to label point features with an attribute.
 
 <map name="Feature Geometry Demo" type="geocentric" version="2">
             
-    <image driver="gdal">
+    <image name="World Imagery" driver="gdal">
         <url>../data/world.tif</url>
     </image>
     
-    <model name="cities" driver="feature_geom">
-
-        <features name="cities" driver="ogr">
-            <url>../data/ne_cities.shp</url>
-            <profile>spherical-mercator</profile>
-        </features>
-        
+    <feature_source name="city-data" driver="ogr">
+        <url>../data/cities.shp</url>
+    </feature_source>
+    
+    <feature_model name="cities" feature_source="city-data">        
         <feature_indexing enabled="true"/>
-
         <styles>
             <selector class="cities">
-                <query><expr><![CDATA[rank_max > 9]]></expr></query>
+                <query><expr><![CDATA[scalerank < 5]]></expr></query>
             </selector>
             <style type="text/css">              
                 cities {
@@ -29,26 +26,22 @@ This shows how to label point features with an attribute.
                     icon-declutter: true;
                     icon-align:     center-bottom;
                     text-content:   [name];
-                    text-priority:  [rank_max];
+                    text-priority:  10-[scalerank];
                     text-align:     center-bottom;
-                    text-halo:      #3f3f3f;
-                    text-size:      12+3*([rank_max]-9);
+                    text-halo:      #1f1f1f;
+                    text-size:      6+2*(10-[scalerank]);
                 }     
             </style>
         </styles>
-    </model>
-    
-    <options lighting="false"/>
+    </feature_model>
     
-    <external>
-        <screen_space_layout>
-            <out_animation_time>  0.0  </out_animation_time>
-            <in_animation_time>   0.25 </in_animation_time>
-            <min_animation_scale> 0.45 </min_animation_scale>
-            <min_animation_alpha> 0.0  </min_animation_alpha>
-            <sort_by_priority>    true </sort_by_priority>
-            <snap_to_pixel>       true </snap_to_pixel>
-        </screen_space_layout>
-    </external>
+    <screen_space_layout>
+        <out_animation_time>  0.0  </out_animation_time>
+        <in_animation_time>   0.25 </in_animation_time>
+        <min_animation_scale> 0.45 </min_animation_scale>
+        <min_animation_alpha> 0.0  </min_animation_alpha>
+        <sort_by_priority>    true </sort_by_priority>
+        <snap_to_pixel>       true </snap_to_pixel>
+    </screen_space_layout>
   
 </map>
diff --git a/tests/feature_labels_script.earth b/tests/feature_labels_script.earth
index 4ab49b2..e193acd 100644
--- a/tests/feature_labels_script.earth
+++ b/tests/feature_labels_script.earth
@@ -9,8 +9,7 @@ This shows how to label point features with an attribute (with some extra zing).
         <url>../data/world.tif</url>
     </image>
     
-    <model name="cities" driver="feature_geom">
-
+    <feature_model name="cities">
         <features name="cities" driver="ogr">
             <url>../data/world.shp</url>
         </features>
@@ -38,20 +37,15 @@ This shows how to label point features with an attribute (with some extra zing).
 					altitude-technique: scene;
                 }     
             </style>
-        </styles>
-        
-    </model>
-    
-    <options lighting="false"/>
+        </styles>        
+    </feature_model>
     
-    <external>
-        <decluttering>
-            <out_animation_time>  0.0  </out_animation_time>
-            <in_animation_time>   0.25 </in_animation_time>
-            <min_animation_scale> 0.45 </min_animation_scale>
-            <min_animation_alpha> 0.35 </min_animation_alpha>
-            <sort_by_priority>    true </sort_by_priority>
-        </decluttering>
-    </external>
+    <screen_space_layout>
+        <out_animation_time>  0.0  </out_animation_time>
+        <in_animation_time>   0.25 </in_animation_time>
+        <min_animation_scale> 0.45 </min_animation_scale>
+        <min_animation_alpha> 0.35 </min_animation_alpha>
+        <sort_by_priority>    true </sort_by_priority>
+    </screen_space_layout>
   
 </map>
diff --git a/tests/feature_levels_and_selectors.earth b/tests/feature_levels_and_selectors.earth
index dc6719f..6b0bdbe 100644
--- a/tests/feature_levels_and_selectors.earth
+++ b/tests/feature_levels_and_selectors.earth
@@ -27,7 +27,7 @@ Shows how to use Levels and Selectors together when rendering feature data.
     <model name="cities" driver="feature_geom">
 
         <features name="cities" driver="ogr">
-            <url>../data/ne_cities.shp</url>
+            <url>../data/cities.shp</url>
             <build_spatial_index>true</build_spatial_index>
         </features>
         
@@ -85,10 +85,8 @@ Shows how to use Levels and Selectors together when rendering feature data.
     
     <options lighting="false"/>
     
-    <external>
-        <decluttering>
-            <sort_by_priority>true</sort_by_priority>
-        </decluttering>
-    </external>
+    <screen_space_layout>
+        <sort_by_priority>true</sort_by_priority>
+    </screen_space_layout>
   
 </map>
diff --git a/tests/feature_model_scatter.earth b/tests/feature_model_scatter.earth
index e3adac6..8a2ab4a 100644
--- a/tests/feature_model_scatter.earth
+++ b/tests/feature_model_scatter.earth
@@ -13,7 +13,7 @@ is randomized, but it is randomized exactly the same way each time.
     <!-- Our features layer. The "feature_geom" driver will analyze the
          style sheet and determine how to render the feature data. -->
          
-    <model name="trees" driver="feature_geom" enabled="true">
+    <feature_model name="trees" enabled="true">
           
         <!-- Feature data set to load. This is a set of polygons representing
              the public parks in Washington DC -->
@@ -72,7 +72,7 @@ is randomized, but it is randomized exactly the same way each time.
                 }                
             </style>
         </styles>        
-    </model>
+    </feature_model>
     
     <image name="ReadyMap.org - Imagery" driver="tms">
         <url>http://readymap.org/readymap/tiles/1.0.0/7/</url>
diff --git a/tests/feature_models.earth b/tests/feature_models.earth
index ebb0a7a..14acec7 100644
--- a/tests/feature_models.earth
+++ b/tests/feature_models.earth
@@ -10,8 +10,7 @@
         <styles>
             <style type="text/css">
                 points {
-                   model:               "../data/red_flag.osg.2500.scale";
-                   altitude-clamping:   terrain;
+                   model: "../data/red_flag.osg.2500.scale";
                 }                                            
             </style>
         </styles>   
@@ -22,10 +21,8 @@
         <url>http://readymap.org/readymap/tiles/1.0.0/7/</url>
     </image>
 	
-    <external>	  
-		<viewpoints>
-			<viewpoint name="Models" lat="25.311" long="-80.807" pitch="-21" range="177351"/>
-		</viewpoints>
-    </external>
+    <viewpoints>
+        <viewpoint name="Models" lat="25.311" long="-80.807" pitch="-21" range="177351"/>
+    </viewpoints>
   
 </map>
\ No newline at end of file
diff --git a/tests/feature_occlusion_culling.earth b/tests/feature_occlusion_culling.earth
index 4c89cca..36efa1a 100644
--- a/tests/feature_occlusion_culling.earth
+++ b/tests/feature_occlusion_culling.earth
@@ -12,40 +12,25 @@ Demonstrates occlusion culling on feature labels.
     <elevation name="readymap_elevation" driver="tms">
         <url>http://readymap.org/readymap/tiles/1.0.0/9/</url>
     </elevation>
-			
-	<model name="cities" driver="feature_geom">
+            
+    <feature_model name="labels">
 
-        <features name="cities" driver="ogr">
+        <features name="labels" driver="ogr">
             <url>../data/world.shp</url>
         </features>
 
         <styles>
             <style type="text/css">              
-                cities {                   			
-                   text-provider:       annotation;
+                labels {                            
                    text-content:        [cntry_name];
                    text-priority:       [pop_cntry];
                    text-halo:           #3f3f7f;
                    text-align:          center_center;
                    text-declutter:      true;
-				   text-occlusion-cull: true;	                   
-				   altitude-clamping:   terrain;	                   				   				   
-				   altitude-technique:  scene;
+                   text-occlusion-cull: true;
                 }     
             </style>
         </styles>        
-    </model>
-		
-    <options lighting="false"/>
-    
-    <external>
-        <decluttering>
-            <out_animation_time>  0.0  </out_animation_time>
-            <in_animation_time>   0.25 </in_animation_time>
-            <min_animation_scale> 0.45 </min_animation_scale>
-            <min_animation_alpha> 0.35 </min_animation_alpha>
-            <sort_by_priority>    true </sort_by_priority>
-        </decluttering>
-    </external>
+    </feature_model>
     
 </map>
diff --git a/tests/feature_overlay.earth b/tests/feature_overlay.earth
deleted file mode 100644
index 251531b..0000000
--- a/tests/feature_overlay.earth
+++ /dev/null
@@ -1,38 +0,0 @@
-<!--
-osgEarth Sample
-Drawing simple lines at a set altitude.
--->
-
-<map name="Geometry Rasterizer Demo" type="round" version="2">
-  
-    <options>
-        <lighting>false</lighting>
-    </options>
-
-    <image name="world" driver="gdal">
-        <url>../data/world.tif</url>
-        <cache_policy usage="no_cache"/>
-    </image>
-    
-    
-    <model name="world_boundaries" driver="feature_geom">
-           
-        <features name="world" driver="ogr">
-            <url>../data/world.shp</url>
-            <build_spatial_index>true</build_spatial_index>
-        </features>
-                
-        <styles>
-            <style type="text/css">
-                world {
-                   stroke:                   #ffff00;
-                   stroke-width:             2.0;
-                   stroke-tessellation-size: 100km;
-                   altitude-offset:          1000.0;
-                }            
-            </style>
-        </styles>
-        
-    </model>
-  
-</map>
diff --git a/tests/feature_population_cylinders.earth b/tests/feature_population_cylinders.earth
index b942ad9..7a0d35e 100644
--- a/tests/feature_population_cylinders.earth
+++ b/tests/feature_population_cylinders.earth
@@ -9,19 +9,19 @@ Shows how to use JavaScript to create geometry parametrically.
         <url>../data/world.tif</url>
     </image>
     
-    <model name="cities" driver="feature_geom">
-
-        <features name="cities" driver="ogr" build_spatial_index="true">
-            <url>../data/ne_cities.shp</url>
-        </features>
-
+    <feature_source name="cities-data" driver="ogr">
+        <url>../data/cities_mercator.shp</url>
+        <build_spatial_index>true</build_spatial_index>
+    </feature_source>
+    
+    <feature_model name="City Population" feature_source="cities-data">
         <styles>
             <script profile="full">
               <![CDATA[
-                var min_rank = 12;
+                var maxRank = 2;
                 function makePopCircles() {
-                    if (feature.properties.rank_max >= min_rank) {
-                        var radius = (feature.properties.rank_max-min_rank+1) * 75000;
+                    if (feature.properties.scalerank <= maxRank) {
+                        var radius = (maxRank-feature.properties.scalerank+1) * 75000;
                         feature.geometry = feature.geometry.buffer(radius);
                         feature.properties.height = radius*1.5;
                     }
@@ -42,29 +42,20 @@ Shows how to use JavaScript to create geometry parametrically.
                     render-bin:        DepthSortedBin;
                 }     
             </style>
-        </styles>
-        
-    </model>
+        </styles>        
+    </feature_model>
     
     
-    <model name="cities" driver="feature_geom">
-
-        <features name="cities" driver="ogr">
-            <url>../data/ne_cities.shp</url>
-            <build_spatial_index>true</build_spatial_index>
-        </features>
-        
+    <feature_model name="City Labels" feature_source="cities-data">        
         <layout>
             <level name="far" style="large"  max_range="1e10"/>
         </layout>
-
         <styles>
             <selector name="large" class="label-large">
                 <query>
-                    <expr> <![CDATA[ rank_max >= 12 ]]> </expr>
+                    <expr> <![CDATA[ scalerank <= 2 ]]> </expr>
                 </query>
-            </selector>
-            
+            </selector>            
             <style type="text/css">              
                 label-large {
                     text-declutter: true;
@@ -72,21 +63,18 @@ Shows how to use JavaScript to create geometry parametrically.
                     text-size:      16.0;
                     text-align:     center_center;
                     text-halo:      #1f1f1f;
-                    text-priority:  [rank_max];
+                    text-priority:  [scalerank];
                 }
             </style>
-        </styles>
-        
-    </model>
+        </styles>        
+    </feature_model>
     
-    <external>
-        <decluttering>
-            <out_animation_time>  0.0  </out_animation_time>
-            <in_animation_time>   0.25 </in_animation_time>
-            <min_animation_scale> 0.45 </min_animation_scale>
-            <min_animation_alpha> 0.35 </min_animation_alpha>
-            <sort_by_priority>    true </sort_by_priority>
-        </decluttering>
-    </external>
+    <screen_space_layout>
+        <out_animation_time>  0.0  </out_animation_time>
+        <in_animation_time>   0.25 </in_animation_time>
+        <min_animation_scale> 0.45 </min_animation_scale>
+        <min_animation_alpha> 0.35 </min_animation_alpha>
+        <sort_by_priority>    true </sort_by_priority>
+    </screen_space_layout>
   
 </map>
diff --git a/tests/feature_raster.earth b/tests/feature_raster.earth
index e9ed897..87f3d06 100644
--- a/tests/feature_raster.earth
+++ b/tests/feature_raster.earth
@@ -12,6 +12,8 @@ osgEarth Sample - Raster to feature example.
         <url>http://readymap.org/readymap/tiles/1.0.0/116/</url>
     </elevation>
 
+
+    <libraries>osgEarthSplat</libraries>
     
     <image name="CLASSMAP" driver="landuse" shared="true" visible="false" coverage="true" max_data_level="15">
         <base_lod>12</base_lod>
@@ -21,6 +23,7 @@ osgEarth Sample - Raster to feature example.
             <image name="ESA" driver="gdal" coverage="true">
                 <url>H:/data/esa/GLOBCOVER_L4_200901_200912_V2.3_Ant_tiled.tif</url>
                 <warp>0.01</warp>
+                <profile>global-geodetic</profile>
             </image>
         </images>
         <shared_sampler> landUseTex       </shared_sampler>
@@ -76,44 +79,30 @@ osgEarth Sample - Raster to feature example.
         </styles>  
 
     </model>
+    
+    
+    <image name="ReadyMap 15m" driver="tms" opacity="0.8">
+        <url>http://readymap.org/readymap/tiles/1.0.0/7/</url>
+    </image>
+  
+    <viewpoints>            
+        <viewpoint name="Trees 1">
+          <heading>-66.2945</heading>
+          <pitch>-14.0905</pitch>
+          <range>3408.89m</range>
+          <long>-121.7544800272052</long>
+          <lat>46.7792209225515</lat>
+          <height>1445.844014885835</height>
+        </viewpoint>
+        
+        <viewpoint name="Trees 2">              
+          <heading>12.2384</heading>
+          <pitch>-4.26323</pitch>
+          <range>1421.77m</range>
+          <long>-121.8282019325911</long>
+          <lat>46.65132101133439</lat>
+          <height>1128.989560711198</height>
+        </viewpoint>
+    </viewpoints>
 
-    <extensions> 
-      
-         <splat>        
-            <coverage>
-                <layer> CLASSMAP </layer>
-                <legend>../data/splat/GLOBCOVER_legend.xml</legend>
-            </coverage>
-            
-            <zones>
-                <zone name="default" doc="Default Climate Zone">            
-                    <surface>
-                        <catalog>../data/splat/splat_catalog.xml</catalog>
-                    </surface>
-                </zone>                
-            </zones>
-            
-        </splat>
-
-        <viewpoints>            
-            <viewpoint name="Trees 1">
-              <heading>-66.2945</heading>
-              <pitch>-14.0905</pitch>
-              <range>3408.89m</range>
-              <long>-121.7544800272052</long>
-              <lat>46.7792209225515</lat>
-              <height>1445.844014885835</height>
-            </viewpoint>
-            
-            <viewpoint name="Trees 2">              
-              <heading>12.2384</heading>
-              <pitch>-4.26323</pitch>
-              <range>1421.77m</range>
-              <long>-121.8282019325911</long>
-              <lat>46.65132101133439</lat>
-              <height>1128.989560711198</height>
-            </viewpoint>
-        </viewpoints>
-               
-    </extensions>
 </map>
diff --git a/tests/feature_rasterize.earth b/tests/feature_rasterize.earth
index 90d23d4..4f97e89 100644
--- a/tests/feature_rasterize.earth
+++ b/tests/feature_rasterize.earth
@@ -5,10 +5,6 @@ Demonstrates use of the "agglite" feature rasterization driver.
 
 <map name="Geometry Rasterizer Demo" type="round" version="2">
 
-    <external>
-        <lod_blending/>
-    </external>
-
     <image name="world" driver="gdal">
         <url>../data/world.tif</url>
         <cache_policy usage="no_cache"/>
diff --git a/tests/feature_tfs.earth b/tests/feature_tfs.earth
index a6f25e7..8c16c16 100644
--- a/tests/feature_tfs.earth
+++ b/tests/feature_tfs.earth
@@ -6,16 +6,16 @@ This example shows how to use the TFS driver.
 <map name="TFS" type="geocentric" version="2">
 
     <model name="buildings" driver="feature_geom">
-    
+
         <features name="buildings" driver="tfs">		                
-			<url>http://readymap.org/readymap/features/tfs/4/</url>
+            <url>http://readymap.org/readymap/features/tfs/4/</url>
             <format>json</format>            
         </features>
-        
+
         <layout>        
             <tile_size_factor>5.0</tile_size_factor>
         </layout>
-        
+
         <styles>                
             <style type="text/css">
                 buildings {
@@ -28,19 +28,14 @@ This example shows how to use the TFS driver.
             </style>
         </styles>
     </model>
-                    
- 
 
     <image name="esri imagery" driver="arcgis">
         <url>http://server.arcgisonline.com/ArcGIS/rest/services/ESRI_Imagery_World_2D/MapServer</url>
         <nodata_image>http://server.arcgisonline.com/ArcGIS/rest/services/ESRI_Imagery_World_2D/MapServer/tile/100/0/0.jpeg</nodata_image>
     </image>
-    
-    <external>
-        <sky hours="20.0"/>
-        <viewpoints>
-            <viewpoint name="Mexico Buildings" height="0" lat="19.42" long="-99.163" pitch="-89" range="5000"/>
-        </viewpoints>
-    </external>
-  
+
+    <viewpoints>
+        <viewpoint name="Mexico Buildings" height="0" lat="19.42" long="-99.163" pitch="-89" range="5000"/>
+    </viewpoints>
+
 </map>
diff --git a/tests/feature_tfs_scripting.earth b/tests/feature_tfs_scripting.earth
index a676113..800522b 100644
--- a/tests/feature_tfs_scripting.earth
+++ b/tests/feature_tfs_scripting.earth
@@ -6,16 +6,16 @@ This example shows how to use the TFS driver.
 <map name="TFS" type="geocentric" version="2">
 
     <model name="buildings" driver="feature_geom">
-    
+
         <features name="buildings" driver="tfs">                        
             <url>http://readymap.org/readymap/features/tfs/4/</url>
             <format>json</format>            
         </features>
-        
+
         <layout>        
             <tile_size_factor>5.0</tile_size_factor>
         </layout>
-        
+
         <styles>                
             <style type="text/css">
                 b1 {
@@ -40,11 +40,11 @@ This example shows how to use the TFS driver.
                     altitude-resolution: 0.1;
                 }
             </style>
-            
+
             <selector name="default" style_expr="selectStyle()"/>
-            
+
             <script language="javascript">
-              <![CDATA[
+                <![CDATA[
                 rotator = 0;
                 function selectStyle() {
                     rotator = (rotator+1)%3;
@@ -52,23 +52,20 @@ This example shows how to use the TFS driver.
                     else if (rotator==1) return "b2";
                     else                 return "b3";
                 }
-              ]]>
+                ]]>
             </script>
         </styles>  
-        
+
         <lighting>true</lighting>
     </model>
-                    
- 
+
+
     <image name="readymap_imagery" driver="tms">
         <url>http://readymap.org/readymap/tiles/1.0.0/7/</url>
     </image>
-    
-    <external>
-        <sky hours="20.0"/>
-        <viewpoints>
-            <viewpoint name="Mexico Buildings" height="0" lat="19.42" long="-99.163" pitch="-89" range="5000"/>
-        </viewpoints>
-    </external>
-  
+
+    <viewpoints>
+        <viewpoint name="Mexico Buildings" height="0" lat="19.42" long="-99.163" pitch="-89" range="5000"/>
+    </viewpoints>
+
 </map>
diff --git a/tests/feature_wfs.earth b/tests/feature_wfs.earth
index 4905bba..dd2bb41 100644
--- a/tests/feature_wfs.earth
+++ b/tests/feature_wfs.earth
@@ -5,14 +5,14 @@ Demonstrates how to read feature data from a WFS server
 
 <map name="WFS Feature Demo" type="geocentric" version="2">
         
-    <image driver="gdal">
+    <image name="World image" driver="gdal">
         <url>../data/world.tif</url>
     </image>
     
-    <model name="states" driver="feature_geom">
+    <model name="States" driver="feature_geom">
         <features name="states" driver="wfs">
             <url>http://demo.opengeo.org/geoserver/wfs</url>           
-            <typename>states</typename>
+            <typename>topp:states</typename>
             <outputformat>json</outputformat>
         </features>
                   
@@ -27,4 +27,19 @@ Demonstrates how to read feature data from a WFS server
             </style>
         </styles>
     </model>
+    
+    <model name="Labels" driver="feature_geom">
+        <features name="states" driver="wfs">
+            <url>http://demo.opengeo.org/geoserver/wfs</url>           
+            <typename>topp:states</typename>
+            <outputformat>json</outputformat>
+        </features>
+        <styles>
+            <style type="text/css">              
+                names {
+                    text-content: feature.properties.STATE_NAME;
+                }     
+            </style>
+        </styles>
+    </model>
 </map>
diff --git a/tests/fractal_elevation.earth b/tests/fractal_elevation.earth
new file mode 100644
index 0000000..22db9d1
--- /dev/null
+++ b/tests/fractal_elevation.earth
@@ -0,0 +1,99 @@
+<map>
+    <options>
+        <terrain driver="rex"/>
+    </options>
+
+    <!-- Land cover dictionary -->
+    <xi:include href="../data/land_cover_dictionary.xml"/>
+
+    <!-- Land cover layer -->
+    <land_cover name="land-cover">
+        <cache_policy usage="no_cache"/>
+        <coverages>
+            <coverage name="ESA GLOBCOVER" driver="gdal">
+                <url>H:/data/esa/GLOBCOVER_L4_200901_200912_V2.3_Ant_tiled.tif</url>
+                <xi:include href="../data/land_cover_ESA_GLOBCOVER.xml"/>
+                <warp>0.025</warp>
+            </coverage>
+        </coverages>
+    </land_cover>
+
+    <elevation name="ReadyMap elevation" driver="tms">
+        <url>http://readymap.org/readymap/tiles/1.0.0/116/</url>
+    </elevation>
+
+    <fractal_elevation name="Fractal Offset" min_level="13" max_data_level="14" offset="true">
+        <cache_policy usage="no_cache"/>
+        <noise_image>H:/data/textures/seamless-noise-1.png</noise_image>
+        <amplitude>8</amplitude>
+        <land_cover_mappings>
+            <mapping class="water" amplitude="0"/>
+        </land_cover_mappings>
+    </fractal_elevation>
+
+    <!-- Satellite imagery -->
+    <image name="ReadyMap imagery" driver="tms" opacity="0.75" enabled="true">
+        <url>http://readymap.org/readymap/tiles/1.0.0/7/</url>
+    </image>
+
+    <feature_model name="roads" min_level="12">
+        <feature_source>roads-data</feature_source>
+        <styles>
+            <style type="text/css">
+                default {
+                    stroke: #333333;
+                    stroke-width: 15m;
+                    altitude-clamping: terrain-drape;
+                }
+            </style>
+        </styles>
+    </feature_model>
+
+    <viewpoints>
+
+        <viewpoint name="Far range">
+            <heading>20.0616</heading>
+            <pitch>-10.5897</pitch>
+            <range>8568.71m</range>
+            <long>-121.8132467079796</long>
+            <lat>46.75415816484834</lat>
+            <height>884.2401606887579</height>
+            <srs>+proj=longlat +ellps=WGS84 +towgs84=0,0,0,0,0,0,0 +no_defs </srs>
+        </viewpoint>
+
+        <viewpoint name="Close range (15m wide roads)">
+            <heading>8.88942</heading>
+            <pitch>-13.3873</pitch>
+            <range>814.424m</range>
+            <long>-121.8306682896568</long>
+            <lat>46.7268954550194</lat>
+            <height>768.5787042481825</height>
+            <srs>+proj=longlat +ellps=WGS84 +towgs84=0,0,0,0,0,0,0 +no_defs </srs>
+        </viewpoint>
+
+        <viewpoint name="water">
+            <heading>-11.0209</heading>
+            <pitch>-13.723</pitch>
+            <range>1738.75m</range>
+            <long>-122.2281684156838</long>
+            <lat>46.77304632999795</lat>
+            <height>354.0491745267063</height>
+            <srs>+proj=longlat +ellps=WGS84 +towgs84=0,0,0,0,0,0,0 +no_defs </srs>
+        </viewpoint>
+
+
+    </viewpoints>
+
+    <feature_source name="roads-data" driver="tfs">
+        <url>http://readymap.org/osm/</url>
+        <min_level>14</min_level>
+        <max_level>14</max_level>
+        <profile>spherical-mercator</profile>
+        <format>pbf</format>
+        <filters>
+            <script language="javascript">
+                <![CDATA[ ("highway" in feature.properties); ]]>
+            </script>
+        </filters>
+    </feature_source>
+</map>
diff --git a/tests/gdal_multiple_files.earth b/tests/gdal_multiple_files.earth
index 6f0bef7..22cef33 100644
--- a/tests/gdal_multiple_files.earth
+++ b/tests/gdal_multiple_files.earth
@@ -18,7 +18,7 @@ Note:  There are tons of NoData areas in the sample terrain files, so expect see
     </image>
 
     <!--Load a folder full of terrain data as an elevation source-->
-    <heightfield name="terrain" driver="gdal">
+    <elevation name="terrain" driver="gdal">
 
         <!--To load the files in a directory, just point the URL to a directory instead of a file-->
         <url>..\data\terrain</url>
@@ -33,6 +33,6 @@ Note:  There are tons of NoData areas in the sample terrain files, so expect see
 	    <!--Tell the GDAL driver to just look for tifs-->
 	    <extensions>tif</extensions>  
 
-    </heightfield>
+    </elevation>
     
 </map>
\ No newline at end of file
diff --git a/tests/geomshader.earth b/tests/geomshader.earth
index 798c2c5..4451cb9 100644
--- a/tests/geomshader.earth
+++ b/tests/geomshader.earth
@@ -19,8 +19,8 @@ Rendering pipeline.
         <code><![CDATA[
         
             #version 330
-            #pragma vp_entryPoint "pulsateEffect"
-            #pragma vp_location   "geometry"
+            #pragma vp_entryPoint demo
+            #pragma vp_location   geometry
 
             layout(triangles) in;
             layout(triangle_strip) out;
@@ -34,10 +34,15 @@ Rendering pipeline.
             
             vec3 vp_Normal;
             
-            void pulsateEffect()
+            vec2 rotate(in vec2 p, in float angle)
             {
-                float strength = 0.25 + sin(osg_FrameTime*2.0)*0.25;
-                vec4 cen = (gl_in[0].gl_Position + gl_in[1].gl_Position + gl_in[2].gl_Position)/3.0;
+                return vec2(cos(angle)*p.x + sin(angle)*p.y,
+                            cos(angle)*p.y - sin(angle)*p.x);
+            }
+            
+            void demo()
+            {
+                float strength = mod(osg_FrameTime, 6.28);
                                     
                 // For each vertex in the input triangle:
                 for(int i=0; i < 3; ++i)
@@ -47,11 +52,10 @@ Rendering pipeline.
                     
                     // Transform the vertex:
                     vec4 pos = gl_in[i].gl_Position;
-                    pos.xyz += vec3(0,0,1) * 1e6 * strength;
-                    //pos += vec4(normalize(cen.xyz-pos.xyz) * distance(cen, pos) * strength, 0.0);
+                    pos.xy = rotate(pos.xy, strength);
                     gl_Position = pos;
                     
-                    // Copies stage globals to output and calles EmitVertex():
+                    // Copies stage globals to output and calls EmitVertex():
                     VP_EmitModelVertex();
                 }
                 EndPrimitive();
diff --git a/tests/glsl.earth b/tests/glsl.earth
index a8d5157..1d0aec5 100644
--- a/tests/glsl.earth
+++ b/tests/glsl.earth
@@ -3,24 +3,22 @@ osgEarth Sample - GLSL in the Earth File.
 -->
 
 <map>
+    <options>
+        <terrain driver="rex"></terrain>
+    </options>
+    
     <image driver="gdal" name="world-tiff">
         <url>../data/world.tif</url>
         <caching_policy usage="no_cache"/>
-    </image>
-    
-    <terrainshader>
-        <code><![CDATA[
-        
-            #version 110
-            #pragma vp_entryPoint "adjustGamma"
-            #pragma vp_location   "fragment_coloring"
-            
-            void adjustGamma(inout vec4 color)
+        <shader>
+          <![CDATA[
+            #pragma vp_entryPoint invert
+            #pragma vp_location   fragment_coloring
+            void invert(inout vec4 color)
             {
-                const float gamma = 2.7;
-                color.rgb = pow(color.rgb, 1.0/vec3(gamma));
-            }
-            
-        ]]></code>
-    </terrainshader>
+                color.rgb = 1.0-color.rgb;
+            }            
+          ]]>
+        </shader>
+    </image>
 </map>
\ No newline at end of file
diff --git a/tests/glsl_filter.earth b/tests/glsl_filter.earth
deleted file mode 100644
index ca7591c..0000000
--- a/tests/glsl_filter.earth
+++ /dev/null
@@ -1,16 +0,0 @@
-<!--
-osgEarth Sample
-Use a simple GLSL code snippet to adjust the color of a layer.
--->
-<map name="readymap.org" type="geocentric" version="2">
-
-    <image name="readymap_imagery" driver="tms">
-        <url>http://readymap.org/readymap/tiles/1.0.0/7/</url>
-        <color_filters>
-			<glsl name="gamma correction">
-				color.rgb = pow(color.rgb, 1.0/vec3(1.3));
-			</glsl>
-        </color_filters>
-    </image>
-    
-</map>
diff --git a/tests/graticule.earth b/tests/graticule.earth
deleted file mode 100644
index e0f9e99..0000000
--- a/tests/graticule.earth
+++ /dev/null
@@ -1,36 +0,0 @@
-<!--
-osgEarth Sample - Graticule Extension.
-
--->
-<map name="Graticule demo" type="geocentric">
-    
-    <image name="readymap_imagery" driver="tms">
-        <url>http://readymap.org/readymap/tiles/1.0.0/7/</url>
-    </image>
-        
-    <elevation name="readymap_elevation" driver="tms">
-        <url>http://readymap.org/readymap/tiles/1.0.0/9/</url>
-    </elevation>
-    
-    <graticule>
-        <!-- The approximate number of grid lines that you would like to see in your view extent.
-             This number, along with the resolutions list, will be used to select a resolution on each view.
-         -->
-        <grid_lines>10</grid_lines>
-
-        <!-- The grid resolutions, in degrees that you want to see, all separated by a space and sorted from lowest resolution to highest -->
-        <!--
-        <resolutions>10 5 2.5 1.25</resolutions>
-         -->
-
-        <!-- The grid line color -->
-        <color>#f7a73f70</color>
-        
-        <!-- The label color -->
-        <label_color>#ffff00ff</label_color>
-        
-        <!-- Specify the line width -->
-        <line_width>2</line_width>
-    </graticule>
-
-</map>
diff --git a/tests/graticules.earth b/tests/graticules.earth
new file mode 100644
index 0000000..89d1230
--- /dev/null
+++ b/tests/graticules.earth
@@ -0,0 +1,79 @@
+<!--
+osgEarth Sample - Graticules
+Run this with osgearth_toc to toggle the various graticule types on and off.
+-->
+
+<map name="osgEarth Graticules" type="geocentric">
+
+    <image name="readymap_imagery" driver="tms">
+        <url>http://readymap.org/readymap/tiles/1.0.0/7/</url>
+    </image>  
+    
+    <gars_graticule name="GARS" visible="false">
+    </gars_graticule>  
+    
+    <mgrs_graticule name="MGRS" visible="true">
+        <sqid_data>../data/mgrs_sqid.bin</sqid_data>
+        <styles>
+            <style type="text/css">
+                gzd {
+                    stroke: #ff000059;
+                    stroke-width: 4px;
+                    stroke-tessellation: 20;
+                    text-fill: #7f7f7fff;
+                    text-align: left_bottom;
+                }
+                100000 {
+                    stroke: #ffff0059;
+                    stroke-width: 3px;
+                    text-size: 28;
+                    text-fill: #ffffff7f;
+                    text-align: left_bottom;
+                }
+                10000 {
+                    stroke: #00ff0059;
+                    stroke-width: 2px;
+                }
+                1000 {
+                    stroke: #7f7fff59;
+                    stroke-width: 2px;
+                }
+                100 {
+                    stroke: #ffffff59;
+                    stroke-width: 1px;
+                }
+                10 {
+                    stroke: #ffffff59;
+                    stroke-width: 1px;
+                }
+                1 {
+                    stroke: #ffffff59;
+                    stroke-width: 1px;
+                }                
+            </style>
+        </styles>
+    </mgrs_graticule>
+    
+    <mgrs_graticule name="UTM" visible="false">
+        <use_default_styles>false</use_default_styles>
+        <styles>
+            <style type="text/css">
+                gzd {
+                    stroke: #ffffff59;
+                    stroke-width: 3px;
+                    stroke-tessellation: 20;
+                    text-fill: #7f7f7fff;
+                    text-align: left_bottom;
+                }        
+            </style>
+        </styles>
+    </mgrs_graticule>
+    
+    <geodetic_graticule name="Geodetic" visible="false">
+        <color>#ffff007f</color>
+        <label_color>#ffffffff</label_color>
+        <grid_lines>10</grid_lines>
+        <resolutions>10 5 2.5 1.0 0.5 0.25 0.125 0.0625 0.3125</resolutions>
+    </geodetic_graticule>
+    
+</map>
diff --git a/tests/intersect_filter.earth b/tests/intersect_filter.earth
index e8821a9..0fb084b 100644
--- a/tests/intersect_filter.earth
+++ b/tests/intersect_filter.earth
@@ -17,16 +17,15 @@ to a polygon mask.
         <styles>
             <style>
                 stroke: #ffff00;
-                altitude-clamping: terrain-scene;
                 stroke-tessellation-size: 10km;
+                render-depth-offset: true;
             </style>
         </styles>
     </model>
     
-    <model name="cities" driver="feature_geom" comment="Cities in France">
+    <model name="Cities in France" driver="feature_geom">
         <features name="cities" driver="ogr">
-            <url>../data/ne_cities.shp</url>
-            <profile>spherical-mercator</profile>
+            <url>../data/cities.shp</url>
             <filters>
                 <intersect>
                     <comment>
diff --git a/tests/land_cover_mixed.earth b/tests/land_cover_mixed.earth
new file mode 100644
index 0000000..e06f009
--- /dev/null
+++ b/tests/land_cover_mixed.earth
@@ -0,0 +1,84 @@
+<map>
+    <options>
+        <terrain driver="rex"/>
+    </options>
+    
+    <xi:include href="../data/land_cover_dictionary.xml"/>
+
+    <land_cover name="Land Cover Raster" max_data_level="15">
+        <cache_policy usage="no_cache"/>
+
+        <coverages>
+            <coverage name="ESA GLOBCOVER" driver="gdal">
+                <url>H:/data/esa/GLOBCOVER_L4_200901_200912_V2.3_Ant_tiled.tif</url>
+                <profile>global-geodetic</profile>
+                <xi:include href="../data/land_cover_ESA_GLOBCOVER.xml"/>
+                <warp>0.05</warp>
+            </coverage>  
+
+            <coverage name="NCLD 2011" driver="gdal">
+                <url>H:/data/nlcd/nlcd_2011_landcover_2011_edition_2014_03_31.tif</url>
+                <xi:include href="H:/data/nlcd/land_cover_NLCD.xml"/>
+            </coverage>
+            
+            <coverage name="US Highways" driver="agglite" min_level="10" max_data_level="15" coverage="true">
+                <cache_policy usage="no_cache"/>
+                <features name="roads-data" driver="ogr">
+                    <url>H:/data/fhwa/nhpn2005_08/NHPNLine-mercator.shp</url>
+                    <build_spatial_index>true</build_spatial_index>
+                </features>
+                <styles>
+                    <style type="text/css">
+                        default {
+                            stroke-width: 22m;
+                            coverage-value: 1;
+                        }
+                    </style>
+                </styles>                
+                <land_cover_mappings>
+                    <mapping value="1" class="asphalt"/>
+                </land_cover_mappings>
+            </coverage> 
+        </coverages>
+        
+        <noise_lod>12</noise_lod>
+
+        <shared_sampler>CLASSMAP</shared_sampler>
+        <shared_matrix>CLASSMAP_matrix</shared_matrix>
+    </land_cover>
+
+    <terrainshader>
+        <code>
+            <![CDATA[
+            #pragma vp_entryPoint colorize
+            #pragma vp_location fragment_coloring
+            uniform sampler2D CLASSMAP;
+            uniform mat4 CLASSMAP_matrix;
+            vec4 oe_layer_tilec;
+            void colorize(inout vec4 color) {
+                int c = int(texture(CLASSMAP, (CLASSMAP_matrix*oe_layer_tilec).st).r);
+                ivec3 v;
+                if      (c == 1)  v.rgb = ivec3(0, 64, 0);      // forest
+                else if (c == 2)  v.rgb = ivec3(0, 127, 0);     // cropland
+                else if (c == 3)  v.rgb = ivec3(0, 192, 0);     // grassland
+                else if (c == 4)  v.rgb = ivec3(192, 192, 128); // savanna
+                else if (c == 5)  v.rgb = ivec3(40, 50, 70);    // swamp
+                else if (c == 6)  v.rgb = ivec3(192, 128, 64);  // desert
+                else if (c == 7)  v.rgb = ivec3(100, 100, 100); // rock
+                else if (c == 8)  v.rgb = ivec3(64, 64, 128);   // water
+                else if (c == 9)  v.rgb = ivec3(250, 250, 250); // tundra
+                else if (c == 10) v.rgb = ivec3(128, 128, 128); // urban
+                else if (c == 11) v.rgb = ivec3(48, 48, 48);    // asphalt
+                else v.rgb = ivec3(0,0,0);
+                color.rgb = vec3(float(v.r)/255.0, float(v.g)/255.0, float(v.b)/255.0);
+            }
+          ]]>
+        </code>
+    </terrainshader>
+
+    <elevation name="ReadyMap 90m" driver="tms">
+        <url>http://readymap.org/readymap/tiles/1.0.0/116/</url>
+    </elevation>
+    
+    <xi:include href="viewpoints.xml"/>
+</map>
diff --git a/tests/lod_blending.earth b/tests/lod_blending.earth
deleted file mode 100644
index b39cf8b..0000000
--- a/tests/lod_blending.earth
+++ /dev/null
@@ -1,31 +0,0 @@
-<!--
-osgEarth Sample
-LOD Blending creates smoother transitions between levels of detail.
--->
-<map name="readymap.org" type="geocentric" version="2">
-    
-    <options>
-        <!-- elevation_tile_size must be an odd number for morphing. -->
-		<elevation_interpolation>triangulate</elevation_interpolation>
-        
-        <!-- extend the min_lod so we can see MORE morphing. -->
-        <terrain first_lod="1" min_lod="19"/>
-    </options>
-    
-    <image name="readymap_imagery" driver="tms" visible="true">
-        <url>http://readymap.org/readymap/tiles/1.0.0/7/</url>
-    </image>
-        
-    <elevation name="readymap_elevation" driver="tms">
-        <url>http://readymap.org/readymap/tiles/1.0.0/9/</url>
-    </elevation>
-    
-    <external>
-        <lod_blending>
-            <duration>1.0</duration>
-			<blend_imagery>true</blend_imagery>
-			<blend_elevation>true</blend_elevation>
-        </lod_blending>
-    </external>
-    
-</map>
diff --git a/tests/mapbox.earth b/tests/mapbox.earth
new file mode 100644
index 0000000..cf548c1
--- /dev/null
+++ b/tests/mapbox.earth
@@ -0,0 +1,178 @@
+<!--
+Demo of MapBox's satellite, terrain and streets layers
+
+Please change your API key to your own MapBox account, this key is for demo purposes only.
+-->
+
+<map name="MapBox" type="geocentric" version="2">
+
+
+    <image name="mapbox_satellite" driver="xyz">
+        <url>http://a.tiles.mapbox.com/v4/mapbox.satellite/{z}/{x}/{y}.jpg?access_token=YOUR_TOKEN_HERE</url>
+        <profile>spherical-mercator</profile>
+    </image>
+
+
+    <elevation name="mapbox_terrain" driver="xyz" max_data_level="14">
+        <url>http://api.mapbox.com/v4/mapbox.terrain-rgb/{z}/{x}/{y}.pngraw?access_token=YOUR_TOKEN_HERE</url>
+        <profile>spherical-mercator</profile>
+        <elevation_encoding>mapbox</elevation_encoding>
+    </elevation>
+
+
+    <model name="mapbox_streets" driver="feature_geom">
+
+        <features name="mapbox_streets" driver="xyz">
+            <url>http://[abcd].tiles.mapbox.com/v4/mapbox.mapbox-streets-v7/{z}/{x}/{y}.vector.pbf?access_token=YOUR_TOKEN_HERE</url>
+            <!-- Change this to pick what level you want to view the mapbox data at, just keep min_level and max_level the same -->
+            <min_level>15</min_level>
+            <max_level>15</max_level>
+            <profile>spherical-mercator</profile>
+            <format>pbf</format>
+            <invert_y>true</invert_y>
+        </features>
+
+
+        <instancing>true</instancing>
+
+        <feature_indexing enabled="true"></feature_indexing>
+
+        <styles>
+
+            <library name="us_resources">
+                <url>../data/resources/textures_us/catalog.xml</url>
+            </library>
+
+            <style type="text/css">
+                hospital {
+                     icon: "../data/hospital.png";
+                     icon-align:    center-center;
+                     icon-declutter: true;
+                     text-content:  getName();
+                     altitude-clamping:       terrain;
+                     altitude-resolution: 0.001;
+                }
+
+                school {
+                     icon: "../data/school.png";
+                     icon-align:    center-center;
+                     icon-declutter: true;
+                     text-content:  getName();
+                     altitude-clamping:       terrain;
+                     altitude-resolution: 0.001;
+                }
+
+                bank {
+                     icon: "../data/bank.png";
+                     icon-align:    center-center;
+                     icon-declutter: true;
+                     text-content:  getName();
+                     altitude-clamping:       terrain;
+                     altitude-resolution: 0.001;
+                }
+
+                forest {
+                   model:               "../data/tree.ive";
+                   model-placement:     random;
+                   model-density:       10000;
+                   model-scale:         2.5;
+                   model-random-seed:   1;
+                   altitude-clamping:       terrain;
+                   altitude-resolution: 0.001;
+                }
+
+                grass {
+                   model:               "../data/tree.ive";
+                   model-placement:     random;
+                   model-density:       10000;
+                   model-scale:         2.5;
+                   model-random-seed:   1;
+                   altitude-clamping:       terrain;
+                   altitude-resolution: 0.001;
+                }
+
+                water {
+                   fill:               #6BA8FF;
+                   render-depth-test:  false;
+                   altitude-clamping:  terrain-drape;
+                }
+
+                buildings {
+                  extrusion-height:      getBuildingHeight();
+                  extrusion-flatten:     true;
+                  extrusion-wall-style:  building-wall;
+                  extrusion-roof-style:  building-rooftop;
+                  altitude-clamping:     terrain;
+                  altitude-resolution: 0.001;
+                }
+
+                building-wall {
+                  skin-library:     us_resources;
+                  skin-tags:        building;
+                  skin-random-seed: 1;
+                  fill:             #ffffff;
+                }
+                building-rooftop {
+                  skin-library:     us_resources;
+                  skin-tags:        rooftop;
+                  skin-tiled:       true;
+                  skin-random-seed: 1;
+                  fill:             #ffffff;
+                }
+
+                roads {
+                  stroke:             #656363;
+                  stroke-width:       5m;
+                  altitude-clamping:  terrain-drape;
+                  stroke-tessellation-size: 5m;
+                  render-order: 100;
+                }
+
+            </style>
+
+            <selector name="default" style_expr="selectStyle()"/>
+
+            <script language="javascript">
+              <![CDATA[
+
+                function selectStyle() {
+
+                    var layer = feature.properties["mvt_layer"];
+                    if (layer === "building") return "buildings";
+                    if (layer === "water") return "water";
+                    if (layer === "road") return "roads";
+                    if (layer === "landuse") {
+                         var cls = feature.properties["class"];
+                         if (cls === "grass") {
+                             return "forest";
+                         }
+                         else if (cls === "wood") {
+                             return "forest";
+                         }
+                    }
+                    return null;
+                }
+
+                function getName() {
+                    if ("name" in feature.properties) {
+                        return feature.properties["name"];
+                    }
+                    return "";
+                }
+
+                function getBuildingHeight() {
+                    return feature.properties["height"];
+                }
+              ]]>
+            </script>
+
+        </styles>
+    </model>
+
+
+
+
+
+    <xi:include href="viewpoints.xml"/>
+
+</map>
diff --git a/tests/mask.earth b/tests/mask.earth
index 120bac3..518f9ce 100644
--- a/tests/mask.earth
+++ b/tests/mask.earth
@@ -8,17 +8,16 @@ Demonstrates the use a a MaskLayer to cut out an area of the globe.
         <url>../data/world.tif</url>
     </image>
     
-    <!-- masks out the US State of Utah -->
-    <mask driver="feature" name="mask">
+    <feature_mask name="mask">
         <features driver="ogr">
             <geometry>
                 POLYGON(( -111.0466 42.0015 0, -111.0467 40.9979 0, -109.0501 41.0007 0, -109.0452 36.9991 0, -114.0506 37.0004 0, -114.0417 41.9937 0)) 
             </geometry>
         </features>
         <profile>global-geodetic</profile>
-    </mask>
+    </feature_mask>
     
-    <options lighting="false">
+    <options>
 	    <terrain min_lod="14"/>
 	</options>
 </map>
diff --git a/tests/mb_tiles.earth b/tests/mb_tiles.earth
index 5bd2f84..7674c4a 100644
--- a/tests/mb_tiles.earth
+++ b/tests/mb_tiles.earth
@@ -1,13 +1,15 @@
 <!--
 osgEarth Sample
-
-This example shows how to access an MBTiles dataset.  The MBTiles datasets are very large so they are no in the osgEarth repository.  You can download samples from http://mapbox.com/#/
+This example shows how to access an MBTiles dataset. 
+The MBTiles datasets are very large so they are no in the osgEarth repository.
+You can download the sample from:
+https://www.arcgis.com/home/item.html?id=7b650618563741ca9a5186c1aa69126e
 -->
 
 <map name="MBTiles" type="geocentric" version="2">    
       
-    <image name="haiti" driver="mbtiles">
-        <filename>../data/world.mbtiles</filename>
+    <image name="Sample MBTiles Database" driver="mbtiles">
+        <filename>world_countries.mbtiles</filename>
     </image>
    
 </map>
diff --git a/tests/min_max_range.earth b/tests/min_max_range.earth
index 00d63c6..4c093f8 100644
--- a/tests/min_max_range.earth
+++ b/tests/min_max_range.earth
@@ -18,4 +18,8 @@ TIP: set your OSG_NUM_HTTP_DATABASE_THREADS to 4 or more!
         <url>http://readymap.org/readymap/tiles/1.0.0/7/</url>
     </image>
     
+    <options>
+        <terrain attenuation_distance="6000000"/>
+    </options>
+    
 </map>
diff --git a/tests/multiple_heightfields.earth b/tests/multiple_heightfields.earth
index 09010a9..b9afd51 100644
--- a/tests/multiple_heightfields.earth
+++ b/tests/multiple_heightfields.earth
@@ -10,7 +10,10 @@ lo-res heightfield underlay.
 
 <map type="geocentric" version="2">
 
-    <image driver="gdal" name="world-tiff">
+    
+    <contour_map opacity="1"/>
+    
+    <image driver="gdal" name="world-tiff" enabled="false">
         <url>../data/world.tif</url>
     </image>
 
@@ -30,6 +33,4 @@ lo-res heightfield underlay.
         <lighting>false</lighting>
         <cache_policy usage="no_cache"/>
     </options>
-    
-    <contour_map opacity="0.35"/>
 </map>
\ No newline at end of file
diff --git a/tests/nodata.earth b/tests/nodata.earth
index ff8e6f7..270495a 100644
--- a/tests/nodata.earth
+++ b/tests/nodata.earth
@@ -8,12 +8,12 @@ Demonstrates the use of a file with nodata.  The white circle is a GeoTiff which
         <url>../data/world.tif</url>
         <caching_policy usage="no_cache"/>
     </image>
-	
-	<image driver="gdal" name="nodata-tiff" cache_enabled="false">
+
+    <image driver="gdal" name="nodata-tiff" cache_enabled="false">
         <url>../data/nodata.tif</url>
         <caching_policy usage="no_cache"/>
     </image>
-    
+
     <options lighting="false"/>
 
     <viewpoints>
diff --git a/tests/noise.earth b/tests/noise.earth
deleted file mode 100644
index ec1be7c..0000000
--- a/tests/noise.earth
+++ /dev/null
@@ -1,77 +0,0 @@
-<!-- 
-osgEarth Sample
--->
-
-<map version="2">
-
-    <options>
-        <terrain normal_maps="true"/>
-    </options>
-
-    <elevation name="readymap_elevation" driver="tms">
-        <url>http://readymap.org/readymap/tiles/1.0.0/116/</url>
-    </elevation>
-    
-    <noise size="1024" num_channels="1"/>
-    
-    <contour_map/>
-    
-    <sky_simple hours="20.0"/>
-    
-    <terrainshader>
-      <code><![CDATA[
-      
-        #version 330
-        
-        #pragma vp_entryPoint "makeSomeNoise"
-        #pragma vp_location   "fragment_coloring"
-        
-        // Try modifying these for fun.
-        uniform float baseLOD = 1;
-         
-        uniform sampler2D oe_noise_tex;
-        
-        uniform vec4 oe_tile_key;
-        in vec4 oe_layer_tilec;
-        
-        vec2 getNoiseCoords(in float lod)
-        {
-            float dL        = oe_tile_key.z - lod;
-            float factor    = exp2(dL);
-            float invFactor = 1.0/factor;
-            vec2  result    = oe_layer_tilec.st * vec2(invFactor);
-
-            // For upsampling we need to calculate an offset as well
-            if ( factor >= 1.0 )
-            {
-                vec2 a = floor(oe_tile_key.xy * invFactor);
-                vec2 b = a * factor;
-                vec2 c = (a+1.0) * factor;
-                vec2 offset = (oe_tile_key.xy-b)/(c-b);
-                result += offset;
-            }
-            return result;
-        }
-
-        void makeSomeNoise(inout vec4 color)
-        {
-            vec2 coords = getNoiseCoords( floor(baseLOD) );
-            float n = 1.5 * texture( oe_noise_tex, coords ).r;
-            color.rgb *= n;
-        }
-        
-      ]]></code>
-    </terrainshader>
-    
-    <viewpoints>
-        <viewpoint name="Wash St. 430K" heading="-1.002577141807595" height="3694.875054217875" lat="46.85393268075167" long="-121.7764141794478" pitch="-89.85464953482169" range="426454.3850159062"/>
-        <viewpoint name="Mt R. Nadir 30K" heading="0.5013023037097585" height="4101.627114404924" lat="46.85909894548915" long="-121.7598368518208" pitch="-89.43249895879129" range="29029.34246828893"/>
-        <viewpoint name="Mt R. Oblique 30K" heading="17.33521725357022" height="2462.60273069609" lat="46.82181702111031" long="-121.7814936386096" pitch="-21.29241356548601" range="23926.75258864516"/>
-        <viewpoint name="Mt R. Closeup" heading="-109.6842970297122" height="3843.486737414263" lat="46.85528453766688" long="-121.7455004166102" pitch="-4.617466338845979" range="951.4780720092711"/>
-        <viewpoint name="Mt R. Trees" heading="-98.36122712710565" height="1639.304918398149" lat="46.78673277044066" long="-121.743286318636" pitch="-10.85365380742088" range="257.5853045645545"/>
-        <viewpoint name="Nepal" heading="-72.70835146844568" height="6334.845537136309" lat="27.94213038800919" long="86.9936567556778" pitch="-18.63803872963365" range="13611.24948464565"/>
-        <viewpoint name="Nepal NF" heading="-49.14546953546358" height="6334.332569343038" lat="27.9421778947837" long="86.9935949004298" pitch="-3.643325527310435" range="13302.81192964212"/>
-        <viewpoint name="Matterhorn" heading="-1.429462844200832" height="2282.858508689329" lat="45.95106319557" long="7.642741711675961" pitch="-25.12269405854052" range="26690.10606054494"/>
-    </viewpoints>
-
-</map>
diff --git a/tests/normalmap.earth b/tests/normalmap.earth
deleted file mode 100644
index 29aed15..0000000
--- a/tests/normalmap.earth
+++ /dev/null
@@ -1,22 +0,0 @@
-<!--
-osgEarth Sample - Shows an elevation-derived normal map.
--->
-<map name="readymap.org" type="geocentric">    
-
-    <options>
-        <terrain normal_maps="true"/>
-    </options>
-    
-    <image name="readymap_imagery" driver="tms">
-        <url>http://readymap.org/readymap/tiles/1.0.0/7/</url>
-    </image>
-        
-    <elevation name="readymap_elevation" driver="tms">
-        <url>http://readymap.org/readymap/tiles/1.0.0/116/</url>
-    </elevation>
-    
-    <sky_simple/>
-    
-    <xi:include href="viewpoints.xml"/>
-
-</map>
diff --git a/tests/ocean.earth b/tests/ocean.earth
index 4c03981..7f850a9 100644
--- a/tests/ocean.earth
+++ b/tests/ocean.earth
@@ -13,33 +13,28 @@ http://readymap.org
 -->
 <map name="readymap.org" type="geocentric" version="2">
 
+    <options>
+        <terrain driver="rex"/>
+    </options>
+    
     <image name="ReadyMap.org - Imagery" driver="tms">
         <url>http://readymap.org/readymap/tiles/1.0.0/7/</url>
     </image>
         
     <elevation name="ReadyMap.org - Elevation" driver="tms">
-        <url>http://readymap.org/readymap/tiles/1.0.0/9/</url>
+        <url>http://readymap.org/readymap/tiles/1.0.0/116/</url>
     </elevation>
     
+    <simple_ocean>      
+        <color>#1D2C4FF7</color>
+        <max_altitude>500000</max_altitude>
+        <mask_layer>ocean_mask</mask_layer>
+    </simple_ocean>
     
-    <!-- Ocean parameters. -->
-    <ocean driver="simple">
-        
-        <!-- Masking layer to use (optional...without a masking layer, osgEarth will sample the
-             terrain elevation data to determine where the ocean is.
-        -->
-        <mask_layer driver="tms">
-            <url>http://readymap.org/readymap/tiles/1.0.0/2/</url>
-        </mask_layer>
-        
-        <!-- surface color -->
-        <base_color>#1c6ba0f7</base_color>
-        
-        <max_altitude>250000</max_altitude>
+    <image name="ocean_mask" visible="false" shared="true" driver="tms">
+        <url>http://readymap.org/readymap/tiles/1.0.0/2/</url>
+    </image>
         
-    </ocean>
-    
-    
     <viewpoints>
         <viewpoint name="Los Angeles" heading="35.27" height="97.48" lat="34.051" long="-117.974" pitch="-17" range="136405"/>
         <viewpoint name="Above water" heading="-76.17264538992794" height="-199.5569639196619" lat="33.27975381179682" long="-118.3307776586542" pitch="-10.06523772274543" range="3739.161570538204"/>
diff --git a/tests/ocean.earth b/tests/ocean_no_elevation.earth
similarity index 50%
copy from tests/ocean.earth
copy to tests/ocean_no_elevation.earth
index 4c03981..6a049c6 100644
--- a/tests/ocean.earth
+++ b/tests/ocean_no_elevation.earth
@@ -13,33 +13,55 @@ http://readymap.org
 -->
 <map name="readymap.org" type="geocentric" version="2">
 
+    <options>
+        <terrain driver="rex"/>
+    </options>
+    
     <image name="ReadyMap.org - Imagery" driver="tms">
         <url>http://readymap.org/readymap/tiles/1.0.0/7/</url>
     </image>
         
+    <!--
     <elevation name="ReadyMap.org - Elevation" driver="tms">
-        <url>http://readymap.org/readymap/tiles/1.0.0/9/</url>
+        <url>http://readymap.org/readymap/tiles/1.0.0/116/</url>
     </elevation>
+    -->
     
+    <simple_ocean>      
+        <color>#1D2C4FFF</color>
+        <max_altitude>1e10</max_altitude>
+        <mask_layer>ocean_mask</mask_layer>
+        <use_bathymetry>false</use_bathymetry>
+        <shader_define>IS_OCEAN</shader_define>
+    </simple_ocean>
     
-    <!-- Ocean parameters. -->
-    <ocean driver="simple">
-        
-        <!-- Masking layer to use (optional...without a masking layer, osgEarth will sample the
-             terrain elevation data to determine where the ocean is.
-        -->
-        <mask_layer driver="tms">
-            <url>http://readymap.org/readymap/tiles/1.0.0/2/</url>
-        </mask_layer>
-        
-        <!-- surface color -->
-        <base_color>#1c6ba0f7</base_color>
-        
-        <max_altitude>250000</max_altitude>
-        
-    </ocean>
-    
+    <image name="ocean_mask" visible="false" shared="true" driver="tms">
+        <url>http://readymap.org/readymap/tiles/1.0.0/2/</url>
+        <shared_sampler>ocean_mask</shared_sampler>
+        <shared_matrix>ocean_mask_matrix</shared_matrix>
+    </image>
     
+    <terrainshader>
+        <code><![CDATA[
+            #version 330
+            #pragma vp_entryPoint discardTheOcean
+            #pragma vp_location fragment_coloring
+            #pragma import_defines(IS_OCEAN)
+            
+            uniform sampler2D ocean_mask;
+            uniform mat4 ocean_mask_matrix;
+            vec4 oe_layer_tilec;
+            
+            void discardTheOcean(inout vec4 color) {
+              #ifndef IS_OCEAN
+                vec4 mask = texture(ocean_mask, (ocean_mask_matrix*oe_layer_tilec).st);
+                if (mask.a > 0.75)
+                    discard;
+              #endif
+            }
+        ]]></code>
+    </terrainshader>
+        
     <viewpoints>
         <viewpoint name="Los Angeles" heading="35.27" height="97.48" lat="34.051" long="-117.974" pitch="-17" range="136405"/>
         <viewpoint name="Above water" heading="-76.17264538992794" height="-199.5569639196619" lat="33.27975381179682" long="-118.3307776586542" pitch="-10.06523772274543" range="3739.161570538204"/>
diff --git a/tests/openstreetmap_buildings.earth b/tests/openstreetmap_buildings.earth
index 9053325..2838c32 100644
--- a/tests/openstreetmap_buildings.earth
+++ b/tests/openstreetmap_buildings.earth
@@ -9,32 +9,28 @@ You must have the mapnik vector tiles driver built by including the protobuf lib
     <options>
         <terrain driver="rex"/>
     </options>
-    
-    <model name="buildings" driver="feature_geom">
-    
-        <features name="buildings" driver="tfs">
-            <url>http://readymap.org/osm/osm/osm/</url>
-            <invert_y>true</invert_y>
-            <min_level>14</min_level>
-            <max_level>14</max_level>
-            <profile>spherical-mercator</profile>
-            <format>pbf</format>    
-            <filters>
-                <script>
-                 <![CDATA[
-                   ("building" in feature.properties);
-                 ]]>
-                </script>
-            </filters>           
-        </features>
 
-        <styles>                
+    <feature_source name="buildings-data" driver="tfs">
+        <url>http://readymap.org/osm-buildings/</url>
+        <min_level>14</min_level>
+        <max_level>14</max_level>
+        <profile>spherical-mercator</profile>
+        <format>pbf</format>
+        <filters>
+            <script>
+              <![CDATA[ ("building" in feature.properties); ]]>
+            </script>
+        </filters>
+    </feature_source>
+
+    <feature_model name="buildings" feature_source="buildings-data">
+        <styles>
             <library name="us_resources">
                 <url>../data/resources/textures_us/catalog.xml</url>
-            </library>            
+            </library>
 
             <style type="text/css">
-                buildings {
+                default {
                     extrusion-height:      getHeight();
                     extrusion-flatten:     true;
                     extrusion-wall-style:  building-wall;
@@ -43,7 +39,7 @@ You must have the mapnik vector tiles driver built by including the protobuf lib
                     altitude-technique:    map;
                     altitude-binding:      vertex;
                     altitude-resolution:   0;
-                }            
+                }
                 building-wall {
                     skin-library:     us_resources;
                     skin-tags:        building;
@@ -58,9 +54,9 @@ You must have the mapnik vector tiles driver built by including the protobuf lib
                     fill:             #ffffff;
                 }
             </style>
-            
+
             <script>
-                function getHeight() {                    
+                function getHeight() {
 					if ("height" in feature.properties) {
 						var h = feature.properties["height"].replace('m','');
 						return Math.max(h, 4.0);
@@ -75,7 +71,7 @@ You must have the mapnik vector tiles driver built by including the protobuf lib
                 }
             </script>
         </styles>
-    </model>
+    </feature_model>
 
     <image name="osm_mapnik" driver="xyz">
         <url>http://[abc].tile.openstreetmap.org/{z}/{x}/{y}.png</url>
@@ -87,7 +83,7 @@ You must have the mapnik vector tiles driver built by including the protobuf lib
         <url>http://readymap.org/readymap/tiles/1.0.0/116/</url>
         <vdatum>egm96</vdatum>
     </elevation>
-    
+
     <xi:include href="viewpoints.xml"/>
-  
+
 </map>
diff --git a/tests/openstreetmap_flat.earth b/tests/openstreetmap_flat.earth
index f918732..9dfebe3 100644
--- a/tests/openstreetmap_flat.earth
+++ b/tests/openstreetmap_flat.earth
@@ -8,11 +8,7 @@ http://wiki.openstreetmap.org/wiki/Slippy_map_tilenames
 
     <options>
         <profile>spherical-mercator</profile>
-        <terrain driver="mp" 
-            tile_size="2" 
-            range_mode="PIXEL_SIZE_ON_SCREEN"
-            tile_pixel_size="400"
-            skirt_ratio="0"/>
+        <terrain driver="rex" tile_size="2" morph_imagery="false"/>
     </options>
     
     <image name="osm_mapnik" driver="xyz">
@@ -21,4 +17,5 @@ http://wiki.openstreetmap.org/wiki/Slippy_map_tilenames
         <cache_policy usage="none"/>
     </image>
     
+    <xi:include href="viewpoints_flat.xml"/>
 </map>
diff --git a/tests/openstreetmap_full.earth b/tests/openstreetmap_full.earth
index 97cc147..5651edd 100644
--- a/tests/openstreetmap_full.earth
+++ b/tests/openstreetmap_full.earth
@@ -6,19 +6,13 @@ You must have the mapnik vector tiles driver built by including the protobuf lib
 -->
 <map name="TFS" type="geocentric" version="2">
 
- 
- 
-
-    <model name="osm" driver="feature_geom">
-
-   
-       <features name="osm" driver="tfs">
-            <url>http://readymap.org/osm/osm/osm/</url>
-            <invert_y>true</invert_y>
+    <feature_model name="osm">
+        <features name="osm" driver="tfs">
+            <url>http://readymap.org/osm/</url>
             <min_level>14</min_level>
             <max_level>14</max_level>
             <profile>spherical-mercator</profile>
-            <format>pbf</format>            
+            <format>pbf</format>
         </features>
 
         <instancing>true</instancing>
@@ -26,7 +20,7 @@ You must have the mapnik vector tiles driver built by including the protobuf lib
         <feature_indexing enabled="false"></feature_indexing>
 
         <styles>
-            
+
             <library name="us_resources">
                 <url>../data/resources/textures_us/catalog.xml</url>
             </library>
@@ -38,7 +32,7 @@ You must have the mapnik vector tiles driver built by including the protobuf lib
                      icon-declutter: true;
                      text-content:  getName();
                      altitude-clamping:       terrain;
-                }     
+                }
 
                 school {
                      icon: "../data/school.png";
@@ -46,7 +40,7 @@ You must have the mapnik vector tiles driver built by including the protobuf lib
                      icon-declutter: true;
                      text-content:  getName();
                      altitude-clamping:       terrain;
-                }     
+                }
 
                 bank {
                      icon: "../data/bank.png";
@@ -63,7 +57,7 @@ You must have the mapnik vector tiles driver built by including the protobuf lib
                    model-scale:         2.5;
                    model-random-seed:   1;
                    altitude-clamping:       terrain;
-                }            
+                }
 
                 grass {
                    model:               "../data/tree.ive";
@@ -86,7 +80,7 @@ You must have the mapnik vector tiles driver built by including the protobuf lib
                   extrusion-wall-style:  building-wall;
                   extrusion-roof-style:  building-rooftop;
                   altitude-clamping:     terrain;
-                }            
+                }
 
                 building-wall {
                   skin-library:     us_resources;
@@ -105,9 +99,9 @@ You must have the mapnik vector tiles driver built by including the protobuf lib
             </style>
 
             <selector name="default" style_expr="selectStyle()"/>
-            
+
             <script language="javascript">
-              <![CDATA[
+                <![CDATA[
 
                 function selectStyle() {
                     if ("building" in feature.properties) {
@@ -120,7 +114,7 @@ You must have the mapnik vector tiles driver built by including the protobuf lib
                              landuse == "meadow"
                             ) {
                                 return "forest";
-                            }                                            
+                            }
                         else if (landuse == "reservoir") {
                             return "water";
                         }
@@ -152,7 +146,7 @@ You must have the mapnik vector tiles driver built by including the protobuf lib
                             return "bank";
                         }
                     }
-                    return null;                    
+                    return null;
                 }
 
                 function getName() {
@@ -161,8 +155,8 @@ You must have the mapnik vector tiles driver built by including the protobuf lib
                     }
                     return "";
                 }
-                
-                function getBuildingHeight() {                    
+
+                function getBuildingHeight() {
 					if ("height" in feature.properties) {
 						var h = feature.properties["height"].replace('m','');
 						return Math.max(h, 4.0);
@@ -179,9 +173,9 @@ You must have the mapnik vector tiles driver built by including the protobuf lib
             </script>
 
         </styles>
-    </model>
- 
- 
+    </feature_model>
+
+
 
     <image name="esri imagery" driver="arcgis">
         <url>http://server.arcgisonline.com/ArcGIS/rest/services/ESRI_Imagery_World_2D/MapServer</url>
@@ -189,47 +183,33 @@ You must have the mapnik vector tiles driver built by including the protobuf lib
     </image>
 
     <image name="osm_roads" driver="agglite" min_level="14">
-      
-       <max_data_level>14</max_data_level>
-       
-       <profile>spherical-mercator</profile>
-        
-        <features name="roads" driver="tfs">
-            <url>http://readymap.org/osm/osm/osm/</url>
-            <invert_y>true</invert_y>
+
+        <max_data_level>14</max_data_level>
+
+        <profile>spherical-mercator</profile>
+
+        <features name="osm" driver="tfs">
+            <url>http://readymap.org/osm/</url>
             <min_level>14</min_level>
             <max_level>14</max_level>
             <profile>spherical-mercator</profile>
-            <format>pbf</format>            
-
+            <format>pbf</format>
             <filters>
                 <script language="javascript">
-                    <![CDATA[
-                    function filter() {
-                        if ("highway" in feature.properties) {
-                            return true;             
-                        }
-                        return false;
-                    }
-
-                    filter();                    
-
-                    ]]>
+                    <![CDATA[ ("highway" in feature.properties) ]]>
                 </script>
             </filters>
         </features>
-
-
         <styles>
             <style type="text/css">
                 roads {
                   stroke: #656363;
                   stroke-width: 15m;
                   stroke-linecap: square;
-                }           
+                }
             </style>
         </styles>
-        
+
         <cache_policy usage="none"/>
     </image>
 
@@ -237,6 +217,6 @@ You must have the mapnik vector tiles driver built by including the protobuf lib
     <elevation name="readymap_elevation" driver="tms">
         <url>http://readymap.org/readymap/tiles/1.0.0/116/</url>
     </elevation>
-    
+
     <xi:include href="viewpoints.xml"/>
 </map>
diff --git a/tests/openweathermap_clouds.earth b/tests/openweathermap.earth
similarity index 58%
rename from tests/openweathermap_clouds.earth
rename to tests/openweathermap.earth
index edde2c9..9522fad 100644
--- a/tests/openweathermap_clouds.earth
+++ b/tests/openweathermap.earth
@@ -17,14 +17,22 @@ http://readymap.org
         <url>http://readymap.org/readymap/tiles/1.0.0/22/</url>
     </image>
 
-    <image name="clouds" driver="xyz">
+    <image name="Cloud Cover" driver="xyz" visible="true">
         <url>http://[abc].tile.openweathermap.org/map/clouds/{z}/{x}/{y}.png</url>
         <profile>spherical-mercator</profile>
         <cache_policy usage="no_cache"/>
     </image>
 
-    <elevation name="readymap_elevation" driver="tms">
-        <url>http://readymap.org/readymap/tiles/1.0.0/9/</url>
-    </elevation>
+    <image name="Precipitation" driver="xyz" visible="false">
+        <url>http://[abc].tile.openweathermap.org/map/precipitation/{z}/{x}/{y}.png</url>
+        <profile>spherical-mercator</profile>
+        <cache_policy usage="no_cache"/>
+    </image>
+
+    <image name="Pressure" driver="xyz" visible="false">
+        <url>http://[abc].tile.openweathermap.org/map/pressure_cntr/{z}/{x}/{y}.png</url>
+        <profile>spherical-mercator</profile>
+        <cache_policy usage="no_cache"/>
+    </image>
 
 </map>
diff --git a/tests/openweathermap_precipitation.earth b/tests/openweathermap_precipitation.earth
deleted file mode 100644
index 83cef88..0000000
--- a/tests/openweathermap_precipitation.earth
+++ /dev/null
@@ -1,31 +0,0 @@
-<!--
-osgEarth Sample - OpenWeatherMap Precipitation
-
-ReadyMap.ORG provides free global base map data for osgEarth developers!
-This tiled, worldwide dataset of imagery, elevation, and street map data
-is a great base map that provides global context for your own local datasets.
-It works "out of the box" with osgEarth applications.
-
-**** NOTICE ****
-YOU ARE RESPONSIBLE for abiding by the TERMS AND CONDITIONS outlined at:
-http://readymap.org
-
--->
-<map name="readymap.org" type="geocentric">
-
-    <image name="readymap_imagery" driver="tms">
-        <url>http://readymap.org/readymap/tiles/1.0.0/22/</url>
-    </image>
-
-    <image name="clouds" driver="xyz">
-        <url>http://[abc].tile.openweathermap.org/map/precipitation/{z}/{x}/{y}.png</url>
-        <profile>spherical-mercator</profile>
-        <cache_policy usage="no_cache"/>
-    </image>
-
-    <elevation name="readymap_elevation" driver="tms">
-        <url>http://readymap.org/readymap/tiles/1.0.0/9/</url>
-    </elevation>
-
-
-</map>
diff --git a/tests/openweathermap_pressure.earth b/tests/openweathermap_pressure.earth
deleted file mode 100644
index 797be7a..0000000
--- a/tests/openweathermap_pressure.earth
+++ /dev/null
@@ -1,31 +0,0 @@
-<!--
-osgEarth Sample - OpenWeatherMap Pressure
-
-ReadyMap.ORG provides free global base map data for osgEarth developers!
-This tiled, worldwide dataset of imagery, elevation, and street map data
-is a great base map that provides global context for your own local datasets.
-It works "out of the box" with osgEarth applications.
-
-**** NOTICE ****
-YOU ARE RESPONSIBLE for abiding by the TERMS AND CONDITIONS outlined at:
-http://readymap.org
-
--->
-<map name="readymap.org" type="geocentric">
-
-    <image name="readymap_imagery" driver="tms">
-        <url>http://readymap.org/readymap/tiles/1.0.0/22/</url>
-    </image>
-
-    <image name="clouds" driver="xyz">
-        <url>http://[abc].tile.openweathermap.org/map/pressure_cntr/{z}/{x}/{y}.png</url>
-        <profile>spherical-mercator</profile>
-        <cache_policy usage="no_cache"/>
-    </image>
-
-    <elevation name="readymap_elevation" driver="tms">
-        <url>http://readymap.org/readymap/tiles/1.0.0/9/</url>
-    </elevation>
-
-
-</map>
diff --git a/tests/readymap.earth b/tests/readymap-elevation-only.earth
similarity index 83%
copy from tests/readymap.earth
copy to tests/readymap-elevation-only.earth
index 42fd5ea..b48f5cf 100644
--- a/tests/readymap.earth
+++ b/tests/readymap-elevation-only.earth
@@ -12,14 +12,14 @@ http://readymap.org
 
 -->
 <map name="readymap.org" type="geocentric">    
-    
-    <image name="readymap_imagery" driver="tms">
-        <url>http://readymap.org/readymap/tiles/1.0.0/7/</url>
-    </image>
-        
+            
     <elevation name="readymap_elevation" driver="tms">
         <url>http://readymap.org/readymap/tiles/1.0.0/116/</url>
     </elevation>
     
     <xi:include href="viewpoints.xml"/>
+    
+    <options>
+        <terrain driver="rex"/>
+    </options>
 </map>
diff --git a/tests/readymap-priority.earth b/tests/readymap-priority.earth
deleted file mode 100644
index e04e888..0000000
--- a/tests/readymap-priority.earth
+++ /dev/null
@@ -1,35 +0,0 @@
-<!--
-osgEarth Sample - ReadyMap.ORG Server - http://readymap.org
-
-ReadyMap.ORG provides free global base map data for osgEarth developers!
-This tiled, worldwide dataset of imagery, elevation, and street map data
-is a great base map that provides global context for your own local datasets.
-It works "out of the box" with osgEarth applications.
-
-**** NOTICE ****
-YOU ARE RESPONSIBLE for abiding by the TERMS AND CONDITIONS outlined at:
-http://readymap.org
-
--->
-<map name="readymap.org" type="geocentric">
-    
-    <options>
-        <terrain first_lod="1" driver="rex" high_resolution_first="true"/>
-    </options>
-    
-    <image name="readymap_imagery" driver="tms">
-        <url>http://readymap.org/readymap/tiles/1.0.0/7/</url>
-    </image>
-        
-    <elevation name="readymap_elevation" driver="tms">
-        <url>http://readymap.org/readymap/tiles/1.0.0/116/</url>
-    </elevation>
-    
-    <extensions>        
-        <viewpoints>
-            <viewpoint name="Washington St" heading="0.5013023037097585" height="4101.627114404924" lat="46.85909894548915" long="-121.7598368518208" pitch="-89.43249895879129" range="29029.34246828893"/>
-            <viewpoint name="Nepal" heading="-72.70835146844568" height="6334.845537136309" lat="27.94213038800919" long="86.9936567556778" pitch="-18.63803872963365" range="13611.24948464565"/>
-            <viewpoint name="Matterhorn" heading="-1.429462844200832" height="2282.858508689329" lat="45.95106319557" long="7.642741711675961" pitch="-25.12269405854052" range="26690.10606054494"/>
-        </viewpoints>
-    </extensions>
-</map>
diff --git a/tests/readymap-rex.earth b/tests/readymap-rex.earth
index 8b6e8de..b567a62 100644
--- a/tests/readymap-rex.earth
+++ b/tests/readymap-rex.earth
@@ -11,6 +11,7 @@
                  morph_terrain         = "true"
                  morph_imagery         = "true"
                  high_resolution_first = "true"
+                 progressive           = "false"
                  skirt_ratio           = "0.0"
                  normal_maps           = "true">
         </terrain>
@@ -24,8 +25,6 @@
         <url>http://readymap.org/readymap/tiles/1.0.0/116/</url>
     </elevation>
     
-    <extensions>
-        <xi:include href="viewpoints.xml"/>
-    </extensions>
+    <xi:include href="viewpoints.xml"/>
     
 </map>
diff --git a/tests/readymap.earth b/tests/readymap.earth
index 42fd5ea..dc89878 100644
--- a/tests/readymap.earth
+++ b/tests/readymap.earth
@@ -11,15 +11,15 @@ YOU ARE RESPONSIBLE for abiding by the TERMS AND CONDITIONS outlined at:
 http://readymap.org
 
 -->
-<map name="readymap.org" type="geocentric">    
-    
+<map name="readymap.org" type="geocentric">
+
     <image name="readymap_imagery" driver="tms">
         <url>http://readymap.org/readymap/tiles/1.0.0/7/</url>
     </image>
-        
-    <elevation name="readymap_elevation" driver="tms">
+
+    <elevation name="readymap_elevation" driver="tms" vdatum="egm96">
         <url>http://readymap.org/readymap/tiles/1.0.0/116/</url>
     </elevation>
-    
+
     <xi:include href="viewpoints.xml"/>
 </map>
diff --git a/tests/readymap_pixel_size.earth b/tests/readymap_flat.earth
similarity index 78%
rename from tests/readymap_pixel_size.earth
rename to tests/readymap_flat.earth
index 6843049..bc3af29 100644
--- a/tests/readymap_pixel_size.earth
+++ b/tests/readymap_flat.earth
@@ -11,10 +11,11 @@ YOU ARE RESPONSIBLE for abiding by the TERMS AND CONDITIONS outlined at:
 http://readymap.org
 
 -->
-<map name="readymap.org" type="geocentric">
+<map name="readymap.org" type="projected">    
     
     <options>
-        <terrain first_lod="1" tile_pixel_size="256" range_mode="PIXEL_SIZE_ON_SCREEN"/>
+        <profile>plate-carre</profile>
+        <terrain driver="rex" tile_size="2" morph_terrain="false"/>
     </options>
     
     <image name="readymap_imagery" driver="tms">
@@ -24,8 +25,4 @@ http://readymap.org
     <elevation name="readymap_elevation" driver="tms">
         <url>http://readymap.org/readymap/tiles/1.0.0/116/</url>
     </elevation>
-    
-    <extensions>
-        <xi:include href="viewpoints.xml"/>
-    </extensions>
 </map>
diff --git a/tests/roads-flattened.earth b/tests/roads-flattened.earth
new file mode 100644
index 0000000..df309d6
--- /dev/null
+++ b/tests/roads-flattened.earth
@@ -0,0 +1,198 @@
+<map>
+
+    <options>
+        <terrain driver="rex">
+        </terrain>
+    </options>
+
+    <elevation name="ReadyMap Elevation" driver="tms">
+        <url>http://readymap.org/readymap/tiles/1.0.0/116/</url>
+    </elevation>
+
+    <fractal_elevation name="Fractal" min_level="8" offset="true">
+        <noise_image>../data/seamless-noise-1.png</noise_image>
+        <amplitude>7</amplitude>
+    </fractal_elevation>
+
+    <flattened_elevation name="Road Flattener" min_level="14" max_data_level="16" enabled="true" cache_enabled="false">
+        <script language="javascript">
+            <![CDATA[
+            function getLineWidth() {
+                var hwy = feature.properties["highway"];
+                if (hwy === "motorway" || hwy === "trunk" || hwy == "primary" || hwy === "secondary") {
+                    return 12.0;
+                }
+                return 4.0;
+            }
+
+            function getBufferWidth() {
+                return 12.0;
+            }
+        ]]>
+        </script>
+
+        <feature_source>roads-data</feature_source>
+        <line_width>getLineWidth()</line_width>
+        <buffer_width>getBufferWidth()</buffer_width>
+    </flattened_elevation>
+
+    <!-- Procedural terrain imagery from land cover data -->
+    <splat_imagery name="Splat Imagery" land_cover_layer="LandCover">
+        <zones>
+            <zone name="default">
+                <surface>
+                    <catalog>../data/splat/splat_catalog.xml</catalog>
+                </surface>
+            </zone>
+        </zones>
+    </splat_imagery>
+
+    <!-- Road surface rendering -->
+    <road_surface name="RoadSurface" min_level="13" max_data_level="19" shared="true" cacheid="road_surface">
+        <cache_policy usage="no_cache"/>
+        <tile_size>256</tile_size>
+        <feature_source>roads-data</feature_source>
+        <styles>
+            <script language="javascript">
+                <![CDATA[
+                function selectStyle() {
+                    if ("highway" in feature.properties) {
+                        var hwy = feature.properties["highway"];
+                        if (hwy === "motorway" || hwy === "trunk" || hwy == "primary" || hwy === "secondary") {
+                            return "default";
+                        }
+                        return "asphalt";
+                    }
+                    return "default";
+                }
+            ]]>
+            </script>
+
+            <selector name="default" style_expr="selectStyle()"/>
+
+            <style type="text/css">
+                default {
+                   stroke: #ffffff;
+                   stroke-width: 12m;
+                   stroke-image: ../data/road.png;
+                }
+                asphalt {
+                   stroke: #ffffff;
+                   stroke-width: 4m;
+                   stroke-image: ../data/asphalt.jpg;
+                }
+            </style>
+        </styles>
+
+        <buffer_width>12m</buffer_width>
+    </road_surface>
+
+    <!-- GPU trees from land cover data -->
+    <splat_groundcover name="Trees" land_cover_layer="LandCover">
+        <lod>14</lod>
+        <cast_shadows>true</cast_shadows>
+        <mask_layer>RoadSurface</mask_layer>
+        <zones>
+            <zone name="default">
+                <groundcover>
+                    <max_distance>6400</max_distance>
+                    <density>2.0</density>
+                    <fill>0.85</fill>
+                    <brightness>3.0</brightness>
+                    <contrast>0.5</contrast>
+                    <biomes>
+                        <biome classes="forest">
+                            <billboard url="../data/splat/cypress.png" width="12.0" height="14.0"/>
+                            <billboard url="../data/splat/pine.png"    width="16.0" height="22.0" />
+                            <billboard url="../data/splat/pine2.png"   width="15.0" height="18.0"/>
+                        </biome>
+                    </biomes>
+                </groundcover>
+            </zone>
+        </zones>
+    </splat_groundcover>
+
+    <viewpoints>
+        <viewpoint name="Rainier">
+            <heading>17.33521725357022</heading>
+            <height>2462.60273069609</height>
+            <lat>46.82181702111031</lat>
+            <long>-121.7814936386096</long>
+            <pitch>-21.29241356548601</pitch>
+            <range>23926.75258864516</range>
+        </viewpoint>
+        <viewpoint name="Road 1">
+            <heading>-20.9485</heading>
+            <pitch>-2.03292</pitch>
+            <range>129.689m</range>
+            <long>-121.6310413445486</long>
+            <lat>46.7629136754553</lat>
+            <height>933.7423594370484</height>
+            <srs>+proj=longlat +ellps=WGS84 +towgs84=0,0,0,0,0,0,0 +no_defs </srs>
+        </viewpoint>
+        <viewpoint name="Road 2">
+            <heading>7.34763</heading>
+            <pitch>-15.3353</pitch>
+            <range>1133.16m</range>
+            <long>-121.6146067722042</long>
+            <lat>46.73737123277496</lat>
+            <height>868.470358453691</height>
+            <srs>+proj=longlat +ellps=WGS84 +towgs84=0,0,0,0,0,0,0 +no_defs </srs>
+        </viewpoint>
+        <viewpoint name="Junction">
+            <heading>-0.384023</heading>
+            <pitch>-65.4952</pitch>
+            <range>294.541m</range>
+            <long>-121.63970476221</long>
+            <lat>46.75991953164118</lat>
+            <height>953.2630590070039</height>
+            <srs>+proj=longlat +ellps=WGS84 +towgs84=0,0,0,0,0,0,0 +no_defs </srs>
+        </viewpoint>
+        <viewpoint name="Overlap">
+            <heading>-57.5371</heading>
+            <pitch>-56.6654</pitch>
+            <range>95.9873m</range>
+            <long>-121.6366175925203</long>
+            <lat>46.76520332634789</lat>
+            <height>941.8131768433377</height>
+            <srs>+proj=longlat +ellps=WGS84 +towgs84=0,0,0,0,0,0,0 +no_defs </srs>
+        </viewpoint>
+
+    </viewpoints>
+
+    <!-- Land cover dictionary -->
+    <xi:include href="../data/land_cover_dictionary.xml"/>
+
+    <!-- Land cover layer -->
+    <land_cover name="LandCover" max_data_level="15">
+        <cache_policy usage="no_cache"/>
+        <coverages>
+            <coverage name="ESA GLOBCOVER" driver="gdal">
+                <profile>global-geodetic</profile>
+                <url>H:/data/esa/GLOBCOVER_L4_200901_200912_V2.3_Ant_tiled.tif</url>
+                <xi:include href="../data/land_cover_ESA_GLOBCOVER.xml"/>
+            </coverage>
+            <coverage name="NCLD 2011" driver="gdal">
+                <url>H:/data/nlcd/nlcd_2011_landcover_2011_edition_2014_03_31.tif</url>
+                <xi:include href="H:/data/nlcd/land_cover_NLCD.xml"/>
+                <warp>0.01</warp>
+            </coverage>
+        </coverages>
+    </land_cover>
+
+    <!-- Road features -->
+    <feature_source name="roads-data" driver="tfs">
+        <url>http://readymap.org/osm/</url>
+        <min_level>14</min_level>
+        <max_level>14</max_level>
+        <profile>spherical-mercator</profile>
+        <format>pbf</format>
+        <filters>
+            <script language="javascript">
+                <![CDATA[ ("highway" in feature.properties); ]]>
+            </script>
+        </filters>
+    </feature_source>
+
+    <libraries>osgEarthSplat</libraries>
+</map>
diff --git a/tests/roads-test.earth b/tests/roads-test.earth
new file mode 100644
index 0000000..744875e
--- /dev/null
+++ b/tests/roads-test.earth
@@ -0,0 +1,107 @@
+<!--
+osgEarth Sample - ReadyMap.ORG Server - http://readymap.org
+
+ReadyMap.ORG provides free global base map data for osgEarth developers!
+This tiled, worldwide dataset of imagery, elevation, and street map data
+is a great base map that provides global context for your own local datasets.
+It works "out of the box" with osgEarth applications.
+
+**** NOTICE ****
+YOU ARE RESPONSIBLE for abiding by the TERMS AND CONDITIONS outlined at:
+http://readymap.org
+
+-->
+<map name="readymap.org" type="geocentric">
+    
+    <options>
+        <terrain driver="rex" max_lod="18">
+        </terrain>
+    </options>
+
+    <image name="readymap_imagery" driver="tms" visible="true" opacity="0.5">
+        <url>http://readymap.org/readymap/tiles/1.0.0/7/</url>
+    </image>
+    
+    <image name="Tilekeys" driver="debug">
+    </image>
+
+    <road_surface name="road-surface" min_level="14" max_level="18" shared="true" cacheid="road_surface">
+        <cache_policy usage="no_cache"/>
+        <tile_size>512</tile_size>
+        <feature_source>roads-data</feature_source>
+        <style type="text/css">
+            default {
+               stroke: #ffffff;
+               stroke-width: 65m;
+               stroke-image: "../data/road.png";
+            }                    
+        </style>
+    </road_surface>
+    
+    <feature_model name="Road verts">
+        <feature_source>roads-data</feature_source>
+        <styles>
+            <style type="text/css">
+                default {
+                    point-fill: #00ffff;
+                    point-size: 12;
+                    stroke: #ffff00;
+                    stroke-width: 2px;
+                    render-depth-test: false;
+                    render-order: 1;
+                }
+            </style>
+        </styles>
+    </feature_model>
+    
+    <feature_model name="Road skeleton">
+        <feature_source>roads-data</feature_source>
+        <styles>
+            <style type="text/css">
+                default {
+                    stroke: #00ffff7f;
+                    stroke-width: 65m;
+                    render-depth-test: false;
+                    render-order: 2;
+                    stroke-image: "../data/grid.png";
+                }
+            </style>
+        </styles>
+    </feature_model>
+    
+    <feature_source name="roads-data" driver="tfs">
+        <url>http://readymap.org/osm/</url>
+        <min_level>14</min_level>
+        <max_level>14</max_level>
+        <profile>spherical-mercator</profile>
+        <format>pbf</format>
+        <filters>
+            <script language="javascript">
+              <![CDATA[ ("highway" in feature.properties); ]]>
+            </script>
+        </filters>
+    </feature_source>
+    
+    <viewpoints>
+        <viewpoint name="45 Lat">
+            <heading>-1.6803</heading>
+            <pitch>-89.8584</pitch>
+            <range>135.785m</range>
+            <long>-121.629455948385</long>
+            <lat>46.76105148181426</lat>
+            <height>0.0005331700667738915</height>
+            <srs>+proj=longlat +ellps=WGS84 +towgs84=0,0,0,0,0,0,0 +no_defs </srs>
+        </viewpoint>
+        <viewpoint name="Equator">
+            <heading>-2.31914e-008</heading>
+            <pitch>-89.0007</pitch>
+            <range>3664.39m</range>
+            <long>-80.08167477509062</long>
+            <lat>-0.001110080780562921</lat>
+            <height>-3.306940197944641e-005</height>
+            <srs>+proj=longlat +ellps=WGS84 +towgs84=0,0,0,0,0,0,0 +no_defs </srs>
+        </viewpoint>
+    </viewpoints>
+    
+    <libraries>osgEarthSplat</libraries>
+</map>
diff --git a/tests/roads.earth b/tests/roads.earth
new file mode 100644
index 0000000..365cbae
--- /dev/null
+++ b/tests/roads.earth
@@ -0,0 +1,109 @@
+<!--
+osgEarth Sample - ReadyMap.ORG Server - http://readymap.org
+
+ReadyMap.ORG provides free global base map data for osgEarth developers!
+This tiled, worldwide dataset of imagery, elevation, and street map data
+is a great base map that provides global context for your own local datasets.
+It works "out of the box" with osgEarth applications.
+
+**** NOTICE ****
+YOU ARE RESPONSIBLE for abiding by the TERMS AND CONDITIONS outlined at:
+http://readymap.org
+
+-->
+<map name="readymap.org" type="geocentric">
+
+    <options>
+        <terrain driver="rex" progressive="true">
+        </terrain>
+    </options>
+
+    <image name="readymap_imagery" driver="tms" visible="true" opacity="0.5">
+        <url>http://readymap.org/readymap/tiles/1.0.0/7/</url>
+    </image>
+
+    <image name="Tilekeys" driver="debug">
+    </image>
+
+    <road_surface name="road-surface" min_level="14" max_level="18">
+        <cache_policy usage="no_cache"/>
+        <tile_size>512</tile_size>
+        <feature_source>roads-data</feature_source>
+        <styles>
+            <style type="text/css">
+                default {
+                   stroke: #ffffff;
+                   stroke-width: 65m;
+                   stroke-image: "../data/road.png";
+                }
+            </style>
+        </styles>
+    </road_surface>
+
+    <feature_model name="Road verts">
+        <feature_source>roads-data</feature_source>
+        <styles>
+            <style type="text/css">
+                default {
+                    point-fill: #00ffff;
+                    point-size: 12;
+                    stroke: #ffff00;
+                    stroke-width: 2px;
+                    render-depth-test: false;
+                    render-order: 1;
+                }
+            </style>
+        </styles>
+    </feature_model>
+
+    <feature_model name="Road skeleton" visible="false">
+        <feature_source>roads-data</feature_source>
+        <styles>
+            <style type="text/css">
+                default {
+                    stroke: #00ffff7f;
+                    stroke-width: 65m;
+                    render-depth-test: false;
+                    render-order: 2;
+                    stroke-image: "../data/grid.png";
+                }
+            </style>
+        </styles>
+    </feature_model>
+
+    <feature_source name="roads-data" driver="tfs">
+        <url>http://readymap.org/osm/</url>
+        <min_level>14</min_level>
+        <max_level>14</max_level>
+        <profile>spherical-mercator</profile>
+        <format>pbf</format>
+        <filters>
+            <script language="javascript">
+              <![CDATA[ ("highway" in feature.properties); ]]>
+            </script>
+        </filters>
+    </feature_source>
+
+    <viewpoints>
+        <viewpoint name="45 Lat">
+            <heading>-1.6803</heading>
+            <pitch>-89.8584</pitch>
+            <range>135.785m</range>
+            <long>-121.629455948385</long>
+            <lat>46.76105148181426</lat>
+            <height>0.0005331700667738915</height>
+            <srs>+proj=longlat +ellps=WGS84 +towgs84=0,0,0,0,0,0,0 +no_defs </srs>
+        </viewpoint>
+        <viewpoint name="Equator">
+            <heading>-2.31914e-008</heading>
+            <pitch>-89.0007</pitch>
+            <range>3664.39m</range>
+            <long>-80.08167477509062</long>
+            <lat>-0.001110080780562921</lat>
+            <height>-3.306940197944641e-005</height>
+            <srs>+proj=longlat +ellps=WGS84 +towgs84=0,0,0,0,0,0,0 +no_defs </srs>
+        </viewpoint>
+    </viewpoints>
+
+    <libraries>osgEarthSplat</libraries>
+</map>
diff --git a/tests/scene_clamping.earth b/tests/scene_clamping.earth
new file mode 100644
index 0000000..3506407
--- /dev/null
+++ b/tests/scene_clamping.earth
@@ -0,0 +1,130 @@
+<!--
+osgEarth Sample - Annotations
+-->
+<map name="readymap.org" type="geocentric" version="2">
+
+    <image name="ReadyMap.org - Imagery" driver="tms">
+        <url>http://readymap.org/readymap/tiles/1.0.0/7/</url>
+    </image>
+
+    <elevation name="ReadyMap.org - Elevation" driver="tms">
+        <url>http://readymap.org/readymap/tiles/1.0.0/116/</url>
+    </elevation>
+
+    <image driver="debug" visible="false"/>
+
+    <viewpoints time="0.1">
+        <viewpoint name="Circles">
+            <heading>25.2721</heading>
+            <pitch>-24.7961</pitch>
+            <range>22653.9m</range>
+            <long>-159.6077378781463</long>
+            <lat>22.08047563003597</lat>
+            <height>1057.80545252189</height>
+            <srs>+proj=longlat +ellps=WGS84 +towgs84=0,0,0,0,0,0,0 +no_defs </srs>
+        </viewpoint>
+    </viewpoints>
+
+    <annotations>
+    
+        <circle name="Circle clamped directly to the ground">
+            <position lat="22.074" long="-159.606"/>
+            <radius value="1.2" units="km"/>
+            <style type="text/css">
+                stroke:             #ffffff;
+                stroke-width:       2px;
+                altitude-clamping:  relative;
+                altitude-technique: scene;
+                altitude-binding:   vertex;
+                render-depth-offset-auto: true;
+            </style>
+        </circle>
+        <label text="WHITE = clamped to ground" lat="22.074" long="-159.5"/>
+        
+        <circle name="Circle clamped with an altitude offset">
+            <position lat="22.074" long="-159.606"/>
+            <radius value="1.2" units="km"/>
+            <style type="text/css">
+                stroke:             #ffff00;
+                stroke-width:       2px;
+                altitude-clamping:  relative;
+                altitude-technique: scene;
+                altitude-binding:   vertex;
+                altitude-offset:    1000;
+            </style>
+        </circle>
+        <label text="YELLOW = clamped relative to ground" lat="22.17" long="-159.606"/>
+             
+        <circle name="Circle clamped above the ground by its centroid">
+            <position lat="22.074" long="-159.606"/>
+            <radius value="1.2" units="km"/>
+            <style type="text/css">
+                stroke:             #00ffff;
+                stroke-width:       2px;
+                altitude-clamping:  relative;
+                altitude-technique: scene;
+                altitude-binding:   centroid;
+                altitude-offset:    2000;
+            </style>
+        </circle>
+        <label text="CYAN = clamped relative to ground by centroid" lat="22.074" long="-159.7"/>
+        
+        
+        <feature name="Feature clamped to ground">
+            <srs>wgs84</srs>
+            <geometry>
+                LINESTRING(-159.606 22.074, -159.606 22.5)
+            </geometry>
+            <style type="text/css">
+                stroke:              #ffffff;
+                stroke-width:        3;
+                stroke-tessellation-size: 500m;
+                altitude-clamping:   terrain;
+                altitude-technique:  scene;
+                render-depth-offset-auto: true;
+            </style>
+        </feature>
+        
+        <feature name="Feature clamped relative to ground">
+            <srs>wgs84</srs>
+            <geometry>
+                LINESTRING(-159.606 22.074, -159.606 22.5)
+            </geometry>
+            <style type="text/css">
+                stroke:              #ffff00;
+                stroke-width:        3;
+                stroke-tessellation-size: 500m;
+                altitude-clamping:   relative;
+                altitude-technique:  scene;
+                altitude-offset:     1000;
+            </style>
+        </feature>
+        
+        <local_geometry name="Local Geometry tessellated and clamped">
+            <geometry>
+                LINESTRING(0 0, 25000 0, 25000 25000, 0 25000, 0 0);
+            </geometry>
+            <position lat="22.078" long="-159.602"/>
+            <style type="text/css">
+                stroke:          #ff00ff;
+                stroke-width:    3px;
+                stroke-tessellation: 128;
+                render-lighting: false;
+                altitude-clamping: terrain;
+                altitude-technique: scene;
+                render-depth-offset-auto: true;
+            </style>
+        </local_geometry>
+        
+        
+        <place name="Placemark" text="Placemark">
+            <position lat="22.074" long="-159.606"/>
+            <icon>../data/placemark32.png</icon>
+            <style type="text/css">
+                text-declutter: true;
+                text-halo:      #777;
+            </style>
+        </place>
+
+    </annotations>
+</map>
diff --git a/tests/gdal_tiff.earth b/tests/simple.earth
similarity index 57%
rename from tests/gdal_tiff.earth
rename to tests/simple.earth
index 73ef1f4..1f29988 100644
--- a/tests/gdal_tiff.earth
+++ b/tests/simple.earth
@@ -3,9 +3,8 @@ osgEarth Sample - GDAL Driver
 Demonstrates the simplest possible use of the GDAL driver to load a GeoTIFF image.
 -->
 
-<map version="2">
-    <image driver="gdal" name="world-tiff" cache_enabled="false">
+<map>
+    <image name="World GeoTIFF" driver="gdal">
         <url>../data/world.tif</url>
-        <cache_policy usage="no_cache"/>
     </image>
-</map>
\ No newline at end of file
+</map>
diff --git a/tests/splat-blended-with-imagery.earth b/tests/splat-blended-with-imagery.earth
new file mode 100644
index 0000000..b3b0a39
--- /dev/null
+++ b/tests/splat-blended-with-imagery.earth
@@ -0,0 +1,47 @@
+<!--
+|  Procedural terrain splatting blended with satellite imagery.
+|  As you get close to the ground, the imagery is replaced with splatting.
+-->
+
+<map>
+    <options>
+        <terrain driver="rex" attenuation_distance="250000">
+        </terrain>
+    </options>
+
+    <libraries>osgEarthSplat</libraries>
+    
+    <xi:include href="../data/land_cover_dictionary.xml"/>
+    
+    <land_cover name="LandCover" max_data_level="15">
+        <cache_policy usage="no_cache"/>
+        <coverages>
+            <coverage name="ESA GLOBCOVER" driver="gdal">
+                <url>H:/data/esa/GLOBCOVER_L4_200901_200912_V2.3_Ant_tiled.tif</url>
+                <profile>global-geodetic</profile>
+                <xi:include href="../data/land_cover_ESA_GLOBCOVER.xml"/>
+                <warp>0.05</warp>
+            </coverage>
+        </coverages>
+    </land_cover>
+    
+    <elevation name="readymap_elevation" driver="tms" enabled="true">
+        <url>http://readymap.org/readymap/tiles/1.0.0/116/</url>
+    </elevation>
+        
+    <splat_imagery name="Splat Imagery" land_cover_layer="LandCover">
+        <zones>
+            <zone name="default">
+                <surface>
+                    <catalog>../data/splat/splat_catalog.xml</catalog>
+                </surface>
+            </zone>
+        </zones>
+    </splat_imagery>
+    
+    <image name="readymap_imagery" driver="tms" min_range="500000">
+        <url>http://readymap.org/readymap/tiles/1.0.0/7/</url>
+    </image>
+            
+    <xi:include href="viewpoints.xml"/>
+</map>
diff --git a/tests/splat-edit.bat b/tests/splat-detail-tool.bat
similarity index 100%
rename from tests/splat-edit.bat
rename to tests/splat-detail-tool.bat
diff --git a/tests/splat-gpunoise.bat b/tests/splat-gpunoise.bat
deleted file mode 100644
index 4be402d..0000000
--- a/tests/splat-gpunoise.bat
+++ /dev/null
@@ -1,31 +0,0 @@
-setlocal
-set OSGEARTH_SPLAT_EDIT=1
-set OSGEARTH_SPLAT_GPU_NOISE=1
-osgearth_viewer splat.earth ^
-	--ico ^
-	--logdepth ^
-	--sky ^
-	--uniform oe_splat_blending_range 200000 0 ^
-	--uniform oe_splat_detail_range 100000 0 ^
-	--uniform oe_splat_warp 0 0.01 ^
-	--uniform oe_splat_blur 1 4 ^
-	--uniform oe_splat_scaleOffset 0 7 ^
-	--uniform oe_splat_useBilinear 1 -1 ^
-	--uniform oe_splat_freq 1 128 ^
-	--uniform oe_splat_pers 0.1 6.0 ^
-	--uniform oe_splat_lac  0.1 6.0 ^
-	--uniform oe_splat_octaves 1 9 ^
-	--uniform oe_splat_noiseScale 1 21 ^
-	--uniform oe_splat_saturation 0 1 ^
-	--uniform oe_splat_threshold 0 1 ^
-	--uniform oe_splat_minSlope 0 1 ^
-	--uniform oe_splat_contrast 1 4 ^
-	--uniform oe_splat_brightness 1 10 ^
-	--uniform oe_splat_snowMinElevation 8000 1 ^
-	--uniform oe_splat_snowPatchiness 1 6 ^
-	--uniform oe_bumpmap_intensity 0 2.0 ^
-	--uniform oe_bumpmap_scale 1 20 ^
-	%*
-endlocal
-
-	
\ No newline at end of file
diff --git a/tests/splat-groundcover-tool.bat b/tests/splat-groundcover-tool.bat
new file mode 100644
index 0000000..59a18d3
--- /dev/null
+++ b/tests/splat-groundcover-tool.bat
@@ -0,0 +1,15 @@
+ at echo off
+rem Batch file to play with the Ground Cover parameters
+setlocal
+osgearth_viewer splat.earth ^
+    --sky ^
+    --logdepth ^
+    --samples 4 ^
+    --uniform oe_GroundCover_density 1 7 ^
+    --uniform oe_GroundCover_fill 1 0 ^
+    --uniform oe_GroundCover_brightness 1 3 ^
+    --uniform oe_GroundCover_contrast 0 1 ^
+    --uniform oe_GroundCover_maxDistance 7000 200 ^
+    --uniform oe_GroundCover_windFactor 0 3 ^
+    %*    
+endlocal
\ No newline at end of file
diff --git a/tests/splat-ranges.earth b/tests/splat-ranges.earth
deleted file mode 100644
index a652eaa..0000000
--- a/tests/splat-ranges.earth
+++ /dev/null
@@ -1,55 +0,0 @@
-<!--
-|  Procedural terrain splatting.
-|  Run with splat.bat, (or splat-edit.bat for tweakery)
--->
-
-<map>
-        
-    <options>
-        <terrain driver="rex" 
-                 tile_size="17"
-                 high_resolution_first="true" 
-                 merges_per_frame="50"
-                 skirt_ratio="0.01">
-        </terrain>
-    </options>
-    
-    <elevation name="readymap_elevation" driver="tms" enabled="true">
-        <url>http://readymap.org/readymap/tiles/1.0.0/116/</url>
-    </elevation>
-     
-    <image name="CLASSMAP" driver="landuse" shared="true" visible="false" coverage="true" max_data_level="15">
-        <base_lod>12</base_lod>
-        <tile_size>256</tile_size>
-        <cache_policy usage="none"/>
-        <images>
-            <image name="ESA" driver="gdal" coverage="true">
-                <url>H:/data/esa/GLOBCOVER_L4_200901_200912_V2.3_Ant_tiled.tif</url>
-                <warp>0.01</warp>
-            </image>
-        </images>
-    </image>
-
-    <extensions>
-   
-        <splat>        
-            <coverage>
-                <layer> CLASSMAP </layer>
-                <legend>../data/splat/GLOBCOVER_legend.xml</legend>
-            </coverage>
-            
-            <zones>
-                <zone name="default" doc="Default Climate Zone">            
-                    <surface>
-                        <catalog>../data/splat/splat_catalog_rangetest.xml</catalog>
-                    </surface>
-                </zone>                
-            </zones>
-            
-        </splat>
-            
-        <xi:include href="viewpoints.xml"/>
-        
-    </extensions>
-
-</map>
diff --git a/tests/splat-server.earth b/tests/splat-server.earth
deleted file mode 100644
index 743d1da..0000000
--- a/tests/splat-server.earth
+++ /dev/null
@@ -1,105 +0,0 @@
-<!--
-|  Test for osgearth_server, generating tiles using the GPU in osgEarth and serving them out as TMS tiles!
-|  Run with osgearth_server.
--->
-
-<map type="flat">
-        
-    <options>
-        <profile>spherical-mercator</profile>
-   </options>
-   
-    <elevation name="readymap_elevation" driver="tms" enabled="true">
-        <url>http://readymap.org/readymap/tiles/1.0.0/116/</url>
-    </elevation>
-     
-    <image name="CLASSMAP" driver="landuse" shared="true" visible="false" coverage="true" max_data_level="15">
-        <base_lod>12</base_lod>
-        <tile_size>256</tile_size>
-        <cache_policy usage="none"/>
-        <images>
-            <image name="ESA" driver="gdal" coverage="true">
-                <url>d:/geodata/splatting/GLOBCOVER_L4_200901_200912_V2.3_Ant_tiled.tif</url>
-                <warp>0.01</warp>
-            </image>
-        </images>
-    </image>
-
-    <model name="buildings" driver="feature_geom">
-             
-        <features name="buildings" driver="ogr">
-            <url>../data/boston_buildings_utm19.shp</url>
-            <build_spatial_index>true</build_spatial_index>
-        </features>
-        
-        <layout>
-            <tile_size_factor>45</tile_size_factor>
-            <level name="default" max_range="20000">
-                <selector class="buildings"/>
-            </level>
-        </layout>
-        
-        <styles>            
-            <library name="us_resources">
-                <url>../data/resources/textures_us/catalog.xml</url>
-            </library>
-            
-            <style type="text/css">
-                buildings {
-                    extrusion-height:      3.5 * max([story_ht_], 1);
-                    extrusion-flatten:     true;
-                    extrusion-wall-style:  building-wall;
-                    extrusion-roof-style:  building-rooftop;
-                    altitude-clamping:     none;
-                }            
-                building-wall {
-                    skin-library:     us_resources;
-                    skin-tags:        building;
-                    skin-random-seed: 1;
-                    fill:             #ffffff;
-                }
-                building-rooftop {
-                    skin-library:     us_resources;
-                    skin-tags:        rooftop;
-                    skin-tiled:       true;
-                    skin-random-seed: 1;
-                    fill:             #ffffff;
-                }
-            </style>
-            
-                        
-            <!--Exclude certain buildings from being rendered b/c they will be replaced with geospecific buildings -->
-            <selector class="buildings">
-                <query>
-                    <expr><![CDATA[ OBJECTID_1 <> 91506 and OBJECTID_1 <> 12921 and OBJECTID_1 <> 11460 and OBJECTID_1 <> 11474 and OBJECTID_1 <> 11471 and OBJECTID_1 <> 11439 and OBJECTID_1 <> 11432 and  OBJECTID_1 <> 91499 and OBJECTID_1 <> 10878 ]]> </expr>
-                </query>
-            </selector>         
-            
-        </styles>
-
-        <lighting>true</lighting>        
-    </model>
-
-    <extensions>
-   
-        <splat>        
-            <coverage>
-                <layer> CLASSMAP </layer>
-                <legend>../data/splat/GLOBCOVER_legend.xml</legend>
-            </coverage>
-            
-            <zones>
-                <zone name="default" doc="Default Climate Zone">            
-                    <surface>
-                        <catalog>../data/splat/splat_catalog.xml</catalog>
-                    </surface>
-                </zone>                
-            </zones>
-            
-        </splat>
-            
-        <xi:include href="viewpoints.xml"/>
-        
-    </extensions>
-
-</map>
diff --git a/tests/splat-with-mask-layer.earth b/tests/splat-with-mask-layer.earth
new file mode 100644
index 0000000..29ce89b
--- /dev/null
+++ b/tests/splat-with-mask-layer.earth
@@ -0,0 +1,92 @@
+<!--
+|  Procedural trees with a mask layer.
+|  The mask layer prevents the trees from drawing wherever there is geometry.
+-->
+
+<map>
+    <options>
+        <terrain driver="rex"/>
+    </options>
+
+    <libraries>osgEarthSplat</libraries>
+
+    <xi:include href="../data/land_cover_dictionary.xml"/>
+
+    <land_cover name="LandCover" max_data_level="15">
+        <cache_policy usage="no_cache"/>
+        <coverages>
+            <coverage name="ESA GLOBCOVER" driver="gdal">
+                <url>H:/data/esa/GLOBCOVER_L4_200901_200912_V2.3_Ant_tiled.tif</url>
+                <profile>global-geodetic</profile>
+                <xi:include href="../data/land_cover_ESA_GLOBCOVER.xml"/>
+                <warp>0.05</warp>
+            </coverage>
+        </coverages>
+    </land_cover>
+
+    <elevation name="readymap_elevation" driver="tms" enabled="true">
+        <url>http://readymap.org/readymap/tiles/1.0.0/116/</url>
+    </elevation>
+
+    <splat_imagery name="Splat Imagery" land_cover_layer="LandCover">
+        <zones>
+            <zone name="default">
+                <surface>
+                    <catalog>../data/splat/splat_catalog.xml</catalog>
+                </surface>
+            </zone>
+        </zones>
+    </splat_imagery>
+
+    <splat_groundcover name="Trees" land_cover_layer="LandCover">
+        <lod>13</lod>
+        <cast_shadows>true</cast_shadows>
+        <mask_layer>Boston Streets</mask_layer>
+        <zones>
+            <zone name="default">
+                <groundcover>
+                    <max_distance>10000</max_distance>
+                    <density>4.0</density>
+                    <fill>1.0</fill>
+                    <brightness>3.0</brightness>
+                    <contrast>1.0</contrast>
+                    <biomes>
+                        <biome classes="forest urban grassland cropland">
+                            <billboard url="../data/splat/pine2.png" width="12.0" height="16.0"/>
+                        </biome>
+                    </biomes>
+                </groundcover>
+            </zone>
+        </zones>
+    </splat_groundcover>
+
+    <!-- Setting shared="true" lets us use this as our mask layer -->
+    <image name="Boston Streets" driver="agglite" shared="true" min_level="10" max_data_level="17">
+        <warp>0</warp>
+        <features driver="ogr" build_spatial_index="true">
+            <url>../data/boston-scl-utm19n-meters.shp</url>
+        </features>
+        <styles>
+            <style type="text/css">
+                default {
+                    stroke-width: 10m;
+                    stroke-image: "../data/road.png";
+                }
+            </style>
+        </styles>
+        <cache_policy usage="no_cache"/>
+    </image>
+
+    <viewpoints>
+        <viewpoint name="Tree-Covered Boston">
+            <heading>9.95514</heading>
+            <pitch>-19.0589</pitch>
+            <range>3450.07m</range>
+            <long>-71.06514757538032</long>
+            <lat>42.35534282973394</lat>
+            <height>14.42520617321134</height>
+            <srs>+proj=longlat +ellps=WGS84 +towgs84=0,0,0,0,0,0,0 +no_defs </srs>
+        </viewpoint>
+    </viewpoints>
+
+</map>
diff --git a/tests/splat-with-multiple-zones.earth b/tests/splat-with-multiple-zones.earth
new file mode 100644
index 0000000..29c64c8
--- /dev/null
+++ b/tests/splat-with-multiple-zones.earth
@@ -0,0 +1,116 @@
+<!--
+|  Procedural terrain splatting and ground cover;
+|  Demonstrates the use of multiple geographic zones.
+|
+|  Suggest using --logdepth to see the grass when you get in close.
+-->
+
+<map>
+    <options>
+        <terrain driver="rex"/>
+    </options>
+    
+    <elevation name="readymap_elevation" driver="tms" enabled="true">
+        <url>http://readymap.org/readymap/tiles/1.0.0/116/</url>
+    </elevation>
+
+    <!-- Pre-load the splatting nodekit -->
+    <libraries>osgEarthSplat</libraries>
+    
+    <!-- Land cover requires a dictionary that defines all the core classes -->
+    <xi:include href="../data/land_cover_dictionary.xml"/>
+    
+    <!-- Land cover layer defines world wide classes -->
+    <land_cover name="LandCover" max_data_level="15">
+        <cache_policy usage="no_cache"/>
+        <coverages>
+            <coverage name="ESA GLOBCOVER" driver="gdal">
+                <url>H:/data/esa/GLOBCOVER_L4_200901_200912_V2.3_Ant_tiled.tif</url>
+                <xi:include href="../data/land_cover_ESA_GLOBCOVER.xml"/>
+                <warp>0.05</warp>
+            </coverage>
+        </coverages>
+    </land_cover>
+        
+    <!-- Procedural terrain imagery from land cover data -->
+    <splat_imagery name="Splat Imagery" land_cover_layer="LandCover">
+        <zones>
+            <zone name="default">
+                <surface>
+                    <catalog>../data/splat/splat_catalog.xml</catalog>
+                </surface>
+            </zone>
+            <zone name="australia">
+                <boundaries>
+                    <boundary xmin="112" xmax="154" ymin="-43.50" ymax="-11.16" zmax="2000000"/>
+                </boundaries>
+                <surface>
+                    <catalog>../data/splat/splat_catalog_au.xml</catalog>
+                </surface>
+            </zone>
+        </zones>
+    </splat_imagery>
+    
+    <!-- GPU trees from land cover data -->
+    <splat_groundcover name="Trees" land_cover_layer="LandCover">
+        <lod>13</lod>
+        <cast_shadows>true</cast_shadows>
+        <zones>
+            <zone name="default">
+                <groundcover>
+                    <max_distance>6400</max_distance>
+                    <density>3.4</density>
+                    <fill>0.45</fill>
+                    <brightness>2.0</brightness>
+                    <contrast>0.5</contrast>
+                    <biomes>
+                        <biome classes="forest">
+                            <billboard url="../data/splat/cypress.png" width="12.0" height="14.0"/>
+                            <billboard url="../data/splat/pine.png"    width="16.0" height="22.0" />
+                            <billboard url="../data/splat/pine2.png"   width="15.0" height="18.0"/>
+                        </biome>
+                    </biomes>
+                </groundcover>
+            </zone>
+        </zones>
+    </splat_groundcover>
+
+    <!-- GPU grass from land cover data -->
+    <splat_groundcover name="Grass" land_cover_layer="LandCover">
+        <lod>19</lod>
+        <cast_shadows>false</cast_shadows>
+        <zones>
+            <zone name="default">
+                <groundcover>
+                    <max_distance>150</max_distance>
+                    <density>3.8</density>
+                    <fill>0.50</fill>
+                    <wind>0.15</wind>
+                    <biomes>
+                        <biome name="Grass" classes="forest">
+                            <billboard url="../data/splat/grass1.png" width="0.85" height="0.4"/>
+                        </biome>
+                    </biomes>
+                </groundcover>
+            </zone>
+            <zone name="australia">
+                <boundaries>
+                    <boundary xmin="112" xmax="154" ymin="-43.50" ymax="-11.16" zmax="2000000"/>
+                </boundaries>
+                <groundcover>
+                    <max_distance>150</max_distance>
+                    <density>2</density>
+                    <fill>0.50</fill>
+                    <wind>0.15</wind>
+                    <biomes>
+                        <biome name="Grass" classes="forest">
+                            <billboard url="../data/splat/grass2.png" width="0.85" height="0.4"/>
+                        </biome>
+                    </biomes>
+                </groundcover>
+            </zone>
+        </zones>
+    </splat_groundcover>
+            
+    <xi:include href="viewpoints.xml"/>
+</map>
diff --git a/tests/splat-with-rasterized-land-cover.earth b/tests/splat-with-rasterized-land-cover.earth
new file mode 100644
index 0000000..d3b5a9d
--- /dev/null
+++ b/tests/splat-with-rasterized-land-cover.earth
@@ -0,0 +1,81 @@
+<!--
+|  Procedural terrain splatting and ground cover.
+|  Suggest using --logdepth to see the grass when you get in close.
+-->
+
+<map>
+    <options>
+        <terrain driver="rex"/>
+    </options>
+
+    <!-- Pre-load the splatting nodekit -->
+    <libraries>osgEarthSplat</libraries>
+    
+    <!-- Land cover requires a dictionary that defines all the core classes -->
+    <xi:include href="../data/land_cover_dictionary.xml"/>
+    
+    <!-- Land cover layer defines world wide classes -->
+    <land_cover name="LandCover" max_data_level="15">
+        <cache_policy usage="no_cache"/>
+        <coverages>
+            <coverage name="ESA GLOBCOVER" driver="gdal">
+                <url>H:/data/esa/GLOBCOVER_L4_200901_200912_V2.3_Ant_tiled.tif</url>
+                <profile>global-geodetic</profile>
+                <xi:include href="../data/land_cover_ESA_GLOBCOVER.xml"/>
+                <warp>0.05</warp>
+                <cache_policy usage="no_cache"/>
+            </coverage>
+            
+            <coverage name="Lakes" driver="agglite" coverage="true">
+                <warp>0</warp>
+                <features driver="ogr">
+                    <url>H:/data/naturalearth/vector-10m/ne_10m_lakes.shp</url>
+                </features>
+                <styles>
+                    <style type="text/css">
+                        default {
+                            coverage-value: 1;
+                        }
+                    </style>
+                </styles>
+                <land_cover_mappings>
+                    <mapping value="1" class="water"/>
+                </land_cover_mappings>
+                <cache_policy usage="no_cache"/>
+            </coverage>
+
+            <coverage name="Boston streets" driver="agglite" coverage="true" min_level="10" max_data_level="17">
+                <warp>0</warp>
+                <features driver="ogr" build_spatial_index="true">
+                    <url>../data/boston-scl-utm19n-meters.shp</url>
+                </features>                
+                <styles>
+                    <style type="text/css">
+                        default {
+                            stroke-width: 10m;
+                            coverage-value: 1;
+                        }
+                    </style>
+                </styles>
+                <land_cover_mappings>
+                    <mapping value="1" class="asphalt"/>
+                </land_cover_mappings>
+                <cache_policy usage="no_cache"/>     
+            </coverage>
+        </coverages>
+    </land_cover>
+        
+    <!-- Procedural terrain imagery from land cover data -->
+    <splat_imagery name="Splat Imagery" land_cover_layer="LandCover">
+        <zones>
+            <zone name="default">
+                <surface>
+                    <catalog>../data/splat/splat_catalog.xml</catalog>
+                </surface>
+            </zone>
+        </zones>
+    </splat_imagery>
+
+    
+    <xi:include href="viewpoints.xml"/>
+</map>
diff --git a/tests/splat-with-vectors.earth b/tests/splat-with-vectors.earth
deleted file mode 100644
index f821e74..0000000
--- a/tests/splat-with-vectors.earth
+++ /dev/null
@@ -1,109 +0,0 @@
-<!--
-|  Texture splatting test.
-|
-|  Run with splat.bat, (or splat-edit.bat for tweakery)
--->
-
-<map>
-        
-    <options>
-        <terrain driver="rex" skirt_ratio="0" normal_maps="true"/>
-    </options>
-    
-    <elevation name="readymap_elevation" driver="tms" enabled="true">
-        <url>http://readymap.org/readymap/tiles/1.0.0/116/</url>
-    </elevation>
-        
-    <image name="CLASSMAP" driver="landuse" shared="true" visible="false" coverage="true">
-        <bits>16</bits>
-        <warp>0.01</warp>
-        <base_lod>12</base_lod>
-        
-        <images>
-            <image name="ESA" driver="gdal" coverage="true">
-                <url>H:/data/ESA/GLOBCOVER_L4_200901_200912_V2.3_Ant_tiled.tif</url>
-                <cache_policy usage="no_cache"/>
-            </image>
-            
-            <image name="Lakes" driver="agglite" coverage="true">
-                <warp>0</warp>
-                <features driver="ogr">
-                    <url>H:/data/naturalearth/vector-10m/ne_10m_lakes.shp</url>
-                </features>
-                <styles>
-                    <style type="text/css">
-                        default {
-                            coverage-value: 230;
-                        }
-                    </style>
-                </styles>
-                <cache_policy usage="no_cache"/>
-            </image>
-                    
-            <image name="Boston streets" driver="agglite" coverage="true" min_level="10" max_data_level="17">
-                <warp>0</warp>
-                <features driver="ogr" build_spatial_index="true">
-                    <url>../data/boston-scl-utm19n-meters.shp</url>
-                    <filters>
-                        <buffer distance="5"/>
-                    </filters>
-                </features>                
-                <styles>
-                    <style type="text/css">
-                        default {
-                            coverage-value: 900;
-                        }
-                    </style>
-                </styles>   
-                <cache_policy usage="no_cache"/>     
-            </image>
-            
-        </images>
-        
-        <shared_sampler> landUseTex       </shared_sampler>
-        <shared_matrix>  landUseTexMatrix </shared_matrix>
-        
-        <cache_policy usage="no_cache"></cache_policy>
-    </image>
-    
-    
-    <extensions>
-    
-        <splat>        
-            <coverage>
-                <layer> CLASSMAP </layer>
-                <legend>../data/splat/GLOBCOVER_legend.xml</legend>
-            </coverage>            
-            <zones>
-                <zone name="default" doc="Default Climate Zone">            
-                    <surface>
-                        <catalog>../data/splat/splat_catalog.xml</catalog>
-                    </surface>
-                </zone>                
-            </zones>            
-        </splat>
-        
-        <viewpoints>
-            <viewpoint name="Roads">
-                <heading>-1.71887</heading>
-                <pitch>-15.3643</pitch>
-                <range>2429.2m</range>
-                <long>-71.06130588150768</long>
-                <lat>42.35594985954756</lat>
-                <height>31.36476541217417</height>
-                <srs>+proj=longlat +ellps=WGS84 +towgs84=0,0,0,0,0,0,0 +no_defs </srs>
-            </viewpoint>
-            <viewpoint name="Lake">
-                <heading>-9.40528</heading>
-                <pitch>-34.7212</pitch>
-                <range>453747m</range>
-                <long>32.78247923080883</long>
-                <lat>-1.680124158232579</lat>
-                <height>1133.724704638124</height>
-                <srs>+proj=longlat +ellps=WGS84 +towgs84=0,0,0,0,0,0,0 +no_defs </srs>
-            </viewpoint>
-        </viewpoints>
-        
-    </extensions>
-
-</map>
diff --git a/tests/splat.bat b/tests/splat.bat
deleted file mode 100644
index 27d2112..0000000
--- a/tests/splat.bat
+++ /dev/null
@@ -1,14 +0,0 @@
- at echo off
-setlocal
-osgearth_viewer splat.earth ^
-    --sky ^
-    --logdepth ^
-    --samples 4 ^
-    --uniform oe_landcover_density 1 7 ^
-    --uniform oe_landcover_fill 1 0 ^
-    --uniform oe_landcover_brightness 1 3 ^
-    --uniform oe_landcover_contrast 0 1 ^
-    --uniform oe_landcover_maxDistance 7000 200 ^
-    --uniform oe_landcover_windFactor 0 3 ^
-    %*    
-endlocal
\ No newline at end of file
diff --git a/tests/splat.earth b/tests/splat.earth
index 6b94488..6d993f9 100644
--- a/tests/splat.earth
+++ b/tests/splat.earth
@@ -1,78 +1,92 @@
 <!--
-|  Procedural terrain splatting.
-|  Run with splat.bat, (or splat-edit.bat for tweakery)
+|  Procedural terrain splatting and ground cover.
+|  Suggest using --logdepth to see the grass when you get in close.
 -->
 
 <map>
-        
     <options>
-        <terrain driver="rex" 
-                 tile_size="17"
-                 high_resolution_first="true" 
-                 merges_per_frame="25">
-        </terrain>
+        <terrain driver="rex"/>
     </options>
     
     <elevation name="readymap_elevation" driver="tms" enabled="true">
         <url>http://readymap.org/readymap/tiles/1.0.0/116/</url>
     </elevation>
-     
-    <image name="CLASSMAP" driver="landuse" shared="true" visible="false" coverage="true" max_data_level="15">
-        <base_lod>12</base_lod>
-        <tile_size>256</tile_size>
-        <cache_policy usage="none"/>
-        <images>
-            <image name="ESA" driver="gdal" coverage="true">
-                <url>H:/data/esa/GLOBCOVER_L4_200901_200912_V2.3_Ant_tiled.tif</url>
-                <warp>0.01</warp>
-            </image>
-        </images>
-    </image>
 
-    <bumpmap>
-        <image>../data/rock_hard.jpg</image>
-        <octaves>8</octaves>
-        <intensity>2.2</intensity>
-    </bumpmap>
-   
-    <splat>        
-        <coverage>
-            <layer> CLASSMAP </layer>
-            <legend>../data/splat/GLOBCOVER_legend.xml</legend>
-        </coverage>
+    <!-- Pre-load the splatting nodekit -->
+    <libraries>osgEarthSplat</libraries>
+    
+    <!-- Land cover requires a dictionary that defines all the core classes -->
+    <xi:include href="../data/land_cover_dictionary.xml"/>
+    
+    <!-- Land cover layer defines world wide classes -->
+    <land_cover name="LandCover" max_data_level="15">
+        <cache_policy usage="no_cache"/>
+        <coverages>
+            <coverage name="ESA GLOBCOVER" driver="gdal">
+                <url>H:/data/esa/GLOBCOVER_L4_200901_200912_V2.3_Ant_tiled.tif</url>
+                <profile>global-geodetic</profile>
+                <xi:include href="../data/land_cover_ESA_GLOBCOVER.xml"/>
+                <warp>0.05</warp>
+            </coverage>
+        </coverages>
+    </land_cover>
         
+    <!-- Procedural terrain imagery from land cover data -->
+    <splat_imagery name="Splat Imagery" land_cover_layer="LandCover">
         <zones>
-            <zone name="default" doc="Default Climate Zone">            
+            <zone name="default">
                 <surface>
                     <catalog>../data/splat/splat_catalog.xml</catalog>
                 </surface>
-        
-                <land_cover>
-                    <layers>
-                        <layer name="trees" lod="13" max_distance="6400" density="3.4" fill="0.45" brightness="2.0" contrast="0.5" cast_shadows="true">
-                            <biomes>
-                                <biome classes="forest forest1 forest2 forest3 forest4">
-                                    <billboard url="../data/splat/cypress.png" width="12.0" height="14.0"/>
-                                    <billboard url="../data/splat/pine.png"    width="16.0" height="22.0" />
-                                    <billboard url="../data/splat/pine2.png"   width="15.0" height="18.0"/>
-                                </biome>
-                            </biomes>
-                        </layer>
-                    
-                        <layer name="grass" lod="19" max_distance="150" density="3.8" fill="0.50" wind="0.15">
-                            <biomes>
-                                <biome name="Grass" classes="grassland grassland2 forest forest1 forest2 forest3 forest4">
-                                    <billboard url="../data/splat/grass1.png" width="0.85" height="0.4"/>
-                                </biome>
-                            </biomes>
-                        </layer>
-                    </layers>                    
-                </land_cover>
-            </zone>                
+            </zone>
         </zones>
-        
-    </splat>
-            
-    <xi:include href="viewpoints.xml"/>
+    </splat_imagery>
+    
+    <!-- GPU trees from land cover data -->
+    <splat_groundcover name="Trees" land_cover_layer="LandCover">
+        <lod>13</lod>
+        <cast_shadows>true</cast_shadows>
+        <zones>
+            <zone name="default">
+                <groundcover>
+                    <max_distance>6400</max_distance>
+                    <density>3.4</density>
+                    <fill>0.45</fill>
+                    <brightness>2.0</brightness>
+                    <contrast>0.5</contrast>
+                    <biomes>
+                        <biome classes="forest">
+                            <billboard url="../data/splat/cypress.png" width="12.0" height="14.0"/>
+                            <billboard url="../data/splat/pine.png"    width="16.0" height="22.0" />
+                            <billboard url="../data/splat/pine2.png"   width="15.0" height="18.0"/>
+                        </biome>
+                    </biomes>
+                </groundcover>
+            </zone>
+        </zones>
+    </splat_groundcover>
+
+    <!-- GPU grass from land cover data -->
+    <splat_groundcover name="Grass" land_cover_layer="LandCover">
+        <lod>19</lod>
+        <cast_shadows>false</cast_shadows>
+        <zones>
+            <zone name="default">
+                <groundcover>
+                    <max_distance>150</max_distance>
+                    <density>3.8</density>
+                    <fill>0.50</fill>
+                    <wind>0.15</wind>
+                    <biomes>
+                        <biome name="Grass" classes="forest">
+                            <billboard url="../data/splat/grass1.png" width="0.85" height="0.4"/>
+                        </biome>
+                    </biomes>
+                </groundcover>
+            </zone>
+        </zones>
+    </splat_groundcover>
 
+    
+    <xi:include href="viewpoints.xml"/>
 </map>
diff --git a/tests/tess-coastlines.earth b/tests/tess-coastlines.earth
deleted file mode 100644
index bac9591..0000000
--- a/tests/tess-coastlines.earth
+++ /dev/null
@@ -1,157 +0,0 @@
-<!--
-osgEarth Sample - OpenGL Tessellation Shaders
-
-This example injects a Tessellation Control Shader (TCS) and 
-Tessellation Evaluation Shader (TES) into the terrain rendering
-pipeline. This is EXPERIMENTAL so please be advised of the 
-following caveats:
-
- * Terrain skirts will not render. Set "skirt_ratio" to zero or you
-   will get GL errors.
- * Set gpu_tessellation="true" on the terrain options.
- * EarthManipulator doesn't like GL_PATCHES (yet). Use the viewpoints.
-  
-Press 'w' for wireframe mode to see the tessellation!
--->
-
-<map name="Coastlines" type="geocentric">
-    
-    <options>         
-        <terrain driver="mp" first_lod="1" skirt_ratio="0" tile_size="5" gpu_tessellation="true"/>
-    </options>
-    
-    <image name="readymap_imagery" driver="tms">
-        <url>http://readymap.org/readymap/tiles/1.0.0/7/</url>
-    </image>
-        
-    <elevation name="readymap_elevation" driver="tms">
-        <url>http://readymap.org/readymap/tiles/1.0.0/116/</url>
-    </elevation>
-    
-    <!--
-     | Rasterized feature layer that we use to mask the tessellation.
-     | The max_data_level keeps the mask lo-res enough what we don't get
-     | artifacts at high LODs.
-     |
-     | Data is from NaturalEarth (www.naturalearthdata.com)
-     -->
-    <image name="Coastlines" driver="agglite" shared="true" visible="false" max_data_level="7">
-        <features driver="ogr">
-            <url>../data/ne_10m_coastline.zip/ne_10m_coastline.shp</url>
-        </features>
-        <styles>
-            <style type="text/css">
-                default {
-                    stroke:       #ffbf007f;
-                    stroke-width: 2.5km;
-                }
-            </style>
-        </styles>
-        <cache_policy usage="no_cache"/>
-        <shared_sampler>coastlines</shared_sampler>
-        <shared_matrix>coastlinesMat</shared_matrix>
-    </image>
-
-    <terrainshader>
-        <code><![CDATA[
-        
-            #version 430 compatibility
-            #pragma vp_entryPoint "configureTess"
-            #pragma vp_location   "tess_control"
-            
-            // Access to the rasterized mask layer. In the future these
-            // will be programmable..
-            uniform sampler2D coastlines;
-            uniform mat4      coastlinesMat;
-
-            // General tile coords [0..1]
-            in vec4 oe_layer_tilec;
-
-            layout(vertices=3) out;
-            
-            // Internal helper functions:
-            void VP_LoadVertex(in int);
-            
-            void configureTess()
-            {
-                #define id gl_InvocationID
-                // Only required for the first invocation:
-                if ( id == 0 )
-                {                         
-                    vec3 v;
-                    
-                    // Sample the mask texture at all 3 verts of the triangle:
-                    for(int i=0; i<3; ++i)
-                    {
-                        VP_LoadVertex(i);
-                        v[i] = texture(coastlines, (coastlinesMat*oe_layer_tilec).st).a;
-                    }
-                    
-                    // Average the weights for each edge level:
-                    gl_TessLevelOuter[0] = 1.0 + 5.0*mix(v[1], v[2], 0.5);
-                    gl_TessLevelOuter[1] = 1.0 + 5.0*mix(v[2], v[0], 0.5);
-                    gl_TessLevelOuter[2] = 1.0 + 5.0*mix(v[0], v[1], 0.5);
-                    
-                    // Average all weights for the inner ring count:
-                    gl_TessLevelInner[0] = 1.0 + 4.0*(v.x+v.y+v.z)/3.0;
-                    
-                    // Reset the loaded vertex to the current invocation.
-                    VP_LoadVertex(0);
-                }
-            }
-            
-        ]]></code>
-    </terrainshader>
-    
-    <terrainshader>
-        <code><![CDATA[            
-            #version 410
-            #pragma vp_name       "Terrain TES Shader"
-            #pragma vp_entryPoint "generateVertex"
-            #pragma vp_location   "tess_eval"
-
-            // osgEarth terrain is always CCW winding
-            layout(triangles, equal_spacing, ccw) in;
-
-            // Internal helpers:
-            void VP_Interpolate3();
-            void VP_EmitVertex();
-            
-            // User must supply interpolators for all the base types:
-            
-            float VP_Interpolate3(float a, float b, float c) {
-                return dot(gl_TessCoord.xyz, vec3(a,b,c));
-            }
-            
-            vec2 VP_Interpolate3(vec2 a, vec2 b, vec2 c) {
-                return vec2(dot(gl_TessCoord.xyz, vec3(a.x,b.x,c.x)),
-                            dot(gl_TessCoord.xyz, vec3(a.y,b.y,c.y)));
-            }
-
-            vec3 VP_Interpolate3(vec3 a, vec3 b, vec3 c) {
-                return vec3(dot(gl_TessCoord.xyz, vec3(a.x,b.x,c.x)),
-                            dot(gl_TessCoord.xyz, vec3(a.y,b.y,c.y)),
-                            dot(gl_TessCoord.xyz, vec3(a.z,b.z,c.z)));
-            }
-
-            vec4 VP_Interpolate3(vec4 a, vec4 b, vec4 c) {
-                return vec4(dot(gl_TessCoord.xyz, vec3(a.x,b.x,c.x)),
-                            dot(gl_TessCoord.xyz, vec3(a.y,b.y,c.y)),
-                            dot(gl_TessCoord.xyz, vec3(a.z,b.z,c.z)),
-                            dot(gl_TessCoord.xyz, vec3(a.w,b.w,c.w)));
-            }
-            
-            // simplest possible pass-though:
-            void generateVertex()
-            {                    
-                VP_Interpolate3();
-                VP_EmitVertex();
-            }
-        ]]></code>
-    </terrainshader>
-    
-    <viewpoints>
-        <viewpoint name="San Francisco" heading="8.806974516644791" height="4465.397310772911" lat="37.55814749233749" long="-122.334535784141" pitch="-34.79540299384382" range="78142.53643278375" srs="+proj=longlat +ellps=WGS84 +towgs84=0,0,0,0,0,0,0 +no_defs " />
-    </viewpoints>
-        
-</map>
diff --git a/tests/tess-masking.earth b/tests/tess-masking.earth
deleted file mode 100644
index 2c64ba1..0000000
--- a/tests/tess-masking.earth
+++ /dev/null
@@ -1,176 +0,0 @@
-<!--
-osgEarth Sample - OpenGL Tessellation Shaders
-
-This example injects a Tessellation Control Shader (TCS) and 
-Tessellation Evaluation Shader (TES) into the terrain rendering
-pipeline. This is EXPERIMENTAL so please be advised of the 
-following caveats:
-
- * Terrain skirts will not render. Set "skirt_ratio" to zero or you
-   will get GL errors.
- * Set gpu_tessellation="true" on the terrain options.
- * EarthManipulator doesn't like GL_PATCHES (yet). Use the viewpoints.
-  
-Press 'w' for wireframe mode to see the tessellation!
--->
-
-<map name="readymap.org" type="geocentric">
-    
-    <options>         
-        <terrain driver="rex" first_lod="1" skirt_ratio="0" gpu_tessellation="true"/>
-    </options>
-    
-    <image name="readymap_imagery" driver="tms">
-        <url>http://readymap.org/readymap/tiles/1.0.0/7/</url>
-    </image>
-        
-    <elevation name="readymap_elevation" driver="tms">
-        <url>http://readymap.org/readymap/tiles/1.0.0/116/</url>
-    </elevation>    
-	
-    <image name="pavement" driver="agglite" enabled="true" shared="true" visible="false" min_level="7" max_data_level="10">  
-        <features name="world" driver="ogr">
-            <url>H:/data/xplane/aerodrome/APTBoundary_all.shp</url>
-            <build_spatial_index>true</build_spatial_index>
-        </features> 
-		<layout>
-			<level max_range="50000" class="default"/>
-		</layout>
-        <styles>
-            <style type="text/css">
-                default {
-				   fill: #ffffffff;
-                }            
-            </style>
-        </styles>    
-        <shared_sampler>maskTex</shared_sampler>
-        <shared_matrix>maskMatrix</shared_matrix>        
-    </image>	
-    
-    <extensions>
-    
-        <terrainshader>
-            <code><![CDATA[
-            
-                #version 430 compatibility
-                #pragma vp_entryPoint "configureTess"
-                #pragma vp_location   "tess_control"
-                
-                // Access to the rasterized mask layer. In the future these will be programmable..
-                uniform sampler2D maskTex;
-                uniform mat4      maskMatrix;
-
-                // General tile coords [0..1]
-                in vec4 oe_layer_tilec;
-                
-                uniform vec4 oe_tile_key;
-
-                layout(vertices=3) out;
-                
-                uniform float level = 5.0;
-                
-                // Internal helper functions:
-                void VP_LoadVertex(in int);
-                
-                void configureTess()
-                {                       
-                    // Only required for the first invocation:
-                    if ( gl_InvocationID == 0 )
-                    {                                    
-                        // Sample the mask texture at all 3 verts of the triangle:     
-                        vec3 v = vec3(0);
-
-                        if ( oe_tile_key.z >= 8 )
-                        {
-                            for(int i=0; i<3; ++i)
-                            {
-                                VP_LoadVertex(i);
-                                v[i] = texture(maskTex, (maskMatrix*oe_layer_tilec).st).a;
-                                v[i] = 1.0 - 2.0*abs(v[i]-0.5); // remap to [0..1] where 0 = in/out, 1 = on the line.
-                            }
-                        }
-                        
-                        // Average the weights for each edge level:
-                        gl_TessLevelOuter[0] = 1.0 + level*mix(v[1], v[2], 0.5);
-                        gl_TessLevelOuter[1] = 1.0 + level*mix(v[2], v[0], 0.5);
-                        gl_TessLevelOuter[2] = 1.0 + level*mix(v[0], v[1], 0.5);
-                        
-                        // Average all weights for the inner ring count:
-                        gl_TessLevelInner[0] = 1.0 + (level-1.0)*(v.x+v.y+v.z)/3.0;
-                            
-                        // Reset the loaded vertex to the current invocation.
-                        VP_LoadVertex(0);
-                    }
-                }
-                
-            ]]></code>
-        </terrainshader>
-        
-        <terrainshader>
-            <code><![CDATA[            
-                #version 410
-                #pragma vp_name       "Terrain TES Shader"
-                #pragma vp_entryPoint "generateVertex"
-                #pragma vp_location   "tess_eval"
-                
-                uniform sampler2D oe_layer_2_tex;
-                uniform mat4      oe_layer_2_texMatrix;
-                in vec4 oe_layer_tilec;
-                in vec4 vp_Vertex;
-                in vec3 vp_Normal;
-
-                // osgEarth terrain is always CCW winding
-                layout(triangles, equal_spacing, ccw) in;
-
-                // Internal helpers:
-                void VP_Interpolate3();
-                void VP_EmitVertex();
-                
-                // User must supply interpolators for all the base types:
-                
-                float VP_Interpolate3(float a, float b, float c) {
-                    return dot(gl_TessCoord.xyz, vec3(a,b,c));
-                }
-                
-                vec2 VP_Interpolate3(vec2 a, vec2 b, vec2 c) {
-                    return vec2(dot(gl_TessCoord.xyz, vec3(a.x,b.x,c.x)),
-                                dot(gl_TessCoord.xyz, vec3(a.y,b.y,c.y)));
-                }
-
-                vec3 VP_Interpolate3(vec3 a, vec3 b, vec3 c) {
-                    return vec3(dot(gl_TessCoord.xyz, vec3(a.x,b.x,c.x)),
-                                dot(gl_TessCoord.xyz, vec3(a.y,b.y,c.y)),
-                                dot(gl_TessCoord.xyz, vec3(a.z,b.z,c.z)));
-                }
-
-                vec4 VP_Interpolate3(vec4 a, vec4 b, vec4 c) {
-                    return vec4(dot(gl_TessCoord.xyz, vec3(a.x,b.x,c.x)),
-                                dot(gl_TessCoord.xyz, vec3(a.y,b.y,c.y)),
-                                dot(gl_TessCoord.xyz, vec3(a.z,b.z,c.z)),
-                                dot(gl_TessCoord.xyz, vec3(a.w,b.w,c.w)));
-                }
-                
-                uniform float offset = -100.0;
-                
-                // simplest possible pass-though:
-                void generateVertex()
-                {                    
-                    VP_Interpolate3();
-                    
-                    float mask = texture(oe_layer_2_tex, (oe_layer_2_texMatrix*oe_layer_tilec).st).a;
-                    vp_Normal = normalize(vp_Normal);
-                    vp_Vertex.xyz += vp_Normal * offset * mask;
-                    
-                    VP_EmitVertex();
-                }
-            ]]></code>
-        </terrainshader>
-        
-        <viewpoints>
-            <viewpoint name="San Francisco" heading="8.806974516644791" height="4465.397310772911" lat="37.55814749233749" long="-122.334535784141" pitch="-34.79540299384382" range="78142.53643278375" srs="+proj=longlat +ellps=WGS84 +towgs84=0,0,0,0,0,0,0 +no_defs " />
-            <viewpoint name="SFO" heading="27.57985140028968" height="4465.588079094887" lat="37.56462223501358" long="-122.4153260104297" pitch="-31.6655000582341" range="72.45169535764561"/>
-            <viewpoint name="BOS" heading="-3.005093201136807" height="10.50455343350768" lat="42.3626762635888" long="-71.01223797197133" pitch="-46.98927698631228" range="8966.122488728281"/>
-        </viewpoints>
-        
-    </extensions>
-</map>
diff --git a/tests/tess-terrain.earth b/tests/tess-terrain.earth
deleted file mode 100644
index 556239d..0000000
--- a/tests/tess-terrain.earth
+++ /dev/null
@@ -1,146 +0,0 @@
-<!--
-osgEarth Sample - OpenGL Tessellation Shaders
-
-This example injects a Tessellation Control Shader (TCS) and 
-Tessellation Evaluation Shader (TES) into the terrain rendering
-pipeline. This is EXPERIMENTAL so please be advised of the 
-following caveats:
-
- * Terrain skirts will not render. Set "skirt_ratio" to zero or you
-   will get GL errors.
- * Set gpu_tessellation="true" on the terrain options.
- * EarthManipulator doesn't like GL_PATCHES (yet). Use the viewpoints.
-  
-Press 'w' for wireframe mode to see the tessellation!
--->
-
-<map name="readymap.org" type="geocentric">
-    
-    <options>        
-        <terrain driver="mp" gpu_tessellation="true" min_lod="25" tile_size="9" skirt_ratio="0"/>
-    </options>
-    
-    <image name="readymap_imagery" driver="tms">
-        <url>http://readymap.org/readymap/tiles/1.0.0/7/</url>
-    </image>
-    
-    <elevation name="readymap_elevation" driver="tms">
-        <url>http://readymap.org/readymap/tiles/1.0.0/116/</url>
-    </elevation>
-    
-    <extensions>
-    
-        <terrainshader>
-            <code><![CDATA[
-            
-                #version 400 compatibility
-                #pragma vp_entryPoint "configureTess"
-                #pragma vp_location   "tess_control"
-
-                layout(vertices = 3) out;
-                
-                uniform float pixelsPerEdge = 7.5;
-                uniform vec2  viewportSize  = vec2(1024,1024);
-                                
-                // convert model position to viewport coordinates.
-                vec2 toPixels(in vec4 pos)
-                {    
-                    vec4 clip = gl_ModelViewProjectionMatrix * pos;
-                    return (clip.xy/clip.w) * (viewportSize*0.5);
-                }
-                
-                // calculate a tessellation level based on the maximum edge length uniform.
-                float tessLevel(in vec2 v0, in vec2 v1)
-                {
-                    return clamp(distance(v0,v1)/pixelsPerEdge, 1.0, 128.0);
-                }
-                
-                void configureTess()
-                {
-                    // Only required for the first invocation:
-                    if ( gl_InvocationID == 0 )
-                    {
-                        vec2 U = toPixels( gl_in[0].gl_Position );
-                        vec2 V = toPixels( gl_in[1].gl_Position );
-                        vec2 W = toPixels( gl_in[2].gl_Position );
-                        
-                        float E0 = tessLevel(V, W);
-                        float E1 = tessLevel(W, U);
-                        float E2 = tessLevel(U, V);
-                        
-                        gl_TessLevelOuter[0] = E0;
-                        gl_TessLevelOuter[1] = E1;
-                        gl_TessLevelOuter[2] = E2;
-                        
-                        gl_TessLevelInner[0] = (E0+E1+E2)/3.0;
-                    }
-                }
-                
-            ]]></code>
-        </terrainshader>
-        
-        <terrainshader>
-            <code><![CDATA[            
-                #version 410
-                #pragma vp_name       "Terrain TES Shader"
-                #pragma vp_entryPoint "generateVertex"
-                #pragma vp_location   "tess_eval"
-
-                // osgEarth terrain is always CCW winding
-                layout(triangles, equal_spacing, ccw) in;
-
-                // Internal helpers:
-                void VP_Interpolate3();
-                void VP_EmitVertex();
-                
-                
-                float VP_Interpolate3(float a, float b, float c) {
-                    return dot(gl_TessCoord.xyz, vec3(a,b,c));
-                }
-                
-                vec2 VP_Interpolate3(vec2 a, vec2 b, vec2 c) {
-                    return vec2(dot(gl_TessCoord.xyz, vec3(a.x,b.x,c.x)),
-                                dot(gl_TessCoord.xyz, vec3(a.y,b.y,c.y)));
-                }
-
-                vec3 VP_Interpolate3(vec3 a, vec3 b, vec3 c) {
-                    return vec3(dot(gl_TessCoord.xyz, vec3(a.x,b.x,c.x)),
-                                dot(gl_TessCoord.xyz, vec3(a.y,b.y,c.y)),
-                                dot(gl_TessCoord.xyz, vec3(a.z,b.z,c.z)));
-                }
-
-                vec4 VP_Interpolate3(vec4 a, vec4 b, vec4 c) {
-                    return vec4(dot(gl_TessCoord.xyz, vec3(a.x,b.x,c.x)),
-                                dot(gl_TessCoord.xyz, vec3(a.y,b.y,c.y)),
-                                dot(gl_TessCoord.xyz, vec3(a.z,b.z,c.z)),
-                                dot(gl_TessCoord.xyz, vec3(a.w,b.w,c.w)));
-                }
-                
-                vec3 vp_Normal;
-                
-                // simplest possible pass-though:
-                void generateVertex()
-                {                    
-                    VP_Interpolate3();
-                    
-                    // Must re-normalize the normal vector since interpolation was linear?
-                    vp_Normal = normalize(vp_Normal);
-                    
-                    VP_EmitVertex();
-                }
-            ]]></code>
-        </terrainshader>
-        
-        <viewpoints>
-            <viewpoint name="San Francisco" heading="8.806974516644791" height="4465.397310772911" lat="37.55814749233749" long="-122.334535784141" pitch="-34.79540299384382" range="78142.53643278375" srs="+proj=longlat +ellps=WGS84 +towgs84=0,0,0,0,0,0,0 +no_defs " />
-            <viewpoint name="Mt R. Nadir 30K" heading="0.5013023037097585" height="4101.627114404924" lat="46.85909894548915" long="-121.7598368518208" pitch="-89.43249895879129" range="29029.34246828893"/>
-            <viewpoint name="Mt R. Oblique 30K" heading="17.33521725357022" height="2462.60273069609" lat="46.82181702111031" long="-121.7814936386096" pitch="-21.29241356548601" range="23926.75258864516"/>
-            <viewpoint name="Mt R. Closeup" heading="-109.6842970297122" height="3843.486737414263" lat="46.85528453766688" long="-121.7455004166102" pitch="-4.617466338845979" range="951.4780720092711"/>
-            <viewpoint name="Mt R. Trees" heading="-98.36122712710565" height="1639.304918398149" lat="46.78673277044066" long="-121.743286318636" pitch="-10.85365380742088" range="257.5853045645545"/>
-            <viewpoint name="Nepal" heading="-72.70835146844568" height="6334.845537136309" lat="27.94213038800919" long="86.9936567556778" pitch="-18.63803872963365" range="13611.24948464565"/>
-            <viewpoint name="Nepal NF" heading="-49.14546953546358" height="6334.332569343038" lat="27.9421778947837" long="86.9935949004298" pitch="-3.643325527310435" range="13302.81192964212"/>
-            <viewpoint name="Matterhorn" heading="-1.429462844200832" height="2282.858508689329" lat="45.95106319557" long="7.642741711675961" pitch="-25.12269405854052" range="26690.10606054494"/>
-        </viewpoints>
-               
-    </extensions>
-</map>
diff --git a/tests/tess_screen_space.earth b/tests/tess_screen_space.earth
deleted file mode 100644
index d7d2dd4..0000000
--- a/tests/tess_screen_space.earth
+++ /dev/null
@@ -1,120 +0,0 @@
-<!--
-osgEarth Sample - OpenGL Tessellation Shaders
-
-This example demonstrates screen-space terrain tessellation.
--->
-
-<map name="readymap.org" type="geocentric">
-    
-    <options>        
-        <terrain max_lod="3" skirt_ratio="0" tile_size="7" gpu_tessellation="true"/>
-    </options>
-    
-    <image name="readymap_imagery" driver="tms" opacity="0.5">
-        <url>http://readymap.org/readymap/tiles/1.0.0/7/</url>
-    </image>
-        
-    <extensions>
-    
-        <terrainshader>
-            <code><![CDATA[
-            
-                #version 430 compatibility
-                #pragma vp_entryPoint "configureTess"
-                #pragma vp_location   "tess_control"
-                
-                layout(vertices=3) out;
-                
-                // http://codeflow.org/entries/2010/nov/07/opengl-4-tessellation/
-                
-                uniform float pixelsPerEdge = 64;
-                uniform vec2  viewportSize  = vec2(1024,1024);
-                                
-                // convert model position to viewport coordinates.
-                vec2 toPixels(in vec4 pos) {    
-                    vec4 clip = gl_ModelViewProjectionMatrix * pos;
-                    return clamp( (clip.xy/clip.w), -1.3, 1.3) * (viewportSize*0.5);
-                }
-                
-                // calculate a tesselation level based on the maximum edge length uniform.
-                float tessLevel(in vec2 v0, in vec2 v1) {
-                    return clamp(distance(v0,v1)/pixelsPerEdge, 1.0, 64.0);
-                }
-                
-                void configureTess()
-                {
-                    // Only required for the first invocation:
-                    if ( gl_InvocationID == 0 )
-                    {                       
-                        vec2 ss0 = toPixels( gl_in[0].gl_Position );
-                        vec2 ss1 = toPixels( gl_in[1].gl_Position );
-                        vec2 ss2 = toPixels( gl_in[2].gl_Position );
-                        
-                        float e0 = tessLevel(ss1, ss2);
-                        float e1 = tessLevel(ss2, ss0);
-                        float e2 = tessLevel(ss0, ss1);
-                        
-                        gl_TessLevelOuter[0] = e0;
-                        gl_TessLevelOuter[1] = e1;
-                        gl_TessLevelOuter[2] = e2;
-                        
-                        gl_TessLevelInner[0] = (e0+e1+e2)/3.0;
-                    }
-                }
-                
-            ]]></code>
-        </terrainshader>
-        
-        <terrainshader>
-            <code><![CDATA[            
-                #version 410
-                #pragma vp_name       "Terrain TES Shader"
-                #pragma vp_entryPoint "generateVertex"
-                #pragma vp_location   "tess_eval"
-
-                // osgEarth terrain is always CCW winding
-                layout(triangles, equal_spacing, ccw) in;
-
-                // Internal helpers:
-                void VP_Interpolate3();
-                void VP_EmitVertex();
-                
-                // User must supply interpolators for all the base types:
-                
-                float VP_Interpolate3(float a, float b, float c) {
-                    return dot(gl_TessCoord.xyz, vec3(a,b,c));
-                }
-                
-                vec2 VP_Interpolate3(vec2 a, vec2 b, vec2 c) {
-                    return vec2(dot(gl_TessCoord.xyz, vec3(a.x,b.x,c.x)),
-                                dot(gl_TessCoord.xyz, vec3(a.y,b.y,c.y)));
-                }
-
-                vec3 VP_Interpolate3(vec3 a, vec3 b, vec3 c) {
-                    return vec3(dot(gl_TessCoord.xyz, vec3(a.x,b.x,c.x)),
-                                dot(gl_TessCoord.xyz, vec3(a.y,b.y,c.y)),
-                                dot(gl_TessCoord.xyz, vec3(a.z,b.z,c.z)));
-                }
-
-                vec4 VP_Interpolate3(vec4 a, vec4 b, vec4 c) {
-                    return vec4(dot(gl_TessCoord.xyz, vec3(a.x,b.x,c.x)),
-                                dot(gl_TessCoord.xyz, vec3(a.y,b.y,c.y)),
-                                dot(gl_TessCoord.xyz, vec3(a.z,b.z,c.z)),
-                                dot(gl_TessCoord.xyz, vec3(a.w,b.w,c.w)));
-                }
-                
-                // simplest possible pass-though:
-                void generateVertex()
-                {                    
-                    VP_Interpolate3();
-                    VP_EmitVertex();
-                }
-            ]]></code>
-        </terrainshader>
-        
-        <viewpoints>
-            <viewpoint name="San Francisco" heading="8.806974516644791" height="4465.397310772911" lat="37.55814749233749" long="-122.334535784141" pitch="-34.79540299384382" range="78142.53643278375" srs="+proj=longlat +ellps=WGS84 +towgs84=0,0,0,0,0,0,0 +no_defs " />
-        </viewpoints>
-        
-    </extensions>
-</map>
diff --git a/tests/test-morphing.earth b/tests/test-morphing.earth
deleted file mode 100644
index 1d6530b..0000000
--- a/tests/test-morphing.earth
+++ /dev/null
@@ -1,50 +0,0 @@
-<!--
-osgEarth Sample - OpenGL Tessellation Shaders
-
-This example injects a Tessellation Control Shader (TCS) and 
-Tessellation Evaluation Shader (TES) into the terrain rendering
-pipeline. This is EXPERIMENTAL so please be advised of the 
-following caveats:
-
- * Terrain skirts will not render. Set "skirt_ratio" to zero or you
-   will get GL errors.
- * Set gpu_tessellation="true" on the terrain options.
- * EarthManipulator doesn't like GL_PATCHES (yet). Use the viewpoints.
-  
-Press 'w' for wireframe mode to see the tessellation!
--->
-
-<map name="readymap.org" type="geocentric">
-    
-    <options>        
-        <terrain driver="rex" min_lod="15" tile_size="17" skirt_ratio="0.01" />
-    </options>
-    
-    <image name="readymap_imagery" driver="tms">
-        <url>http://readymap.org/readymap/tiles/1.0.0/7/</url>
-    </image>
-	<elevation name="readymap_elevation" driver="tms">
-        <url>http://readymap.org/readymap/tiles/1.0.0/116/</url>
-    </elevation> 
-  <extensions>
-
-    <viewpoints>
-<viewpoint heading="-0.673756rad" height="2463.694609582424" lat="46.95613641063451" long="-121.7620302808055" pitch="-0.545402rad" range="16414.2m" srs="+proj=longlat +ellps=WGS84 +towgs84=0,0,0,0,0,0,0 +no_defs " />
-<viewpoint heading="-0.673756rad" height="2463.694609590806" lat="46.95613641067459" long="-121.7620302808307" pitch="-0.545402rad" range="14137.1m" srs="+proj=longlat +ellps=WGS84 +towgs84=0,0,0,0,0,0,0 +no_defs " />
-	
-	<viewpoint heading="-0.0111633rad" height="2463.63338462729" lat="46.94509938539743" long="-121.7784264529069" pitch="-0.1252rad" range="14255.2m" srs="+proj=longlat +ellps=WGS84 +towgs84=0,0,0,0,0,0,0 +no_defs " />
-
-      <viewpoint name="San Francisco" heading="8.806974516644791" height="4465.397310772911" lat="37.55814749233749" long="-122.334535784141" pitch="-34.79540299384382" range="78142.53643278375" srs="+proj=longlat +ellps=WGS84 +towgs84=0,0,0,0,0,0,0 +no_defs " />
-      <viewpoint name="Mt R. Nadir 30K" heading="0.5013023037097585" height="4101.627114404924" lat="46.85909894548915" long="-121.7598368518208" pitch="-89.43249895879129" range="29029.34246828893"/>
-	  	<viewpoint heading="-0.271177rad" height="2462.848437966779" lat="46.8358008418158" long="-121.8823059183911" pitch="-0.338073rad" range="10578.2m" srs="+proj=longlat +ellps=WGS84 +towgs84=0,0,0,0,0,0,0 +no_defs " />
-      <viewpoint name="Mt R. Oblique 30K" heading="17.33521725357022" height="2462.60273069609" lat="46.82181702111031" long="-121.7814936386096" pitch="-21.29241356548601" range="23926.75258864516"/>
-      <viewpoint name="Mt R. Closeup" heading="-109.6842970297122" height="3843.486737414263" lat="46.85528453766688" long="-121.7455004166102" pitch="-4.617466338845979" range="951.4780720092711"/>
-      <viewpoint name="Mt R. Trees" heading="-98.36122712710565" height="1639.304918398149" lat="46.78673277044066" long="-121.743286318636" pitch="-10.85365380742088" range="257.5853045645545"/>
-      <viewpoint name="Nepal" heading="-72.70835146844568" height="6334.845537136309" lat="27.94213038800919" long="86.9936567556778" pitch="-18.63803872963365" range="13611.24948464565"/>
-      <viewpoint name="Nepal NF" heading="-49.14546953546358" height="6334.332569343038" lat="27.9421778947837" long="86.9935949004298" pitch="-3.643325527310435" range="13302.81192964212"/>
-      <viewpoint name="Matterhorn" heading="-1.429462844200832" height="2282.858508689329" lat="45.95106319557" long="7.642741711675961" pitch="-25.12269405854052" range="26690.10606054494"/>
-
-    </viewpoints>
-
-  </extensions>
-</map>
diff --git a/tests/triton.earth b/tests/triton.earth
index 292151b..5b898d2 100644
--- a/tests/triton.earth
+++ b/tests/triton.earth
@@ -3,33 +3,30 @@ Tests the Triton ocean plugin.
 http://sundog-soft.com/sds/features/real-time-3d-clouds/
 -->
 <map name="readymap.org" type="geocentric" version="2">
-    
-    <options>
-        <terrain first_lod="1" tile_size="7"/>
-    </options>
-    
+
     <image name="readymap_imagery" driver="tms" visible="true">
         <url>http://readymap.org/readymap/tiles/1.0.0/7/</url>
     </image>
         
     <elevation name="readymap_elevation" driver="tms">
-        <url>http://readymap.org/readymap/tiles/1.0.0/9/</url>
+        <url>http://readymap.org/readymap/tiles/1.0.0/116/</url>
     </elevation>
-               
-    <sky driver="gl">
-        <hours>18</hours>
-    </sky>
-    
-    <ocean driver="triton">
+
+    <image name="ocean_mask" driver="tms" shared="true" visible="false">
+        <url>http://readymap.org/readymap/tiles/1.0.0/2/</url>
+    </image>
+
+    <triton>
         <user> my_user_name </user>
         <license_code> my_license_code </license_code>
-        <max_altitude> 10000 </max_altitude>
-    </ocean>
+        <max_altitude> 500000 </max_altitude>
+        <mask_layer>ocean_mask</mask_layer>
+    </triton>
 
     <viewpoints>
-        <viewpoint name="Hawaii"   heading="-29" height="-204" lat="20.6714" long="-156.5384" pitch="-4" range="3270"/>
-        <viewpoint name="Snafu"    heading="-35.42227636584842" height="-188.2792971581221" lat="20.68154179570284" long="-156.5452311560784" pitch="-14.59403736732238" range="5469.652750028356"/>
-        <viewpoint name="NearClip" heading="-0.618211rad" height="-190.1852927561849" lat="20.67586333023495" long="-156.5418074743535" pitch="-0.2546rad" range="1154.32m"/>
+        <viewpoint name="Start" heading="-29.7876" pitch="-12" range="4200" long="-156.537" lat="20.6702" />
+        <viewpoint name="Above" heading="-35.42227636584842" height="-188.2792971581221" lat="20.68154179570284" long="-156.5452311560784" pitch="-30" range="5469"/>
+        <viewpoint name="Near clip" heading="-0.618211rad" height="-190.1852927561849" lat="20.67586333023495" long="-156.5418074743535" pitch="-0.2546rad" range="1154.32m"/>
         <viewpoint name="Horizon"  heading="-27.1911" height="-206.3652788307518" lat="20.69785423327782" long="-156.5550697849549" pitch="-16.0293" range="68323m"/>
     </viewpoints>
         
diff --git a/tests/readymap-osm.earth b/tests/utm.earth
similarity index 59%
rename from tests/readymap-osm.earth
rename to tests/utm.earth
index 678c9cd..0db6425 100644
--- a/tests/readymap-osm.earth
+++ b/tests/utm.earth
@@ -11,17 +11,21 @@ YOU ARE RESPONSIBLE for abiding by the TERMS AND CONDITIONS outlined at:
 http://readymap.org
 
 -->
-<map name="readymap.org" type="geocentric" version="2">
-
+<map name="readymap.org" type="projected">    
+    
+    <options>
+        <profile>
+            <srs>+proj=utm +zone=31 +units=m +no_defs</srs>
+        </profile>
+        
+        <terrain driver="rex" tile_size="2" morph_terrain="false"/>
+    </options>
+    
     <image name="readymap_imagery" driver="tms">
         <url>http://readymap.org/readymap/tiles/1.0.0/7/</url>
     </image>
-
-    <image name="readymap_streets" driver="tms">
-        <url>http://readymap.org/readymap/tiles/1.0.0/119/</url>
-    </image>
-    
-    <viewpoints>
-        <viewpoint name="Los Angeles" heading="35.27" height="97.48" lat="34.051" long="-117.974" pitch="-17" range="136405"/>
-    </viewpoints>
+        
+    <elevation name="readymap_elevation" driver="tms">
+        <url>http://readymap.org/readymap/tiles/1.0.0/116/</url>
+    </elevation>
 </map>
diff --git a/tests/viewpoints.xml b/tests/viewpoints.xml
index 2d89078..d7a759f 100644
--- a/tests/viewpoints.xml
+++ b/tests/viewpoints.xml
@@ -1,65 +1,74 @@
 <?xml version="1.0" ?>
 <viewpoints>
-  <viewpoint>
-    <heading>8.806974516644791</heading>
-    <height>4465.397310772911</height>
-    <lat>37.55814749233749</lat>
-    <long>-122.334535784141</long>
-    <name>San Francisco, California</name>
-    <pitch>-34.79540299384382</pitch>
-    <range>78142.53643278375</range>
-  </viewpoint>
-  <viewpoint>
-    <heading>17.33521725357022</heading>
-    <height>2462.60273069609</height>
-    <lat>46.82181702111031</lat>
-    <long>-121.7814936386096</long>
-    <name>Mt Rainier, Washington</name>
-    <pitch>-21.29241356548601</pitch>
-    <range>23926.75258864516</range>
-  </viewpoint>
-  <viewpoint>
-    <heading>-72.70835146844568</heading>
-    <height>6334.845537136309</height>
-    <lat>27.94213038800919</lat>
-    <long>86.9936567556778</long>
-    <name>Nepal</name>
-    <pitch>-18.63803872963365</pitch>
-    <range>13611.24948464565</range>
-  </viewpoint>
-  <viewpoint>
-    <heading>-1.429462844200832</heading>
-    <height>2282.858508689329</height>
-    <lat>45.95106319557</lat>
-    <long>7.642741711675961</long>
-    <name>The Matterhorn</name>
-    <pitch>-25.12269405854052</pitch>
-    <range>26690.10606054494</range>
-  </viewpoint>
-  <viewpoint>
-    <name>Hallstatt, Austria</name>
-    <heading>-21.36</heading>
-    <pitch>-35.0965</pitch>
-    <range>14786m</range>
-    <long>13.65444893405288</long>
-    <lat>47.56470451831095</lat>
-    <height>502.9998890347779</height>
-  </viewpoint>
-  <viewpoint>
-    <name>Anchorage, Alaska</name>
-    <heading>-6.513</heading>
-    <pitch>-14.4413</pitch>
-    <range>21030.5m</range>
-    <long>-149.9001345817515</long>
-    <lat>61.16407743963008</lat>
-    <height>30.04646527767181</height>
-  </viewpoint>
-  <viewpoint name="Mountainside">
-      <heading>67.9132</heading>
-      <pitch>-6.07322</pitch>
-      <range>2852.25m</range>
-      <long>-121.7712665799263</long>
-      <lat>46.79220713459502</lat>
-      <height>1636.236482798122</height>
+    <viewpoint>
+        <heading>8.806974516644791</heading>
+        <height>4465.397310772911</height>
+        <lat>37.55814749233749</lat>
+        <long>-122.334535784141</long>
+        <name>San Francisco, California</name>
+        <pitch>-34.79540299384382</pitch>
+        <range>78142.53643278375</range>
+    </viewpoint>
+    <viewpoint>
+        <heading>17.33521725357022</heading>
+        <height>2462.60273069609</height>
+        <lat>46.82181702111031</lat>
+        <long>-121.7814936386096</long>
+        <name>Mt Rainier, Washington</name>
+        <pitch>-21.29241356548601</pitch>
+        <range>23926.75258864516</range>
+    </viewpoint>
+    <viewpoint>
+        <heading>-72.70835146844568</heading>
+        <height>6334.845537136309</height>
+        <lat>27.94213038800919</lat>
+        <long>86.9936567556778</long>
+        <name>Nepal</name>
+        <pitch>-18.63803872963365</pitch>
+        <range>13611.24948464565</range>
+    </viewpoint>
+    <viewpoint>
+        <heading>-1.429462844200832</heading>
+        <height>2282.858508689329</height>
+        <lat>45.95106319557</lat>
+        <long>7.642741711675961</long>
+        <name>The Matterhorn</name>
+        <pitch>-25.12269405854052</pitch>
+        <range>26690.10606054494</range>
+    </viewpoint>
+    <viewpoint>
+        <name>Hallstatt, Austria</name>
+        <heading>-21.36</heading>
+        <pitch>-35.0965</pitch>
+        <range>14786m</range>
+        <long>13.65444893405288</long>
+        <lat>47.56470451831095</lat>
+        <height>502.9998890347779</height>
+    </viewpoint>
+    <viewpoint>
+        <name>Anchorage, Alaska</name>
+        <heading>-6.513</heading>
+        <pitch>-14.4413</pitch>
+        <range>21030.5m</range>
+        <long>-149.9001345817515</long>
+        <lat>61.16407743963008</lat>
+        <height>30.04646527767181</height>
+    </viewpoint>
+    <viewpoint name="Mountainside">
+        <heading>67.9132</heading>
+        <pitch>-6.07322</pitch>
+        <range>2852.25m</range>
+        <long>-121.7712665799263</long>
+        <lat>46.79220713459502</lat>
+        <height>1636.236482798122</height>
+    </viewpoint>
+    <viewpoint name="Chicago">
+        <heading>107.859</heading>
+        <pitch>-16.1441</pitch>
+        <range>2290.68m</range>
+        <long>-87.63760536472377</long>
+        <lat>41.88440204687279</lat>
+        <height>186.9731654571369</height>
+        <srs>+proj=longlat +ellps=WGS84 +towgs84=0,0,0,0,0,0,0 +no_defs </srs>
     </viewpoint>
 </viewpoints>
diff --git a/tests/viewpoints_flat.xml b/tests/viewpoints_flat.xml
new file mode 100644
index 0000000..275ef03
--- /dev/null
+++ b/tests/viewpoints_flat.xml
@@ -0,0 +1,74 @@
+<?xml version="1.0" ?>
+<viewpoints>
+    <viewpoint>
+        <heading>0.0</heading>
+        <height>4465.397310772911</height>
+        <lat>37.55814749233749</lat>
+        <long>-122.334535784141</long>
+        <name>San Francisco, California</name>
+        <pitch>-90</pitch>
+        <range>78142.53643278375</range>
+    </viewpoint>
+    <viewpoint>
+        <heading>0.0</heading>
+        <height>2462.60273069609</height>
+        <lat>46.82181702111031</lat>
+        <long>-121.7814936386096</long>
+        <name>Mt Rainier, Washington</name>
+        <pitch>-90</pitch>
+        <range>23926.75258864516</range>
+    </viewpoint>
+    <viewpoint>
+        <heading>0.0</heading>
+        <height>6334.845537136309</height>
+        <lat>27.94213038800919</lat>
+        <long>86.9936567556778</long>
+        <name>Nepal</name>
+        <pitch>-90</pitch>
+        <range>13611.24948464565</range>
+    </viewpoint>
+    <viewpoint>
+        <heading>-1.429462844200832</heading>
+        <heading>0.0</heading>
+        <lat>45.95106319557</lat>
+        <long>7.642741711675961</long>
+        <name>The Matterhorn</name>
+        <pitch>-90</pitch>
+        <range>26690.10606054494</range>
+    </viewpoint>
+    <viewpoint>
+        <name>Hallstatt, Austria</name>
+        <heading>0.0</heading>
+        <pitch>-90</pitch>
+        <range>14786m</range>
+        <long>13.65444893405288</long>
+        <lat>47.56470451831095</lat>
+        <height>502.9998890347779</height>
+    </viewpoint>
+    <viewpoint>
+        <name>Anchorage, Alaska</name>
+        <heading>0.0</heading>
+        <pitch>-90</pitch>
+        <range>21030.5m</range>
+        <long>-149.9001345817515</long>
+        <lat>61.16407743963008</lat>
+        <height>30.04646527767181</height>
+    </viewpoint>
+    <viewpoint name="Mountainside">
+        <heading>0.0</heading>
+        <pitch>-90</pitch>
+        <range>2852.25m</range>
+        <long>-121.7712665799263</long>
+        <lat>46.79220713459502</lat>
+        <height>1636.236482798122</height>
+    </viewpoint>
+    <viewpoint name="Chicago">
+        <heading>0.0</heading>
+        <pitch>-90</pitch>
+        <range>2290.68m</range>
+        <long>-87.63760536472377</long>
+        <lat>41.88440204687279</lat>
+        <height>186.9731654571369</height>
+        <srs>+proj=longlat +ellps=WGS84 +towgs84=0,0,0,0,0,0,0 +no_defs </srs>
+    </viewpoint>
+</viewpoints>

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-grass/osgearth.git



More information about the Pkg-grass-devel mailing list