[osgearth] 03/13: Imported Upstream version 2.7.0+dfsg

Sebastiaan Couwenberg sebastic at moszumanska.debian.org
Sun Jul 26 21:27:23 UTC 2015


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

sebastic pushed a commit to branch master
in repository osgearth.

commit 947a70e1e4fa9593292e545854685e77c76d5176
Author: Bas Couwenberg <sebastic at xs4all.nl>
Date:   Sun Jul 26 13:48:29 2015 +0200

    Imported Upstream version 2.7.0+dfsg
---
 CMakeLists.txt                                     |    8 +-
 CMakeModules/ConfigureShaders.cmake.in             |   35 +
 CMakeModules/FindLevelDB.cmake                     |    1 +
 CMakeModules/ModuleInstall.cmake                   |   13 +-
 .../ModuleInstallOsgEarthDriverIncludes.cmake      |    2 +-
 CMakeModules/OsgEarthMacroUtils.cmake              |  163 +-
 README.txt                                         |   14 +-
 docs/source/data.rst                               |    2 +-
 docs/source/developer/shader_composition.rst       |  302 ++-
 docs/source/faq.rst                                |   54 +-
 docs/source/index.rst                              |   13 +-
 docs/source/install.rst                            |    4 +-
 docs/source/references/drivers/feature/ogr.rst     |   14 +-
 docs/source/references/drivers/terrain/mp.rst      |    2 +-
 docs/source/references/drivers/tile/index.rst      |    1 +
 docs/source/references/drivers/tile/quadkey.rst    |   27 +
 docs/source/references/earthfile.rst               |   77 +-
 docs/source/references/envvars.rst                 |    4 +-
 docs/source/references/symbology.rst               |   13 +
 docs/source/releasenotes.rst                       |   33 +
 docs/source/startup.rst                            |   12 +-
 docs/source/user/caching.rst                       |    1 +
 docs/source/user/earthfiles.rst                    |    2 +-
 docs/source/user/features.rst                      |   41 +-
 docs/source/user/tools.rst                         |  101 +-
 src/CMakeLists.txt                                 |    4 +-
 src/applications/CMakeLists.txt                    |    8 +-
 .../osgearth_annotation/osgearth_annotation.cpp    |  156 +-
 src/applications/osgearth_atlas/osgearth_atlas.cpp |   18 +-
 .../osgearth_backfill/osgearth_backfill.cpp        |    2 +-
 src/applications/osgearth_boundarygen/BoundaryUtil |   13 +-
 .../osgearth_boundarygen/BoundaryUtil.cpp          |   13 +-
 .../osgearth_boundarygen/VertexCollectionVisitor   |   13 +-
 .../VertexCollectionVisitor.cpp                    |   13 +-
 .../osgearth_boundarygen/boundarygen.cpp           |   13 +-
 .../osgearth_cache_test/CMakeLists.txt             |    7 +
 .../osgearth_cache_test/osgearth_cache_test.cpp    |   96 +
 src/applications/osgearth_city/osgearth_city.cpp   |   25 +-
 src/applications/osgearth_clamp/osgearth_clamp.cpp |   15 +-
 .../osgearth_clipplane/osgearth_clipplane.cpp      |   15 +-
 .../osgearth_colorfilter/osgearth_colorfilter.cpp  |   13 +-
 .../osgearth_controls/osgearth_controls.cpp        |   13 +-
 src/applications/osgearth_conv/osgearth_conv.cpp   |   13 +-
 .../osgearth_createtile/CMakeLists.txt             |    7 +
 .../osgearth_createtile/osgearth_createtile.cpp    |  243 ++
 src/applications/osgearth_demo/osgearth_demo.cpp   |   13 +-
 .../osgearth_elevation/osgearth_elevation.cpp      |   30 +-
 .../osgearth_featureeditor.cpp                     |   25 +-
 .../osgearth_featurefilter.cpp                     |   15 +-
 .../osgearth_featureinfo/osgearth_featureinfo.cpp  |   13 +-
 .../osgearth_featurequery.cpp                      |  115 +-
 .../osgearth_features/osgearth_features.cpp        |   13 +-
 src/applications/osgearth_fog/osgearth_fog.cpp     |   13 +-
 .../osgearth_graticule/osgearth_graticule.cpp      |  107 +-
 .../osgearth_imageoverlay.cpp                      |   13 +-
 src/applications/osgearth_los/osgearth_los.cpp     |   62 +-
 src/applications/osgearth_manip/osgearth_manip.cpp |  389 ++-
 src/applications/osgearth_map/osgearth_map.cpp     |   16 +-
 .../osgearth_measure/osgearth_measure.cpp          |   13 +-
 .../osgearth_minimap/osgearth_minimap.cpp          |  127 +-
 src/applications/osgearth_mrt/osgearth_mrt.cpp     |   17 +-
 .../osgearth_occlusionculling.cpp                  |   13 +-
 .../osgearth_overlayviewer.cpp                     |   18 +-
 .../osgearth_package/osgearth_package.cpp          |    8 +-
 src/applications/osgearth_package_qt/ExportDialog  |   13 +-
 .../osgearth_package_qt/ExportDialog.cpp           |   13 +-
 .../osgearth_package_qt/PackageQtMainWindow        |   13 +-
 .../osgearth_package_qt/SceneController.cpp        |    2 +-
 .../osgearth_package_qt/SceneController.h          |   13 +-
 .../osgearth_package_qt/TMSExporter.cpp            |   14 +-
 src/applications/osgearth_package_qt/TMSExporter.h |   13 +-
 src/applications/osgearth_package_qt/WaitDialog    |   13 +-
 .../osgearth_package_qt/WaitDialog.cpp             |   13 +-
 .../osgearth_package_qt/package_qt.cpp             |   13 +-
 src/applications/osgearth_pick/CMakeLists.txt      |    7 +
 src/applications/osgearth_pick/osgearth_pick.cpp   |  318 +++
 src/applications/osgearth_qt/DemoMainWindow        |   15 +-
 src/applications/osgearth_qt/osgearth_qt.cpp       |   21 +-
 .../osgearth_qt_simple/osgearth_qt_simple.cpp      |   13 +-
 .../osgearth_qt_windows/osgearth_qt_windows.cpp    |   13 +-
 src/applications/osgearth_seed/osgearth_seed.cpp   |   13 +-
 .../osgearth_sequencecontrol.cpp                   |   13 +-
 .../osgearth_shadercomp/osgearth_shadercomp.cpp    |  104 +-
 .../osgearth_shadergen/osgearth_shadergen.cpp      |   13 +-
 .../osgearth_sharedlayer/osgearth_sharedlayer.cpp  |   13 +-
 .../osgearth_terraineffects.cpp                    |   16 -
 .../osgearth_terrainprofile.cpp                    |   13 +-
 src/applications/osgearth_tfs/osgearth_tfs.cpp     |   89 +-
 .../osgearth_tileindex/osgearth_tileindex.cpp      |   13 +-
 .../osgearth_tilesource/osgearth_tilesource.cpp    |   21 +-
 src/applications/osgearth_toc/osgearth_toc.cpp     |   40 +-
 .../osgearth_tracks/osgearth_tracks.cpp            |   24 +-
 .../osgearth_transform/osgearth_transform.cpp      |   24 +-
 .../osgearth_version/osgearth_version.cpp          |   13 +-
 .../osgearth_viewer/osgearth_viewer.cpp            |   19 +-
 src/osgEarth/AlphaEffect                           |   11 +-
 src/osgEarth/AlphaEffect.cpp                       |   33 +-
 src/osgEarth/AlphaEffect.frag.glsl                 |   12 +
 src/osgEarth/AutoScale                             |   13 +-
 src/osgEarth/AutoScale.cpp                         |   57 +-
 src/osgEarth/Bounds                                |   13 +-
 src/osgEarth/Bounds.cpp                            |    2 +-
 src/osgEarth/CMakeLists.txt                        |   67 +-
 src/osgEarth/Cache                                 |   17 +-
 src/osgEarth/Cache.cpp                             |    4 +-
 src/osgEarth/CacheBin                              |   13 +-
 src/osgEarth/CacheEstimator                        |   13 +-
 src/osgEarth/CacheEstimator.cpp                    |    2 +-
 src/osgEarth/CachePolicy                           |   13 +-
 src/osgEarth/CachePolicy.cpp                       |    2 +-
 src/osgEarth/CacheSeed                             |   13 +-
 src/osgEarth/CacheSeed.cpp                         |   23 +-
 src/osgEarth/Capabilities                          |   21 +-
 src/osgEarth/Capabilities.cpp                      |   45 +-
 src/osgEarth/ClampableNode.cpp                     |    2 +-
 src/osgEarth/Clamping                              |   78 +
 src/osgEarth/Clamping.cpp                          |  110 +
 src/osgEarth/ClampingTechnique                     |   15 +-
 src/osgEarth/ClampingTechnique.cpp                 |  143 +-
 src/osgEarth/ColorFilter                           |    2 +-
 src/osgEarth/ColorFilter.cpp                       |    2 +-
 src/osgEarth/Common                                |    5 +-
 src/osgEarth/CompositeTileSource                   |    2 +-
 src/osgEarth/CompositeTileSource.cpp               |   13 +-
 src/osgEarth/Config                                |    2 +-
 src/osgEarth/Config.cpp                            |   50 +-
 src/osgEarth/Containers                            |  376 ++-
 src/osgEarth/Cube                                  |    2 +-
 src/osgEarth/Cube.cpp                              |    2 +-
 src/osgEarth/CullingUtils                          |   29 +-
 src/osgEarth/CullingUtils.cpp                      |  228 +-
 src/osgEarth/DPLineSegmentIntersector              |    2 +-
 src/osgEarth/DPLineSegmentIntersector.cpp          |    2 +-
 src/osgEarth/DateTime                              |   11 +-
 src/osgEarth/DateTime.cpp                          |   11 +-
 src/osgEarth/Decluttering                          |   13 +-
 src/osgEarth/Decluttering.cpp                      |  100 +-
 src/osgEarth/DepthOffset                           |    2 +-
 src/osgEarth/DepthOffset.cpp                       |  127 +-
 src/osgEarth/DepthOffset.vert.glsl                 |   30 +
 src/osgEarth/Draggers                              |  170 --
 src/osgEarth/Draggers.cpp                          |  486 ----
 src/osgEarth/DrapeableNode.cpp                     |    2 +-
 src/osgEarth/Draping.frag.glsl                     |   19 +
 src/osgEarth/Draping.vert.glsl                     |   13 +
 src/osgEarth/DrapingTechnique                      |   13 +-
 src/osgEarth/DrapingTechnique.cpp                  |   61 +-
 src/osgEarth/DrawInstanced                         |    2 +-
 src/osgEarth/DrawInstanced.cpp                     |  320 ++-
 src/osgEarth/ECEF                                  |    2 +-
 src/osgEarth/ECEF.cpp                              |   40 +-
 src/osgEarth/ElevationField                        |   79 +
 src/osgEarth/ElevationField.cpp                    |  139 ++
 src/osgEarth/ElevationLOD                          |    2 +-
 src/osgEarth/ElevationLOD.cpp                      |    2 +-
 src/osgEarth/ElevationLayer                        |    9 +-
 src/osgEarth/ElevationLayer.cpp                    |  244 +-
 src/osgEarth/ElevationQuery                        |   52 +-
 src/osgEarth/ElevationQuery.cpp                    |  201 +-
 src/osgEarth/Export                                |    2 +-
 src/osgEarth/Extension                             |  106 +
 src/osgEarth/Extension.cpp                         |   71 +
 src/osgEarth/FadeEffect                            |    6 +-
 src/osgEarth/FadeEffect.cpp                        |   24 +-
 src/osgEarth/FileUtils                             |    2 +-
 src/osgEarth/FileUtils.cpp                         |    2 +-
 src/osgEarth/GPUClamping.frag.glsl                 |   11 +
 src/osgEarth/GPUClamping.vert.glsl                 |   77 +
 src/osgEarth/GPUClamping.vert.lib.glsl             |   36 +
 src/osgEarth/GeoCommon                             |    6 +-
 src/osgEarth/GeoData                               |   16 +-
 src/osgEarth/GeoData.cpp                           |  296 ++-
 src/osgEarth/GeoMath                               |    2 +-
 src/osgEarth/GeoMath.cpp                           |    8 +-
 src/osgEarth/GeoTransform                          |    2 +-
 src/osgEarth/GeoTransform.cpp                      |    2 +-
 src/osgEarth/Geoid                                 |    2 +-
 src/osgEarth/Geoid.cpp                             |    2 +-
 src/osgEarth/HTTPClient                            |    4 +-
 src/osgEarth/HTTPClient.cpp                        |  128 +-
 src/osgEarth/HeightFieldUtils                      |   49 +-
 src/osgEarth/HeightFieldUtils.cpp                  |  177 +-
 src/osgEarth/Horizon                               |  114 +
 src/osgEarth/Horizon.cpp                           |  200 ++
 src/osgEarth/IOTypes                               |   42 +-
 src/osgEarth/IOTypes.cpp                           |    2 +-
 src/osgEarth/ImageLayer                            |   71 +-
 src/osgEarth/ImageLayer.cpp                        |  234 +-
 src/osgEarth/ImageMosaic                           |    2 +-
 src/osgEarth/ImageMosaic.cpp                       |    9 +-
 src/osgEarth/ImageToHeightFieldConverter           |   13 +-
 src/osgEarth/ImageToHeightFieldConverter.cpp       |   38 +-
 src/osgEarth/ImageUtils                            |   70 +-
 src/osgEarth/ImageUtils.cpp                        |  326 ++-
 src/osgEarth/Instancing.vert.glsl                  |   31 +
 src/osgEarth/IntersectionPicker                    |  118 +
 src/osgEarth/IntersectionPicker.cpp                |  208 ++
 src/osgEarth/JsonUtils                             |    2 +-
 src/osgEarth/JsonUtils.cpp                         |    2 +-
 src/osgEarth/Layer                                 |    2 +-
 src/osgEarth/Layer.cpp                             |    3 +-
 src/osgEarth/LineFunctor                           |    2 +-
 src/osgEarth/LocalTangentPlane                     |    2 +-
 src/osgEarth/LocalTangentPlane.cpp                 |    2 +-
 src/osgEarth/Locators                              |    3 +-
 src/osgEarth/Locators.cpp                          |   19 +-
 src/osgEarth/Map                                   |    4 +-
 src/osgEarth/Map.cpp                               |   88 +-
 src/osgEarth/MapCallback                           |    2 +-
 src/osgEarth/MapCallback.cpp                       |    2 +-
 src/osgEarth/MapFrame                              |   48 +-
 src/osgEarth/MapFrame.cpp                          |  123 +-
 src/osgEarth/MapInfo                               |    2 +-
 src/osgEarth/MapInfo.cpp                           |    2 +-
 src/osgEarth/MapModelChange                        |    2 +-
 src/osgEarth/MapNode                               |   35 +-
 src/osgEarth/MapNode.cpp                           |  102 +-
 src/osgEarth/MapNodeObserver                       |    2 +-
 src/osgEarth/MapNodeOptions                        |   13 +-
 src/osgEarth/MapNodeOptions.cpp                    |    2 +-
 src/osgEarth/MapOptions                            |   13 +-
 src/osgEarth/MapOptions.cpp                        |    2 +-
 src/osgEarth/MaskLayer                             |    3 +-
 src/osgEarth/MaskLayer.cpp                         |    3 +-
 src/osgEarth/MaskNode                              |    2 +-
 src/osgEarth/MaskNode.cpp                          |    2 +-
 src/osgEarth/MaskSource                            |    2 +-
 src/osgEarth/MaskSource.cpp                        |    2 +-
 src/osgEarth/MemCache                              |    2 +-
 src/osgEarth/MemCache.cpp                          |    2 +-
 src/osgEarth/MimeTypes.cpp                         |    2 +-
 src/osgEarth/ModelLayer                            |   31 +-
 src/osgEarth/ModelLayer.cpp                        |   87 +-
 src/osgEarth/ModelSource                           |   12 +-
 src/osgEarth/ModelSource.cpp                       |    9 +-
 src/osgEarth/NativeProgramAdapter                  |  149 ++
 src/osgEarth/NodeUtils                             |   13 +-
 src/osgEarth/NodeUtils.cpp                         |    2 +-
 src/osgEarth/Notify                                |    2 +-
 src/osgEarth/Notify.cpp                            |    2 +-
 src/osgEarth/ObjectIndex                           |  213 ++
 src/osgEarth/ObjectIndex.cpp                       |  234 ++
 src/osgEarth/OverlayDecorator                      |   13 +-
 src/osgEarth/OverlayDecorator.cpp                  |   13 +-
 src/osgEarth/OverlayNode.cpp                       |    2 +-
 src/osgEarth/PhongLightingEffect                   |   11 +-
 src/osgEarth/PhongLightingEffect.cpp               |   27 +-
 src/osgEarth/Picker                                |   62 +
 src/osgEarth/Pickers                               |  112 -
 src/osgEarth/Pickers.cpp                           |  149 --
 src/osgEarth/PrimitiveIntersector                  |    2 +-
 src/osgEarth/PrimitiveIntersector.cpp              |   59 +-
 src/osgEarth/Profile                               |    5 +-
 src/osgEarth/Profile.cpp                           |   39 +-
 src/osgEarth/Profiler                              |   70 +
 src/osgEarth/Profiler.cpp                          |   75 +
 src/osgEarth/Progress                              |    8 +-
 src/osgEarth/Progress.cpp                          |    2 +-
 src/osgEarth/Random                                |   13 +-
 src/osgEarth/Random.cpp                            |    2 +-
 src/osgEarth/Registry                              |   31 +-
 src/osgEarth/Registry.cpp                          |   47 +-
 src/osgEarth/Revisioning                           |    2 +-
 src/osgEarth/Revisioning.cpp                       |    7 +-
 src/osgEarth/ShaderFactory                         |   17 +-
 src/osgEarth/ShaderFactory.cpp                     |    7 +-
 src/osgEarth/ShaderGenerator                       |   53 +-
 src/osgEarth/ShaderGenerator.cpp                   |  432 +++-
 src/osgEarth/ShaderLoader                          |  149 ++
 src/osgEarth/ShaderLoader.cpp                      |  391 +++
 src/osgEarth/ShaderUtils                           |    5 +-
 src/osgEarth/ShaderUtils.cpp                       |   33 +-
 src/osgEarth/Shaders                               |   43 +
 src/osgEarth/Shaders.cpp.in                        |   42 +
 src/osgEarth/SharedSARepo                          |   13 +-
 src/osgEarth/SparseTexture2DArray                  |   79 -
 src/osgEarth/SparseTexture2DArray.cpp              |  406 ---
 src/osgEarth/SpatialReference                      |   14 +-
 src/osgEarth/SpatialReference.cpp                  |   36 +-
 src/osgEarth/StateSetCache                         |   13 +-
 src/osgEarth/StateSetCache.cpp                     |    2 +-
 src/osgEarth/StateSetLOD                           |   13 +-
 src/osgEarth/StateSetLOD.cpp                       |   13 +-
 src/osgEarth/StringUtils                           |    9 +-
 src/osgEarth/StringUtils.cpp                       |   14 +-
 src/osgEarth/TaskService                           |   10 +-
 src/osgEarth/TaskService.cpp                       |   79 +-
 src/osgEarth/Terrain                               |    2 +-
 src/osgEarth/Terrain.cpp                           |   15 +-
 src/osgEarth/TerrainEffect                         |    2 +-
 src/osgEarth/TerrainEngineNode                     |   83 +-
 src/osgEarth/TerrainEngineNode.cpp                 |  106 +-
 src/osgEarth/TerrainLayer                          |   31 +-
 src/osgEarth/TerrainLayer.cpp                      |  187 +-
 src/osgEarth/TerrainOptions                        |   47 +-
 src/osgEarth/TerrainOptions.cpp                    |   19 +-
 src/osgEarth/TerrainTileNode                       |   88 +
 src/osgEarth/Tessellator                           |   13 +-
 src/osgEarth/Tessellator.cpp                       |   13 +-
 src/osgEarth/TextureCompositor                     |   21 +-
 src/osgEarth/TextureCompositor.cpp                 |   25 +-
 src/osgEarth/ThreadingUtils                        |  229 +-
 src/osgEarth/ThreadingUtils.cpp                    |    4 +-
 src/osgEarth/TileHandler                           |   13 +-
 src/osgEarth/TileHandler.cpp                       |   13 +-
 src/osgEarth/TileKey                               |    9 +-
 src/osgEarth/TileKey.cpp                           |    2 +-
 src/osgEarth/TileNode.cpp                          |    0
 src/osgEarth/TileSource                            |   68 +-
 src/osgEarth/TileSource.cpp                        |  194 +-
 src/osgEarth/TileVisitor                           |   13 +-
 src/osgEarth/TileVisitor.cpp                       |    6 +-
 src/osgEarth/TimeControl                           |   13 +-
 src/osgEarth/TimeControl.cpp                       |    2 +-
 src/osgEarth/TraversalData                         |    3 +-
 src/osgEarth/TraversalData.cpp                     |    4 +-
 src/osgEarth/URI                                   |    6 +-
 src/osgEarth/URI.cpp                               |   27 +-
 src/osgEarth/Units                                 |  211 +-
 src/osgEarth/Units.cpp                             |    2 +-
 src/osgEarth/Utils                                 |   24 +-
 src/osgEarth/Utils.cpp                             |  148 +-
 src/osgEarth/Version                               |   15 +-
 src/osgEarth/Version.cpp                           |    2 +-
 src/osgEarth/VerticalDatum                         |    6 +-
 src/osgEarth/VerticalDatum.cpp                     |    9 +-
 src/osgEarth/Viewpoint                             |  208 +-
 src/osgEarth/Viewpoint.cpp                         |  305 +--
 src/osgEarth/VirtualProgram                        |   99 +-
 src/osgEarth/VirtualProgram.cpp                    |  609 +++--
 src/osgEarth/XmlUtils                              |    9 +-
 src/osgEarth/XmlUtils.cpp                          |   57 +-
 src/osgEarth/optional                              |   13 +-
 src/osgEarthAnnotation/AnnotationData              |   13 +-
 src/osgEarthAnnotation/AnnotationData.cpp          |   13 +-
 src/osgEarthAnnotation/AnnotationEditing           |   16 +-
 src/osgEarthAnnotation/AnnotationEditing.cpp       |   61 +-
 src/osgEarthAnnotation/AnnotationNode              |   18 +-
 src/osgEarthAnnotation/AnnotationNode.cpp          |   35 +-
 src/osgEarthAnnotation/AnnotationRegistry          |   13 +-
 src/osgEarthAnnotation/AnnotationRegistry.cpp      |   19 +-
 src/osgEarthAnnotation/AnnotationSettings          |   13 +-
 src/osgEarthAnnotation/AnnotationSettings.cpp      |   13 +-
 src/osgEarthAnnotation/AnnotationUtils             |   33 +-
 src/osgEarthAnnotation/AnnotationUtils.cpp         |  232 +-
 src/osgEarthAnnotation/CMakeLists.txt              |    2 +
 src/osgEarthAnnotation/CircleNode                  |   13 +-
 src/osgEarthAnnotation/CircleNode.cpp              |   13 +-
 src/osgEarthAnnotation/Common                      |    2 +-
 src/osgEarthAnnotation/Decoration                  |   13 +-
 src/osgEarthAnnotation/Decoration.cpp              |   13 +-
 src/osgEarthAnnotation/Draggers                    |  175 ++
 src/osgEarthAnnotation/Draggers.cpp                |  492 ++++
 src/osgEarthAnnotation/EllipseNode                 |   19 +-
 src/osgEarthAnnotation/EllipseNode.cpp             |   14 +-
 src/osgEarthAnnotation/Export                      |    2 +-
 src/osgEarthAnnotation/FeatureEditing              |   10 +-
 src/osgEarthAnnotation/FeatureEditing.cpp          |   24 +-
 src/osgEarthAnnotation/FeatureNode                 |   82 +-
 src/osgEarthAnnotation/FeatureNode.cpp             |  278 ++-
 src/osgEarthAnnotation/HighlightDecoration         |   13 +-
 src/osgEarthAnnotation/HighlightDecoration.cpp     |   15 +-
 src/osgEarthAnnotation/ImageOverlay                |    2 +-
 src/osgEarthAnnotation/ImageOverlay.cpp            |   17 +-
 src/osgEarthAnnotation/ImageOverlayEditor          |    7 +-
 src/osgEarthAnnotation/ImageOverlayEditor.cpp      |   20 +-
 src/osgEarthAnnotation/LabelNode                   |   14 +-
 src/osgEarthAnnotation/LabelNode.cpp               |   18 +-
 src/osgEarthAnnotation/LocalGeometryNode           |   13 +-
 src/osgEarthAnnotation/LocalGeometryNode.cpp       |   13 +-
 src/osgEarthAnnotation/LocalizedNode               |   22 +-
 src/osgEarthAnnotation/LocalizedNode.cpp           |   51 +-
 src/osgEarthAnnotation/ModelNode                   |   17 +-
 src/osgEarthAnnotation/ModelNode.cpp               |   23 +-
 src/osgEarthAnnotation/OrthoNode                   |   22 +-
 src/osgEarthAnnotation/OrthoNode.cpp               |  196 +-
 src/osgEarthAnnotation/PlaceNode                   |   15 +-
 src/osgEarthAnnotation/PlaceNode.cpp               |   22 +-
 src/osgEarthAnnotation/RectangleNode               |   13 +-
 src/osgEarthAnnotation/RectangleNode.cpp           |   13 +-
 src/osgEarthAnnotation/ScaleDecoration             |   13 +-
 src/osgEarthAnnotation/TrackNode                   |   13 +-
 src/osgEarthAnnotation/TrackNode.cpp               |   13 +-
 src/osgEarthDrivers/CMakeLists.txt                 |   82 +-
 src/osgEarthDrivers/agglite/AGGLiteOptions         |    4 +-
 .../agglite/AGGLiteRasterizerTileSource.cpp        |  206 +-
 src/osgEarthDrivers/arcgis/ArcGISOptions           |    2 +-
 src/osgEarthDrivers/arcgis/Extent.h                |    2 +-
 src/osgEarthDrivers/arcgis/MapService.h            |    2 +-
 src/osgEarthDrivers/arcgis/ReaderWriterArcGIS.cpp  |   13 +-
 .../ReaderWriterArcGISMapCache.cpp                 |    2 +-
 src/osgEarthDrivers/bing/BingOptions               |    2 +-
 .../cache_filesystem/FileSystemCache               |    2 +-
 .../cache_filesystem/FileSystemCache.cpp           |   14 +-
 src/osgEarthDrivers/cache_leveldb/CMakeLists.txt   |    3 +
 src/osgEarthDrivers/cache_leveldb/LevelDBCache     |    2 +-
 src/osgEarthDrivers/cache_leveldb/LevelDBCache.cpp |    7 +-
 src/osgEarthDrivers/cache_leveldb/LevelDBCacheBin  |    6 +-
 .../cache_leveldb/LevelDBCacheBin.cpp              |   34 +-
 .../cache_leveldb/LevelDBCacheDriver.cpp           |    2 +-
 .../cache_leveldb/LevelDBCacheOptions              |    2 +-
 src/osgEarthDrivers/cache_leveldb/Tracker          |    2 +-
 src/osgEarthDrivers/colorramp/ColorRampOptions     |    2 +-
 .../colorramp/ColorRampTileSource.cpp              |    4 +-
 src/osgEarthDrivers/debug/DebugOptions             |    6 +-
 src/osgEarthDrivers/debug/DebugTileSource.cpp      |    2 +-
 src/osgEarthDrivers/earth/EarthFileSerializer      |    2 +-
 src/osgEarthDrivers/earth/EarthFileSerializer1.cpp |    2 +-
 src/osgEarthDrivers/earth/EarthFileSerializer2.cpp |   26 +-
 src/osgEarthDrivers/earth/ReaderWriterOsgEarth.cpp |   56 +-
 .../engine_byo/BYOTerrainEngineDriver.cpp          |    2 +-
 .../engine_byo/BYOTerrainEngineNode                |    2 +-
 .../engine_byo/BYOTerrainEngineNode.cpp            |   13 +-
 .../engine_byo/BYOTerrainEngineOptions             |    2 +-
 src/osgEarthDrivers/engine_byo/Common              |    2 +-
 src/osgEarthDrivers/engine_mp/CMakeLists.txt       |   21 +-
 src/osgEarthDrivers/engine_mp/Common               |    2 +-
 .../engine_mp/DynamicLODScaleCallback              |   13 +-
 src/osgEarthDrivers/engine_mp/FileLocationCallback |   13 +-
 src/osgEarthDrivers/engine_mp/HeightFieldCache     |   99 +
 src/osgEarthDrivers/engine_mp/HeightFieldCache.cpp |  145 ++
 src/osgEarthDrivers/engine_mp/KeyNodeFactory       |   13 +-
 src/osgEarthDrivers/engine_mp/KeyNodeFactory.cpp   |   13 +-
 src/osgEarthDrivers/engine_mp/MPEngine.frag.glsl   |   37 +
 .../engine_mp/MPEngine.vert.model.glsl             |   15 +
 .../engine_mp/MPEngine.vert.view.glsl              |   27 +
 src/osgEarthDrivers/engine_mp/MPGeometry           |   27 +-
 src/osgEarthDrivers/engine_mp/MPGeometry.cpp       |  224 +-
 src/osgEarthDrivers/engine_mp/MPShaders            |   39 +
 src/osgEarthDrivers/engine_mp/MPShaders.cpp.in     |   19 +
 .../engine_mp/MPTerrainEngineDriver.cpp            |   23 +-
 src/osgEarthDrivers/engine_mp/MPTerrainEngineNode  |   22 +-
 .../engine_mp/MPTerrainEngineNode.cpp              |  477 +++-
 .../engine_mp/MPTerrainEngineOptions               |   43 +-
 .../engine_mp/QuickReleaseGLObjects                |   13 +-
 src/osgEarthDrivers/engine_mp/SingleKeyNodeFactory |   23 +-
 .../engine_mp/SingleKeyNodeFactory.cpp             |  155 +-
 src/osgEarthDrivers/engine_mp/TerrainNode          |   13 +-
 src/osgEarthDrivers/engine_mp/TerrainNode.cpp      |   13 +-
 src/osgEarthDrivers/engine_mp/TileGroup            |   13 +-
 src/osgEarthDrivers/engine_mp/TileGroup.cpp        |   21 +-
 src/osgEarthDrivers/engine_mp/TileModel            |   83 +-
 src/osgEarthDrivers/engine_mp/TileModel.cpp        |  168 +-
 src/osgEarthDrivers/engine_mp/TileModelCompiler    |   21 +-
 .../engine_mp/TileModelCompiler.cpp                | 1086 ++++----
 src/osgEarthDrivers/engine_mp/TileModelFactory     |  184 +-
 src/osgEarthDrivers/engine_mp/TileModelFactory.cpp |  209 +-
 src/osgEarthDrivers/engine_mp/TileNode             |   74 +-
 src/osgEarthDrivers/engine_mp/TileNode.cpp         |  207 +-
 src/osgEarthDrivers/engine_mp/TileNodeRegistry     |   36 +-
 src/osgEarthDrivers/engine_mp/TileNodeRegistry.cpp |   69 +-
 src/osgEarthDrivers/engine_mp/TilePagedLOD         |   32 +-
 src/osgEarthDrivers/engine_mp/TilePagedLOD.cpp     |  202 +-
 src/osgEarthDrivers/fastdxt/CMakeLists.txt         |   39 +
 .../fastdxt/FastDXTImageProcessor.cpp              |  104 +
 src/osgEarthDrivers/fastdxt/dxt.cpp                |  542 ++++
 src/osgEarthDrivers/fastdxt/dxt.h                  |   68 +
 src/osgEarthDrivers/fastdxt/intrinsic.cpp          |  533 ++++
 src/osgEarthDrivers/fastdxt/libdxt.cpp             |   95 +
 src/osgEarthDrivers/fastdxt/libdxt.h               |   35 +
 src/osgEarthDrivers/fastdxt/util.cpp               |  342 +++
 src/osgEarthDrivers/fastdxt/util.h                 |  108 +
 .../feature_elevation/CMakeLists.txt               |   17 +
 .../feature_elevation/FeatureElevationOptions      |   85 +
 .../ReaderWriterFeatureElevation.cpp               |  269 ++
 src/osgEarthDrivers/feature_ogr/FeatureCursorOGR   |    2 +-
 .../feature_ogr/FeatureCursorOGR.cpp               |   33 +-
 .../feature_ogr/FeatureSourceOGR.cpp               |   38 +-
 src/osgEarthDrivers/feature_ogr/OGRFeatureOptions  |    2 +-
 src/osgEarthDrivers/feature_raster/CMakeLists.txt  |   15 +
 .../feature_raster/FeatureSourceRaster.cpp         |  243 ++
 .../feature_raster/RasterFeatureOptions            |   89 +
 .../feature_tfs/FeatureSourceTFS.cpp               |   58 +-
 src/osgEarthDrivers/feature_tfs/TFSFeatureOptions  |    2 +-
 .../feature_wfs/FeatureSourceWFS.cpp               |   39 +-
 src/osgEarthDrivers/feature_wfs/WFSFeatureOptions  |    2 +-
 src/osgEarthDrivers/gdal/GDALOptions               |    2 +-
 src/osgEarthDrivers/gdal/ReaderWriterGDAL.cpp      |  322 ++-
 src/osgEarthDrivers/kml/CMakeLists.txt             |    4 +-
 src/osgEarthDrivers/kml/KML                        |    2 +-
 src/osgEarthDrivers/kml/KMLOptions                 |    2 +-
 src/osgEarthDrivers/kml/KMLReader                  |   11 +-
 src/osgEarthDrivers/kml/KMLReader.cpp              |   72 +-
 src/osgEarthDrivers/kml/KML_Common                 |  102 +-
 src/osgEarthDrivers/kml/KML_Container              |   14 +-
 src/osgEarthDrivers/kml/KML_Document               |    8 +-
 src/osgEarthDrivers/kml/KML_Document.cpp           |   24 +-
 src/osgEarthDrivers/kml/KML_Feature                |    8 +-
 src/osgEarthDrivers/kml/KML_Feature.cpp            |   67 +-
 src/osgEarthDrivers/kml/KML_Folder                 |    8 +-
 src/osgEarthDrivers/kml/KML_Folder.cpp             |   20 +-
 src/osgEarthDrivers/kml/KML_Geometry               |   10 +-
 src/osgEarthDrivers/kml/KML_Geometry.cpp           |  189 +-
 src/osgEarthDrivers/kml/KML_GroundOverlay          |    6 +-
 src/osgEarthDrivers/kml/KML_GroundOverlay.cpp      |   44 +-
 src/osgEarthDrivers/kml/KML_IconStyle              |    4 +-
 src/osgEarthDrivers/kml/KML_IconStyle.cpp          |   33 +-
 src/osgEarthDrivers/kml/KML_LabelStyle             |    4 +-
 src/osgEarthDrivers/kml/KML_LabelStyle.cpp         |    4 +-
 src/osgEarthDrivers/kml/KML_LineString             |    6 +-
 src/osgEarthDrivers/kml/KML_LineString.cpp         |   12 +-
 src/osgEarthDrivers/kml/KML_LineStyle              |    4 +-
 src/osgEarthDrivers/kml/KML_LineStyle.cpp          |   16 +-
 src/osgEarthDrivers/kml/KML_LinearRing             |    6 +-
 src/osgEarthDrivers/kml/KML_LinearRing.cpp         |   12 +-
 src/osgEarthDrivers/kml/KML_Model                  |    6 +-
 src/osgEarthDrivers/kml/KML_Model.cpp              |   54 +-
 src/osgEarthDrivers/kml/KML_MultiGeometry          |    4 +-
 src/osgEarthDrivers/kml/KML_MultiGeometry.cpp      |    4 +-
 src/osgEarthDrivers/kml/KML_NetworkLink            |    2 +-
 src/osgEarthDrivers/kml/KML_NetworkLink.cpp        |   36 +-
 src/osgEarthDrivers/kml/KML_NetworkLinkControl     |    6 +-
 src/osgEarthDrivers/kml/KML_NetworkLinkControl.cpp |   10 +-
 src/osgEarthDrivers/kml/KML_Object                 |    8 +-
 src/osgEarthDrivers/kml/KML_Object.cpp             |    4 +-
 src/osgEarthDrivers/kml/KML_Overlay                |    6 +-
 src/osgEarthDrivers/kml/KML_Overlay.cpp            |   10 +-
 src/osgEarthDrivers/kml/KML_PhotoOverlay           |    6 +-
 src/osgEarthDrivers/kml/KML_PhotoOverlay.cpp       |   10 +-
 src/osgEarthDrivers/kml/KML_Placemark              |    2 +-
 src/osgEarthDrivers/kml/KML_Placemark.cpp          |   55 +-
 src/osgEarthDrivers/kml/KML_Point                  |    4 +-
 src/osgEarthDrivers/kml/KML_Point.cpp              |    6 +-
 src/osgEarthDrivers/kml/KML_PolyStyle              |    4 +-
 src/osgEarthDrivers/kml/KML_PolyStyle.cpp          |   24 +-
 src/osgEarthDrivers/kml/KML_Polygon                |    6 +-
 src/osgEarthDrivers/kml/KML_Polygon.cpp            |   49 +-
 src/osgEarthDrivers/kml/KML_Root                   |    9 +-
 src/osgEarthDrivers/kml/KML_Root.cpp               |   20 +-
 src/osgEarthDrivers/kml/KML_Schema                 |    2 +-
 src/osgEarthDrivers/kml/KML_Schema.cpp             |    2 +-
 src/osgEarthDrivers/kml/KML_ScreenOverlay          |    6 +-
 src/osgEarthDrivers/kml/KML_ScreenOverlay.cpp      |    8 +-
 src/osgEarthDrivers/kml/KML_Style                  |    4 +-
 src/osgEarthDrivers/kml/KML_Style.cpp              |   14 +-
 src/osgEarthDrivers/kml/KML_StyleMap               |    4 +-
 src/osgEarthDrivers/kml/KML_StyleMap.cpp           |   12 +-
 src/osgEarthDrivers/kml/KML_StyleSelector          |    2 +-
 src/osgEarthDrivers/kml/KMZArchive                 |    2 +-
 src/osgEarthDrivers/kml/KMZArchive.cpp             |    2 +-
 src/osgEarthDrivers/kml/ReaderWriterKML.cpp        |    5 +-
 src/osgEarthDrivers/kml/rapidxml.hpp               | 2596 ++++++++++++++++++++
 src/osgEarthDrivers/kml/rapidxml_ext.hpp           |   61 +
 src/osgEarthDrivers/kml/rapidxml_iterators.hpp     |  174 ++
 src/osgEarthDrivers/kml/rapidxml_print.hpp         |  421 ++++
 src/osgEarthDrivers/kml/rapidxml_utils.hpp         |  122 +
 .../label_annotation/AnnotationLabelSource.cpp     |   28 +-
 .../label_overlay/OverlayLabelSource.cpp           |  216 --
 .../mask_feature/FeatureMaskOptions                |    2 +-
 .../mask_feature/FeatureMaskSource.cpp             |    2 +-
 src/osgEarthDrivers/mbtiles/CMakeLists.txt         |    4 +
 src/osgEarthDrivers/mbtiles/MBTilesOptions         |    5 +-
 src/osgEarthDrivers/mbtiles/MBTilesPlugin.cpp      |   11 +-
 src/osgEarthDrivers/mbtiles/MBTilesTileSource      |   11 +-
 src/osgEarthDrivers/mbtiles/MBTilesTileSource.cpp  |   26 +-
 .../model_feature_geom/FeatureGeomModelOptions     |    2 +-
 .../model_feature_geom/FeatureGeomModelSource.cpp  |    2 +-
 .../FeatureStencilModelOptions                     |    2 +-
 .../FeatureStencilModelSource.cpp                  |    2 +-
 .../model_simple/SimpleModelOptions                |    2 +-
 .../model_simple/SimpleModelSource.cpp             |   70 +-
 src/osgEarthDrivers/noise/NoiseDriver.cpp          |  321 ---
 .../ocean_simple/ElevationProxyImageLayer          |    4 +-
 .../ocean_simple/ElevationProxyImageLayer.cpp      |   11 +-
 .../ocean_simple/SimpleOceanDriver.cpp             |    2 +-
 src/osgEarthDrivers/ocean_simple/SimpleOceanNode   |    2 +-
 .../ocean_simple/SimpleOceanNode.cpp               |    6 +-
 .../ocean_simple/SimpleOceanOptions                |    4 +-
 .../ocean_simple/SimpleOceanShaders                |    2 +-
 src/osgEarthDrivers/ocean_triton/CMakeLists.txt    |    3 +
 src/osgEarthDrivers/ocean_triton/TritonContext     |   19 +-
 src/osgEarthDrivers/ocean_triton/TritonContext.cpp |   12 +-
 src/osgEarthDrivers/ocean_triton/TritonDrawable    |   23 +-
 .../ocean_triton/TritonDrawable.cpp                |  633 ++---
 src/osgEarthDrivers/ocean_triton/TritonDriver.cpp  |   13 +-
 src/osgEarthDrivers/ocean_triton/TritonNode        |   19 +-
 src/osgEarthDrivers/ocean_triton/TritonNode.cpp    |   16 +-
 src/osgEarthDrivers/ocean_triton/TritonOptions     |   51 +-
 src/osgEarthDrivers/osg/OSGOptions                 |    2 +-
 src/osgEarthDrivers/osg/OSGTileSource.cpp          |    2 +-
 src/osgEarthDrivers/quadkey/CMakeLists.txt         |   15 +
 src/osgEarthDrivers/quadkey/QuadKeyOptions         |   82 +
 .../quadkey/ReaderWriterQuadKey.cpp                |  199 ++
 .../refresh/ReaderWriterRefresh.cpp                |   13 +-
 src/osgEarthDrivers/refresh/RefreshOptions         |    2 +-
 .../script_engine_duktape/CMakeLists.txt           |    3 +
 .../script_engine_duktape/DuktapeEngine            |    6 +-
 .../script_engine_duktape/DuktapeEngine.cpp        |    2 +-
 .../script_engine_duktape/JSGeometry               |    2 +-
 .../script_engine_duktape/Plugin.cpp               |    2 +-
 .../script_engine_duktape/duktape.h                |    3 +-
 .../script_engine_javascriptcore/CMakeLists.txt    |    3 +
 .../script_engine_v8/CMakeLists.txt                |    3 +
 src/osgEarthDrivers/sky_gl/GLSkyDriver.cpp         |    2 +-
 src/osgEarthDrivers/sky_gl/GLSkyNode               |    3 +-
 src/osgEarthDrivers/sky_gl/GLSkyNode.cpp           |   20 +-
 src/osgEarthDrivers/sky_gl/GLSkyOptions            |    2 +-
 src/osgEarthDrivers/sky_gl/GLSkyShaders            |   12 +-
 .../sky_silverlining/CMakeLists.txt                |    4 +
 .../sky_silverlining/SilverLiningCloudsDrawable    |   14 +-
 .../SilverLiningCloudsDrawable.cpp                 |   30 +-
 .../sky_silverlining/SilverLiningContext           |   22 +-
 .../sky_silverlining/SilverLiningContext.cpp       |   47 +-
 .../sky_silverlining/SilverLiningDriver.cpp        |   12 +-
 .../sky_silverlining/SilverLiningNode              |   21 +-
 .../sky_silverlining/SilverLiningNode.cpp          |   62 +-
 .../sky_silverlining/SilverLiningOptions           |   37 +-
 .../sky_silverlining/SilverLiningSkyDrawable       |    8 +-
 .../sky_silverlining/SilverLiningSkyDrawable.cpp   |    8 +-
 src/osgEarthDrivers/sky_simple/CMakeLists.txt      |   52 +-
 .../sky_simple/SimpleSky.Atmosphere.frag.glsl      |   34 +
 .../sky_simple/SimpleSky.Atmosphere.vert.glsl      |  155 ++
 .../sky_simple/SimpleSky.Ground.ONeil.frag.glsl    |   67 +
 .../sky_simple/SimpleSky.Ground.ONeil.vert.glsl    |  166 ++
 .../sky_simple/SimpleSky.Moon.frag.glsl            |   10 +
 .../sky_simple/SimpleSky.Moon.vert.glsl            |   11 +
 .../sky_simple/SimpleSky.Stars.GLES.frag.glsl      |    9 +
 .../sky_simple/SimpleSky.Stars.GLES.vert.glsl      |   28 +
 .../sky_simple/SimpleSky.Stars.frag.glsl           |   12 +
 .../sky_simple/SimpleSky.Stars.vert.glsl           |   27 +
 .../sky_simple/SimpleSky.Sun.frag.glsl             |   18 +
 .../sky_simple/SimpleSky.Sun.vert.glsl             |   12 +
 src/osgEarthDrivers/sky_simple/SimpleSkyDriver.cpp |    2 +-
 src/osgEarthDrivers/sky_simple/SimpleSkyNode       |    3 +-
 src/osgEarthDrivers/sky_simple/SimpleSkyNode.cpp   |   97 +-
 src/osgEarthDrivers/sky_simple/SimpleSkyOptions    |   12 +-
 src/osgEarthDrivers/sky_simple/SimpleSkyShaders    |  675 +----
 .../sky_simple/SimpleSkyShaders.cpp.in             |   45 +
 src/osgEarthDrivers/splat_mask/SplatMaskDriver.cpp |    2 +-
 src/osgEarthDrivers/splat_mask/SplatMaskOptions    |    2 +-
 .../template_matclass/TemplateMatClassDriver.cpp   |    2 +-
 .../template_matclass/TemplateMatClassOptions      |    2 +-
 .../tilecache/ReaderWriterTileCache.cpp            |    2 +-
 src/osgEarthDrivers/tilecache/TileCacheOptions     |    2 +-
 .../tileindex/ReaderWriterTileIndex.cpp            |   13 +-
 src/osgEarthDrivers/tileindex/TileIndexOptions     |    2 +-
 .../tileservice/ReaderWriterTileService.cpp        |    2 +-
 src/osgEarthDrivers/tileservice/TileServiceOptions |    2 +-
 src/osgEarthDrivers/tms/TMSOptions                 |    2 +-
 src/osgEarthDrivers/tms/TMSPlugin.cpp              |   13 +-
 src/osgEarthDrivers/tms/TMSTileSource              |   13 +-
 src/osgEarthDrivers/tms/TMSTileSource.cpp          |   13 +-
 src/osgEarthDrivers/vdatum_egm2008/EGM2008.cpp     |    2 +-
 src/osgEarthDrivers/vdatum_egm2008/EGM2008Grid.h   |    2 +-
 src/osgEarthDrivers/vdatum_egm84/EGM84.cpp         |    4 +-
 src/osgEarthDrivers/vdatum_egm84/EGM84Grid.h       |    2 +-
 src/osgEarthDrivers/vdatum_egm96/EGM96.cpp         |    2 +-
 src/osgEarthDrivers/vdatum_egm96/EGM96Grid.h       |    2 +-
 src/osgEarthDrivers/vpb/ReaderWriterVPB.cpp        |    2 +-
 src/osgEarthDrivers/vpb/VPBOptions                 |    2 +-
 src/osgEarthDrivers/wcs/ReaderWriterWCS.cpp        |    2 +-
 src/osgEarthDrivers/wcs/WCS11Source.cpp            |    2 +-
 src/osgEarthDrivers/wcs/WCS11Source.h              |    2 +-
 src/osgEarthDrivers/wcs/WCSOptions                 |    2 +-
 src/osgEarthDrivers/wms/ReaderWriterWMS.cpp        |   39 +-
 src/osgEarthDrivers/wms/TileService                |    2 +-
 src/osgEarthDrivers/wms/TileService.cpp            |    2 +-
 src/osgEarthDrivers/wms/WMSOptions                 |    2 +-
 src/osgEarthDrivers/xyz/ReaderWriterXYZ.cpp        |   13 +-
 src/osgEarthDrivers/xyz/XYZOptions                 |    2 +-
 src/osgEarthDrivers/yahoo/ReaderWriterYahoo.cpp    |    2 +-
 src/osgEarthDrivers/yahoo/YahooOptions             |    2 +-
 .../cache_sqlite3}/cache_sqlite3/CMakeLists.txt    |    0
 .../cache_sqlite3}/cache_sqlite3/Sqlite3Cache.cpp  |    0
 .../cache_sqlite3/Sqlite3CacheOptions              |    0
 .../engine_droam/AMRGeometry                       |    0
 .../engine_droam/AMRGeometry.cpp                   |    0
 .../engine_droam/AMRShaders.h                      |    0
 .../engine_droam/CMakeLists.txt                    |    0
 .../engine_droam/Common                            |    0
 .../engine_droam/CubeManifold                      |    0
 .../engine_droam/CubeManifold.cpp                  |    0
 .../engine_droam/DRoamNode                         |    0
 .../engine_droam/DRoamNode.cpp                     |    0
 .../engine_droam/Diamond                           |    0
 .../engine_droam/Diamond.cpp                       |    0
 .../engine_droam/GeodeticManifold                  |    0
 .../engine_droam/GeodeticManifold.cpp              |    0
 .../engine_droam/Manifold                          |    0
 .../engine_droam/Manifold.cpp                      |    0
 .../engine_droam/MeshManager                       |    0
 .../engine_droam/MeshManager.cpp                   |    0
 .../engine_droam/Plugin.cpp                        |    0
 .../engine_osgterrain/CMakeLists.txt               |    0
 .../engine_osgterrain/Common                       |    0
 .../engine_osgterrain/CustomTerrain                |    0
 .../engine_osgterrain/CustomTerrain.cpp            |    0
 .../engine_osgterrain/CustomTerrainTechnique       |    0
 .../engine_osgterrain/CustomTile                   |    0
 .../engine_osgterrain/CustomTile.cpp               |    0
 .../engine_osgterrain/DynamicLODScaleCallback      |    0
 .../engine_osgterrain/FileLocationCallback         |    0
 .../engine_osgterrain/KeyNodeFactory               |    0
 .../engine_osgterrain/KeyNodeFactory.cpp           |    0
 .../engine_osgterrain/LODFactorCallback            |    0
 .../engine_osgterrain/LODFactorCallback.cpp        |    0
 .../engine_osgterrain/MultiPassTerrainTechnique    |    0
 .../MultiPassTerrainTechnique.cpp                  |    0
 .../engine_osgterrain/OSGTerrainEngineNode         |    0
 .../engine_osgterrain/OSGTerrainEngineNode.cpp     |    0
 .../engine_osgterrain/OSGTerrainOptions            |    0
 .../engine_osgterrain/OSGTileFactory               |    0
 .../engine_osgterrain/OSGTileFactory.cpp           |    0
 .../engine_osgterrain/ParallelKeyNodeFactory       |    0
 .../engine_osgterrain/ParallelKeyNodeFactory.cpp   |    0
 .../engine_osgterrain/Plugin.cpp                   |    0
 .../engine_osgterrain/SerialKeyNodeFactory         |    0
 .../engine_osgterrain/SerialKeyNodeFactory.cpp     |    0
 .../engine_osgterrain/SinglePassTerrainTechnique   |    0
 .../SinglePassTerrainTechnique.cpp                 |    0
 .../engine_osgterrain/StreamingTerrainNode         |    0
 .../engine_osgterrain/StreamingTerrainNode.cpp     |    0
 .../engine_osgterrain/StreamingTile                |    0
 .../engine_osgterrain/StreamingTile.cpp            |    0
 .../engine_osgterrain/Terrain                      |    0
 .../engine_osgterrain/Terrain.cpp                  |    0
 .../engine_osgterrain/TerrainNode                  |    0
 .../engine_osgterrain/TerrainNode.cpp              |    0
 .../engine_osgterrain/Tile                         |    0
 .../engine_osgterrain/Tile.cpp                     |    0
 .../engine_osgterrain/TileBuilder                  |    0
 .../engine_osgterrain/TileBuilder.cpp              |    0
 .../engine_osgterrain/TransparentLayer             |    0
 .../engine_quadtree/CMakeLists.txt                 |    0
 .../engine_quadtree/Common                         |    0
 .../engine_quadtree/CustomPagedLOD                 |    0
 .../engine_quadtree/CustomPagedLOD.cpp             |    0
 .../engine_quadtree/DynamicLODScaleCallback        |    0
 .../engine_quadtree/FileLocationCallback           |    0
 .../engine_quadtree/KeyNodeFactory                 |    0
 .../engine_quadtree/KeyNodeFactory.cpp             |    0
 .../engine_quadtree/LODFactorCallback              |    0
 .../engine_quadtree/LODFactorCallback.cpp          |    0
 .../QuadTreeTerrainEngineDriver.cpp                |    0
 .../engine_quadtree/QuadTreeTerrainEngineNode      |    0
 .../engine_quadtree/QuadTreeTerrainEngineNode.cpp  |    0
 .../engine_quadtree/QuadTreeTerrainEngineOptions   |    0
 .../engine_quadtree/QuickReleaseGLObjects          |    0
 .../engine_quadtree/SerialKeyNodeFactory           |    0
 .../engine_quadtree/SerialKeyNodeFactory.cpp       |    0
 .../engine_quadtree/TerrainNode                    |    0
 .../engine_quadtree/TerrainNode.cpp                |    0
 .../engine_quadtree/TileModel                      |    0
 .../engine_quadtree/TileModelCompiler              |    0
 .../engine_quadtree/TileModelCompiler.cpp          |    0
 .../engine_quadtree/TileModelFactory               |    0
 .../engine_quadtree/TileModelFactory.cpp           |    0
 .../engine_quadtree/TileNode                       |    0
 .../engine_quadtree/TileNode.cpp                   |    0
 .../engine_quadtree/TileNodeRegistry               |    0
 .../engine_quadtree/TileNodeRegistry.cpp           |    0
 .../engine_seamless/AutoBuffer                     |    0
 .../engine_seamless/CMakeLists.txt                 |    0
 .../engine_seamless/Euler                          |    0
 .../engine_seamless/Euler.cpp                      |    0
 .../engine_seamless/GeoPatch                       |    0
 .../engine_seamless/GeoPatch.cpp                   |    0
 .../engine_seamless/Geographic                     |    0
 .../engine_seamless/Geographic.cpp                 |    0
 .../engine_seamless/MultiArray                     |    0
 .../engine_seamless/Patch                          |    0
 .../engine_seamless/Patch.cpp                      |    0
 .../engine_seamless/PatchGroup                     |    0
 .../engine_seamless/PatchGroup.cpp                 |    0
 .../engine_seamless/PatchInfo                      |    0
 .../engine_seamless/PatchSet                       |    0
 .../engine_seamless/PatchSet.cpp                   |    0
 .../engine_seamless/Projected                      |    0
 .../engine_seamless/Projected.cpp                  |    0
 .../engine_seamless/QSC                            |    0
 .../engine_seamless/QSC.cpp                        |    0
 .../engine_seamless/SeamlessEngineNode             |    0
 .../engine_seamless/SeamlessEngineNode.cpp         |    0
 .../engine_seamless/SeamlessOptions                |    0
 .../engine_seamless/SeamlessPlugin.cpp             |    0
 .../engine_seamless/doc/README                     |    0
 .../engine_seamless/doc/euler.kml                  |    0
 .../engine_seamless/doc/notes.org                  |    0
 .../feature_mapnikvectortiles/CMakeLists.txt       |   29 +
 .../feature_mapnikvectortiles/FeatureSourceMVT.cpp |  446 ++++
 .../feature_mapnikvectortiles/MVTFeatureOptions    |   76 +
 .../feature_mapnikvectortiles/vector_tile.proto    |   92 +
 .../label_overlay/CMakeLists.txt                   |    0
 .../label_overlay/OverlayLabelSource               |    0
 .../label_overlay/OverlayLabelSource.cpp           |  213 ++
 .../model_feature_label/CMakeLists.txt             |    0
 .../model_feature_label/FeatureLabelModelOptions   |    0
 .../FeatureLabelModelSource.cpp                    |    0
 .../noise/CMakeLists.txt                           |    0
 src/osgEarthDriversDisabled/noise/NoiseDriver.cpp  |  322 +++
 .../noise/NoiseOptions                             |    0
 src/osgEarthExtensions/CMakeLists.txt              |   29 +
 .../billboard/BillboardExtension                   |   71 +
 .../billboard/BillboardExtension.cpp               |  288 +++
 src/osgEarthExtensions/billboard/BillboardOptions  |  111 +
 .../billboard/BillboardPlugin.cpp                  |   56 +
 src/osgEarthExtensions/billboard/BillboardShaders  |  106 +
 src/osgEarthExtensions/billboard/CMakeLists.txt    |   27 +
 .../bumpmap/BumpMap.frag.common.glsl               |   26 +
 .../bumpmap/BumpMap.frag.progressive.glsl          |   56 +
 .../bumpmap/BumpMap.frag.simple.glsl               |   23 +
 .../bumpmap/BumpMap.vert.model.glsl                |   60 +
 .../bumpmap/BumpMap.vert.view.glsl                 |   12 +
 src/osgEarthExtensions/bumpmap/BumpMapExtension    |   73 +
 .../bumpmap/BumpMapExtension.cpp                   |   98 +
 src/osgEarthExtensions/bumpmap/BumpMapOptions      |  110 +
 src/osgEarthExtensions/bumpmap/BumpMapPlugin.cpp   |   56 +
 src/osgEarthExtensions/bumpmap/BumpMapShaders      |   39 +
 .../bumpmap/BumpMapShaders.cpp.in                  |   24 +
 .../bumpmap/BumpMapTerrainEffect                   |   82 +
 .../bumpmap/BumpMapTerrainEffect.cpp               |  140 ++
 src/osgEarthExtensions/bumpmap/CMakeLists.txt      |   46 +
 src/osgEarthExtensions/mapinspector/CMakeLists.txt |   27 +
 .../mapinspector/MapInspectorExtension             |   84 +
 .../mapinspector/MapInspectorExtension.cpp         |  112 +
 .../mapinspector/MapInspectorPlugin.cpp            |   55 +
 src/osgEarthExtensions/mapinspector/MapInspectorUI |   49 +
 .../mapinspector/MapInspectorUI.cpp                |  162 ++
 src/osgEarthExtensions/noise/CMakeLists.txt        |   25 +
 src/osgEarthExtensions/noise/NoiseExtension        |   73 +
 src/osgEarthExtensions/noise/NoiseExtension.cpp    |   78 +
 src/osgEarthExtensions/noise/NoiseOptions          |   68 +
 src/osgEarthExtensions/noise/NoisePlugin.cpp       |   56 +
 src/osgEarthExtensions/noise/NoiseTerrainEffect    |   62 +
 .../noise/NoiseTerrainEffect.cpp                   |  166 ++
 src/osgEarthExtensions/normalmap/CMakeLists.txt    |   43 +
 .../normalmap/NormalMap.frag.glsl                  |   27 +
 .../normalmap/NormalMap.vert.glsl                  |   25 +
 .../normalmap/NormalMapExtension                   |   73 +
 .../normalmap/NormalMapExtension.cpp               |   80 +
 src/osgEarthExtensions/normalmap/NormalMapOptions  |   72 +
 .../normalmap/NormalMapPlugin.cpp                  |   56 +
 src/osgEarthExtensions/normalmap/NormalMapShaders  |   33 +
 .../normalmap/NormalMapShaders.cpp.in              |   14 +
 .../normalmap/NormalMapTerrainEffect               |   64 +
 .../normalmap/NormalMapTerrainEffect.cpp           |  136 +
 src/osgEarthExtensions/splat/Biome                 |   83 +
 src/osgEarthExtensions/splat/Biome.cpp             |   73 +
 src/osgEarthExtensions/splat/BiomeSelector         |   53 +
 src/osgEarthExtensions/splat/BiomeSelector.cpp     |  143 ++
 src/osgEarthExtensions/splat/CMakeLists.txt        |   60 +
 src/osgEarthExtensions/splat/LandUseTileSource     |  180 ++
 src/osgEarthExtensions/splat/LandUseTileSource.cpp |  283 +++
 src/osgEarthExtensions/splat/ModelSplatter         |   70 +
 src/osgEarthExtensions/splat/ModelSplatter.cpp     |  206 ++
 src/osgEarthExtensions/splat/Splat.Noise.glsl      |  157 ++
 .../splat/Splat.frag.common.glsl                   |   29 +
 .../splat/Splat.frag.getRenderInfo.glsl            |   18 +
 src/osgEarthExtensions/splat/Splat.frag.glsl       |  275 +++
 src/osgEarthExtensions/splat/Splat.types.glsl      |   21 +
 src/osgEarthExtensions/splat/Splat.util.glsl       |   64 +
 src/osgEarthExtensions/splat/Splat.vert.model.glsl |   14 +
 src/osgEarthExtensions/splat/Splat.vert.view.glsl  |   24 +
 src/osgEarthExtensions/splat/SplatCatalog          |  178 ++
 src/osgEarthExtensions/splat/SplatCatalog.cpp      |  389 +++
 src/osgEarthExtensions/splat/SplatCoverageLegend   |  108 +
 .../splat/SplatCoverageLegend.cpp                  |   78 +
 src/osgEarthExtensions/splat/SplatExport           |   63 +
 src/osgEarthExtensions/splat/SplatExtension        |   83 +
 src/osgEarthExtensions/splat/SplatExtension.cpp    |  239 ++
 src/osgEarthExtensions/splat/SplatOptions          |  151 ++
 src/osgEarthExtensions/splat/SplatPlugin.cpp       |   56 +
 src/osgEarthExtensions/splat/SplatShaders          |   46 +
 src/osgEarthExtensions/splat/SplatShaders.cpp.in   |   34 +
 src/osgEarthExtensions/splat/SplatTerrainEffect    |  138 ++
 .../splat/SplatTerrainEffect.cpp                   |  513 ++++
 .../terrainshader/CMakeLists.txt                   |   24 +
 .../terrainshader/TerrainShaderExtension           |   68 +
 .../terrainshader/TerrainShaderExtension.cpp       |  121 +
 .../terrainshader/TerrainShaderOptions             |   87 +
 .../terrainshader/TerrainShaderPlugin.cpp          |   56 +
 src/osgEarthExtensions/viewpoints/CMakeLists.txt   |   24 +
 .../viewpoints/ViewpointsExtension                 |   83 +
 .../viewpoints/ViewpointsExtension.cpp             |  242 ++
 .../viewpoints/ViewpointsPlugin.cpp                |   55 +
 src/osgEarthFeatures/AltitudeFilter                |    2 +-
 src/osgEarthFeatures/AltitudeFilter.cpp            |   25 +-
 src/osgEarthFeatures/BufferFilter                  |    2 +-
 src/osgEarthFeatures/BufferFilter.cpp              |    2 +-
 src/osgEarthFeatures/BuildGeometryFilter           |   10 +-
 src/osgEarthFeatures/BuildGeometryFilter.cpp       |  160 +-
 src/osgEarthFeatures/BuildTextFilter               |    2 +-
 src/osgEarthFeatures/BuildTextFilter.cpp           |    2 +-
 src/osgEarthFeatures/BuildTextOperator             |   13 +-
 src/osgEarthFeatures/BuildTextOperator.cpp         |    2 +-
 src/osgEarthFeatures/CMakeLists.txt                |    1 +
 src/osgEarthFeatures/CentroidFilter                |    2 +-
 src/osgEarthFeatures/CentroidFilter.cpp            |    2 +-
 src/osgEarthFeatures/Common                        |    2 +-
 src/osgEarthFeatures/ConvertTypeFilter             |    2 +-
 src/osgEarthFeatures/ConvertTypeFilter.cpp         |    2 +-
 src/osgEarthFeatures/CropFilter                    |    2 +-
 src/osgEarthFeatures/CropFilter.cpp                |    3 +-
 src/osgEarthFeatures/ExtrudeGeometryFilter         |   40 +-
 src/osgEarthFeatures/ExtrudeGeometryFilter.cpp     |  315 ++-
 src/osgEarthFeatures/Feature                       |   25 +-
 src/osgEarthFeatures/Feature.cpp                   |   11 +-
 src/osgEarthFeatures/FeatureCursor                 |    2 +-
 src/osgEarthFeatures/FeatureCursor.cpp             |   18 +-
 src/osgEarthFeatures/FeatureDisplayLayout          |   17 +-
 src/osgEarthFeatures/FeatureDisplayLayout.cpp      |   12 +-
 src/osgEarthFeatures/FeatureDrawSet                |    2 +-
 src/osgEarthFeatures/FeatureDrawSet.cpp            |    2 +-
 src/osgEarthFeatures/FeatureIndex                  |   54 +
 src/osgEarthFeatures/FeatureListSource             |   15 +-
 src/osgEarthFeatures/FeatureListSource.cpp         |    2 +-
 src/osgEarthFeatures/FeatureModelGraph             |   37 +-
 src/osgEarthFeatures/FeatureModelGraph.cpp         |  394 ++-
 src/osgEarthFeatures/FeatureModelSource            |   13 +-
 src/osgEarthFeatures/FeatureModelSource.cpp        |   21 +-
 src/osgEarthFeatures/FeatureRasterizer             |   49 -
 src/osgEarthFeatures/FeatureSource                 |   23 +-
 src/osgEarthFeatures/FeatureSource.cpp             |    6 +-
 src/osgEarthFeatures/FeatureSourceIndexNode        |  196 +-
 src/osgEarthFeatures/FeatureSourceIndexNode.cpp    |  357 +--
 src/osgEarthFeatures/FeatureTileSource             |   22 +-
 src/osgEarthFeatures/FeatureTileSource.cpp         |   26 +-
 src/osgEarthFeatures/Filter                        |    2 +-
 src/osgEarthFeatures/Filter.cpp                    |   17 +-
 src/osgEarthFeatures/FilterContext                 |   26 +-
 src/osgEarthFeatures/FilterContext.cpp             |   28 +-
 src/osgEarthFeatures/GeometryCompiler              |   12 +-
 src/osgEarthFeatures/GeometryCompiler.cpp          |  153 +-
 src/osgEarthFeatures/GeometryUtils                 |   13 +-
 src/osgEarthFeatures/GeometryUtils.cpp             |   13 +-
 src/osgEarthFeatures/LabelSource                   |   17 +-
 src/osgEarthFeatures/LabelSource.cpp               |    2 +-
 src/osgEarthFeatures/MeshClamper                   |    2 +-
 src/osgEarthFeatures/MeshClamper.cpp               |    2 +-
 src/osgEarthFeatures/OgrUtils                      |   21 +-
 src/osgEarthFeatures/OgrUtils.cpp                  |   30 +-
 src/osgEarthFeatures/OptimizerHints                |    2 +-
 src/osgEarthFeatures/OptimizerHints.cpp            |    2 +-
 src/osgEarthFeatures/PolygonizeLines               |    2 +-
 src/osgEarthFeatures/PolygonizeLines.cpp           |  173 +-
 src/osgEarthFeatures/ResampleFilter                |    2 +-
 src/osgEarthFeatures/ResampleFilter.cpp            |    7 +-
 src/osgEarthFeatures/ScaleFilter                   |    2 +-
 src/osgEarthFeatures/ScaleFilter.cpp               |    2 +-
 src/osgEarthFeatures/ScatterFilter                 |    2 +-
 src/osgEarthFeatures/ScatterFilter.cpp             |    2 +-
 src/osgEarthFeatures/Script                        |    2 +-
 src/osgEarthFeatures/ScriptEngine                  |    2 +-
 src/osgEarthFeatures/ScriptEngine.cpp              |    2 +-
 src/osgEarthFeatures/Session                       |    7 +-
 src/osgEarthFeatures/Session.cpp                   |    4 +-
 src/osgEarthFeatures/StencilVolumeNode             |  103 -
 src/osgEarthFeatures/SubstituteModelFilter         |    3 +-
 src/osgEarthFeatures/SubstituteModelFilter.cpp     |  271 +-
 src/osgEarthFeatures/TessellateOperator            |   35 +-
 src/osgEarthFeatures/TessellateOperator.cpp        |   42 +-
 src/osgEarthFeatures/TextSymbolizer                |    2 +-
 src/osgEarthFeatures/TextSymbolizer.cpp            |    2 +-
 src/osgEarthFeatures/TransformFilter               |    2 +-
 src/osgEarthFeatures/TransformFilter.cpp           |    2 +-
 src/osgEarthFeatures/VirtualFeatureSource          |    2 +-
 src/osgEarthFeatures/VirtualFeatureSource.cpp      |    2 +-
 src/osgEarthQt/Actions                             |    2 +-
 src/osgEarthQt/AnnotationDialogs                   |    8 +-
 src/osgEarthQt/AnnotationDialogs.cpp               |   71 +-
 src/osgEarthQt/AnnotationListWidget                |    2 +-
 src/osgEarthQt/AnnotationListWidget.cpp            |   16 +-
 src/osgEarthQt/AnnotationToolbar                   |    2 +-
 src/osgEarthQt/AnnotationToolbar.cpp               |   13 +-
 src/osgEarthQt/CollapsiblePairWidget               |    2 +-
 src/osgEarthQt/CollapsiblePairWidget.cpp           |   13 +-
 src/osgEarthQt/Common                              |    2 +-
 src/osgEarthQt/DataManager                         |    2 +-
 src/osgEarthQt/DataManager.cpp                     |   13 +-
 src/osgEarthQt/GuiActions                          |    2 +-
 src/osgEarthQt/LOSControlWidget                    |    2 +-
 src/osgEarthQt/LOSControlWidget.cpp                |   16 +-
 src/osgEarthQt/LOSCreationDialog                   |    8 +-
 src/osgEarthQt/LOSCreationDialog.cpp               |   16 +-
 src/osgEarthQt/LayerManagerWidget                  |    2 +-
 src/osgEarthQt/LayerManagerWidget.cpp              |   19 +-
 src/osgEarthQt/MapCatalogWidget                    |    2 +-
 src/osgEarthQt/MapCatalogWidget.cpp                |   29 +-
 src/osgEarthQt/TerrainProfileGraph                 |    2 +-
 src/osgEarthQt/TerrainProfileGraph.cpp             |   13 +-
 src/osgEarthQt/TerrainProfileWidget                |    2 +-
 src/osgEarthQt/TerrainProfileWidget.cpp            |   13 +-
 src/osgEarthQt/ViewWidget                          |   16 +-
 src/osgEarthQt/ViewWidget.cpp                      |   22 +-
 src/osgEarthQt/ViewerWidget                        |    2 +-
 src/osgEarthQt/ViewerWidget.cpp                    |   13 +-
 src/osgEarthSymbology/AGG.h                        |   20 +-
 src/osgEarthSymbology/AltitudeSymbol               |    5 +-
 src/osgEarthSymbology/AltitudeSymbol.cpp           |   14 +-
 src/osgEarthSymbology/CMakeLists.txt               |    4 +
 src/osgEarthSymbology/Color                        |    2 +-
 src/osgEarthSymbology/Color.cpp                    |    2 +-
 src/osgEarthSymbology/Common                       |    2 +-
 src/osgEarthSymbology/CoverageSymbol               |   61 +
 src/osgEarthSymbology/CoverageSymbol.cpp           |   63 +
 src/osgEarthSymbology/CssUtils                     |    2 +-
 src/osgEarthSymbology/CssUtils.cpp                 |    2 +-
 src/osgEarthSymbology/Expression                   |    2 +-
 src/osgEarthSymbology/Expression.cpp               |   13 +-
 src/osgEarthSymbology/ExtrusionSymbol              |    5 +-
 src/osgEarthSymbology/ExtrusionSymbol.cpp          |   14 +-
 src/osgEarthSymbology/FeatureDataSet               |   54 -
 src/osgEarthSymbology/FeatureDataSetAdapter        |   58 -
 src/osgEarthSymbology/Fill                         |    2 +-
 src/osgEarthSymbology/Fill.cpp                     |    2 +-
 src/osgEarthSymbology/GEOS                         |    2 +-
 src/osgEarthSymbology/GEOS.cpp                     |   24 +-
 src/osgEarthSymbology/Geometry                     |   32 +-
 src/osgEarthSymbology/Geometry.cpp                 |  167 +-
 src/osgEarthSymbology/GeometryExtrudeSymbolizer    |   52 -
 src/osgEarthSymbology/GeometryFactory              |    2 +-
 src/osgEarthSymbology/GeometryFactory.cpp          |    2 +-
 src/osgEarthSymbology/GeometryInput                |   44 -
 src/osgEarthSymbology/GeometryRasterizer           |    2 +-
 src/osgEarthSymbology/GeometryRasterizer.cpp       |    4 +-
 src/osgEarthSymbology/GeometrySymbol               |  190 --
 src/osgEarthSymbology/IconResource                 |    2 +-
 src/osgEarthSymbology/IconResource.cpp             |    4 +-
 src/osgEarthSymbology/IconSymbol                   |    6 +-
 src/osgEarthSymbology/IconSymbol.cpp               |   15 +-
 src/osgEarthSymbology/InstanceResource             |    2 +-
 src/osgEarthSymbology/InstanceResource.cpp         |    2 +-
 src/osgEarthSymbology/InstanceSymbol               |   11 +-
 src/osgEarthSymbology/InstanceSymbol.cpp           |   19 +-
 src/osgEarthSymbology/LineSymbol                   |   12 +-
 src/osgEarthSymbology/LineSymbol.cpp               |   21 +-
 src/osgEarthSymbology/MarkerResource               |    2 +-
 src/osgEarthSymbology/MarkerResource.cpp           |    2 +-
 src/osgEarthSymbology/MarkerSymbol                 |   12 +-
 src/osgEarthSymbology/MarkerSymbol.cpp             |   28 +-
 src/osgEarthSymbology/MarkerSymbolizer             |   47 -
 src/osgEarthSymbology/MeshConsolidator             |    2 +-
 src/osgEarthSymbology/MeshConsolidator.cpp         |  183 +-
 src/osgEarthSymbology/MeshFlattener                |   91 +
 src/osgEarthSymbology/MeshFlattener.cpp            |  204 ++
 src/osgEarthSymbology/MeshSubdivider               |    2 +-
 src/osgEarthSymbology/MeshSubdivider.cpp           |    2 +-
 src/osgEarthSymbology/ModelResource                |    2 +-
 src/osgEarthSymbology/ModelResource.cpp            |   10 +-
 src/osgEarthSymbology/ModelSymbol                  |    5 +-
 src/osgEarthSymbology/ModelSymbol.cpp              |   14 +-
 src/osgEarthSymbology/ModelSymbolizer              |   44 -
 src/osgEarthSymbology/PointSymbol                  |    5 +-
 src/osgEarthSymbology/PointSymbol.cpp              |    9 +-
 src/osgEarthSymbology/PolygonSymbol                |    5 +-
 src/osgEarthSymbology/PolygonSymbol.cpp            |    8 +-
 src/osgEarthSymbology/Query                        |    8 +-
 src/osgEarthSymbology/Query.cpp                    |   45 +-
 src/osgEarthSymbology/RenderSymbol                 |   10 +-
 src/osgEarthSymbology/RenderSymbol.cpp             |   20 +-
 src/osgEarthSymbology/Resource                     |   12 +-
 src/osgEarthSymbology/Resource.cpp                 |    2 +-
 src/osgEarthSymbology/ResourceCache                |    2 +-
 src/osgEarthSymbology/ResourceCache.cpp            |    9 +-
 src/osgEarthSymbology/ResourceLibrary              |    2 +-
 src/osgEarthSymbology/ResourceLibrary.cpp          |    2 +-
 src/osgEarthSymbology/Skins                        |   13 +-
 src/osgEarthSymbology/Skins.cpp                    |   20 +-
 src/osgEarthSymbology/StencilVolumeNode            |    2 +-
 src/osgEarthSymbology/StencilVolumeNode.cpp        |    2 +-
 src/osgEarthSymbology/Stroke                       |    2 +-
 src/osgEarthSymbology/Stroke.cpp                   |    2 +-
 src/osgEarthSymbology/Style                        |    5 +-
 src/osgEarthSymbology/Style.cpp                    |   42 +-
 src/osgEarthSymbology/StyleSelector                |    2 +-
 src/osgEarthSymbology/StyleSelector.cpp            |   13 +-
 src/osgEarthSymbology/StyleSheet                   |    2 +-
 src/osgEarthSymbology/StyleSheet.cpp               |   13 +-
 src/osgEarthSymbology/Symbol                       |   19 +-
 src/osgEarthSymbology/Symbol.cpp                   |    9 +-
 src/osgEarthSymbology/Tags                         |   13 +-
 src/osgEarthSymbology/TextSymbol                   |    5 +-
 src/osgEarthSymbology/TextSymbol.cpp               |   34 +-
 src/osgEarthUtil/ActivityMonitorTool               |    2 +-
 src/osgEarthUtil/ActivityMonitorTool.cpp           |    2 +-
 src/osgEarthUtil/AnnotationEvents                  |   22 +-
 src/osgEarthUtil/AnnotationEvents.cpp              |   29 +-
 src/osgEarthUtil/ArcGIS                            |   13 +-
 src/osgEarthUtil/ArcGIS.cpp                        |    2 +-
 src/osgEarthUtil/AtlasBuilder                      |   20 +-
 src/osgEarthUtil/AtlasBuilder.cpp                  |    7 +-
 src/osgEarthUtil/AutoClipPlaneHandler              |    6 +-
 src/osgEarthUtil/AutoClipPlaneHandler.cpp          |    2 +-
 src/osgEarthUtil/BrightnessContrastColorFilter     |   13 +-
 src/osgEarthUtil/BrightnessContrastColorFilter.cpp |   13 +-
 src/osgEarthUtil/CMYKColorFilter                   |   13 +-
 src/osgEarthUtil/CMYKColorFilter.cpp               |   13 +-
 src/osgEarthUtil/CMakeLists.txt                    |   52 +-
 src/osgEarthUtil/ChromaKeyColorFilter              |    2 +-
 src/osgEarthUtil/ChromaKeyColorFilter.cpp          |    2 +-
 src/osgEarthUtil/ClampCallback                     |   13 +-
 src/osgEarthUtil/ClampCallback.cpp                 |   13 +-
 src/osgEarthUtil/Common                            |    2 +-
 src/osgEarthUtil/ContourMap                        |   11 +-
 src/osgEarthUtil/ContourMap.cpp                    |   60 +-
 src/osgEarthUtil/ContourMap.frag.glsl              |   16 +
 src/osgEarthUtil/ContourMap.vert.glsl              |   18 +
 src/osgEarthUtil/Controls                          |    9 +-
 src/osgEarthUtil/Controls.cpp                      |   86 +-
 src/osgEarthUtil/DataScanner                       |   13 +-
 src/osgEarthUtil/DataScanner.cpp                   |   13 +-
 src/osgEarthUtil/DateTime                          |   34 -
 src/osgEarthUtil/DateTime.cpp                      |   85 -
 src/osgEarthUtil/DetailTexture                     |  114 -
 src/osgEarthUtil/DetailTexture.cpp                 |  478 ----
 src/osgEarthUtil/EarthManipulator                  |  200 +-
 src/osgEarthUtil/EarthManipulator.cpp              | 1735 +++++++------
 src/osgEarthUtil/Ephemeris                         |   13 +-
 src/osgEarthUtil/Ephemeris.cpp                     |   13 +-
 src/osgEarthUtil/ExampleResources                  |   26 +-
 src/osgEarthUtil/ExampleResources.cpp              |  350 ++-
 src/osgEarthUtil/Export                            |    2 +-
 src/osgEarthUtil/FeatureQueryTool                  |  136 +-
 src/osgEarthUtil/FeatureQueryTool.cpp              |  304 +--
 src/osgEarthUtil/Fog                               |   11 +-
 src/osgEarthUtil/Fog.cpp                           |   51 +-
 src/osgEarthUtil/Fog.frag.glsl                     |   13 +
 src/osgEarthUtil/Fog.vert.glsl                     |   16 +
 src/osgEarthUtil/Formatter                         |    2 +-
 src/osgEarthUtil/GLSLColorFilter                   |   13 +-
 src/osgEarthUtil/GLSLColorFilter.cpp               |   13 +-
 src/osgEarthUtil/GammaColorFilter                  |   13 +-
 src/osgEarthUtil/GammaColorFilter.cpp              |   13 +-
 src/osgEarthUtil/GeodeticGraticule                 |    2 +-
 src/osgEarthUtil/GeodeticGraticule.cpp             |    6 +-
 src/osgEarthUtil/Graticule.frag.glsl               |   31 +
 src/osgEarthUtil/Graticule.vert.glsl               |   16 +
 src/osgEarthUtil/GraticuleExtension                |   74 +
 src/osgEarthUtil/GraticuleExtension.cpp            |  109 +
 src/osgEarthUtil/GraticuleNode                     |  117 +
 src/osgEarthUtil/GraticuleNode.cpp                 |  417 ++++
 src/osgEarthUtil/GraticuleOptions                  |  114 +
 src/osgEarthUtil/GraticuleTerrainEffect            |   61 +
 src/osgEarthUtil/GraticuleTerrainEffect.cpp        |   87 +
 src/osgEarthUtil/HSLColorFilter                    |    2 +-
 src/osgEarthUtil/HSLColorFilter.cpp                |   13 +-
 src/osgEarthUtil/HTM                               |   13 +-
 src/osgEarthUtil/HTM.cpp                           |   13 +-
 src/osgEarthUtil/LODBlending                       |   11 +-
 src/osgEarthUtil/LODBlending.cpp                   |   28 +-
 src/osgEarthUtil/LatLongFormatter                  |   10 +-
 src/osgEarthUtil/LatLongFormatter.cpp              |  122 +-
 src/osgEarthUtil/LineOfSight                       |    2 +-
 src/osgEarthUtil/LinearLineOfSight                 |   19 +-
 src/osgEarthUtil/LinearLineOfSight.cpp             |   25 +-
 src/osgEarthUtil/LogDepthBuffer.VertOnly.vert.glsl |   15 +
 src/osgEarthUtil/LogDepthBuffer.frag.glsl          |   16 +
 src/osgEarthUtil/LogDepthBuffer.vert.glsl          |   17 +
 src/osgEarthUtil/LogarithmicDepthBuffer            |   26 +-
 src/osgEarthUtil/LogarithmicDepthBuffer.cpp        |  178 +-
 src/osgEarthUtil/MGRSFormatter                     |    2 +-
 src/osgEarthUtil/MGRSFormatter.cpp                 |    2 +-
 src/osgEarthUtil/MGRSGraticule                     |    2 +-
 src/osgEarthUtil/MGRSGraticule.cpp                 |    2 +-
 src/osgEarthUtil/MeasureTool                       |    2 +-
 src/osgEarthUtil/MeasureTool.cpp                   |    3 +-
 src/osgEarthUtil/MouseCoordsTool                   |    2 +-
 src/osgEarthUtil/MouseCoordsTool.cpp               |    2 +-
 src/osgEarthUtil/NearFarGroup                      |   52 -
 src/osgEarthUtil/NightColorFilter                  |   55 +
 src/osgEarthUtil/NightColorFilter.cpp              |  114 +
 src/osgEarthUtil/NormalMap                         |   78 -
 src/osgEarthUtil/NormalMap.cpp                     |  209 --
 src/osgEarthUtil/ObjectLocator                     |   20 +-
 src/osgEarthUtil/Ocean                             |   13 +-
 src/osgEarthUtil/Ocean.cpp                         |   17 +-
 src/osgEarthUtil/PolyhedralLineOfSight             |   13 +-
 src/osgEarthUtil/PolyhedralLineOfSight.cpp         |   13 +-
 src/osgEarthUtil/RGBColorFilter                    |   13 +-
 src/osgEarthUtil/RGBColorFilter.cpp                |   13 +-
 src/osgEarthUtil/RTTPicker                         |  144 ++
 src/osgEarthUtil/RTTPicker.cpp                     |  409 +++
 src/osgEarthUtil/RadialLineOfSight                 |   17 +-
 src/osgEarthUtil/RadialLineOfSight.cpp             |   23 +-
 src/osgEarthUtil/Shaders                           |   50 +
 src/osgEarthUtil/Shaders.cpp.in                    |   36 +
 src/osgEarthUtil/Shadowing                         |   13 +-
 src/osgEarthUtil/Shadowing.cpp                     |   19 +-
 src/osgEarthUtil/SimplexNoise                      |   29 +-
 src/osgEarthUtil/SimplexNoise.cpp                  |   92 +-
 src/osgEarthUtil/Sky                               |   19 +-
 src/osgEarthUtil/Sky.cpp                           |   25 +-
 src/osgEarthUtil/SpatialData                       |    2 +-
 src/osgEarthUtil/SpatialData.cpp                   |    2 +-
 src/osgEarthUtil/StarData                          |   13 +-
 src/osgEarthUtil/TFS                               |   13 +-
 src/osgEarthUtil/TFS.cpp                           |    2 +-
 src/osgEarthUtil/TFSPackager                       |    2 +-
 src/osgEarthUtil/TFSPackager.cpp                   |   13 +-
 src/osgEarthUtil/TMS                               |   13 +-
 src/osgEarthUtil/TMS.cpp                           |    3 +-
 src/osgEarthUtil/TMSBackFiller                     |   13 +-
 src/osgEarthUtil/TMSBackFiller.cpp                 |   13 +-
 src/osgEarthUtil/TMSPackager                       |   25 +-
 src/osgEarthUtil/TMSPackager.cpp                   |   98 +-
 src/osgEarthUtil/TerrainProfile                    |   13 +-
 src/osgEarthUtil/TerrainProfile.cpp                |   13 +-
 src/osgEarthUtil/TextureSplatter                   |  121 -
 src/osgEarthUtil/TextureSplatter.cpp               |  362 ---
 src/osgEarthUtil/TileIndex                         |   13 +-
 src/osgEarthUtil/TileIndex.cpp                     |   13 +-
 src/osgEarthUtil/TileIndexBuilder                  |   13 +-
 src/osgEarthUtil/TileIndexBuilder.cpp              |   13 +-
 src/osgEarthUtil/UTMGraticule                      |    2 +-
 src/osgEarthUtil/UTMGraticule.cpp                  |    6 +-
 src/osgEarthUtil/VerticalScale                     |   11 +-
 src/osgEarthUtil/VerticalScale.cpp                 |   13 +-
 src/osgEarthUtil/WFS                               |   13 +-
 src/osgEarthUtil/WFS.cpp                           |    2 +-
 src/osgEarthUtil/WMS                               |   13 +-
 src/osgEarthUtil/WMS.cpp                           |    2 +-
 tests/annotation.earth                             |    4 +-
 tests/annotation_flat.earth                        |  129 +-
 tests/arcgisonline.earth                           |    4 +-
 tests/billboard.earth                              |   39 +
 tests/boston-gpu.earth                             |  172 ++
 tests/boston.earth                                 |    5 +-
 tests/boston_buildings.earth                       |   10 +-
 tests/boston_projected.earth                       |    2 +-
 tests/detail_texture.earth                         |   35 -
 tests/feature_clip_plane.earth                     |   31 +
 tests/feature_draped_lines.earth                   |    2 -
 tests/feature_gpx.earth                            |    4 +-
 tests/feature_labels.earth                         |   30 +-
 tests/feature_model_scatter.earth                  |   13 +-
 tests/feature_models.earth                         |    3 +-
 tests/feature_offset_polygons.earth                |  112 +
 tests/feature_overlay.earth                        |   12 +-
 tests/feature_population_cylinders.earth           |   61 +-
 tests/feature_tfs.earth                            |    4 +-
 tests/feature_tfs_scripting.earth                  |    4 +-
 tests/fractal_detail.earth                         |   58 -
 tests/glsl.earth                                   |   28 +
 tests/graticule.earth                              |   41 +
 tests/ldb.earth                                    |   28 +
 tests/min_max_range.earth                          |    8 +-
 tests/night.earth                                  |   27 +
 tests/nodata.earth                                 |    6 +
 tests/noise.earth                                  |   81 +-
 tests/normalmap.earth                              |   51 +-
 tests/ocean.earth                                  |   30 +-
 tests/openweathermap_clouds.earth                  |   30 +
 tests/openweathermap_precipitation.earth           |   31 +
 tests/openweathermap_pressure.earth                |   31 +
 tests/readymap-osm.earth                           |   17 +-
 tests/readymap.earth                               |    4 +-
 tests/silverlining.earth                           |   15 +-
 tests/simple_model.earth                           |    4 +-
 tests/splat-edit.bat                               |   21 +
 tests/splat-gpunoise.bat                           |   31 +
 tests/splat-test.earth                             |   57 +
 tests/splat-with-imagery.earth                     |   52 +
 tests/splat.bat                                    |    7 +
 tests/splat.earth                                  |  118 -
 tests/triton.earth                                 |   21 +-
 tests/wms_jpl_landsat.earth                        |   18 -
 1256 files changed, 42368 insertions(+), 15681 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index fcde579..38ab336 100755
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -24,7 +24,7 @@ SET_PROPERTY( GLOBAL PROPERTY PREDEFINED_TARGETS_FOLDER "CMake Targets" )
 PROJECT(OSGEARTH)
 
 SET(OSGEARTH_MAJOR_VERSION 2)
-SET(OSGEARTH_MINOR_VERSION 5)
+SET(OSGEARTH_MINOR_VERSION 6)
 SET(OSGEARTH_PATCH_VERSION 0)
 SET(OSGEARTH_SOVERSION     0)
 
@@ -126,6 +126,7 @@ FIND_PACKAGE(LevelDB)
 
 FIND_PACKAGE(SilverLining)
 FIND_PACKAGE(Triton)
+FIND_PACKAGE(Protobuf)
 
 # JavaScript Engines:
 SET(V8_DIR "" CACHE PATH "set to base V8 install path")
@@ -137,6 +138,8 @@ 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)
@@ -154,6 +157,9 @@ ENDIF ()
 
 OPTION(OSGEARTH_USE_QT "Enable to use Qt (build Qt-dependent libraries, plugins and examples)" ON)
 
+# option to install shaders:
+OPTION(OSGEARTH_INSTALL_SHADERS "Whether to deploy GLSL shaders when doing a Make INSTALL" OFF)
+
 SET (WITH_EXTERNAL_TINYXML FALSE CACHE BOOL "Use bundled or system wide version of TinyXML")
 IF (WITH_EXTERNAL_TINYXML)
     FIND_PACKAGE(TinyXML)
diff --git a/CMakeModules/ConfigureShaders.cmake.in b/CMakeModules/ConfigureShaders.cmake.in
new file mode 100644
index 0000000..6336562
--- /dev/null
+++ b/CMakeModules/ConfigureShaders.cmake.in
@@ -0,0 +1,35 @@
+# configureshaders.cmake.in
+
+set(source_dir      "@CMAKE_CURRENT_SOURCE_DIR@")
+set(bin_dir         "@CMAKE_CURRENT_BINARY_DIR@")
+set(glsl_files      "@GLSL_FILES@")
+set(template_file   "@TEMPLATE_FILE@")
+set(output_cpp_file "@OUTPUT_CPP_FILE@")
+
+# modify the contents for inlining; replace input with output (var: file)
+# i.e., the file name (in the form @my_file_name@) gets replaced with the
+# actual contents of the named file and then processed a bit.
+foreach(file ${glsl_files})
+
+    # read the file into 'contents':
+    file(READ ${source_dir}/${file} contents)
+
+    # append a newline so we do not break the MULTILINE macro when the last line
+    # of the file is a comment:
+    set(contents "${contents}\n")
+
+    # replace hashtags with a marker string to avoid breaking the MULTILINE macro:
+	string(REGEX REPLACE "#" "$__HASHTAG__" tempString "${contents}")
+
+    # replace newlines with printable newlines, and overwrite the original
+    # file name with the processed contents.
+	string(REGEX REPLACE "\n" "\\\\n\n" ${file} "${tempString}")
+
+endforeach(file)
+
+# send the processed glsl_files to the next template to create the
+# shader source file.
+configure_file(
+	${source_dir}/${template_file}
+	${output_cpp_file}
+	@ONLY )
diff --git a/CMakeModules/FindLevelDB.cmake b/CMakeModules/FindLevelDB.cmake
index 0014325..15e3db2 100644
--- a/CMakeModules/FindLevelDB.cmake
+++ b/CMakeModules/FindLevelDB.cmake
@@ -5,6 +5,7 @@
 # LEVELDB_INCLUDE_DIR, where to find the headers
 
 FIND_PATH(LEVELDB_INCLUDE_DIR leveldb/db.h
+  PATHS
   $ENV{LEVELDB_DIR}
   NO_DEFAULT_PATH
     PATH_SUFFIXES include
diff --git a/CMakeModules/ModuleInstall.cmake b/CMakeModules/ModuleInstall.cmake
index efe90b2..0c510b4 100644
--- a/CMakeModules/ModuleInstall.cmake
+++ b/CMakeModules/ModuleInstall.cmake
@@ -1,5 +1,3 @@
-# INSTALL and SOURCE_GROUP commands for OSG/OT/Producer Modules
-
 # Required Vars:
 # ${LIB_NAME}
 # ${LIB_PUBLIC_HEADERS}
@@ -23,8 +21,11 @@ IF(NOT USE_CUSTOM_SOURCE_GROUPS)
         ${HEADERS_GROUP}
         FILES ${LIB_PUBLIC_HEADERS}
     )
+		
 ENDIF()
 
+source_group("Shaders"        FILES ${TARGET_GLSL} )
+source_group("Template Files" FILES ${TARGET_IN} )
 
 INSTALL(
     TARGETS ${LIB_NAME}
@@ -33,6 +34,14 @@ INSTALL(
     ARCHIVE DESTINATION ${INSTALL_ARCHIVEDIR}
 )
 
+# deploy the shaders for this library, if requested.
+if(OSGEARTH_INSTALL_SHADERS)
+	install(
+		FILES ${TARGET_GLSL}
+		DESTINATION resources/shaders )
+endif(OSGEARTH_INSTALL_SHADERS)
+
+
 IF(NOT OSGEARTH_BUILD_FRAMEWORKS)
     INSTALL(
         FILES        ${LIB_PUBLIC_HEADERS}
diff --git a/CMakeModules/ModuleInstallOsgEarthDriverIncludes.cmake b/CMakeModules/ModuleInstallOsgEarthDriverIncludes.cmake
index 403f850..f814a86 100644
--- a/CMakeModules/ModuleInstallOsgEarthDriverIncludes.cmake
+++ b/CMakeModules/ModuleInstallOsgEarthDriverIncludes.cmake
@@ -6,6 +6,6 @@ SET(INSTALL_INCDIR include)
 
 # FIXME: Do not run for OS X framework
 INSTALL(
-    FILES        ${LIB_PUBLIC_HEADERS}
+    FILES       ${LIB_PUBLIC_HEADERS}
     DESTINATION ${INSTALL_INCDIR}/osgEarthDrivers/${LIB_NAME}
 )
diff --git a/CMakeModules/OsgEarthMacroUtils.cmake b/CMakeModules/OsgEarthMacroUtils.cmake
index 4d8bb70..492e916 100644
--- a/CMakeModules/OsgEarthMacroUtils.cmake
+++ b/CMakeModules/OsgEarthMacroUtils.cmake
@@ -6,7 +6,11 @@ MACRO(DETECT_OSG_VERSION)
     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)
+    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
@@ -88,7 +92,11 @@ ENDMACRO(DETECT_OSG_VERSION)
 MACRO(LINK_WITH_VARIABLES TRGTNAME)
     FOREACH(varname ${ARGN})
         IF(${varname}_DEBUG)
-            TARGET_LINK_LIBRARIES(${TRGTNAME} optimized "${${varname}}" debug "${${varname}_DEBUG}")
+            IF(${varname})
+                TARGET_LINK_LIBRARIES(${TRGTNAME} optimized "${${varname}}" debug "${${varname}_DEBUG}")
+            ELSE(${varname})
+                TARGET_LINK_LIBRARIES(${TRGTNAME} debug "${${varname}_DEBUG}")
+            ENDIF(${varname})
         ELSE(${varname}_DEBUG)
             TARGET_LINK_LIBRARIES(${TRGTNAME} "${${varname}}" )
         ENDIF(${varname}_DEBUG)
@@ -193,7 +201,9 @@ MACRO(SETUP_PLUGIN PLUGIN_NAME)
 
     #MESSAGE("in -->SETUP_PLUGIN<-- ${TARGET_NAME}-->${TARGET_SRC} <--> ${TARGET_H}<--")
     
-    SOURCE_GROUP( "Header Files" FILES ${TARGET_H} )
+    SOURCE_GROUP( "Header Files"   FILES ${TARGET_H} )
+    SOURCE_GROUP( "Shader Files"   FILES ${TARGET_GLSL} )
+    SOURCe_GROUP( "Template Files" FILES ${TARGET_IN} )
 
     ## we have set up the target label and targetname by taking into account global prfix (osgdb_)
 
@@ -207,9 +217,9 @@ MACRO(SETUP_PLUGIN PLUGIN_NAME)
 # here we use the command to generate the library
 
     IF   (DYNAMIC_OSGEARTH)
-        ADD_LIBRARY(${TARGET_TARGETNAME} MODULE ${TARGET_SRC} ${TARGET_H})
+        ADD_LIBRARY(${TARGET_TARGETNAME} MODULE ${TARGET_SRC} ${TARGET_H} ${TARGET_GLSL} ${TARGET_IN})
     ELSE (DYNAMIC_OSGEARTH)
-        ADD_LIBRARY(${TARGET_TARGETNAME} STATIC ${TARGET_SRC} ${TARGET_H})
+        ADD_LIBRARY(${TARGET_TARGETNAME} STATIC ${TARGET_SRC} ${TARGET_H} ${TARGET_GLSL} ${TARGET_IN})
     ENDIF(DYNAMIC_OSGEARTH)
 
     #not sure if needed, but for plugins only msvc need the d suffix
@@ -240,6 +250,13 @@ MACRO(SETUP_PLUGIN PLUGIN_NAME)
 		ENDIF(OSGEARTH_INSTALL_TO_OSG_DIR AND OSG_DIR)
 		
     ENDIF(WIN32)
+
+    # install the shader source files
+    if(OSGEARTH_INSTALL_SHADERS)
+        INSTALL(
+            FILES ${TARGET_GLSL} 
+            DESTINATION resources/shaders )
+    endif(OSGEARTH_INSTALL_SHADERS)
     
 #finally, set up the solution folder -gw
     SET_PROPERTY(TARGET ${TARGET_TARGETNAME} PROPERTY FOLDER "Plugins")    
@@ -247,6 +264,95 @@ MACRO(SETUP_PLUGIN PLUGIN_NAME)
 ENDMACRO(SETUP_PLUGIN)
 
 
+
+
+
+MACRO(SETUP_EXTENSION PLUGIN_NAME)
+
+    SET(TARGET_NAME ${PLUGIN_NAME} )
+
+    #MESSAGE("in -->SETUP_EXTENSION<-- ${TARGET_NAME}-->${TARGET_SRC} <--> ${TARGET_H}<--")
+    
+    SOURCE_GROUP( "Header Files"   FILES ${TARGET_H} )
+    SOURCE_GROUP( "Shader Files"   FILES ${TARGET_GLSL} )
+    SOURCe_GROUP( "Template Files" FILES ${TARGET_IN} )
+
+    ## we have set up the target label and targetname by taking into account global prefix (osgdb_)
+
+    IF(NOT TARGET_TARGETNAME)
+            SET(TARGET_TARGETNAME "${TARGET_DEFAULT_PREFIX}${TARGET_NAME}")
+    ENDIF(NOT TARGET_TARGETNAME)
+    IF(NOT TARGET_LABEL)
+            SET(TARGET_LABEL "${TARGET_DEFAULT_LABEL_PREFIX} ${TARGET_NAME}")
+    ENDIF(NOT TARGET_LABEL)
+
+# here we use the command to generate the library
+
+    IF   (DYNAMIC_OSGEARTH)
+        ADD_LIBRARY(${TARGET_TARGETNAME} MODULE ${TARGET_SRC} ${TARGET_H} ${TARGET_GLSL} ${TARGET_IN})
+    ELSE (DYNAMIC_OSGEARTH)
+        ADD_LIBRARY(${TARGET_TARGETNAME} STATIC ${TARGET_SRC} ${TARGET_H} ${TARGET_GLSL} ${TARGET_IN})
+    ENDIF(DYNAMIC_OSGEARTH)
+
+    #not sure if needed, but for plugins only msvc need the d suffix
+    IF(NOT MSVC)
+      IF(NOT UNIX)
+           SET_TARGET_PROPERTIES(${TARGET_TARGETNAME} PROPERTIES DEBUG_POSTFIX "")
+      ENDIF(NOT UNIX)
+    ENDIF(NOT MSVC)
+    SET_TARGET_PROPERTIES(${TARGET_TARGETNAME} PROPERTIES PROJECT_LABEL "${TARGET_LABEL}")
+
+    SETUP_LINK_LIBRARIES()
+
+#the installation path are differentiated for win32 that install in bib versus other architecture that install in lib${LIB_POSTFIX}/${VPB_PLUGINS}
+    IF(WIN32)
+        INSTALL(
+            TARGETS ${TARGET_TARGETNAME}
+            RUNTIME DESTINATION bin
+            ARCHIVE DESTINATION lib/${OSG_PLUGINS}
+            LIBRARY DESTINATION bin/${OSG_PLUGINS} )
+	    
+		#Install to the OSG_DIR as well
+		IF(OSGEARTH_INSTALL_TO_OSG_DIR AND OSG_DIR)
+		    INSTALL(
+                TARGETS ${TARGET_TARGETNAME} 
+                RUNTIME DESTINATION ${OSG_DIR}/bin/${OSG_PLUGINS}
+                LIBRARY DESTINATION ${OSG_DIR}/bin/${OSG_PLUGINS} )
+		ENDIF(OSGEARTH_INSTALL_TO_OSG_DIR AND OSG_DIR)
+		
+    ELSE(WIN32)
+        INSTALL(
+            TARGETS ${TARGET_TARGETNAME} 
+            RUNTIME DESTINATION bin 
+            ARCHIVE DESTINATION lib${LIB_POSTFIX}/${OSG_PLUGINS} 
+            LIBRARY DESTINATION lib${LIB_POSTFIX}/${OSG_PLUGINS} )
+
+		#Install to the OSG_DIR as well
+		IF(OSGEARTH_INSTALL_TO_OSG_DIR AND OSG_DIR)
+		    INSTALL(
+                TARGETS ${TARGET_TARGETNAME}
+                RUNTIME DESTINATION ${OSG_DIR}/bin
+                LIBRARY DESTINATION lib${LIB_POSTFIX}/bin )
+		ENDIF(OSGEARTH_INSTALL_TO_OSG_DIR AND OSG_DIR)
+		
+    ENDIF(WIN32)
+    
+    # install the shader source files
+    if(OSGEARTH_INSTALL_SHADERS)
+        INSTALL(
+            FILES ${TARGET_GLSL} 
+            DESTINATION resources/shaders )
+    endif(OSGEARTH_INSTALL_SHADERS)
+    
+#finally, set up the solution folder -gw
+    SET_PROPERTY(TARGET ${TARGET_TARGETNAME} PROPERTY FOLDER "Extensions")    
+    
+ENDMACRO(SETUP_EXTENSION)
+
+
+
+
+
 #################################################################################################################
 # this is the macro for example and application setup
 ###########################################################
@@ -372,3 +478,50 @@ MACRO(SETUP_COMMANDLINE_EXAMPLE EXAMPLE_NAME)
     SETUP_EXAMPLE(${EXAMPLE_NAME} 1)
 
 ENDMACRO(SETUP_COMMANDLINE_EXAMPLE)
+
+
+# -----------------------------------------------------------------------
+# configure_shaders -gw
+#
+# Bakes GLSL shaders to make into a CPP file at runtime.
+# Example:
+#
+#   configure_shaders( MyTemplate.cpp.in ${CMAKE_CURRENT_BINARY_DIR}/AutoGen.cpp file1.glsl file2.glsl )
+#
+macro(configure_shaders templateFile autoGenCppFile)
+	
+	# set up configure variables:
+	set(TEMPLATE_FILE   ${templateFile} )
+	set(GLSL_FILES      ${ARGN} )
+	set(OUTPUT_CPP_FILE ${autoGenCppFile})
+	
+	# generate the build-time script that will create out cpp file with inline shaders:
+	configure_file(
+		"${CMAKE_SOURCE_DIR}/CMakeModules/ConfigureShaders.cmake.in"
+		"${CMAKE_CURRENT_BINARY_DIR}/ConfigureShaders.cmake"
+		@ONLY)
+	
+	# add the custom build-time command to run the script:
+	add_custom_command(
+		OUTPUT
+			"${autoGenCppFile}"
+		COMMAND
+			"${CMAKE_COMMAND}" -P "${CMAKE_CURRENT_BINARY_DIR}/ConfigureShaders.cmake"
+		DEPENDS
+			${GLSL_FILES}
+			"${TEMPLATE_FILE}"
+			"${CMAKE_SOURCE_DIR}/CMakeModules/ConfigureShaders.cmake.in" )
+	
+endmacro(configure_shaders)
+
+# http://stackoverflow.com/questions/7787823/cmake-how-to-get-the-name-of-all-subdirectories-of-a-directory
+MACRO(SUBDIRLIST result curdir)
+  FILE(GLOB children RELATIVE ${curdir} ${curdir}/*)
+  SET(dirlist "")
+  FOREACH(child ${children})
+    IF(IS_DIRECTORY ${curdir}/${child})
+        LIST(APPEND dirlist ${child})
+    ENDIF()
+  ENDFOREACH()
+  SET(${result} ${dirlist})
+ENDMACRO()
diff --git a/README.txt b/README.txt
index 601627a..a4b0171 100644
--- a/README.txt
+++ b/README.txt
@@ -1,18 +1,20 @@
 osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-Copyright 2008-2014 Pelican Mapping
+Copyright 2015 Pelican Mapping
 
 http://osgearth.org
-git://github.com/gwaldron/osgearth
 
 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.
+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/>
diff --git a/docs/source/data.rst b/docs/source/data.rst
index abe3d1a..3230ad2 100644
--- a/docs/source/data.rst
+++ b/docs/source/data.rst
@@ -142,4 +142,4 @@ Tips for Preparing your own Data
         --overwrite                         Force overwriting of existing files
         --keep-empties                      Writes fully transparent image tiles (normally discarded)
         --db-options                        An optional OSG options string
-        --quiet                             Suppress progress reporting
+        --verbose                           Displays progress of the operation
diff --git a/docs/source/developer/shader_composition.rst b/docs/source/developer/shader_composition.rst
index 02c2005..b6a0822 100644
--- a/docs/source/developer/shader_composition.rst
+++ b/docs/source/developer/shader_composition.rst
@@ -1,27 +1,43 @@
 Shader Composition
 ==================
 
-osgEarth uses GLSL shaders in several of its rendering modes. By default,
+osgEarth uses GLSL shaders in several of its rendering modes. By default
 osgEarth will detect the capabilities of your graphics hardware and
 automatically select an appropriate mode to use.
 
-Since osgEarth relies on shaders, and since you as the developer may wish
-to use your own shader code as well, osgEarth provides a *shader composition*
-framework. This allows you a great deal of flexibility when incorporating
-your own shaders into osgEarth.
+Since osgEarth relies on shaders, you as a developer may wish to customize
+the rendering or add your own effects and features in GLSL. Anyone who has
+wokred with shaders has run into the same challenges:
+
+* Shader programs as monolithic. Adding new shader code requires you to
+  copy, modify, and replace the existing code so you don't lose its
+  functionality.
+* Keeping your changes in sync with changes to the original code's 
+  shaders is a maintenance nightmare.
+* Maintaining multiple versions of shader main()s is cumbersome and
+  difficult.
+* Maintaining the dreaded "uber shader" becomes unmanagable as the 
+  GLSL code base grows in complexity and you add more features.
+  
+*Shader Composition* solves these problems by *modularizing* the shader
+pipeline. You can add and remove *functions* at any point in the program
+without and copying, pasting, or hacking other people's GLSL code.
+
+Next we will discuss the structure of osgEarth's shader composition framework.
 
-There are several ways to integrate your own shader code into osgEarth.
-We discuss these below. But first it is important to understand the basics of
-osgEarth's shader composition framework.
 
 Framework Basics
 ----------------
 
-osgEarth installs default shaders for rendering. The default shaders are shown
-below. The ``LOCATION_*`` designators allow you to inject functions at
-various points in the shader's execution.
+The Shader Composition framework provides the main() functions automatically.
+You do not need to write them. Instead, you write modular functions and tell the 
+framework when and where to execute them.
 
-Here is the pseudo-code for osgEarth's built-in shaders::
+Below you can see the main() functions that osgEarth creates.
+The ``LOCATION_*`` designators allow you to inject functions at
+various points in the shader's execution pipeline.
+
+Here is the pseudo-code for osgEarth's built-in shaders mains::
 
     // VERTEX SHADER:
 
@@ -31,6 +47,7 @@ Here is the pseudo-code for osgEarth's built-in shaders::
 
         // "LOCATION_VERTEX_MODEL" user functions are called here:
         model_func_1(vertex);
+        model_func_2(vertex);
         ...
 
         vertex = gl_ModelViewMatrix * vertex;
@@ -39,7 +56,7 @@ Here is the pseudo-code for osgEarth's built-in shaders::
         view_func_1(vertex);
         ...
 
-        vertes = gl_ProjectionMatrix * vertex;
+        vertex = gl_ProjectionMatrix * vertex;
         
         // "LOCATION_VERTEX_CLIP" user functions are called last:
         clip_func_1(vertex);
@@ -65,76 +82,69 @@ Here is the pseudo-code for osgEarth's built-in shaders::
         ...
 
         gl_FragColor = color;
-    }  
+    }
+    
+As you can see, we have made the design decision to designate function
+injection points that make sense for *most* applications. That is not to say
+that they are perfect for everything, rather that we believe this approach
+makes the Framework easy to use and not too "low-level".
+
+*Important*: The Shader Composition Framework at this time only supports VERTEX and FRAGMENT
+shaders. It does not support GEOMETRY or TESSELLATION shaders yet. We are planning
+to add this in the future.
 
 
 VirtualProgram
 --------------
 
-osgEarth include an OSG state attribute called ``VirtualProgram`` that performs
+osgEarth introduces a new OSG state attribute called ``VirtualProgram`` that performs
 the runtime shader composition. Since ``VirtualProgram`` is an ``osg::StateAttribute``,
 you can attach one to any node in the scene graph. Shaders that belong to a
-``VirtualProgram`` can override shaders lower down on the attribute stack
-(i.e., higher up in the scene graph). In the way you can override individual shader
-functions in osgEarth.
-
-The sections below on integration will demonstrate how to use ``VirtualProgram``.
-
+``VirtualProgram`` can override shaders higher up in the scene graph.
+In this way you can add, combine, and override individual shader functions in osgEarth.
 
-Integrating Custom Shaders
---------------------------
-
-There are two ways to use shader composition in osgEarth.
-
-* Injecting user functions
-* Overriding osgEarth's built-in functions with a custom ``ShaderFactory``
+At run time, a ``VirtualProgram`` will look at the current state and assemble a full
+``osg::Program`` that uses the built-in main()s and calls all the functions that you
+have injected via ``VirtualProgram``.
 
  
-Injecting User Functions
-~~~~~~~~~~~~~~~~~~~~~~~~
+Adding Functions
+~~~~~~~~~~~~~~~~
 
-In the core shader code above, osgEarth calls into user functions.
+From the generated mains we saw earlier, osgEarth calls into user functions.
 These don't exist in the default shaders that osgEarth generates;
 rather, they represent code that you as the developer can "inject"
-into various locations in the built-in shaders.
-
-For example, let's use User Functions to create a simple "haze" effect.
-(NOTE: see this example in its entirety in osgearth_shadercomp.cpp)::
-
-    static char s_hazeVertShader[] =
-        "varying vec3 v_pos; \n"
-        "void setup_haze(inout vec4 vertexVIEW) \n"
-        "{ \n"
-        "    v_pos = vec3(vertexVIEW); \n"
-        "} \n";
-
-    static char s_hazeFragShader[] =
-        "varying vec3 v_pos; \n"
-        "void apply_haze(inout vec4 color) \n"
-        "{ \n"
-        "    float dist = clamp( length(v_pos)/10000000.0, 0, 0.75 ); \n"
-        "    color = mix(color, vec4(0.5, 0.5, 0.5, 1.0), dist); \n"
-        "} \n";
-
-    osg::StateAttribute*
-    createHaze()
-    {
-        osgEarth::VirtualProgram* vp = new osgEarth::VirtualProgram();
+into various locations in the shader pipeline.
 
-        vp->setFunction( "setup_haze", s_hazeVertShader, osgEarth::ShaderComp::LOCATION_VERTEX_VIEW);
-        vp->setFunction( "apply_haze", s_hazeFragShader, osgEarth::ShaderComp::LOCATION_FRAGMENT_LIGHTING);
+For example, let's use user functions to create a simple "haze" effect::
 
-        return vp;
+    // haze_vertex:
+    varying vec3 v_pos;
+    void setup_have(inout vec4 vertexView)
+    {
+        v_pos = vertexView.xyz;
+    }
+    
+    // haze_fragment:
+    varying vec3 v_pos;
+    void apply_haze(inout vec4 color)
+    {
+        float dist = clamp( length(v_pos)/10000000.0, 0, 0.75 );
+        color = mix(color, vec4(0.5, 0.5, 0.5, 1.0), dist);
     }
+    
+    // C++:
+    VirtualProgram* vp = VirtualProgram::getOrCreate( stateSet );
 
-    ...
-    sceneGraph->getOrCreateStateSet()->setAttributeAndModes( createHaze() );
+    vp->setFunction( "setup_haze", haze_vertex,   ShaderComp::LOCATION_VERTEX_VIEW);
+    vp->setFunction( "apply_haze", haze_fragment, ShaderComp::LOCATION_FRAGMENT_LIGHTING);
 
-In this example, the function ``setup_haze`` is called from the core vertex shader
-after the built-in vertex functions. The ``apply_haze`` function gets called from
-the core fragment shader after the built-in fragment functions.
 
-There are FIVE injection points, as follows:
+In this example, the function ``setup_haze`` is called from the built-in vertex shader
+main() after the built-in vertex functions. The ``apply_haze`` function gets called from
+the core fragment shader main() after the built-in fragment functions.
+
+There are SIX injection points, as follows:
 
 +----------------------------------------+-------------+------------------------------+
 | Location                               | Shader Type | Signature                    |
@@ -149,6 +159,8 @@ There are FIVE injection points, as follows:
 +----------------------------------------+-------------+------------------------------+
 | ShaderComp::LOCATION_FRAGMENT_LIGHTING | FRAGMENT    | void func(inout vec4 color)  |
 +----------------------------------------+-------------+------------------------------+
+| ShaderComp::LOCATION_FRAGMENT_OUTPUT   | FRAGMENT    | void func(inout vec4 color)  |
++----------------------------------------+-------------+------------------------------+
 
 Each VERTEX locations let you operate on the vertex in a particular *coordinate space*. 
 You can alter the vertex, but you *must* leave it in the same space.
@@ -161,12 +173,130 @@ You can alter the vertex, but you *must* leave it in the same space.
          three axis, and is the result of transforming the original vertex by
          ``gl_ModelViewProjectionMatrix``.
          
+The FRAGMENT locations are as follows.
+
+:COLORING:  Functions here are called when resolving the fragment color before
+            lighting is applied. Texturing or color adjustments typically 
+            happen during this stage.
+:LIGHTING:  Functions here affect the lighting applied to a fragment color. This is 
+            where things like sun lighting, bump mapping or normal mapping would
+            typically occur.
+:OUTPUT:    This is where gl_FragColor is set. By default, the built-in fragment
+            main() will set it for you. But you can set an OUTPUT shader to 
+            replace this behavior with your own. A typical reason to do this would
+            be to implement MRT rendering (see the osgearth_mrt example).
+
+
+Shader Packages
+---------------
+
+Earlier we shows you how to inject functions using ``VirtualProgram``. 
+The Shader Composition Framework also provides the concept of a ``ShaderPackage`` that supports
+more advances methods of shader management. We will talk about some of those now.
+
+
+VirtualProgram Metadata
+~~~~~~~~~~~~~~~~~~~~~~~
+
+As we have seen, when you add a shader function to the pipeline using ``VirtualProgram``
+you need to tell osgEarth the name of the GLSL function to call, and the location in
+the pipeline at which to call it, like so::
+
+    VirtualProgram* vp;
+    ....
+    vp->setFunction( "color_it_red", shaderSource, ShaderComp::LOCATION_FRAGMENT_COLORING );
+
+That works. But if the function name or the inject location changes, you need to remember
+to keep the GLSL code in sync with the ``setFunction()`` parameters.
+
+It would be easier to specify this all in once place. A ``ShaderPackage`` lets you do just that.
+Here is an example::
+
+    #version 110
+    
+    #pragma vp_entryPoint  "color_it_red"
+    #pragma vp_location    "fragment_coloring"
+    #pragam vp_order       "1.0"
+    
+    void color_it_red(inout vec4 color)
+    {
+        color.r = 1.0;
+    }
+    
+Now instead of calling ``VirtualProgram::setFunction()`` directory, you can create a
+``ShaderPackage``, add your code, and call load to create the function on the ``VirtualProgram``::
+
+    ShaderPackage package;
+    package.add( shaderFileName, shaderSource );
+    package.load( virtualProgram, shaderFileName );
+    
+It takes a "file name" because the shader can be in an external file.
+But that is not a requirement. Read on for more details.
+
+The ``vp_location`` values follow the code-based values, and are as follows::
+
+    vertex_model
+    vertex_view
+    vertex_clip
+    fragment_coloring
+    fragment_lighting
+    fragment_output
+
+
+External GLSL Files
+~~~~~~~~~~~~~~~~~~~
+
+The ``ShaderPackage`` lets you load GLSL code from either a file or a string.
+When you call the ``add`` method as show above, this tells the package to 
+(a) first look for a file by that name and load from that file; and 
+(b) if the file doesn't exist, use the code in the source string.
+
+So let's look at this example::
+
+    ShaderPackage package;
+    package.add( "myshader.frag.glsl", backupSourceCode );
+    ...
+    package.load( virtualProgram, "myshader.frag.glsl" );
+
+The package will try to load the shader from the GLSL file. It will search for it in the ``OSG_FILE_PATH``.
+If it cannot find the file, it will load the shader from the backup source code associated with
+that shader in the package.
+
+osgEarth uses this technique internally to "inline" its stock shader code.
+That gives you the option of deploying GLSL files with your application OR 
+keeping them inline -- the application will still work either way.
+
+
+Include Files
+~~~~~~~~~~~~~
+
+The ``ShaderPackage`` support the concept if *include files*. Your GLSL code
+can *include* any other shaders in the same package by referencing their file names.
+Use a custom ``#pragma`` to include another file::
+
+    #pragma include "myCode.vertex.glsl"
+
+Just as in C++, the *include* will load the other file (or source code) directly
+inline. So the file you are including must be structured as if you had placed it right
+in the including file. (That means it cannot have its own ``#version`` string, for example.)
+
+Again: the *includer* and the *includee* must be registered with the same ``ShaderPackage``.
+
+----
+
+Concepts Specific to osgEarth
+-----------------------------
+
+Even though the VirtualProgram framework is included in the osgEarth SDK,
+it really has nothing to do with map rendering. In this section we will go over some
+of the things that osgEarth does with shader composition.
+
          
-Shader Variables
-~~~~~~~~~~~~~~~~
+Terrain Variables
+~~~~~~~~~~~~~~~~~
 
-There are some built-in shader variables that osgEarth installs and that you can 
-access from your shader functions.
+There are some built-in shader ``uniforms`` and ``variables`` that the osgEarth terrain
+engine uses and that are available to the developer.
 
     *Important: Shader variables starting with the prefix ``oe_`` or ``osgearth_``
     are reserved for osgEarth internal use.*
@@ -175,8 +305,8 @@ Uniforms:
 
   :oe_tile_key:          (vec4) elements 0-2 hold the x, y, and LOD tile key values;
                          element 3 holds the tile's bounding sphere radius (in meters)
-  :oe_layer_tex:         (sampler2D) texture applied to the current tile
-  :oe_layer_texc:        (vec4) texture coordinate for current tile
+  :oe_layer_tex:         (sampler2D) texture applied to the current layer of the current tile
+  :oe_layer_texc:        (vec4) texture coordinates for current tile
   :oe_layer_tilec:       (vec4) unit coordinates for the current tile (0..1 in x and y)
   :oe_layer_uid:         (int) Unique ID of the active layer
   :oe_layer_order:       (int) Render order of the active layer
@@ -193,10 +323,9 @@ Vertex attributes:
 Shared Image Layers
 ~~~~~~~~~~~~~~~~~~~
 
-By default, osgEarth gives you access to the layer it's currently drawing (via the
-``oe_layer_tex`` uniform; see above). But sometimes you want to access more than one
-layer at a time. For example, you might have a masking layer that indicates land vs.
-water. You may not actually want to *draw* this layer, but you want to use it to modulate
+Sometimes you want to access more than one image layer at a time.
+For example, you might have a masking layer that indicates land vs. water.
+You may not actually want to *draw* this layer, but you want to use it to modulate
 another visible layer.
 
 You can do this using *shared image layers*. In the ``Map``, mark an image layer as
@@ -205,28 +334,3 @@ to all the other layers in a secondary sampler.
 
     Please refer to ``osgearth_sharedlayer.cpp`` for a usage example!
 
-
-Customizing the Shader Factory
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-This is amore advanced topic.
-If you want to replace osgEarth's built-in shader functions, you can install a custom
-``ShaderFactory``. The ``ShaderFactory`` is stored in the osgEarth ``Registry`` and contains
-all the methods for creating the built-in functions. You can install your own ``ShaderFactory``
-like so::
-
-    #include <osgEarth/ShaderFactory>
-    ...
-
-    class CustomShaderFactory : public osgEarth::ShaderFactory
-    {
-        ... override desired methods here ...
-    };
-    ...
-
-    osgEarth::Registry::instance()->setShaderFactory( new CustomShaderFactory() );
-
-This method is good for replacing osgEarth's built-in lighting shader code.
-HOWEVER: be aware that override the built-in texturing functions may not work.
-This is because osgEarth's image layer composition mechanisms override these methods
-themselves to perform layer rendering.
diff --git a/docs/source/faq.rst b/docs/source/faq.rst
index 304a14a..de96386 100644
--- a/docs/source/faq.rst
+++ b/docs/source/faq.rst
@@ -17,19 +17,8 @@ Common Usage
 How do I place a 3D model on the map?
 .....................................
 
-    The most basic approach is to make a ``osg::Matrix`` so you can position
-    a model using your own ``osg::MatrixTransform``. You can use the ``GeoPoint``
-    class like so::
-    
-        GeoPoint point(latLong, -121.0, 34.0, 1000.0, ALTMODE_ABSOLUTE);
-        osg::Matrix matrix;
-        point.createLocalToWorld( matrix );
-        myMatrixTransform->setMatrix( matrix );
-
-    Another option is the ``osgEarth::GeoTransform`` class. It inherits from
-    ``osg::Transform`` so you can add your own nodes as children. ``GeoTransform``
-    can automatically convert coordinates as well, as long as you tell it 
-    about your map's terrain::
+    The ``osgEarth::GeoTransform`` class inherits from ``osg::Transform``
+    and will convert map coordinates into OSG world coordinates for you::
 
         GeoTransform* xform = new GeoTransform();
         ...
@@ -38,31 +27,13 @@ How do I place a 3D model on the map?
         GeoPoint point(srs, -121.0, 34.0, 1000.0, ALTMODE_ABSOLUTE);
         xform->setPosition(point);
 
-    Finally, you can position a node by using the ``ModelNode`` from the
-    osgEarth::Annotation namespace. This is more complicated, but lets you
-    take advantage of symbology::
-
-        using namespace osgEarth;
-        using namespace osgEarth::Symbology;
-        ...
-
-        // load your model:
-        osg::Node* myModel = osgDB::readNodeFile(...);
-        
-        // establish the coordinate system you wish to use:
-        const SpatialReference* latLong = SpatialReference::get("wgs84");
-        
-        // construct your symbology:
-        Style style;
-        style.getOrCreate<ModelSymbol>()->setModel( myModel );
-        
-        // make a ModelNode:
-        ModelNode* model = new ModelNode( mapNode, style );
-        
-        // Set its location.
-        model->setPosition( GeoPoint(latLong, -121.0, 34.0, 1000.0, ALTMODE_ABSOLUTE) );
-
-    Look at the ``osgearth_annotation.cpp`` sample for more inspiration.
+    A lower-level approach is to make a ``osg::Matrix`` so you can position
+    a model using your own ``osg::MatrixTransform``::
+    
+        GeoPoint point(latLong, -121.0, 34.0, 1000.0, ALTMODE_ABSOLUTE);
+        osg::Matrix matrix;
+        point.createLocalToWorld( matrix );
+        myMatrixTransform->setMatrix( matrix );
     
 
 How do make the terrain transparent?
@@ -111,8 +82,9 @@ How do I set the resolution of terrain tiles?
 
         <map>
             <options>
-                <elevation_tile_size>31</elevation_tile_size>
-                ...
+                <terrain>
+                    <tile_size>32</tile_size> 
+                    ...
 
 
 ----
@@ -150,7 +122,7 @@ Does osgEarth work with VirtualPlanetBuilder?
 Can osgEarth load TerraPage or MetaFlight?
 ..........................................
 
-	osgEarth cannot natively load TerraPage (TXP) or MetaFlight. However, osgEarth does have a
+	osgEarth cannot load TerraPage (TXP) or MetaFlight. However, osgEarth does have a
 	"bring your own terrain" plugin that allows you to load an external model and use it as your
 	terrain. The caveat is that since osgEarth doesn't know anything about your terrain model, you
 	will not be able to use some of the features of osgEarth (like being able to add or remove layers).
diff --git a/docs/source/index.rst b/docs/source/index.rst
index 851e3eb..8a50310 100644
--- a/docs/source/index.rst
+++ b/docs/source/index.rst
@@ -3,14 +3,13 @@
    You can adapt this file completely to your liking, but it should at least
    contain the root `toctree` directive.
 
-osgEarth - a C++ Terrain SDK
-============================
-Welcome to the osgEarth_ documentation project!
+osgEarth - a C++ Geospatial SDK
+===============================
+Welcome to the osgEarth_ documentation!
 
-osgEarth_ is a big SDK. Keeping up on the documentation is not easy!
-So now we've moved the docs right into the osgEarth Git repository to make
-it easier for the osgEarth team and user community to help. Check the
-links at the bottom of the sidebar.
+The osgEarth documentation is stored in the git repository alongside the
+code. So if you see missing docs, please help by writing and contributing!
+Thank you!
 
 Table of Contents
 -----------------
diff --git a/docs/source/install.rst b/docs/source/install.rst
index 1a9d4b1..77fe66d 100644
--- a/docs/source/install.rst
+++ b/docs/source/install.rst
@@ -1,5 +1,5 @@
 Building osgEarth
-===============
+=================
 osgEarth is a cross-platform library. It uses the  CMake cross-platform build system, version 2.8 or newer. This is the same build system that  OpenSceneGraph uses.
 
 Get the source code
@@ -17,7 +17,7 @@ To download a tarball, visit http://github.com/gwaldron/osgearth/tags and select
 
 
 Get the dependencies
--------------
+--------------------
 Words can have *emphasis in italics* or be **bold** and you can define
 code samples with back quotes, like when you talk about a command: ``sudo`` 
 gives you super user powers! 
diff --git a/docs/source/references/drivers/feature/ogr.rst b/docs/source/references/drivers/feature/ogr.rst
index e4e9203..5f23706 100644
--- a/docs/source/references/drivers/feature/ogr.rst
+++ b/docs/source/references/drivers/feature/ogr.rst
@@ -2,7 +2,7 @@ OGR
 ===
 This plugin reads vector data from any of the formats supported by the
 `OGR Simple Feature Library`_ (which is quite a lot). Most common among
-these includes ESRI Shapefiles and GML.
+these includes ESRI Shapefiles, GML, and PostGIS.
 
 Example usage::
 
@@ -26,6 +26,18 @@ Properties:
     :layer:                 Some datasets require an addition layer identifier for sub-datasets;
                             Set that here (integer).
 
+*Special Note on PostGIS usage:*
+
+PostGIS uses a ``connection`` string instead of a ``url`` to make its database connection.
+It is common to include a tables reference such as ``table=something``. In this driver,
+however, that can lead to problems; instead specify your table in the ``layer`` property.
+For example::
+
+    <features driver="ogr">
+        <connection>PG:dbname=mydb host=127.0.0.1 ...</connection>
+        <layer>myTableName</layer>
+    </features>
+   
 
 .. _OGR Simple Feature Library:  http://www.gdal.org/ogr
 .. _OGR driver:                  http://www.gdal.org/ogr/ogr_formats.html
diff --git a/docs/source/references/drivers/terrain/mp.rst b/docs/source/references/drivers/terrain/mp.rst
index 0669061..7d90c70 100644
--- a/docs/source/references/drivers/terrain/mp.rst
+++ b/docs/source/references/drivers/terrain/mp.rst
@@ -10,7 +10,7 @@ Example usage::
             <terrain driver                   = "mp"
                      skirt_ratio              = "0.05"
                      color                    = "#ffffffff"
-                     normalize_edges          = "true"
+                     normalize_edges          = "false"
                      incremental_update       = "false"
                      quick_release_gl_objects = "true"
                      min_tile_range_factor    = "6.0"
diff --git a/docs/source/references/drivers/tile/index.rst b/docs/source/references/drivers/tile/index.rst
index 253c6ee..5f2d27e 100644
--- a/docs/source/references/drivers/tile/index.rst
+++ b/docs/source/references/drivers/tile/index.rst
@@ -15,6 +15,7 @@ terrain engine. It can produce image tiles, elevation grid tiles, or both.
    mbtiles
    noise
    osg
+   quadkey
    tilecache
    tileservice
    tms
diff --git a/docs/source/references/drivers/tile/quadkey.rst b/docs/source/references/drivers/tile/quadkey.rst
new file mode 100644
index 0000000..0ca2048
--- /dev/null
+++ b/docs/source/references/drivers/tile/quadkey.rst
@@ -0,0 +1,27 @@
+QuadKey
+=======
+The QuadKey plugin is useful for reading web map tile repositories
+that follow the Bing_ maps tile system.  It is assumed that the
+dataset is in spherical-mercator with 2x2 tiles at the root just like Bing.
+
+Example usage::
+
+    <image name="imagery" driver="quadkey">
+        <url>http://[1234].server.com/tiles/{key}.png</url>
+    </image>
+
+Creating the URL template:
+
+    The square brackets [] indicate that osgEarth should "cycle through" the characters
+    within, resulting in round-robin server requests. Some services require this.
+
+    You will need to provide {key} template within the URL where osgEarth will
+    insert the quadkey for the tile it's requesting.
+
+Properties:
+
+    :url:            Location of the tile repository (URL template -- see above)
+    :profile:        Spatial profile of the repository
+    :format:         If the format is not part of the URL itself, you can specify it here.
+
+.. _Bing: http://msdn.microsoft.com/en-us/library/bb259689.aspx
diff --git a/docs/source/references/earthfile.rst b/docs/source/references/earthfile.rst
index 154e010..9cf8ffe 100644
--- a/docs/source/references/earthfile.rst
+++ b/docs/source/references/earthfile.rst
@@ -58,8 +58,7 @@ the entire map.
 +--------------------------+--------------------------------------------------------------------+
 | Property                 | Description                                                        |
 +==========================+====================================================================+
-| lighting                 | Whether to enable GL_LIGHTING on the entire map. By default this is|
-|                          | unset, meaning it will inherit the lighting mode of the scene.     |
+| lighting                 | Whether to allow lighting shaders to affect the map.               |
 +--------------------------+--------------------------------------------------------------------+
 | elevation_interpolation  | Algorithm to use when resampling elevation source data:            |
 |                          |   :nearest:     Nearest neighbor                                   |
@@ -67,9 +66,6 @@ the entire map.
 |                          |   :bilinear:    Linear interpolation in both axes                  |
 |                          |   :triangulate: Interp follows triangle slope                      |
 +--------------------------+--------------------------------------------------------------------+
-| elevation_tile_size      | Sets the number of samples to render for each terrain tile         |
-|                          | (width and height)                                                 |
-+--------------------------+--------------------------------------------------------------------+
 | overlay_texture_size     | Sets the texture size to use for draping (projective texturing)    |
 +--------------------------+--------------------------------------------------------------------+
 | overlay_blending         | Whether overlay geometry blends with the terrain during draping    |
@@ -104,7 +100,10 @@ These options control the rendering of the terrain surface.
                      cluster_culling       = "true"
                      mercator_fast_path    = "true"
                      blending              = "false"
-                     color                 = "#ffffffff" >
+                     color                 = "#ffffffff"
+                     tile_size             = "17"
+                     normalize_edges       = "false"
+                     elevation_smoothing   = "false">
 
 +-----------------------+--------------------------------------------------------------------+
 | Property              | Description                                                        |
@@ -144,7 +143,15 @@ These options control the rendering of the terrain surface.
 |                       | underlying geometry. This lets you make the globe partially        |
 |                       | transparent. This is handy for seeing underground objects.         |
 +-----------------------+--------------------------------------------------------------------+
-| color                 | Color on the underlying (untextures) terrain.                      |
+| tile_size             | The dimensions of each terrain tile. Each terrain tile will have   |
+|                       | ``tile_size`` X ``tile_size`` verticies.                           |
++-----------------------+--------------------------------------------------------------------+
+| normalize_edges       | Calculate normal vectors along the edges of terrain tiles so that  |
+|                       | lighting appears smoother from one tile to the next.               |
++-----------------------+--------------------------------------------------------------------+
+| elevation_smoothing   | Whether to smooth the transition across elevation data insets.     |
+|                       | Doing so will give a smoother appearance to disparate height field |
+|                       | data, but elevations will not be as accurate. Default = false      |
 +-----------------------+--------------------------------------------------------------------+
 
 
@@ -171,6 +178,9 @@ An *image layer* is a raster image overlaid on the map's geometry.
                enabled        = "true"
                visible        = "true"
                shared         = "false"
+               shared_sampler = "string"
+               shared_matrix  = "string"
+               coverage       = "false"
                feather_pixels = "false"
                min_filter     = "LINEAR"
                mag_filter     = "LINEAR" 
@@ -227,6 +237,20 @@ An *image layer* is a raster image overlaid on the map's geometry.
 | shared                | Generates a secondary, dedicated sampler for this layer so that it |
 |                       | may be accessed globally by custom shaders.                        |
 +-----------------------+--------------------------------------------------------------------+
+| shared_sampler        | For a shared layer, the uniform name of the sampler that will be   |
+|                       | available in GLSL code.                                            |
++-----------------------+--------------------------------------------------------------------+
+| shared_matrix         | For a shared layer, the uniform name of the texture matrix that    |
+|                       | will be available in GLSL code that you can use to access          |
+|                       | the proper texture coordinate for the ``shared_sampler`` above.    |
++-----------------------+--------------------------------------------------------------------+
+| coverage              | Indicates that this is a coverage layer, i.e. a layer that conveys |
+|                       | discrete values with particular semantics. An example would be a   |
+|                       | "land use" layer in which each pixel holds a value that indicates  |
+|                       | whether the area is grassland, desert, etc. Marking a layer as a   |
+|                       | coverage disables any interpolation, filtering, or compression as  |
+|                       | these will corrupt the sampled data values on the GPU.             |
++-----------------------+--------------------------------------------------------------------+
 | feather_pixels        | Whether to feather out alpha regions for this image layer with the |
 |                       | featherAlphaRegions function. Used to get proper blending when you |
 |                       | have datasets that abutt exactly with no overlap.                  |
@@ -238,7 +262,9 @@ An *image layer* is a raster image overlaid on the map's geometry.
 | mag_filter            | OpenGL texture magnification filter to use for this layer.         |
 |                       | Options are the same as for ``min_filter`` above.                  |
 +-----------------------+--------------------------------------------------------------------+
-| texture_compression   | "auto" to compress textures on the GPU; "none" to disable.         |
+| texture_compression   | "auto" to compress textures on the GPU;                            |
+|                       | "none" to disable.                                                 |
+|                       | "fastdxt" to use the FastDXT real time DXT compressor              |
 +-----------------------+--------------------------------------------------------------------+
 
 
@@ -252,15 +278,18 @@ will composite all elevation data into a single heightmap and use that to build
 .. parsed-literal::
 
     <map>
-        <elevation name           = "text"
-                   driver         = "gdal"
-                   min_level      = "0"
-                   max_level      = "23"
-                   min_resolution = "100.0"
-                   max_resolution = "0.0"
-                   enabled        = "true"
-                   offset         = "false"
-                   nodata_policy  = "interpolate" >
+        <elevation name            = "text"
+                   driver          = "gdal"
+                   min_level       = "0"
+                   max_level       = "23"
+                   min_resolution  = "100.0"
+                   max_resolution  = "0.0"
+                   enabled         = "true"
+                   offset          = "false"
+                   nodata_value    = "-32768"
+                   min_valid_value = "-32768"
+                   max_valid_value = "32768"
+                   nodata_policy   = "interpolate" >
 
 
 +-----------------------+--------------------------------------------------------------------+
@@ -293,6 +322,12 @@ will composite all elevation data into a single heightmap and use that to build
 |                       | will interpolate neighboring values to fill holes. Set it to "msl" |
 |                       | to replace "no data" samples with the current sea level value.     |
 +-----------------------+--------------------------------------------------------------------+
+| nodata_value          | Treat this value as "no data".                                     |
++-----------------------+--------------------------------------------------------------------+
+| min_valid_value       | Treat anything less than this value as "no data".                  |
++-----------------------+--------------------------------------------------------------------+
+| max_valid_value       | Treat anything greater than this value as "no data".               |
++-----------------------+--------------------------------------------------------------------+
 
 
 .. _ModelLayer:
@@ -409,11 +444,9 @@ Configures a cache for tile data.
 +-----------------------+--------------------------------------------------------------------+
 | Property              | Description                                                        |
 +=======================+====================================================================+
-| driver                | Plugin to use for caching.                                         |
-|                       | At the moment there is only one caching plugin that comes with     |
-|                       | osgEarth, the ``filesystem`` plugin.                               |
+| driver                | Plugin to use for caching, ``filesystem`` or ``leveldb``.          |
 +-----------------------+--------------------------------------------------------------------+
-| path                  | Path (relative or absolute) or the root of a ``filesystem`` cache. |
+| path                  | Path (relative or absolute) or the cache folder or file.           |
 +-----------------------+--------------------------------------------------------------------+
 
 
@@ -438,6 +471,8 @@ Policy that determines how a given element will interact with a configured cache
 |                       |   :no_cache:    Ignore caching and always read from the data       |
 |                       |                 source.                                            |
 +-----------------------+--------------------------------------------------------------------+
+| max_age               | Treat cache entries older than this value (in seconds) as expired. |
++-----------------------+--------------------------------------------------------------------+
 
 
 
diff --git a/docs/source/references/envvars.rst b/docs/source/references/envvars.rst
index be11509..8623fb5 100644
--- a/docs/source/references/envvars.rst
+++ b/docs/source/references/envvars.rst
@@ -25,7 +25,9 @@ Debugging:
                                 and ``WARN``. Default is ``NOTICE``. (This is distinct from
                                 OSG's notify level.)
     :OSGEARTH_MP_PROFILE:       Dumps verbose profiling and timing data about the terrain engine's
-                                tile generator to the console.
+                                tile generator to the console. Set to 1 for detailed per-tile
+                                timings; Set to 2 for average tile load time calculations
+    :OSGEARTH_MP_DEBUG:         Draws tile bounding boxes and tilekey labels atop the map
     :OSGEARTH_MERGE_SHADERS:    Consolidate all shaders within a single shader program; this
                                 is required for GLES (mobile devices) and is therefore useful
                                 for testing. (set to 1).
diff --git a/docs/source/references/symbology.rst b/docs/source/references/symbology.rst
index f342512..be3a7cd 100644
--- a/docs/source/references/symbology.rst
+++ b/docs/source/references/symbology.rst
@@ -16,6 +16,7 @@ Jump to a symbol:
  * Render_
  * Skin_
  * Text_
+ * Coverage_
  
 **Developer Note**:
 
@@ -418,3 +419,15 @@ The *text symbol* (SDK: ``TextSymbol``) controls the existance and appearance of
 |                                | when line of sight is obstructed by terrain                        |
 +--------------------------------+--------------------------------------------------------------------+
 
+
+Coverage
+--------
+
+The *coverage symbol* (SDK: ``CoverageSymbol``) controls how a feature is rasterized into
+coverage data with discrete values.
+
++-----------------------+--------------------------------------------------------------------+
+| Property              | Description                                                        |
++=======================+====================================================================+
+| coverage-value        | Expression resolving to the floating-point value to encode.        |
++-----------------------+--------------------------------------------------------------------+
diff --git a/docs/source/releasenotes.rst b/docs/source/releasenotes.rst
index cf1555c..1de1b71 100644
--- a/docs/source/releasenotes.rst
+++ b/docs/source/releasenotes.rst
@@ -1,6 +1,39 @@
 Release Notes
 =============
 
+Version 2.7 (July 2015)
+---------------------------
+
+* New ObjectIndex system for picking and selection
+* New RTT-based picker that works for all geometry including GPU-modified geometry
+* Extensions - modular code for extending the capabilities of osgEarth
+* New procedural texture splatting extension
+* Upgraded ShaderLoader for better modularization of VirtualProgram code
+* New "elevation smoothing" property to MP terrain engine
+* New support for default MapNodeOptions
+* Logarithmic depth buffer lets you extend your near and far planes
+* Better Triton and Silverlining support
+* Overhaul of the elevation compositing engine and ElevationQuery utility
+* New Raster Feature driver lets you generate features from raster data
+* Attenuation and min/max range for image layers
+* New shader-based geodetic graticule
+* New day/night color filter
+* Viewpoint: consolidation of look-ats and tethering
+* New CoverageSymbol for rastering features into coverage data; agglite driver support
+* New feature clustering and instancing algorithms for better performance and scalability
+* Noise extension for creating a simplex noise sampler
+* New TerrainShader extension lets you inject arbitrary shader code from an earth file
+* VirtualProgram: specify all VP injection criteria with GLSL #pragmas
+* Normal mapping extension with automatic edge-normalization
+* Bump map extension for simple detail bumping
+* Performance improvements based on GlowCode profiling results
+
+
+Version 2.6 (October 2014)
+--------------------------
+
+Maintenance Release. Release notes TBD.
+
 Version 2.5 (November 2013)
 ---------------------------
 
diff --git a/docs/source/startup.rst b/docs/source/startup.rst
index 9e5641a..b37a047 100644
--- a/docs/source/startup.rst
+++ b/docs/source/startup.rst
@@ -49,9 +49,11 @@ will be missing:
 
     * LevelDB_ - Google's embedded key/value store. Include this if you want to build
       osgEarth's optional "leveldb" cache driver.
-
-    * Duktape_ - Embedded JavaScript engine. Include this is you want to embed JavaScript
-      code in your earth files to control feature styling.
+	
+	* SQLite_ - Self-contained, serverless, zero-configuration, transactional SQL database engine.
+	  Used for accessing sqlite/mbtiles datasets. You may need these tips to create the necessary
+	  .lib file from the .def and .dll files included in the Windows binaries:
+	  http://eli.thegreenplace.net/2009/09/23/compiling-sqlite-on-windows
     
 **Deprecated dependencies**: osgEarth can still use these, but they will probably go away
 in the future:
@@ -84,7 +86,7 @@ Here are a few tips.
       that is separate from the source code. This makes it easier to maintain separate
       versions and to keep GIT updates clean.
       
-    * For optional dependencies (like GEOS_ or V8_), just leave the CMake field blank
+    * For optional dependencies (like GEOS_), just leave the CMake field blank
       if you are not using it.
       
     * For the OSG dependencies, just input the **OSG_DIR** variable, and when you generate
@@ -114,4 +116,4 @@ Here are a few tips.
 .. _Mike Weiblen:   http://mew.cx/osg/
 .. _the forum:      http://forum.osgearth.org
 .. _LevelDB:        https://github.com/pelicanmapping/leveldb
-.. _Duktape:        http://duktape.org
+.. _SQLite:         http://www.sqlite.org/
diff --git a/docs/source/user/caching.rst b/docs/source/user/caching.rst
index f3bb70a..01dae30 100644
--- a/docs/source/user/caching.rst
+++ b/docs/source/user/caching.rst
@@ -36,6 +36,7 @@ In code this would look like this::
 Or, you can use an environment variable that will apply to all earth files. 
 Keep in mind that this will *override* a cache setting in the earth file::
 
+   set OSGEARTH_CACHE_DRIVER=leveldb
    set OSGEARTH_CACHE_PATH=folder_name
 
 In code you can set a global cache in the osgEarth resgistry::
diff --git a/docs/source/user/earthfiles.rst b/docs/source/user/earthfiles.rst
index 43cc2f6..6e3ee54 100644
--- a/docs/source/user/earthfiles.rst
+++ b/docs/source/user/earthfiles.rst
@@ -126,7 +126,7 @@ Here's an example cache setup::
     <map name="TMS Example" type="geocentric" version="2">
     
         <image name="metacarta blue marble" driver="tms">
-            <url>http://labs.metacarta.com/wms-c/Basic.py/1.0.0/satellite/</url>
+            <url>http://readymap.org/readymap/tiles/1.0.0/7/</url>
         </image>
 
         <options>
diff --git a/docs/source/user/features.rst b/docs/source/user/features.rst
index 450e3f7..2a8a9f4 100644
--- a/docs/source/user/features.rst
+++ b/docs/source/user/features.rst
@@ -193,18 +193,23 @@ The numeric expression evaluator supports basic arithmetic (+, -, *, / %), some
 functions (min, max), and grouping with parentheses. It also works for string values. 
 There are no operators, but you can still embed attributes.
 
-If simple expressions are not enough, you can embed JavaScript code -- please see the
-section on Scripting_ for more information.
+If simple expressions are not enough, you can use Javascript::
 
-Style Selectors
-~~~~~~~~~~~~~~~
-
-TBD.
+    <styles>
+        <script language="javascript">
+            function getOffset() {
+                return feature.properties.base_offset + 1.0;
+            }
+        </script>
 
-Scripting
-~~~~~~~~~
+        <style type="text/css">
+            mystyle {
+                extrusion-height: feature.properties.hgt * 0.3048;
+                altitude-offset:  getOffset();
+            }
+        </style>
+    </styles>
 
-TBD.
 
 Terrain Following
 -----------------
@@ -329,8 +334,8 @@ Let's modify the previous example::
       </features>
 
       <layout>
-          <tile_size_factor>15.0</tile_size_factor>
-          <level name="only" max_range="100000"/>
+          <tile_size>250000</tile_size>
+          <level name="data" max_range="100000"/>
       </layout>
 
       <styles>
@@ -351,6 +356,9 @@ all load at once. With one or more levels, osgEarth will break up the feature da
 into tiles at one or more levels of detail and page those tiles in individually.
 More below.
 
+Paging breaks the data up into tiles. The ``tile_size`` is the width (in meters)
+of each paged tile.
+
 Levels
 ~~~~~~
 
@@ -358,16 +366,7 @@ Each level describes a level of detail. This is a camera range (between ``min_ra
 and ``max_range``) at which tiles in this level of detail are rendered. But how
 big is each tile? This is calculated based on the *tile range factor*.
 
-The ``tile_size_factor`` determines the size of a tile, based on the ``max_range``
-of the LOD. The tile range factor is the multiple of a tile's radius at which the
-LOD's ``max_range`` takes effect. In other words::
-
-    tile radius = max range / tile size factor.
-    
-The default tile range factor is **15.0**.
-
-The upshot is: if you want larger tiles, reduce the range factor.
-For smaller tiles, increase the factor.
+The ``tile_size`` sets the size of a tile (in meters).
 
 Why do you care about tile size? Because the density of your data will affect
 how much geometry is in each tile. And since OSG (OpenGL, really) benefits from
diff --git a/docs/source/user/tools.rst b/docs/source/user/tools.rst
index 61c9551..071931c 100644
--- a/docs/source/user/tools.rst
+++ b/docs/source/user/tools.rst
@@ -13,33 +13,42 @@ used to control the camera and is optimized for viewing geospatial data.
     osgearth_viewer earthfile.earth [options]
 
 
-+----------------------------+--------------------------------------------------------------------+
-| Option                     | Description                                                        |
-+============================+====================================================================+
-| ``--sky``                  | Installs a SkyNode (sun, moon, stars and atmosphere..globe only)   |
-+----------------------------+--------------------------------------------------------------------+
-| ``--ocean``                | Installs a sample ocean surface node                               |
-+----------------------------+--------------------------------------------------------------------+
-| ``--kml [file.kml]``       | Loads a KML or KMZ file                                            |
-+----------------------------+--------------------------------------------------------------------+
-| ``--coords``               | Displays map coords under mouse                                    |
-+----------------------------+--------------------------------------------------------------------+
-| ``--dms``                  | Displays map coords as degrees/mins/seconds                        |
-+----------------------------+--------------------------------------------------------------------+
-| ``--dd``                   | Displays map coords as decimal degrees                             |
-+----------------------------+--------------------------------------------------------------------+
-| ``--mgrs``                 | Displays map coords as MGRS                                        |
-+----------------------------+--------------------------------------------------------------------+
-| ``--ortho``                | Installs an orthographic camera projection                         |
-+----------------------------+--------------------------------------------------------------------+
-| ``--autoclip``             | Installs an automatic clip plane handler                           |
-+----------------------------+--------------------------------------------------------------------+
-| ``--images [path]``        | Finds images in [path] and loads them as image layers              |
-+----------------------------+--------------------------------------------------------------------+
-| ``--image-extensions [*]`` | With ``--images``, only considers the listed extensions            |
-+----------------------------+--------------------------------------------------------------------+
-| ``--out-earth [out.earth]``| With ``--images``, writes out an earth file                        |
-+----------------------------+--------------------------------------------------------------------+
++----------------------------------+--------------------------------------------------------------------+
+| Option                           | Description                                                        |
++==================================+====================================================================+
+| ``--sky``                        | Installs a SkyNode (sun, moon, stars and atmosphere..globe only)   |
++----------------------------------+--------------------------------------------------------------------+
+| ``--kml [file.kml]``             | Loads a KML or KMZ file                                            |
++----------------------------------+--------------------------------------------------------------------+
+| ``--kmlui``                      | Displays a limited UI for toggling KML placemarks and folders      |
++----------------------------------+--------------------------------------------------------------------+
+| ``--coords``                     | Displays map coords under mouse                                    |
++----------------------------------+--------------------------------------------------------------------+
+| ``--dms``                        | Displays map coords as degrees/mins/seconds                        |
++----------------------------------+--------------------------------------------------------------------+
+| ``--dd``                         | Displays map coords as decimal degrees                             |
++----------------------------------+--------------------------------------------------------------------+
+| ``--mgrs``                       | Displays map coords as MGRS                                        |
++----------------------------------+--------------------------------------------------------------------+
+| ``--ortho``                      | Installs an orthographic camera projection                         |
++----------------------------------+--------------------------------------------------------------------+
+| ``--images [path]``              | Finds images in [path] and loads them as image layers              |
++----------------------------------+--------------------------------------------------------------------+
+| ``--image-extensions [*]``       | With ``--images``, only considers the listed extensions            |
++----------------------------------+--------------------------------------------------------------------+
+| ``--out-earth [out.earth]``      | With ``--images``, writes out an earth file                        |
++----------------------------------+--------------------------------------------------------------------+
+| ``--logdepth``                   | Activates the logarithmic depth buffer in high-speed mode.         |
++----------------------------------+--------------------------------------------------------------------+
+| ``--logdepth2``                  | Activates the logarithmic depth buffer in high-precision mode.     |
++----------------------------------+--------------------------------------------------------------------+
+| ``--uniform [name] [min] [max]`` | Installs a uniform and displays an on-screen slider to control its |
+|                                  | value. Helpful for debugging.                                      |
++----------------------------------+--------------------------------------------------------------------+
+| ``--ico``                        | Activates OSG's IncrementalCompileOperation, which will compile    |
+|                                  | paged objects over a series of frames (reducing frame breaks).     |
+|                                  | This is actually an OpenSceneGraph option, but useful for osgEarth |
++----------------------------------+--------------------------------------------------------------------+
 
 
 osgearth_version
@@ -154,6 +163,38 @@ osgearth_package creates a redistributable `TMS`_ based package from an earth fi
 | ``--concurrency``                  | The number of threads or proceses to use if --mp or --mt           |
 |                                    | are provided                                                       | 
 +------------------------------------+--------------------------------------------------------------------+
+| ``--alpha-mask``                   | Mask out imagery that isn't in the provided extents.               |
++------------------------------------+--------------------------------------------------------------------+
+| ``--verbose``                      | Displays progress of the operation                                 |
++------------------------------------+--------------------------------------------------------------------+
+
+osgearth_conv
+----------------
+osgearth_conv copies the contents of one TileSource to another. All arguments are Config name/value pairs,
+so you need to look in the header file for each driver's Options structure for options. Of course, the output
+driver must support writing (by implementing the ReadWriteTileSource interface). The "in" properties come
+from the GDALOptions getConfig method. The "out" properties come from the MBTilesOptions getConfig method.
+
+**Sample Usage**
+::
+    osgearth_conv --in driver gdal --in url world.tif --out driver mbtiles --out filename world.db
+
++------------------------------------+--------------------------------------------------------------------+
+| Argument                           | Description                                                        |
++====================================+====================================================================+
+| ``--elevation``                    | convert as elevation data (instead of image data)                  |
++------------------------------------+--------------------------------------------------------------------+
+| ``--profile [profile]``            | reproject to the target profile, e.g. "wgs84"                      |
++------------------------------------+--------------------------------------------------------------------+
+| ``--min-level [int]``              | min level of detail to copy                                        |
++------------------------------------+--------------------------------------------------------------------+
+| ``--max-level [int]``              | max level of detail to copy                                        |
++------------------------------------+--------------------------------------------------------------------+
+| ``--threads [n]``                  | threads to use (Careful, may crash. Doesn't help with GDAL inputs) |
++------------------------------------+--------------------------------------------------------------------+
+| ``--extents [minLat] [minLong]``   | Lat/Long extends to copy                                           |
+| ``[maxLat] [maxLong]``             |                                                                    |
++------------------------------------+--------------------------------------------------------------------+
 
 osgearth_tfs
 ------------
@@ -176,6 +217,9 @@ In addition, the TFS package generated can be served by any standard web server,
 +----------------------------------+--------------------------------------------------------------------+
 | ``--max-features``               | The maximum number of features per tile                            |
 +----------------------------------+--------------------------------------------------------------------+
+| ``--grid``                       | Generate a single level grid with the specified resolution.        |
+|                                  | Default units are meters. (ex. 50, 100km, 200mi)                   |
++----------------------------------+--------------------------------------------------------------------+
 | ``--out``                        | The destination directory                                          |
 +----------------------------------+--------------------------------------------------------------------+
 | ``--layer``                      | The name of the layer to be written to the metadata document       |
@@ -260,6 +304,3 @@ view of the map and another that shows the bounding frustums that are used for t
 
 .. _TMS: http://en.wikipedia.org/wiki/Tile_Map_Service
 
-
-
-
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 2c4b0b8..caa6e46 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -4,7 +4,8 @@ FOREACH( lib
          osgEarthAnnotation
          osgEarthFeatures
          osgEarthUtil
-         osgEarthSymbology )
+         osgEarthSymbology
+ )
 
     ADD_SUBDIRECTORY(${lib})
     
@@ -13,6 +14,7 @@ FOREACH( lib
 ENDFOREACH( lib )
 
 ADD_SUBDIRECTORY( osgEarthDrivers )
+ADD_SUBDIRECTORY( osgEarthExtensions )
 
 IF(NOT OSG_BUILD_PLATFORM_IPHONE AND NOT OSG_BUILD_PLATFORM_IPHONE_SIMULATOR AND NOT ANDROID)
 ADD_SUBDIRECTORY( applications )
diff --git a/src/applications/CMakeLists.txt b/src/applications/CMakeLists.txt
index 7afa5df..ac1152f 100644
--- a/src/applications/CMakeLists.txt
+++ b/src/applications/CMakeLists.txt
@@ -41,12 +41,12 @@ IF (Qt5Widgets_FOUND OR QT4_FOUND AND NOT ANDROID AND OSGEARTH_USE_QT)
     ADD_SUBDIRECTORY(osgearth_package_qt)
 ENDIF()
 
-
 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)
@@ -69,22 +69,20 @@ ADD_SUBDIRECTORY(osgearth_tilesource)
 ADD_SUBDIRECTORY(osgearth_imageoverlay)
 ADD_SUBDIRECTORY(osgearth_city)
 ADD_SUBDIRECTORY(osgearth_graticule)
-ADD_SUBDIRECTORY(osgearth_featuremanip)
 ADD_SUBDIRECTORY(osgearth_featurequery)
 ADD_SUBDIRECTORY(osgearth_occlusionculling)
 ADD_SUBDIRECTORY(osgearth_colorfilter)
-#ADD_SUBDIRECTORY(osgearth_verticalscale)
 ADD_SUBDIRECTORY(osgearth_sequencecontrol)
 ADD_SUBDIRECTORY(osgearth_minimap)
-#ADD_SUBDIRECTORY(osgearth_detailtex)
 ADD_SUBDIRECTORY(osgearth_sharedlayer)
-ADD_SUBDIRECTORY(osgearth_terraineffects)
 ADD_SUBDIRECTORY(osgearth_mrt)
 ADD_SUBDIRECTORY(osgearth_fog)
 ADD_SUBDIRECTORY(osgearth_shadergen)
 ADD_SUBDIRECTORY(osgearth_atlas)
 ADD_SUBDIRECTORY(osgearth_conv)
 ADD_SUBDIRECTORY(osgearth_clipplane)
+ADD_SUBDIRECTORY(osgearth_cache_test)
+ADD_SUBDIRECTORY(osgearth_pick)
 
 IF (Qt5Widgets_FOUND OR QT4_FOUND AND NOT ANDROID AND OSGEARTH_USE_QT)
     ADD_SUBDIRECTORY(osgearth_qt)
diff --git a/src/applications/osgearth_annotation/osgearth_annotation.cpp b/src/applications/osgearth_annotation/osgearth_annotation.cpp
index 96d0bb2..f2c1b1d 100644
--- a/src/applications/osgearth_annotation/osgearth_annotation.cpp
+++ b/src/applications/osgearth_annotation/osgearth_annotation.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
@@ -20,6 +23,7 @@
 #include <osgEarth/MapNode>
 #include <osgEarth/Decluttering>
 #include <osgEarth/ECEF>
+#include <osgEarth/Registry>
 
 #include <osgEarthUtil/EarthManipulator>
 #include <osgEarthUtil/AnnotationEvents>
@@ -39,6 +43,7 @@
 #include <osgEarthAnnotation/FeatureNode>
 #include <osgEarthAnnotation/HighlightDecoration>
 #include <osgEarthAnnotation/ScaleDecoration>
+#include <osgEarthUtil/ActivityMonitorTool>
 
 #include <osgEarthSymbology/GeometryFactory>
 
@@ -48,8 +53,6 @@
 #include <osgGA/EventVisitor>
 #include <osgDB/WriteFile>
 
-#include <osgEarth/Pickers>
-
 using namespace osgEarth;
 using namespace osgEarth::Annotation;
 using namespace osgEarth::Features;
@@ -63,6 +66,15 @@ usage( char** argv )
     return -1;
 }
 
+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);
+}
+
 
 //------------------------------------------------------------------
 
@@ -75,27 +87,25 @@ struct MyAnnoEventHandler : public AnnotationEventHandler
     void onHoverEnter( AnnotationNode* anno, const EventArgs& args )
     {
         anno->setDecoration( "hover" );
-        OE_NOTICE << "Hover enter" << std::endl;
+        Registry::instance()->startActivity( "Hovering " + anno->getText() );
+        Registry::instance()->endActivity( _lastClick );
     }
 
     void onHoverLeave( AnnotationNode* anno, const EventArgs& args )
     {
         anno->clearDecoration();
-        OE_NOTICE << "Hover leave" << std::endl;
+        Registry::instance()->endActivity( "Hovering " + anno->getText() );
+        Registry::instance()->endActivity( _lastClick );
     }
 
-    virtual void onClick( AnnotationNode* node, const EventArgs& details )
-    {        
-        PlaceNode* place = dynamic_cast<PlaceNode*>(node);
-        if (place == NULL)
-        {
-            OE_NOTICE << "Thanks for clicking this annotation" << std::endl;
-        }
-        else
-        {
-            OE_NOTICE << "Thanks for clicking the PlaceNode: " << place->getText() << std::endl;
-        }
+    virtual void onClick( AnnotationNode* anno, const EventArgs& details )
+    {
+        Registry::instance()->endActivity( _lastClick );
+        _lastClick = "Clicked " + anno->getText();
+        Registry::instance()->startActivity( _lastClick );
     }
+
+    std::string _lastClick;
 };
 
 //------------------------------------------------------------------
@@ -157,6 +167,12 @@ main(int argc, char** argv)
     labelControl->setFontSize( 24.0f );
     box->addControl( labelControl  );
 
+    // Activity monitor:
+    VBox* activityBox = ControlCanvas::getOrCreate(&viewer)->addControl( new VBox() );
+    activityBox->setHorizAlign(activityBox->ALIGN_RIGHT);
+    activityBox->setVertAlign(activityBox->ALIGN_BOTTOM);
+    activityBox->setBackColor(0,0,0,0.75);
+    viewer.addEventHandler(new ActivityMonitorTool(activityBox));
 
     // Make a group for 2D items, and activate the decluttering engine. Decluttering
     // will migitate overlap between elements that occupy the same screen real estate.
@@ -213,7 +229,7 @@ main(int argc, char** argv)
         geomStyle.getOrCreate<LineSymbol>()->stroke()->width() = 5.0f;
         geomStyle.getOrCreate<AltitudeSymbol>()->clamping() = AltitudeSymbol::CLAMP_TO_TERRAIN;
         geomStyle.getOrCreate<AltitudeSymbol>()->technique() = AltitudeSymbol::TECHNIQUE_GPU;
-        FeatureNode* gnode = new FeatureNode(mapNode, new Feature(geom, geoSRS, geomStyle));
+        FeatureNode* gnode = new FeatureNode(mapNode, new Feature(geom, geoSRS), geomStyle);
         annoGroup->addChild( gnode );
 
         labelGroup->addChild( new LabelNode(mapNode, GeoPoint(geoSRS,-30, 50), "Rhumb line polygon", labelStyle) );
@@ -233,7 +249,7 @@ main(int argc, char** argv)
         geomStyle.getOrCreate<LineSymbol>()->stroke()->width() = 3.0f;
         geomStyle.getOrCreate<AltitudeSymbol>()->clamping() = AltitudeSymbol::CLAMP_TO_TERRAIN;
         geomStyle.getOrCreate<AltitudeSymbol>()->technique() = AltitudeSymbol::TECHNIQUE_GPU;
-        FeatureNode* gnode = new FeatureNode(mapNode, new Feature(geom, geoSRS, geomStyle));
+        FeatureNode* gnode = new FeatureNode(mapNode, new Feature(geom, geoSRS), geomStyle);
         annoGroup->addChild( gnode );
 
         labelGroup->addChild( new LabelNode(mapNode, GeoPoint(geoSRS, -175, -35), "Antimeridian polygon", labelStyle) );
@@ -241,7 +257,11 @@ main(int argc, char** argv)
 
     //--------------------------------------------------------------------
 
+
+
     // A path using great-circle interpolation.
+    // Keep a pointer to it so we can modify it later on.
+    FeatureNode* pathNode = 0;
     {
         Geometry* path = new LineString();
         path->push_back( osg::Vec3d(-74, 40.714, 0) );   // New York
@@ -253,12 +273,12 @@ main(int argc, char** argv)
         pathStyle.getOrCreate<AltitudeSymbol>()->clamping() = AltitudeSymbol::CLAMP_TO_TERRAIN;
         pathStyle.getOrCreate<AltitudeSymbol>()->technique() = AltitudeSymbol::TECHNIQUE_GPU;
 
-        Feature* pathFeature = new Feature(path, geoSRS, pathStyle);
+        Feature* pathFeature = new Feature(path, geoSRS);
         pathFeature->geoInterp() = GEOINTERP_GREAT_CIRCLE;
 
         //OE_INFO << "Path extent = " << pathFeature->getExtent().toString() << std::endl;
 
-        FeatureNode* pathNode = new FeatureNode(mapNode, pathFeature);
+        pathNode = new FeatureNode(mapNode, pathFeature, pathStyle);
         annoGroup->addChild( pathNode );
 
         labelGroup->addChild( new LabelNode(mapNode, GeoPoint(geoSRS,-170, 61.2), "Great circle path", labelStyle) );
@@ -374,8 +394,8 @@ main(int argc, char** argv)
         utahStyle.getOrCreate<ExtrusionSymbol>()->height() = 250000.0; // meters MSL
         utahStyle.getOrCreate<PolygonSymbol>()->fill()->color() = Color(Color::White, 0.8);
 
-        Feature*     utahFeature = new Feature(utah, geoSRS, utahStyle);
-        FeatureNode* featureNode = new FeatureNode(mapNode, utahFeature);
+        Feature*     utahFeature = new Feature(utah, geoSRS);
+        FeatureNode* featureNode = new FeatureNode(mapNode, utahFeature, utahStyle);
         annoGroup->addChild( featureNode );
     }
 
@@ -394,6 +414,73 @@ main(int argc, char** argv)
             editorGroup->addChild( new ImageOverlayEditor( imageOverlay ) );
         }
     }
+
+    /*
+    // Add a bunch of features as individual FeatureNodes.  This is slow.
+    {
+        // A lat/lon grid, showing lots of features as individual feature nodes
+        for (unsigned int i = 0; i < 360; i++)
+        {
+            for (unsigned int j = 0; j < 180; j++)
+            {
+                double w = -180.0 + (double)(i);
+                double e = w + 1.0;
+                double s = -90 + (double)(j);
+                double n = s + 1.0;
+
+                Geometry* geometry = new Polygon();
+                geometry->push_back( w, s );
+                geometry->push_back( e, s );
+                geometry->push_back( e, n );
+                geometry->push_back( w, n );
+                
+                Style style;
+                style.getOrCreate<LineSymbol>()->stroke()->color() = Color::Red;
+                style.getOrCreate<LineSymbol>()->stroke()->width() = 3.0f;
+
+                Feature*     feature = new Feature(geometry, geoSRS, style);
+                FeatureNode* featureNode = new FeatureNode(mapNode, feature);
+                annoGroup->addChild( featureNode );
+            }
+        }
+    }
+    */
+
+    /*
+     // Add a bunch of features to a single FeatureNodes.  This is slow.
+    {
+        FeatureList features;
+
+        Style style;
+        style.getOrCreate<LineSymbol>()->stroke()->color() = Color::Red;
+        style.getOrCreate<LineSymbol>()->stroke()->width() = 3.0f;
+
+        // A lat/lon grid, showing lots of features within a single FeatureNode
+        for (unsigned int i = 0; i < 360; i++)
+        {
+            for (unsigned int j = 0; j < 180; j++)
+            {
+                double w = -180.0 + (double)(i);
+                double e = w + 1.0;
+                double s = -90 + (double)(j);
+                double n = s + 1.0;
+
+                Geometry* geometry = new Polygon();
+                geometry->push_back( w, s );
+                geometry->push_back( e, s );
+                geometry->push_back( e, n );
+                geometry->push_back( w, n );
+
+                Feature*     feature = new Feature(geometry, geoSRS );
+                features.push_back( feature );
+            }
+        }
+
+        FeatureNode* featureNode = new FeatureNode( mapNode, features );
+        featureNode->setStyle( style );
+        annoGroup->addChild( featureNode );
+    }
+    */
     
     //--------------------------------------------------------------------
 
@@ -424,6 +511,21 @@ main(int argc, char** argv)
     viewer.addEventHandler(new osgViewer::StatsHandler());
     viewer.addEventHandler(new osgViewer::WindowSizeHandler());
     viewer.addEventHandler(new osgGA::StateSetManipulator(viewer.getCamera()->getOrCreateStateSet()));
+    
+    viewer.getCamera()->setSmallFeatureCullingPixelSize(-1.0f);
+
+    while (!viewer.done())
+    {
+        if (viewer.getFrameStamp()->getFrameNumber() % 100 == 0)
+        {
+            // Change the color of the great circle path every 100 frames
+            Style pathStyle = pathNode->getStyle();
+            pathStyle.getOrCreate<LineSymbol>()->stroke()->color() = randomColor();
+            pathNode->setStyle( pathStyle );
+        }
+        
+        viewer.frame();
+    }
 
-    return viewer.run();
+    return 0;
 }
diff --git a/src/applications/osgearth_atlas/osgearth_atlas.cpp b/src/applications/osgearth_atlas/osgearth_atlas.cpp
index 060d248..b01d1c0 100644
--- a/src/applications/osgearth_atlas/osgearth_atlas.cpp
+++ b/src/applications/osgearth_atlas/osgearth_atlas.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
@@ -120,6 +123,11 @@ build(osg::ArgumentParser& arguments)
     // the output catalog file describing the texture atlas contents:
     std::string outCatalogFile = osgDB::getSimpleFileName(outImageFile) + ".xml";
 
+    // Whether to build RGB images
+    bool rgb = arguments.read("--rgb");
+    builder.setRGB( rgb );
+    
+
     // auxiliary atlas patterns:
     std::string pattern;
     float r, g, b, a;
diff --git a/src/applications/osgearth_backfill/osgearth_backfill.cpp b/src/applications/osgearth_backfill/osgearth_backfill.cpp
index 3f97d12..afdaed7 100644
--- a/src/applications/osgearth_backfill/osgearth_backfill.cpp
+++ b/src/applications/osgearth_backfill/osgearth_backfill.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/applications/osgearth_boundarygen/BoundaryUtil b/src/applications/osgearth_boundarygen/BoundaryUtil
index 080fcc4..7856b5a 100644
--- a/src/applications/osgearth_boundarygen/BoundaryUtil
+++ b/src/applications/osgearth_boundarygen/BoundaryUtil
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/applications/osgearth_boundarygen/BoundaryUtil.cpp b/src/applications/osgearth_boundarygen/BoundaryUtil.cpp
index e4812ff..74941d1 100644
--- a/src/applications/osgearth_boundarygen/BoundaryUtil.cpp
+++ b/src/applications/osgearth_boundarygen/BoundaryUtil.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/applications/osgearth_boundarygen/VertexCollectionVisitor b/src/applications/osgearth_boundarygen/VertexCollectionVisitor
index b79a07c..a723ba9 100644
--- a/src/applications/osgearth_boundarygen/VertexCollectionVisitor
+++ b/src/applications/osgearth_boundarygen/VertexCollectionVisitor
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/applications/osgearth_boundarygen/VertexCollectionVisitor.cpp b/src/applications/osgearth_boundarygen/VertexCollectionVisitor.cpp
index 6fb8b71..0353825 100644
--- a/src/applications/osgearth_boundarygen/VertexCollectionVisitor.cpp
+++ b/src/applications/osgearth_boundarygen/VertexCollectionVisitor.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/applications/osgearth_boundarygen/boundarygen.cpp b/src/applications/osgearth_boundarygen/boundarygen.cpp
index f4d5e12..c7df676 100644
--- a/src/applications/osgearth_boundarygen/boundarygen.cpp
+++ b/src/applications/osgearth_boundarygen/boundarygen.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/applications/osgearth_cache_test/CMakeLists.txt b/src/applications/osgearth_cache_test/CMakeLists.txt
new file mode 100644
index 0000000..7250791
--- /dev/null
+++ b/src/applications/osgearth_cache_test/CMakeLists.txt
@@ -0,0 +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_cache_test.cpp )
+
+#### end var setup  ###
+SETUP_APPLICATION(osgearth_cache_test)
\ No newline at end of file
diff --git a/src/applications/osgearth_cache_test/osgearth_cache_test.cpp b/src/applications/osgearth_cache_test/osgearth_cache_test.cpp
new file mode 100644
index 0000000..25e6a06
--- /dev/null
+++ b/src/applications/osgearth_cache_test/osgearth_cache_test.cpp
@@ -0,0 +1,96 @@
+/* -*-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.
+*
+* 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/Notify>
+#include <osgEarth/Cache>
+#include <osgEarth/Registry>
+#include <osgEarth/StringUtils>
+#include <osgEarth/ImageUtils>
+#include <osg/ArgumentParser>
+
+#define LC "[cache_test] "
+
+using namespace osgEarth;
+
+
+int
+quit(const std::string& msg)
+{
+    OE_NOTICE << msg << std::endl;
+    return -1;
+}
+
+int
+main(int argc, char** argv)
+{
+    osg::ref_ptr<Cache> cache = Registry::instance()->getCache();
+    if ( !cache.valid() )
+    {
+        return quit( "Please configure a cache path in your environment (OSGEARTH_CACHE_PATH)." );
+    }
+
+    // open a bin:
+    CacheBin* bin = cache->addBin("test_bin");
+    if (!bin)
+        return quit( "Failed to open the cache bin!" );
+
+    // STRING:
+    {
+        std::string value( "What is the sound of one hand clapping?" );
+        osg::ref_ptr<StringObject> s = new StringObject( value );
+
+        if ( !bin->write("string_key", s.get()) )
+            return quit( "String write failed." );
+
+        ReadResult r = bin->readString("string_key");
+        if ( r.failed() )
+            return quit( Stringify() << "String read failed - " << r.getResultCodeString() );
+
+        if ( r.getString().compare(value) != 0 )
+            return quit( "String read error - values do not match" );
+
+        OE_NOTICE << "String test: PASS" << std::endl;
+    }
+
+    // IMAGE:
+    {
+        osg::ref_ptr<osg::Image> image = ImageUtils::createOnePixelImage(osg::Vec4(1,0,0,1));
+
+        if ( !bin->write("image_key", image.get()) )
+            return quit("Image write failed.");
+
+        ReadResult r = bin->readImage("image_key");
+        if ( r.failed() )
+            return quit( Stringify() << "Image read failed - " << r.getResultCodeString() );
+
+        if ( !ImageUtils::areEquivalent(r.getImage(), image.get()) )
+            return quit( "Image read error - images do not match" );
+
+        OE_NOTICE << "Image test: PASS" << std::endl;
+    }
+
+    // Need to properly shut down the cache here
+    Registry::instance()->setCache( 0L );
+
+    OE_NOTICE << "All tests passed." << std::endl;
+    return 0;
+}
diff --git a/src/applications/osgearth_city/osgearth_city.cpp b/src/applications/osgearth_city/osgearth_city.cpp
index 3d9947a..781fe53 100644
--- a/src/applications/osgearth_city/osgearth_city.cpp
+++ b/src/applications/osgearth_city/osgearth_city.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
@@ -87,17 +90,13 @@ main(int argc, char** argv)
     // make the map scene graph:
     MapNode* mapNode = new MapNode( map );
     root->addChild( mapNode );
-    
-    // Process cmdline args.
-    MapNodeHelper helper;
-    helper.configureView( &viewer );
-    helper.parse(mapNode, arguments, &viewer, root, new LabelControl("City Demo"));
 
     // zoom to a good startup position
     manip->setViewpoint( Viewpoint(
+        "Home",
         -71.0763, 42.34425, 0,   // longitude, latitude, altitude
          24.261, -21.6, 3450.0), // heading, pitch, range
-       5.0 );                    // duration
+         5.0 );                    // duration
 
     // This will mitigate near clip plane issues if you zoom in close to the ground:
     LogarithmicDepthBuffer buf;
@@ -155,7 +154,7 @@ void addBuildings(Map* map)
     Style wallStyle;
     wallStyle.setName( "building-wall" );
     SkinSymbol* wallSkin = wallStyle.getOrCreate<SkinSymbol>();
-    wallSkin->libraryName() = "us_resources";
+    wallSkin->library() = "us_resources";
     wallSkin->addTag( "building" );
     wallSkin->randomSeed() = 1;
 
@@ -163,7 +162,7 @@ void addBuildings(Map* map)
     Style roofStyle;
     roofStyle.setName( "building-roof" );
     SkinSymbol* roofSkin = roofStyle.getOrCreate<SkinSymbol>();
-    roofSkin->libraryName() = "us_resources";
+    roofSkin->library() = "us_resources";
     roofSkin->addTag( "rooftop" );
     roofSkin->randomSeed() = 1;
     roofSkin->isTiled() = true;
diff --git a/src/applications/osgearth_clamp/osgearth_clamp.cpp b/src/applications/osgearth_clamp/osgearth_clamp.cpp
index 185fab6..6218371 100644
--- a/src/applications/osgearth_clamp/osgearth_clamp.cpp
+++ b/src/applications/osgearth_clamp/osgearth_clamp.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
@@ -160,7 +163,7 @@ main(int argc, char** argv)
         mapNode->getTerrain()->addTerrainCallback( new ClampObjectLocatorCallback(locator) );        
     }    
     
-    manip->setHomeViewpoint(Viewpoint( "Mt Rainier",        osg::Vec3d(    centerLon,   centerLat, 0.0 ), 0.0, -90, 45000 ));
+    manip->setHomeViewpoint(Viewpoint("Home", centerLon, centerLat, 0.0, 0.0, -90, 45000 ));
 
     viewer.setSceneData( root );    
 
diff --git a/src/applications/osgearth_clipplane/osgearth_clipplane.cpp b/src/applications/osgearth_clipplane/osgearth_clipplane.cpp
index 457023a..cf6b5af 100644
--- a/src/applications/osgearth_clipplane/osgearth_clipplane.cpp
+++ b/src/applications/osgearth_clipplane/osgearth_clipplane.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
@@ -107,7 +110,7 @@ main(int argc, char** argv)
 
         // We also need a shader that will activate clipping in GLSL.
         VirtualProgram* vp = VirtualProgram::getOrCreate(root->getOrCreateStateSet());
-        vp->setFunction("oe_clip_vert", clipvs, ShaderComp::LOCATION_VERTEX_VIEW);
+        vp->setFunction("oe_clip_vert", clipvs, ShaderComp::LOCATION_VERTEX_VIEW, 0.5f);
 
         // Now everything is set up. The last thing to do is: anywhere in your
         // scene graph that you want to activate the clipping plane, set the 
diff --git a/src/applications/osgearth_colorfilter/osgearth_colorfilter.cpp b/src/applications/osgearth_colorfilter/osgearth_colorfilter.cpp
index bb137fa..460a22b 100644
--- a/src/applications/osgearth_colorfilter/osgearth_colorfilter.cpp
+++ b/src/applications/osgearth_colorfilter/osgearth_colorfilter.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/applications/osgearth_controls/osgearth_controls.cpp b/src/applications/osgearth_controls/osgearth_controls.cpp
index eadd9b0..eff9e95 100644
--- a/src/applications/osgearth_controls/osgearth_controls.cpp
+++ b/src/applications/osgearth_controls/osgearth_controls.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/applications/osgearth_conv/osgearth_conv.cpp b/src/applications/osgearth_conv/osgearth_conv.cpp
index b486e53..c0f7c2c 100644
--- a/src/applications/osgearth_conv/osgearth_conv.cpp
+++ b/src/applications/osgearth_conv/osgearth_conv.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/applications/osgearth_createtile/CMakeLists.txt b/src/applications/osgearth_createtile/CMakeLists.txt
new file mode 100644
index 0000000..c085a03
--- /dev/null
+++ b/src/applications/osgearth_createtile/CMakeLists.txt
@@ -0,0 +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_createtile.cpp )
+
+#### end var setup  ###
+SETUP_APPLICATION(osgearth_createtile)
diff --git a/src/applications/osgearth_createtile/osgearth_createtile.cpp b/src/applications/osgearth_createtile/osgearth_createtile.cpp
new file mode 100644
index 0000000..5eac5fb
--- /dev/null
+++ b/src/applications/osgearth_createtile/osgearth_createtile.cpp
@@ -0,0 +1,243 @@
+/* -*-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/>
+*/
+
+/**
+ * This app demonstrates the use of TerrainEngine::createTile(), which lets
+ * you create the geometry for an arbitrary terrain tile that you can use for
+ * external purposes.
+ */
+
+#include <osgGA/StateSetManipulator>
+#include <osgGA/GUIEventHandler>
+#include <osgViewer/Viewer>
+#include <osgViewer/ViewerEventHandlers>
+#include <osgUtil/LineSegmentIntersector>
+#include <osgEarth/MapNode>
+#include <osgEarth/TerrainEngineNode>
+#include <osgEarth/ElevationQuery>
+#include <osgEarth/StringUtils>
+#include <osgEarth/Terrain>
+#include <osgEarthUtil/EarthManipulator>
+#include <osgEarthUtil/Controls>
+#include <osgEarthUtil/LatLongFormatter>
+#include <osg/TriangleFunctor>
+#include <osgDB/WriteFile>
+#include <iomanip>
+
+using namespace osgEarth;
+using namespace osgEarth::Util;
+
+static MapNode*       s_mapNode     = 0L;
+static osg::Group*    s_root        = 0L;
+
+struct CollectTriangles
+{
+    CollectTriangles()
+    {
+        verts = new osg::Vec3Array();
+    }
+    inline void operator () (const osg::Vec3& v1,const osg::Vec3& v2,const osg::Vec3& v3, bool treatVertexDataAsTemporary)
+    {
+        verts->push_back(v1);
+        verts->push_back(v2);
+        verts->push_back(v3);
+    }
+
+    osg::ref_ptr< osg::Vec3Array > verts;
+};
+
+struct CollectTrianglesVisitor : public osg::NodeVisitor
+{
+    CollectTrianglesVisitor():
+        osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN)
+    {
+        _vertices = new osg::Vec3dArray();
+    }
+
+    void apply(osg::Transform& transform)
+    {
+        osg::Matrix matrix;
+        if (!_matrixStack.empty()) matrix = _matrixStack.back();
+
+        transform.computeLocalToWorldMatrix(matrix,this);
+
+        pushMatrix(matrix);
+
+        traverse(transform);
+
+        popMatrix();
+    }
+
+    void apply(osg::Geode& geode)
+    {
+        for(unsigned int i=0; i<geode.getNumDrawables(); ++i)
+        {
+            osg::TriangleFunctor<CollectTriangles> triangleCollector;
+            geode.getDrawable(i)->accept(triangleCollector);
+            for (unsigned int j = 0; j < triangleCollector.verts->size(); j++)
+            {
+                osg::Matrix& matrix = _matrixStack.back();
+                _vertices->push_back((*triangleCollector.verts)[j] * matrix);
+            }
+        }
+    }
+
+    osg::Node* buildNode()
+    {
+        osg::Geometry* geom = new osg::Geometry;
+        osg::Vec3Array* verts = new osg::Vec3Array;
+        geom->setVertexArray(verts);
+        osg::Vec4ubArray* colors = new osg::Vec4ubArray(1);
+        (*colors)[0] = osg::Vec4ub(255,0,0,255);
+        geom->setColorArray(colors);
+        geom->setColorBinding(osg::Geometry::BIND_OVERALL);
+
+        bool first = true;
+        osg::Vec3d anchor;
+
+        for (unsigned int i = 0; i < _vertices->size(); i++)
+        {
+            if (first)
+            {
+                anchor = (*_vertices)[i];
+                first = false;
+            }
+
+            verts->push_back((*_vertices)[i] - anchor);
+        }
+
+        OSG_NOTICE << "Building scene with " << verts->size() << " verts" << std::endl;
+
+        osg::MatrixTransform* mt = new osg::MatrixTransform;
+        mt->setMatrix(osg::Matrixd::translate(anchor));
+
+        osg::Geode* geode = new osg::Geode;
+        geode->addDrawable(geom);
+        geom->addPrimitiveSet(new osg::DrawArrays(GL_TRIANGLES, 0, verts->size()));
+        mt->addChild(geode);
+        mt->getOrCreateStateSet()->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF);
+        mt->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
+        return mt;
+    }
+
+    inline void pushMatrix(osg::Matrix& matrix) { _matrixStack.push_back(matrix); }
+
+    inline void popMatrix() { _matrixStack.pop_back(); }
+
+    typedef std::vector<osg::Matrix> MatrixStack;
+    osg::ref_ptr<osg::Vec3dArray>  _vertices;
+    MatrixStack _matrixStack;
+};
+
+
+
+
+
+// An event handler that will create a tile that can be used for intersections
+struct CreateTileHandler : public osgGA::GUIEventHandler
+{
+    CreateTileHandler()
+    {
+    }
+
+    void update( float x, float y, osgViewer::View* view )
+    {
+        bool yes = false;
+
+        // look under the mouse:
+        osg::Vec3d world;
+        osgUtil::LineSegmentIntersector::Intersections hits;
+        if ( view->computeIntersections(x, y, hits) )
+        {
+            world = hits.begin()->getWorldIntersectPoint();
+
+            // convert to map coords:
+            GeoPoint mapPoint;
+            mapPoint.fromWorld( s_mapNode->getMapSRS(), world );
+
+            TileKey key = s_mapNode->getMap()->getProfile()->createTileKey(mapPoint.x(), mapPoint.y(), 13);
+            OE_NOTICE << "Creating tile " << key.str() << std::endl;
+            osg::ref_ptr<osg::Node> node = s_mapNode->getTerrainEngine()->createTile(key);
+            if (node.valid())
+            {
+                OE_NOTICE << "Created tile for " << key.str() << std::endl;
+                CollectTrianglesVisitor v;
+                node->accept(v);
+
+                osg::ref_ptr<osg::Node> output = v.buildNode();
+                osgDB::writeNodeFile( *output.get(), "createtile.osgt" );
+                OE_NOTICE << "Wrote tile to createtile.osgt\n";
+                //osgDB::writeNodeFile(v.buildNode(
+                //s_root->addChild(v.buildNode());
+            }
+            else
+            {
+                OE_NOTICE << "Failed to create tile for " << key.str() << std::endl;
+            }
+        }
+    }
+
+    bool handle( const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa )
+    {
+        if (ea.getEventType() == osgGA::GUIEventAdapter::PUSH && ea.getButton() == osgGA::GUIEventAdapter::LEFT_MOUSE_BUTTON)
+        {
+            osgViewer::View* view = static_cast<osgViewer::View*>(aa.asView());
+            update( ea.getX(), ea.getY(), view );
+        }
+
+        return false;
+    }
+};
+
+
+int main(int argc, char** argv)
+{
+    osg::ArgumentParser arguments(&argc,argv);
+
+    osgViewer::Viewer viewer(arguments);
+
+    s_mapNode = MapNode::load(arguments);
+    if ( !s_mapNode )
+    {
+        OE_WARN << "Unable to load earth file." << std::endl;
+        return -1;
+    }
+
+    s_root = new osg::Group();
+    viewer.setSceneData( s_root );
+
+    // install the programmable manipulator.
+    viewer.setCameraManipulator( new osgEarth::Util::EarthManipulator() );
+
+    // The MapNode will render the Map object in the scene graph.
+    s_root->addChild( s_mapNode );
+
+    // An event handler that will respond to mouse clicks:
+    viewer.addEventHandler( new CreateTileHandler() );
+
+    // 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_demo/osgearth_demo.cpp b/src/applications/osgearth_demo/osgearth_demo.cpp
index 5bb4579..989d661 100644
--- a/src/applications/osgearth_demo/osgearth_demo.cpp
+++ b/src/applications/osgearth_demo/osgearth_demo.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/applications/osgearth_elevation/osgearth_elevation.cpp b/src/applications/osgearth_elevation/osgearth_elevation.cpp
index 67a4097..fcb4144 100644
--- a/src/applications/osgearth_elevation/osgearth_elevation.cpp
+++ b/src/applications/osgearth_elevation/osgearth_elevation.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
@@ -30,6 +33,7 @@
 #include <osgEarthUtil/EarthManipulator>
 #include <osgEarthUtil/Controls>
 #include <osgEarthUtil/LatLongFormatter>
+#include <osgEarthUtil/ExampleResources>
 #include <iomanip>
 
 using namespace osgEarth;
@@ -55,6 +59,7 @@ struct QueryElevationHandler : public osgGA::GUIEventHandler
     {
         _map = s_mapNode->getMap();
         _query.setMaxTilesToCache(10);
+        _query.setFallBackOnNoData( false );
         _path.push_back( s_mapNode->getTerrainEngine() );
     }
 
@@ -94,9 +99,9 @@ struct QueryElevationHandler : public osgGA::GUIEventHandler
 
                 s_posLabel->setText( Stringify()
                     << std::fixed << std::setprecision(2) 
-                    << s_f.format(mapPointGeodetic.y())
+                    << s_f.format(mapPointGeodetic.y(), true)
                     << ", " 
-                    << s_f.format(mapPointGeodetic.x()) );
+                    << s_f.format(mapPointGeodetic.x(), false) );
 
                 s_mslLabel->setText( Stringify() << out_hamsl );
                 s_haeLabel->setText( Stringify() << mapPointGeodetic.z() );
@@ -146,7 +151,11 @@ int main(int argc, char** argv)
 
     osgViewer::Viewer viewer(arguments);
 
-    s_mapNode = MapNode::load(arguments);
+    s_mapNode = 0L;
+    osg::Node* earthFile = MapNodeHelper().load(arguments, &viewer);
+    if (earthFile)
+        s_mapNode = MapNode::get(earthFile);
+
     if ( !s_mapNode )
     {
         OE_WARN << "Unable to load earth file." << std::endl;
@@ -160,7 +169,7 @@ int main(int argc, char** argv)
     viewer.setCameraManipulator( new osgEarth::Util::EarthManipulator() );
 
     // The MapNode will render the Map object in the scene graph.
-    root->addChild( s_mapNode );
+    root->addChild( earthFile );
 
     // Make the readout:
     Grid* grid = new Grid();
@@ -183,8 +192,7 @@ int main(int argc, char** argv)
         mapSRS->getVerticalDatum()->getName() : 
         Stringify() << "geodetic (" << mapSRS->getEllipsoid()->getName() << ")" );
 
-    ControlCanvas* canvas = new ControlCanvas();    
-    root->addChild(canvas);
+    ControlCanvas* canvas = ControlCanvas::get(&viewer);
     canvas->addControl( grid );
 
     // An event handler that will respond to mouse clicks:
diff --git a/src/applications/osgearth_featureeditor/osgearth_featureeditor.cpp b/src/applications/osgearth_featureeditor/osgearth_featureeditor.cpp
index 0ce3033..070fcfa 100644
--- a/src/applications/osgearth_featureeditor/osgearth_featureeditor.cpp
+++ b/src/applications/osgearth_featureeditor/osgearth_featureeditor.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
@@ -65,6 +68,7 @@ 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()
@@ -91,7 +95,7 @@ struct AddVertsModeHandler : public ControlEventHandler
         //remove the editor if it's valid
         if (s_editor.valid())
         {
-            s_root->removeChild( s_editor.get() );
+            s_editorsRoot->removeChild( s_editor.get() );
             s_editor = NULL;
 
             // Unset the stipple on the line
@@ -131,7 +135,7 @@ struct EditModeHandler : public ControlEventHandler
             style.getOrCreate<LineSymbol>()->stroke()->stipple() = 0x00FF;            
             s_featureNode->setStyle( style );            
             s_editor = new FeatureEditor( s_featureNode );
-            s_root->addChild( s_editor.get() );            
+            s_editorsRoot->addChild( s_editor.get() );            
         }
     }    
 };
@@ -211,11 +215,14 @@ int main(int argc, char** argv)
     Feature* feature = new Feature(line, 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() );
 
-    s_root->addChild( s_featureNode );
 
     //Setup the controls
     ControlCanvas* canvas = ControlCanvas::getOrCreate( &viewer );
diff --git a/src/applications/osgearth_featurefilter/osgearth_featurefilter.cpp b/src/applications/osgearth_featurefilter/osgearth_featurefilter.cpp
index de3115c..f3c5f51 100644
--- a/src/applications/osgearth_featurefilter/osgearth_featurefilter.cpp
+++ b/src/applications/osgearth_featurefilter/osgearth_featurefilter.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
@@ -102,6 +105,8 @@ main(int argc, char** argv)
         // configure the near/far so we don't clip things that are up close
         viewer.getCamera()->setNearFarRatio(0.00002);
 
+        viewer.getCamera()->setSmallFeatureCullingPixelSize(-1.0f);
+
         viewer.run();
     }
     else
diff --git a/src/applications/osgearth_featureinfo/osgearth_featureinfo.cpp b/src/applications/osgearth_featureinfo/osgearth_featureinfo.cpp
index 6d99d37..fbeead7 100644
--- a/src/applications/osgearth_featureinfo/osgearth_featureinfo.cpp
+++ b/src/applications/osgearth_featureinfo/osgearth_featureinfo.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/applications/osgearth_featurequery/osgearth_featurequery.cpp b/src/applications/osgearth_featurequery/osgearth_featurequery.cpp
index 6da05e2..5f21cd1 100644
--- a/src/applications/osgearth_featurequery/osgearth_featurequery.cpp
+++ b/src/applications/osgearth_featurequery/osgearth_featurequery.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,21 +8,26 @@
 * 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.
+* 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/ShaderLoader>
+#include <osgEarth/VirtualProgram>
+#include <osgEarth/Registry>
+#include <osgEarth/ObjectIndex>
 #include <osgEarthUtil/EarthManipulator>
 #include <osgEarthUtil/ExampleResources>
 #include <osgEarthUtil/Controls>
@@ -33,18 +38,78 @@
 using namespace osgEarth::Util;
 using namespace osgEarth::Util::Controls;
 
-//------------------------------------------------------------------------
-// creaes a simple user interface for the manip demo
-Control*
-createUI()
+//-----------------------------------------------------------------------
+
+/**
+ * Creates a simple user interface for the demo.
+ */
+Container* createUI()
 {
     VBox* vbox = new VBox();
     vbox->setVertAlign( Control::ALIGN_TOP );
-    vbox->setHorizAlign( Control::ALIGN_LEFT );
+    vbox->setHorizAlign( Control::ALIGN_RIGHT );
     vbox->addControl( new LabelControl("Feature Query Demo", Color::Yellow) );
     vbox->addControl( new LabelControl("Click on a feature to see its attributes.") );
     return vbox;
-} 
+}
+
+//-----------------------------------------------------------------------
+
+/**
+ * Query Callback that displays the targeted feature's attributes in a
+ * user interface grid control.
+ */
+
+class ReadoutCallback : public FeatureQueryTool::Callback
+{
+public:
+    ReadoutCallback(ControlCanvas* container) : _lastFID( ~0 )
+    {
+        _grid = new Grid();
+        _grid->setBackColor( Color(Color::Black,0.7f) );
+        container->addControl( _grid );
+    }
+
+    void onHit(ObjectID id)
+    {
+        FeatureIndex* index = Registry::objectIndex()->get<FeatureIndex>( id );
+        Feature* feature = index ? index->getFeature( id ) : 0L;
+        if ( feature && feature->getFID() != _lastFID )
+        {
+            _grid->clearControls();
+            unsigned r=0;
+
+            _grid->setControl( 0, r, new LabelControl("FID", Color::Red) );
+            _grid->setControl( 1, r, new LabelControl(Stringify()<<feature->getFID(), Color::White) );
+            ++r;
+
+            const AttributeTable& attrs = feature->getAttrs();
+            for( AttributeTable::const_iterator i = attrs.begin(); i != attrs.end(); ++i, ++r )
+            {
+                _grid->setControl( 0, r, new LabelControl(i->first, 14.0f, Color::Yellow) );
+                _grid->setControl( 1, r, new LabelControl(i->second.getString(), 14.0f, Color::White) );
+            }
+            if ( !_grid->visible() )
+                _grid->setVisible( true );
+        
+            _lastFID = feature->getFID();
+        }
+    }
+
+    void onMiss()
+    {
+        _grid->setVisible(false);
+        _lastFID = 0u;
+    }
+
+    bool accept(const osgGA::GUIEventAdapter& ea, const osgGA::GUIActionAdapter& aa) 
+    {
+        return ea.getEventType() == ea.RELEASE; // click
+    }
+
+    Grid*     _grid;
+    FeatureID _lastFID;
+};
 
 //------------------------------------------------------------------------
 
@@ -52,8 +117,6 @@ 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);
@@ -68,27 +131,17 @@ main(int argc, char** argv)
     {
         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 )
         {
-            FeatureQueryTool* tool = new FeatureQueryTool( mapNode );
+            // Install the query tool.
+            FeatureQueryTool* tool = new FeatureQueryTool();
             viewer.addEventHandler( tool );
+            tool->addChild( mapNode );
 
-            VBox* readout = ControlCanvas::getOrCreate(&viewer)->addControl( new VBox() );
-            readout->setHorizAlign( Control::ALIGN_RIGHT );
-            readout->setBackColor( Color(Color::Black,0.8) );
-            tool->addCallback( new FeatureReadoutCallback(readout) );
-            tool->addCallback( new FeatureHighlightCallback() );
+            // Install a readout for feature metadata.
+            ControlCanvas* canvas = ControlCanvas::getOrCreate(&viewer);
+            tool->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 e666ae1..2bb1ed8 100644
--- a/src/applications/osgearth_features/osgearth_features.cpp
+++ b/src/applications/osgearth_features/osgearth_features.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/applications/osgearth_fog/osgearth_fog.cpp b/src/applications/osgearth_fog/osgearth_fog.cpp
index e2ceacf..2873dae 100644
--- a/src/applications/osgearth_fog/osgearth_fog.cpp
+++ b/src/applications/osgearth_fog/osgearth_fog.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/applications/osgearth_graticule/osgearth_graticule.cpp b/src/applications/osgearth_graticule/osgearth_graticule.cpp
index 1fa0306..ab31968 100644
--- a/src/applications/osgearth_graticule/osgearth_graticule.cpp
+++ b/src/applications/osgearth_graticule/osgearth_graticule.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
@@ -31,6 +34,7 @@
 #include <osgEarthUtil/GeodeticGraticule>
 #include <osgEarthUtil/MGRSGraticule>
 #include <osgEarthUtil/UTMGraticule>
+#include <osgEarthUtil/GraticuleNode>
 
 using namespace osgEarth::Util;
 
@@ -42,12 +46,43 @@ usage( const std::string& msg )
         << "USAGE: osgearth_graticule [options] file.earth" << std::endl
         << "   --geodetic            : display a geodetic (lat/long) graticule" << std::endl
         << "   --utm                 : display a UTM graticule" << std::endl
-        << "   --mgrs                : display an MGRS graticule" << std::endl;        
+        << "   --mgrs                : display an MGRS graticule" << std::endl
+        << "   --shader              : display a geodetic graticule using the glsl shaders" << 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)
 {
@@ -57,7 +92,9 @@ main(int argc, char** argv)
     // parse command line:
     bool isUTM = arguments.read("--utm");
     bool isMGRS = arguments.read("--mgrs");
-    bool isGeodetic = !isUTM && !isMGRS;
+    bool isGeodetic = arguments.read("--geodetic");
+
+    bool isShader = !isUTM && !isMGRS && !isGeodetic;
 
     // load the .earth file from the command line.
     MapNode* mapNode = MapNode::load( arguments );
@@ -71,6 +108,8 @@ main(int argc, char** argv)
     osg::Group* root = new osg::Group();
     root->addChild( mapNode );
 
+    GraticuleNode* graticuleNode = 0;
+
     Formatter* formatter = 0L;
     if ( isUTM )
     {
@@ -84,7 +123,7 @@ main(int argc, char** argv)
         root->addChild( gr );
         formatter = new MGRSFormatter();
     }
-    else // if ( isGeodetic )
+    else if ( isGeodetic )
     {
         GeodeticGraticule* gr = new GeodeticGraticule( mapNode );
         GeodeticGraticuleOptions o = gr->getOptions();
@@ -93,13 +132,63 @@ main(int argc, char** argv)
         root->addChild( gr );
         formatter = new LatLongFormatter();
     }
+    else
+    {
+        graticuleNode = new GraticuleNode( mapNode );
+        root->addChild( graticuleNode );
+    }
 
+   
     // mouse coordinate readout:
     ControlCanvas* canvas = new ControlCanvas();
     root->addChild( canvas );
+    VBox* vbox = new VBox();
+    canvas->addControl( vbox );
+
 
     LabelControl* readout = new LabelControl();
-    canvas->addControl( readout );
+    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) );
diff --git a/src/applications/osgearth_imageoverlay/osgearth_imageoverlay.cpp b/src/applications/osgearth_imageoverlay/osgearth_imageoverlay.cpp
index 5db8eae..6ebee99 100644
--- a/src/applications/osgearth_imageoverlay/osgearth_imageoverlay.cpp
+++ b/src/applications/osgearth_imageoverlay/osgearth_imageoverlay.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/applications/osgearth_los/osgearth_los.cpp b/src/applications/osgearth_los/osgearth_los.cpp
index 6ca4386..751d007 100644
--- a/src/applications/osgearth_los/osgearth_los.cpp
+++ b/src/applications/osgearth_los/osgearth_los.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
@@ -30,6 +33,7 @@
 #include <osgEarthUtil/RadialLineOfSight>
 #include <osg/io_utils>
 #include <osg/MatrixTransform>
+#include <osg/Depth>
 
 using namespace osgEarth;
 using namespace osgEarth::Util;
@@ -126,7 +130,12 @@ main(int argc, char** argv)
     viewer.setCameraManipulator( manip );
     
     root->addChild( earthNode );    
-    viewer.getCamera()->addCullCallback( new AutoClipPlaneCullCallback(mapNode));
+    //viewer.getCamera()->addCullCallback( new AutoClipPlaneCullCallback(mapNode));
+
+    osg::Group* losGroup = new osg::Group();
+    losGroup->getOrCreateStateSet()->setRenderingHint(osg::StateSet::TRANSPARENT_BIN);
+    losGroup->getOrCreateStateSet()->setAttributeAndModes(new osg::Depth(osg::Depth::ALWAYS, 0, 1, false));
+    root->addChild(losGroup);
 
     // so we can speak lat/long:
     const SpatialReference* mapSRS = mapNode->getMapSRS();
@@ -138,9 +147,7 @@ main(int argc, char** argv)
         GeoPoint(geoSRS, -121.665, 46.0878, 1258.00, ALTMODE_ABSOLUTE),
         GeoPoint(geoSRS, -121.488, 46.2054, 3620.11, ALTMODE_ABSOLUTE) );
 
-    root->addChild( los );
-    los->getOrCreateStateSet()->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF);
-
+    losGroup->addChild( los );
     
     //Create an editor for the point to point line of sight that allows you to drag the beginning and end points around.
     //This is just one way that you could manipulator the LineOfSightNode.
@@ -153,8 +160,7 @@ main(int argc, char** argv)
         GeoPoint(geoSRS, -121.2, 46.1, 10, ALTMODE_RELATIVE),
         GeoPoint(geoSRS, -121.488, 46.2054, 10, ALTMODE_RELATIVE) );
 
-    root->addChild( relativeLOS );
-    relativeLOS->getOrCreateStateSet()->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF);
+    losGroup->addChild( relativeLOS );
 
     LinearLineOfSightEditor* relEditor = new LinearLineOfSightEditor( relativeLOS );
     root->addChild( relEditor );
@@ -164,23 +170,21 @@ main(int argc, char** argv)
     radial->setCenter( GeoPoint(geoSRS, -121.515, 46.054, 847.604, ALTMODE_ABSOLUTE) );
     radial->setRadius( 2000 );
     radial->setNumSpokes( 100 );    
-    radial->getOrCreateStateSet()->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF);
-    root->addChild( radial );
+    losGroup->addChild( radial );
     RadialLineOfSightEditor* radialEditor = new RadialLineOfSightEditor( radial );
-    root->addChild( radialEditor );
+    losGroup->addChild( radialEditor );
 
     //Create a relative RadialLineOfSightNode that allows you to do a 360 degree line of sight analysis.
     RadialLineOfSightNode* radialRelative = new RadialLineOfSightNode( mapNode );
     radialRelative->setCenter( GeoPoint(geoSRS, -121.2, 46.054, 10, ALTMODE_RELATIVE) );
     radialRelative->setRadius( 3000 );
     radialRelative->setNumSpokes(60);    
-    radialRelative->getOrCreateStateSet()->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF);
-    root->addChild( radialRelative );
+    losGroup->addChild( radialRelative );
     RadialLineOfSightEditor* radialRelEditor = new RadialLineOfSightEditor( radialRelative );
-    root->addChild( radialRelEditor );
+    losGroup->addChild( radialRelEditor );
 
     //Load a plane model.  
-    osg::ref_ptr< osg::Node >  plane = osgDB::readNodeFile("../data/cessna.osg.5,5,5.scale");
+    osg::ref_ptr< osg::Node >  plane = osgDB::readNodeFile("../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);
@@ -191,15 +195,13 @@ main(int argc, char** argv)
     //Create a LineOfSightNode that will use a LineOfSightTether callback to monitor
     //the two plane's positions and recompute the LOS when they move
     LinearLineOfSightNode* tetheredLOS = new LinearLineOfSightNode( mapNode);
-    tetheredLOS->getOrCreateStateSet()->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF);
-    root->addChild( tetheredLOS );
+    losGroup->addChild( tetheredLOS );
     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);
-    root->addChild( plane3 );
-    RadialLineOfSightNode* tetheredRadial = new RadialLineOfSightNode( mapNode );    
-    tetheredRadial->getOrCreateStateSet()->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF);    
+    losGroup->addChild( plane3 );
+    RadialLineOfSightNode* tetheredRadial = new RadialLineOfSightNode( mapNode );
     tetheredRadial->setRadius( 5000 );
 
     //This RadialLineOfSightNode is going to be filled, so set some alpha values for the colors so it's partially transparent
@@ -207,14 +209,16 @@ main(int argc, char** argv)
     tetheredRadial->setGoodColor( osg::Vec4(0,1,0,0.3) );
     tetheredRadial->setBadColor( osg::Vec4(1,0,0,0.3) );
     tetheredRadial->setNumSpokes( 100 );
-    root->addChild( tetheredRadial );
+    losGroup->addChild( tetheredRadial );
     tetheredRadial->setUpdateCallback( new RadialLineOfSightTether( plane3 ) );
 
-    manip->setHomeViewpoint( Viewpoint( 
-        "Mt Rainier",        
-        osg::Vec3d( -121.488, 46.2054, 0 ), 
-        0.0, -50, 100000,
-        geoSRS) );
+    osgEarth::Viewpoint vp;
+    vp.name() = "Mt Ranier";
+    vp.focalPoint()->set(geoSRS, -121.488, 46.2054, 0, ALTMODE_ABSOLUTE);
+    vp.pitch() = -50.0;
+    vp.range() = 100000;
+
+    manip->setHomeViewpoint( vp );
 
     viewer.setSceneData( root );    
 
diff --git a/src/applications/osgearth_manip/osgearth_manip.cpp b/src/applications/osgearth_manip/osgearth_manip.cpp
index 6a7ef1d..41589df 100644
--- a/src/applications/osgearth_manip/osgearth_manip.cpp
+++ b/src/applications/osgearth_manip/osgearth_manip.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
@@ -36,6 +39,7 @@
 #include <osgEarthUtil/AutoClipPlaneHandler>
 #include <osgEarthUtil/Controls>
 #include <osgEarthUtil/ExampleResources>
+#include <osgEarthUtil/LogarithmicDepthBuffer>
 #include <osgEarthAnnotation/AnnotationUtils>
 #include <osgEarthAnnotation/LabelNode>
 #include <osgEarthSymbology/Style>
@@ -50,9 +54,25 @@ using namespace osgEarth::Annotation;
 namespace
 {
     /**
+     * Tether callback test.
+     */
+    struct TetherCB : public EarthManipulator::TetherCallback
+    {
+        void operator()(osg::Node* node)
+        {
+            if ( node ) {
+                OE_WARN << "Tether on\n";
+            }
+            else {
+                OE_WARN << "Tether off\n";
+            }
+        }
+    };
+
+    /**
      * Builds our help menu UI.
      */
-    Control* createHelp( osgViewer::View* view )
+    Container* createHelp( osgViewer::View* view )
     {
         const char* text[] =
         {
@@ -65,17 +85,23 @@ namespace
             "1-6 :",               "fly to preset viewpoints",
             "shift-right-mouse :", "locked panning",
             "u :",                 "toggle azimuth lock",
-            "c :",                 "toggle perspective/ortho",
-            "t :",                 "toggle tethering",
+            "o :",                 "toggle perspective/ortho",
+            "8 :",                 "Tether to thing 1",
+            "9 :",                 "Tether to thing 2",
+            "t :",                 "cycle tethermode",
+            "b :",                 "break tether",
             "a :",                 "toggle viewpoint arcing",
-            "z :",                 "toggle throwing"
+            "z :",                 "toggle throwing",
+            "k :",                 "toggle collision",
+            "L :",                 "toggle log depth buffer"
         };
 
         Grid* g = new Grid();
-        for( unsigned i=0; i<sizeof(text)/sizeof(text[0]); ++i )
+        unsigned i, c, r;
+        for( i=0; i<sizeof(text)/sizeof(text[0]); ++i )
         {
-            unsigned c = i % 2;
-            unsigned r = i / 2;
+            c = i % 2;
+            r = i / 2;
             g->setControl( c, r, new LabelControl(text[i]) );
         }
 
@@ -90,12 +116,12 @@ namespace
      * Some preset viewpoints to show off the setViewpoint function.
      */
     static Viewpoint VPs[] = {
-        Viewpoint( "Africa",        osg::Vec3d(    0.0,   0.0, 0.0 ), 0.0, -90.0, 10e6 ),
-        Viewpoint( "California",    osg::Vec3d( -121.0,  34.0, 0.0 ), 0.0, -90.0, 6e6 ),
-        Viewpoint( "Europe",        osg::Vec3d(    0.0,  45.0, 0.0 ), 0.0, -90.0, 4e6 ),
-        Viewpoint( "Washington DC", osg::Vec3d(  -77.0,  38.0, 0.0 ), 0.0, -90.0, 1e6 ),
-        Viewpoint( "Australia",     osg::Vec3d(  135.0, -20.0, 0.0 ), 0.0, -90.0, 2e6 ),
-        Viewpoint( "Boston",        osg::Vec3d( -71.096936, 42.332771, 0 ), 0.0, -90, 1e5 )
+        Viewpoint( "Africa",            0.0,   0.0, 0.0, 0.0, -90.0, 10e6 ),
+        Viewpoint( "California",     -121.0,  34.0, 0.0, 0.0, -90.0, 6e6 ),
+        Viewpoint( "Europe",            0.0,  45.0, 0.0, 0.0, -90.0, 4e6 ),
+        Viewpoint( "Washington DC",   -77.0,  38.0, 0.0, 0.0, -90.0, 1e6 ),
+        Viewpoint( "Australia",       135.0, -20.0, 0.0, 0.0, -90.0, 2e6 ),
+        Viewpoint( "Boston",         -71.096936, 42.332771, 0, 0.0, -90, 1e5 )
     };
 
 
@@ -119,7 +145,48 @@ namespace
 
         osg::observer_ptr<EarthManipulator> _manip;
     };
+    
 
+    /**
+     * Toggles the logarithmic depth buffer
+     */
+    struct ToggleLDB : public osgGA::GUIEventHandler
+    {
+        ToggleLDB(char key) : _key(key), _installed(false) { }
+
+        bool handle(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa)
+        {
+            if (ea.getEventType() == ea.KEYDOWN && ea.getKey() == _key)
+            {
+                if ( !_installed )
+                {
+                    _nfratio = aa.asView()->getCamera()->getNearFarRatio();
+                    _ldb.install(aa.asView()->getCamera());
+                    aa.asView()->getCamera()->setNearFarRatio(0.00001);
+                }
+                else
+                {
+                    _ldb.uninstall(aa.asView()->getCamera());
+                    aa.asView()->getCamera()->setNearFarRatio(_nfratio);
+                }
+
+                _installed = !_installed;
+                return true;
+            }
+            return false;
+        }
+
+        void getUsage(osg::ApplicationUsage& usage) const
+        {
+            using namespace std;
+            usage.addKeyboardMouseBinding(string(1, _key), string("Toggle LDB"));
+        }
+
+        char _key;
+        float _nfratio;
+        bool _installed;
+        osgEarth::Util::LogarithmicDepthBuffer _ldb;
+    };
 
     /**
      * Handler to toggle "azimuth locking", which locks the camera's relative Azimuth
@@ -187,11 +254,11 @@ namespace
 
 
     /**
-     * Toggles the projection matrix between perspective and orthographic.
+     * Toggles the throwing feature.
      */
-    struct ToggleProjectionHandler : public osgGA::GUIEventHandler
+    struct ToggleThrowingHandler : public osgGA::GUIEventHandler
     {
-        ToggleProjectionHandler(char key, EarthManipulator* manip)
+        ToggleThrowingHandler(char key, EarthManipulator* manip)
             : _key(key), _manip(manip)
         {
         }
@@ -200,10 +267,8 @@ namespace
         {
             if (ea.getEventType() == ea.KEYDOWN && ea.getKey() == _key)
             {
-                if ( _manip->getSettings()->getCameraProjection() == EarthManipulator::PROJ_PERSPECTIVE )
-                    _manip->getSettings()->setCameraProjection( EarthManipulator::PROJ_ORTHOGRAPHIC );
-                else
-                    _manip->getSettings()->setCameraProjection( EarthManipulator::PROJ_PERSPECTIVE );
+                bool throwing = _manip->getSettings()->getThrowingEnabled();
+                _manip->getSettings()->setThrowingEnabled( !throwing );
                 aa.requestRedraw();
                 return true;
             }
@@ -213,20 +278,19 @@ namespace
         void getUsage(osg::ApplicationUsage& usage) const
         {
             using namespace std;
-            usage.addKeyboardMouseBinding(string(1, _key), string("Toggle projection type"));
+            usage.addKeyboardMouseBinding(string(1, _key), string("Toggle throwing"));
         }
 
         char _key;
         osg::ref_ptr<EarthManipulator> _manip;
     };
 
-
     /**
-     * Toggles the throwing feature.
+     * Toggles the collision feature.
      */
-    struct ToggleThrowingHandler : public osgGA::GUIEventHandler
+    struct ToggleCollisionHandler : public osgGA::GUIEventHandler
     {
-        ToggleThrowingHandler(char key, EarthManipulator* manip)
+        ToggleCollisionHandler(char key, EarthManipulator* manip)
             : _key(key), _manip(manip)
         {
         }
@@ -235,8 +299,8 @@ namespace
         {
             if (ea.getEventType() == ea.KEYDOWN && ea.getKey() == _key)
             {
-                bool throwing = _manip->getSettings()->getThrowingEnabled();
-                _manip->getSettings()->setThrowingEnabled( !throwing );
+                bool value = _manip->getSettings()->getTerrainAvoidanceEnabled();
+                _manip->getSettings()->setTerrainAvoidanceEnabled( !value );
                 aa.requestRedraw();
                 return true;
             }
@@ -246,26 +310,190 @@ namespace
         void getUsage(osg::ApplicationUsage& usage) const
         {
             using namespace std;
-            usage.addKeyboardMouseBinding(string(1, _key), string("Toggle throwing"));
+            usage.addKeyboardMouseBinding(string(1, _key), string("Toggle terrain avoidance"));
+        }
+
+        char _key;
+        osg::ref_ptr<EarthManipulator> _manip;
+    };
+
+    /**
+     * Breaks a tether.
+     */
+    struct CycleTetherMode : public osgGA::GUIEventHandler
+    {
+        CycleTetherMode(char key, EarthManipulator* manip)
+            : _key(key), _manip(manip) { }
+
+        bool handle(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa)
+        {
+            if (ea.getEventType() == ea.KEYDOWN && ea.getKey() == _key)
+            {
+                EarthManipulator::TetherMode mode = _manip->getSettings()->getTetherMode();
+                if ( mode == _manip->TETHER_CENTER ) {
+                    _manip->getSettings()->setTetherMode( _manip->TETHER_CENTER_AND_HEADING );
+                    OE_NOTICE << "Tether mode = TETHER_CENTER_AND_HEADING\n";
+                }
+                else if ( mode == _manip->TETHER_CENTER_AND_HEADING ) {
+                    _manip->getSettings()->setTetherMode( _manip->TETHER_CENTER_AND_ROTATION );
+                    OE_NOTICE << "Tether mode = TETHER_CENTER_AND_ROTATION\n";
+                }
+                else {
+                    _manip->getSettings()->setTetherMode( _manip->TETHER_CENTER );
+                    OE_NOTICE << "Tether mode = CENTER\n";
+                }
+
+                aa.requestRedraw();
+                return true;
+            }
+            return false;
+        }
+
+        void getUsage(osg::ApplicationUsage& usage) const
+        {
+            using namespace std;
+            usage.addKeyboardMouseBinding(string(1, _key), string("Cycle Tether Mode"));
+        }
+
+        char _key;
+        osg::ref_ptr<EarthManipulator> _manip;
+    };
+
+    /**
+     * Breaks a tether.
+     */
+    struct BreakTetherHandler : public osgGA::GUIEventHandler
+    {
+        BreakTetherHandler(char key, EarthManipulator* manip)
+            : _key(key), _manip(manip) { }
+
+        bool handle(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa)
+        {
+            if (ea.getEventType() == ea.KEYDOWN && ea.getKey() == _key)
+            {
+                _manip->clearViewpoint();
+                aa.requestRedraw();
+                return true;
+            }
+            return false;
+        }
+
+        void getUsage(osg::ApplicationUsage& usage) const
+        {
+            using namespace std;
+            usage.addKeyboardMouseBinding(string(1, _key), string("Break a tether"));
         }
 
         char _key;
         osg::ref_ptr<EarthManipulator> _manip;
     };
+    
+
+    /**
+     * Adjusts the position offset.
+     */
+    struct SetPositionOffset : public osgGA::GUIEventHandler
+    {
+        SetPositionOffset(EarthManipulator* manip)
+            : _manip(manip) { }
+
+        bool handle(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa)
+        {
+            if (ea.getEventType() == ea.KEYDOWN && (ea.getModKeyMask() & ea.MODKEY_SHIFT) )
+            {
+                Viewpoint oldvp = _manip->getViewpoint();
+
+                double seconds = 0.5;
+
+                if ( ea.getKey() == ea.KEY_Left )
+                {
+                    Viewpoint vp;
+                    vp.positionOffset() = oldvp.positionOffset().get() + osg::Vec3f(-1000,0,0);
+                    _manip->setViewpoint( vp, seconds );
+                }
+                else if ( ea.getKey() == ea.KEY_Right )
+                {
+                    Viewpoint vp;
+                    vp.positionOffset() = oldvp.positionOffset().get() + osg::Vec3f(1000,0,0);
+                    _manip->setViewpoint( vp, seconds );
+                }
+                else if ( ea.getKey() == ea.KEY_Up )
+                {
+                    Viewpoint vp;
+                    vp.positionOffset() = oldvp.positionOffset().get() + osg::Vec3f(0,0,1000);
+                    _manip->setViewpoint( vp, seconds );
+                }
+                else if ( ea.getKey() == ea.KEY_Down )
+                {
+                    Viewpoint vp;
+                    vp.positionOffset() = oldvp.positionOffset().get() + osg::Vec3f(0,0,-1000);
+                    _manip->setViewpoint( vp, seconds );
+                }
+                aa.requestRedraw();
+                return true;
+            }
+            return false;
+        }
+
+        osg::ref_ptr<EarthManipulator> _manip;
+    };
 
 
     /**
+     * Toggles perspective/ortho projection matrix.
+     */
+    struct ToggleProjMatrix : public osgGA::GUIEventHandler
+    {
+        ToggleProjMatrix(char key, EarthManipulator* manip)
+            : _key(key), _manip(manip)
+        {
+        }
+
+        bool handle(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa)
+        {
+            if (ea.getEventType() == ea.KEYDOWN && ea.getKey() == _key)
+            {
+                osg::Matrix proj = aa.asView()->getCamera()->getProjectionMatrix();
+                if ( proj(3,3) == 0 )
+                {
+                    OE_NOTICE << "Switching to orthographc.\n";
+                    proj.getPerspective(_vfov, _ar, _zn, _zf);
+                    aa.asView()->getCamera()->setProjectionMatrixAsOrtho(-1, 1, -1, 1, _zn, _zf);
+                }
+                else
+                {
+                    OE_NOTICE << "Switching to perspective.\n";
+                    aa.asView()->getCamera()->setProjectionMatrixAsPerspective(_vfov, _ar, _zn, _zf);
+                }
+                aa.requestRedraw();
+                return true;
+            }
+            return false;
+        }
+
+        void getUsage(osg::ApplicationUsage& usage) const
+        {
+            using namespace std;
+            usage.addKeyboardMouseBinding(string(1, _key), string("Toggle projection matrix type"));
+        }
+
+        char _key;
+        osg::ref_ptr<EarthManipulator> _manip;
+        double _vfov, _ar, _zn, _zf;
+    };
+
+    /**
      * A simple simulator that moves an object around the Earth. We use this to
      * demonstrate/test tethering.
      */
     struct Simulator : public osgGA::GUIEventHandler
     {
-        Simulator( osg::Group* root, EarthManipulator* manip, MapNode* mapnode, osg::Node* model)
-            : _manip(manip), _mapnode(mapnode), _model(model), _lat0(55.0), _lon0(45.0), _lat1(-55.0), _lon1(-45.0)
+        Simulator( osg::Group* root, EarthManipulator* manip, MapNode* mapnode, osg::Node* model, const char* name, char key)
+            : _manip(manip), _mapnode(mapnode), _model(model), _name(name), _key(key)
         {
             if ( !model )
             { 
-                _model = AnnotationUtils::createSphere( 250.0, osg::Vec4(1,.7,.4,1) );
+                _model = AnnotationUtils::createHemisphere(250.0, osg::Vec4(1,.7,.4,1)); //, 90.0f); //AnnotationUtils::createSphere( 250.0, osg::Vec4(1,.7,.4,1) );
             }
             
             _xform = new GeoTransform();
@@ -283,7 +511,7 @@ namespace
             Style style;
             style.getOrCreate<TextSymbol>()->size() = 32.0f;
             style.getOrCreate<TextSymbol>()->declutter() = false;
-            _label = new LabelNode(_mapnode, GeoPoint(), "Hello World", style);
+            _label = new LabelNode(_mapnode, GeoPoint(), _name, style);
             _label->setDynamic( true );
             _cam->addChild( _label );
 
@@ -294,27 +522,42 @@ namespace
         {
             if ( ea.getEventType() == ea.FRAME )
             {
-                double t = fmod( osg::Timer::instance()->time_s(), 600.0 ) / 600.0;
+                double t0 = osg::Timer::instance()->time_s();
+                double t = fmod( t0, 600.0 ) / 600.0;
                 double lat, lon;
                 GeoMath::interpolate( D2R*_lat0, D2R*_lon0, D2R*_lat1, D2R*_lon1, t, lat, lon );
                 GeoPoint p( SpatialReference::create("wgs84"), R2D*lon, R2D*lat, 2500.0 );
-                double bearing = GeoMath::bearing(D2R*_lat1, D2R*_lon1, lat, lon);
+                double bearing = GeoMath::bearing(D2R*_lat0, D2R*_lon0, lat, lon);
+
+                float a = sin(t0*0.2);
+                bearing += a * 0.5 * osg::PI;
+                float pitch = 0.0; //a * 0.1 * osg::PI;
+
                 _xform->setPosition( p );
-                _pat->setAttitude(osg::Quat(bearing, osg::Vec3d(0,0,1)));
+
+                _pat->setAttitude(
+                    osg::Quat(pitch, osg::Vec3d(1,0,0)) *
+                    osg::Quat(bearing, osg::Vec3d(0,0,-1)));
+
                 _label->setPosition( p );
             }
-            else if ( ea.getEventType() == ea.KEYDOWN && ea.getKey() == 't' )
-            {                                
-                _manip->getSettings()->setTetherMode(osgEarth::Util::EarthManipulator::TETHER_CENTER_AND_HEADING);
-                _manip->setTetherNode( _manip->getTetherNode() ? 0L : _xform.get(), 2.0 );
-                Viewpoint vp = _manip->getViewpoint();
-                vp.setRange(5000);
-                _manip->setViewpoint(vp);
+            else if ( ea.getEventType() == ea.KEYDOWN )
+            {
+                if ( ea.getKey() == _key )
+                {                                
+                    Viewpoint vp = _manip->getViewpoint();
+                    vp.setNode( _pat.get() );
+                    vp.range() = 25000.0;
+                    vp.pitch() = -45.0;
+                    _manip->setViewpoint(vp, 2.0);
+                }
                 return true;
             }
             return false;
         }
 
+        std::string                        _name;
+        char                               _key;
         MapNode*                           _mapnode;
         EarthManipulator*                  _manip;
         osg::ref_ptr<osg::Camera>          _cam;
@@ -323,6 +566,8 @@ namespace
         double                             _lat0, _lon0, _lat1, _lon1;
         LabelNode*                         _label;
         osg::Node*                         _model;
+        float                              _heading;
+        float                              _pitch;
     };
 }
 
@@ -345,7 +590,7 @@ int main(int argc, char** argv)
     viewer.setCameraManipulator( manip );
 
     // UI:
-    Control* help = createHelp(&viewer);
+    Container* help = createHelp(&viewer);
 
     osg::Node* earthNode = MapNodeHelper().load( arguments, &viewer, help );
     if (!earthNode)
@@ -358,17 +603,6 @@ int main(int argc, char** argv)
     root->addChild( earthNode );
 
     osgEarth::MapNode* mapNode = osgEarth::MapNode::findMapNode( earthNode );
-    if ( mapNode )
-    {
-        if ( mapNode )
-            manip->setNode( mapNode->getTerrainEngine() );
-
-        if ( mapNode->getMap()->isGeocentric() )
-        {
-            manip->setHomeViewpoint( 
-                Viewpoint( osg::Vec3d( -90, 0, 0 ), 0.0, -90.0, 5e7 ) );
-        }
-    }
 
     // user model?
     osg::Node* model = 0L;
@@ -377,10 +611,32 @@ int main(int argc, char** argv)
         model = osgDB::readNodeFile(modelFile);
 
     // Simulator for tethering:
-    viewer.addEventHandler( new Simulator(root, manip, mapNode, model) );
-    manip->getSettings()->getBreakTetherActions().push_back( EarthManipulator::ACTION_PAN );
+    Simulator* sim1 = new Simulator(root, manip, mapNode, model, "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(root, manip, mapNode, model, "Thing 2", '9');
+    sim2->_name = "Thing 2";
+    sim2->_lat0 = 54.0;
+    sim2->_lon0 = 45.0;
+    sim2->_lat1 = -54.0;
+    sim2->_lon1 = -44.0;
+    viewer.addEventHandler(sim2);
+
     manip->getSettings()->getBreakTetherActions().push_back( EarthManipulator::ACTION_GOTO );    
 
+    // Set the minimum distance to something larger than the default
+    manip->getSettings()->setMinMaxDistance(10.0, manip->getSettings()->getMaxDistance());
+
+    // Sets the maximum focal point offsets (usually for tethering)
+    manip->getSettings()->setMaxOffset(5000.0, 5000.0);
+    
+    // Pitch limits.
+    manip->getSettings()->setMinMaxPitch(-90, 90);
+
 
     viewer.setSceneData( root );
 
@@ -390,12 +646,21 @@ int main(int argc, char** argv)
         osgGA::GUIEventAdapter::MODKEY_SHIFT );
 
     manip->getSettings()->setArcViewpointTransitions( true );    
+
+    manip->setTetherCallback( new TetherCB() );
     
     viewer.addEventHandler(new FlyToViewpointHandler( manip ));
     viewer.addEventHandler(new LockAzimuthHandler('u', manip));
-    viewer.addEventHandler(new ToggleProjectionHandler('c', manip));
     viewer.addEventHandler(new ToggleArcViewpointTransitionsHandler('a', manip));
     viewer.addEventHandler(new ToggleThrowingHandler('z', manip));
+    viewer.addEventHandler(new ToggleCollisionHandler('k', manip));
+    viewer.addEventHandler(new ToggleProjMatrix('o', manip));
+    viewer.addEventHandler(new BreakTetherHandler('b', manip));
+    viewer.addEventHandler(new CycleTetherMode('t', manip));
+    viewer.addEventHandler(new SetPositionOffset(manip));
+    viewer.addEventHandler(new ToggleLDB('L'));
+
+    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 d9f6f96..e9e7c4f 100644
--- a/src/applications/osgearth_map/osgearth_map.cpp
+++ b/src/applications/osgearth_map/osgearth_map.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
@@ -33,6 +36,9 @@ using namespace osgEarth;
 using namespace osgEarth::Drivers;
 using namespace osgEarth::Util;
 
+/**
+ * How to create a simple osgEarth map and display it.
+ */
 int
 main(int argc, char** argv)
 {
diff --git a/src/applications/osgearth_measure/osgearth_measure.cpp b/src/applications/osgearth_measure/osgearth_measure.cpp
index c068fee..f0c0051 100644
--- a/src/applications/osgearth_measure/osgearth_measure.cpp
+++ b/src/applications/osgearth_measure/osgearth_measure.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/applications/osgearth_minimap/osgearth_minimap.cpp b/src/applications/osgearth_minimap/osgearth_minimap.cpp
index a6ac340..a605354 100644
--- a/src/applications/osgearth_minimap/osgearth_minimap.cpp
+++ b/src/applications/osgearth_minimap/osgearth_minimap.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
@@ -22,6 +25,7 @@
 #include <osgEarthUtil/EarthManipulator>
 #include <osgEarthUtil/ExampleResources>
 #include <osgEarthAnnotation/PlaceNode>
+#include <osgEarthAnnotation/FeatureNode>
 #include <osgViewer/CompositeViewer>
 #include <osgEarthDrivers/gdal/GDALOptions>
 
@@ -38,7 +42,8 @@ using namespace osgEarth::Drivers;
  */
 MapNode* makeMiniMapNode( ) {    
     MapOptions mapOpt;
-    mapOpt.coordSysType() = MapOptions::CSTYPE_PROJECTED;    
+    mapOpt.coordSysType() = MapOptions::CSTYPE_PROJECTED;  
+    mapOpt.profile() = ProfileOptions("plate-carre");
     Map* map = new Map( mapOpt );    
 
     GDALOptions basemapOpt;
@@ -52,6 +57,101 @@ MapNode* makeMiniMapNode( ) {
     return new MapNode( map, mapNodeOptions );
 }
 
+osg::Node* drawBounds(MapNode* mapNode, osgEarth::GeoExtent& bounds)
+{
+    if (bounds.crossesAntimeridian())
+    {
+        GeoExtent first, second;
+        bounds.splitAcrossAntimeridian(first, second);
+        osg::Group* group = new osg::Group;
+        group->addChild( drawBounds( mapNode, first ) );
+        group->addChild( drawBounds( mapNode, second) );
+        return group;
+    }
+    else
+    {
+        osgEarth::Symbology::LineString* geom = new osgEarth::Symbology::LineString();
+        geom->push_back(osg::Vec3d(bounds.xMin(), bounds.yMin(), 0));
+        geom->push_back(osg::Vec3d(bounds.xMax(), bounds.yMin(), 0));
+        geom->push_back(osg::Vec3d(bounds.xMax(), bounds.yMax(), 0));
+        geom->push_back(osg::Vec3d(bounds.xMin(), bounds.yMax(), 0));
+        geom->push_back(osg::Vec3d(bounds.xMin(), bounds.yMin(), 0));
+        osgEarth::Features::Feature* feature = new osgEarth::Features::Feature(geom, osgEarth::SpatialReference::create("wgs84"));
+        Style style;
+        style.getOrCreateSymbol<LineSymbol>()->stroke()->color() = Color::Yellow;
+        feature->style() = style;
+        FeatureNode* featureNode = new FeatureNode(mapNode, feature);
+
+        featureNode->getOrCreateStateSet()->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF);
+        return featureNode;
+    }
+}
+
+osgEarth::GeoExtent getExtent(osgViewer::View* view)
+{
+    // Get the corners of all points on the view frustum.  Mostly modified from osgthirdpersonview
+    osg::Matrixd proj = view->getCamera()->getProjectionMatrix();
+    osg::Matrixd mv = view->getCamera()->getViewMatrix();
+    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;
+    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;
+}
+
 int
 main(int argc, char** argv)
 {
@@ -88,6 +188,8 @@ main(int argc, char** argv)
     osg::Node* node = MapNodeHelper().load( arguments, mainView );
     if ( node )
     {
+        MapNode* mapNode = MapNode::findMapNode(node);
+    
         //Set the main view's scene data to the loaded earth file
         mainView->setSceneData( node );
 
@@ -108,7 +210,10 @@ main(int argc, char** argv)
         markerStyle.getOrCreate<IconSymbol>()->url()->setLiteral( "../data/placemark32.png" );
         PlaceNode* eyeMarker = new PlaceNode(miniMapNode, GeoPoint(miniMapNode->getMapSRS(), 0, 0), "", markerStyle);
         miniMapGroup->addChild( eyeMarker );        
+        miniMapGroup->getOrCreateStateSet()->setRenderBinDetails(100, "RenderBin");
 
+        osg::Node* bounds = 0;
+  
         while (!viewer.done())
         {
             //Reset the viewport so that the camera's viewport is static and doesn't resize with window resizes
@@ -125,10 +230,18 @@ main(int argc, char** argv)
 
             //We want the marker to be positioned at elevation 0, so zero out any elevation in the eye point
             eyeGeo.z() = 0;           
-        
+ 
             //Set the position of the marker
             eyeMarker->setPosition( eyeGeo );
 
+            if (bounds)
+            {
+                miniMapGroup->removeChild( bounds );
+            }
+            GeoExtent extent = getExtent( mainView );
+            bounds = drawBounds( miniMapNode, extent );
+            miniMapGroup->addChild( bounds );
+
             viewer.frame();
         }        
     }
diff --git a/src/applications/osgearth_mrt/osgearth_mrt.cpp b/src/applications/osgearth_mrt/osgearth_mrt.cpp
index ce5610e..6c15366 100644
--- a/src/applications/osgearth_mrt/osgearth_mrt.cpp
+++ b/src/applications/osgearth_mrt/osgearth_mrt.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
@@ -62,11 +65,11 @@ createMRTPass(App& app, osg::Node* sceneGraph)
 
     static const char* fragSource =
         "varying float mrt_depth;\n"
-        "varying vec3 oe_Normal; \n"
+        "vec3 oe_global_Normal; \n"
         "void oe_mrt_fragment(inout vec4 color)\n"
         "{\n"
         "    gl_FragData[0] = color; \n"
-        "    gl_FragData[1] = vec4((oe_Normal+1.0)/2.0,1.0);\n"
+        "    gl_FragData[1] = vec4((oe_global_Normal+1.0)/2.0,1.0);\n"
         "    gl_FragData[2] = vec4(mrt_depth,mrt_depth,mrt_depth,1.0); \n"
         "}\n";
 
diff --git a/src/applications/osgearth_occlusionculling/osgearth_occlusionculling.cpp b/src/applications/osgearth_occlusionculling/osgearth_occlusionculling.cpp
index 064a31f..39ea18d 100644
--- a/src/applications/osgearth_occlusionculling/osgearth_occlusionculling.cpp
+++ b/src/applications/osgearth_occlusionculling/osgearth_occlusionculling.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/applications/osgearth_overlayviewer/osgearth_overlayviewer.cpp b/src/applications/osgearth_overlayviewer/osgearth_overlayviewer.cpp
index a8c2b66..d8dd107 100644
--- a/src/applications/osgearth_overlayviewer/osgearth_overlayviewer.cpp
+++ b/src/applications/osgearth_overlayviewer/osgearth_overlayviewer.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
@@ -189,9 +192,8 @@ main(int argc, char** argv)
 
     osgViewer::View* overlayView = new osgViewer::View();
     overlayView->getCamera()->setNearFarRatio(0.00002);
-    EarthManipulator* overlayEM = new EarthManipulator();
-    overlayEM->getSettings()->setCameraProjection(overlayEM->PROJ_ORTHOGRAPHIC);
-    overlayView->setCameraManipulator( overlayEM );
+    //overlayView->getCamera()->setProjectionMatrixAsOrtho2D(-1,1,-1,1);
+    overlayView->setCameraManipulator( new EarthManipulator() );
     
     //overlayView->setUpViewInWindow( 700, 50, 600, 600 );
     overlayView->setUpViewInWindow( (width/2), b, (width/2)-b*2, (height-b*4) );
diff --git a/src/applications/osgearth_package/osgearth_package.cpp b/src/applications/osgearth_package/osgearth_package.cpp
index d2b8c82..1f22e66 100644
--- a/src/applications/osgearth_package/osgearth_package.cpp
+++ b/src/applications/osgearth_package/osgearth_package.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -72,8 +72,9 @@ usage( const std::string& msg = "" )
         << "            [--mp]                          ; Use multiprocessing to process the tiles.  Useful for GDAL sources as this avoids the global GDAL lock" << std::endl
         << "            [--mt]                          ; Use multithreading to process the tiles." << std::endl
         << "            [--concurrency]                 ; The number of threads or proceses to use if --mp or --mt are provided." << std::endl
+        << "            [--alpha-mask]                  ; Mask out imagery that isn't in the provided extents." << std::endl
         << std::endl
-        << "         [--quiet]               : suppress progress output" << std::endl;
+        << "            [--verbose]                     ; Displays progress of the operation" << std::endl;
 
     return -1;
 }
@@ -145,6 +146,8 @@ makeTMS( osg::ArgumentParser& args )
     args.read("-c", concurrency);
     args.read("--concurrency", concurrency);
 
+    bool applyAlphaMask = args.read("--alpha-mask");
+
     bool writeXML = true;
 
     // load up the map
@@ -326,6 +329,7 @@ makeTMS( osg::ArgumentParser& args )
     packager.setWriteOptions(options);    
     packager.setOverwrite(overwrite);
     packager.setKeepEmpties(keepEmpties);
+    packager.setApplyAlphaMask(applyAlphaMask);
 
 
     // new map for an output earth file if necessary.
diff --git a/src/applications/osgearth_package_qt/ExportDialog b/src/applications/osgearth_package_qt/ExportDialog
index 34c6d45..658fcfc 100644
--- a/src/applications/osgearth_package_qt/ExportDialog
+++ b/src/applications/osgearth_package_qt/ExportDialog
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/applications/osgearth_package_qt/ExportDialog.cpp b/src/applications/osgearth_package_qt/ExportDialog.cpp
index 5a9c7f6..b8fc655 100644
--- a/src/applications/osgearth_package_qt/ExportDialog.cpp
+++ b/src/applications/osgearth_package_qt/ExportDialog.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/applications/osgearth_package_qt/PackageQtMainWindow b/src/applications/osgearth_package_qt/PackageQtMainWindow
index 5095748..c495f62 100644
--- a/src/applications/osgearth_package_qt/PackageQtMainWindow
+++ b/src/applications/osgearth_package_qt/PackageQtMainWindow
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/applications/osgearth_package_qt/SceneController.cpp b/src/applications/osgearth_package_qt/SceneController.cpp
index 96c8050..42f9640 100644
--- a/src/applications/osgearth_package_qt/SceneController.cpp
+++ b/src/applications/osgearth_package_qt/SceneController.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/applications/osgearth_package_qt/SceneController.h b/src/applications/osgearth_package_qt/SceneController.h
index 1e1d604..a16c31e 100644
--- a/src/applications/osgearth_package_qt/SceneController.h
+++ b/src/applications/osgearth_package_qt/SceneController.h
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/applications/osgearth_package_qt/TMSExporter.cpp b/src/applications/osgearth_package_qt/TMSExporter.cpp
index a1f2aef..ec11a39 100644
--- a/src/applications/osgearth_package_qt/TMSExporter.cpp
+++ b/src/applications/osgearth_package_qt/TMSExporter.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
@@ -148,6 +151,7 @@ int TMSExporter::exportTMS(MapNode* mapNode, const std::string& earthFilePath, c
     }
     packager.setExtension(extension);  
     packager.setOverwrite(overwrite);
+    packager.setKeepEmpties(_keepEmpties);
     packager.getTileVisitor()->setProgressCallback( _progress.get() );
     packager.getTileVisitor()->setMaxLevel(_maxLevel);
 
diff --git a/src/applications/osgearth_package_qt/TMSExporter.h b/src/applications/osgearth_package_qt/TMSExporter.h
index f3ce6f5..a3b0752 100644
--- a/src/applications/osgearth_package_qt/TMSExporter.h
+++ b/src/applications/osgearth_package_qt/TMSExporter.h
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/applications/osgearth_package_qt/WaitDialog b/src/applications/osgearth_package_qt/WaitDialog
index c59d64f..37672ee 100644
--- a/src/applications/osgearth_package_qt/WaitDialog
+++ b/src/applications/osgearth_package_qt/WaitDialog
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/applications/osgearth_package_qt/WaitDialog.cpp b/src/applications/osgearth_package_qt/WaitDialog.cpp
index 3f353ff..637d632 100644
--- a/src/applications/osgearth_package_qt/WaitDialog.cpp
+++ b/src/applications/osgearth_package_qt/WaitDialog.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/applications/osgearth_package_qt/package_qt.cpp b/src/applications/osgearth_package_qt/package_qt.cpp
index 330ca59..11ed9b8 100644
--- a/src/applications/osgearth_package_qt/package_qt.cpp
+++ b/src/applications/osgearth_package_qt/package_qt.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/applications/osgearth_pick/CMakeLists.txt b/src/applications/osgearth_pick/CMakeLists.txt
new file mode 100644
index 0000000..fb3968b
--- /dev/null
+++ b/src/applications/osgearth_pick/CMakeLists.txt
@@ -0,0 +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_pick.cpp )
+
+#### end var setup  ###
+SETUP_APPLICATION(osgearth_pick)
\ No newline at end of file
diff --git a/src/applications/osgearth_pick/osgearth_pick.cpp b/src/applications/osgearth_pick/osgearth_pick.cpp
new file mode 100644
index 0000000..7251236
--- /dev/null
+++ b/src/applications/osgearth_pick/osgearth_pick.cpp
@@ -0,0 +1,318 @@
+/* -*-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/Registry>
+#include <osgEarth/ShaderGenerator>
+#include <osgEarth/ObjectIndex>
+#include <osgEarthUtil/EarthManipulator>
+#include <osgEarthUtil/ExampleResources>
+#include <osgEarthUtil/Controls>
+#include <osgEarthUtil/RTTPicker>
+#include <osgEarthFeatures/Feature>
+#include <osgEarthFeatures/FeatureIndex>
+#include <osgEarthAnnotation/AnnotationNode>
+
+#include <osgEarth/IntersectionPicker>
+
+#include <osgViewer/CompositeViewer>
+#include <osgGA/TrackballManipulator>
+#include <osg/BlendFunc>
+
+#define LC "[rttpicker] "
+
+using namespace osgEarth;
+using namespace osgEarth::Util;
+using namespace osgEarth::Features;
+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
+{
+    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 << "Old Picker 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 << "Old Picker found OID " << oid << "\n";
+                        OE_NOTICE << "...feature ID = " << f->getFID() << "\n";
+                    }
+                    osg::ref_ptr<AnnotationNode> a = index->get<AnnotationNode>(oid);
+                    if ( a ) {
+                        OE_NOTICE << "Old Picker found annotation " << a->getName() << "\n";
+                    }
+                }
+                else {
+                    OE_NOTICE << "picked, but no OIDs\n";
+                }
+            }
+            else {
+                OE_NOTICE << "no intersect\n";
+            }
+        }
+        return false;
+    }
+};
+
+
+//-----------------------------------------------------------------------
+
+/**
+ * Callback that you install on the RTTPicker.
+ */
+struct MyPickCallback : public RTTPicker::Callback
+{
+    void onHit(ObjectID id)
+    {
+        // First see whether it's a feature:
+        FeatureIndex* index = Registry::objectIndex()->get<FeatureIndex>( id );
+        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") );
+        }
+
+        else
+        {
+            // Check whether it's an annotation:
+            AnnotationNode* anno = Registry::objectIndex()->get<AnnotationNode>( id );
+            if ( anno )
+            {
+                s_fidLabel->setText( Stringify() << "ObjectID = " << id );
+                s_nameLabel->setName( Stringify() << "Name = " << anno->getName() );
+            }
+
+            // None of the above.. clear.
+            else
+            {
+                s_fidLabel->setText( Stringify() << "oid = " << id );
+                s_nameLabel->setText( "Name = " );
+            }
+        }
+
+        s_highlightUniform->set( id );
+    }
+
+    void onMiss()
+    {
+        s_fidLabel->setText( "No pick." );
+        s_nameLabel->setText( "Name = " );
+        s_highlightUniform->set( 0u );
+    }
+
+    // pick whenever the mouse moves.
+    bool accept(const osgGA::GUIEventAdapter& ea, const osgGA::GUIActionAdapter& aa)
+    {
+        return ea.getEventType() == ea.MOVE;
+    }
+};
+
+//-----------------------------------------------------------------------
+
+// Shaders that will highlight the currently "picked" feature.
+
+const char* highlightVert =
+    "#version 130\n"
+    "uniform uint objectid_to_highlight; \n"
+    "uint oe_index_objectid;      // Stage global containing object id \n"
+    "flat out int selected; \n"
+    "void checkForHighlight(inout vec4 vertex) \n"
+    "{ \n"
+    "    selected = (objectid_to_highlight > 1u && objectid_to_highlight == oe_index_objectid) ? 1 : 0; \n"
+    "} \n";
+
+const char* highlightFrag =
+    "#version 130\n"
+    "flat in int selected; \n"
+    "void highlightFragment(inout vec4 color) \n"
+    "{ \n"
+    "    if ( selected == 1 ) \n"
+    "        color.rgb = mix(color.rgb, clamp(vec3(0.5,0.5,2.0)*(1.0-color.rgb), 0.0, 1.0), 0.5); \n"
+    "} \n";
+
+void installHighlighter(osg::StateSet* stateSet, int attrLocation)
+{
+    // This shader program will highlight the selected object.
+    VirtualProgram* vp = VirtualProgram::getOrCreate(stateSet);
+    vp->setFunction( "checkForHighlight",  highlightVert, ShaderComp::LOCATION_VERTEX_CLIP );
+    vp->setFunction( "highlightFragment",  highlightFrag, ShaderComp::LOCATION_FRAGMENT_COLORING );
+
+    // Since we're accessing object IDs, we need to load the indexing shader as well:
+    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 );
+}
+
+//------------------------------------------------------------------------
+
+// Configures a window that lets you see what the RTT camera sees.
+void
+setupRTTView(osgViewer::View* view, osg::Texture* rttTex)
+{
+    view->setCameraManipulator(0L);
+    view->getCamera()->setViewport(0,0,256,256);
+    view->getCamera()->setClearColor(osg::Vec4(1,1,1,1));
+    view->getCamera()->setProjectionMatrixAsOrtho2D(-.5,.5,-.5,.5);
+    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::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::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) );
+
+    osg::Geode* geode = new osg::Geode();
+    geode->addDrawable( g );
+
+    osg::StateSet* stateSet = geode->getOrCreateStateSet();
+    stateSet->setDataVariance(osg::Object::DYNAMIC);
+
+    stateSet->setTextureAttributeAndModes(0, rttTex, 1);
+    rttTex->setUnRefImageDataAfterApply( false );
+    rttTex->setResizeNonPowerOfTwoHint(false);
+
+    stateSet->setMode(GL_LIGHTING, 0);
+    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";
+    osgEarth::Registry::shaderGenerator().run(geode);
+    VirtualProgram::getOrCreate(geode->getOrCreateStateSet())->setFunction("swap", fs, ShaderComp::LOCATION_FRAGMENT_COLORING);
+
+    view->setSceneData( geode );
+}
+
+//-----------------------------------------------------------------------
+
+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);
+    if ( arguments.read("--help") )
+        return usage(argv[0]);
+
+    // create a viewer that will hold 2 viewports.
+    osgViewer::CompositeViewer viewer(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 );
+
+    // install our default manipulator (do this before calling load)
+    mainView->setCameraManipulator( new EarthManipulator() );    
+
+    // Made some UI components:
+    ui::VBox* uiContainer = new ui::VBox();
+    uiContainer->setAlign( ui::Control::ALIGN_LEFT, ui::Control::ALIGN_TOP );
+    uiContainer->setAbsorbEvents( true );
+    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( "---" ) );
+
+    // Load up the earth file.
+    osg::Node* node = MapNodeHelper().load( arguments, 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) );
+
+        // install a callback that controls the picker and listens for hits.
+        picker->setDefaultCallback( new MyPickCallback() );
+
+        // 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) );
+
+        // Hightlight features as we pick'em.
+        installHighlighter(
+            MapNode::get(node)->getOrCreateStateSet(),
+            Registry::objectIndex()->getObjectIDAttribLocation() );
+
+        return viewer.run();
+    }
+    else
+    {
+        return usage(argv[0]);
+    }
+}
diff --git a/src/applications/osgearth_qt/DemoMainWindow b/src/applications/osgearth_qt/DemoMainWindow
index 5e3681a..bcfafda 100644
--- a/src/applications/osgearth_qt/DemoMainWindow
+++ b/src/applications/osgearth_qt/DemoMainWindow
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
@@ -111,7 +114,7 @@ public:
 
         osgEarth::Annotation::AnnotationData* annoData = new osgEarth::Annotation::AnnotationData();
         annoData->setName("New York");
-        annoData->setViewpoint(osgEarth::Viewpoint(osg::Vec3d(-74, 40.714, 0), 0.0, -90.0, 1e5));
+        annoData->setViewpoint(osgEarth::Viewpoint("New Tork", -74, 40.714, 0, 0.0, -90.0, 1e5));
         annotation->setAnnotationData(annoData);
 
         annotation->installDecoration("selected", new osgEarth::Annotation::ScaleDecoration(2.0f));
diff --git a/src/applications/osgearth_qt/osgearth_qt.cpp b/src/applications/osgearth_qt/osgearth_qt.cpp
index d8a70a2..b0b8d5a 100644
--- a/src/applications/osgearth_qt/osgearth_qt.cpp
+++ b/src/applications/osgearth_qt/osgearth_qt.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,16 +8,20 @@
 * 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.
+* 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/Version>
 #include <osgEarth/ImageUtils>
 #include <osgEarth/MapNode>
 #include <osgEarthAnnotation/AnnotationData>
@@ -178,7 +182,7 @@ TrackNode* createTrack(TrackNodeFieldSchema& schema, osg::Image* image, const st
 
   AnnotationData* data = new AnnotationData();
   data->setName(name);
-  data->setViewpoint(osgEarth::Viewpoint(center, 0.0, -90.0, 1e5));
+  data->setViewpoint(osgEarth::Viewpoint(0L, center.x(), center.y(), center.z(), 0.0, -90.0, 1e5));
   track->setAnnotationData( data );
 
   trackSims.push_back(new TrackSim(track, center, radius, time, mapNode));
@@ -262,6 +266,11 @@ main(int argc, char** argv)
         viewerWidget = new osgEarth::QtGui::ViewerWidget( root );
     }
 
+#if OSG_MIN_VERSION_REQUIRED(3,3,2)
+    // Enable touch events on the viewer
+    viewerWidget->getGraphicsWindow()->setTouchEventsEnabled(true);
+#endif
+
     //osgEarth::QtGui::ViewerWidget* viewerWidget = new osgEarth::QtGui::ViewerWidget(root);
     //viewerWidget->setGeometry(50, 50, 1024, 768);
 
diff --git a/src/applications/osgearth_qt_simple/osgearth_qt_simple.cpp b/src/applications/osgearth_qt_simple/osgearth_qt_simple.cpp
index 49dc50f..cb458ba 100644
--- a/src/applications/osgearth_qt_simple/osgearth_qt_simple.cpp
+++ b/src/applications/osgearth_qt_simple/osgearth_qt_simple.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/applications/osgearth_qt_windows/osgearth_qt_windows.cpp b/src/applications/osgearth_qt_windows/osgearth_qt_windows.cpp
index 727e6ee..d87b1b1 100644
--- a/src/applications/osgearth_qt_windows/osgearth_qt_windows.cpp
+++ b/src/applications/osgearth_qt_windows/osgearth_qt_windows.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/applications/osgearth_seed/osgearth_seed.cpp b/src/applications/osgearth_seed/osgearth_seed.cpp
index 62a14a6..e73ec74 100644
--- a/src/applications/osgearth_seed/osgearth_seed.cpp
+++ b/src/applications/osgearth_seed/osgearth_seed.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/applications/osgearth_sequencecontrol/osgearth_sequencecontrol.cpp b/src/applications/osgearth_sequencecontrol/osgearth_sequencecontrol.cpp
index c07d118..acb41be 100644
--- a/src/applications/osgearth_sequencecontrol/osgearth_sequencecontrol.cpp
+++ b/src/applications/osgearth_sequencecontrol/osgearth_sequencecontrol.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/applications/osgearth_shadercomp/osgearth_shadercomp.cpp b/src/applications/osgearth_shadercomp/osgearth_shadercomp.cpp
index 74b3941..332cf80 100644
--- a/src/applications/osgearth_shadercomp/osgearth_shadercomp.cpp
+++ b/src/applications/osgearth_shadercomp/osgearth_shadercomp.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,17 +8,20 @@
 * 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.
+* 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/>
 */
 
 /**
- * This sample demonstrates a simple use of osgEarth's shader composition framework.
+ * This is a set of unit tests for osgEarth's shader composition framework.
  *
  * By default, osgEarth uses GL shaders to render the terrain. Shader composition is a
  * mechanism by which you can inject custom shader code into osgEarth's shader program
@@ -33,6 +36,7 @@
 #include <osgEarthUtil/EarthManipulator>
 #include <osgEarth/VirtualProgram>
 #include <osgEarth/Registry>
+#include <osgEarth/Capabilities>
 #include <osgEarth/ShaderUtils>
 #include <osgEarthUtil/Controls>
 
@@ -51,6 +55,7 @@ int usage( const std::string& msg )
         << "           [--test3]    : Run the shader LOD test \n"
         << "           [--test4]    : Run the memory test \n"
         << "           [--test5]    : Run the Program state set test \n"
+        << "           [--test6]    : Run the 2-camera test \n"
         << std::endl;
 
     return -1;
@@ -104,6 +109,7 @@ namespace TEST_1
         osgEarth::VirtualProgram* vp = new osgEarth::VirtualProgram();
         vp->setFunction( "setup_haze", s_hazeVertShader, osgEarth::ShaderComp::LOCATION_VERTEX_VIEW );
         vp->setFunction( "apply_haze", s_hazeFragShader, osgEarth::ShaderComp::LOCATION_FRAGMENT_LIGHTING );
+        vp->setShaderLogging(true, "shaders.txt");
         return vp;
     }
 
@@ -293,6 +299,79 @@ namespace TEST_5
     }
 }
 
+//-------------------------------------------------------------------------
+
+// Tests the VirtualProgram's ShaderComp::AcceptCallback with multiple cameras
+// in order to vertify that the State Stack Memory is being properly disabled
+// when Accept Callbacks are in play.
+namespace TEST_6
+{
+    const char* fragShader =
+        "#version 110\n"
+        "void make_it_red(inout vec4 color) {\n"
+        "    color.r = 1.0;\n"
+        "}\n";
+    const char* fragShader2 =
+        "#version 110\n"
+        "void make_it_blue(inout vec4 color) {\n"
+        "    color.b = 1.0;\n"
+        "}\n";
+
+    // This acceptor will only include the fragment shader snippet above
+    // when the camera's viewport.x == 0. Normally the Program will only
+    // be applied once in succession. 
+    struct Acceptor : public ShaderComp::AcceptCallback
+    {
+        Acceptor() : _fn(0) { }
+
+        // Return true to activate the shader function.
+        bool operator()(const osg::State& state)
+        {
+            const osg::Viewport* vp = state.getCurrentViewport();
+            return vp && vp->x() == 0.0;
+        }
+
+        unsigned _fn;
+    };
+
+    osg::Group* run(osg::Node* node)
+    {
+        osg::Group* group1 = new osg::Group();
+        VirtualProgram* vp1 = VirtualProgram::getOrCreate(group1->getOrCreateStateSet());
+        vp1->setFunction("make_it_red", fragShader, ShaderComp::LOCATION_FRAGMENT_LIGHTING, new Acceptor());
+        vp1->setAcceptCallbacksVaryPerFrame(true);
+        group1->addChild( node );
+
+        osg::Camera* cam1 = new osg::Camera();
+        cam1->setViewport(0, 0, 200, 200);
+        cam1->addChild( group1 );
+
+        osg::Camera* cam2 = new osg::Camera();
+        cam2->setViewport(201, 0, 200, 200);
+        cam2->addChild( group1 );        
+
+        osg::Group* group2 = new osg::Group();
+        VirtualProgram* vp2 =  VirtualProgram::getOrCreate(group2->getOrCreateStateSet());
+        vp2->setFunction("make_it_blue", fragShader2, ShaderComp::LOCATION_FRAGMENT_LIGHTING);
+        group2->addChild( node );
+
+        osg::Camera* cam3 = new osg::Camera();
+        cam3->setViewport(0, 201, 200, 200);
+        cam3->addChild( group2 );
+
+        osg::Camera* cam4 = new osg::Camera();
+        cam4->setViewport(201, 201, 200, 200);
+        cam4->addChild( group2 );
+        
+        osg::Group* g = new osg::Group();
+        g->addChild( cam1 );
+        g->addChild( cam2 );
+        g->addChild( cam3 );
+        g->addChild( cam4 );
+
+        return g;
+    }
+}
 
 //-------------------------------------------------------------------------
 
@@ -306,7 +385,8 @@ int main(int argc, char** argv)
     bool test3 = arguments.read("--test3");
     bool test4 = arguments.read("--test4");
     bool test5 = arguments.read("--test5");
-    bool ok    = test1 || test2 || test3 || test4 || test5; 
+    bool test6 = arguments.read("--test6");
+    bool ok    = test1 || test2 || test3 || test4 || test5 || test6;
 
     if ( !ok )
     {
@@ -324,7 +404,7 @@ int main(int argc, char** argv)
     LabelControl* label = new LabelControl();
     canvas->addControl(label);
 
-    if ( !test5 )
+    if ( test1 || test2 || test3 || test4 || test6 )
     {
         osg::Node* earthNode = osgDB::readNodeFile( "gdal_tiff.earth" );
         if (!earthNode)
@@ -352,11 +432,17 @@ int main(int argc, char** argv)
             root->addChild( TEST_4::run(earthNode) );
             label->setText("Memory management test; monitor memory for stability");
         }
+        else if ( test6 )
+        {
+            root->addChild( TEST_6::run(earthNode) );
+            label->setText("State Memory Stack test; top row, both=blue. bottom left=red, bottom right=normal.");
+        }
         
         viewer.setCameraManipulator( new osgEarth::Util::EarthManipulator() );
     }
-    else // if ( test5 )
+    else if ( test5 )
     {
+        osgEarth::Registry::instance()->getCapabilities();
         root->addChild( TEST_5::run() );
         label->setText( "Leakage test: red tri on the left, blue on the right." );
     }
diff --git a/src/applications/osgearth_shadergen/osgearth_shadergen.cpp b/src/applications/osgearth_shadergen/osgearth_shadergen.cpp
index 72426f6..6ddf7f6 100644
--- a/src/applications/osgearth_shadergen/osgearth_shadergen.cpp
+++ b/src/applications/osgearth_shadergen/osgearth_shadergen.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/applications/osgearth_sharedlayer/osgearth_sharedlayer.cpp b/src/applications/osgearth_sharedlayer/osgearth_sharedlayer.cpp
index c2df7ee..844d6b7 100644
--- a/src/applications/osgearth_sharedlayer/osgearth_sharedlayer.cpp
+++ b/src/applications/osgearth_sharedlayer/osgearth_sharedlayer.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/applications/osgearth_terraineffects/osgearth_terraineffects.cpp b/src/applications/osgearth_terraineffects/osgearth_terraineffects.cpp
index 1b0a8ce..83668da 100644
--- a/src/applications/osgearth_terraineffects/osgearth_terraineffects.cpp
+++ b/src/applications/osgearth_terraineffects/osgearth_terraineffects.cpp
@@ -31,8 +31,6 @@
 #include <osgEarthUtil/Controls>
 
 #include <osgEarthUtil/ContourMap>
-#include <osgEarthUtil/LODBlending>
-#include <osgEarthUtil/NormalMap>
 #include <osgEarthUtil/VerticalScale>
 
 #include <osgEarthSymbology/Color>
@@ -60,15 +58,11 @@ struct App
     TerrainEngineNode*           engine;
 
     osg::ref_ptr<ContourMap>     contourMap;
-    osg::ref_ptr<LODBlending>    lodBlending;
-    osg::ref_ptr<NormalMap>      normalMap;
     osg::ref_ptr<VerticalScale>  verticalScale;
 
     App()
     {
         contourMap = new ContourMap();
-        lodBlending = new LODBlending();
-        normalMap = new NormalMap();
         verticalScale = new VerticalScale();
     }
 };
@@ -131,15 +125,6 @@ struct LODBlendingController {
     }
 };
 
-struct NormalMapController {
-    TOGGLE   ( normalMap );
-    NormalMapController(App& app, ui::Grid* grid) {
-        int r = grid->getNumRows();
-        grid->setControl(0, r, new ui::LabelControl("NormalMap"));
-        grid->setControl(1, r, new ui::CheckBoxControl(false, new Toggle(app)));
-    }
-};
-
 struct VerticalScaleController {
     TOGGLE   ( verticalScale );
     SET_FLOAT( verticalScale, setScale );
@@ -166,7 +151,6 @@ ui::Control* createUI( App& app )
 
     ContourMapController    (app, grid);
     LODBlendingController   (app, grid);
-    NormalMapController     (app, grid);
     VerticalScaleController (app, grid);
 
     return grid;
diff --git a/src/applications/osgearth_terrainprofile/osgearth_terrainprofile.cpp b/src/applications/osgearth_terrainprofile/osgearth_terrainprofile.cpp
index c4abec0..39cf919 100644
--- a/src/applications/osgearth_terrainprofile/osgearth_terrainprofile.cpp
+++ b/src/applications/osgearth_terrainprofile/osgearth_terrainprofile.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/applications/osgearth_tfs/osgearth_tfs.cpp b/src/applications/osgearth_tfs/osgearth_tfs.cpp
index b652646..b2dbb26 100644
--- a/src/applications/osgearth_tfs/osgearth_tfs.cpp
+++ b/src/applications/osgearth_tfs/osgearth_tfs.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
@@ -45,6 +48,7 @@ usage( const std::string& msg )
         << "    --first-level      ; The first level where features will be added to the quadtree" << std::endl
         << "    --max-level        ; The maximum level of the feature quadtree" << std::endl
         << "    --max-features     ; The maximum number of features per tile" << std::endl
+        << "    --grid             ; Generate a single level grid with the specified resolution.  Default units are meters. (ex. 50, 100km, 200mi)" << std::endl
         << "    --out              ; The destination directory" << std::endl
         << "    --layer            ; The name of the layer to be written to the metadata document" << std::endl
         << "    --description      ; The abstract/description of the layer to be written to the metadata document" << std::endl
@@ -107,6 +111,18 @@ int main(int argc, char** argv)
     std::string destSRS;
     while(arguments.read("--dest-srs", destSRS));
 
+    std::string grid;
+    float gridSizeMeters = -1.0f;
+    while (arguments.read("--grid", grid));
+    if (!grid.empty())
+    {
+        float gridSize;
+        Units units;
+        if ( Units::parse(grid, gridSize, units, Units::METERS) ) {
+             gridSizeMeters = Distance(gridSize, units).as(Units::METERS);
+        }
+    }
+
     // Custom bounding box
     Bounds bounds;
     double xmin=DBL_MAX, ymin=DBL_MAX, xmax=DBL_MIN, ymax=DBL_MIN;
@@ -156,20 +172,6 @@ int main(int argc, char** argv)
 
     std::string method = cropMethod == CropFilter::METHOD_CENTROID ? "Centroid" : "Cropping";
 
-    OE_NOTICE << "Processing " << filename << std::endl
-              << "  FirstLevel=" << firstLevel << std::endl
-              << "  MaxLevel=" << maxLevel << std::endl
-              << "  MaxFeatures=" << maxFeatures << std::endl
-              << "  Destination=" << destination << std::endl
-              << "  Layer=" << layer << std::endl
-              << "  Description=" << description << std::endl
-              << "  Expression=" << queryExpression << std::endl
-              << "  OrderBy=" << queryOrderBy << std::endl
-              << "  Method= " << method << std::endl
-              << "  DestSRS= " << destSRS << std::endl
-              << std::endl;
-
-
     Query query;
     if (!queryExpression.empty())
     {
@@ -182,6 +184,49 @@ int main(int argc, char** argv)
     }    
 
     osg::Timer_t startTime = osg::Timer::instance()->tick();
+
+    // Use the feature extent by default.
+    GeoExtent ext = features->getFeatureProfile()->getExtent();
+    if (!destSRS.empty())
+    {
+        ext = ext.transform(osgEarth::SpatialReference::create( destSRS ));
+    }
+
+    if (bounds.isValid())
+    {
+        // If a custom bounds was specified use that instead.
+        ext = GeoExtent(osgEarth::SpatialReference::create( destSRS ), bounds);
+    }
+
+    if (gridSizeMeters > 0.0)
+    {
+        // Compute the level in the destination profile that is closest to the desired resolution.
+        osg::ref_ptr< const osgEarth::Profile > profile = osgEarth::Profile::create(ext.getSRS(), ext.xMin(), ext.yMin(), ext.xMax(), ext.yMax(), 1, 1);
+        float res = gridSizeMeters;
+        // Estimate meters to degrees if necessary
+        if (profile->getSRS()->isGeographic())
+        {
+            res /= 111000.0;
+        }
+        unsigned int level = profile->getLevelOfDetailForHorizResolution(res, 1.0);
+        firstLevel = level;
+        maxLevel = level;
+        OE_NOTICE << "Computed grid level " << level << " from grid resolution of " << gridSizeMeters << "m" << std::endl;
+    }
+
+    OE_NOTICE << "Processing " << filename << std::endl
+        << "  FirstLevel=" << firstLevel << std::endl
+        << "  MaxLevel=" << maxLevel << std::endl
+        << "  MaxFeatures=" << maxFeatures << std::endl
+        << "  Destination=" << destination << std::endl
+        << "  Layer=" << layer << std::endl
+        << "  Description=" << description << std::endl
+        << "  Expression=" << queryExpression << std::endl
+        << "  OrderBy=" << queryOrderBy << std::endl
+        << "  Method= " << method << std::endl
+        << "  DestSRS= " << destSRS << std::endl
+        << std::endl;
+
     //buildTFS( features.get(), firstLevel, maxLevel, maxFeatures, destination, layer, description, query, cropMethod);
     TFSPackager packager;
     packager.setFirstLevel( firstLevel );
@@ -190,10 +235,8 @@ int main(int argc, char** argv)
     packager.setQuery( query );
     packager.setMethod( cropMethod );    
     packager.setDestSRS( destSRS );
-    if (bounds.isValid())
-    {
-        packager.setLod0Extent(GeoExtent(osgEarth::SpatialReference::create( destSRS ), bounds));
-    }
+    packager.setLod0Extent(ext);
+
     packager.package( features, 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_tileindex/osgearth_tileindex.cpp b/src/applications/osgearth_tileindex/osgearth_tileindex.cpp
index bba3e6a..dc6c1b0 100644
--- a/src/applications/osgearth_tileindex/osgearth_tileindex.cpp
+++ b/src/applications/osgearth_tileindex/osgearth_tileindex.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/applications/osgearth_tilesource/osgearth_tilesource.cpp b/src/applications/osgearth_tilesource/osgearth_tilesource.cpp
index 3413221..940055e 100644
--- a/src/applications/osgearth_tilesource/osgearth_tilesource.cpp
+++ b/src/applications/osgearth_tilesource/osgearth_tilesource.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
@@ -50,7 +53,7 @@ class CustomTileSource : public TileSource
 {
 public:
     // Constructor that takes the user-provided options.
-    CustomTileSource( const TileSourceOptions& options =TileSourceOptions() ) : TileSource(options)
+    CustomTileSource() : TileSource(TileSourceOptions())
     {
         _geom = new Ring();
         _geom->push_back( osg::Vec3(5, 5, 0) );
@@ -69,6 +72,12 @@ public:
         return STATUS_OK;
     }
 
+    // Tells the layer not to cache data from this tile source.
+    CachePolicy getCachePolicyHint(const Profile* profile) const 
+    {
+        return CachePolicy::NO_CACHE;
+    }
+
     // Define this method to return an image corresponding to the given TileKey.
     osg::Image* createImage( const TileKey& key, ProgressCallback* progress )
     {
diff --git a/src/applications/osgearth_toc/osgearth_toc.cpp b/src/applications/osgearth_toc/osgearth_toc.cpp
index 85864d2..5849549 100644
--- a/src/applications/osgearth_toc/osgearth_toc.cpp
+++ b/src/applications/osgearth_toc/osgearth_toc.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
@@ -19,6 +22,7 @@
 #include <osgEarth/Map>
 #include <osgEarth/MapFrame>
 #include <osgEarth/MapNode>
+#include <osgEarth/MapModelChange>
 #include <osgEarthUtil/EarthManipulator>
 #include <osgEarthUtil/Controls>
 #include <osgEarthUtil/ExampleResources>
@@ -40,6 +44,7 @@ static Grid* s_imageBox;
 static Grid* s_elevationBox;
 static Grid* s_modelBox;
 static bool s_updateRequired = true;
+static MapModelChange::ActionType s_changeAction;
 
 //------------------------------------------------------------------------
 
@@ -47,6 +52,7 @@ struct MyMapListener : public MapCallback
 {
     void onMapModelChanged( const MapModelChange& change ) {
         s_updateRequired = true;
+        s_changeAction = change.getAction();
     }
 };
 
@@ -60,6 +66,30 @@ struct UpdateOperation : public osg::Operation
         {
             updateControlPanel();
             s_updateRequired = false;
+
+            if (s_changeAction == MapModelChange::ADD_ELEVATION_LAYER ||
+                s_changeAction == MapModelChange::REMOVE_ELEVATION_LAYER)
+            {
+                OE_NOTICE << "Dirtying model layers.\n";
+                dirtyModelLayers();
+            }
+        }
+    }
+
+    void dirtyModelLayers()
+    {
+        for(unsigned i=0; i<s_activeMap->getNumModelLayers(); ++i)
+        {
+            ModelSource* ms = s_activeMap->getModelLayerAt(i)->getModelSource();
+            if ( ms )
+            {
+                ms->dirty();
+            }
+            else
+            {
+                OE_NOTICE << s_activeMap->getModelLayerAt(i)->getName()
+                    << " has no model source.\n";
+            }
         }
     }
 };
diff --git a/src/applications/osgearth_tracks/osgearth_tracks.cpp b/src/applications/osgearth_tracks/osgearth_tracks.cpp
index 2c78fe8..77481c7 100644
--- a/src/applications/osgearth_tracks/osgearth_tracks.cpp
+++ b/src/applications/osgearth_tracks/osgearth_tracks.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
@@ -63,7 +66,6 @@ using namespace osgEarth::Symbology;
 static MGRSFormatter s_format(MGRSFormatter::PRECISION_10000M);
 
 // globals for this demo
-osg::StateSet*      g_declutterStateSet = 0L;
 bool                g_showCoords        = true;
 optional<float>     g_duration          = 60.0;
 unsigned            g_numTracks         = 500;
@@ -193,6 +195,8 @@ createTrackNodes( MapNode* mapNode, osg::Group* parent, const TrackNodeFieldSche
         data->setPriority( float(i) );
         track->setAnnotationData( data );
 
+        Decluttering::setEnabled(track->getOrCreateStateSet(), true);
+
         parent->addChild( track );
 
         // add a simulator for this guy
@@ -221,7 +225,7 @@ createControls( osgViewer::View* view )
     // checkbox that toggles decluttering of tracks
     struct ToggleDecluttering : public ControlEventHandler {
         void onValueChanged( Control* c, bool on ) {
-            Decluttering::setEnabled( g_declutterStateSet, on );
+            Decluttering::setEnabled( on );
         }
     };
     HBox* dcToggle = vbox->addControl( new HBox() );
@@ -319,7 +323,6 @@ main(int argc, char** argv)
     // create some track nodes.
     TrackSims trackSims;
     osg::Group* tracks = new osg::Group();
-    //HTMGroup* tracks = new HTMGroup();
     createTrackNodes( mapNode, tracks, schema, trackSims );
     root->addChild( tracks );
 
@@ -328,8 +331,6 @@ main(int argc, char** argv)
     // sorting, which looks at the AnnotationData::priority field for each drawable.
     // (By default, objects are sorted by disatnce-to-camera.) Finally, we customize 
     // a couple of the decluttering options to get the animation effects we want.
-    g_declutterStateSet = tracks->getOrCreateStateSet();
-    Decluttering::setEnabled( g_declutterStateSet, true );
     g_dcOptions = Decluttering::getOptions();
     g_dcOptions.inAnimationTime()  = 1.0f;
     g_dcOptions.outAnimationTime() = 1.0f;
@@ -342,6 +343,7 @@ main(int argc, char** argv)
 
     // configure a UI for controlling the demo
     createControls( &viewer );
-
+    
+    viewer.getCamera()->setSmallFeatureCullingPixelSize(-1.0f);
     viewer.run();
 }
diff --git a/src/applications/osgearth_transform/osgearth_transform.cpp b/src/applications/osgearth_transform/osgearth_transform.cpp
index 3d3a282..528bd2e 100644
--- a/src/applications/osgearth_transform/osgearth_transform.cpp
+++ b/src/applications/osgearth_transform/osgearth_transform.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
@@ -34,6 +37,7 @@
 
 #define LC "[osgearth_transform] "
 
+
 using namespace osgEarth;
 using namespace osgEarth::Util;
 
@@ -132,7 +136,7 @@ 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("cessna.osgt.1,-1,1.scale");
+    osg::Node* model = osgDB::readNodeFile("../data/axes.osgt.(1000).scale");
     if ( !model )
         return usage(argv[0]);
 
@@ -157,7 +161,13 @@ main(int argc, char** argv)
     app.apply();
 
     em->setTetherNode( app.geo );
-    em->setViewpoint(osgEarth::Viewpoint(0, 0, 0, -45, -20, model->getBound().radius()*10.0));
+
+    osgEarth::Viewpoint vp;
+    vp.setNode( app.geo );
+    vp.heading()->set( -45.0, Units::DEGREES );
+    vp.pitch()->set( -20.0, Units::DEGREES );
+    vp.range()->set( model->getBound().radius()*10.0, Units::METERS );
+    em->setViewpoint( vp );
 
     return viewer.run();
 }
diff --git a/src/applications/osgearth_version/osgearth_version.cpp b/src/applications/osgearth_version/osgearth_version.cpp
index 9597105..94ae058 100644
--- a/src/applications/osgearth_version/osgearth_version.cpp
+++ b/src/applications/osgearth_version/osgearth_version.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/applications/osgearth_viewer/osgearth_viewer.cpp b/src/applications/osgearth_viewer/osgearth_viewer.cpp
index b8935cb..62edb19 100644
--- a/src/applications/osgearth_viewer/osgearth_viewer.cpp
+++ b/src/applications/osgearth_viewer/osgearth_viewer.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
@@ -22,6 +25,9 @@
 #include <osgEarthUtil/EarthManipulator>
 #include <osgEarthUtil/ExampleResources>
 
+#include <osgEarth/Units>
+#include <osgEarth/Viewpoint>
+
 #define LC "[viewer] "
 
 using namespace osgEarth;
@@ -56,7 +62,7 @@ main(int argc, char** argv)
     viewer.getDatabasePager()->setUnrefImageDataAfterApplyPolicy( false, false );
 
     // install our default manipulator (do this before calling load)
-    viewer.setCameraManipulator( new EarthManipulator() );
+    viewer.setCameraManipulator( new EarthManipulator() );    
 
     // load an earth file, and support all or our example command-line options
     // and earth file <external> tags    
@@ -65,7 +71,6 @@ main(int argc, char** argv)
     {
         viewer.setSceneData( node );
 
-        viewer.getCamera()->setNearFarRatio(0.00002);
         viewer.getCamera()->setSmallFeatureCullingPixelSize(-1.0f);
 
         return viewer.run();
diff --git a/src/osgEarth/AlphaEffect b/src/osgEarth/AlphaEffect
index 07191c3..6984d8f 100644
--- a/src/osgEarth/AlphaEffect
+++ b/src/osgEarth/AlphaEffect
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/osgEarth/AlphaEffect.cpp b/src/osgEarth/AlphaEffect.cpp
index 876b28f..5b0a325 100644
--- a/src/osgEarth/AlphaEffect.cpp
+++ b/src/osgEarth/AlphaEffect.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
@@ -22,20 +25,10 @@
 #include <osgEarth/VirtualProgram>
 #include <osgEarth/Registry>
 #include <osgEarth/Capabilities>
+#include <osgEarth/Shaders>
 
 using namespace osgEarth;
 
-namespace
-{
-    const char fragment[] =
-        "#version " GLSL_VERSION_STR "\n"
-        GLSL_DEFAULT_PRECISION_FLOAT "\n"
-        "uniform float oe_alphaeffect_alpha;\n"
-        "void oe_alphaeffect_fragment(inout vec4 color) {\n"
-        "    color = color * oe_alphaeffect_alpha;\n"
-        "}\n";
-}
-
 AlphaEffect::AlphaEffect()
 {
     init();
@@ -53,7 +46,7 @@ AlphaEffect::init()
     _active = Registry::capabilities().supportsGLSL(110u);
     if ( _active )
     {
-        _alphaUniform = new osg::Uniform(osg::Uniform::FLOAT, "oe_alphaeffect_alpha");
+        _alphaUniform = new osg::Uniform(osg::Uniform::FLOAT, "oe_alphaEffect_alpha");
         _alphaUniform->set( 1.0f );
     }
 }
@@ -87,7 +80,8 @@ AlphaEffect::attach(osg::StateSet* stateset)
         _statesets.push_back(stateset);
         VirtualProgram* vp = VirtualProgram::getOrCreate(stateset);
         vp->setName( "osgEarth.AlphaEffect" );
-        vp->setFunction( "oe_alphaeffect_fragment", fragment, ShaderComp::LOCATION_FRAGMENT_COLORING, 2 );
+        Shaders pkg;
+        pkg.load( vp, pkg.AlphaEffectFragment );
         stateset->addUniform( _alphaUniform.get() );
     }
 }
@@ -120,7 +114,8 @@ AlphaEffect::detach(osg::StateSet* stateset)
         VirtualProgram* vp = VirtualProgram::get( stateset );
         if ( vp )
         {
-            vp->removeShader( "oe_alphaeffect_fragment" );
+            Shaders pkg;
+            pkg.unload( vp, pkg.AlphaEffectFragment );
         }
     }
 }
diff --git a/src/osgEarth/AlphaEffect.frag.glsl b/src/osgEarth/AlphaEffect.frag.glsl
new file mode 100644
index 0000000..7a3501a
--- /dev/null
+++ b/src/osgEarth/AlphaEffect.frag.glsl
@@ -0,0 +1,12 @@
+#version 110
+
+#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 b/src/osgEarth/AutoScale
index 289daa6..a2cfa7b 100644
--- a/src/osgEarth/AutoScale
+++ b/src/osgEarth/AutoScale
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/osgEarth/AutoScale.cpp b/src/osgEarth/AutoScale.cpp
index 70c6495..21b32ff 100644
--- a/src/osgEarth/AutoScale.cpp
+++ b/src/osgEarth/AutoScale.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -39,60 +39,8 @@ namespace
 
         "uniform float oe_autoscale_zp; \n"
 
-#if 0
-        "uniform float oe_autoscale_scale; \n"
-
-        "vec3 oe_autoscale_multquat( in vec3 v, in vec4 quat ) \n"
-        "{ \n"
-        "    vec3 uv, uuv; \n"
-        "    uv = cross(quat.xyz, v); \n"
-        "    uuv = cross(quat.xyz, uv); \n"
-        "    uv *= (2.0 * quat.w); \n"
-        "    uuv *= 2.0; \n"
-        "    return v + uv + uuv; \n"
-        "} \n"
-
-        "vec4 oe_autoscale_makequat( in vec3 a, in vec3 b ) \n"
-        "{ \n"
-        "    float dotProdPlus1 = 1.0 + dot(a, b); \n"
-
-        "    if ( dotProdPlus1 < 0.000001 ) { \n"
-        "        if (abs(a.x) < 0.6) { \n"
-        "            float n = sqrt(1.0 - a.x*a.x); \n"
-        "            return vec4(0.0, a.z/n, -a.y/n, 0.0); \n"
-        "        } \n"
-        "        else if (abs(a.y) < 0.6) { \n"
-        "            float n = sqrt(1.0 - a.y*a.y); \n"
-        "            return vec4(-a.z/n, 0.0, a.x/n, 0.0); \n"
-        "        } \n"
-        "        else { \n"
-        "            float n = sqrt(1.0 - a.z*a.z); \n"
-        "            return vec4(a.y/n, -a.x/n, 0.0, 0.0); \n"
-        "        } \n"
-        "    } \n"
-        "    else { \n"
-        "        float s = sqrt(0.5 * dotProdPlus1); \n"
-        "        vec3 tmp = cross(a, b/(2.0*s)); \n"
-        "        return vec4(tmp, s); \n"
-        "    } \n"
-        "} \n"
-
-        "void oe_autoscale_rotate( inout vec4 VertexVIEW ) \n"
-        "{ \n"
-        "    float s = oe_autoscale_scale; \n"
-        "    mat4 m2; \n"
-        "    m2[0] = vec4( s,   0.0,  0.0, 0.0 ); \n"
-        "    m2[1] = vec4( 0.0, 0.0, -s,   0.0 ); \n"
-        "    m2[2] = vec4( 0.0, s,    0.0, 0.0 ); \n"
-        "    m2[3] = gl_ModelViewMatrix[3]; \n"
-        "    VertexVIEW = m2 * gl_Vertex; \n"
-        "} \n"
-#endif
-
         "void oe_autoscale_vertex( inout vec4 VertexVIEW ) \n"
         "{ \n"
-        //"    oe_autoscale_rotate(VertexVIEW); \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
@@ -134,8 +82,7 @@ namespace
             _stateset = new osg::StateSet();
 
             VirtualProgram* vp = VirtualProgram::getOrCreate(_stateset.get());
-            vp->setFunction( "oe_autoscale_vertex", vs, ShaderComp::LOCATION_VERTEX_VIEW );
-            //_stateset->setAttributeAndModes( vp, osg::StateAttribute::ON );
+            vp->setFunction( "oe_autoscale_vertex", vs, ShaderComp::LOCATION_VERTEX_VIEW, 0.5f );
 
             _zp = _stateset->getOrCreateUniform("oe_autoscale_zp", osg::Uniform::FLOAT);
         }
diff --git a/src/osgEarth/Bounds b/src/osgEarth/Bounds
index a595369..425c826 100644
--- a/src/osgEarth/Bounds
+++ b/src/osgEarth/Bounds
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/osgEarth/Bounds.cpp b/src/osgEarth/Bounds.cpp
index bd83e21..b7cfec2 100644
--- a/src/osgEarth/Bounds.cpp
+++ b/src/osgEarth/Bounds.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarth/CMakeLists.txt b/src/osgEarth/CMakeLists.txt
index a2a89e8..fbc3ac7 100644
--- a/src/osgEarth/CMakeLists.txt
+++ b/src/osgEarth/CMakeLists.txt
@@ -19,7 +19,28 @@ 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)
+
+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
     AlphaEffect
     AutoScale
@@ -30,6 +51,7 @@ SET(LIB_PUBLIC_HEADERS
     CachePolicy
     CacheSeed
     Capabilities
+    Clamping
     ClampableNode
     ClampingTechnique
     ColorFilter
@@ -43,15 +65,16 @@ SET(LIB_PUBLIC_HEADERS
     Decluttering
     DepthOffset
     DPLineSegmentIntersector
-    Draggers
     DrapeableNode
     DrapingTechnique
     DrawInstanced
     ECEF
+    ElevationField
     ElevationLayer
     ElevationLOD
     ElevationQuery
     Export
+	Extension
     FadeEffect
     FileUtils
     GeoCommon
@@ -60,6 +83,7 @@ SET(LIB_PUBLIC_HEADERS
     GeoMath
 	GeoTransform
     HeightFieldUtils
+    Horizon
     HTTPClient
     ImageLayer
     ImageMosaic
@@ -86,24 +110,29 @@ SET(LIB_PUBLIC_HEADERS
     MemCache
     ModelLayer
     ModelSource
+    NativeProgramAdapter
     NodeUtils
     Notify
     optional
+    ObjectIndex
     OverlayDecorator
     OverlayNode
 	PhongLightingEffect
-    Pickers
+    Picker
+    IntersectionPicker
     PrimitiveIntersector
     Profile
+    Profiler
     Progress
     Random
     Registry
     Revisioning
+    Shaders
     ShaderFactory
     ShaderGenerator
+    ShaderLoader
     ShaderUtils
 	SharedSARepo
-    SparseTexture2DArray
     SpatialReference
     StateSetCache
 	StateSetLOD
@@ -114,12 +143,13 @@ SET(LIB_PUBLIC_HEADERS
     TerrainLayer
     TerrainOptions
     TerrainEngineNode
+    TerrainTileNode
     Tessellator
     TextureCompositor
     TileKey
     TileHandler
+	TileSource
     TileVisitor
-    TileSource
     TimeControl
     TraversalData
     ThreadingUtils
@@ -165,11 +195,7 @@ IF (OSGEARTH_EMBED_GIT_SHA)
 	set(VERSION_GIT_SOURCE "${CMAKE_CURRENT_BINARY_DIR}/VersionGit.cpp")	
 ENDIF (OSGEARTH_EMBED_GIT_SHA)
 
-
-ADD_LIBRARY(${LIB_NAME} ${OSGEARTH_USER_DEFINED_DYNAMIC_OR_STATIC}
-    ${LIB_PUBLIC_HEADERS}
-    ${TINYXML_SRC}
-    ${VERSION_GIT_SOURCE}
+set(TARGET_SRC
     AlphaEffect.cpp
     AutoScale.cpp
     Bounds.cpp
@@ -178,6 +204,7 @@ ADD_LIBRARY(${LIB_NAME} ${OSGEARTH_USER_DEFINED_DYNAMIC_OR_STATIC}
     CachePolicy.cpp
     CacheSeed.cpp
     Capabilities.cpp
+    Clamping.cpp
     ClampableNode.cpp
     ClampingTechnique.cpp
     ColorFilter.cpp
@@ -188,15 +215,16 @@ ADD_LIBRARY(${LIB_NAME} ${OSGEARTH_USER_DEFINED_DYNAMIC_OR_STATIC}
     DateTime.cpp
     Decluttering.cpp
     DepthOffset.cpp
-    Draggers.cpp
     DPLineSegmentIntersector.cpp
     DrapeableNode.cpp
     DrapingTechnique.cpp
     DrawInstanced.cpp
     ECEF.cpp
+    ElevationField.cpp
     ElevationLayer.cpp
     ElevationLOD.cpp
     ElevationQuery.cpp
+	Extension.cpp
     FadeEffect.cpp
     FileUtils.cpp
     GeoData.cpp
@@ -204,6 +232,7 @@ ADD_LIBRARY(${LIB_NAME} ${OSGEARTH_USER_DEFINED_DYNAMIC_OR_STATIC}
     GeoMath.cpp
 	GeoTransform.cpp
     HeightFieldUtils.cpp
+    Horizon.cpp
     HTTPClient.cpp
     ImageLayer.cpp
     ImageMosaic.cpp
@@ -230,20 +259,22 @@ ADD_LIBRARY(${LIB_NAME} ${OSGEARTH_USER_DEFINED_DYNAMIC_OR_STATIC}
     ModelSource.cpp
     NodeUtils.cpp
     Notify.cpp
+    ObjectIndex.cpp
     OverlayDecorator.cpp
     OverlayNode.cpp
 	PhongLightingEffect.cpp
-    Pickers.cpp
+    IntersectionPicker.cpp
     PrimitiveIntersector.cpp
     Profile.cpp
+    Profiler.cpp
     Progress.cpp
     Random.cpp
     Registry.cpp
     Revisioning.cpp
     ShaderFactory.cpp
     ShaderGenerator.cpp
+    ShaderLoader.cpp
     ShaderUtils.cpp
-    SparseTexture2DArray.cpp
     SpatialReference.cpp
     StateSetCache.cpp
 	StateSetLOD.cpp
@@ -270,7 +301,17 @@ ADD_LIBRARY(${LIB_NAME} ${OSGEARTH_USER_DEFINED_DYNAMIC_OR_STATIC}
     Viewpoint.cpp
     VirtualProgram.cpp
     XmlUtils.cpp
-)
+    ${SHADERS_CPP} )
+
+
+ADD_LIBRARY(
+	${LIB_NAME} ${OSGEARTH_USER_DEFINED_DYNAMIC_OR_STATIC}
+    ${LIB_PUBLIC_HEADERS}
+    ${TINYXML_SRC}
+    ${VERSION_GIT_SOURCE}
+	${TARGET_SRC}
+    ${TARGET_GLSL}
+    ${TARGET_IN} )
 
 INCLUDE_DIRECTORIES(${GDAL_INCLUDE_DIR} ${CURL_INCLUDE_DIR} ${OSG_INCLUDE_DIR} )
 
diff --git a/src/osgEarth/Cache b/src/osgEarth/Cache
index a17f437..a3979db 100644
--- a/src/osgEarth/Cache
+++ b/src/osgEarth/Cache
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
@@ -24,7 +27,7 @@
 #include <osgEarth/CacheBin>
 #include <osgEarth/Config>
 #include <osgEarth/TileKey>
-#include <osgEarth/ThreadingUtils>
+#include <osgEarth/Containers>
 #include <sys/types.h>
 
 // environment variables
@@ -70,7 +73,7 @@ namespace osgEarth
 
 //--------------------------------------------------------------------
 
-    typedef Threading::PerObjectRefMap<std::string, CacheBin> ThreadSafeCacheBinMap;
+    typedef PerObjectRefMap<std::string, CacheBin> ThreadSafeCacheBinMap;
 
 
     /**
diff --git a/src/osgEarth/Cache.cpp b/src/osgEarth/Cache.cpp
index 121b138..7cde03e 100644
--- a/src/osgEarth/Cache.cpp
+++ b/src/osgEarth/Cache.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -89,7 +89,7 @@ CacheFactory::create( const CacheOptions& options )
     }
     else // try to load from a plugin
     {
-        osg::ref_ptr<osgDB::Options> rwopt = Registry::instance()->cloneOrCreateOptions();
+        osg::ref_ptr<osgDB::Options> rwopt = Registry::cloneOrCreateOptions();
         rwopt->setPluginData( CACHE_OPTIONS_TAG, (void*)&options );
 
         std::string driverExt = std::string(".osgearth_cache_") + options.getDriver();
diff --git a/src/osgEarth/CacheBin b/src/osgEarth/CacheBin
index b5751b1..fae3516 100644
--- a/src/osgEarth/CacheBin
+++ b/src/osgEarth/CacheBin
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/osgEarth/CacheEstimator b/src/osgEarth/CacheEstimator
index b67a096..f9781de 100644
--- a/src/osgEarth/CacheEstimator
+++ b/src/osgEarth/CacheEstimator
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/osgEarth/CacheEstimator.cpp b/src/osgEarth/CacheEstimator.cpp
index e7d94a0..51d5aa1 100644
--- a/src/osgEarth/CacheEstimator.cpp
+++ b/src/osgEarth/CacheEstimator.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarth/CachePolicy b/src/osgEarth/CachePolicy
index b6764d2..68b1e3f 100644
--- a/src/osgEarth/CachePolicy
+++ b/src/osgEarth/CachePolicy
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/osgEarth/CachePolicy.cpp b/src/osgEarth/CachePolicy.cpp
index d6dd270..12ebb3d 100644
--- a/src/osgEarth/CachePolicy.cpp
+++ b/src/osgEarth/CachePolicy.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarth/CacheSeed b/src/osgEarth/CacheSeed
index a68712f..2c88a74 100644
--- a/src/osgEarth/CacheSeed
+++ b/src/osgEarth/CacheSeed
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/osgEarth/CacheSeed.cpp b/src/osgEarth/CacheSeed.cpp
index 14929c4..6a3bc25 100644
--- a/src/osgEarth/CacheSeed.cpp
+++ b/src/osgEarth/CacheSeed.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
@@ -56,6 +59,14 @@ bool CacheTileHandler::handleTile(const TileKey& key, const TileVisitor& tv)
             return true;
         }            
     }
+
+    // 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))
+    {
+        return true;
+    }
+
     return false;        
 }   
 
@@ -112,7 +123,7 @@ _visitor(new TileVisitor())
 
 TileVisitor* CacheSeed::getVisitor() const
 {
-    return _visitor;
+    return _visitor.get();
 }
 
 void CacheSeed::setVisitor(TileVisitor* visitor)
diff --git a/src/osgEarth/Capabilities b/src/osgEarth/Capabilities
index 387b1b3..5afde8a 100644
--- a/src/osgEarth/Capabilities
+++ b/src/osgEarth/Capabilities
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
@@ -141,6 +144,12 @@ namespace osgEarth
         /** whether the GPU supports a texture compression scheme */
         bool supportsTextureCompression(const osg::Texture::InternalFormatMode& mode) const;
 
+        /** whether texture buffers are supported */
+        bool supportsTextureBuffer() const { return _supportsTextureBuffer; }
+
+        /** Maximum size of a texture buffer, when supportsTextureBuffer() is true. */
+        int getMaxTextureBufferSize() const { return _maxTextureBufferSize; }
+
     protected:
         Capabilities();
 
@@ -183,6 +192,8 @@ namespace osgEarth
         bool _supportsETC;
         bool _supportsRGTC;
         bool _isGLES;
+        bool _supportsTextureBuffer;
+        int  _maxTextureBufferSize;
 
     public:
         friend class Registry;
diff --git a/src/osgEarth/Capabilities.cpp b/src/osgEarth/Capabilities.cpp
index f8f2f12..e0d174c 100644
--- a/src/osgEarth/Capabilities.cpp
+++ b/src/osgEarth/Capabilities.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -135,7 +135,9 @@ _supportsS3TC           ( false ),
 _supportsPVRTC          ( false ),
 _supportsARBTC          ( false ),
 _supportsETC            ( false ),
-_supportsRGTC           ( false )
+_supportsRGTC           ( false ),
+_supportsTextureBuffer  ( false ),
+_maxTextureBufferSize   ( 0 )
 {
     // little hack to force the osgViewer library to link so we can create a graphics context
     osgViewerGetVersion();
@@ -171,7 +173,11 @@ _supportsRGTC           ( false )
         }
         else
         {
-            _supportsGLSL = GL2->isGlslSupported();
+#if OSG_MIN_VERSION_REQUIRED(3,3,3)
+            _supportsGLSL = GL2->isGlslSupported;
+#else
+			_supportsGLSL = GL2->isGlslSupported();
+#endif
         }
 
         OE_INFO << LC << "Detected hardware capabilities:" << std::endl;
@@ -186,7 +192,7 @@ _supportsRGTC           ( false )
         OE_INFO << LC << "  Version = " << _version << std::endl;
 
         glGetIntegerv( GL_MAX_TEXTURE_UNITS, &_maxFFPTextureUnits );
-        OE_INFO << LC << "  Max FFP texture units = " << _maxFFPTextureUnits << std::endl;
+        //OE_INFO << LC << "  Max FFP texture units = " << _maxFFPTextureUnits << std::endl;
 
         glGetIntegerv( GL_MAX_TEXTURE_IMAGE_UNITS_ARB, &_maxGPUTextureUnits );
         OE_INFO << LC << "  Max GPU texture units = " << _maxGPUTextureUnits << std::endl;
@@ -230,7 +236,11 @@ _supportsRGTC           ( false )
 
         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;
         }
 
@@ -250,17 +260,17 @@ _supportsRGTC           ( false )
         OE_INFO << LC << "  Multitexturing = " << SAYBOOL(_supportsMultiTexture) << std::endl;
 
         _supportsStencilWrap = osg::isGLExtensionSupported( id, "GL_EXT_stencil_wrap" );
-        OE_INFO << LC << "  Stencil wrapping = " << SAYBOOL(_supportsStencilWrap) << std::endl;
+        //OE_INFO << LC << "  Stencil wrapping = " << SAYBOOL(_supportsStencilWrap) << std::endl;
 
         _supportsTwoSidedStencil = osg::isGLExtensionSupported( id, "GL_EXT_stencil_two_side" );
-        OE_INFO << LC << "  2-sided stencils = " << SAYBOOL(_supportsTwoSidedStencil) << std::endl;
+        //OE_INFO << LC << "  2-sided stencils = " << SAYBOOL(_supportsTwoSidedStencil) << std::endl;
 
         _supportsDepthPackedStencilBuffer = osg::isGLExtensionSupported( id, "GL_EXT_packed_depth_stencil" ) || 
                                             osg::isGLExtensionSupported( id, "GL_OES_packed_depth_stencil" );
-        OE_INFO << LC << "  depth-packed stencil = " << SAYBOOL(_supportsDepthPackedStencilBuffer) << std::endl;
+        //OE_INFO << LC << "  depth-packed stencil = " << SAYBOOL(_supportsDepthPackedStencilBuffer) << std::endl;
 
         _supportsOcclusionQuery = osg::isGLExtensionSupported( id, "GL_ARB_occlusion_query" );
-        OE_INFO << LC << "  occlusion query = " << SAYBOOL(_supportsOcclusionQuery) << std::endl;
+        //OE_INFO << LC << "  occlusion query = " << SAYBOOL(_supportsOcclusionQuery) << std::endl;
 
         _supportsDrawInstanced = 
             _supportsGLSL &&
@@ -268,7 +278,7 @@ _supportsRGTC           ( false )
         OE_INFO << LC << "  draw instanced = " << SAYBOOL(_supportsDrawInstanced) << std::endl;
 
         glGetIntegerv( GL_MAX_UNIFORM_BLOCK_SIZE, &_maxUniformBlockSize );
-        OE_INFO << LC << "  max uniform block size = " << _maxUniformBlockSize << std::endl;
+        //OE_INFO << LC << "  max uniform block size = " << _maxUniformBlockSize << std::endl;
 
         _supportsUniformBufferObjects = 
             _supportsGLSL &&
@@ -285,6 +295,23 @@ _supportsRGTC           ( false )
             osg::isGLExtensionSupported( id, "GL_ARB_texture_non_power_of_two" );
         OE_INFO << LC << "  NPOT textures = " << SAYBOOL(_supportsNonPowerOfTwoTextures) << std::endl;
 
+#if OSG_VERSION_GREATER_OR_EQUAL(3, 1, 7)
+        _supportsTextureBuffer = 
+            osg::isGLExtensionOrVersionSupported( id, "GL_ARB_texture_buffer_object", 3.0 ) ||
+            osg::isGLExtensionOrVersionSupported( id, "GL_EXT_texture_buffer_object", 3.0 );
+
+        if ( _supportsTextureBuffer )
+        {
+            glGetIntegerv( GL_MAX_TEXTURE_BUFFER_SIZE, &_maxTextureBufferSize );
+        }
+#endif
+
+        OE_INFO << LC << "  Texture buffers = " << SAYBOOL(_supportsTextureBuffer) << std::endl;
+        if ( _supportsTextureBuffer )
+        {
+            OE_INFO << LC << "  Texture buffer max size = " << _maxTextureBufferSize << std::endl;
+        }            
+
 
         // Writing to gl_FragDepth is not supported under GLES:
 #if (defined(OSG_GLES1_AVAILABLE) || defined(OSG_GLES2_AVAILABLE))
diff --git a/src/osgEarth/ClampableNode.cpp b/src/osgEarth/ClampableNode.cpp
index d45a0e4..5387ff0 100644
--- a/src/osgEarth/ClampableNode.cpp
+++ b/src/osgEarth/ClampableNode.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarth/Clamping b/src/osgEarth/Clamping
new file mode 100644
index 0000000..caf8db0
--- /dev/null
+++ b/src/osgEarth/Clamping
@@ -0,0 +1,78 @@
+/* -*-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_CLAMPING_H
+#define OSGEARTH_CLAMPING_H
+
+#include <osgEarth/Common>
+#include <osg/Node>
+#include <osg/Geometry>
+
+namespace osgEarth
+{
+    /**
+     * Support for GPU Clamping.
+     *
+     * GPU Clamping makes use of a custom vertex attribute. The contents of this 
+     * vec4 attribute are:
+     *   attr[0] : X coordinate of reference vertex
+     *   attr[1] : Y coordinate of reference vertex
+     *   attr[2] : Vertical offset to apply after clamping
+     *   attr[3] : 0 = clamp vertex directly; 1 = clamp indirectly using reference vertex
+     */
+    class OSGEARTH_EXPORT Clamping
+    {
+    public:
+        // GLSL attribute binding location for clamping vertex attributes
+        static const int AnchorAttrLocation;
+
+        // Name of the clamping anchor vertex attribute
+        static const char* AnchorAttrName;
+
+        // Name of the boolean uniform that tells the shader whether the
+        // geometry has clamping attributes.
+        static const char* HasAttrsUniformName;
+
+        // Name of the uniform used to apply an altitude offset to clamped geometry
+        static const char* AltitudeOffsetUniformName;
+
+        // When setting clamping vertex attributes, use these constants in
+        // the alpha channel to indicate whether the vertex is to be clamped
+        // to the reference vertex in the attribute data, or to the ground.
+        static const float ClampToAnchor;
+        static const float ClampToGround;
+
+        // Installs a uniform on the stateset indicating that geometry has the
+        // clamping vertex attribute installed.
+        static void installHasAttrsUniform(
+            osg::StateSet* stateset );
+
+        static void applyDefaultClampingAttrs(
+            osg::Node* node,
+            float      verticalOffset =0.0f);
+
+        static void applyDefaultClampingAttrs(
+            osg::Geometry* geom,
+            float          verticalOffset =0.0f);
+    };
+}
+
+#endif // OSGEARTH_CLAMPING_H
diff --git a/src/osgEarth/Clamping.cpp b/src/osgEarth/Clamping.cpp
new file mode 100644
index 0000000..8745ec2
--- /dev/null
+++ b/src/osgEarth/Clamping.cpp
@@ -0,0 +1,110 @@
+/* -*-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/Clamping>
+#include <osg/Drawable>
+#include <osg/NodeVisitor>
+#include <osg/Geode>
+#include <osg/Geometry>
+
+using namespace osgEarth;
+
+const int   Clamping::AnchorAttrLocation        = osg::Drawable::ATTRIBUTE_6;
+const char* Clamping::AnchorAttrName            = "oe_clamp_attrs";
+const char* Clamping::HasAttrsUniformName       = "oe_clamp_hasAttrs";
+const char* Clamping::AltitudeOffsetUniformName = "oe_clamp_altitudeOffset";
+
+const float Clamping::ClampToAnchor = 1.0f;
+const float Clamping::ClampToGround = 0.0f;
+
+namespace
+{
+    struct ApplyDefaultsVisitor : public osg::NodeVisitor
+    {
+        float _verticalOffset;
+
+        ApplyDefaultsVisitor(float verticalOffset)
+        {
+            _verticalOffset = verticalOffset;
+            setTraversalMode( TRAVERSE_ALL_CHILDREN );
+            setNodeMaskOverride( ~0 );
+        }
+
+        void apply(osg::Geometry* geom)
+        {
+            if ( geom )
+            {
+                osg::Vec3Array* verts = static_cast<osg::Vec3Array*>(geom->getVertexArray());
+                osg::Vec4Array* anchors = new osg::Vec4Array();
+                anchors->reserve( verts->size() );
+                for(unsigned i=0; i<verts->size(); ++i)
+                {
+                    anchors->push_back( osg::Vec4f(
+                        (*verts)[i].x(), (*verts)[i].y(),
+                        _verticalOffset,
+                        Clamping::ClampToGround) );
+                }
+
+                geom->setVertexAttribArray    (Clamping::AnchorAttrLocation, anchors);
+                geom->setVertexAttribBinding  (Clamping::AnchorAttrLocation, geom->BIND_PER_VERTEX);
+                geom->setVertexAttribNormalize(Clamping::AnchorAttrLocation, false);
+            }
+        }
+
+        void apply(osg::Geode& geode) 
+        {
+            for(unsigned i=0; i<geode.getNumDrawables(); ++i)
+            {
+                apply( geode.getDrawable(i)->asGeometry() );
+            }
+        }
+    };
+}
+
+void
+Clamping::applyDefaultClampingAttrs(osg::Node* node, float verticalOffset)
+{
+    if ( node )
+    {
+        ApplyDefaultsVisitor visitor( verticalOffset );
+        node->accept( visitor );
+    }
+}
+
+void
+Clamping::applyDefaultClampingAttrs(osg::Geometry* geom, float verticalOffset)
+{
+    if ( geom )
+    {
+        ApplyDefaultsVisitor visitor( verticalOffset );
+        visitor.apply( geom );
+    }
+}
+
+
+void
+Clamping::installHasAttrsUniform(osg::StateSet* stateset)
+{
+    if ( stateset )
+    {
+        stateset->addUniform( new osg::Uniform(Clamping::HasAttrsUniformName, true) );
+    }
+}
diff --git a/src/osgEarth/ClampingTechnique b/src/osgEarth/ClampingTechnique
index dca03a1..31603eb 100644
--- a/src/osgEarth/ClampingTechnique
+++ b/src/osgEarth/ClampingTechnique
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
@@ -24,7 +27,7 @@
 #include <osg/TexGenNode>
 #include <osg/Uniform>
 
-#define OSGEARTH_CLAMPING_BIN "osgEarth::ClampingBin"
+#define OSGEARTH_CLAMPING_BIN           "osgEarth::ClampingBin"
 
 namespace osgEarth
 {
diff --git a/src/osgEarth/ClampingTechnique.cpp b/src/osgEarth/ClampingTechnique.cpp
index c9de48d..8a5608c 100644
--- a/src/osgEarth/ClampingTechnique.cpp
+++ b/src/osgEarth/ClampingTechnique.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
@@ -23,6 +26,8 @@
 #include <osgEarth/VirtualProgram>
 #include <osgEarth/MapNode>
 #include <osgEarth/Utils>
+#include <osgEarth/Shaders>
+#include <osgEarth/Clamping>
 
 #include <osg/Depth>
 #include <osg/PolygonMode>
@@ -75,92 +80,6 @@ namespace
 
 ClampingTechnique::TechniqueProvider ClampingTechnique::Provider = s_providerImpl;
 
-//--------------------------------------------------------------------------
-
-
-// SUPPORT_Z is a placeholder - we need to come up with another method for
-// clamping verts relative to Z without needing the current Model Matrix
-// (as we would now). Leave this #undef's until further notice.
-#define SUPPORT_Z 1
-#undef  SUPPORT_Z
-
-namespace
-{
-    const char clampingVertexShader[] =
-
-        "#version " GLSL_VERSION_STR "\n"
-        GLSL_DEFAULT_PRECISION_FLOAT "\n"
-
-         // uniforms from this ClampingTechnique:
-         "uniform sampler2D oe_clamp_depthTex; \n"
-         "uniform mat4 oe_clamp_cameraView2depthClip; \n"
-#ifdef SUPPORT_Z
-         "uniform mat4 oe_clamp_depthClip2depthView; \n"
-         "uniform mat4 oe_clamp_depthView2cameraView; \n"
-#else
-         "uniform mat4 oe_clamp_depthClip2cameraView; \n"
-#endif
-
-         "uniform float oe_clamp_horizonDistance; \n"
-         "varying float oe_clamp_alphaFactor; \n"
-
-         "void oe_clamp_vertex(inout vec4 VertexVIEW) \n"
-         "{ \n"
-         //   start by mocing the vertex into view space.
-         "    vec4 v_view_orig = VertexVIEW; \n"
-
-         //   if the distance to the vertex is beyond the visible horizon,
-         //   "hide" the vertex by setting its alpha component to 0.0.
-         //   if this is the case, there's no point in continuing -- so we 
-         //   would normally branch here, but since this happens on the fly 
-         //   the shader engine will run both branches regardless. So keep going.
-         "    float vert_distance = length(v_view_orig.xyz/v_view_orig.w); \n"
-         "    oe_clamp_alphaFactor = clamp(oe_clamp_horizonDistance - vert_distance, 0.0, 1.0 ); \n"
-
-         //   transform the vertex into the depth texture's clip coordinates.
-         "    vec4 v_depthClip = oe_clamp_cameraView2depthClip * v_view_orig; \n"
-
-         //   sample the depth map.
-         "    float d = texture2DProj( oe_clamp_depthTex, v_depthClip ).r; \n"
-
-         //   blank it out if it's at the far plane (no terrain visible)
-         "    if ( d > 0.999999 ) { oe_clamp_alphaFactor = 0.0; } \n"
-
-         //   now transform into depth-view space so we can apply the height-above-ground:
-         "    vec4 p_depthClip = vec4(v_depthClip.x, v_depthClip.y, d, 1.0); \n"
-
-#ifdef SUPPORT_Z
-         "    vec4 p_depthView = oe_clamp_depthClip2depthView * p_depthClip; \n"
-
-              // next, apply the vert's Z value for that ground offset.
-              // TODO: This calculation is not right!
-              //       I think perhaps the model matrix is not earth-aligned.
-         "    p_depthView.z += gl_Vertex.z*gl_Vertex.w/p_depthView.w; \n"
-
-              // then transform the vert back into camera view space.
-         "    VertexVIEW = oe_clamp_depthView2cameraView * p_depthView; \n"
-#else
-              // transform the depth-clip point back into camera view coords.
-         "    VertexVIEW = oe_clamp_depthClip2cameraView * p_depthClip; \n"
-#endif
-         "} \n";
-
-
-    const char clampingFragmentShader[] =
-
-        "#version " GLSL_VERSION_STR "\n"
-        GLSL_DEFAULT_PRECISION_FLOAT "\n"
-
-        "varying float oe_clamp_alphaFactor; \n"
-
-        "void oe_clamp_fragment(inout vec4 color)\n"
-        "{ \n"
-             // adjust the alpha component to "hide" geometry beyond the visible horizon.
-        "    color.a *= oe_clamp_alphaFactor; \n"
-        "}\n";
-
-}
-
 //---------------------------------------------------------------------------
 
 namespace
@@ -176,7 +95,7 @@ namespace
         osg::ref_ptr<osg::Uniform>   _depthClipToDepthViewUniform;
         osg::ref_ptr<osg::Uniform>   _depthViewToCamViewUniform;
 
-        osg::ref_ptr<osg::Uniform>   _horizonDistanceUniform;
+        osg::ref_ptr<osg::Uniform>   _horizonDistance2Uniform;
 
         unsigned _renderLeafCount;
 
@@ -362,6 +281,12 @@ ClampingTechnique::setUpCamera(OverlayDecorator::TechRTTParams& params)
     rttStateSet->setAttributeAndModes(
         new osg::PolygonMode( osg::PolygonMode::FRONT_AND_BACK, osg::PolygonMode::FILL ),
         osg::StateAttribute::ON | osg::StateAttribute::PROTECTED );
+
+    // install a VP on the stateset that cancels out any higher-up VP code.
+    // This will prevent things like VPs on the main camera (e.g., log depth buffer)
+    // from interfering with the depth camera
+    VirtualProgram* rttVP = VirtualProgram::getOrCreate(rttStateSet);
+    rttVP->setInheritShaders(false);
     
     // attach the terrain to the camera.
     // todo: should probably protect this with a mutex.....
@@ -387,8 +312,8 @@ ClampingTechnique::setUpCamera(OverlayDecorator::TechRTTParams& params)
     local->_groupStateSet->setRenderingHint( osg::StateSet::TRANSPARENT_BIN );
 
     // uniform for the horizon distance (== max clamping distance)
-    local->_horizonDistanceUniform = local->_groupStateSet->getOrCreateUniform(
-        "oe_clamp_horizonDistance",
+    local->_horizonDistance2Uniform = local->_groupStateSet->getOrCreateUniform(
+        "oe_clamp_horizonDistance2",
         osg::Uniform::FLOAT );
 
     // sampler for depth map texture:
@@ -422,11 +347,21 @@ ClampingTechnique::setUpCamera(OverlayDecorator::TechRTTParams& params)
 
 #endif
 
+    // default value for altitude offset; can be overriden by geometry.
+    local->_groupStateSet->addUniform( new osg::Uniform(Clamping::AltitudeOffsetUniformName, 0.0f) );
+
     // make the shader that will do clamping and depth offsetting.
     VirtualProgram* vp = VirtualProgram::getOrCreate(local->_groupStateSet.get());
-    vp->setName( "ClampingTechnique" );
-    vp->setFunction( "oe_clamp_vertex",   clampingVertexShader,   ShaderComp::LOCATION_VERTEX_VIEW );
-    vp->setFunction( "oe_clamp_fragment", clampingFragmentShader, ShaderComp::LOCATION_FRAGMENT_COLORING );
+    vp->setName( "GPUClamping" );
+
+    // Bind clamping attribute location, and a default uniform indicating whether
+    // they are available (default is false).
+    vp->addBindAttribLocation( Clamping::AnchorAttrName, Clamping::AnchorAttrLocation );
+    local->_groupStateSet->addUniform( new osg::Uniform(Clamping::HasAttrsUniformName, false) );
+
+    osgEarth::Shaders pkg;
+    pkg.load(vp, pkg.GPUClampingVertex);
+    pkg.load(vp, pkg.GPUClampingFragment);
 }
 
 
@@ -473,16 +408,6 @@ ClampingTechnique::cullOverlayGroup(OverlayDecorator::TechRTTParams& params,
 
         LocalPerViewData& local = *static_cast<LocalPerViewData*>(params._techniqueData.get());
 
-#if 0
-        osg::Vec3d eye, lookat, up;
-        params._rttViewMatrix.getLookAt(eye, lookat, up);
-        OE_WARN << "rtt eye=" << eye.x() << ", " << eye.y() << ", " << eye.z() << std::endl;
-
-        double left, right, bottom, top, n, f;
-        params._rttProjMatrix.getOrtho(left, right, bottom, top, n, f);
-        OE_WARN << "rtt prj=" << left << ", " << right << ", " << bottom << ", " << top << ", " << n << ", " << f << std::endl << std::endl;
-#endif
-
         // create the depth texture (render the terrain to tex)
         params._rttCamera->accept( *cv );
 
@@ -512,7 +437,8 @@ ClampingTechnique::cullOverlayGroup(OverlayDecorator::TechRTTParams& params,
             depthViewToDepthClip;
         local._camViewToDepthClipUniform->set( cameraViewToDepthClip );
 
-        local._horizonDistanceUniform->set( float(*params._horizonDistance) );
+        float hd = (float)(*params._horizonDistance);
+        local._horizonDistance2Uniform->set( hd*hd );
 
         //OE_NOTICE << "HD = " << std::setprecision(8) << float(*params._horizonDistance) << std::endl;
 
@@ -545,7 +471,6 @@ ClampingTechnique::cullOverlayGroup(OverlayDecorator::TechRTTParams& params,
 
             // cull the clampable geometry.
             params._group->accept( pcv );
-            //params._group->accept( *cv ); // old way - direct traversal
 
             // done; pop the clamping shaders.
             cv->popStateSet();
diff --git a/src/osgEarth/ColorFilter b/src/osgEarth/ColorFilter
index 62cbbb5..d03fcf9 100644
--- a/src/osgEarth/ColorFilter
+++ b/src/osgEarth/ColorFilter
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarth/ColorFilter.cpp b/src/osgEarth/ColorFilter.cpp
index 6d6e218..d3eb215 100644
--- a/src/osgEarth/ColorFilter.cpp
+++ b/src/osgEarth/ColorFilter.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarth/Common b/src/osgEarth/Common
index d50e859..d17931b 100644
--- a/src/osgEarth/Common
+++ b/src/osgEarth/Common
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -34,6 +34,9 @@
 #  define OSG_VERSION_GREATER_OR_EQUAL(MAJOR, MINOR, PATCH) ((OPENSCENEGRAPH_MAJOR_VERSION>MAJOR) || (OPENSCENEGRAPH_MAJOR_VERSION==MAJOR && (OPENSCENEGRAPH_MINOR_VERSION>MINOR || (OPENSCENEGRAPH_MINOR_VERSION==MINOR && OPENSCENEGRAPH_PATCH_VERSION>=PATCH))))
 #endif
 
+// text inlining macro
+#define OE_MULTILINE(...) #__VA_ARGS__
+
 /** osgEarth core */
 namespace osgEarth
 {
diff --git a/src/osgEarth/CompositeTileSource b/src/osgEarth/CompositeTileSource
index 365e7a7..0ab1c0f 100644
--- a/src/osgEarth/CompositeTileSource
+++ b/src/osgEarth/CompositeTileSource
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarth/CompositeTileSource.cpp b/src/osgEarth/CompositeTileSource.cpp
index d854555..68cfa9a 100644
--- a/src/osgEarth/CompositeTileSource.cpp
+++ b/src/osgEarth/CompositeTileSource.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -209,7 +209,8 @@ CompositeTileSource::createImage(const TileKey&    key,
                 if (image.valid())
                 {                                        
                     // TODO:  Bilinear options?
-                    GeoImage cropped = image.crop( key.getExtent(), true, textureSize.x(), textureSize.y(), true);
+                    bool bilinear = layer->isCoverage() ? false : true;
+                    GeoImage cropped = image.crop( key.getExtent(), true, textureSize.x(), textureSize.y(), bilinear);
                     info.image = cropped.getImage();
                 }                    
             }
@@ -261,7 +262,7 @@ CompositeTileSource::createImage(const TileKey&    key,
             {
                 if (imageInfo.image.valid())
                 {
-                    ImageUtils::mix( result, imageInfo.image, imageInfo.opacity );
+                    ImageUtils::mix( result, imageInfo.image.get(), imageInfo.opacity );
                 }
             }            
         }        
@@ -359,6 +360,9 @@ CompositeTileSource::initialize(const osgDB::Options* dbOptions)
     {        
         if ( i->_imageLayerOptions.isSet() )
         {
+            // Disable the l2 cache for composite layers so that we don't get run out of memory on very large datasets.
+            i->_imageLayerOptions->driver()->L2CacheSize() = 0;
+
             osg::ref_ptr< ImageLayer > layer = new ImageLayer(*i->_imageLayerOptions);
             if (!layer->getTileSource())
             {
@@ -372,6 +376,9 @@ CompositeTileSource::initialize(const osgDB::Options* dbOptions)
         }
         else if (i->_elevationLayerOptions.isSet())
         {
+            // Disable the l2 cache for composite layers so that we don't get run out of memory on very large datasets.
+            i->_elevationLayerOptions->driver()->L2CacheSize() = 0;
+
             osg::ref_ptr< ElevationLayer > layer = new ElevationLayer(*i->_elevationLayerOptions);            
             if (!layer->getTileSource())
             {
diff --git a/src/osgEarth/Config b/src/osgEarth/Config
index 04433d8..7b2d66e 100644
--- a/src/osgEarth/Config
+++ b/src/osgEarth/Config
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarth/Config.cpp b/src/osgEarth/Config.cpp
index 370e779..383fb90 100644
--- a/src/osgEarth/Config.cpp
+++ b/src/osgEarth/Config.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
@@ -217,7 +220,8 @@ namespace
                             {
                                 array_value.append( conf2json(*j, nicer, depth+1) );
                             }
-                            value[array_key] = array_value;
+                            value = array_value;
+                            //value[array_key] = array_value;
                         }
                     }
                 }
@@ -277,14 +281,23 @@ namespace
                         conf.add( element );
                     }
                 }
-                else if ( value.isArray() && endsWith(*i, "_$set") )
+                else if ( value.isArray() )
                 {
-                    std::string key = i->substr(0, i->length()-5);
-                    for( Json::Value::const_iterator j = value.begin(); j != value.end(); ++j )
+                    if ( endsWith(*i, "_$set") )
                     {
-                        Config child( key );
-                        json2conf( *j, child, depth+1 );
-                        conf.add( child );
+                        std::string key = i->substr(0, i->length()-5);
+                        for( Json::Value::const_iterator j = value.begin(); j != value.end(); ++j )
+                        {
+                            Config child( key );
+                            json2conf( *j, child, depth+1 );
+                            conf.add( child );
+                        }
+                    }
+                    else
+                    {
+                        Config element( *i );
+                        json2conf( value, element, depth+1 );
+                        conf.add( element );
                     }
                 }
                 else if ( (*i) == "$key" )
@@ -299,12 +312,6 @@ namespace
                 {
                     json2conf( value, conf, depth+1 );
                 }
-                else if ( value.isArray() )
-                {
-                    Config element( *i );
-                    json2conf( value, element, depth+1 );
-                    conf.add( element );
-                }
                 else
                 {
                     conf.add( *i, value.asString() );
@@ -348,6 +355,13 @@ Config::fromJSON( const std::string& input )
         json2conf( root, *this, 0 );
         return true;
     }
+    else
+    {
+        OE_WARN 
+            << "JSON decoding error: "
+            << reader.getFormatedErrorMessages() 
+            << std::endl;
+    }
     return false;
 }
 
diff --git a/src/osgEarth/Containers b/src/osgEarth/Containers
index c05e4d7..067575e 100644
--- a/src/osgEarth/Containers
+++ b/src/osgEarth/Containers
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
@@ -21,14 +24,107 @@
 
 #include <osgEarth/Common>
 #include <osgEarth/ThreadingUtils>
+#include <osg/ref_ptr>
 #include <osg/observer_ptr>
 #include <osg/State>
 #include <list>
 #include <vector>
+#include <set>
+#include <map>
 
 namespace osgEarth
 {
     /**
+     * A std::map-like map that uses a vector and a getUID method for keying.
+     * DATA must have a getUID() method.
+     */
+    template<typename KEY,typename DATA>
+    struct vector_map
+    {
+        struct ENTRY {
+            inline const KEY&  key()  const { return _key; }
+            inline const DATA& data() const { return _data; }
+            inline DATA&       data()       { return _data; }
+            KEY   _key;
+            DATA _data;
+        };
+        typedef std::vector<ENTRY> container_t;
+
+        //typedef std::vector<KEY>  keys_t;
+        //typedef std::vector<DATA> container_t;
+
+        typedef typename container_t::iterator       iterator;
+        typedef typename container_t::const_iterator const_iterator;
+
+        container_t _container;        
+
+        inline DATA& operator[](const KEY& key) {
+            for(unsigned i=0; i<_container.size(); ++i) {
+                if (_container[i]._key == key) {
+                    return _container[i]._data;
+                }
+            }
+            _container.resize(_container.size()+1);
+            //_container.push_back(ENTRY());
+            _container.back()._key = key;
+            return _container.back()._data;
+        }
+
+        inline DATA* find(const KEY& key) {
+            for(unsigned i=0; i<_container.size(); ++i) {
+                if (_container[i]._key == key) {
+                    return &_container[i]._data;
+                }
+            }
+            return 0L;
+        }
+
+        inline const DATA* find(const KEY& key) const {
+            for(unsigned i=0; i<_container.size(); ++i) {
+                if (_container[i]._key == key) {
+                    return &_container[i]._data;
+                }
+            }
+            return 0L;
+        }
+
+        inline const_iterator begin() const { return _container.begin(); }
+        inline const_iterator end()   const { return _container.end(); }
+        inline iterator begin()             { return _container.begin(); }
+        inline iterator end()               { return _container.end(); }
+
+        inline bool empty() const { return _container.empty(); }
+
+        inline void clear() { _container.clear(); }
+        
+        //iterator erase(iterator& i) {
+        //    iterator j = i; ++j;
+        //    *i = _data.at(_data.size()-1);
+        //    _data.resize(_data.size()-1);
+        //    return j;
+        //}
+
+        inline void erase(const KEY& key) {
+            for(unsigned i=0; i<_container.size(); ++i) {
+                if (_container[i]._key == key) {
+                    if (i+1 < _container.size()) {
+                        _container[i] = _container[_container.size()-1];
+                    }
+                    _container.resize(_container.size()-1);
+                    break;
+                }
+            }
+        }
+
+        inline int size() const { return _container.size(); }
+
+        template<typename InputIterator>
+        void insert(InputIterator a, InputIterator b) {
+            for(InputIterator i = a; i != b; ++i) (*this)[i->first] = i->second;
+        }
+    };
+
+    /**
      * A std::map-like container that is faster than std::map for small amounts
      * of data accessed by a single user
      */
@@ -91,7 +187,19 @@ namespace osgEarth
         
         iterator erase(iterator& i) { return _data.erase( i ); }
 
+        void erase(const KEY& key) {
+            iterator i = find(key);
+            if ( i != end() ) {
+                erase( i );
+            }
+        }
+
         int size() const { return _data.size(); }
+
+        template<typename InputIterator>
+        void insert(InputIterator a, InputIterator b) {
+            for(InputIterator i = a; i != b; ++i) (*this)[i->first] = i->second;
+        }
     };
 
     //------------------------------------------------------------------------
@@ -464,6 +572,264 @@ namespace osgEarth
     private:
         vector_type _impl;
     };
+
+
+    /** Template for per-thread data storage */
+    template<typename T>
+    struct PerThread
+    {
+        T& get() {
+            Threading::ScopedMutexLock lock(_mutex);
+            return _data[Threading::getCurrentThreadId()];
+        }
+    private:
+        std::map<unsigned,T> _data;
+        Threading::Mutex     _mutex;
+    };
+    
+
+    /** Template for thread safe per-object data storage */
+    template<typename KEY, typename DATA>
+    struct PerObjectMap
+    {
+        DATA& get(KEY k)
+        {
+            {
+                osgEarth::Threading::ScopedReadLock readLock(_mutex);
+                typename std::map<KEY,DATA>::iterator i = _data.find(k);
+                if ( i != _data.end() )
+                    return i->second;
+            }
+            {
+                osgEarth::Threading::ScopedWriteLock lock(_mutex);
+                typename std::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);
+            _data.erase( k );
+        }
+
+    private:
+        std::map<KEY,DATA>                  _data;
+        osgEarth::Threading::ReadWriteMutex _mutex;
+    };
+
+    /** Template for thread safe per-object data storage */
+    template<typename KEY, typename DATA>
+    struct PerObjectFastMap
+    {
+        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];
+            }
+        }
+
+        void remove(KEY k)
+        {
+            osgEarth::Threading::ScopedWriteLock exclusive(_mutex);
+            _data.erase( k );
+        }
+
+    private:
+        osgEarth::fast_map<KEY,DATA>        _data;
+        osgEarth::Threading::ReadWriteMutex _mutex;
+    };
+
+    /** Template for thread safe per-object data storage */
+    template<typename KEY, typename DATA>
+    struct PerObjectRefMap
+    {
+        DATA* get(KEY k)
+        {
+            osgEarth::Threading::ScopedReadLock lock(_mutex);
+            typename std::map<KEY,osg::ref_ptr<DATA > >::const_iterator i = _data.find(k);
+            if ( i != _data.end() )
+                return i->second.get();
+
+            return 0L;
+        }
+
+        DATA* getOrCreate(KEY k, DATA* newDataIfNeeded)
+        {
+            osg::ref_ptr<DATA> _refReleaser = newDataIfNeeded;
+            {
+                osgEarth::Threading::ScopedReadLock lock(_mutex);
+                typename std::map<KEY,osg::ref_ptr<DATA> >::const_iterator i = _data.find(k);
+                if ( i != _data.end() )
+                    return i->second.get();
+            }
+
+            {
+                osgEarth::Threading::ScopedWriteLock lock(_mutex);
+                typename std::map<KEY,osg::ref_ptr<DATA> >::iterator i = _data.find(k);
+                if ( i != _data.end() )
+                    return i->second.get();
+
+                _data[k] = newDataIfNeeded;
+                return newDataIfNeeded;
+            }
+        }
+
+        void remove(KEY k)
+        {
+            osgEarth::Threading::ScopedWriteLock exclusive(_mutex);
+            _data.erase( k );
+        }
+
+        void remove(DATA* data)
+        {
+            osgEarth::Threading::ScopedWriteLock exclusive(_mutex);
+            for( typename std::map<KEY,osg::ref_ptr<DATA> >::iterator i = _data.begin(); i != _data.end(); ++i )
+            {
+                if ( i->second.get() == data )
+                {
+                    _data.erase( i );
+                    break;
+                }
+            }
+        }
+
+    private:
+        std::map<KEY,osg::ref_ptr<DATA> >    _data;
+        osgEarth::Threading::ReadWriteMutex  _mutex;
+    };
+
+    /** Template for thread safe per-object data storage */
+    template<typename KEY, typename DATA>
+    struct PerObjectObsMap
+    {
+        DATA* get(KEY k)
+        {
+            osgEarth::Threading::ScopedReadLock lock(_mutex);
+            typename std::map<KEY, osg::observer_ptr<DATA> >::const_iterator i = _data.find(k);
+            if ( i != _data.end() )
+                return i->second.get();
+
+            return 0L;
+        }
+
+        DATA* getOrCreate(KEY k, DATA* newDataIfNeeded)
+        {
+            osg::ref_ptr<DATA> _refReleaser = newDataIfNeeded;
+            {
+                osgEarth::Threading::ScopedReadLock lock(_mutex);
+                typename std::map<KEY,osg::observer_ptr<DATA> >::const_iterator i = _data.find(k);
+                if ( i != _data.end() )
+                    return i->second.get();
+            }
+
+            {
+                osgEarth::Threading::ScopedWriteLock lock(_mutex);
+                typename std::map<KEY,osg::observer_ptr<DATA> >::iterator i = _data.find(k);
+                if ( i != _data.end() )
+                    return i->second.get();
+
+                _data[k] = newDataIfNeeded;
+                return newDataIfNeeded;
+            }
+        }
+
+        void remove(KEY k)
+        {
+            osgEarth::Threading::ScopedWriteLock exclusive(_mutex);
+            _data.erase( k );
+        }
+
+        void remove(DATA* data)
+        {
+            osgEarth::Threading::ScopedWriteLock exclusive(_mutex);
+            for( typename std::map<KEY,osg::observer_ptr<DATA> >::iterator i = _data.begin(); i != _data.end(); ++i )
+            {
+                if ( i->second.get() == data )
+                {
+                    _data.erase( i );
+                    break;
+                }
+            }
+        }
+
+    private:
+        std::map<KEY,osg::observer_ptr<DATA> >    _data;
+        osgEarth::Threading::ReadWriteMutex       _mutex;
+    };
+
+
+    /** Template for thread safe observer set */
+    template<typename T>
+    struct ThreadSafeObserverSet
+    {
+        typedef void (*Functor)(T*);
+        typedef void (*ConstFunctor)(const T*);
+
+        void iterate( const Functor& f )
+        {
+            osgEarth::Threading::ScopedWriteLock lock(_mutex);
+            for( typename std::set<T>::iterator i = _data.begin(); i != _data.end(); )
+            {
+                if ( i->valid() )
+                {
+                    f( i->get() );
+                    ++i;
+                }
+                else
+                {
+                    i = _data.erase( i );
+                }
+            }
+        }
+
+        void iterate( const ConstFunctor& f )
+        {
+            osgEarth::Threading::ScopedReadLock lock(_mutex);
+            for( typename std::set<T>::iterator i = _data.begin(); i != _data.end(); )
+            {
+                if ( i->valid() )
+                {
+                    f( i->get() );
+                    ++i;
+                }
+                else
+                {
+                    i = _data.erase( i );
+                }
+            }
+        }
+
+        void insert(T* obj)
+        {
+            osgEarth::Threading::ScopedWriteLock lock(_mutex);
+            _data.insert( obj );
+        }
+
+        void remove(T* obj)
+        {
+            osgEarth::Threading::ScopedWriteLock lock(_mutex);
+            _data.erase( obj );
+        }
+
+    private:
+        std::set<osg::observer_ptr<T> >      _data;
+        osgEarth::Threading::ReadWriteMutex  _mutex;
+    };
 }
 
 #endif // OSGEARTH_CONTAINERS_H
diff --git a/src/osgEarth/Cube b/src/osgEarth/Cube
index 87e2466..dcee0d6 100644
--- a/src/osgEarth/Cube
+++ b/src/osgEarth/Cube
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarth/Cube.cpp b/src/osgEarth/Cube.cpp
index d53b37c..624ea27 100644
--- a/src/osgEarth/Cube.cpp
+++ b/src/osgEarth/Cube.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarth/CullingUtils b/src/osgEarth/CullingUtils
index 6dce750..b608558 100644
--- a/src/osgEarth/CullingUtils
+++ b/src/osgEarth/CullingUtils
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -96,25 +96,6 @@ namespace osgEarth
         }
     };
 
-    /**
-     * Culler that tests whether the line-of-sight vector between the camera
-     * and the node intersects the Ellipsoid, and if so, culls the node.
-     */
-    struct OSGEARTH_EXPORT CullNodeByEllipsoid : public osg::NodeCallback {
-        double _minRadius;
-        CullNodeByEllipsoid( const osg::EllipsoidModel* model );
-        void operator()(osg::Node* node, osg::NodeVisitor* nv);
-    };
-
-    struct OSGEARTH_EXPORT CullNodeByHorizon : public osg::NodeCallback {
-        osg::observer_ptr<osg::MatrixTransform> _xform;
-        osg::Vec3d _world;
-        double _r, _r2;
-        CullNodeByHorizon( const osg::Vec3d& world, const osg::EllipsoidModel* model );
-        CullNodeByHorizon( osg::MatrixTransform* xform, const osg::EllipsoidModel* model );
-        void operator()(osg::Node* node, osg::NodeVisitor* nv );
-    };
-
     struct OSGEARTH_EXPORT CullNodeByFrameNumber : public osg::NodeCallback {
         unsigned _frame;
         CullNodeByFrameNumber() : _frame(0) { }
@@ -274,8 +255,10 @@ namespace osgEarth
 
 
     
-    // Cull callback that figures out where the visible horizon is
-    // based on the eyepoint.
+    /**
+     * Cull callback that sets a clip plane at the visible horizon
+     * of the Ellipsoid.
+     */
     class OSGEARTH_EXPORT ClipToGeocentricHorizon : public osg::NodeCallback
     {
     public:
@@ -286,7 +269,7 @@ namespace osgEarth
         void operator()(osg::Node* node, osg::NodeVisitor* nv);
 
     protected:
-        double _radius;
+        osg::Vec3d                        _radii;
         osg::observer_ptr<osg::ClipPlane> _clipPlane;
     };
 }
diff --git a/src/osgEarth/CullingUtils.cpp b/src/osgEarth/CullingUtils.cpp
index 4a426dd..a4b82fd 100644
--- a/src/osgEarth/CullingUtils.cpp
+++ b/src/osgEarth/CullingUtils.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -332,10 +332,12 @@ DoNotComputeNearFarCullCallback::operator()(osg::Node* node, osg::NodeVisitor* n
 
 //----------------------------------------------------------------------------
 
-
 bool 
 SuperClusterCullingCallback::cull(osg::NodeVisitor* nv, osg::Drawable* , osg::State*) const
 {
+    static int c0, c1;
+    static int frame = -1;
+
     osgUtil::CullVisitor* cv = Culling::asCullVisitor(nv);
 
     if (!cv) return false;
@@ -344,41 +346,58 @@ SuperClusterCullingCallback::cull(osg::NodeVisitor* nv, osg::Drawable* , osg::St
     if ( !(cv->getCullingMode() & osg::CullSettings::CLUSTER_CULLING) )
         return false;
 
+    bool visible = true;
+
     // quick bail is the deviation is maxed out
-    if ( _deviation <= -1.0f )
-        return false;
+    if ( _deviation > -1.0f )
+    {
+        // accept if we're within the culling radius
+        osg::Vec3d eye_cp = nv->getViewPoint() - _controlPoint;
+        float radius = (float)eye_cp.length();
+        if ( radius >= _radius )
+        {
+            // handle perspective and orthographic projections differently.
+            const osg::Matrixd& proj = *cv->getProjectionMatrix();
+            bool isOrtho = ( proj(3,3) == 1. ) && ( proj(2,3) == 0. ) && ( proj(1,3) == 0. ) && ( proj(0,3) == 0.);
 
-    // accept if we're within the culling radius
-    osg::Vec3d eye_cp = nv->getViewPoint() - _controlPoint;
-    float radius = (float)eye_cp.length();
-    if (radius < _radius)
-        return false;
+            float deviation;
+            if ( isOrtho )
+            {
+                // For an ortho camera, use the reverse look vector instead of the eye->controlpoint
+                // vector for the deviation test. Transform the local reverse-look vector (always 0,0,1)
+                // into world space and dot them. (Use 3x3 since we're xforming a vector, not a point)
+                osg::Vec3d revLookWorld = osg::Matrix::transform3x3( *cv->getModelViewMatrix(), osg::Vec3d(0,0,1) );
+                revLookWorld.normalize();
+                deviation = revLookWorld * _normal;
+                visible = deviation >= _deviation;
+            }
 
-#if 0 // underwater test.
-    if (radius-_radius < 1000000)
-        return false;
-#endif
+            else // isPerspective
+            {
+                deviation = (eye_cp * _normal)/radius;
+                visible = deviation >= _deviation;
+            }
 
-    // handle perspective and orthographic projections differently.
-    const osg::Matrixd& proj = *cv->getProjectionMatrix();
-    bool isOrtho = ( proj(3,3) == 1. ) && ( proj(2,3) == 0. ) && ( proj(1,3) == 0. ) && ( proj(0,3) == 0.);
+            visible = deviation >= _deviation;            
+        }
+    }
 
-    if ( isOrtho )
+#if 0 // debugging
+    int fn = cv->getFrameStamp()->getFrameNumber();
+    if ( fn != frame )
     {
-        // For an ortho camera, use the reverse look vector instead of the eye->controlpoint
-        // vector for the deviation test. Transform the local reverse-look vector (always 0,0,1)
-        // into world space and dot them. (Use 3x3 since we're xforming a vector, not a point)
-        osg::Vec3d revLookWorld = osg::Matrix::transform3x3( *cv->getModelViewMatrix(), osg::Vec3d(0,0,1) );
-        revLookWorld.normalize();
-        float deviation = revLookWorld * _normal;
-        return deviation < _deviation;
+        frame = fn;
+        OE_INFO << "Frame: " << fn << "try = " << c0 << ", viz = " << c1 << "\n";
+        c0 = c1 = 0;
     }
-
-    else // isPerspective
+    else
     {
-        float deviation = (eye_cp * _normal)/radius;
-        return deviation < _deviation;
+        c0 ++;
+        c1 += visible ? 1 : 0;
     }
+#endif
+
+    return !visible;
 }
 
 
@@ -466,126 +485,6 @@ ClusterCullingFactory::create(const osg::Vec3& controlPoint,
     return ccc;
 }
 
-
-//------------------------------------------------------------------------
-
-CullNodeByEllipsoid::CullNodeByEllipsoid( const osg::EllipsoidModel* model ) :
-_minRadius( std::min(model->getRadiusPolar(), model->getRadiusEquator()) )
-{
-    //nop
-}
-
-
-void
-CullNodeByEllipsoid::operator()(osg::Node* node, osg::NodeVisitor* nv)
-{
-    if ( nv )
-    {
-        osgUtil::CullVisitor* cv = Culling::asCullVisitor(nv);
-
-        // camera location
-        osg::Vec3d vp, center, up;
-        cv->getCurrentCamera()->getViewMatrixAsLookAt(vp, center, up);
-        double vpLen2 = vp.length2();
-
-        // world bound of this model
-        osg::Matrix l2w = osg::computeLocalToWorld( nv->getNodePath() );
-        const osg::BoundingSphere& bs = node->getBound();
-        osg::BoundingSphere bsWorld( bs.center() * l2w, bs.radius() * l2w.getScale().x() );
-        double bswLen2 = bsWorld.center().length2();
-
-        double vpLen = vp.length();
-        osg::Vec3d vpToTarget = bsWorld.center() - vp;
-        
-        vp.normalize();
-        vpToTarget.normalize();
-        double theta = acos( vpToTarget * -vp );
-        double r = vpLen * sin(theta);
-        //double p = 
-
-        // "r" is the length of the shortest line between the center of the 
-        // ellipsoid and the line of light. If (r) is less than the ellipsoid's
-        // minumum radius, that means the ellipsoid is blocking the LOS.
-        // (We "tweak" r a bit: increase it by the target object's radius so we
-        // can account for the whole object, and subtract the lower point on 
-        // earth to account for the camera being underground)
-        if ( r + bsWorld.radius() > _minRadius - 11000.0 )
-        {
-            OE_NOTICE 
-                << "r=" << r << ", rad="<<bsWorld.radius()<<", min=" << _minRadius
-                << std::endl;
-            traverse(node, nv);
-        }
-    }
-}
-
-//------------------------------------------------------------------------
-
-CullNodeByHorizon::CullNodeByHorizon( const osg::Vec3d& world, const osg::EllipsoidModel* model ) :
-_world(world),
-_r    (model->getRadiusPolar()),
-_r2   (model->getRadiusPolar() * model->getRadiusPolar())
-{
-    //nop
-}
-CullNodeByHorizon::CullNodeByHorizon( osg::MatrixTransform* xform, const osg::EllipsoidModel* model ) :
-_xform(xform),
-_r    (model->getRadiusPolar()),
-_r2   (model->getRadiusPolar() * model->getRadiusPolar())
-{
-    //nop
-}
-
-void
-CullNodeByHorizon::operator()(osg::Node* node, osg::NodeVisitor* nv)
-{
-    if ( nv )
-    {
-        osgUtil::CullVisitor* cv = Culling::asCullVisitor(nv);
-
-        // get the viewpoint. It will be relative to the current reference location (world).
-        osg::Matrix l2w = osg::computeLocalToWorld( nv->getNodePath(), true );
-        osg::Vec3d vp  = cv->getViewPoint() * l2w;
-
-        osg::Vec3d world = _xform.valid() ? _xform->getMatrix().getTrans() : _world;
-
-        // same quadrant:
-        if ( vp * world >= 0.0 )
-        {
-            double d2 = vp.length2();
-            double horiz2 = d2 - _r2;
-            double dist2 = (world-vp).length2();
-            if ( dist2 < horiz2 )
-            {
-                traverse(node, nv);
-            }
-        }
-
-        // different quadrants:
-        else
-        {
-            // there's a horizon between them; now see if the thing is visible.
-            // find the triangle formed by the viewpoint, the target point, and 
-            // the center of the earth.
-            double a = (world-vp).length();
-            double b = world.length();
-            double c = vp.length();
-
-            // Heron's formula for triangle area:
-            double s = 0.5*(a+b+c);
-            double area = 0.25*sqrt( s*(s-a)*(s-b)*(s-c) );
-
-            // Get the triangle's height:
-            double h = (2*area)/a;
-
-            if ( h >= _r )
-            {
-                traverse(node, nv);
-            }
-        }
-    }
-}
-
 //------------------------------------------------------------------------
 
 CullNodeByNormal::CullNodeByNormal( const osg::Vec3d& normal )
@@ -1108,9 +1007,10 @@ LODScaleGroup::traverse(osg::NodeVisitor& nv)
 ClipToGeocentricHorizon::ClipToGeocentricHorizon(const osgEarth::SpatialReference* srs,
                                                  osg::ClipPlane*                   clipPlane)
 {
-    _radius = std::min(
-        srs->getEllipsoid()->getRadiusPolar(),
-        srs->getEllipsoid()->getRadiusEquator() );
+    _radii.set(
+        srs->getEllipsoid()->getRadiusEquator(),
+        srs->getEllipsoid()->getRadiusEquator(),
+        srs->getEllipsoid()->getRadiusPolar() );
 
     _clipPlane = clipPlane;
 }
@@ -1121,12 +1021,26 @@ ClipToGeocentricHorizon::operator()(osg::Node* node, osg::NodeVisitor* nv)
     osg::ref_ptr<osg::ClipPlane> clipPlane;
     if ( _clipPlane.lock(clipPlane) )
     {
-        osg::Vec3 eye = nv->getEyePoint();
-        double d = eye.length();
-        double a = acos(_radius/d);
-        double horizonRadius = _radius*cos(a);
-        eye.normalize();
-        clipPlane->setClipPlane(osg::Plane(eye, eye*horizonRadius));
+        osg::Vec3d eye = nv->getEyePoint();
+
+        // viewer in ellipsoidal unit space:
+        osg::Vec3d unitEye( eye.x()/_radii.x(), eye.y()/_radii.y(), eye.z()/_radii.z());
+
+        // calculate scaled distance from center to viewer:
+        double unitEyeLen = unitEye.length();
+
+        // calculate scaled distance from center to horizon plane:
+        double unitHorizonPlaneLen = 1.0/unitEyeLen;
+
+        // convert back to real space:
+        double eyeLen = eye.length();
+        double horizonPlaneLen = eyeLen * unitHorizonPlaneLen/unitEyeLen;
+
+        // normalize the eye vector:
+        eye /= eyeLen;
+
+        // compute a new clip plane:
+        clipPlane->setClipPlane(osg::Plane(eye, eye*horizonPlaneLen));
     }
     traverse(node, nv);
 }
diff --git a/src/osgEarth/DPLineSegmentIntersector b/src/osgEarth/DPLineSegmentIntersector
index 393db81..2193903 100644
--- a/src/osgEarth/DPLineSegmentIntersector
+++ b/src/osgEarth/DPLineSegmentIntersector
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarth/DPLineSegmentIntersector.cpp b/src/osgEarth/DPLineSegmentIntersector.cpp
index d180fd3..7d2897b 100644
--- a/src/osgEarth/DPLineSegmentIntersector.cpp
+++ b/src/osgEarth/DPLineSegmentIntersector.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarth/DateTime b/src/osgEarth/DateTime
index 7291dcf..1f1b5a3 100644
--- a/src/osgEarth/DateTime
+++ b/src/osgEarth/DateTime
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/osgEarth/DateTime.cpp b/src/osgEarth/DateTime.cpp
index 83a34f8..89f26f7 100644
--- a/src/osgEarth/DateTime.cpp
+++ b/src/osgEarth/DateTime.cpp
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/osgEarth/Decluttering b/src/osgEarth/Decluttering
index 9c6ed64..e833e3b 100644
--- a/src/osgEarth/Decluttering
+++ b/src/osgEarth/Decluttering
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/osgEarth/Decluttering.cpp b/src/osgEarth/Decluttering.cpp
index 935f0ee..6ee6705 100644
--- a/src/osgEarth/Decluttering.cpp
+++ b/src/osgEarth/Decluttering.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,17 +8,20 @@
 * 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.
+* 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/Decluttering>
-//#include <osgEarthAnnotation/AnnotationData>
 #include <osgEarth/ThreadingUtils>
+#include <osgEarth/Containers>
 #include <osgEarth/Utils>
 #include <osgEarth/VirtualProgram>
 #include <osgUtil/RenderBin>
@@ -87,9 +90,9 @@ namespace
     typedef std::pair<const osg::Node*, osg::BoundingBox> RenderLeafBox;
 
     // Data structure stored one-per-View.
-    struct PerViewInfo
+    struct PerCamInfo
     {
-        PerViewInfo() : _lastTimeStamp(0.0) { }
+        PerCamInfo() : _firstFrame(true) { }
 
         // remembers the state of each drawable from the previous pass
         DrawableMemory _memory;
@@ -100,12 +103,14 @@ namespace
         std::vector<RenderLeafBox>         _used;
 
         // time stamp of the previous pass, for calculating animation speed
-        double _lastTimeStamp;
+        //double _lastTimeStamp;
+        osg::Timer_t _lastTimeStamp;
+        bool _firstFrame;
     };
 
     static bool s_enabledGlobally = true;
 
-    static char* s_faderFS =
+    static const char* s_faderFS =
         "#version " GLSL_VERSION_STR "\n"
         GLSL_DEFAULT_PRECISION_FLOAT "\n"
         "uniform float " FADE_UNIFORM_NAME ";\n"
@@ -164,7 +169,7 @@ struct /*internal*/ DeclutterSort : public osgUtil::RenderBin::SortCallback
     DeclutterSortFunctor* _customSortFunctor;
     DeclutterContext*     _context;
 
-    Threading::PerObjectMap<osg::View*, PerViewInfo> _perView;
+    PerObjectFastMap<osg::Camera*, PerCamInfo> _perCam;
 
     /**
      * Constructs the new sorter.
@@ -202,14 +207,19 @@ struct /*internal*/ DeclutterSort : public osgUtil::RenderBin::SortCallback
             return;
 
         // access the view-specific persistent data:
-        osg::Camera* cam   = bin->getStage()->getCamera();
-        osg::View*   view  = cam->getView();
-        PerViewInfo& local = _perView.get( view );   
-        
+        osg::Camera* cam   = bin->getStage()->getCamera();                
+        PerCamInfo& local = _perCam.get( cam );
+
+        osg::Timer_t now = osg::Timer::instance()->tick();
+        if (local._firstFrame)
+        {            
+            local._firstFrame = false;
+            local._lastTimeStamp = now;
+        }
+
         // calculate the elapsed time since the previous pass; we'll use this for
-        // the animations
-        double now = view ? view->getFrameStamp()->getReferenceTime() : 0.0;
-        float elapsedSeconds = float(now - local._lastTimeStamp);
+        // the animations                
+        float elapsedSeconds = osg::Timer::instance()->delta_s(local._lastTimeStamp, now);
         local._lastTimeStamp = now;
 
         // Reset the local re-usable containers
@@ -217,10 +227,29 @@ struct /*internal*/ DeclutterSort : public osgUtil::RenderBin::SortCallback
         local._failed.clear();          // drawables that fail occlusion test
         local._used.clear();            // list of occupied bounding boxes in screen space
 
-        // compute a window matrix so we can do window-space culling:
+        // compute a window matrix so we can do window-space culling. If this is an RTT camera
+        // with a reference camera attachment, we actually want to declutter in the window-space
+        // of the reference camera.
         const osg::Viewport* vp = cam->getViewport();
+
         osg::Matrix windowMatrix = vp->computeWindowMatrix();
 
+        osg::Vec3f  refCamScale(1.0f, 1.0f, 1.0f);
+        osg::Matrix refCamScaleMat;
+        osg::Matrix refWindowMatrix = windowMatrix;
+
+        if ( cam->isRenderToTextureCamera() )
+        {
+            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();
+            }
+        }
+
         // Track the parent nodes of drawables that are obscured (and culled). Drawables
         // with the same parent node (typically a Geode) are considered to be grouped and
         // will be culled as a group.
@@ -245,10 +274,19 @@ struct /*internal*/ DeclutterSort : public osgUtil::RenderBin::SortCallback
             osg::BoundingBox box = Utils::getBoundingBox(drawable);
 
             static osg::Vec4d s_zero_w(0,0,0,1);
-            osg::Vec4d clip = s_zero_w * (*leaf->_modelview.get()) * (*leaf->_projection.get());
+            osg::Matrix MVP = (*leaf->_modelview.get()) * (*leaf->_projection.get());
+            osg::Vec4d clip = s_zero_w * MVP;
             osg::Vec3d clip_ndc( clip.x()/clip.w(), clip.y()/clip.w(), clip.z()/clip.w() );
             osg::Vec3f winPos = clip_ndc * windowMatrix;
+
+            // this accounts for the size difference when using a reference camera (RTT/picking)
+            box.xMin() *= refCamScale.x();
+            box.xMax() *= refCamScale.x();
+            box.yMin() *= refCamScale.y();
+            box.yMax() *= refCamScale.y();
+
             osg::Vec2f offset( -box.xMin(), -box.yMin() );
+
             box.set(
                 winPos.x() + box.xMin(),
                 winPos.y() + box.yMin(),
@@ -293,6 +331,7 @@ struct /*internal*/ DeclutterSort : public osgUtil::RenderBin::SortCallback
             {
                 // passed the test, so add the leaf's bbox to the "used" list, and add the leaf
                 // to the final draw list.
+                //local._used.push_back( std::make_pair(drawableParent, box) );
                 local._used.push_back( std::make_pair(drawableParent, box) );
                 local._passed.push_back( leaf );
             }
@@ -309,7 +348,7 @@ struct /*internal*/ DeclutterSort : public osgUtil::RenderBin::SortCallback
             // projection when it's drawn later. We'll also preserve the scale.
             osg::Matrix newModelView;
             newModelView.makeTranslate( box.xMin() + offset.x(), box.yMin() + offset.y(), 0 );
-            newModelView.preMultScale( leaf->_modelview->getScale() );
+            newModelView.preMultScale( leaf->_modelview->getScale() * refCamScaleMat );
             
             // Leaf modelview matrixes are shared (by objects in the traversal stack) so we 
             // cannot just replace it unfortunately. Have to make a new one. Perhaps a nice
@@ -415,9 +454,9 @@ struct /*internal*/ DeclutterSort : public osgUtil::RenderBin::SortCallback
  */
 struct DeclutterDraw : public osgUtil::RenderBin::DrawCallback
 {
-    DeclutterContext*                                    _context;
-    Threading::PerThread< osg::ref_ptr<osg::RefMatrix> > _ortho2D;
-    osg::ref_ptr<osg::Uniform> _fade;
+    DeclutterContext*                         _context;
+    PerThread< osg::ref_ptr<osg::RefMatrix> > _ortho2D;
+    osg::ref_ptr<osg::Uniform>                _fade;
 
     /**
      * Constructs the decluttering draw callback.
@@ -581,8 +620,7 @@ public:
 
         // set up a VP to do fading.
         VirtualProgram* vp = VirtualProgram::getOrCreate(stateSet);
-        vp->setFunction( "oe_declutter_apply_fade", s_faderFS, ShaderComp::LOCATION_FRAGMENT_COLORING );
-        //stateSet->setAttributeAndModes(vp, 1);
+        vp->setFunction( "oe_declutter_apply_fade", s_faderFS, ShaderComp::LOCATION_FRAGMENT_COLORING, 0.5f );
     }
 
     void setSortingFunctor( DeclutterSortFunctor* f )
@@ -634,10 +672,14 @@ Decluttering::setEnabled( osg::StateSet* stateSet, bool enable, int binNum )
                 udc->addUserObject( prevStateSet );
             }
 
-            stateSet->setRenderBinDetails( binNum, OSGEARTH_DECLUTTER_BIN );
+            // the OVERRIDE prevents subsequent statesets from disabling the decluttering bin,
+            // I guess. This wasn't needed in OSG 3.1.4 but now it is.
+            stateSet->setRenderBinDetails(
+                binNum,
+                OSGEARTH_DECLUTTER_BIN,
+                osg::StateSet::OVERRIDE_RENDERBIN_DETAILS);
 
-            // disable renderbin nesting b/c it is incompatible with decluttering;
-            // i.e. we only want one decluttering bin per render stage
+            // Force a single shared decluttering bin per render stage
             stateSet->setNestRenderBins( false );
         }
         else
diff --git a/src/osgEarth/DepthOffset b/src/osgEarth/DepthOffset
index ab025cf..0471852 100644
--- a/src/osgEarth/DepthOffset
+++ b/src/osgEarth/DepthOffset
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarth/DepthOffset.cpp b/src/osgEarth/DepthOffset.cpp
index a52c25a..dfe4cfc 100644
--- a/src/osgEarth/DepthOffset.cpp
+++ b/src/osgEarth/DepthOffset.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -24,6 +24,7 @@
 #include <osgEarth/NodeUtils>
 #include <osgEarth/Capabilities>
 #include <osgEarth/VirtualProgram>
+#include <osgEarth/Shaders>
 
 #include <osg/Geode>
 #include <osg/Geometry>
@@ -88,97 +89,6 @@ namespace
         LineFunctor<SegmentAnalyzer> _segmentAnalyzer;
         int                          _maxSegmentsToAnalyze;
     };
-
-
-    //...............................
-    // Shader code:
-
-#ifdef VERTEX_ONLY_METHOD
-
-    const char* s_vertex =
-        "#version " GLSL_VERSION_STR "\n"
-        GLSL_DEFAULT_PRECISION_FLOAT "\n"
-
-        "uniform float oe_doff_min_bias; \n"
-        "uniform float oe_doff_max_bias; \n"
-        "uniform float oe_doff_min_range; \n"
-        "uniform float oe_doff_max_range; \n"
-
-        "void oe_doff_vertex(inout vec4 VertexVIEW) \n"
-        "{ \n"
-        //   calculate range to target:
-        "    vec3 vert3 = VertexVIEW.xyz/VertexVIEW.w; \n"
-        "    float range = length(vert3); \n"
-
-        //   calculate the depth offset bias for this range:
-        "    float ratio = (clamp(range, oe_doff_min_range, oe_doff_max_range)-oe_doff_min_range)/(oe_doff_max_range-oe_doff_min_range);\n"
-        "    float bias = oe_doff_min_bias + ratio * (oe_doff_max_bias-oe_doff_min_bias);\n"
-
-        //   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); \n"
-
-        //   pull the vertex towards the camera.
-        "    vec3 pullVec = normalize(vert3); \n"
-        "    vec3 simVert3 = vert3 - pullVec*bias; \n"
-        "    VertexVIEW = vec4( simVert3 * VertexVIEW.w, VertexVIEW.w ); \n"
-        "} \n";
-
-#else
-
-    const char* s_vertex =
-        "#version " GLSL_VERSION_STR "\n"
-        GLSL_DEFAULT_PRECISION_FLOAT "\n"
-        
-        "uniform float oe_doff_min_bias; \n"
-        "uniform float oe_doff_max_bias; \n"
-        "uniform float oe_doff_min_range; \n"
-        "uniform float oe_doff_max_range; \n"
-
-        // values to pass to fragment shader:
-        "varying vec4 oe_doff_vert; \n"
-        "varying float oe_doff_vertRange; \n"
-
-        "void oe_doff_vertex(inout vec4 VertexVIEW) \n"
-        "{ \n"
-        //   calculate range to target:
-        "    vec3 vert3 = VertexVIEW.xyz/VertexVIEW.w; \n"
-        "    float range = length(vert3); \n"
-
-        //   calculate the depth offset bias for this range:
-        "    float ratio = (clamp(range, oe_doff_min_range, oe_doff_max_range)-oe_doff_min_range)/(oe_doff_max_range-oe_doff_min_range);\n"
-        "    float bias = oe_doff_min_bias + ratio * (oe_doff_max_bias-oe_doff_min_bias);\n"
-
-        //   calculate the "simulated" vertex, pulled toward the camera:
-        "    vec3 pullVec = normalize(vert3); \n"
-        "    vec3 simVert3 = vert3 - pullVec*bias; \n"
-        "    vec4 simVert = vec4( simVert3 * VertexVIEW.w, VertexVIEW.w ); \n"
-        "    oe_doff_vert = gl_ProjectionMatrix * simVert; \n"
-        "    oe_doff_vertRange = range - bias; \n"
-        "} \n";
-
-    const char* s_fragment =
-        "#version " GLSL_VERSION_STR "\n"
-        GLSL_DEFAULT_PRECISION_FLOAT "\n"
-
-        // values to pass to fragment shader:
-        "varying vec4 oe_doff_vert; \n"
-        "varying float oe_doff_vertRange; \n"
-
-        "void oe_doff_fragment(inout vec4 color) \n"
-        "{ \n"
-        //   calculate the new depth value for the zbuffer.
-        "    float sim_depth = 0.5 * (1.0+(oe_doff_vert.z/oe_doff_vert.w));\n"
-
-        //   if the offset pushed the Z behind the eye, the projection mapping will
-        //   result in a z>1. We need to bring these values back down to the 
-        //   near clip plan (z=0). We need to check simRange too before doing this
-        //   so we don't draw fragments that are legitimently beyond the far clip plane.
-        "    if ( sim_depth > 1.0 && oe_doff_vertRange < 0.0 ) { sim_depth = 0.0; } \n"
-        "    gl_FragDepth = max(0.0, sim_depth); \n"
-        "} \n";
-
-#endif // !VERTEX_ONLY_METHOD
 }
 
 //------------------------------------------------------------------------
@@ -236,10 +146,10 @@ DepthOffsetAdapter::init()
     _supported = Registry::capabilities().supportsGLSL();
     if ( _supported )
     {
-        _minBiasUniform = new osg::Uniform(osg::Uniform::FLOAT, "oe_doff_min_bias");
-        _maxBiasUniform = new osg::Uniform(osg::Uniform::FLOAT, "oe_doff_max_bias");
-        _minRangeUniform = new osg::Uniform(osg::Uniform::FLOAT, "oe_doff_min_range");
-        _maxRangeUniform = new osg::Uniform(osg::Uniform::FLOAT, "oe_doff_max_range");
+        _minBiasUniform  = new osg::Uniform(osg::Uniform::FLOAT, "oe_depthOffset_minBias");
+        _maxBiasUniform  = new osg::Uniform(osg::Uniform::FLOAT, "oe_depthOffset_maxBias");
+        _minRangeUniform = new osg::Uniform(osg::Uniform::FLOAT, "oe_depthOffset_minRange");
+        _maxRangeUniform = new osg::Uniform(osg::Uniform::FLOAT, "oe_depthOffset_maxRange");
         updateUniforms();
     }
 }
@@ -260,6 +170,9 @@ DepthOffsetAdapter::setGraph(osg::Node* graph)
         (graph && graphChanging ) || 
         (graph && (_options.enabled() == true));
 
+    // shader package:
+    Shaders shaders;
+
     if ( uninstall )
     {
         OE_TEST << LC << "Removing depth offset shaders" << std::endl;
@@ -270,16 +183,8 @@ DepthOffsetAdapter::setGraph(osg::Node* graph)
         s->removeUniform( _maxBiasUniform.get() );
         s->removeUniform( _minRangeUniform.get() );
         s->removeUniform( _maxRangeUniform.get() );
-
-        VirtualProgram* vp = VirtualProgram::get( s );
-        if ( vp )
-        {
-            vp->removeShader( "oe_doff_vertex" );
-
-#ifndef VERTEX_ONLY_METHOD
-            vp->removeShader( "oe_doff_fragment" );
-#endif
-        }
+        
+        shaders.unload( VirtualProgram::get(s), shaders.DepthOffsetVertex );
     }
 
     if ( install )
@@ -292,14 +197,8 @@ DepthOffsetAdapter::setGraph(osg::Node* graph)
         s->addUniform( _maxBiasUniform.get() );
         s->addUniform( _minRangeUniform.get() );
         s->addUniform( _maxRangeUniform.get() );
-
-        VirtualProgram* vp = VirtualProgram::getOrCreate( s );
-
-        vp->setFunction( "oe_doff_vertex", s_vertex, ShaderComp::LOCATION_VERTEX_VIEW );
-
-#ifndef VERTEX_ONLY_METHOD
-        vp->setFunction( "oe_doff_fragment", s_fragment, ShaderComp::LOCATION_FRAGMENT_COLORING );
-#endif
+        
+        shaders.load(VirtualProgram::getOrCreate(s), shaders.DepthOffsetVertex);        
     }
 
     if ( graphChanging )
diff --git a/src/osgEarth/DepthOffset.vert.glsl b/src/osgEarth/DepthOffset.vert.glsl
new file mode 100644
index 0000000..c22e2a8
--- /dev/null
+++ b/src/osgEarth/DepthOffset.vert.glsl
@@ -0,0 +1,30 @@
+#version $GLSL_VERSION_STR
+$GLSL_DEFAULT_PRECISION_FLOAT
+
+#pragma vp_entryPoint "oe_depthOffset_vertex"
+#pragma vp_location   "vertex_view"
+#pragma vp_order      "0.8"
+
+uniform float oe_depthOffset_minBias;
+uniform float oe_depthOffset_maxBias;
+uniform float oe_depthOffset_minRange;
+uniform float oe_depthOffset_maxRange;
+
+void oe_depthOffset_vertex(inout vec4 vertexView)
+{
+    // calculate range to target:
+    float range = length(vertexView.xyz);
+
+    // calculate the depth offset bias for this range:
+    float ratio = (clamp(range, oe_depthOffset_minRange, oe_depthOffset_maxRange)-oe_depthOffset_minRange)/(oe_depthOffset_maxRange-oe_depthOffset_minRange);
+    float bias = oe_depthOffset_minBias + ratio * (oe_depthOffset_maxBias-oe_depthOffset_minBias);
+
+	// 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);
+
+    //   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/Draggers b/src/osgEarth/Draggers
deleted file mode 100644
index ee47c6f..0000000
--- a/src/osgEarth/Draggers
+++ /dev/null
@@ -1,170 +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_DRAGGERS_H
-#define OSGEARTH_DRAGGERS_H
-
-#include <osgEarth/Common>
-#include <osgEarth/GeoData>
-#include <osgEarth/TileKey>
-#include <osgEarth/MapNodeObserver>
-#include <osg/MatrixTransform>
-#include <osg/ShapeDrawable>
-#include <osgGA/GUIEventHandler>
-#include <osgManipulator/Projector>
-#include <osgEarth/Terrain>
-
-namespace osgEarth
-{
-    class MapNode;
-    class Terrain;
-
-    class OSGEARTH_EXPORT Dragger : public osg::MatrixTransform, public MapNodeObserver
-    {
-    public:
-        /**
-        * Callback that is fired when the position changes
-        */
-        struct PositionChangedCallback : public osg::Referenced
-        {
-        public:
-            virtual void onPositionChanged(const Dragger* sender, const osgEarth::GeoPoint& position) {};
-            virtual ~PositionChangedCallback() { }
-        };
-
-        typedef std::list< osg::ref_ptr<PositionChangedCallback> > PositionChangedCallbackList;
-
-        enum DragMode
-        {
-          DRAGMODE_HORIZONTAL,
-          DRAGMODE_VERTICAL
-        };
-
-        Dragger( MapNode* mapNode, int modKeyMask=0, const DragMode& defaultMode=DRAGMODE_HORIZONTAL );
-
-        /** dtor */
-        virtual ~Dragger();
-
-        bool getDragging() const;
-
-        bool getHovered() const;
-
-        const osgEarth::GeoPoint& getPosition() const;
-
-        void setPosition( const osgEarth::GeoPoint& position, bool fireEvents=true);
-
-        void setModKeyMask(int mask) { _modKeyMask = mask; }
-
-        int getModKeyMask() const { return _modKeyMask; }
-
-        void setDefaultDragMode(const DragMode& mode) { _defaultMode = mode; }
-
-        DragMode& getDefaultDragMode() { return _defaultMode; }
-
-        void setVerticalMinimum(double min) { _verticalMinimum = min; }
-
-        double getVerticalMinimim() const { return _verticalMinimum; }
-
-
-        void updateTransform(osg::Node* patch = 0);
-
-        virtual void enter();
-
-        virtual void leave();
-
-        virtual void setColor( const osg::Vec4f& color ) =0;
-
-        virtual void setPickColor( const osg::Vec4f& color ) =0;
-
-        void addPositionChangedCallback( PositionChangedCallback* callback );
-
-        void removePositionChangedCallback( PositionChangedCallback* callback );
-
-        virtual void traverse(osg::NodeVisitor& nv);        
-
-        virtual void reclamp( const TileKey& key, osg::Node* tile, const Terrain* terrain );
-
-
-    public: // MapNodeObserver
-
-        virtual void setMapNode( MapNode* mapNode );
-
-        virtual MapNode* getMapNode() { return _mapNode.get(); }
-
-
-    protected:
-        virtual bool handle(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa);
-        void setHover( bool hovered);
-        void firePositionChanged();
-
-        osg::ref_ptr< TerrainCallback > _autoClampCallback;
-
-        osg::observer_ptr< MapNode > _mapNode;
-        osgEarth::GeoPoint _position;
-        bool _dragging;
-        bool _hovered;
-        PositionChangedCallbackList _callbacks;
-
-        osg::ref_ptr<  osgManipulator::LineProjector >  _projector;
-        osgManipulator::PointerInfo  _pointer;
-        osg::Vec3d _startProjectedPoint;
-        bool _elevationDragging;
-        int _modKeyMask;
-        DragMode _defaultMode;
-        double _verticalMinimum;
-    };
-
-    /**********************************************************/
-    class OSGEARTH_EXPORT SphereDragger : public Dragger
-    {
-    public:
-        SphereDragger(MapNode* mapNode);
-
-        /** dtor */
-        virtual ~SphereDragger() { }
-
-        const osg::Vec4f& getColor() const;
-
-        void setColor(const osg::Vec4f& color);
-
-        const osg::Vec4f& getPickColor() const;
-
-        void setPickColor(const osg::Vec4f& pickColor);
-
-        float getSize() const;
-        void setSize(float size);
-
-        virtual void enter();
-
-        virtual void leave();
-
-    protected:
-
-        void updateColor();
-
-        osg::MatrixTransform* _scaler;
-        osg::ShapeDrawable* _shapeDrawable;
-        osg::Vec4f _pickColor;
-        osg::Vec4f _color;
-        float _size;
-    };
-
-
-} // namespace osgEarth
-
-#endif // OSGEARTH_DRAGGERS_H
diff --git a/src/osgEarth/Draggers.cpp b/src/osgEarth/Draggers.cpp
deleted file mode 100644
index 53f4c82..0000000
--- a/src/osgEarth/Draggers.cpp
+++ /dev/null
@@ -1,486 +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 <osgEarth/Draggers>
-#include <osgEarth/MapNode>
-#include <osgEarth/Pickers>
-
-#include <osg/AutoTransform>
-#include <osgViewer/View>
-
-#include <osg/io_utils>
-
-#include <osgGA/EventVisitor>
-
-#include <osgManipulator/Dragger>
-
-
-
-using namespace osgEarth;
-
-struct ClampDraggerCallback : public TerrainCallback
-{
-    ClampDraggerCallback( Dragger* dragger ):
-_dragger( dragger )
-{
-}
-
-void onTileAdded( const TileKey& key, osg::Node* tile, TerrainCallbackContext& context )
-{    
-    _dragger->reclamp( key, tile, context.getTerrain() );
-}
-
-Dragger* _dragger;
-};
-
-/**********************************************************/
-Dragger::Dragger( MapNode* mapNode, int modKeyMask, const DragMode& defaultMode ):
-_position( mapNode->getMapSRS(), 0,0,0, ALTMODE_RELATIVE),
-_dragging(false),
-_hovered(false),
-_modKeyMask(modKeyMask),
-_defaultMode(defaultMode),
-_elevationDragging(false),
-_verticalMinimum(0.0)
-{    
-    setNumChildrenRequiringEventTraversal( 1 );
-
-    _autoClampCallback = new ClampDraggerCallback( this );
-    _projector = new osgManipulator::LineProjector;
-
-    setMapNode( mapNode );
-}
-
-Dragger::~Dragger()
-{
-    setMapNode( 0L );
-}
-
-void
-Dragger::setMapNode( MapNode* mapNode )
-{
-    MapNode* oldMapNode = getMapNode();
-
-    if ( oldMapNode != mapNode )
-    {
-        if ( oldMapNode && _autoClampCallback.valid() )
-        {
-            oldMapNode->getTerrain()->removeTerrainCallback( _autoClampCallback.get() );
-        }
-
-        _mapNode = mapNode;
-
-        if ( _mapNode.valid() && _autoClampCallback.valid() )
-        {            
-            _mapNode->getTerrain()->addTerrainCallback( _autoClampCallback.get() );
-        }
-    }
-}
-
-bool Dragger::getDragging() const
-{
-    return _dragging;
-}
-
-bool Dragger::getHovered() const
-{
-    return _hovered;
-}
-
-const GeoPoint& Dragger::getPosition() const
-{
-    return _position;
-}
-
-void Dragger::setPosition( const GeoPoint& position, bool fireEvents)
-{
-    if (_position != position)
-    {
-        _position = position;
-        updateTransform();
-
-        if ( fireEvents )
-            firePositionChanged();
-    }
-}
-
-void Dragger::firePositionChanged()
-{
-    for( PositionChangedCallbackList::iterator i = _callbacks.begin(); i != _callbacks.end(); i++ )
-    {
-        i->get()->onPositionChanged(this, _position);
-    }
-}
-
-void Dragger::updateTransform(osg::Node* patch)
-{
-    if ( getMapNode() )
-    {
-        osg::Matrixd matrix;
-        
-        GeoPoint mapPoint( _position );
-        mapPoint = mapPoint.transform( _mapNode->getMapSRS() );
-        if (!mapPoint.makeAbsolute( getMapNode()->getTerrain() ))
-        {
-            OE_WARN << "Failed to clamp dragger" << std::endl;
-            return;            
-        }
-
-        mapPoint.createLocalToWorld( matrix );
-        setMatrix( matrix );
-    }
-}
-
-void Dragger::enter()
-{
-}
-
-void Dragger::leave()
-{        
-}    
-
-void Dragger::addPositionChangedCallback( PositionChangedCallback* callback )
-{
-    _callbacks.push_back( callback );
-}
-
-void Dragger::removePositionChangedCallback( PositionChangedCallback* callback )
-{
-    PositionChangedCallbackList::iterator i = std::find( _callbacks.begin(), _callbacks.end(), callback);
-    if (i != _callbacks.end())
-    {
-        _callbacks.erase( i );
-    }    
-}
-
-void Dragger::traverse(osg::NodeVisitor& nv)
-{
-    if (nv.getVisitorType() == osg::NodeVisitor::EVENT_VISITOR)
-    {
-        osgGA::EventVisitor* ev = static_cast<osgGA::EventVisitor*>(&nv);
-        for(osgGA::EventQueue::Events::iterator itr = ev->getEvents().begin();
-            itr != ev->getEvents().end();
-            ++itr)
-        {
-            osgGA::GUIEventAdapter* ea = dynamic_cast<osgGA::GUIEventAdapter*>(itr->get());
-            if ( ea && handle(*ea, *(ev->getActionAdapter())))
-                ea->setHandled(true);
-        }
-    }
-    osg::MatrixTransform::traverse(nv);
-}
-
-bool Dragger::handle(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa)
-{
-    if (ea.getHandled()) return false;
-
-    osgViewer::View* view = dynamic_cast<osgViewer::View*>(&aa);
-    if (!view) return false;
-    if (!_mapNode.valid()) return false;
-
-    if (ea.getEventType() == osgGA::GUIEventAdapter::PUSH)
-    {
-        Picker picker( view, this );
-        Picker::Hits hits;
-
-        if ( picker.pick( ea.getX(), ea.getY(), hits ) )
-        {
-            _dragging = true;
-
-            //Check for and handle vertical dragging if necessary
-            bool pressedAlt = _modKeyMask && (ea.getModKeyMask() & _modKeyMask) > 0;
-            _elevationDragging = (_defaultMode == Dragger::DRAGMODE_VERTICAL && !pressedAlt) || (_defaultMode == Dragger::DRAGMODE_HORIZONTAL && pressedAlt);
-
-            if (_elevationDragging)
-            {
-              _pointer.reset();
-
-              // set movement range
-              // TODO: values 0.0 and 300000.0 are rather experimental
-              GeoPoint posStart(_position.getSRS(), _position.x(), _position.y(), 0.0, ALTMODE_ABSOLUTE);
-              osg::Vec3d posStartXYZ;
-              posStart.toWorld(posStartXYZ);
-
-              GeoPoint posEnd(_position.getSRS(), _position.x(), _position.y(), 300000.0, ALTMODE_ABSOLUTE);
-              osg::Vec3d posEndXYZ;
-              posEnd.toWorld(posEndXYZ);
-
-              _projector->setLine(posStartXYZ, posEndXYZ);
-
-              // set camera
-              osgUtil::LineSegmentIntersector::Intersections intersections;
-              osg::Node::NodeMask intersectionMask = 0xffffffff;
-              osgViewer::View* view = dynamic_cast<osgViewer::View*>(&aa);
-              if (view->computeIntersections(ea.getX(),ea.getY(),intersections, intersectionMask))
-              {
-                  for (osgUtil::LineSegmentIntersector::Intersections::iterator hitr = intersections.begin(); hitr != intersections.end(); ++hitr)
-                  {
-                      _pointer.addIntersection(hitr->nodePath, hitr->getLocalIntersectPoint());
-                  }
-
-                  bool draggerFound = false;
-                  for (osgManipulator::PointerInfo::IntersectionList::iterator piit = _pointer._hitList.begin(); piit != _pointer._hitList.end(); ++piit)
-                  {
-                      for (osg::NodePath::iterator itr = piit->first.begin(); itr != piit->first.end(); ++itr)
-                      {
-                          Dragger* dragger = dynamic_cast<Dragger*>(*itr);
-                          if (dragger==this)
-                          {
-                            draggerFound = true;
-                              osg::Camera *rootCamera = view->getCamera();
-                              osg::NodePath nodePath = _pointer._hitList.front().first;
-                              osg::NodePath::reverse_iterator ritr;
-                              for (ritr = nodePath.rbegin(); ritr != nodePath.rend(); ++ritr)
-                              {
-                                  osg::Camera* camera = dynamic_cast<osg::Camera*>(*ritr);
-                                  if (camera && (camera->getReferenceFrame()!=osg::Transform::RELATIVE_RF || camera->getParents().empty()))
-                                  {
-                                       rootCamera = camera;
-                                       break;
-                                  }
-                              }
-                              _pointer.setCamera(rootCamera);
-                              _pointer.setMousePosition(ea.getX(), ea.getY());
-
-                              break;
-                          }
-                      }
-
-                      if (draggerFound)
-                        break;
-                  }
-              }
-            }
-
-            aa.requestRedraw();
-            return true;
-        }
-    }
-    else if (ea.getEventType() == osgGA::GUIEventAdapter::RELEASE)
-    {
-        _elevationDragging = false;
-
-        if ( _dragging )
-        {
-            _dragging = false;
-            firePositionChanged();
-        }
-
-        aa.requestRedraw();
-    }
-    else if (ea.getEventType() == osgGA::GUIEventAdapter::DRAG)
-    {
-        if (_elevationDragging) 
-        {
-            _pointer._hitIter = _pointer._hitList.begin();
-            _pointer.setMousePosition(ea.getX(), ea.getY());
-
-            if (_projector->project(_pointer, _startProjectedPoint)) 
-            {
-                //Get the absolute mapPoint that they've drug it to.
-                GeoPoint projectedPos;
-                projectedPos.fromWorld(_position.getSRS(), _startProjectedPoint);
-
-                // make sure point is not dragged down below
-                // TODO: think of a better solution / HeightAboveTerrain performance issues?
-                if (projectedPos.z() >= _verticalMinimum)
-                {
-                    //If the current position is relative, we need to convert the absolute world point to relative.
-                    //If the point is absolute then just emit the absolute point.
-                    if (_position.altitudeMode() == ALTMODE_RELATIVE)
-                    {
-                        projectedPos.transformZ(ALTMODE_RELATIVE, getMapNode()->getTerrain());
-                    }
-
-                    setPosition( projectedPos );
-                    aa.requestRedraw();
-                }
-            }
-
-            return true;
-        }
-        
-        if (_dragging)
-        {
-            osg::Vec3d world;
-            if ( getMapNode() && getMapNode()->getTerrain()->getWorldCoordsUnderMouse(view, ea.getX(), ea.getY(), world) )
-            {
-                //Get the absolute mapPoint that they've drug it to.
-                GeoPoint mapPoint;
-                mapPoint.fromWorld( getMapNode()->getMapSRS(), world );
-                //_mapNode->getMap()->worldPointToMapPoint(world, mapPoint);
-
-                //If the current position is relative, we need to convert the absolute world point to relative.
-                //If the point is absolute then just emit the absolute point.
-                if (_position.altitudeMode() == ALTMODE_RELATIVE)
-                {
-                    mapPoint.alt() = _position.alt();
-                    mapPoint.altitudeMode() = ALTMODE_RELATIVE;
-                }
-
-                setPosition( mapPoint );
-                aa.requestRedraw();
-                return true;
-            }
-        }
-    }   
-    else if (ea.getEventType() == osgGA::GUIEventAdapter::MOVE)
-    {
-        Picker picker( view, this );
-        Picker::Hits hits;
-
-        if ( picker.pick( ea.getX(), ea.getY(), hits ) )
-        {
-            setHover( true );
-        }
-        else
-        {
-            setHover( false );
-        }        
-        aa.requestRedraw();
-    }
-    return false;
-}
-
-void Dragger::setHover( bool hovered)
-{
-    if (_hovered != hovered)
-    {
-        bool wasHovered = _hovered;
-        _hovered = hovered;
-        if (wasHovered)
-        {
-            leave();            
-        }
-        else
-        {
-            enter();
-        }
-    }
-}
-
-void Dragger::reclamp( const TileKey& key, osg::Node* tile, const Terrain* terrain )
-{            
-    GeoPoint p;
-    _position.transform( key.getExtent().getSRS(), p );
-    // first verify that the control position intersects the tile:
-    if ( key.getExtent().contains( p.x(), p.y() ) )
-    {
-        updateTransform( tile );
-    }
-}
-
-
-
-/**********************************************************/
-
-SphereDragger::SphereDragger(MapNode* mapNode):
-Dragger(mapNode),
-_pickColor(1.0f, 1.0f, 0.0f, 1.0f),
-_color(0.0f, 1.0f, 0.0f, 1.0f),
-_size( 5.0f )
-{
-    //Disable culling
-    setCullingActive( false );
-
-    //Build the handle
-    osg::Sphere* shape = new osg::Sphere(osg::Vec3(0,0,0), 1.0f);   
-    osg::Geode* geode = new osg::Geode();
-    _shapeDrawable = new osg::ShapeDrawable( shape );    
-    _shapeDrawable->setDataVariance( osg::Object::DYNAMIC );
-    geode->addDrawable( _shapeDrawable );          
-
-    geode->getOrCreateStateSet()->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF);
-    geode->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
-
-    _scaler = new osg::MatrixTransform;
-    _scaler->setMatrix( osg::Matrixd::scale( _size, _size, _size ));
-    _scaler->addChild( geode );
-
-    osg::AutoTransform* at = new osg::AutoTransform;
-    at->setAutoScaleToScreen( true );
-    at->addChild( _scaler );
-    addChild( at );
-
-    updateColor();
-}
-
-const osg::Vec4f& SphereDragger::getColor() const
-{
-    return _color;
-}
-
-void SphereDragger::setColor(const osg::Vec4f& color)
-{
-    if (_color != color)
-    {
-        _color = color;
-        updateColor();
-    }
-}
-
-const osg::Vec4f& SphereDragger::getPickColor() const
-{
-    return _pickColor;
-}
-
-void SphereDragger::setPickColor(const osg::Vec4f& pickColor)
-{
-    if (_pickColor != pickColor)
-    {
-        _pickColor = pickColor;
-        updateColor();
-    }
-}
-
-float SphereDragger::getSize() const
-{
-    return _size;
-}
-
-void SphereDragger::setSize(float size)
-{
-    if (_size != size)
-    {
-        _size = size;
-        _scaler->setMatrix( osg::Matrixd::scale( _size, _size, _size ));
-    }
-}
-
-void SphereDragger::enter()
-{
-    updateColor();
-}
-
-void SphereDragger::leave()
-{
-    updateColor();
-}
-
-void SphereDragger::updateColor()
-{
-    if (getHovered())
-    {
-        _shapeDrawable->setColor( _pickColor );
-    }        
-    else
-    {
-        _shapeDrawable->setColor( _color );
-    }
-}
-
diff --git a/src/osgEarth/DrapeableNode.cpp b/src/osgEarth/DrapeableNode.cpp
index 1ccaa33..13a9a2a 100644
--- a/src/osgEarth/DrapeableNode.cpp
+++ b/src/osgEarth/DrapeableNode.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarth/Draping.frag.glsl b/src/osgEarth/Draping.frag.glsl
new file mode 100644
index 0000000..3ef7e0b
--- /dev/null
+++ b/src/osgEarth/Draping.frag.glsl
@@ -0,0 +1,19 @@
+#version $GLSL_VERSION_STR
+$GLSL_DEFAULT_PRECISION_FLOAT
+
+#pragma vp_entryPoint "oe_overlay_fragment"
+#pragma vp_location   "fragment_coloring"
+#pragma vp_order      "0.6"
+
+uniform bool      oe_isPickCamera;
+uniform sampler2D oe_overlay_tex;
+varying 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);
+
+    float pick = oe_isPickCamera? 1.0 : 0.0;
+    color = mix(blendedTexel, texel, pick);
+}
diff --git a/src/osgEarth/Draping.vert.glsl b/src/osgEarth/Draping.vert.glsl
new file mode 100644
index 0000000..3da1f21
--- /dev/null
+++ b/src/osgEarth/Draping.vert.glsl
@@ -0,0 +1,13 @@
+#version $GLSL_VERSION_STR
+$GLSL_DEFAULT_PRECISION_FLOAT
+
+#pragma vp_entryPoint "oe_overlay_vertex"
+#pragma vp_location   "vertex_view"
+
+uniform mat4 oe_overlay_texmatrix;
+varying 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/DrapingTechnique b/src/osgEarth/DrapingTechnique
index 06de95d..d6f7f90 100644
--- a/src/osgEarth/DrapingTechnique
+++ b/src/osgEarth/DrapingTechnique
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/osgEarth/DrapingTechnique.cpp b/src/osgEarth/DrapingTechnique.cpp
index bcc66f0..4328777 100644
--- a/src/osgEarth/DrapingTechnique.cpp
+++ b/src/osgEarth/DrapingTechnique.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
@@ -19,7 +22,9 @@
 #include <osgEarth/DrapingTechnique>
 #include <osgEarth/Capabilities>
 #include <osgEarth/Registry>
+#include <osgEarth/ShaderFactory>
 #include <osgEarth/VirtualProgram>
+#include <osgEarth/Shaders>
 
 #include <osg/BlendFunc>
 #include <osg/TexGen>
@@ -331,7 +336,7 @@ DrapingTechnique::reestablish(TerrainEngineNode* engine)
         else if ( !_textureUnit.isSet() )
         {
             int texUnit;
-            if ( engine->getTextureCompositor()->reserveTextureImageUnit( texUnit ) )
+            if ( engine->getResources()->reserveTextureImageUnit(texUnit, "DrapingTechnique") )
             {
                 _textureUnit = texUnit;
                 OE_INFO << LC << "Reserved texture image unit " << *_textureUnit << std::endl;
@@ -405,13 +410,11 @@ DrapingTechnique::setUpCamera(OverlayDecorator::TechRTTParams& params)
     // set up a StateSet for the RTT camera.
     osg::StateSet* rttStateSet = params._rttCamera->getOrCreateStateSet();
 
-    // lighting is off. We don't want draped items to be lit.
-    rttStateSet->setMode( GL_LIGHTING, osg::StateAttribute::OFF | osg::StateAttribute::PROTECTED );
+    osg::StateAttribute::OverrideValue forceOff =
+        osg::StateAttribute::OFF | osg::StateAttribute::PROTECTED | osg::StateAttribute::OVERRIDE;
 
-    // install a new default shader program that replaces anything from above.
-    VirtualProgram* rtt_vp = VirtualProgram::getOrCreate(rttStateSet);
-    rtt_vp->setName( "DrapingTechnique RTT" );
-    rtt_vp->setInheritShaders( false );
+    rttStateSet->addUniform( Registry::shaderFactory()->createUniformForGLMode(GL_LIGHTING, forceOff) );
+    rttStateSet->setMode( GL_LIGHTING, forceOff );
     
     // activate blending within the RTT camera's FBO
     if ( _rttBlending )
@@ -468,34 +471,10 @@ DrapingTechnique::setUpCamera(OverlayDecorator::TechRTTParams& params)
     local->_texGenUniform = params._terrainStateSet->getOrCreateUniform(
         "oe_overlay_texmatrix", osg::Uniform::FLOAT_MAT4 );
 
-    // vertex shader - subgraph
-    std::string vs =
-        "#version " GLSL_VERSION_STR "\n"
-        GLSL_DEFAULT_PRECISION_FLOAT "\n"
-        "uniform mat4 oe_overlay_texmatrix; \n"
-        "varying vec4 oe_overlay_texcoord; \n"
-
-        "void oe_overlay_vertex(inout vec4 VertexVIEW) \n"
-        "{ \n"
-        "    oe_overlay_texcoord = oe_overlay_texmatrix * VertexVIEW; \n"
-        "} \n";
-
-    terrain_vp->setFunction( "oe_overlay_vertex", vs, ShaderComp::LOCATION_VERTEX_VIEW );
-
-    // fragment shader - subgraph
-    std::string fs =
-        "#version " GLSL_VERSION_STR "\n"
-        GLSL_DEFAULT_PRECISION_FLOAT "\n"
-        "uniform sampler2D oe_overlay_tex; \n"
-        "varying vec4      oe_overlay_texcoord; \n"
-
-        "void oe_overlay_fragment( inout vec4 color ) \n"
-        "{ \n"
-        "    vec4 texel = texture2DProj(oe_overlay_tex, oe_overlay_texcoord); \n"
-        "    color = vec4( mix( color.rgb, texel.rgb, texel.a ), color.a); \n"
-        "} \n";
-
-    terrain_vp->setFunction( "oe_overlay_fragment", fs, ShaderComp::LOCATION_FRAGMENT_COLORING );
+    // shaders
+    Shaders pkg;
+    pkg.load( terrain_vp, pkg.DrapingVertex );
+    pkg.load( terrain_vp, pkg.DrapingFragment );
 }
 
 
@@ -642,7 +621,7 @@ DrapingTechnique::onUninstall( TerrainEngineNode* engine )
 {
     if ( !_explicitTextureUnit.isSet() && _textureUnit.isSet() )
     {
-        engine->getTextureCompositor()->releaseTextureImageUnit( *_textureUnit );
+        engine->getResources()->releaseTextureImageUnit( *_textureUnit );
         _textureUnit.unset();
     }
 }
diff --git a/src/osgEarth/DrawInstanced b/src/osgEarth/DrawInstanced
index 5db2179..0297904 100644
--- a/src/osgEarth/DrawInstanced
+++ b/src/osgEarth/DrawInstanced
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarth/DrawInstanced.cpp b/src/osgEarth/DrawInstanced.cpp
index 166d29f..d4e70f4 100644
--- a/src/osgEarth/DrawInstanced.cpp
+++ b/src/osgEarth/DrawInstanced.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -19,16 +19,20 @@
 #include <osgEarth/DrawInstanced>
 #include <osgEarth/CullingUtils>
 #include <osgEarth/ShaderGenerator>
+#include <osgEarth/ShaderUtils>
 #include <osgEarth/StateSetCache>
 #include <osgEarth/Registry>
 #include <osgEarth/Capabilities>
 #include <osgEarth/ImageUtils>
 #include <osgEarth/Utils>
+#include <osgEarth/Shaders>
+#include <osgEarth/ObjectIndex>
 
 #include <osg/ComputeBoundsVisitor>
 #include <osg/MatrixTransform>
 #include <osg/UserDataContainer>
 #include <osg/LOD>
+#include <osg/TextureBuffer>
 #include <osgUtil/MeshOptimizers>
 
 #define LC "[DrawInstanced] "
@@ -38,9 +42,7 @@ using namespace osgEarth::DrawInstanced;
 
 // Ref: http://sol.gfxile.net/instancing.html
 
-#define POSTEX_TEXTURE_UNIT 5
-#define POSTEX_MAX_TEXTURE_SIZE 256
-
+#define POSTEX_TBO_UNIT 5
 #define TAG_MATRIX_VECTOR "osgEarth::DrawInstanced::MatrixRefVector"
 
 //Uncomment to experiment with instance count adjustment
@@ -117,7 +119,14 @@ namespace
     };
 #endif // USE_INSTANCE_LODS
 
-    typedef std::map< osg::ref_ptr<osg::Node>, std::vector<osg::Matrix> > ModelNodeMatrices;
+    struct ModelInstance
+    {
+        ModelInstance() : objectID( OSGEARTH_OBJECTID_EMPTY ) { }
+        osg::Matrix matrix;
+        ObjectID    objectID;
+    };
+
+    typedef std::map< osg::ref_ptr<osg::Node>, std::vector<ModelInstance> > ModelInstanceMap;
     
     /**
      * Simple bbox callback to return a static bbox.
@@ -130,7 +139,7 @@ namespace
     };
 
     // assume x is positive
-    int nextPowerOf2(int x)
+    static int nextPowerOf2(int x)
     {
         --x;
         x |= x >> 1;
@@ -140,36 +149,6 @@ namespace
         x |= x >> 16;
         return x+1;
     }
-
-    osg::Vec2f calculateIdealTextureSize(unsigned numMats, unsigned maxNumVec4sPerSpan)
-    {
-        unsigned numVec4s = 4 * numMats;
-
-        bool npotOK = false; //Registry::capabilities().supportsNonPowerOfTwoTextures();
-        if ( npotOK )
-        {
-            unsigned cols = std::min(numVec4s, maxNumVec4sPerSpan);
-            unsigned rows = 
-                cols < maxNumVec4sPerSpan ? 1 : 
-                numVec4s % cols == 0 ? numVec4s / cols :
-                1 + (numVec4s / cols);
-            return osg::Vec2f( (float)cols, (float)rows );
-        }
-        else
-        {
-            // start with a square:
-            int x = (int)ceil(sqrt((float)numVec4s));
-
-            // round the x dimension up to a power of 2:
-            x = nextPowerOf2( x );
-
-            // recalculate the necessary rows, given the new column count:
-            int y = numVec4s % x == 0 ? numVec4s/x : 1 + (numVec4s/x);
-            y = nextPowerOf2( y );
-
-            return osg::Vec2f((float)x, (float)y);
-        }
-    }
 }
 
 //----------------------------------------------------------------------
@@ -203,8 +182,11 @@ ConvertToDrawInstanced::apply( osg::Geode& geode )
                 geom->setUseVertexBufferObjects( true );
             }
 
-            geom->setComputeBoundingBoxCallback( _staticBBoxCallback.get() ); 
-            geom->dirtyBound();
+            if ( _staticBBoxCallback.valid() )
+            {
+                geom->setComputeBoundingBoxCallback( _staticBBoxCallback.get() ); 
+                geom->dirtyBound();
+            }
 
             // convert to use DrawInstanced
             for( unsigned p=0; p<geom->getNumPrimitiveSets(); ++p )
@@ -256,35 +238,13 @@ DrawInstanced::install(osg::StateSet* stateset)
     if ( !stateset )
         return;
 
-    // simple vertex program to position a vertex based on its instance
-    // matrix, which is stored in a texture.
-     std::string src_vert = Stringify() 
-        << "#version 120 \n" 
-        << "#extension GL_EXT_gpu_shader4 : enable \n" 
-        << "#extension GL_ARB_draw_instanced: enable \n" 
-        << "uniform sampler2D oe_di_postex; \n" 
-        << "uniform vec2 oe_di_postex_size; \n" 
-        << "void oe_di_setInstancePosition(inout vec4 VertexMODEL) \n" 
-        << "{ \n" 
-        << "    float index = float(4 * gl_InstanceID) / oe_di_postex_size.x; \n" 
-        << "    float s = fract(index); \n" 
-        << "    float t = floor(index)/oe_di_postex_size.y; \n" 
-        << "    float step = 1.0 / oe_di_postex_size.x; \n"  // step from one vec4 to the next 
-        << "    vec4 m0 = texture2D(oe_di_postex, vec2(s, t)); \n" 
-        << "    vec4 m1 = texture2D(oe_di_postex, vec2(s+step, t)); \n" 
-        << "    vec4 m2 = texture2D(oe_di_postex, vec2(s+step+step, t)); \n" 
-        << "    vec4 m3 = texture2D(oe_di_postex, vec2(s+step+step+step, t)); \n" 
-        << "    VertexMODEL = VertexMODEL * mat4(m0, m1, m2, m3); \n" // why??? 
-        << "} \n"; 
 
     VirtualProgram* vp = VirtualProgram::getOrCreate(stateset);
+    
+    osgEarth::Shaders pkg;
+    pkg.load( vp, pkg.InstancingVertex );
 
-    vp->setFunction(
-        "oe_di_setInstancePosition",
-        src_vert,
-        ShaderComp::LOCATION_VERTEX_MODEL );
-
-    stateset->getOrCreateUniform("oe_di_postex", osg::Uniform::SAMPLER_2D)->set(POSTEX_TEXTURE_UNIT);
+    stateset->getOrCreateUniform("oe_di_postex_TBO", osg::Uniform::SAMPLER_BUFFER)->set(POSTEX_TBO_UNIT);
 }
 
 
@@ -298,10 +258,11 @@ DrawInstanced::remove(osg::StateSet* stateset)
     if ( !vp )
         return;
 
-    vp->removeShader( "oe_di_setInstancePosition" );
+    Shaders pkg;
+    pkg.unload( vp, pkg.InstancingVertex );
 
-    stateset->removeUniform("oe_di_postex");
-    stateset->removeUniform("oe_di_postex_size");
+    stateset->removeUniform("oe_di_postex_TBO");
+    stateset->removeUniform("oe_di_postex_TBO_size");
 }
 
 
@@ -314,7 +275,7 @@ DrawInstanced::convertGraphToUseDrawInstanced( osg::Group* parent )
     parent->setComputeBoundingSphereCallback( new StaticBound(bs) );
     parent->dirtyBound();
 
-    ModelNodeMatrices models;
+    ModelInstanceMap models;
 
     // collect the matrices for all the MT's under the parent. Obviously this assumes
     // a particular scene graph structure.
@@ -324,25 +285,42 @@ DrawInstanced::convertGraphToUseDrawInstanced( osg::Group* parent )
         osg::MatrixTransform* mt = dynamic_cast<osg::MatrixTransform*>( parent->getChild(i) );
         if ( mt )
         {
-            // we have to deep-copy the primitives because we're going to convert them
-            // to use draw-instancing.
             osg::Node* n = mt->getChild(0);
-            models[n].push_back( mt->getMatrix() );
+            //models[n].push_back( mt->getMatrix() );
+
+            ModelInstance instance;
+            instance.matrix = mt->getMatrix();
+
+            // See whether the ObjectID is encoded in a uniform on the MT.
+            osg::StateSet* stateSet = mt->getStateSet();
+            if ( stateSet )
+            {
+                osg::Uniform* uniform = stateSet->getUniform( Registry::objectIndex()->getObjectIDUniformName() );
+                if ( uniform )
+                {
+                    uniform->get( (unsigned&)instance.objectID );
+                }
+            }
+
+            models[n].push_back( instance );
         }
     }
 
     // get rid of the old matrix transforms.
     parent->removeChildren(0, parent->getNumChildren());
 
-    // maximum size of a slice.
-    unsigned maxTexSize   = POSTEX_MAX_TEXTURE_SIZE;
-    unsigned maxSliceSize = (maxTexSize*maxTexSize)/4; // 4 vec4s per matrix.
+	// This is the maximum size of the tbo 
+	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.
 
     // For each model:
-    for( ModelNodeMatrices::iterator i = models.begin(); i != models.end(); ++i )
+    for( ModelInstanceMap::iterator i = models.begin(); i != models.end(); ++i )
     {
-        osg::Node*                node     = i->first.get();
-        std::vector<osg::Matrix>& matrices = i->second;
+        osg::Node*                  node      = i->first.get();
+        std::vector<ModelInstance>& instances = i->second;
 
         // calculate the overall bounding box for the model:
         osg::ComputeBoundsVisitor cbv;
@@ -350,124 +328,100 @@ DrawInstanced::convertGraphToUseDrawInstanced( osg::Group* parent )
         const osg::BoundingBox& nodeBox = cbv.getBoundingBox();
 
         osg::BoundingBox bbox;
-        for( std::vector<osg::Matrix>::iterator m = matrices.begin(); m != matrices.end(); ++m )
+        for( std::vector<ModelInstance>::iterator m = instances.begin(); m != instances.end(); ++m )
         {
-            osg::Matrix& matrix = *m;
-            bbox.expandBy(nodeBox.corner(0) * matrix);
-            bbox.expandBy(nodeBox.corner(1) * matrix);
-            bbox.expandBy(nodeBox.corner(2) * matrix);
-            bbox.expandBy(nodeBox.corner(3) * matrix);
-            bbox.expandBy(nodeBox.corner(4) * matrix);
-            bbox.expandBy(nodeBox.corner(5) * matrix);
-            bbox.expandBy(nodeBox.corner(6) * matrix);
-            bbox.expandBy(nodeBox.corner(7) * matrix);
+            bbox.expandBy(nodeBox.corner(0) * m->matrix);
+            bbox.expandBy(nodeBox.corner(1) * m->matrix);
+            bbox.expandBy(nodeBox.corner(2) * m->matrix);
+            bbox.expandBy(nodeBox.corner(3) * m->matrix);
+            bbox.expandBy(nodeBox.corner(4) * m->matrix);
+            bbox.expandBy(nodeBox.corner(5) * m->matrix);
+            bbox.expandBy(nodeBox.corner(6) * m->matrix);
+            bbox.expandBy(nodeBox.corner(7) * m->matrix);
         }
 
-        // calculate slice count and sizes:
-        unsigned sliceSize = std::min(matrices.size(), (size_t)maxSliceSize);
-        unsigned numSlices = matrices.size() / maxSliceSize;
-        unsigned lastSliceSize = matrices.size() % maxSliceSize;
-        if ( lastSliceSize == 0 )
-            lastSliceSize = sliceSize;
-        else
-            ++numSlices;
-
+		unsigned tboSize = 0;
+		unsigned numInstancesToStore = 0;
+
+		if (instances.size()<maxTBOInstancesSize)
+		{
+			tboSize = nextPowerOf2(instances.size());
+			numInstancesToStore = instances.size();
+		}
+		else
+		{
+			OE_WARN << "Number of Instances: " << instances.size() << " exceeds Number of instances TBO can store: " << maxTBOInstancesSize << std::endl;
+			OE_WARN << "Storing maximum possible instances in TBO, and skipping the rest"<<std::endl;
+			tboSize = maxTBOInstancesSize;
+			numInstancesToStore = maxTBOInstancesSize;
+		}
+		
         // Convert the node's primitive sets to use "draw-instanced" rendering; at the
         // same time, assign our computed bounding box as the static bounds for all
         // geometries. (As DI's they cannot report bounds naturally.)
-        ConvertToDrawInstanced cdi(sliceSize, bbox, true);
+        ConvertToDrawInstanced cdi(numInstancesToStore, bbox, true);
         node->accept( cdi );
-
-        // If the number of instances is not an exact multiple of the number of slices,
-        // replicate the node so we can draw a difference instance count in the final group.
-        osg::Node* lastNode = node;
-        if ( numSlices > 1 && lastSliceSize < sliceSize )
-        {
-            // clone, but only make copies of necessary things
-            lastNode = osg::clone(
-                node, 
-                osg::CopyOp::DEEP_COPY_NODES | osg::CopyOp::DEEP_COPY_DRAWABLES | osg::CopyOp::DEEP_COPY_PRIMITIVES );
-
-            ConvertToDrawInstanced cdi(lastSliceSize, bbox, false);
-            lastNode->accept( cdi );
-        }
-
-        // Assign matrix vectors to the nodes, so the application can easily retrieve
+		
+        // Assign matrix vectors to the node, so the application can easily retrieve
         // the original position data if necessary.
         MatrixRefVector* nodeMats = new MatrixRefVector();
         nodeMats->setName(TAG_MATRIX_VECTOR);
-        nodeMats->reserve(lastNode != node ? sliceSize*(numSlices-1) : sliceSize*numSlices);
+        nodeMats->reserve(numInstancesToStore);
         node->getOrCreateUserDataContainer()->addUserObject(nodeMats);
 
-        // ...and a separate one for lastNode if necessary
-        MatrixRefVector* lastNodeMats = 0L;
-        if (lastNode != node)
-        {
-            lastNodeMats = new MatrixRefVector();
-            lastNodeMats->setName(TAG_MATRIX_VECTOR);
-            lastNodeMats->reserve(lastSliceSize);
-            lastNode->getOrCreateUserDataContainer()->addUserObject(lastNodeMats);
-        }
-
-        // Next, break the rendering down into "slices". GLSL will only support a limited
-        // amount of pre-instance uniform data, so we have to portion the graph out into
-        // slices of no more than this chunk size.
-        for( unsigned slice = 0; slice < numSlices; ++slice )
-        {
-            unsigned   offset      = slice * sliceSize;
-            unsigned   currentSize = slice == numSlices-1 ? lastSliceSize : sliceSize;
-            osg::Node* currentNode = slice == numSlices-1 ? lastNode      : node;
-
-            // this group is simply a container for the uniform:
-            osg::Group* sliceGroup = new osg::Group();
-
-            // calculate the ideal texture size for this slice:
-            osg::Vec2f texSize = calculateIdealTextureSize(currentSize, maxTexSize);
-            OE_DEBUG << LC << "size = " << currentSize << ", tex = " << texSize.x() << ", " << texSize.y() << std::endl;
-
-            // sampler that will hold the instance matrices:
-            osg::Image* image = new osg::Image();
-            image->setName("osgearth.drawinstanced.postex");
-            image->allocateImage( (int)texSize.x(), (int)texSize.y(), 1, GL_RGBA, GL_FLOAT );
-
-            osg::Texture2D* postex = new osg::Texture2D( image );
-            postex->setInternalFormat( GL_RGBA16F_ARB );
-            postex->setFilter( osg::Texture::MIN_FILTER, osg::Texture::NEAREST );
-            postex->setFilter( osg::Texture::MAG_FILTER, osg::Texture::NEAREST );
-            postex->setWrap( osg::Texture::WRAP_S, osg::Texture::CLAMP );
-            postex->setWrap( osg::Texture::WRAP_T, osg::Texture::CLAMP );
-            postex->setUnRefImageDataAfterApply( true );
-            if ( !ImageUtils::isPowerOfTwo(image) )
-                postex->setResizeNonPowerOfTwoHint( false );
-
-            // Tell the SG to skip the positioning texture.
-            ShaderGenerator::setIgnoreHint(postex, true);
-
-            osg::StateSet* stateset = sliceGroup->getOrCreateStateSet();
-            stateset->setTextureAttributeAndModes(POSTEX_TEXTURE_UNIT, postex, 1);
-            stateset->getOrCreateUniform("oe_di_postex_size", osg::Uniform::FLOAT_VEC2)->set(texSize);
-
-            // could use PixelWriter but we know the format.
-            GLfloat* ptr = reinterpret_cast<GLfloat*>( image->data() );
-            for(unsigned m=0; m<currentSize; ++m)
-            {
-                const osg::Matrixf& mat = matrices[offset + m];
-                for(int col=0; col<4; ++col)
-                    for(int row=0; row<4; ++row)
-                        *ptr++ = mat(row,col);
-
-                // store them int the metadata as well
-                if (currentNode == node)
-                    nodeMats->push_back(mat);
-                else
-                    lastNodeMats->push_back(mat);
-            }
-
-            // add the node as a child:
-            sliceGroup->addChild( currentNode );
-
-            parent->addChild( sliceGroup );
-        }
+        // this group is simply a container for the uniform:
+        osg::Group* instanceGroup = new osg::Group();
+
+        // sampler that will hold the instance matrices:
+        osg::Image* image = new osg::Image();
+        image->setName("osgearth.drawinstanced.postex");
+		image->allocateImage( tboSize*4, 1, 1, GL_RGBA, GL_FLOAT );
+
+		// could use PixelWriter but we know the format.
+		// Note: we are building a transposed matrix because it makes the decoding easier in the shader.
+		GLfloat* ptr = reinterpret_cast<GLfloat*>( image->data() );
+		for(unsigned m=0; m<numInstancesToStore; ++m)
+		{
+			ModelInstance& i = instances[m];
+			const osg::Matrixf& mat = i.matrix;
+
+			// copy the first 3 columns:
+			for(int col=0; col<3; ++col)
+			{
+				for(int row=0; row<4; ++row)
+				{
+					*ptr++ = mat(row,col);
+				}
+			}
+
+			// encode the ObjectID in the last column, which is always (0,0,0,1)
+			// in a standard scale/rot/trans matrix. We will reinstate it in the 
+			// shader after extracting the object ID.
+			*ptr++ = (float)((i.objectID      ) & 0xff);
+			*ptr++ = (float)((i.objectID >>  8) & 0xff);
+			*ptr++ = (float)((i.objectID >> 16) & 0xff);
+			*ptr++ = (float)((i.objectID >> 24) & 0xff);
+
+			// store them int the metadata as well
+			nodeMats->push_back(mat);
+		}
+
+        osg::TextureBuffer* posTBO = new osg::TextureBuffer;
+		posTBO->setImage(image);
+        posTBO->setInternalFormat( GL_RGBA32F_ARB );
+        posTBO->setUnRefImageDataAfterApply( true );
+
+        // Tell the SG to skip the positioning texture.
+        ShaderGenerator::setIgnoreHint(posTBO, true);
+
+        osg::StateSet* stateset = instanceGroup->getOrCreateStateSet();
+        stateset->setTextureAttribute(POSTEX_TBO_UNIT, posTBO);
+        stateset->getOrCreateUniform("oe_di_postex_TBO_size", osg::Uniform::INT)->set((int)tboSize);
+
+		// add the node as a child:
+        instanceGroup->addChild( node );
+
+        parent->addChild( instanceGroup );
     }
 }
 
diff --git a/src/osgEarth/ECEF b/src/osgEarth/ECEF
index e2a224e..1e6d9dc 100644
--- a/src/osgEarth/ECEF
+++ b/src/osgEarth/ECEF
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarth/ECEF.cpp b/src/osgEarth/ECEF.cpp
index 548947d..6f656fe 100644
--- a/src/osgEarth/ECEF.cpp
+++ b/src/osgEarth/ECEF.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -110,21 +110,45 @@ ECEF::transformAndLocalize(const std::vector<osg::Vec3d>& input,
 {
     const SpatialReference* ecefSRS = outputSRS->getECEF();
     out_verts->reserve( out_verts->size() + input.size() );
-
-    if ( out_normals )
-        out_normals->reserve( out_verts->size() );
-
+    
     for( std::vector<osg::Vec3d>::const_iterator i = input.begin(); i != input.end(); ++i )
     {
         osg::Vec3d ecef;
         inputSRS->transform( *i, ecefSRS, ecef );
         out_verts->push_back( ecef * world2local );
+    }
 
-        if ( out_normals )
+    if ( out_normals )
+    {
+        out_normals->reserve( out_verts->size() );
+
+        const osg::Vec3f up(0,0,1);
+        osg::Vec3f outNormal;
+        for(unsigned v=0; v < out_verts->size()-1; ++v)
         {
-            ecef.normalize();
-            out_normals->push_back( osg::Matrix::transform3x3(ecef, world2local) );
+            osg::Vec3f normal;
+            osg::Vec3f out = (*out_verts)[v+1] - (*out_verts)[v];
+            osg::Vec3f right = out ^ up;
+            outNormal = right ^ out;
+            
+            if ( v == 0 )
+            {
+                normal = outNormal;
+            }
+            else
+            {
+                osg::Vec3f in = (*out_verts)[v] - (*out_verts)[v-1];
+                osg::Vec3f inNormal = right ^ in;
+                normal = (inNormal + outNormal) * 0.5;
+            }
+
+            normal.normalize();
+            out_normals->push_back( normal );
         }
+
+        // final one.
+        outNormal.normalize();
+        out_normals->push_back( outNormal );
     }
 }
 
diff --git a/src/osgEarth/ElevationField b/src/osgEarth/ElevationField
new file mode 100644
index 0000000..820987f
--- /dev/null
+++ b/src/osgEarth/ElevationField
@@ -0,0 +1,79 @@
+/* -*-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.
+ *
+ * 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_FIELD_H
+#define OSGEARTH_ELEVATION_FIELD_H 1
+
+#include <osgEarth/Common>
+#include <osgEarth/GeoCommon>
+#include <osg/Object>
+#include <osg/Vec3f>
+
+namespace osgEarth
+{
+    /**
+     * A grid of elevation data.
+     */
+    class OSGEARTH_EXPORT ElevationField : public osg::Object
+    {
+    public:
+        META_Object(osgEarth, ElevationField);
+
+        /** Constructor */
+        ElevationField();
+
+        /**
+         * Sets the width and height (in samples) of the field.
+         * This will wipe out any existing data.
+         */
+        void setSize(unsigned width, unsigned height);
+
+        /**
+         * Gets the height value at sample x,y.
+         */
+        float getHeight(unsigned x, unsigned y) const;
+
+        /**
+         * If the data is "dirty", recalculate derived values like
+         * slope, curvature, min and max.
+         */
+        void update(float spacing);
+
+
+    protected:
+        unsigned                _width, _height;
+        std::vector<float>      _heights;
+        std::vector<float>      _slopes;
+        std::vector<float>      _curvatures;
+        std::vector<float>      _aspects;
+        std::vector<osg::Vec3f> _normals;
+        float                   _min, _max;
+        bool                    _dirty;
+        float                   _spacing;
+
+    protected:
+        virtual ~ElevationField() { }
+
+        // Copy CTOR hidden for now
+        ElevationField(const ElevationField& rhs, const osg::CopyOp& copy) { }
+
+        void computeSlopeAndCurvature();
+    };
+}
+
+#endif // OSGEARTH_ELEVATION_FIELD_H
diff --git a/src/osgEarth/ElevationField.cpp b/src/osgEarth/ElevationField.cpp
new file mode 100644
index 0000000..fda839b
--- /dev/null
+++ b/src/osgEarth/ElevationField.cpp
@@ -0,0 +1,139 @@
+/* -*-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.
+ *
+ * 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/ElevationField>
+#include <osg/Vec3>
+
+using namespace osgEarth;
+
+ElevationField::ElevationField() :
+_width ( 0 ),
+_height( 0 ),
+_dirty ( false )
+{
+    //nop
+}
+
+void
+ElevationField::setSize(unsigned width, unsigned height)
+{
+    _width  = width;
+    _height = height;
+
+    // The heights vector has a one-sample border all the way around.
+    _heights.resize( (width+2)*(height+2) );
+    _heights.assign( (width+2)*(height+2), 0.0f );
+
+    // Initialize the "border" samples to "NO DATA".
+    for(unsigned s=0; s<(_width+2); ++s)
+    {
+        _heights[s] = NO_DATA_VALUE;
+        _heights[_width*(_height+1) + s] = NO_DATA_VALUE;
+    }
+    for(unsigned t=1; t<(_height+1); ++t)
+    {
+        _heights[_width*t] = NO_DATA_VALUE;
+        _heights[(_width*t)+(_width-1)] = NO_DATA_VALUE;
+    }
+
+    _slopes.resize( width*height );
+    _slopes.assign( width*height, NO_DATA_VALUE );
+
+    _curvatures.resize( width*height );
+    _curvatures.assign( width*height, NO_DATA_VALUE );
+
+    _dirty = true;
+}
+
+float
+ElevationField::getHeight(unsigned x, unsigned y) const
+{
+    return _heights[(_width+2)*y + (x+1)];
+}
+
+void
+ElevationField::update(float spacing)
+{
+    _spacing = spacing;
+    _dirty = false;
+    computeSlopeAndCurvature();
+}
+
+
+void
+ElevationField::computeSlopeAndCurvature()
+{
+    unsigned w = _width+2, h = _height+2;
+    
+    float L = _spacing;
+    osg::Vec3 up(0, 0, 1);
+
+    _slopes.clear();
+    _curvatures.clear();
+
+    for(unsigned t=1; t<_height-1; ++t)
+    {
+        for(unsigned s=1; s<_width-1; ++s)
+        {
+            float centerZ = _heights[t*w + s];
+
+            osg::Vec3
+                west (-L,  0, 0),
+                east ( L,  0, 0),
+                north( 0, -L, 0),
+                south( 0,  L, 0);
+
+            west.z() = _heights[t*w + s-1];
+            east.z() = _heights[t*w + s+1];
+
+            if ( west.z() == NO_DATA_VALUE )
+                west.z() = east.z() + 2.0*(centerZ-east.z());
+
+            if ( east.z() == NO_DATA_VALUE )
+                east.z() = west.z() + 2.0*(centerZ-west.z());
+            
+            south.z() = _heights[(t+1)*w + s];
+            north.z() = _heights[(t-1)*w + s];
+
+            if ( south.z() == NO_DATA_VALUE )
+                south.z() = north.z() + 2.0*(centerZ-north.z());
+
+            if ( north.z() == NO_DATA_VALUE )
+                north.z() = south.z() + 2.0*(centerZ-south.z());
+
+            // Normal vector: cross of two crossing vectors.
+            osg::Vec3 normal = (east-west) ^ (north-south);
+            normal.normalize();
+            _normals.push_back(normal);
+            
+            // Slope [0..1]; 0=flat, 1=vertical. Dot product of the UP vector and the 
+            // normal vector at a point.
+            float slope = up * normal;
+            _slopes.push_back(slope);
+
+            // Curvature: http://goo.gl/mOcl93
+            float L2 = L*L;
+            float D = (0.5*(west.z() + east.z()) - centerZ) / L2;
+            float E = (0.5*(south.z() + north.z()) - centerZ) / L2;
+
+            // Curvature = [0...)?
+            float curvature = -2.0*(D+E); //*100.0;
+            _curvatures.push_back(curvature);
+        }
+    }
+}
diff --git a/src/osgEarth/ElevationLOD b/src/osgEarth/ElevationLOD
index df4761f..9747988 100644
--- a/src/osgEarth/ElevationLOD
+++ b/src/osgEarth/ElevationLOD
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarth/ElevationLOD.cpp b/src/osgEarth/ElevationLOD.cpp
index d7ee6c7..91bdc58 100644
--- a/src/osgEarth/ElevationLOD.cpp
+++ b/src/osgEarth/ElevationLOD.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarth/ElevationLayer b/src/osgEarth/ElevationLayer
index 730d722..2d247ca 100644
--- a/src/osgEarth/ElevationLayer
+++ b/src/osgEarth/ElevationLayer
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -147,8 +147,6 @@ namespace osgEarth
         
         virtual std::string suggestCacheFormat() const;
 
-        virtual void initTileSource();
-
     private:
         ElevationLayerOptions _runtimeOptions;
 
@@ -156,7 +154,10 @@ namespace osgEarth
         virtual void fireCallback( TerrainLayerCallbackMethodPtr method );
         virtual void fireCallback( ElevationLayerCallbackMethodPtr method );
 
-        osg::ref_ptr<TileSource::HeightFieldOperation> _preCacheOp;
+        mutable osg::ref_ptr<TileSource::HeightFieldOperation> _preCacheOp;
+
+        TileSource::HeightFieldOperation* getOrCreatePreCacheOp();
+        Threading::Mutex _mutex;
 
         void init();
     };
diff --git a/src/osgEarth/ElevationLayer.cpp b/src/osgEarth/ElevationLayer.cpp
index cd94bd9..d11e3a6 100644
--- a/src/osgEarth/ElevationLayer.cpp
+++ b/src/osgEarth/ElevationLayer.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -84,25 +84,33 @@ ElevationLayerOptions::mergeConfig( const Config& conf )
 
 namespace
 {
-    struct ElevationLayerPreCacheOperation : public TileSource::HeightFieldOperation
+    // Opeartion that replaces invalid heights with the NO_DATA_VALUE marker.
+    struct NormalizeNoDataValues : public TileSource::HeightFieldOperation
     {
-        osg::ref_ptr<CompositeValidValueOperator> _ops;
-
-        ElevationLayerPreCacheOperation( TileSource* source )
+        NormalizeNoDataValues(TileSource* source)
         {
-            _ops = new CompositeValidValueOperator;
-            _ops->getOperators().push_back(new osgTerrain::NoDataValue(source->getNoDataValue()));
-            _ops->getOperators().push_back(new osgTerrain::ValidRange(source->getNoDataMinValue(), source->getNoDataMaxValue()));
+            _noDataValue   = source->getNoDataValue();
+            _minValidValue = source->getMinValidValue();
+            _maxValidValue = source->getMaxValidValue();
         }
 
-        void operator()( osg::ref_ptr<osg::HeightField>& hf )
+        void operator()(osg::ref_ptr<osg::HeightField>& hf)
         {
-            //Modify the heightfield data so that is contains a standard value for NO_DATA
-            ReplaceInvalidDataOperator op;
-            op.setReplaceWith(NO_DATA_VALUE);
-            op.setValidDataOperator(_ops.get());
-            op( hf.get() );
+            if ( hf.valid() )
+            {
+                osg::FloatArray* values = hf->getFloatArray();
+                for(osg::FloatArray::iterator i = values->begin(); i != values->end(); ++i)
+                {
+                    float& value = *i;
+                    if ( osg::equivalent(value, _noDataValue) || value < _minValidValue || value > _maxValidValue )
+                    {
+                        value = NO_DATA_VALUE;
+                    }
+                } 
+            }
         }
+
+        float _noDataValue, _minValidValue, _maxValidValue;
     };
 
     // perform very basic sanity-check validation on a heightfield.
@@ -192,16 +200,18 @@ ElevationLayer::fireCallback( ElevationLayerCallbackMethodPtr method )
     }
 }
 
-void
-ElevationLayer::initTileSource()
+TileSource::HeightFieldOperation*
+ElevationLayer::getOrCreatePreCacheOp()
 {
-    // call superclass first.
-    TerrainLayer::initTileSource();
-
-    if ( _tileSource.valid() )
+    if ( !_preCacheOp.valid() && getTileSource() )
     {
-        _preCacheOp = new ElevationLayerPreCacheOperation( _tileSource.get() );        
+        Threading::ScopedMutexLock lock(_mutex);
+        if ( !_preCacheOp.valid() )
+        {
+            _preCacheOp = new NormalizeNoDataValues( getTileSource() );
+        }
     }
+    return _preCacheOp.get();
 }
 
 
@@ -234,8 +244,8 @@ ElevationLayer::createHeightFieldFromTileSource(const TileKey&    key,
         }
 
         // Make it from the source:
-        result = source->createHeightField( key, _preCacheOp.get(), progress );
-
+        result = source->createHeightField( key, getOrCreatePreCacheOp(), progress );
+   
         // 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 )
@@ -250,10 +260,15 @@ ElevationLayer::createHeightFieldFromTileSource(const TileKey&    key,
             }
         }
         
-        // 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 && (!progress || !progress->isCanceled()))
+        // 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)
         {
-            source->getBlacklist()->add( key );
+            if ( progress == 0L ||
+                 ( !progress->isCanceled() && !progress->needsRetry() ) )
+            {
+                source->getBlacklist()->add( key );
+            }
         }
     }
 
@@ -371,6 +386,7 @@ ElevationLayer::createHeightField(const TileKey&    key,
     }
 
     // Check the memory cache first
+    bool fromMemCache = false;
     if ( _memCache.valid() )
     {
         CacheBin* bin = _memCache->getOrCreateBin( key.getProfile()->getFullSignature() );        
@@ -380,6 +396,8 @@ ElevationLayer::createHeightField(const TileKey&    key,
             result = GeoHeightField(
                 static_cast<osg::HeightField*>(cacheResult.releaseObject()),
                 key.getExtent());
+
+            fromMemCache = true;
         }
         //_memCache->dumpStats(key.getProfile()->getFullSignature());
     }
@@ -454,13 +472,6 @@ ElevationLayer::createHeightField(const TileKey&    key,
                 hf = 0L; // to fall back on cached data if possible.
             }
 
-            // memory cache first:
-            if ( hf && _memCache.valid() )
-            {
-                CacheBin* bin = _memCache->getOrCreateBin( key.getProfile()->getFullSignature() ); 
-                bin->write(key.str(), hf.get());
-            }
-
             // cache if necessary
             if ( hf            && 
                  cacheBin      && 
@@ -482,7 +493,7 @@ ElevationLayer::createHeightField(const TileKey&    key,
                 return GeoHeightField::INVALID;
             }
 
-            // Set up the heightfield so we don't have to worry about it later
+            // Set up the heightfield params.
             double minx, miny, maxx, maxy;
             key.getExtent().getBounds(minx, miny, maxx, maxy);
             hf->setOrigin( osg::Vec3d( minx, miny, 0.0 ) );
@@ -499,6 +510,13 @@ ElevationLayer::createHeightField(const TileKey&    key,
         }
     }
 
+    // write to mem cache if needed:
+    if ( result.valid() && !fromMemCache && _memCache.valid() )
+    {
+        CacheBin* bin = _memCache->getOrCreateBin( key.getProfile()->getFullSignature() ); 
+        bin->write(key.str(), result.getHeightField());
+    }
+
     // post-processing:
     if ( result.valid() )
     {
@@ -555,6 +573,12 @@ ElevationLayerVector::setExpressTileSize(unsigned tileSize)
     _expressTileSize = tileSize;
 }
 
+namespace
+{
+    typedef osg::ref_ptr<ElevationLayer>          RefElevationLayer;
+    typedef std::pair<RefElevationLayer, TileKey> LayerAndKey;
+    typedef std::vector<LayerAndKey>              LayerAndKeyVector;
+}
 
 bool
 ElevationLayerVector::populateHeightField(osg::HeightField*      hf,
@@ -578,8 +602,13 @@ ElevationLayerVector::populateHeightField(osg::HeightField*      hf,
     }
     
     // Collect the valid layers for this tile.
-    ElevationLayerVector contenders;
-    ElevationLayerVector offsets;
+    LayerAndKeyVector contenders;
+    LayerAndKeyVector 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)
     {
         ElevationLayer* layer = i->get();
@@ -587,18 +616,52 @@ ElevationLayerVector::populateHeightField(osg::HeightField*      hf,
         if ( layer->getEnabled() && layer->getVisible() )
         {
             // calculate the resolution-mapped key (adjusted for tile resolution differential).            
-            TileKey mappedKey = 
-                keyToUse.mapResolution(hf->getNumColumns(), layer->getTileSize());
+            TileKey mappedKey = keyToUse.mapResolution(
+                hf->getNumColumns(),
+                layer->getTileSize() );
+
+            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->isKeyInRange(key) )
+                {
+                    useLayer = false;
+                }
+                
+
+                // Find the "best available" mapped key from the tile source:
+                else 
+                {
+                    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 )
+                        {
+                            numFallbackLayers++;
+                        }
+                    }
+                    else
+                    {
+                        useLayer = false;
+                    }
+                }
+            }
 
-            // Note: isKeyInRange tests the key, but haData tests the mapped key.
-            // I think that's right!
-            if ((layer->getTileSource() == 0L) || 
-                (layer->isKeyInRange(key) && layer->getTileSource()->hasData(mappedKey)))
+            if ( useLayer )
             {
-                if (layer->isOffset())
-                    offsets.push_back(layer);
+                if ( layer->isOffset() )
+                {
+                    offsets.push_back( std::make_pair(layer, bestKey) );
+                }
                 else
-                    contenders.push_back(layer);
+                {
+                    contenders.push_back( std::make_pair(layer, bestKey) );
+                }
             }
         }
     }
@@ -609,6 +672,11 @@ ElevationLayerVector::populateHeightField(osg::HeightField*      hf,
         return false;
     }
 
+    // if everything is fallback data, bail out.
+    if ( contenders.size() + offsets.size() == numFallbackLayers )
+    {
+        return false;
+    }
     
     // Sample the layers into our target.
     unsigned numColumns = hf->getNumColumns();
@@ -621,13 +689,22 @@ ElevationLayerVector::populateHeightField(osg::HeightField*      hf,
     // We will load the actual heightfields on demand. We might not need them all.
     GeoHeightFieldVector heightFields(contenders.size());
     GeoHeightFieldVector offsetFields(offsets.size());
-    std::vector<bool>    heightFailed (contenders.size(), false);
+    std::vector<bool>    heightFallback(contenders.size(), false);
+    std::vector<bool>    heightFailed(contenders.size(), false);
     std::vector<bool>    offsetFailed(offsets.size(), false);
 
+    // The maximum number of heightfields to keep in this local cache
+    unsigned int maxHeightFields = 50;
+    unsigned numHeightFieldsInCache = 0;
+
     const SpatialReference* keySRS = keyToUse.getProfile()->getSRS();
 
     bool realData = false;
 
+    unsigned int total = numColumns * numRows;
+    unsigned int completed = 0;
+    int nodataCount = 0;
+
     for (unsigned c = 0; c < numColumns; ++c)
     {
         double x = xmin + (dx * (double)c);
@@ -643,29 +720,73 @@ ElevationLayerVector::populateHeightField(osg::HeightField*      hf,
                 if ( heightFailed[i] )
                     continue;
 
+                ElevationLayer* layer = contenders[i].first.get();
+
                 GeoHeightField& layerHF = heightFields[i];
-                if ( !layerHF.valid() )
+                TileKey actualKey = contenders[i].second;
+
+                if (!layerHF.valid())
                 {
-                    TileKey mappedKey = 
-                        keyToUse.mapResolution(hf->getNumColumns(), contenders[i]->getTileSize());
+                    // 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())
+                    {
+                        layerHF = layer->createHeightField(actualKey, progress);
+                        if (!layerHF.valid())
+                        {
+                            actualKey = actualKey.createParentKey();
+                        }
+                    }
 
-                    layerHF = contenders[i]->createHeightField(mappedKey, progress);
-                    if ( !layerHF.valid() )
+                    // Mark this layer as fallback if necessary.
+                    if (layerHF.valid())
+                    {
+                        heightFallback[i] = actualKey != contenders[i].second;
+                        numHeightFieldsInCache++;
+                    }
+                    else
                     {
                         heightFailed[i] = true;
                         continue;
                     }
                 }
 
-                // If we actually got a layer then we have real data
-                realData = true;
+                if (layerHF.valid())
+                {
+                    bool isFallback = heightFallback[i];
 
-                float elevation;
-                if (layerHF.getElevation(keySRS, x, y, interpolation, keySRS, elevation) &&
-                    elevation != NO_DATA_VALUE)
+                    // 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;                    
+                            hf->setHeight(c, r, elevation);
+                        }
+                        else
+                        {
+                            ++nodataCount;
+                        }
+                    }
+                }
+
+
+                // Clear the heightfield cache if we have too many heightfields in the cache.
+                if (numHeightFieldsInCache >= maxHeightFields)
                 {
-                    resolved = true;                    
-                    hf->setHeight(c, r, elevation);
+                    //OE_NOTICE << "Clearing cache" << std::endl;
+                    for (unsigned int k = 0; k < heightFields.size(); k++)
+                    {
+                        heightFields[k] = GeoHeightField::INVALID;
+                        heightFallback[k] = false;
+                    }
+                    numHeightFieldsInCache = 0;
                 }
             }
 
@@ -677,10 +798,9 @@ ElevationLayerVector::populateHeightField(osg::HeightField*      hf,
                 GeoHeightField& layerHF = offsetFields[i];
                 if ( !layerHF.valid() )
                 {
-                    TileKey mappedKey = 
-                        keyToUse.mapResolution(hf->getNumColumns(), offsets[i]->getTileSize());
+                    ElevationLayer* offset = offsets[i].first.get();
 
-                    layerHF = offsets[i]->createHeightField(mappedKey, progress);
+                    layerHF = offset->createHeightField(offsets[i].second, progress);
                     if ( !layerHF.valid() )
                     {
                         offsetFailed[i] = true;
@@ -699,7 +819,7 @@ ElevationLayerVector::populateHeightField(osg::HeightField*      hf,
                 }
             }
         }
-    }   
+    }
 
     // Return whether or not we actually read any real data
     return realData;
diff --git a/src/osgEarth/ElevationQuery b/src/osgEarth/ElevationQuery
index b254faf..d8e4d14 100644
--- a/src/osgEarth/ElevationQuery
+++ b/src/osgEarth/ElevationQuery
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -25,6 +25,30 @@
 
 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();
+
+            virtual osg::Node* readNodeFile(const std::string& filename);
+
+        protected:
+
+            typedef std::map<std::string, osg::ref_ptr<osg::Node> > FileNameSceneMap;
+
+            unsigned int _maxNumFilesToCache;
+            OpenThreads::Mutex  _mutex;
+            FileNameSceneMap    _filenameSceneMap;
+    };
+
+
     /**
      * ElevationQuery (EQ) lets you query the elevation at any point on a map.
      * 
@@ -105,6 +129,13 @@ namespace osgEarth
             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 );
@@ -134,14 +165,27 @@ namespace osgEarth
          * 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.
          */
-        unsigned int getMaxLevel(double x, double y, const SpatialReference* srs, const Profile* profile ) const;
+        int getMaxLevel(double x, double y, const SpatialReference* srs, const Profile* profile, unsigned tileSize) const;
+
+        /** Clear the database cache.*/
+        void clearDatabaseCache() { if (_eqcrc.valid()) _eqcrc->clearDatabaseCache(); }
+
+        /** 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);
+
+        /** Get the ReadCallback that does the reading of external PagedLOD models, and caching of loaded subgraphs.*/
+        ElevationQueryCacheReadCallback* getElevationQueryCacheReadCallback() { return _eqcrc.get(); }
 
     private:
         MapFrame     _mapf;
         unsigned     _maxCacheSize;
         int          _tileSize;        
-        int          _maxLevelOverride;        
+        int          _maxLevelOverride;
+        bool         _fallBackOnNoData;
 
         typedef LRUCache< TileKey, GeoHeightField > TileCache;
         TileCache _cache;
@@ -150,6 +194,8 @@ namespace osgEarth
         std::vector<ModelLayer*> _patchLayers;
         osg::ref_ptr<DPLineSegmentIntersector> _patchLayersLSI;
 
+        osg::ref_ptr<ElevationQueryCacheReadCallback> _eqcrc;
+
     private:
         void postCTOR();
         void sync();
diff --git a/src/osgEarth/ElevationQuery.cpp b/src/osgEarth/ElevationQuery.cpp
index 54d2106..5fb8222 100644
--- a/src/osgEarth/ElevationQuery.cpp
+++ b/src/osgEarth/ElevationQuery.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -40,6 +40,74 @@ namespace
     }
 }
 
+ElevationQueryCacheReadCallback::ElevationQueryCacheReadCallback()
+{
+    _maxNumFilesToCache = 2000;
+}
+
+void ElevationQueryCacheReadCallback::clearDatabaseCache()
+{
+    OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_mutex);
+    _filenameSceneMap.clear();
+}
+
+void ElevationQueryCacheReadCallback::pruneUnusedDatabaseCache()
+{
+}
+
+osg::Node* ElevationQueryCacheReadCallback::readNodeFile(const std::string& filename)
+{
+    // 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::readNodeFile(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;
+        }
+    }
+
+    return node.release();
+}
+
 ElevationQuery::ElevationQuery(const Map* map) :
 _mapf( map, (Map::ModelParts)(Map::TERRAIN_LAYERS | Map::MODEL_LAYERS) )
 {
@@ -58,9 +126,13 @@ ElevationQuery::postCTOR()
     // defaults:
     _maxLevelOverride = -1;
     _queries          = 0.0;
-    _totalTime        = 0.0;  
+    _totalTime        = 0.0;
+    _fallBackOnNoData = false;
     _cache.setMaxSize( 500 );
 
+    // set read callback for IntersectionVisitor
+    setElevationQueryCacheReadCallback(new ElevationQueryCacheReadCallback);
+
     // find terrain patch layers.
     gatherPatchLayers();
 }
@@ -90,12 +162,13 @@ ElevationQuery::gatherPatchLayers()
     }
 }
 
-unsigned
-ElevationQuery::getMaxLevel( double x, double y, const SpatialReference* srs, const Profile* profile ) const
+int
+ElevationQuery::getMaxLevel( double x, double y, const SpatialReference* srs, const Profile* profile, unsigned tileSize) const
 {
-    int targetTileSizePOT = nextPowerOf2((int)_mapf.getMapOptions().elevationTileSize().get());
+    int targetTileSizePOT = nextPowerOf2((int)tileSize);
+
+    int maxLevel = -1;
 
-    int maxLevel = 0;
     for( ElevationLayerVector::const_iterator i = _mapf.elevationLayers().begin(); i != _mapf.elevationLayers().end(); ++i )
     {
         const ElevationLayer* layer = i->get();
@@ -104,7 +177,7 @@ ElevationQuery::getMaxLevel( double x, double y, const SpatialReference* srs, co
         if ( !layer->getEnabled() || !layer->getVisible() )
             continue;
 
-        int layerMaxLevel = 0;
+        optional<int> layerMaxLevel;
 
         osgEarth::TileSource* ts = layer->getTileSource();
         if ( ts )
@@ -122,7 +195,9 @@ ElevationQuery::getMaxLevel( double x, double y, const SpatialReference* srs, co
                 
                 for (osgEarth::DataExtentList::iterator j = ts->getDataExtents().begin(); j != ts->getDataExtents().end(); j++)
                 {
-                    if (j->maxLevel().isSet() && j->maxLevel() > layerMaxLevel && j->contains( tsCoord.x(), tsCoord.y(), tsSRS ))
+                    if (j->maxLevel().isSet() &&
+                        (!layerMaxLevel.isSet() || j->maxLevel() > layerMaxLevel.get() )
+                        && j->contains( tsCoord.x(), tsCoord.y(), tsSRS ))
                     {
                         layerMaxLevel = j->maxLevel().value();
                     }
@@ -135,13 +210,16 @@ ElevationQuery::getMaxLevel( double x, double y, const SpatialReference* srs, co
             }
 
             // cap the max to the layer's express max level (if set).
-            if ( layer->getTerrainLayerRuntimeOptions().maxLevel().isSet() )
+            if ( layerMaxLevel.isSet() && layer->getTerrainLayerRuntimeOptions().maxLevel().isSet() )
             {
-                layerMaxLevel = std::min( layerMaxLevel, (int)(*layer->getTerrainLayerRuntimeOptions().maxLevel()) );
+                layerMaxLevel = std::min( layerMaxLevel.get(), (int)(*layer->getTerrainLayerRuntimeOptions().maxLevel()) );
             }
 
             // Need to convert the layer max of this TileSource to that of the actual profile
-            layerMaxLevel = profile->getEquivalentLOD( ts->getProfile(), layerMaxLevel );
+            if ( layerMaxLevel.isSet() )
+            {
+                layerMaxLevel = profile->getEquivalentLOD( ts->getProfile(), layerMaxLevel.get() );
+            }
         }
         else
         {
@@ -150,26 +228,32 @@ ElevationQuery::getMaxLevel( double x, double y, const SpatialReference* srs, co
         }
 
         // Adjust for the tile size resolution differential, if supported by the layer.
-        int layerTileSize = layer->getTileSize();
-        if (layerTileSize > targetTileSizePOT)
+        if ( layerMaxLevel.isSet() )
         {
-            int oldMaxLevel = layerMaxLevel;
-            int temp = std::max(targetTileSizePOT, 2);
-            while(temp < layerTileSize) {
-                temp *= 2;
-                ++layerMaxLevel;
+#if 0
+            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;
+            if (layerMaxLevel > maxLevel)
+            {
+                maxLevel = layerMaxLevel.get();
+            }
         }
     }
 
     return maxLevel;
 }
 
+
 void
 ElevationQuery::setMaxTilesToCache( int value )
 {
@@ -277,6 +361,9 @@ ElevationQuery::getElevationImpl(const GeoPoint& point, /* abs */
     {
         osgUtil::IntersectionVisitor iv;
 
+        if ( _eqcrc.valid() )
+            iv.setReadCallback(_eqcrc);
+
         for(std::vector<ModelLayer*>::iterator i = _patchLayers.begin(); i != _patchLayers.end(); ++i)
         {
             // find the scene graph for this layer:
@@ -343,19 +430,28 @@ ElevationQuery::getElevationImpl(const GeoPoint& point, /* abs */
         return true;        
     }
 
-    // tile size (resolution of elevation tiles)
-    unsigned tileSize = std::max(_mapf.getMapOptions().elevationTileSize().get(), 2u);
+    // tile size (resolution of elevation tiles) 
+    unsigned tileSize = 17; // ???
 
-    //This is the max resolution that we actually have data at this point
-    unsigned int bestAvailLevel = getMaxLevel( point.x(), point.y(), point.getSRS(), _mapf.getProfile());
+    // 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;
+    }
 
     if (desiredResolution > 0.0)
     {
-        unsigned int desiredLevel = _mapf.getProfile()->getLevelOfDetailForHorizResolution( desiredResolution, tileSize );
-        if (desiredLevel < bestAvailLevel) bestAvailLevel = desiredLevel;
+        int desiredLevel = _mapf.getProfile()->getLevelOfDetailForHorizResolution( desiredResolution, tileSize );
+        if (desiredLevel < bestAvailLevel)
+        {
+            bestAvailLevel = desiredLevel;
+        }
     }
 
-    OE_DEBUG << LC << "Best available data level " << point.x() << ", " << point.y() << " = "  << bestAvailLevel << std::endl;
+    //OE_NOTICE << LC << "Best available data level " << point.x() << ", " << point.y() << " = "  << bestAvailLevel << std::endl;
 
     // transform the input coords to map coords:
     GeoPoint mapPoint = point;
@@ -377,12 +473,14 @@ ElevationQuery::getElevationImpl(const GeoPoint& point, /* abs */
         return false;
     }
         
-    bool result = false;      
-    while (!result)
-    {      
+    bool result = false; 
+
+    while ( !result && key.valid() )
+    {
         GeoHeightField geoHF;
-        TileCache::Record record;
+        
         // Try to get the hf from the cache
+        TileCache::Record record;
         if ( _cache.get( key, record ) )
         {                        
             geoHF = record.value();
@@ -394,12 +492,9 @@ ElevationQuery::getElevationImpl(const GeoPoint& point, /* abs */
             hf->allocate( tileSize, tileSize );
 
             // Initialize the heightfield to nodata
-            for (unsigned int i = 0; i < hf->getFloatArray()->size(); i++)
-            {
-                hf->getFloatArray()->at( i ) = NO_DATA_VALUE;
-            }   
+            hf->getFloatArray()->assign( hf->getFloatArray()->size(), NO_DATA_VALUE );
 
-            if (_mapf.populateHeightField(hf, key, false))
+            if (_mapf.populateHeightField(hf, key, false /*heightsAsHAE*/, 0L))
             {                
                 geoHF = GeoHeightField( hf.get(), key.getExtent() );
                 _cache.insert( key, geoHF );
@@ -411,12 +506,12 @@ ElevationQuery::getElevationImpl(const GeoPoint& point, /* abs */
             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)
-            {                        
-                // see what the actual resolution of the heightfield is.
-                if ( out_actualResolution )
-                    *out_actualResolution = geoHF.getXInterval(); 
+            {
                 out_elevation = (double)elevation;                
-                break;
+
+                // report the actual resolution of the heightfield.
+                if ( out_actualResolution )
+                    *out_actualResolution = geoHF.getXInterval();
             }
             else
             {                               
@@ -424,16 +519,15 @@ ElevationQuery::getElevationImpl(const GeoPoint& point, /* abs */
             }
         }
 
-        if (!result)
+        if ( geoHF.valid() && !_fallBackOnNoData )
         {
-            key = key.createParentKey();                        
-            if (!key.valid())
-            {
-                break;
-            }
-        }         
+            break;
+        }
+        else if ( result == false )
+        {
+            key = key.createParentKey();
+        }
     }
-
          
 
     osg::Timer_t end = osg::Timer::instance()->tick();
@@ -442,3 +536,8 @@ ElevationQuery::getElevationImpl(const GeoPoint& point, /* abs */
 
     return result;
 }
+
+void ElevationQuery::setElevationQueryCacheReadCallback(ElevationQueryCacheReadCallback* eqcrc)
+{
+    _eqcrc = eqcrc;
+}
diff --git a/src/osgEarth/Export b/src/osgEarth/Export
index b786208..4c75699 100644
--- a/src/osgEarth/Export
+++ b/src/osgEarth/Export
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarth/Extension b/src/osgEarth/Extension
new file mode 100644
index 0000000..4d831e2
--- /dev/null
+++ b/src/osgEarth/Extension
@@ -0,0 +1,106 @@
+/* -*-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_EXTENSION_H
+#define OSGEARTH_EXTENSION_H 1
+
+#include <osgEarth/Common>
+#include <osg/Object>
+#include <osgDB/Options>
+
+namespace osgEarth
+{
+    class ConfigOptions;
+}
+
+namespace osgEarth
+{
+    /**
+     * An Extension is an object that can be loaded on demand (from a plugin) 
+     * and can have multiple object-specific interfaces.
+     *
+     * For example, the pattern for declaring an Extension that connects to a
+     * MapNode looks like:
+     *
+     *   public MyExtension : public Extension, public ExtensionInterface<MapNode>
+     *
+     * You can implement more than one interface. This one would connect to a
+     * MapNode and to a UI Control:
+     *
+     *   public MyExtension : public Extension,
+     *                        public ExtensionInterface<MapNode>,
+     *                        public ExtensionInterface<Control>
+     */
+    class OSGEARTH_EXPORT Extension : public osg::Object // header-only
+    {
+    public:
+        /**
+         * Sets DB options that this extension should use when doing IO operations.
+         */
+        virtual void setDBOptions(const osgDB::Options* options) { }
+
+
+    protected:
+        Extension() { }
+
+    protected:
+        virtual ~Extension() { }
+
+    public:
+        /**
+         * Attempts to create an instance of an named extension.
+         */
+        static Extension* create(const std::string& name, const ConfigOptions& options);
+
+        /**
+         * Fetch configuration options from a plugin loader
+         */
+        static const ConfigOptions& getConfigOptions(const osgDB::Options*);
+    };
+
+    
+    /**
+     * This template lets you create object-specific interfaces in an extension.
+     */
+    template<typename T> class ExtensionInterface
+    {
+    public:
+        /**
+         * Connects the extension to an object.
+         */
+        virtual bool connect(T* object) =0;
+
+        /**
+         * Disconnects the extension from an object. This should be the
+         * same object used to call connect().
+         */
+        virtual bool disconnect(T* object) =0;
+        
+        /**
+         * Cast a base extension to this interface, or return NULL
+         * if the extenstion doesn't implemenent this inteface.
+         */
+        static ExtensionInterface<T>* get(Extension* e) {
+            return dynamic_cast< ExtensionInterface<T>* >(e);
+        }
+    };
+
+} // namespace osgEarth
+
+#endif
diff --git a/src/osgEarth/Extension.cpp b/src/osgEarth/Extension.cpp
new file mode 100644
index 0000000..861fedc
--- /dev/null
+++ b/src/osgEarth/Extension.cpp
@@ -0,0 +1,71 @@
+/* -*-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 <osgEarth/Extension>
+#include <osgEarth/Registry>
+#include <osgDB/Registry>
+#include <osgDB/ReadFile>
+
+using namespace osgEarth;
+
+#define LC "[Extension] "
+
+#define EXTENSION_OPTIONS_TAG "__osgEarth::ExtensionOptions"
+
+
+Extension*
+Extension::create(const std::string& name, const ConfigOptions& options)
+{
+    if ( name.empty() )
+    {
+        OE_WARN << LC << "ILLEGAL- no driver set for tile source" << std::endl;
+        return 0L;
+    }
+
+    // convey the configuration options:
+    osg::ref_ptr<osgDB::Options> dbopt = Registry::instance()->cloneOrCreateOptions();
+    dbopt->setPluginData( EXTENSION_OPTIONS_TAG, (void*)&options );
+
+    std::string pluginExtension = std::string( ".osgearth_" ) + 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;
+    }
+
+    Extension* extension = dynamic_cast<Extension*>( rr.getObject() );
+    if ( extension == 0L )
+    {
+        OE_WARN << LC << "Plugin \"" << name << "\" is not an Extension" << std::endl;
+        return 0L;
+    }
+
+    rr.takeObject();
+    return extension;
+}
+
+
+const ConfigOptions&
+Extension::getConfigOptions(const osgDB::Options* options)
+{
+    return *static_cast<const ConfigOptions*>(
+        options->getPluginData( EXTENSION_OPTIONS_TAG ) );
+}
diff --git a/src/osgEarth/FadeEffect b/src/osgEarth/FadeEffect
index a639c39..77cffee 100644
--- a/src/osgEarth/FadeEffect
+++ b/src/osgEarth/FadeEffect
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -22,7 +22,7 @@
 
 #include <osgEarth/Common>
 #include <osgEarth/Config>
-#include <osgEarth/ThreadingUtils>
+#include <osgEarth/Containers>
 #include <osg/Group>
 #include <osg/Uniform>
 
@@ -125,7 +125,7 @@ namespace osgEarth
             osg::ref_ptr<osg::StateSet> _stateSet;
             osg::ref_ptr<osg::Uniform>  _opacity;
         };
-        Threading::PerObjectMap<osg::NodeVisitor*, PerViewData> _perViewData;
+        PerObjectMap<osg::NodeVisitor*, PerViewData> _perViewData;
 
         float _minPixelExtent;
         float _maxPixelExtent;
diff --git a/src/osgEarth/FadeEffect.cpp b/src/osgEarth/FadeEffect.cpp
index ca85a1c..66a1665 100644
--- a/src/osgEarth/FadeEffect.cpp
+++ b/src/osgEarth/FadeEffect.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -52,9 +52,8 @@ namespace
 {
     const char* FadeEffectVertexShader =
         "#version " GLSL_VERSION_STR "\n"
-#ifdef OSG_GLES2_AVAILABLE
-        "precision mediump float; \n"
-#endif
+        GLSL_DEFAULT_PRECISION_FLOAT "\n"
+
         "uniform float oe_fadeeffect_duration; \n"
         "uniform float oe_fadeeffect_startTime; \n"
         "uniform float oe_fadeeffect_maxRange; \n"
@@ -72,9 +71,8 @@ namespace
 
     const char* FadeEffectFragmentShader = 
         "#version " GLSL_VERSION_STR "\n"
-#ifdef OSG_GLES2_AVAILABLE
-        "precision mediump float; \n"
-#endif
+        GLSL_DEFAULT_PRECISION_FLOAT "\n"
+
         "varying float oe_fadeeffect_opacity; \n"
 
         "void oe_fragFadeEffect( inout vec4 color ) \n"
@@ -99,8 +97,8 @@ FadeEffect::FadeEffect()
     {
         VirtualProgram* vp = new VirtualProgram();
 
-        vp->setFunction( "oe_vertFadeEffect", FadeEffectVertexShader,   ShaderComp::LOCATION_VERTEX_VIEW );
-        vp->setFunction( "oe_fragFadeEffect", FadeEffectFragmentShader, ShaderComp::LOCATION_FRAGMENT_COLORING );
+        vp->setFunction( "oe_vertFadeEffect", FadeEffectVertexShader,   ShaderComp::LOCATION_VERTEX_VIEW, 0.5f );
+        vp->setFunction( "oe_fragFadeEffect", FadeEffectFragmentShader, ShaderComp::LOCATION_FRAGMENT_COLORING, 0.5f );
 
         ss->setAttributeAndModes( vp, osg::StateAttribute::ON );
 
@@ -165,9 +163,8 @@ namespace
 {
     const char* FadeLODFragmentShader = 
         "#version " GLSL_VERSION_STR "\n"
-#ifdef OSG_GLES_AVAILABLE
-        "precision mediump float; \n"
-#endif
+        GLSL_DEFAULT_PRECISION_FLOAT "\n"
+
         "varying float oe_FadeLOD_opacity; \n"
         "void oe_fragFadeLOD( inout vec4 color ) \n"
         "{ \n"
@@ -193,7 +190,8 @@ _maxFadeExtent ( 0.0f )
         vp->setFunction(
             "oe_fragFadeLOD",
             FadeLODFragmentShader,
-            ShaderComp::LOCATION_FRAGMENT_COLORING );
+            osgEarth::ShaderComp::LOCATION_FRAGMENT_COLORING,
+            0.5f );
 
         osg::StateSet* ss = getOrCreateStateSet();
 
diff --git a/src/osgEarth/FileUtils b/src/osgEarth/FileUtils
index a88fdbd..0087852 100644
--- a/src/osgEarth/FileUtils
+++ b/src/osgEarth/FileUtils
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarth/FileUtils.cpp b/src/osgEarth/FileUtils.cpp
index 6ac281a..85e592b 100644
--- a/src/osgEarth/FileUtils.cpp
+++ b/src/osgEarth/FileUtils.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarth/GPUClamping.frag.glsl b/src/osgEarth/GPUClamping.frag.glsl
new file mode 100644
index 0000000..00ca4e3
--- /dev/null
+++ b/src/osgEarth/GPUClamping.frag.glsl
@@ -0,0 +1,11 @@
+#version 110
+#pragma vp_entryPoint "oe_clamp_fragment"
+#pragma vp_location   "fragment_coloring"
+
+varying float oe_clamp_alpha;
+
+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
new file mode 100644
index 0000000..81376d6
--- /dev/null
+++ b/src/osgEarth/GPUClamping.vert.glsl
@@ -0,0 +1,77 @@
+#version 120
+
+#pragma vp_entryPoint "oe_clamp_vertex"
+#pragma vp_location   "vertex_view"
+#pragma vp_order      "0.5"
+
+#pragma include "GPUClamping.vert.lib.glsl"
+
+attribute vec4 oe_clamp_attrs;
+attribute float oe_clamp_offset;
+uniform bool oe_clamp_hasAttrs;
+
+uniform bool  oe_isGeocentric;
+uniform float oe_clamp_altitudeOffset;
+uniform float oe_clamp_horizonDistance2;
+varying float oe_clamp_alpha;
+
+void oe_clamp_vertex(inout vec4 vertexView)
+{
+    const float ClampToAnchor = 1.0;
+
+    // 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) :
+        1.0;
+
+    // if visible, calculate clamping.
+    // 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 = 0.0;
+
+        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 = dot(clampedPoint-pointToClamp, vertexView-pointToClamp) < 0.0 ? -1.0 : 1.0;
+            dh += (dist * dir);
+        }
+        else
+        {
+            // if we are clamping to the terrain, the vertex becomes the
+            // clamped point.
+            vertexView.xyz = clampedPoint.xyz/clampedPoint.w;
+        }
+
+        // apply the z-offset if there is one.
+        float hOffset = dh + oe_clamp_altitudeOffset + verticalOffset;
+        if ( hOffset != 0.0 )
+        {
+            vec3 up;
+            oe_getClampingUpVector(up);
+            vertexView.xyz += up * hOffset;
+        }
+
+        // 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);
+    }
+}
diff --git a/src/osgEarth/GPUClamping.vert.lib.glsl b/src/osgEarth/GPUClamping.vert.lib.glsl
new file mode 100644
index 0000000..17b3a48
--- /dev/null
+++ b/src/osgEarth/GPUClamping.vert.lib.glsl
@@ -0,0 +1,36 @@
+// note: this is an include file
+
+// depth texture captures by the clamping technique
+uniform sampler2D oe_clamp_depthTex;
+
+// matrix transforming from view space to depth-texture clip space
+uniform mat4 oe_clamp_cameraView2depthClip;
+
+// matrix transform from depth-tecture clip space to view space
+uniform mat4 oe_clamp_depthClip2cameraView;
+
+// Given a vertex in view space, clamp it to the "ground" as represented
+// by an orthographic depth texture. Return the clamped vertex in view space,
+// along with the associated depth value.
+void oe_getClampedViewVertex(in  vec4  vertView,
+                             out vec4  out_clampedVertView,
+                             out float out_depth)
+{
+    // transform the vertex into the depth texture's clip coordinates.
+    vec4 vertDepthClip = oe_clamp_cameraView2depthClip * vertView;
+
+    // sample the depth map
+    out_depth = texture2DProj( 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);
+
+    // convert back into view space.
+    out_clampedVertView = oe_clamp_depthClip2cameraView * clampedVertDepthClip;
+}
+
+// Returns a vector indiciating the "down" direction.
+void oe_getClampingUpVector(out vec3 up)
+{
+    up = normalize(mat3(oe_clamp_depthClip2cameraView) * vec3(0,0,-1));
+}
diff --git a/src/osgEarth/GeoCommon b/src/osgEarth/GeoCommon
index 52642cf..391157e 100644
--- a/src/osgEarth/GeoCommon
+++ b/src/osgEarth/GeoCommon
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -19,6 +19,10 @@
 #ifndef OSGEARTH_GEOCOMMON_H
 #define OSGEARTH_GEOCOMMON_H 1
 
+// For FLT_MAX
+#include <limits.h>
+#include <cfloat>
+
 namespace osgEarth
 {
     // Default normalized no-data value for elevation
diff --git a/src/osgEarth/GeoData b/src/osgEarth/GeoData
index 5898b37..4ba6465 100644
--- a/src/osgEarth/GeoData
+++ b/src/osgEarth/GeoData
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -28,6 +28,7 @@
 #include <osg/Referenced>
 #include <osg/Image>
 #include <osg/Shape>
+#include <osg/Polytope>
 
 namespace osgEarth
 {
@@ -485,7 +486,6 @@ namespace osgEarth
          */
         double area() const;
 
-
         /**
          * Normalize the longitude values in this GeoExtent
          */
@@ -496,6 +496,12 @@ namespace osgEarth
          */
         double normalizeLongitude( double longitude ) const;
 
+        /**
+         * Generate a polytope in world coordinates that bounds the extent.
+         * Return false if the extent it invalid.
+         */
+        bool createPolytope(osg::Polytope& out) const;
+
     public:
         static GeoExtent INVALID;
 
@@ -543,10 +549,10 @@ namespace osgEarth
         /** Construct an empty (invalid) geoimage. */
         GeoImage();
 
-        /**
+        /** 
          * Constructs a new goereferenced image.
          */
-        GeoImage( osg::Image* image, const GeoExtent& extent );
+        GeoImage(osg::Image* image, const GeoExtent& extent);
 
         /** dtor */
         virtual ~GeoImage() { }
@@ -642,7 +648,7 @@ namespace osgEarth
 
     private:
         osg::ref_ptr<osg::Image> _image;
-        GeoExtent _extent;
+        GeoExtent                _extent;
     };
 
     typedef std::vector<GeoImage> GeoImageVector;
diff --git a/src/osgEarth/GeoData.cpp b/src/osgEarth/GeoData.cpp
index 2fff23e..ad75735 100644
--- a/src/osgEarth/GeoData.cpp
+++ b/src/osgEarth/GeoData.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -64,7 +64,7 @@ namespace
     {
         double result = x;
         while( result < minLon ) result += 360.;
-        while( result >  maxLon ) result -= 360.;
+        while( result > maxLon ) result -= 360.;
         return result;
     }
 
@@ -86,7 +86,7 @@ namespace
     void s_getLongitudeFrame( double longitude, double &minLongitude, double &maxLongitude)
     {
         minLongitude = -180.0;
-        maxLongitude = 180.0;
+        maxLongitude =  180.0;
 
         while ( longitude < minLongitude || longitude > maxLongitude)
         {
@@ -636,7 +636,7 @@ _east   ( DBL_MAX ),
 _south  ( DBL_MAX ),
 _north  ( DBL_MAX )
 {
-    //nop
+    //NOP - invalid
 }
 
 GeoExtent::GeoExtent(const SpatialReference* srs,
@@ -646,8 +646,9 @@ _west   ( west ),
 _east   ( east ),
 _south  ( south ),
 _north  ( north )
-{    
-    recomputeCircle();
+{
+    if ( isValid() )
+        recomputeCircle();
 }
 
 
@@ -658,7 +659,8 @@ _east   ( bounds.xMax() ),
 _south  ( bounds.yMin() ),
 _north  ( bounds.yMax() )
 {    
-    recomputeCircle();
+    if ( isValid() )
+        recomputeCircle();
 }
 
 GeoExtent::GeoExtent( const GeoExtent& rhs ) :
@@ -707,10 +709,10 @@ GeoExtent::isValid() const
 {
     return 
         _srs.valid()       && 
-        _east  != DBL_MAX  &&
-        _west  != DBL_MAX  &&
-        _north != DBL_MAX  &&
-        _south != DBL_MAX;
+        _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
@@ -951,21 +953,44 @@ GeoExtent::recomputeCircle()
 
         if ( getSRS()->isProjected() )
         {
-            _circle.setRadius( (osg::Vec2d(x,y)-osg::Vec2d(_west,_south)).length() );
+            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() );
         }
         else // isGeographic
         {
-            // find the longest east-west edge.
-            double cx = west();
-            double cy =
-                north() > 0.0 && south() > 0.0 ? south() :
-                north() < 0.0 && south() < 0.0 ? north() :
-                north() < fabs(south()) ? north() : south();
-
-            osg::Vec3d p0(x, y, 0.0);
-            osg::Vec3d p1(cx, cy, 0.0);
+            osg::Vec3d center, sw, se, ne, nw;
+
+            GeoPoint(getSRS(), x, y, 0, ALTMODE_ABSOLUTE).toWorld(center);
+            GeoPoint(getSRS(), west(), south(), 0, ALTMODE_ABSOLUTE).toWorld(sw);
+            GeoPoint(getSRS(), east(), south(), 0, ALTMODE_ABSOLUTE).toWorld(se);
+            GeoPoint(getSRS(), east(), north(), 0, ALTMODE_ABSOLUTE).toWorld(ne);
+            GeoPoint(getSRS(), west(), north(), 0, ALTMODE_ABSOLUTE).toWorld(nw);
+            
+            double radius2 = (center-sw).length2();
+            radius2 = std::max(radius2, (center-se).length2());
+            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();
+            }
 
-            _circle.setRadius( GeoMath::distance(p0, p1, getSRS()) );
+            double extMeters = extDegrees * metersPerDegree;
+            _circle.setRadius( 0.5*extMeters * 1.414121356237 ); /*sqrt(2)*/
+#endif
         }
 
         _circle.setCenter( GeoPoint(getSRS(), x, y, 0.0, ALTMODE_ABSOLUTE) );
@@ -1087,7 +1112,7 @@ GeoExtent::expandToInclude( const GeoExtent& rhs )
 GeoExtent
 GeoExtent::intersectionSameSRS( const GeoExtent& rhs ) const
 {
-    if ( isInvalid() || rhs.isInvalid() || !_srs->isEquivalentTo( rhs.getSRS() ) )
+    if ( isInvalid() || rhs.isInvalid() || !_srs->isHorizEquivalentTo( rhs.getSRS() ) )
         return GeoExtent::INVALID;
 
     if ( !intersects(rhs) )
@@ -1220,6 +1245,44 @@ GeoExtent::toString() const
     return bufStr;
 }
 
+bool
+GeoExtent::createPolytope(osg::Polytope& tope) const
+{
+    if ( ! this->isValid() )
+        return false;
+
+    if ( getSRS()->isProjected() )
+    {
+        // add planes for the four sides of the extent, Normals point inwards.
+        double halfWidth  = 0.5*width();
+        double halfHeight = 0.5*height();
+        tope.add( osg::Plane(osg::Vec3d( 1, 0,0), osg::Vec3d(-halfWidth,0,0)) );
+        tope.add( osg::Plane(osg::Vec3d(-1, 0,0), osg::Vec3d( halfWidth,0,0)) );
+        tope.add( osg::Plane(osg::Vec3d( 0, 1,0), osg::Vec3d(0, -halfHeight,0)) );
+        tope.add( osg::Plane(osg::Vec3d( 0,-1,0), osg::Vec3d(0,  halfHeight,0)) );
+    }
+
+    else
+    {
+        // for each extent, create a plane passing through the planet's centroid.
+
+        // 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 );
+
+        // bounding planes in ECEF space:
+        tope.add( osg::Plane(center, nw, sw) ); // west
+        tope.add( osg::Plane(center, sw, se) ); // south
+        tope.add( osg::Plane(center, se, ne) ); // east
+        tope.add( osg::Plane(center, ne, nw) ); // north
+    }
+
+    return true;
+}
 
 /***************************************************************************/
 
@@ -1232,7 +1295,7 @@ GeoExtent(extent)
 
 DataExtent::DataExtent(const osgEarth::GeoExtent& extent, unsigned minLevel) :
 GeoExtent(extent),
-_maxLevel( 0 )
+_maxLevel( 25 )
 {
     _minLevel = minLevel;
 }
@@ -1240,7 +1303,7 @@ _maxLevel( 0 )
 DataExtent::DataExtent(const osgEarth::GeoExtent& extent ) :
 GeoExtent(extent),
 _minLevel( 0 ),
-_maxLevel( 0 )
+_maxLevel( 25 )
 {
     //nop
 }
@@ -1261,8 +1324,7 @@ _extent( GeoExtent::INVALID )
     //nop
 }
 
-
-GeoImage::GeoImage( osg::Image* image, const GeoExtent& extent ) :
+GeoImage::GeoImage(osg::Image* image, const GeoExtent& extent) :
 _image(image),
 _extent(extent)
 {
@@ -1279,22 +1341,26 @@ GeoImage::valid() const
 }
 
 osg::Image*
-GeoImage::getImage() const {
+GeoImage::getImage() const
+{
     return _image.get();
 }
 
 const SpatialReference*
-GeoImage::getSRS() const {
+GeoImage::getSRS() const
+{
     return _extent.getSRS();
 }
 
 const GeoExtent&
-GeoImage::getExtent() const {
+GeoImage::getExtent() const
+{
     return _extent;
 }
 
 double
-GeoImage::getUnitsPerPixel() const {
+GeoImage::getUnitsPerPixel() const
+{
     double uppw = _extent.width() / (double)_image->s();
     double upph = _extent.height() / (double)_image->t();
     return (uppw + upph) / 2.0;
@@ -1303,6 +1369,9 @@ GeoImage::getUnitsPerPixel() const {
 GeoImage
 GeoImage::crop( const GeoExtent& extent, bool exact, unsigned int width, unsigned int height, bool useBilinearInterpolation) const
 {
+    if ( !valid() )
+        return *this;
+
     //Check for equivalence
     if ( extent.getSRS()->isEquivalentTo( getSRS() ) )
     {
@@ -1326,7 +1395,7 @@ GeoImage::crop( const GeoExtent& extent, bool exact, unsigned int width, unsigne
             }
 
             //Note:  Passing in the current SRS simply forces GDAL to not do any warping
-            return reproject( getSRS(), &extent, width, height, useBilinearInterpolation);
+            return reproject( getSRS(), &extent, width, height, useBilinearInterpolation );
         }
         else
         {
@@ -1396,11 +1465,59 @@ namespace
     {
         // called internally -- GDAL lock not required
 
+        int numBands = ds->GetRasterCount();
+        if ( numBands < 1 )
+            return 0L;
+
+        GLenum dataType;
+        int    sampleSize;
+        GLint  internalFormat;
+
+        switch(ds->GetRasterBand(1)->GetRasterDataType())
+        {
+        case GDT_Byte:
+            dataType = GL_UNSIGNED_BYTE;
+            sampleSize = 1;
+            internalFormat = GL_LUMINANCE8;
+            break;
+        case GDT_UInt16:
+        case GDT_Int16:
+            dataType = GL_UNSIGNED_SHORT;
+            sampleSize = 2;
+            internalFormat = GL_LUMINANCE16;
+            break;
+        default:
+            dataType = GL_FLOAT;
+            sampleSize = 4;
+            internalFormat = GL_LUMINANCE32F_ARB;
+        }
+
+        GLenum pixelFormat =
+            numBands == 1 ? GL_LUMINANCE :
+            numBands == 2 ? GL_LUMINANCE_ALPHA :
+            numBands == 3 ? GL_RGB :
+                            GL_RGBA;
+
+        int pixelBytes = sampleSize * numBands;
+
         //Allocate the image
         osg::Image *image = new osg::Image;
-        image->allocateImage(ds->GetRasterXSize(), ds->GetRasterYSize(), 1, GL_RGBA, GL_UNSIGNED_BYTE);
-
-        ds->RasterIO(GF_Read, 0, 0, image->s(), image->t(), (void*)image->data(), image->s(), image->t(), GDT_Byte, 4, NULL, 4, 4 * image->s(), 1);
+        image->allocateImage(ds->GetRasterXSize(), ds->GetRasterYSize(), 1, pixelFormat, dataType);
+
+        ds->RasterIO(
+            GF_Read, 
+            0, 0, 
+            image->s(), image->t(), 
+            (void*)image->data(), 
+            image->s(), image->t(), 
+            ds->GetRasterBand(1)->GetRasterDataType(),
+            numBands,
+            NULL,
+            pixelBytes,
+            pixelBytes * image->s(),
+            1);
+
+//        ds->RasterIO(GF_Read, 0, 0, image->s(), image->t(), (void*)image->data(), image->s(), image->t(), GDT_Byte, 4, NULL, 4, 4 * image->s(), 1);
         ds->FlushCache();
 
         image->flipVertical();
@@ -1409,7 +1526,7 @@ namespace
     }
 
     GDALDataset*
-    createMemDS(int width, int height, double minX, double minY, double maxX, double maxY, const std::string &projection)
+    createMemDS(int width, int height, int numBands, GDALDataType dataType, double minX, double minY, double maxX, double maxY, const std::string &projection)
     {
         //Get the MEM driver
         GDALDriver* memDriver = (GDALDriver*)GDALGetDriverByName("MEM");
@@ -1419,13 +1536,24 @@ namespace
         }
 
         //Create the in memory dataset.
-        GDALDataset* ds = memDriver->Create("", width, height, 4, GDT_Byte, 0);
+        GDALDataset* ds = memDriver->Create("", width, height, numBands, dataType, 0);
 
         //Initialize the color interpretation
-        ds->GetRasterBand(1)->SetColorInterpretation(GCI_RedBand);
-        ds->GetRasterBand(2)->SetColorInterpretation(GCI_GreenBand);
-        ds->GetRasterBand(3)->SetColorInterpretation(GCI_BlueBand);
-        ds->GetRasterBand(4)->SetColorInterpretation(GCI_AlphaBand);
+        if ( numBands == 1 )
+        {
+            ds->GetRasterBand(1)->SetColorInterpretation(GCI_GrayIndex);
+        }
+        else
+        {
+            ds->GetRasterBand(1)->SetColorInterpretation(GCI_RedBand);
+            ds->GetRasterBand(2)->SetColorInterpretation(GCI_GreenBand);
+            ds->GetRasterBand(3)->SetColorInterpretation(GCI_BlueBand);
+
+            if ( numBands == 4 )
+            {
+                ds->GetRasterBand(4)->SetColorInterpretation(GCI_AlphaBand);
+            }
+        }
 
         //Initialize the geotransform
         double geotransform[6];
@@ -1450,9 +1578,51 @@ namespace
         osg::ref_ptr<osg::Image> clonedImage = new osg::Image(*image);
 
         //Flip the image
-        clonedImage->flipVertical();  
+        clonedImage->flipVertical();
 
-        GDALDataset* srcDS = createMemDS(image->s(), image->t(), minX, minY, maxX, maxY, projection);
+        GDALDataType gdalDataType =
+            image->getDataType() == GL_UNSIGNED_BYTE  ? GDT_Byte :
+            image->getDataType() == GL_UNSIGNED_SHORT ? GDT_UInt16 :
+            image->getDataType() == GL_FLOAT          ? GDT_Float32 :
+                                                        GDT_Byte;
+
+        int numBands =
+            image->getPixelFormat() == GL_RGBA      ? 4 :
+            image->getPixelFormat() == GL_RGB       ? 3 :
+            image->getPixelFormat() == GL_LUMINANCE ? 1 : 0;
+
+
+        if ( numBands == 0 )
+        {
+            OE_WARN << LC << "Failure in createDataSetFromImage: unsupported pixel format\n";
+            return 0L;
+        }
+
+        int pixelBytes =
+            gdalDataType == GDT_Byte   ?   numBands :
+            gdalDataType == GDT_UInt16 ? 2*numBands :
+                                         4*numBands;
+        
+        GDALDataset* srcDS = createMemDS(image->s(), image->t(), numBands, gdalDataType, minX, minY, maxX, maxY, projection);
+
+        if ( srcDS )
+        {
+            srcDS->RasterIO(
+                GF_Write, 
+                0, 0,
+                clonedImage->s(), clonedImage->t(),
+                (void*)clonedImage->data(),
+                clonedImage->s(),
+                clonedImage->t(),
+                gdalDataType,
+                numBands,
+                NULL,
+                pixelBytes,
+                pixelBytes * image->s(),
+                1);
+
+
+#if 0
 
         //Write the image data into the memory dataset
         //If the image is already RGBA, just read all 4 bands in one call
@@ -1479,7 +1649,10 @@ namespace
         {
             OE_WARN << LC << "createDataSetFromImage: unsupported pixel format " << std::hex << image->getPixelFormat() << std::endl;
         }
-        srcDS->FlushCache();
+#endif
+
+            srcDS->FlushCache();
+        }
 
         return srcDS;
     }
@@ -1495,7 +1668,7 @@ namespace
         //Create a dataset from the source image
         GDALDataset* srcDS = createDataSetFromImage(srcImage, srcMinX, srcMinY, srcMaxX, srcMaxY, srcWKT);
 
-        OE_DEBUG << LC << "Source image is " << srcImage->s() << "x" << srcImage->t() << std::endl;
+        OE_DEBUG << LC << "Source image is " << srcImage->s() << "x" << srcImage->t() << " in " << srcWKT << std::endl;
 
 
         if (width == 0 || height == 0)
@@ -1512,9 +1685,12 @@ namespace
                 0);
             GDALDestroyGenImgProjTransformer(transformer);
         }
-	    OE_DEBUG << "Creating warped output of " << width <<"x" << height << std::endl;
-       
-        GDALDataset* destDS = createMemDS(width, height, destMinX, destMinY, destMaxX, destMaxY, destWKT);
+	    OE_DEBUG << "Creating warped output of " << width <<"x" << height << " in " << destWKT << std::endl;
+
+        int numBands = srcDS->GetRasterCount();
+        GDALDataType dataType = srcDS->GetRasterBand(1)->GetRasterDataType();
+               
+        GDALDataset* destDS = createMemDS(width, height, numBands, dataType, destMinX, destMinY, destMaxX, destMaxY, destWKT);
 
         if (useBilinearInterpolation == true)
         {
@@ -1544,11 +1720,11 @@ namespace
     }    
 
 
-    osg::Image*
-    manualReproject(
+    osg::Image* manualReproject(
         const osg::Image* image, 
         const GeoExtent&  src_extent, 
         const GeoExtent&  dest_extent,
+        bool              interpolate,
         unsigned int      width = 0, 
         unsigned int      height = 0)
     {
@@ -1565,7 +1741,9 @@ namespace
 
         osg::Image *result = new osg::Image();
         //result->allocateImage(width, height, 1, GL_RGBA, GL_UNSIGNED_BYTE);
-        result->allocateImage(width, height, 1, image->getPixelFormat(), GL_UNSIGNED_BYTE);
+        result->allocateImage(width, height, 1, image->getPixelFormat(), image->getDataType()); //GL_UNSIGNED_BYTE);
+        result->setInternalTextureFormat(image->getInternalTextureFormat());
+        ImageUtils::markAsUnNormalized(result, ImageUtils::isUnNormalized(image));
 
         //Initialize the image to be completely transparent/black
         memset(result->data(), 0, result->getImageSizeInBytes());
@@ -1622,7 +1800,7 @@ namespace
                 osg::Vec4 color(0,0,0,0);
 
                 // TODO: consider this again later. Causes blockiness.
-                if ( false ) //! isSrcContiguous ) // non-contiguous space- use nearest neighbot
+                if ( !interpolate ) //! isSrcContiguous ) // non-contiguous space- use nearest neighbot
                 {
                     color = ia(px_i, py_i);
                 }
@@ -1723,8 +1901,6 @@ namespace
     }
 }
 
-
-
 GeoImage
 GeoImage::reproject(const SpatialReference* to_srs, const GeoExtent* to_extent, unsigned int width, unsigned int height, bool useBilinearInterpolation) const
 {  
@@ -1740,21 +1916,23 @@ GeoImage::reproject(const SpatialReference* to_srs, const GeoExtent* to_extent,
 
     osg::Image* resultImage = 0L;
 
+    bool isNormalized = ImageUtils::isNormalized(getImage());
+    
     if ( getSRS()->isUserDefined()      || 
         to_srs->isUserDefined()         ||
         getSRS()->isSphericalMercator() ||
-        to_srs->isSphericalMercator() )
-        //( getSRS()->isSphericalMercator() && to_srs->isGeographic() ) ||
-        //( getSRS()->isGeographic() && to_srs->isSphericalMercator() ) )
+        to_srs->isSphericalMercator()   ||
+        !isNormalized )
     {
         // if either of the SRS is a custom projection, we have to do a manual reprojection since
         // GDAL will not recognize the SRS.
-        resultImage = manualReproject(getImage(), getExtent(), *to_extent, width, height);
+        resultImage = manualReproject(getImage(), getExtent(), *to_extent, useBilinearInterpolation && isNormalized, width, height);
     }
     else
     {
         // otherwise use GDAL.
-        resultImage = reprojectImage(getImage(),
+        resultImage = reprojectImage(
+            getImage(),
             getSRS()->getWKT(),
             getExtent().xMin(), getExtent().yMin(), getExtent().xMax(), getExtent().yMax(),
             to_srs->getWKT(),
diff --git a/src/osgEarth/GeoMath b/src/osgEarth/GeoMath
index 27d53ef..740a76e 100644
--- a/src/osgEarth/GeoMath
+++ b/src/osgEarth/GeoMath
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarth/GeoMath.cpp b/src/osgEarth/GeoMath.cpp
index de5376b..bbeb51f 100644
--- a/src/osgEarth/GeoMath.cpp
+++ b/src/osgEarth/GeoMath.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -194,7 +194,8 @@ GeoMath::rhumbDistance(double lat1Rad, double lon1Rad,
     double dLon = osg::absolute(lon2Rad - lon1Rad);
 
     double dPhi = log(tan(lat2Rad/2.0+osg::PI/4.0)/tan(lat1Rad/2.0+osg::PI/4.0));
-    double q = (!osg::isNaN(dLat/dPhi)) ? dLat/dPhi : cos(lat1Rad);  // E-W line gives dPhi=0
+    bool eastWest = osg::equivalent(dPhi, 0.0);
+    double q = eastWest ? cos(lat1Rad) : dLat/dPhi;
     // if dLon over 180� take shorter rhumb across 180� meridian:
     if (dLon > osg::PI) dLon = 2.0*osg::PI - dLon;
     double dist = sqrt(dLat*dLat + q*q*dLon*dLon) * radius; 
@@ -243,7 +244,8 @@ GeoMath::rhumbDestination(double lat1Rad, double lon1Rad,
   double lat2Rad = lat1Rad + d*cos(bearing);
   double dLat = lat2Rad-lat1Rad;
   double dPhi = log(tan(lat2Rad/2.0+osg::PI/4.0)/tan(lat1Rad/2.0+osg::PI/4.0));
-  double q = (!osg::isNaN(dLat/dPhi)) ? dLat/dPhi : cos(lat1Rad);  // E-W line gives dPhi=0
+  bool eastWestLine = osg::equivalent(dPhi, 0.0);
+  double q = eastWestLine ? cos(lat1Rad) : dLat/dPhi;
   double dLon = d*sin(bearing)/q;
   // check for some daft bugger going past the pole
   if (osg::absolute(lat2Rad) > osg::PI/2.0) lat2Rad = lat2Rad > 0.0 ? osg::PI-lat2Rad : -(osg::PI-lat2Rad);
diff --git a/src/osgEarth/GeoTransform b/src/osgEarth/GeoTransform
index 8a1b9d2..0859d02 100644
--- a/src/osgEarth/GeoTransform
+++ b/src/osgEarth/GeoTransform
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarth/GeoTransform.cpp b/src/osgEarth/GeoTransform.cpp
index 52910f9..1ddd2f6 100644
--- a/src/osgEarth/GeoTransform.cpp
+++ b/src/osgEarth/GeoTransform.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarth/Geoid b/src/osgEarth/Geoid
index 1944d29..e7013d0 100644
--- a/src/osgEarth/Geoid
+++ b/src/osgEarth/Geoid
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarth/Geoid.cpp b/src/osgEarth/Geoid.cpp
index 3ee4f75..149cccd 100644
--- a/src/osgEarth/Geoid.cpp
+++ b/src/osgEarth/Geoid.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarth/HTTPClient b/src/osgEarth/HTTPClient
index cad2027..f476119 100644
--- a/src/osgEarth/HTTPClient
+++ b/src/osgEarth/HTTPClient
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -395,7 +395,7 @@ namespace osgEarth
         static HTTPClient& getClient();
 
     private:
-        void decodeMultipartStream(
+        bool decodeMultipartStream(
             const std::string&   boundary,
             HTTPResponse::Part*  input,
             HTTPResponse::Parts& output) const;
diff --git a/src/osgEarth/HTTPClient.cpp b/src/osgEarth/HTTPClient.cpp
index 3eb8562..8f9fc0d 100644
--- a/src/osgEarth/HTTPClient.cpp
+++ b/src/osgEarth/HTTPClient.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -352,7 +352,7 @@ 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 Threading::PerThread<HTTPClient> s_clientPerThread;
+    static PerThread<HTTPClient>       s_clientPerThread;
 
     static optional<ProxySettings>     s_proxySettings;
 
@@ -551,7 +551,7 @@ HTTPClient::readOptions(const osgDB::Options* options, std::string& proxy_host,
     }
 }
 
-void
+bool
 HTTPClient::decodeMultipartStream(const std::string&   boundary,
                                   HTTPResponse::Part*  input,
                                   HTTPResponse::Parts& output) const
@@ -566,12 +566,12 @@ HTTPClient::decodeMultipartStream(const std::string&   boundary,
     line = tempbuf;
     if ( line != bstr )
     {
-        OE_WARN << LC 
+        OE_INFO << LC 
             << "decodeMultipartStream: protocol violation; "
             << "expecting boundary; instead got: \"" 
             << line
             << "\"" << std::endl;
-        return;
+        return false;
     }
 
     for( bool done=false; !done; )
@@ -636,6 +636,8 @@ HTTPClient::decodeMultipartStream(const std::string&   boundary,
             output.push_back( next_part.get() );
         }
     }
+
+    return true;
 }
 
 HTTPResponse
@@ -739,7 +741,7 @@ HTTPClient::doGet(const HTTPRequest&    request,
     {       
         proxy_host = proxySettings.get().hostName();
         proxy_port = toString<int>(proxySettings.get().port());
-        OE_DEBUG << "Read proxy settings from options " << proxy_host << " " << proxy_port << std::endl;
+        OE_DEBUG << LC << "Read proxy settings from options " << proxy_host << " " << proxy_port << std::endl;
     }
 
     //Try to get the proxy settings from the environment variable
@@ -772,7 +774,9 @@ HTTPClient::doGet(const HTTPRequest&    request,
         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() );
@@ -781,14 +785,16 @@ HTTPClient::doGet(const HTTPRequest&    request,
         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 << "Removing proxy settings" << std::endl;
+        OE_DEBUG << LC << "Removing proxy settings" << std::endl;
         curl_easy_setopt( _curl_handle, CURLOPT_PROXY, 0 );
     }
 
@@ -799,7 +805,7 @@ HTTPClient::doGet(const HTTPRequest&    request,
     {
         std::string oldURL = url;
         url = rewriter->rewrite( oldURL );
-        OE_INFO << "Rewrote URL " << oldURL << " to " << url << std::endl;
+        OE_INFO << LC << "Rewrote URL " << oldURL << " to " << url << std::endl;
     }
 
     const osgDB::AuthenticationDetails* details = authenticationMap ?
@@ -882,6 +888,9 @@ HTTPClient::doGet(const HTTPRequest&    request,
         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()) {
@@ -892,9 +901,6 @@ HTTPClient::doGet(const HTTPRequest&    request,
         curl_easy_setopt( _curl_handle, CURLOPT_WRITEDATA, (void*)0 );
         curl_easy_setopt( _curl_handle, CURLOPT_PROGRESSDATA, (void*)0);
 
-        //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 );
-
         if (!proxy_addr.empty())
         {
             long connect_code = 0L;
@@ -937,7 +943,11 @@ HTTPClient::doGet(const HTTPRequest&    request,
             OE_DEBUG << LC << "detected multipart data; decoding..." << std::endl;
 
             //TODO: parse out the "wcs" -- this is WCS-specific
-            decodeMultipartStream( "wcs", part.get(), response._parts );
+            if ( !decodeMultipartStream( "wcs", part.get(), response._parts ) )
+            {
+                // error decoding an invalid multipart stream.
+                // should we do anything, or just leave the response empty?
+            }
         }
         else
         {            
@@ -1084,11 +1094,16 @@ namespace
             }
         }
 
-        if ( !reader )
+        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;
@@ -1119,16 +1134,19 @@ HTTPClient::doReadImage(const HTTPRequest&    request,
             osgDB::ReaderWriter::ReadResult rr = reader->readImage(response.getPartStream(0), options);
             if ( rr.validImage() )
             {
-                result = ReadResult(rr.takeImage(), response.getHeadersAsConfig() );
+                result = ReadResult(rr.takeImage());
             }
             else 
             {
-                if ( !rr.message().empty() )
+                if ( s_HTTP_DEBUG )
                 {
-                    OE_WARN << LC << "HTTP error: " << rr.message() << std::endl;
+                    OE_WARN << LC << reader->className() 
+                        << " failed to read image from " << request.getURL() 
+                        << "; message = " << rr.message()
+                        <<  std::endl;
                 }
-                OE_WARN << LC << reader->className() << " failed to read image from " << request.getURL() << std::endl;
                 result = ReadResult(ReadResult::RESULT_READER_ERROR);
+                result.setErrorDetail( rr.message() );
             }
         }
         
@@ -1141,23 +1159,29 @@ HTTPClient::doReadImage(const HTTPRequest&    request,
     else
     {
         result = ReadResult(
-            response.isCancelled() ? ReadResult::RESULT_CANCELED :
-            response.getCode() == HTTPResponse::NOT_FOUND ? ReadResult::RESULT_NOT_FOUND :
+            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 );
+                                                               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)
             {
-                OE_DEBUG << "Error in HTTPClient for " << request.getURL() << " but it's recoverable" << std::endl;
+                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() );
@@ -1189,16 +1213,19 @@ HTTPClient::doReadNode(const HTTPRequest&    request,
             osgDB::ReaderWriter::ReadResult rr = reader->readNode(response.getPartStream(0), options);
             if ( rr.validNode() )
             {
-                result = ReadResult(rr.takeNode(), response.getHeadersAsConfig());
+                result = ReadResult(rr.takeNode());
             }
             else 
             {
-                if ( !rr.message().empty() )
+                if ( s_HTTP_DEBUG )
                 {
-                    OE_WARN << LC << "HTTP error: " << rr.message() << std::endl;
+                    OE_WARN << LC << reader->className() 
+                        << " failed to read node from " << request.getURL() 
+                        << "; message = " << rr.message()
+                        <<  std::endl;
                 }
-                OE_WARN << LC << reader->className() << " failed to read node from " << request.getURL() << std::endl;
                 result = ReadResult(ReadResult::RESULT_READER_ERROR);
+                result.setErrorDetail( rr.message() );
             }
         }
         
@@ -1208,23 +1235,29 @@ HTTPClient::doReadNode(const HTTPRequest&    request,
     else
     {
         result = ReadResult(
-            response.isCancelled() ? ReadResult::RESULT_CANCELED :
-            response.getCode() == HTTPResponse::NOT_FOUND ? ReadResult::RESULT_NOT_FOUND :
+            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 );
+                                                               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)
             {
-                OE_DEBUG << "Error in HTTPClient for " << request.getURL() << " but it's recoverable" << std::endl;
+                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;
 }
 
@@ -1252,16 +1285,19 @@ HTTPClient::doReadObject(const HTTPRequest&    request,
             osgDB::ReaderWriter::ReadResult rr = reader->readObject(response.getPartStream(0), options);
             if ( rr.validObject() )
             {
-                result = ReadResult(rr.takeObject(), response.getHeadersAsConfig());
+                result = ReadResult(rr.takeObject());
             }
             else 
             {
-                if ( !rr.message().empty() )
+                if ( s_HTTP_DEBUG )
                 {
-                    OE_WARN << LC << "HTTP error: " << rr.message() << std::endl;
+                    OE_WARN << LC << reader->className() 
+                        << " failed to read object from " << request.getURL() 
+                        << "; message = " << rr.message()
+                        <<  std::endl;
                 }
-                OE_WARN << LC << reader->className() << " failed to read object from " << request.getURL() << std::endl;
                 result = ReadResult(ReadResult::RESULT_READER_ERROR);
+                result.setErrorDetail( rr.message() );
             }
         }
         
@@ -1282,12 +1318,17 @@ HTTPClient::doReadObject(const HTTPRequest&    request,
         {
             if (callback)
             {
-                OE_DEBUG << "Error in HTTPClient for " << request.getURL() << " but it's recoverable" << std::endl;
+                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;
 }
 
@@ -1304,7 +1345,7 @@ HTTPClient::doReadString(const HTTPRequest&    request,
     HTTPResponse response = this->doGet( request, options, callback );
     if ( response.isOK() )
     {
-        result = ReadResult( new StringObject(response.getPartAsString(0)), response.getHeadersAsConfig());
+        result = ReadResult( new StringObject(response.getPartAsString(0)) );
     }
 
     else if ( response.getCode() >= 400 && response.getCode() < 500 && response.getCode() != 404 )
@@ -1313,30 +1354,35 @@ HTTPClient::doReadString(const HTTPRequest&    request,
         // 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)), 
-            response.getHeadersAsConfig() );
+            new StringObject(response.getPartAsString(0)) );
     }
 
     else
     {
         result = ReadResult(
-            response.isCancelled() ? ReadResult::RESULT_CANCELED :
-            response.getCode() == HTTPResponse::NOT_FOUND ? ReadResult::RESULT_NOT_FOUND :
+            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 );
+                                                               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)
             {
-                OE_DEBUG << "Error in HTTPClient for " << request.getURL() << " but it's recoverable" << std::endl;
+                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( getCurlFileTime(_curl_handle) );
 
diff --git a/src/osgEarth/HeightFieldUtils b/src/osgEarth/HeightFieldUtils
index 20137d0..b5b6b08 100644
--- a/src/osgEarth/HeightFieldUtils
+++ b/src/osgEarth/HeightFieldUtils
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
@@ -24,7 +27,6 @@
 #include <osg/Shape>
 #include <osg/CoordinateSystemNode>
 #include <osg/ClusterCullingCallback>
-#include <osgTerrain/ValidDataOperator>
 
 namespace osgEarth
 {
@@ -51,16 +53,16 @@ namespace osgEarth
             }
         }
 
-        bool getNeighborForNormalizedLocation(double nx, double ny, osg::ref_ptr<osg::HeightField>& hf, double& out_nx, double& out_ny) const {
+        bool getNeighborForNormalizedLocation(double nx, double ny, osg::HeightField*& hfPtr, double& out_nx, double& out_ny) const {
             int xoffset = nx < 0.0 ? -1 : nx > 1.0 ? 1 : 0;
             int yoffset = ny < 0.0 ? 1 : ny > 1.0 ? -1 : 0;
             if ( xoffset != 0 || yoffset != 0 )
-                hf = getNeighbor(xoffset, yoffset);
+                hfPtr = getNeighbor(xoffset, yoffset);
             else
-                hf = _center.get();
+                hfPtr = _center.get();
             out_nx = nx < 0.0 ? 1.0+nx : nx > 1.0 ? nx-1.0 : nx;
             out_ny = ny < 0.0 ? 1.0+ny : ny > 1.0 ? ny-1.0 : ny;
-            return hf.valid();
+            return hfPtr != 0L;
         }
     };
 
@@ -114,7 +116,7 @@ namespace osgEarth
         static bool getHeightAtNormalizedLocation(
             const HeightFieldNeighborhood& hood,
             double nx, double ny,
-            double& output,
+            float& output,
             ElevationInterpolation interp = INTERP_BILINEAR);
 
         /**
@@ -154,10 +156,10 @@ namespace osgEarth
          * Subsamples a heightfield to the specified extent.
          */
         static osg::HeightField* createSubSample(
-            osg::HeightField*      input, 
-            const GeoExtent&       inputEx,
-            const GeoExtent&       outputEx,
-            ElevationInterpolation interpolation = INTERP_BILINEAR);
+            const osg::HeightField* input, 
+            const GeoExtent&        inputEx,
+            const GeoExtent&        outputEx,
+            ElevationInterpolation  interpolation = INTERP_BILINEAR);
 
         /**
          * Resizes a heightfield, keeping the corner values the same and
@@ -189,8 +191,24 @@ namespace osgEarth
             osg::HeightField*          grid, 
             const osg::EllipsoidModel* em, 
             float verticalScale =1.0f );
+
+        /**
+         * Convert a heightfield (and its neighbors) into a normal map* image.
+         */
+        static osg::Image* convertToNormalMap(
+            const HeightFieldNeighborhood& hood,
+            const SpatialReference*        hoodSRS);
+
+
+        /**
+         * Utility function that will take sample points used for interpolation and copy valid values into any of the samples that are NO_DATA_VALUE.
+         * Returns false if all values are NO_DATA_VALUE.
+         * Returns true if all the values are valid or if we were able to replace NO_DATA_VALUE samples with valid values.
+         **/
+        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.
     */
@@ -249,6 +267,7 @@ namespace osgEarth
 
         float _defaultValue;
     };
+#endif
 }
 
 #endif //OSGEARTH_HEIGHTFIELDUTILS_H
diff --git a/src/osgEarth/HeightFieldUtils.cpp b/src/osgEarth/HeightFieldUtils.cpp
index 8f45292..98b080f 100644
--- a/src/osgEarth/HeightFieldUtils.cpp
+++ b/src/osgEarth/HeightFieldUtils.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -21,10 +21,41 @@
 #include <osgEarth/GeoData>
 #include <osgEarth/Geoid>
 #include <osgEarth/CullingUtils>
+#include <osgEarth/ImageUtils>
 #include <osg/Notify>
 
 using namespace osgEarth;
 
+
+bool
+HeightFieldUtils::validateSamples(float &a, float &b, float &c, float &d)
+{
+    // If ALL the sample points are NO_DATA_VALUE then we can't do anything.
+    if (a == NO_DATA_VALUE && b == NO_DATA_VALUE && c == NO_DATA_VALUE && d == NO_DATA_VALUE)
+    {
+        return false;
+    }
+
+    // If any of the samples are valid but some are NO_DATA_VALUE we can replace the nodata with valid values.
+    if (a == NO_DATA_VALUE ||
+        b == NO_DATA_VALUE || 
+        c == NO_DATA_VALUE ||
+        d == NO_DATA_VALUE)
+    {
+        float validValue = a;
+        if (validValue == NO_DATA_VALUE) validValue = b;
+        if (validValue == NO_DATA_VALUE) validValue = c;
+        if (validValue == NO_DATA_VALUE) validValue = d;
+
+        if (a == NO_DATA_VALUE) a = validValue;
+        if (b == NO_DATA_VALUE) b = validValue;
+        if (c == NO_DATA_VALUE) c = validValue;
+        if (d == NO_DATA_VALUE) d = validValue;
+    }
+
+    return true;
+}
+
 float
 HeightFieldUtils::getHeightAtPixel(const osg::HeightField* hf, double c, double r, ElevationInterpolation interpolation)
 {
@@ -75,7 +106,7 @@ HeightFieldUtils::getHeightAtPixel(const osg::HeightField* hf, double c, double
         float lrHeight = hf->getHeight(colMax, rowMin);
 
         //Make sure not to use NoData in the interpolation
-        if (urHeight == NO_DATA_VALUE || llHeight == NO_DATA_VALUE || ulHeight == NO_DATA_VALUE || lrHeight == NO_DATA_VALUE)
+        if (!validateSamples(urHeight, llHeight, ulHeight, lrHeight))
         {
             return NO_DATA_VALUE;
         }
@@ -88,51 +119,6 @@ HeightFieldUtils::getHeightAtPixel(const osg::HeightField* hf, double c, double
         //Determine which triangle the point falls in.
         osg::Vec3d v0, v1, v2;
 
-#if 0
-        bool orientation = fabs(llHeight-urHeight) < fabs(ulHeight-lrHeight);
-        if ( orientation )
-        {
-            double dx = c - (double)colMin;
-            double dy = r - (double)rowMin;
-
-            // divide along ll->ur
-            if (dx > dy)
-            {
-                //The point lies in the right triangle
-                v0.set(colMin, rowMin, llHeight);
-                v1.set(colMax, rowMin, lrHeight);
-                v2.set(colMax, rowMax, urHeight);
-            }
-            else
-            {
-                //The point lies in the left triangle
-                v0.set(colMin, rowMin, llHeight);
-                v1.set(colMax, rowMax, urHeight);
-                v2.set(colMin, rowMax, ulHeight);
-            }
-        }
-        else
-        {
-            double dx = c - (double)colMin;
-            double dy = (double)rowMax - r;
-
-            // divide along ul->lr
-            if (dx > dy)
-            {
-                //The point lies in the right triangle
-                v0.set(colMax, rowMin, lrHeight);
-                v1.set(colMax, rowMax, urHeight);
-                v2.set(colMin, rowMax, ulHeight);
-            }
-            else
-            {
-                //The point lies in the left triangle
-                v0.set(colMin, rowMin, llHeight);
-                v1.set(colMax, rowMin, lrHeight);
-                v2.set(colMin, rowMax, ulHeight);
-            }
-        }
-#else
         double dx = c - (double)colMin;
         double dy = r - (double)rowMin;
 
@@ -150,7 +136,6 @@ HeightFieldUtils::getHeightAtPixel(const osg::HeightField* hf, double c, double
             v1.set(colMax, rowMax, urHeight);
             v2.set(colMin, rowMax, ulHeight);
         }
-#endif
 
         //Compute the normal
         osg::Vec3d n = (v1 - v0) ^ (v2 - v0);
@@ -174,7 +159,7 @@ HeightFieldUtils::getHeightAtPixel(const osg::HeightField* hf, double c, double
         float lrHeight = hf->getHeight(colMax, rowMin);
 
         //Make sure not to use NoData in the interpolation
-        if (urHeight == NO_DATA_VALUE || llHeight == NO_DATA_VALUE || ulHeight == NO_DATA_VALUE || lrHeight == NO_DATA_VALUE)
+        if (!validateSamples(urHeight, llHeight, ulHeight, lrHeight))
         {
             return NO_DATA_VALUE;
         }
@@ -285,16 +270,17 @@ HeightFieldUtils::getHeightAtNormalizedLocation(const osg::HeightField* input,
 bool
 HeightFieldUtils::getHeightAtNormalizedLocation(const HeightFieldNeighborhood& hood,
                                                 double nx, double ny,
-                                                double& output,
+                                                float& output,
                                                 ElevationInterpolation interp)
 {
-    osg::ref_ptr<osg::HeightField> hf;
+    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.get(), px, py, interp );
+        output = getHeightAtPixel( hf, px, py, interp );
         return true;
     }
     return false;
@@ -352,8 +338,10 @@ HeightFieldUtils::scaleHeightFieldToDegrees( osg::HeightField* hf )
 
 
 osg::HeightField*
-HeightFieldUtils::createSubSample(osg::HeightField* input, const GeoExtent& inputEx, 
-                                  const GeoExtent& outputEx, osgEarth::ElevationInterpolation interpolation)
+HeightFieldUtils::createSubSample(const osg::HeightField* input,
+                                  const GeoExtent& inputEx, 
+                                  const GeoExtent& outputEx,
+                                  osgEarth::ElevationInterpolation interpolation)
 {
     double div = outputEx.width()/inputEx.width();
     if ( div >= 1.0f )
@@ -362,9 +350,6 @@ HeightFieldUtils::createSubSample(osg::HeightField* input, const GeoExtent& inpu
     int numCols = input->getNumColumns();
     int numRows = input->getNumRows();
 
-    //float dx = input->getXInterval() * div;
-    //float dy = input->getYInterval() * div;
-
     double xInterval = inputEx.width()  / (double)(input->getNumColumns()-1);
     double yInterval = inputEx.height()  / (double)(input->getNumRows()-1);
     double dx = div * xInterval;
@@ -616,8 +601,83 @@ HeightFieldUtils::createClusterCullingCallback(osg::HeightField*          grid,
     return ccc;
 }
 
-/******************************************************************************************/
 
+osg::Image*
+HeightFieldUtils::convertToNormalMap(const HeightFieldNeighborhood& hood,
+                                     const SpatialReference*        hoodSRS)
+{
+    const osg::HeightField* hf = hood._center.get();
+    
+    osg::Image* image = new osg::Image();
+    image->allocateImage(hf->getNumColumns(), hf->getNumRows(), 1, GL_RGBA, GL_UNSIGNED_BYTE);
+
+    double xcells = (double)(hf->getNumColumns()-1);
+    double ycells = (double)(hf->getNumRows()-1);
+    double xres = 1.0/xcells;
+    double yres = 1.0/ycells;
+
+    // north-south interval in meters:
+    double mPerDegAtEquator = (hoodSRS->getEllipsoid()->getRadiusEquator() * 2.0 * osg::PI)/360.0;
+    double tIntervalMeters = 
+        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):
+        double lat = hf->getOrigin().y() + hf->getYInterval()*(double)t;
+        double sIntervalMeters =
+            hoodSRS->isGeographic() ? hf->getXInterval() * mPerDegAtEquator * cos(osg::DegreesToRadians(lat)) :
+            hf->getXInterval();
+
+        for(int s=0; s<(int)hf->getNumColumns(); ++s)
+        {
+            float centerHeight = hf->getHeight(s, t);
+
+            double nx = xres*(double)s;
+            double ny = yres*(double)t;
+
+            osg::Vec3f west ( -sIntervalMeters, 0, centerHeight );
+            osg::Vec3f east (  sIntervalMeters, 0, centerHeight );
+            osg::Vec3f south( 0, -tIntervalMeters, centerHeight );
+            osg::Vec3f north( 0,  tIntervalMeters, centerHeight );
+
+            if ( !HeightFieldUtils::getHeightAtNormalizedLocation(hood, nx-xres, ny, west.z()) )
+                west.x() = 0.0;
+
+            if ( !HeightFieldUtils::getHeightAtNormalizedLocation(hood, nx+xres, ny, east.z()) )
+                east.x() = 0.0;
+
+            if ( !HeightFieldUtils::getHeightAtNormalizedLocation(hood, nx, ny-yres, south.z()) )
+                south.y() = 0.0;
+
+            if ( !HeightFieldUtils::getHeightAtNormalizedLocation(hood, nx, ny+yres, north.z()) )
+                north.y() = 0.0;
+
+            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 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);
+        }
+    }
+
+    return image;
+}
+
+/******************************************************************************************/
+#if 0
 ReplaceInvalidDataOperator::ReplaceInvalidDataOperator():
 _replaceWith(0.0f)
 {
@@ -683,3 +743,4 @@ FillNoDataOperator::operator ()(osg::HeightField *heightField)
         }
     }
 }
+#endif
\ No newline at end of file
diff --git a/src/osgEarth/Horizon b/src/osgEarth/Horizon
new file mode 100644
index 0000000..3ff932d
--- /dev/null
+++ b/src/osgEarth/Horizon
@@ -0,0 +1,114 @@
+/* -*-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.
+ *
+ * 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_HORIZON_H
+#define OSGEARTH_HORIZON_H 1
+
+#include <osgEarth/Common>
+#include <osgEarth/SpatialReference>
+#include <osg/NodeCallback>
+#include <osg/Vec3d>
+#include <osg/Shape>
+
+namespace osgEarth
+{
+    /**
+     * Horizon operations (for a geocentric map).
+     */
+    class OSGEARTH_EXPORT Horizon
+    {
+    public:
+        /** Construct a horizon using a default WGS84 ellipsoid model. */
+        Horizon();
+
+        /** Construct a horizon providing the ellipsoid model. */
+        Horizon(const osg::EllipsoidModel& ellipsoid);
+
+        /** Copy */
+        Horizon(const Horizon& rhs);
+
+        virtual ~Horizon() { }
+
+        /**
+         * Ellipsoid model to use for occlusion testing
+         */
+        void setEllipsoid(const osg::EllipsoidModel& ellipsoid);
+
+        /**
+         * Sets the eye position to use when testing for occlusion.
+         */
+        void setEye(const osg::Vec3d& eyeECEF);
+
+        /**
+         * Whether a world point is occluded by the horizon. You can
+         * optionally pass in a radius to test against a sphere.
+         */
+        bool occludes(const osg::Vec3d& pointECEF, double radius =0.0) const;
+
+        /**
+         * Sets the output variable to the horizon plane plane with its
+         * normal pointing at the eye. 
+         */
+        bool getPlane(osg::Plane& out_plane) const;
+        
+    protected:
+
+        osg::Vec3d _cv;
+        double     _vhMag2;
+        osg::Vec3d _scale;
+        osg::Vec3d _scaleInv;
+        osg::Vec3d _scaleToMinHAE;
+    };
+
+
+    /**
+     * Cull callback that culls a node if it is occluded by the
+     * horizon.
+     */
+    class OSGEARTH_EXPORT HorizonCullCallback : public osg::NodeCallback
+    {
+    public:
+        /** Construct the callback with a default Horizon model. */
+        HorizonCullCallback();
+        
+        /** Construct a cull callback with the specified horizon */
+        HorizonCullCallback(const Horizon& horizon);
+
+        /** Enable/disable */
+        void setEnabled(bool value) { _enabled = value; }
+
+        /** The horizon to use for culling. */
+        void setHorizon(const Horizon& horizon) { _horizon = horizon; }
+        const Horizon& getHorizon() const       { return _horizon; }
+
+    public: // osg::NodeCallback
+        void operator()(osg::Node* node, osg::NodeVisitor* nv);
+
+    protected:
+        virtual ~HorizonCullCallback() { }
+
+    private:
+        bool    _enabled;
+        Horizon _horizon;
+    };
+
+
+}
+
+#endif // OSGEARTH_HORIZON_H
diff --git a/src/osgEarth/Horizon.cpp b/src/osgEarth/Horizon.cpp
new file mode 100644
index 0000000..25ebbc5
--- /dev/null
+++ b/src/osgEarth/Horizon.cpp
@@ -0,0 +1,200 @@
+/* -*-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.
+ *
+ * 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/Horizon>
+#include <osg/Transform>
+#include <osgEarth/Registry>
+
+using namespace osgEarth;
+
+Horizon::Horizon()
+{
+    setEllipsoid(osg::EllipsoidModel());
+}
+
+Horizon::Horizon(const osg::EllipsoidModel& e)
+{
+    setEllipsoid( e );
+}
+
+Horizon::Horizon(const Horizon& rhs) :
+_scale        ( rhs._scale ),
+_scaleInv     ( rhs._scaleInv ),
+_cv           ( rhs._cv ),
+_vhMag2       ( rhs._vhMag2 ),
+_scaleToMinHAE( rhs._scaleToMinHAE )
+{
+    //nop
+}
+
+void
+Horizon::setEllipsoid(const osg::EllipsoidModel& e)
+{
+    _scaleInv.set( 
+        e.getRadiusEquator(),
+        e.getRadiusEquator(),
+        e.getRadiusPolar() );
+
+    _scale.set(
+        1.0 / e.getRadiusEquator(),
+        1.0 / e.getRadiusEquator(),
+        1.0 / e.getRadiusPolar() );
+
+    // Minimum allowable HAE for calculating horizon distance.
+    const double minHAE = 100.0;
+
+    //double maxRadius = std::max(e.getRadiusEquator(), e.getRadiusPolar());
+    //double minHAEScaled = 1.0 + minHAE/maxRadius;
+    //_minHAEScaled2 = minHAEScaled * minHAEScaled;
+
+    _scaleToMinHAE = (_scale*minHAE) + osg::Vec3d(1,1,1);
+}
+
+void
+Horizon::setEye(const osg::Vec3d& eyeECEF)
+{
+    _cv = osg::componentMultiply(eyeECEF, _scale);
+
+    double cvMag2 = _cv*_cv;
+
+    osg::Vec3d minCV = _cv;
+    minCV.normalize();
+    minCV = osg::componentMultiply(minCV, _scaleToMinHAE);
+    double min_cvMag2 = minCV*minCV;
+
+#if 0 // debugging
+    osg::Vec3d msl = _cv;
+    msl.normalize();
+    msl = osg::componentMultiply(msl, _scale+osg::Vec3d(1,1,1));
+    msl = osg::componentMultiply(msl, _scaleInv);
+    double alt = eyeECEF.length() - msl.length();
+#endif
+
+    if ( cvMag2 >= min_cvMag2 )
+    {
+        _vhMag2 = cvMag2-1.0;
+    }
+    else
+    {
+        _cv = minCV;
+        _vhMag2 = (_cv*_cv)-1.0;
+    }
+    
+#if 0 // debugging
+    osg::Vec3d vh = _cv;
+    vh.normalize();
+    vh = osg::componentMultiply(vh, (_scale*sqrt(_vhMag2))+osg::Vec3d(1,1,1));
+    vh = _scaleInv * sqrt(_vhMag2);
+
+    static int count=0;
+    if (count++ %60 == 0) {
+        OE_NOTICE << "cvmag2="<< cvMag2 << "; minMag2="<< min_cvMag2 << "; vhMag2=" << _vhMag2 << "; alt=" << alt << "; vh=" << vh.length() << "\n";
+    }
+#endif
+}
+
+bool
+Horizon::occludes(const osg::Vec3d& targetECEF,
+                  double            radius) const
+{
+    // ref: https://cesiumjs.org/2013/04/25/Horizon-culling/
+
+    osg::Vec3d tc = targetECEF;
+
+    if ( radius > 0.0 )
+    {
+        // shift the target point outward to account for its bounding radius.
+        double targetLen2 = tc.length2();
+        osg::Vec3d targetUnit = tc;
+        targetUnit.normalize();
+        tc += targetUnit * radius;
+    }
+    
+    tc = osg::componentMultiply(tc, _scale);
+
+    osg::Vec3d vt = tc - _cv;
+    double vtMag2 = vt.length2();
+    double vtDotVc = -(vt*_cv);
+
+    bool behindHorizonPlane = (vtDotVc > _vhMag2);
+    bool insideHorizonCone  = (vtDotVc*vtDotVc / vtMag2) > _vhMag2;
+
+    return behindHorizonPlane && insideHorizonCone;
+}
+
+bool
+Horizon::getPlane(osg::Plane& out_plane) const
+{
+    // calculate scaled distance from center to viewer:
+    double magVC = _cv.length();
+    if ( magVC == 0.0 )
+        return false;
+
+    // calculate scaled distance from center to horizon plane:
+    double magPC = 1.0/magVC;
+
+    osg::Vec3d normal = _cv;
+    normal.normalize();
+
+    osg::Vec3d pcWorld = osg::componentMultiply(normal*magPC, _scaleInv);
+    double dist = pcWorld.length();
+
+    // compute a new clip plane:
+    out_plane.set(normal, -dist);
+    return true;
+}
+
+//........................................................................
+
+HorizonCullCallback::HorizonCullCallback() :
+_enabled( true )
+{
+    //nop
+}
+
+HorizonCullCallback::HorizonCullCallback(const Horizon& horizon) :
+_horizon( horizon ),
+_enabled( true )
+{
+    //nop
+}
+
+void
+HorizonCullCallback::operator()(osg::Node* node, osg::NodeVisitor* nv)
+{
+    bool visible = true;
+
+    if ( _enabled && node && nv && nv->getVisitorType() == nv->CULL_VISITOR )
+    {
+        osg::Matrix local2world = osg::computeLocalToWorld(nv->getNodePath());
+
+        // make a local copy to support multi-threaded cull
+        Horizon horizon(_horizon);
+        horizon.setEye( osg::Vec3d(nv->getViewPoint()) * local2world );
+
+        const osg::BoundingSphere& bs = node->getBound();
+
+        visible = !horizon.occludes( bs.center() * local2world, bs.radius() );
+    }
+
+    if ( visible )
+    {
+        traverse(node, nv);
+    }
+}
+
diff --git a/src/osgEarth/IOTypes b/src/osgEarth/IOTypes
index 35493b7..7c04caf 100644
--- a/src/osgEarth/IOTypes
+++ b/src/osgEarth/IOTypes
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
@@ -87,7 +90,15 @@ namespace osgEarth
         ReadResult( Code code =RESULT_NOT_FOUND )
             : _code(code), _fromCache(false), _lmt(0) { }
 
-        /** Construct a successful result */
+        /** Construct a result with code and data */
+        ReadResult( Code code, osg::Object* result )
+            : _code(code), _result(result), _fromCache(false), _lmt(0) { }
+
+        /** Construct a result with data, possible with an error code */
+        ReadResult( Code code, osg::Object* result, const Config& meta )
+            : _code(code), _result(result), _meta(meta), _fromCache(false), _lmt(0) { }
+
+        /** Construct a successful result (implicit OK code) */
         ReadResult( osg::Object* result )
             : _code(RESULT_OK), _result(result), _fromCache(false), _lmt(0) { }
 
@@ -95,10 +106,6 @@ namespace osgEarth
         ReadResult( osg::Object* result, const Config& meta )
             : _code(RESULT_OK), _result(result), _meta(meta), _fromCache(false), _lmt(0) { }
 
-        /** Construct a result with data, possible with an error code */
-        ReadResult( Code code, osg::Object* result, const Config& meta )
-            : _code(code), _result(result), _meta(meta), _fromCache(false), _lmt(0) { }
-
         /** Copy construct */
         ReadResult( const ReadResult& rhs )
             : _code(rhs._code), _result(rhs._result.get()), _meta(rhs._meta), _fromCache(rhs._fromCache), _lmt(rhs._lmt) { }
@@ -107,10 +114,7 @@ namespace osgEarth
         virtual ~ReadResult() { }
 
         /** Whether the read operation succeeded */
-        bool succeeded() const
-        {
-            return _code == RESULT_OK && _result.valid();
-        }
+        bool succeeded() const { return _code == RESULT_OK && _result.valid(); }
 
         /** Whether the read operation failed */
         bool failed() const { return _code != RESULT_OK; }
@@ -118,6 +122,9 @@ namespace osgEarth
         /** Whether the result contains an object */
         bool empty() const { return !_result.valid(); }
 
+        /** Detail message, sometimes set upon error */
+        const std::string& errorDetail() const { return _detail; }
+
         /** The result code */
         const Code& code() const { return _code; }
 
@@ -166,7 +173,7 @@ namespace osgEarth
                 code == RESULT_NO_READER       ? "No suitable ReaderWriter found" :
                 code == RESULT_READER_ERROR    ? "ReaderWriter error" :
                 code == RESULT_NOT_IMPLEMENTED ? "Not implemented" :
-                "Unknown error";
+                                                 "Unknown error";
         }
 
         std::string getResultCodeString() const
@@ -181,6 +188,10 @@ namespace osgEarth
 
         void setDuration(double s) { _duration_s = s; }
 
+        void setMetadata(const Config& meta) { _meta = meta; }
+
+        void setErrorDetail(const std::string& value) { _detail = value; }
+
     protected:
         Code                      _code;
         osg::ref_ptr<osg::Object> _result;
@@ -190,6 +201,7 @@ namespace osgEarth
         bool                      _fromCache;
         TimeStamp                 _lmt;
         double                    _duration_s;
+        std::string               _detail;
     };
 
 //--------------------------------------------------------------------
diff --git a/src/osgEarth/IOTypes.cpp b/src/osgEarth/IOTypes.cpp
index 1bbcce9..f1775b7 100644
--- a/src/osgEarth/IOTypes.cpp
+++ b/src/osgEarth/IOTypes.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarth/ImageLayer b/src/osgEarth/ImageLayer
index d553dc4..4f3f842 100644
--- a/src/osgEarth/ImageLayer
+++ b/src/osgEarth/ImageLayer
@@ -1,5 +1,6 @@
+/* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -82,12 +83,6 @@ namespace osgEarth
         const optional<osg::Vec4ub>& transparentColor() const { return _transparentColor; }
         
         /**
-         * Whether LOD blending is enabled for this layer
-         */
-        optional<bool>& lodBlending() { return _lodBlending; }
-        const optional<bool>& lodBlending() const { return _lodBlending; }
-
-        /**
          * Filters attached to this layer.
          */
         ColorFilterChain& colorFilters() { return _colorFilters; }
@@ -101,6 +96,14 @@ namespace osgEarth
         const optional<bool>& shared() const { return _shared; }
 
         /**
+         * Whether this is a "coverage" layer, i.e a layer that conveys discrete semantic
+         * values rather than color data. This is a hint to disable any features that would
+         * corrupt the data, like filtering, interpolation, or lossy compression.
+         */
+        optional<bool>& coverage() { return _coverage; }
+        const optional<bool>& coverage() const { return _coverage; }
+
+        /**
          * Whether to feather out alpha regions for this image layer with the featherAlphaRegions function.
          * Used to get proper blending when you have datasets that abutt exactly with no overlap.
          */
@@ -128,6 +131,14 @@ namespace osgEarth
         optional<osg::Texture::InternalFormatMode>& textureCompression() { return _texcomp; }
         const optional<osg::Texture::InternalFormatMode>& textureCompression() const { return _texcomp; }
 
+        /** For shared layer, name of hte texture sampler uniform. */
+        optional<std::string>& shareTexUniformName() { return _shareTexUniformName; }
+        const optional<std::string>& shareTexUniformName() const { return _shareTexUniformName; }
+
+        /** For shared layer, name of hte texture sampler matrix uniform. */
+        optional<std::string>& shareTexMatUniformName() { return _shareTexMatUniformName; }
+        const optional<std::string>& shareTexMatUniformName() const { return _shareTexMatUniformName; }
+
     public:
 
         virtual Config getConfig() const { return getConfig(false); }
@@ -143,13 +154,15 @@ namespace osgEarth
         optional<float>       _maxRange;
         optional<osg::Vec4ub> _transparentColor;
         optional<URI>         _noDataImageFilename;
-        optional<bool>        _lodBlending;
         ColorFilterChain      _colorFilters;
         optional<bool>        _shared;
+        optional<bool>        _coverage;
         optional<bool>        _featherPixels;
         optional<osg::Texture::FilterMode> _minFilter;
         optional<osg::Texture::FilterMode> _magFilter;
         optional<osg::Texture::InternalFormatMode> _texcomp;
+        optional<std::string> _shareTexUniformName;
+        optional<std::string> _shareTexMatUniformName;
     };
 
     //--------------------------------------------------------------------
@@ -262,23 +275,44 @@ namespace osgEarth
 
         float getOpacity() const { return *_runtimeOptions.opacity(); }
 
-        void disableLODBlending();
-        bool isLODBlendingEnabled() const { return *_runtimeOptions.lodBlending(); }
-
         float getMinVisibleRange() const { return *_runtimeOptions.minVisibleRange();}
         void setMinVisibleRange( float minVisibleRange );
 
         float getMaxVisibleRange() const { return *_runtimeOptions.maxVisibleRange();}
         void setMaxVisibleRange( float maxVisibleRange );
 
-        // whether this layer is marked for render sharing
+        /**
+         * Whether this layer is marked for render sharing
+         */
         bool isShared() const { return *_runtimeOptions.shared() == true; }
 
-        // when isShared() == true, the engine will call this function to bind the
-        // shared layer to a texture image unit.
+        /**
+         * 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; }
+
+        /**
+         * When isShared() == true, the engine will call this function to bind the
+         * shared layer to a texture image unit.
+         */
         optional<int>& shareImageUnit() { return _shareImageUnit; }
         const optional<int>& shareImageUnit() const { return _shareImageUnit; }
 
+        /**
+         * When isShared() == true, this will return the name of the uniform holding the
+         * image's texture. 
+         */
+        optional<std::string>& shareTexUniformName() { return _shareTexUniformName; }
+        const optional<std::string>& shareTexUniformName() const { return _shareTexUniformName; }
+
+        /**
+         * When isShared() == true, this will return the name of the uniform holding the
+         * image's texture matrix. 
+         */
+        optional<std::string>& shareTexMatUniformName() { return _shareTexMatUniformName; }
+        const optional<std::string>& shareTexMatUniformName() const { return _shareTexMatUniformName; }
+
 
     public: // methods
 
@@ -303,7 +337,6 @@ namespace osgEarth
 
         CacheBin* getCacheBin( const Profile* profile );
 
-
     protected:
 
         // Creates an image that's in the same profile as the provided key.
@@ -319,20 +352,22 @@ namespace osgEarth
         GeoImage assembleImageFromTileSource(const TileKey& key, ProgressCallback* progress);
 
 
-        virtual void initTileSource();
-
     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();
-        void initPreCacheOp();
+
+        TileSource::ImageOperation* getOrCreatePreCacheOp();
     };
 
     typedef std::vector< osg::ref_ptr<ImageLayer> > ImageLayerVector;
diff --git a/src/osgEarth/ImageLayer.cpp b/src/osgEarth/ImageLayer.cpp
index 8e8d378..0533276 100644
--- a/src/osgEarth/ImageLayer.cpp
+++ b/src/osgEarth/ImageLayer.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -63,13 +63,14 @@ ImageLayerOptions::setDefaults()
 {
     _opacity.init( 1.0f );
     _transparentColor.init( osg::Vec4ub(0,0,0,0) );
-    _minRange.init( -FLT_MAX );
+    _minRange.init( 0.0 );
     _maxRange.init( FLT_MAX );
-    _lodBlending.init( false );
     _featherPixels.init( false );
     _minFilter.init( osg::Texture::LINEAR_MIPMAP_LINEAR );
     _magFilter.init( osg::Texture::LINEAR );
     _texcomp.init( osg::Texture::USE_IMAGE_DATA_FORMAT ); // none
+    _shared.init( false );
+    _coverage.init( false );
 }
 
 void
@@ -86,8 +87,8 @@ ImageLayerOptions::fromConfig( const Config& conf )
     conf.getIfSet( "opacity",        _opacity );
     conf.getIfSet( "min_range",      _minRange );
     conf.getIfSet( "max_range",      _maxRange );
-    conf.getIfSet( "lod_blending",   _lodBlending );
     conf.getIfSet( "shared",         _shared );
+    conf.getIfSet( "coverage",       _coverage );
     conf.getIfSet( "feather_pixels", _featherPixels);
 
     if ( conf.hasValue( "transparent_color" ) )
@@ -114,7 +115,12 @@ ImageLayerOptions::fromConfig( const Config& conf )
 
     conf.getIfSet("texture_compression", "none", _texcomp, osg::Texture::USE_IMAGE_DATA_FORMAT);
     conf.getIfSet("texture_compression", "auto", _texcomp, (osg::Texture::InternalFormatMode)~0);
+    conf.getIfSet("texture_compression", "fastdxt", _texcomp, (osg::Texture::InternalFormatMode)(~0 - 1));
     //TODO add all the enums
+
+    // uniform names
+    conf.getIfSet("shared_sampler", _shareTexUniformName);
+    conf.getIfSet("shared_matrix",  _shareTexMatUniformName);
 }
 
 Config
@@ -126,8 +132,8 @@ ImageLayerOptions::getConfig( bool isolate ) const
     conf.updateIfSet( "opacity",        _opacity );
     conf.updateIfSet( "min_range",      _minRange );
     conf.updateIfSet( "max_range",      _maxRange );
-    conf.updateIfSet( "lod_blending",   _lodBlending );
     conf.updateIfSet( "shared",         _shared );
+    conf.updateIfSet( "coverage",       _coverage );
     conf.updateIfSet( "feather_pixels", _featherPixels );
 
     if (_transparentColor.isSet())
@@ -138,7 +144,8 @@ ImageLayerOptions::getConfig( bool isolate ) const
         Config filtersConf("color_filters");
         if ( ColorFilterRegistry::instance()->writeChain( _colorFilters, filtersConf ) )
         {
-            conf.add( filtersConf );
+            conf.update( filtersConf );
+            //conf.add( filtersConf );
         }
     }
 
@@ -158,8 +165,13 @@ ImageLayerOptions::getConfig( bool isolate ) const
     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));
     //TODO add all the enums
 
+    // uniform names
+    conf.updateIfSet("shared_sampler", _shareTexUniformName);
+    conf.updateIfSet("shared_matrix",  _shareTexMatUniformName);
+
     return conf;
 }
 
@@ -296,6 +308,12 @@ ImageLayer::init()
 
     _emptyImage = ImageUtils::createEmptyImage();
     //*((unsigned*)_emptyImage->data()) = 0x7F0000FF;
+
+    if ( _runtimeOptions.shareTexUniformName().isSet() )
+        _shareTexUniformName = _runtimeOptions.shareTexUniformName().get();
+
+    if ( _runtimeOptions.shareTexMatUniformName().isSet() )
+        _shareTexMatUniformName = _runtimeOptions.shareTexMatUniformName().get();
 }
 
 void
@@ -378,44 +396,35 @@ ImageLayer::getColorFilters() const
     return _runtimeOptions.colorFilters();
 }
 
-void 
-ImageLayer::disableLODBlending()
-{
-    _runtimeOptions.lodBlending() = false;
-}
-
 void
 ImageLayer::setTargetProfileHint( const Profile* profile )
 {
     TerrainLayer::setTargetProfileHint( profile );
 
     // if we've already constructed the pre-cache operation, reinitialize it.
-    if ( _preCacheOp.valid() )
-        initPreCacheOp();
-}
-
-void
-ImageLayer::initTileSource()
-{
-    // call superclass first.
-    TerrainLayer::initTileSource();
-
-    // install the pre-caching image processor operation.
-    initPreCacheOp();
+    _preCacheOp = 0L;
 }
 
-void
-ImageLayer::initPreCacheOp()
+TileSource::ImageOperation*
+ImageLayer::getOrCreatePreCacheOp()
 {
-    bool layerInTargetProfile = 
-        _targetProfileHint.valid() &&
-        getProfile()               &&
-        _targetProfileHint->isEquivalentTo( getProfile() );
+    if ( !_preCacheOp.valid() )
+    {
+        Threading::ScopedMutexLock lock(_mutex);
+        if ( !_preCacheOp.valid() )
+        {
+            bool layerInTargetProfile = 
+                _targetProfileHint.valid() &&
+                getProfile()               &&
+                _targetProfileHint->isEquivalentTo( getProfile() );
 
-    ImageLayerPreCacheOperation* op = new ImageLayerPreCacheOperation();
-    op->_processor.init( _runtimeOptions, _dbOptions.get(), layerInTargetProfile );
+            ImageLayerPreCacheOperation* op = new ImageLayerPreCacheOperation();
+            op->_processor.init( _runtimeOptions, _dbOptions.get(), layerInTargetProfile );
 
-    _preCacheOp = op;
+            _preCacheOp = op;
+        }
+    }
+    return _preCacheOp.get();
 }
 
 
@@ -561,7 +570,7 @@ ImageLayer::createImageInKeyProfile(const TileKey&    key,
         if ( r.succeeded() )
         {
             cachedImage = r.releaseImage();
-            ImageUtils::normalizeImage( cachedImage.get() );            
+            ImageUtils::fixInternalFormat( cachedImage.get() );            
             bool expired = getCachePolicy().isExpired(r.lastModifiedTime());
             if (!expired)
             {
@@ -595,7 +604,7 @@ ImageLayer::createImageInKeyProfile(const TileKey&    key,
     // Normalize the image if necessary
     if ( result.valid() )
     {
-        ImageUtils::normalizeImage( result.getImage() );
+        ImageUtils::fixInternalFormat( result.getImage() );
     }
 
     // memory cache first:
@@ -654,7 +663,7 @@ ImageLayer::createImageFromTileSource(const TileKey&    key,
     }
 
     // Good to go, ask the tile source for an image:
-    osg::ref_ptr<TileSource::ImageOperation> op = _preCacheOp;
+    osg::ref_ptr<TileSource::ImageOperation> op = getOrCreatePreCacheOp();
 
     // Fail is the image is blacklisted.
     if ( source->getBlacklist()->contains(key) )
@@ -670,7 +679,7 @@ ImageLayer::createImageFromTileSource(const TileKey&    key,
     }
 
     // create an image from the tile source.
-    osg::ref_ptr<osg::Image> result = source->createImage( key, op.get(), progress );
+    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())
@@ -678,11 +687,16 @@ ImageLayer::createImageFromTileSource(const TileKey&    key,
         ImageUtils::featherAlphaRegions( result.get() );
     }    
     
-    // If image creation failed (but was not intentionally canceled),
+    // If image creation failed (but was not intentionally canceled and 
+    // didn't time out or end for any other recoverable reason), then
     // blacklist this tile for future requests.
-    if ( result == 0L && (!progress || !progress->isCanceled()) )
+    if (result == 0L)
     {
-        source->getBlacklist()->add( key );
+        if ( progress == 0L ||
+             ( !progress->isCanceled() && !progress->needsRetry() ) )
+        {
+            source->getBlacklist()->add( key );
+        }
     }
 
     return GeoImage(result.get(), key.getExtent());
@@ -717,30 +731,34 @@ ImageLayer::assembleImageFromTileSource(const TileKey&    key,
         bool retry = false;
         ImageMosaic mosaic;
 
+        // keep track of failed tiles.
+        std::vector<TileKey> failedKeys;
+
         for( std::vector<TileKey>::iterator k = intersectingKeys.begin(); k != intersectingKeys.end(); ++k )
         {
-            double minX, minY, maxX, maxY;
-            k->getExtent().getBounds(minX, minY, maxX, maxY);
-
             GeoImage image = createImageFromTileSource( *k, progress );
+
             if ( image.valid() )
             {
-                ImageUtils::normalizeImage(image.getImage());
-
-                // Make sure all images in mosaic are based on "RGBA - unsigned byte" pixels.
-                // This is not the smarter choice (in some case RGB would be sufficient) but
-                // it ensure consistency between all images / layers.
-                //
-                // The main drawback is probably the CPU memory foot-print which would be reduced by allocating RGB instead of RGBA images.
-                // On GPU side, this should not change anything because of data alignements : often RGB and RGBA textures have the same memory footprint
-                //
-                if (   (image.getImage()->getDataType() != GL_UNSIGNED_BYTE)
-                    || (image.getImage()->getPixelFormat() != GL_RGBA) )
+                if ( !isCoverage() )
                 {
-                    osg::ref_ptr<osg::Image> convertedImg = ImageUtils::convertToRGBA8(image.getImage());
-                    if (convertedImg.valid())
+                    ImageUtils::fixInternalFormat(image.getImage());
+
+                    // Make sure all images in mosaic are based on "RGBA - unsigned byte" pixels.
+                    // This is not the smarter choice (in some case RGB would be sufficient) but
+                    // it ensure consistency between all images / layers.
+                    //
+                    // The main drawback is probably the CPU memory foot-print which would be reduced by allocating RGB instead of RGBA images.
+                    // On GPU side, this should not change anything because of data alignements : often RGB and RGBA textures have the same memory footprint
+                    //
+                    if (   (image.getImage()->getDataType() != GL_UNSIGNED_BYTE)
+                        || (image.getImage()->getPixelFormat() != GL_RGBA) )
                     {
-                        image = GeoImage(convertedImg, image.getExtent());
+                        osg::ref_ptr<osg::Image> convertedImg = ImageUtils::convertToRGBA8(image.getImage());
+                        if (convertedImg.valid())
+                        {
+                            image = GeoImage(convertedImg, image.getExtent());
+                        }
                     }
                 }
 
@@ -749,6 +767,8 @@ ImageLayer::assembleImageFromTileSource(const TileKey&    key,
             else
             {
                 // the tile source did not return a tile, so make a note of it.
+                failedKeys.push_back( *k );
+
                 if (progress && (progress->isCanceled() || progress->needsRetry()))
                 {
                     retry = true;
@@ -764,6 +784,58 @@ ImageLayer::assembleImageFromTileSource(const TileKey&    key,
             return GeoImage::INVALID;
         }
 
+        // We got at least one good tile, so go through the bad ones and try to fall back on
+        // lower resolution data to fill in the gaps. The entire mosaic must be populated or
+        // this qualifies as a bad tile.
+        for(std::vector<TileKey>::iterator k = failedKeys.begin(); k != failedKeys.end(); ++k)
+        {
+            GeoImage image;
+
+            for(TileKey parentKey = k->createParentKey();
+                parentKey.valid() && !image.valid();
+                parentKey = parentKey.createParentKey())
+            {
+                image = createImageFromTileSource( parentKey, progress );
+                if ( image.valid() )
+                {
+                    GeoImage cropped;
+
+                    if ( !isCoverage() )
+                    {
+                        ImageUtils::fixInternalFormat(image.getImage());
+                        if (   (image.getImage()->getDataType() != GL_UNSIGNED_BYTE)
+                            || (image.getImage()->getPixelFormat() != GL_RGBA) )
+                        {
+                            osg::ref_ptr<osg::Image> convertedImg = ImageUtils::convertToRGBA8(image.getImage());
+                            if (convertedImg.valid())
+                            {
+                                image = GeoImage(convertedImg, image.getExtent());
+                            }
+                        }
+
+                        cropped = image.crop( k->getExtent(), false, image.getImage()->s(), image.getImage()->t() );
+                    }
+
+                    else
+                    {
+                        // TODO: may not work.... test; tilekey extent will <> cropped extent
+                        cropped = image.crop( k->getExtent(), true, image.getImage()->s(), image.getImage()->t(), false );
+                    }
+
+                    // and queue it.
+                    mosaic.getImages().push_back( TileImage(cropped.getImage(), *k) );       
+
+                }
+            }
+
+            if ( !image.valid() )
+            {
+                // a tile completely failed, even with fallback. Eject.
+                OE_DEBUG << LC << "Couldn't fallback on tiles for ImageMosaic" << std::endl;
+                return GeoImage::INVALID;
+            }
+        }
+
         // all set. Mosaic all the images together.
         double rxmin, rymin, rxmax, rymax;
         mosaic.getExtents( rxmin, rymin, rxmax, rymax );
@@ -790,11 +862,11 @@ ImageLayer::assembleImageFromTileSource(const TileKey&    key,
             &key.getExtent(), 
             *_runtimeOptions.reprojectedTileSize(),
             *_runtimeOptions.reprojectedTileSize(),
-            *_runtimeOptions.driver()->bilinearReprojection());
+            *_runtimeOptions.driver()->bilinearReprojection() );
     }
 
     // Process images with full alpha to properly support MP blending.
-    if ( result.valid() && *_runtimeOptions.featherPixels() )
+    if ( result.valid() && *_runtimeOptions.featherPixels() && !isCoverage() )
     {
         ImageUtils::featherAlphaRegions( result.getImage() );
     }
@@ -809,7 +881,14 @@ ImageLayer::applyTextureCompressionMode(osg::Texture* tex) const
     if ( tex == 0L )
         return;
 
-    if ( _runtimeOptions.textureCompression() == (osg::Texture::InternalFormatMode)~0 )
+    // Coverages are not allowed to use compression since it will corrupt the data
+    if ( isCoverage() )
+    {
+        tex->setInternalFormatMode(osg::Texture::USE_IMAGE_DATA_FORMAT);
+    }
+
+
+    else if ( _runtimeOptions.textureCompression() == (osg::Texture::InternalFormatMode)~0 )
     {
         // auto mode:
         if ( Registry::capabilities().isGLES() )
@@ -830,7 +909,42 @@ ImageLayer::applyTextureCompressionMode(osg::Texture* tex) const
             }
         }
     }
+    else if ( _runtimeOptions.textureCompression() == (osg::Texture::InternalFormatMode)(~0 - 1))
+    {
+        osg::Timer_t start = osg::Timer::instance()->tick();
+        osgDB::ImageProcessor* imageProcessor = osgDB::Registry::instance()->getImageProcessorForExtension("fastdxt");
+        if (imageProcessor)
+        {
+            osg::Texture::InternalFormatMode mode;
+            // RGB uses DXT1
+            if (tex->getImage(0)->getPixelFormat() == GL_RGB)
+            {
+                mode = osg::Texture::USE_S3TC_DXT1_COMPRESSION;
+            }
+            // RGBA uses DXT5
+            else if (tex->getImage(0)->getPixelFormat() == GL_RGBA)
+            {         
+                mode = osg::Texture::USE_S3TC_DXT5_COMPRESSION;
+            }
+            else
+            {
+                OE_INFO << "FastDXT only works on GL_RGBA or GL_RGB images" << std::endl;
+                return;
+            }
 
+            osg::Image *image = tex->getImage(0);
+            imageProcessor->compress(*image, mode, false, true, osgDB::ImageProcessor::USE_CPU, osgDB::ImageProcessor::FASTEST);
+            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;        
+        }
+        else
+        {
+            OE_WARN << "Failed to get ImageProcessor fastdxt" << std::endl;
+        }
+
+    }
     else if ( _runtimeOptions.textureCompression().isSet() )
     {
         // use specifically picked a mode.
diff --git a/src/osgEarth/ImageMosaic b/src/osgEarth/ImageMosaic
index fd866f9..13c5647 100644
--- a/src/osgEarth/ImageMosaic
+++ b/src/osgEarth/ImageMosaic
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarth/ImageMosaic.cpp b/src/osgEarth/ImageMosaic.cpp
index 6684a3b..392fb48 100644
--- a/src/osgEarth/ImageMosaic.cpp
+++ b/src/osgEarth/ImageMosaic.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -102,10 +102,11 @@ ImageMosaic::createImage()
 
     osg::ref_ptr<osg::Image> image = new osg::Image;
     image->allocateImage(pixelsWide, pixelsHigh, 1, _images[0]._image->getPixelFormat(), _images[0]._image->getDataType());
-    image->setInternalTextureFormat(_images[0]._image->getInternalTextureFormat()); 
+    image->setInternalTextureFormat(_images[0]._image->getInternalTextureFormat());
+    ImageUtils::markAsNormalized(image.get(), ImageUtils::isNormalized(_images[0].getImage()));
 
-    //Initialize the image to be completely transparent/black
-    //memset(image->data(), 0, image->getImageSizeInBytes());
+    //Initialize the image to be completely white!
+    memset(image->data(), 0xFF, image->getImageSizeInBytes());
 
     //Composite the incoming images into the master image
     for (TileImageList::iterator i = _images.begin(); i != _images.end(); ++i)
diff --git a/src/osgEarth/ImageToHeightFieldConverter b/src/osgEarth/ImageToHeightFieldConverter
index df73fa3..b53fc3f 100644
--- a/src/osgEarth/ImageToHeightFieldConverter
+++ b/src/osgEarth/ImageToHeightFieldConverter
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/osgEarth/ImageToHeightFieldConverter.cpp b/src/osgEarth/ImageToHeightFieldConverter.cpp
index 5df921a..c6fe55b 100644
--- a/src/osgEarth/ImageToHeightFieldConverter.cpp
+++ b/src/osgEarth/ImageToHeightFieldConverter.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,16 +8,20 @@
 * 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.
+* 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/ImageToHeightFieldConverter>
+#include <osgEarth/GeoCommon>
 #include <osg/Notify>
 #include <limits.h>
 #include <string.h>
@@ -27,13 +31,13 @@ using namespace osgEarth;
 static bool
 isNoData( short s )
 {
-  return s == SHRT_MAX || s == SHRT_MIN;
+  return s == SHRT_MAX || s == -SHRT_MAX;
 }
 
 static bool
 isNoData( float f )
 {
-  return f == FLT_MAX || f == FLT_MIN;
+  return f == FLT_MAX || f == -FLT_MAX;
 }
 
 
@@ -111,7 +115,14 @@ osg::HeightField* ImageToHeightFieldConverter::convert16(const osg::Image* image
   osg::FloatArray* floats = hf->getFloatArray();
 
   for( unsigned int i = 0; i < floats->size(); ++i ) {
-    floats->at( i ) = *(short*)image->data(i);
+      short v = *(short*)image->data(i);
+      float h = (float)v;
+      // Replace short nodata values with our float marker.
+      if (v == -SHRT_MAX || v == SHRT_MAX)
+      {
+          h = NO_DATA_VALUE;
+      }
+      floats->at( i ) = h;
   }
 
   return hf;
@@ -170,7 +181,13 @@ osg::Image* ImageToHeightFieldConverter::convert16(const osg::HeightField* hf )
   const osg::FloatArray* floats = hf->getFloatArray();
 
   for( unsigned int i = 0; i < floats->size(); ++i  ) {
-    *(short*)image->data(i) = (short)floats->at( i );
+      float h = floats->at( i );
+      // 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;
@@ -182,8 +199,7 @@ osg::Image* ImageToHeightFieldConverter::convert32(const osg::HeightField* hf) c
   }
 
   osg::Image* image = new osg::Image();
-  image->allocateImage(hf->getNumColumns(), hf->getNumRows(), 1, GL_LUMINANCE, GL_FLOAT);
-
+  image->allocateImage(hf->getNumColumns(), hf->getNumRows(), 1, GL_LUMINANCE, GL_FLOAT );
   memcpy( image->data(), &hf->getFloatArray()->front(), sizeof(float) * hf->getFloatArray()->size() );
 
   return image;
diff --git a/src/osgEarth/ImageUtils b/src/osgEarth/ImageUtils
index 603a0c7..fcb6138 100644
--- a/src/osgEarth/ImageUtils
+++ b/src/osgEarth/ImageUtils
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -62,7 +62,30 @@ namespace osgEarth
          * array compositor) rely on the internal texture format being correct.
          * (http://http.download.nvidia.com/developer/Papers/2005/Fast_Texture_Transfers/Fast_Texture_Transfers.pdf)
          */
-        static void normalizeImage( osg::Image* image );
+        static void fixInternalFormat(osg::Image* image);
+
+        /**
+         * Marks an image as containing un-normalized data values.
+         *
+         * Normally the values in an image are "normalized", i.e. scaled so they are in the
+         * range [0..1]. This is normal for color values. But when the image is being used
+         * for coverage data (a value lookup table) it is desireable to store the raw 
+         * values instead.
+         */
+        static void markAsUnNormalized(osg::Image* image, bool value);
+
+        /** Inverse of above. */
+        static void markAsNormalized(osg::Image* image, bool value) { markAsUnNormalized(image, !value); }
+
+        /**
+         * Whether the image has been marked as containing un-normalized values.
+         */
+        static bool isUnNormalized(const osg::Image* image);
+
+        /**
+         * Whether the image has been marked as containing normalized values.
+         */
+        static bool isNormalized(const osg::Image* image) { return !isUnNormalized(image); }
 
         /**
          * Copys a portion of one image into another.
@@ -73,8 +96,7 @@ namespace osgEarth
             int dst_start_col, int dst_start_row);
 
         /**
-         * Resizes an image using nearest-neighbor resampling. Returns a new image, leaving
-         * the input image unaltered.
+         * Resizes an image. Returns a new image, leaving the input image unaltered.
          *
          * Note. If the output parameter is NULL, this method will allocate a new image and
          * resize into that new image. If the output parameter is non-NULL, this method will
@@ -89,7 +111,7 @@ namespace osgEarth
             const osg::Image* input, 
             unsigned int new_s, unsigned int new_t,
             osg::ref_ptr<osg::Image>& output,
-            unsigned int mipmapLevel =0, bool bilinear=false );
+            unsigned int mipmapLevel =0, bool bilinear=true );
 
         /**
          * Crops the input image to the dimensions provided and returns a
@@ -129,6 +151,13 @@ namespace osgEarth
         static osg::Image* createMipmapBlendedImage(
             const osg::Image* primary,
             const osg::Image* secondary );
+        
+        /**
+         * Creates a new image containing mipmaps built with nearest-neighbor
+         * sampling.
+         */
+        static osg::Image* buildNearestNeighborMipmaps(
+            const osg::Image* image);
 
         /**
          * Blends the "src" image into the "dest" image, based on the "a" value.
@@ -210,6 +239,12 @@ namespace osgEarth
         static bool sameFormat(const osg::Image* lhs, const osg::Image* rhs);
 
         /**
+         * True if the two images have the same format AND size, and can therefore
+         * be used together in a texture array.
+         */
+        static bool textureArrayCompatible(const osg::Image* lhs, const osg::Image* rhs);
+
+        /**
          *Compares the image data of two images and determines if they are equivalent
          */
         static bool areEquivalent(const osg::Image *lhs, const osg::Image *rhs);
@@ -276,11 +311,20 @@ namespace osgEarth
             osg::Texture::InternalFormatMode& out_mode);
 
         /**
+         * 
+         */
+        static osg::Image* upSampleNN(const osg::Image* src, int quadrant);
+
+        /**
          * Reads color data out of an image, regardles of its internal pixel format.
          */
         class OSGEARTH_EXPORT PixelReader
         {
         public:
+            /**
+             * Constructs a pixel reader. "Normalized" means that the values in the source
+             * image have been scaled to [0..1] and should be denormalized upon reading.
+             */
             PixelReader(const osg::Image* image);
 
             /** Whether PixelReader supports a given format/datatype combiniation. */
@@ -306,8 +350,7 @@ namespace osgEarth
                 return (*_reader)(this,
                     (int)(s * (float)(_image->s()-1)),
                     (int)(t * (float)(_image->t()-1)),
-                    (int)(r * (float)(_image->r()-1)),
-                    m);
+                    r, m);
             }
 
             // internals:
@@ -323,6 +366,7 @@ namespace osgEarth
             unsigned _colMult;
             unsigned _rowMult;
             unsigned _imageSize;
+            bool     _normalized;
         };
         
         /**
@@ -331,6 +375,10 @@ namespace osgEarth
         class OSGEARTH_EXPORT PixelWriter
         {
         public:
+            /**
+             * Constructs a pixel writer. "Normalized" means the values are scaled to [0..1]
+             * before writing.
+             */
             PixelWriter(osg::Image* image);
 
             /** Whether PixelWriter can write to an image with the given format/datatype combo. */
@@ -346,11 +394,19 @@ namespace osgEarth
                 (*_writer)(this, c, s, t, r, m );
             }
 
+            void f(const osg::Vec4& c, float s, float t, int r=0, int m=0) {
+                this->operator()( c,
+                    (int)(s * (float)(_image->s()-1)),
+                    (int)(t * (float)(_image->t()-1)),
+                    r, m);
+            }
+
             // internals:
             osg::Image* _image;
             unsigned _colMult;
             unsigned _rowMult;
             unsigned _imageSize;
+            bool     _normalized;
 
             unsigned char* data(int s=0, int t=0, int r=0, int m=0) const {
                 return m == 0 ?
diff --git a/src/osgEarth/ImageUtils.cpp b/src/osgEarth/ImageUtils.cpp
index 786f31b..40970e6 100644
--- a/src/osgEarth/ImageUtils.cpp
+++ b/src/osgEarth/ImageUtils.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -21,10 +21,12 @@
 #include <osgEarth/ThreadingUtils>
 #include <osgEarth/Registry>
 #include <osgEarth/Capabilities>
+#include <osgEarth/Random>
 #include <osg/Notify>
 #include <osg/Texture>
 #include <osg/ImageSequence>
 #include <osg/Timer>
+#include <osg/ValueObject>
 #include <osgDB/Registry>
 #include <string.h>
 #include <memory.h>
@@ -43,6 +45,7 @@
 
 using namespace osgEarth;
 
+
 osg::Image*
 ImageUtils::cloneImage( const osg::Image* input )
 {
@@ -55,11 +58,14 @@ ImageUtils::cloneImage( const osg::Image* input )
     
     osg::Image* clone = osg::clone( input, osg::CopyOp::DEEP_COPY_ALL );
     clone->dirty();
+    if (isNormalized(input) != isNormalized(clone)) {
+        OE_WARN << LC << "Fail in clone.\n";
+    }
     return clone;
 }
 
 void
-ImageUtils::normalizeImage( osg::Image* image )
+ImageUtils::fixInternalFormat( osg::Image* image )
 {
     // OpenGL is lax about internal texture formats, and e.g. allows GL_RGBA to be used
     // instead of the proper GL_RGBA8, etc. Correct that here, since some of our compositors
@@ -73,6 +79,23 @@ ImageUtils::normalizeImage( osg::Image* image )
     }
 }
 
+void
+ImageUtils::markAsUnNormalized(osg::Image* image, bool value)
+{
+    if ( image )
+    {
+        image->setUserValue("osgEarth.unnormalized", value);
+    }
+}
+
+bool
+ImageUtils::isUnNormalized(const osg::Image* image)
+{
+    if ( !image ) return false;
+    bool result;
+    return image->getUserValue("osgEarth.unnormalized", result) && (result == true);
+}
+
 bool
 ImageUtils::copyAsSubImage(const osg::Image* src, osg::Image* dst, int dst_start_col, int dst_start_row)
 {
@@ -178,8 +201,8 @@ ImageUtils::createBumpMap(const osg::Image* input)
 }
 
 bool
-ImageUtils::resizeImage(const osg::Image* input, 
-                        unsigned int out_s, unsigned int out_t, 
+ImageUtils::resizeImage(const osg::Image* input,
+                        unsigned int out_s, unsigned int out_t,
                         osg::ref_ptr<osg::Image>& output,
                         unsigned int mipmapLevel,
                         bool bilinear)
@@ -210,10 +233,11 @@ 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));
         }
         else
         {
-            // for unsupported write formats, convert to RGBA8 automatically.
+            // for unsupported write formats, convert to normalized RGBA8 automatically.
             output->allocateImage( out_s, out_t, input->r(), GL_RGBA, GL_UNSIGNED_BYTE );
             output->setInternalTextureFormat( GL_RGB8A_INTERNAL );
         }
@@ -298,7 +322,19 @@ ImageUtils::resizeImage(const osg::Image* input,
                     }
                     else
                     {
-                        color = read( (int)input_col, (int)input_row, layer ); // read pixel from mip level 0
+                        // nearest neighbor:
+                        int col = (input_col-(int)input_col) <= (ceil(input_col)-input_col) ?
+                            (int)input_col :
+                            std::min( 1+(int)input_col, (int)in_s-1 );
+
+                        int row = (input_row-(int)input_row) <= (ceil(input_row)-input_row) ?
+                            (int)input_row :
+                            std::min( 1+(int)input_row, (int)in_t-1 );
+
+                        color = read(col, row, layer); // read pixel from mip level 0.
+
+                        // old code
+                        //color = read( (int)input_col, (int)input_row, layer ); // read pixel from mip level 0
                     }
 
                     write( color, output_col, output_row, layer, mipmapLevel ); // write to target mip level
@@ -328,6 +364,7 @@ ImageUtils::flattenImage(osg::Image*                             input,
         osg::Image* layer = new osg::Image();
         layer->allocateImage(input->s(), input->t(), 1, input->getPixelFormat(), input->getDataType(), input->getPacking());
         layer->setPixelAspectRatio(input->getPixelAspectRatio());
+        markAsNormalized(layer, isNormalized(input));
 
 #if OSG_MIN_VERSION_REQUIRED(3,1,0)
         layer->setRowLength(input->getRowLength());
@@ -344,6 +381,59 @@ ImageUtils::flattenImage(osg::Image*                             input,
 }
 
 osg::Image*
+ImageUtils::buildNearestNeighborMipmaps(const osg::Image* input)
+{
+    // first, build the image that will hold all the mipmap levels.
+    int numMipmapLevels = osg::Image::computeNumberOfMipmapLevels( input->s(), input->t() );
+    int pixelSizeBytes  = osg::Image::computeRowWidthInBytes( input->s(), input->getPixelFormat(), input->getDataType(), input->getPacking() ) / input->s();
+    int totalSizeBytes  = 0;
+    std::vector< unsigned int > mipmapDataOffsets;
+
+    mipmapDataOffsets.reserve( numMipmapLevels-1 );
+
+    for( int i=0; i<numMipmapLevels; ++i )
+    {
+        if ( i > 0 )
+            mipmapDataOffsets.push_back( totalSizeBytes );
+
+        int level_s = input->s() >> i;
+        int level_t = input->t() >> i;
+        int levelSizeBytes = level_s * level_t * pixelSizeBytes;
+
+        totalSizeBytes += levelSizeBytes;
+    }
+
+    unsigned char* data = new unsigned char[totalSizeBytes];
+
+    osg::ref_ptr<osg::Image> result = new osg::Image();
+    result->setImage(
+        input->s(), input->t(), 1,
+        input->getInternalTextureFormat(), 
+        input->getPixelFormat(), 
+        input->getDataType(), 
+        data, osg::Image::USE_NEW_DELETE );
+
+    result->setMipmapLevels( mipmapDataOffsets );
+
+    // now, populate the image levels.
+    int level_s = input->s();
+    int level_t = input->t();
+
+    osg::ref_ptr<const osg::Image> input2 = 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);
+        level_s >>= 1;
+        level_t >>= 1;
+        input2 = temp.get();
+    }
+
+    return result.release();
+}
+
+osg::Image*
 ImageUtils::createMipmapBlendedImage( const osg::Image* primary, const osg::Image* secondary )
 {
     // ASSUMPTION: primary and secondary are the same size, same format.
@@ -431,8 +521,8 @@ ImageUtils::mix(osg::Image* dest, const osg::Image* src, float a)
     
     PixelVisitor<MixImage> mixer;
     mixer._a = osg::clampBetween( a, 0.0f, 1.0f );
-    mixer._srcHasAlpha = src->getPixelSizeInBits() == 32;
-    mixer._destHasAlpha = src->getPixelSizeInBits() == 32;
+    mixer._srcHasAlpha = hasAlphaChannel(src); //src->getPixelSizeInBits() == 32;
+    mixer._destHasAlpha = hasAlphaChannel(dest); //dest->getPixelSizeInBits() == 32;
 
     mixer.accept( src, dest );  
 
@@ -483,7 +573,7 @@ ImageUtils::cropImage(const osg::Image* image,
     osg::Image* cropped = new osg::Image;
     cropped->allocateImage(windowWidth, windowHeight, image->r(), image->getPixelFormat(), image->getDataType());
     cropped->setInternalTextureFormat( image->getInternalTextureFormat() );
-    
+    ImageUtils::markAsNormalized( cropped, ImageUtils::isNormalized(image) );    
     
     for (int layer=0; layer<image->r(); ++layer)
     {
@@ -606,6 +696,119 @@ ImageUtils::createOnePixelImage(const osg::Vec4& color)
     return image;
 }
 
+osg::Image*
+ImageUtils::upSampleNN(const osg::Image* src, int quadrant)
+{
+    int soff = quadrant == 0 || quadrant == 2 ? 0 : src->s()/2;
+    int toff = quadrant == 2 || quadrant == 3 ? 0 : src->t()/2;
+    osg::Image* dst = new osg::Image();
+    dst->allocateImage(src->s(), src->t(), 1, src->getPixelFormat(), src->getDataType(), src->getPacking());
+
+    PixelReader readSrc(src);
+    PixelWriter writeDst(dst);
+
+    // first, copy the quadrant into the new image at every other pixel (s and t).
+    for(int s=0; s<src->s()/2; ++s)
+    {
+        for(int t=0; t<src->t()/2; ++t)
+        {           
+            writeDst(readSrc(soff+s,toff+t), 2*s, 2*t);
+        }
+    }
+
+    // next fill in the rows - simply copy the pixel from the left.
+    PixelReader readDst(dst);
+    int seed = *(int*)dst->data(0,0);
+
+    Random rng(seed+quadrant);
+    int c = 0;
+
+    for(int t=0; t<dst->t(); t+=2)
+    {
+        for(int s=1; s<dst->s(); s+=2)
+        {
+            int ss = rng.next(2)%2 && s<dst->s()-1 ? s+1 : s-1;
+            writeDst( readDst(ss,t), s, t );
+        }
+    }
+
+    // fill in the columns - copy the pixel above.
+    for(int t=1; t<dst->t(); t+=2)
+    {
+        for(int s=0; s<dst->s(); s+=2)
+        {
+            int tt = rng.next(2)%2 && t<dst->t()-1 ? t+1 : t-1;
+            writeDst( readDst(s,tt), s, t );
+        }
+    }
+
+    // fill in the LRs.
+    for(int t=1; t<dst->t(); t+=2)
+    {
+        bool last_t = t+2 >= dst->t();
+        for(int s=1; s<dst->s(); s+=2)
+        {
+            bool last_s = s+2 >= dst->s();
+
+            if (!last_s && !last_t)
+            {
+                bool d1 = readDst(s-1,t-1)==readDst(s+1,t+1);
+                bool d2 = readDst(s-1,t+1)==readDst(s+1,t-1);
+
+                if (d1 && !d2)
+                {
+                    writeDst( readDst(s-1,t-1), s, t);
+                }
+                else if (!d1 && d2)
+                {
+                    writeDst( readDst(s+1,t-1), s, t);
+                }
+                else if (d1 && d2)
+                {
+                    writeDst( readDst(s-1,t-1), s, t);
+                }
+                else
+                {
+                    int ss = rng.next(2)%2 ? s+1 : s-1, tt = rng.next(2)%2 ? t+1 : t-1;
+                    //int ss = (c++)%2? s+1, s-1, tt = (c++)%2? t+1 : t-1;
+                    writeDst( readDst(ss, tt), s, t );
+                }
+
+            }
+            else if ( last_s && !last_t )
+            {
+                writeDst( readDst(s,t-1), s, t );
+                //if ( readDst(s, t-1) == readDst(s, t+1) )
+                //{
+                //    writeDst( readDst(s, t-1), s, t );
+                //}
+                //else
+                //{
+                //    writeDst( readDst(s-1, t-1), s, t );
+                //}
+            }
+            else if ( !last_s && last_t )
+            {
+                writeDst( readDst(s-1,t), s, t );
+                //if ( readDst(s-1, t) == readDst(s+1, t) )
+                //{
+                //    writeDst( readDst(s-1,t), s, t );
+                //}
+                //else
+                //{
+                //    writeDst( readDst(s-1,t-1), s, t );
+                //}
+            }
+            else
+            {
+                writeDst( readDst(s-1,t-1), s, t);
+            }
+        }
+    }
+
+    return dst;
+}
+
 bool
 ImageUtils::isSingleColorImage(const osg::Image* image, float threshold)
 {
@@ -771,6 +974,7 @@ ImageUtils::convert(const osg::Image* image, GLenum pixelFormat, GLenum dataType
     osg::Image* result = new osg::Image();
     result->allocateImage(image->s(), image->t(), image->r(), pixelFormat, dataType);
     memset(result->data(), 0, result->getTotalSizeInBytes());
+    markAsNormalized(result, isNormalized(image));
 
     if ( pixelFormat == GL_RGB && dataType == GL_UNSIGNED_BYTE )
         result->setInternalTextureFormat( GL_RGB8_INTERNAL );
@@ -1003,6 +1207,16 @@ ImageUtils::sameFormat(const osg::Image* lhs, const osg::Image* rhs)
         lhs->getDataType()    == rhs->getDataType();
 }
 
+bool
+ImageUtils::textureArrayCompatible(const osg::Image* lhs, const osg::Image* rhs)
+{
+    return
+        sameFormat(lhs, rhs) &&
+        lhs->s() == rhs->s() &&
+        lhs->t() == rhs->t() &&
+        lhs->r() == rhs->r();
+}
+
 //------------------------------------------------------------------------
 
 namespace
@@ -1023,37 +1237,37 @@ namespace
 
     template<> struct GLTypeTraits<GLbyte>
     {
-        static double scale() { return 1.0/128.0; } // XXX
+        static double scale(bool norm) { return norm? 1.0/128.0 : 1.0; } // XXX
     };
 
     template<> struct GLTypeTraits<GLubyte>
     {
-        static double scale() { return 1.0/255.0; }
+        static double scale(bool norm) { return norm? 1.0/255.0 : 1.0; }
     };
 
     template<> struct GLTypeTraits<GLshort>
     {
-        static double scale() { return 1.0/32768.0; } // XXX
+        static double scale(bool norm) { return norm? 1.0/32768.0 : 1.0; } // XXX
     };
 
     template<> struct GLTypeTraits<GLushort>
     {
-        static double scale() { return 1.0/65535.0; }
+        static double scale(bool norm) { return norm? 1.0/65535.0 : 1.0; }
     };
 
     template<> struct GLTypeTraits<GLint>
     {
-        static double scale() { return 1.0/2147483648.0; } // XXX
+        static double scale(bool norm) { return norm? 1.0/2147483648.0 : 1.0; } // XXX
     };
 
     template<> struct GLTypeTraits<GLuint>
     {
-        static double scale() { return 1.0/4294967295.0; }
+        static double scale(bool norm) { return norm? 1.0/4294967295.0 : 1.0; }
     };
 
     template<> struct GLTypeTraits<GLfloat>
     {
-        static double scale() { return 1.0; }
+        static double scale(bool norm) { return 1.0; }
     };
 
     // The Reader function that performs the read.
@@ -1066,7 +1280,7 @@ namespace
         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();
+            float l = float(*ptr) * GLTypeTraits<T>::scale(ia->_normalized);
             return osg::Vec4(l, l, l, 1.0f);
         }
     };
@@ -1077,7 +1291,7 @@ namespace
         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());
+            (*ptr) = (T)(c.r() / GLTypeTraits<T>::scale(iw->_normalized));
         }
     };
 
@@ -1087,7 +1301,7 @@ namespace
         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();
+            float l = float(*ptr) * GLTypeTraits<T>::scale(ia->_normalized);
             return osg::Vec4(l, l, l, 1.0f);
         }
     };
@@ -1098,7 +1312,7 @@ namespace
         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());
+            (*ptr) = (T)(c.r() / GLTypeTraits<T>::scale(iw->_normalized));
         }
     };
 
@@ -1108,7 +1322,7 @@ namespace
         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 a = float(*ptr) * GLTypeTraits<T>::scale();
+            float a = float(*ptr) * GLTypeTraits<T>::scale(ia->_normalized);
             return osg::Vec4(1.0f, 1.0f, 1.0f, a);
         }
     };
@@ -1119,7 +1333,7 @@ namespace
         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.a() / GLTypeTraits<T>::scale());
+            (*ptr) = (T)(c.a() / GLTypeTraits<T>::scale(iw->_normalized));
         }
     };
 
@@ -1129,8 +1343,8 @@ namespace
         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();
-            float a = float(*ptr) * GLTypeTraits<T>::scale();
+            float l = float(*ptr++) * GLTypeTraits<T>::scale(ia->_normalized);
+            float a = float(*ptr) * GLTypeTraits<T>::scale(ia->_normalized);
             return osg::Vec4(l, l, l, a);
         }
     };
@@ -1141,8 +1355,8 @@ namespace
         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() );
-            *ptr   = (T)( c.a() / GLTypeTraits<T>::scale() );
+            *ptr++ = (T)( c.r() / GLTypeTraits<T>::scale(iw->_normalized) );
+            *ptr   = (T)( c.a() / GLTypeTraits<T>::scale(iw->_normalized) );
         }
     };
 
@@ -1152,9 +1366,9 @@ namespace
         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 d = float(*ptr++) * GLTypeTraits<T>::scale();
-            float g = float(*ptr++) * GLTypeTraits<T>::scale();
-            float b = float(*ptr) * GLTypeTraits<T>::scale();
+            float d = float(*ptr++) * GLTypeTraits<T>::scale(ia->_normalized);
+            float g = float(*ptr++) * GLTypeTraits<T>::scale(ia->_normalized);
+            float b = float(*ptr) * GLTypeTraits<T>::scale(ia->_normalized);
             return osg::Vec4(d, g, b, 1.0f);
         }
     };
@@ -1165,9 +1379,9 @@ namespace
         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() );
-            *ptr++ = (T)( c.g() / GLTypeTraits<T>::scale() );
-            *ptr++ = (T)( c.b() / GLTypeTraits<T>::scale() );
+            *ptr++ = (T)( c.r() / GLTypeTraits<T>::scale(iw->_normalized) );
+            *ptr++ = (T)( c.g() / GLTypeTraits<T>::scale(iw->_normalized) );
+            *ptr++ = (T)( c.b() / GLTypeTraits<T>::scale(iw->_normalized) );
         }
     };
 
@@ -1177,10 +1391,10 @@ namespace
         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 d = float(*ptr++) * GLTypeTraits<T>::scale();
-            float g = float(*ptr++) * GLTypeTraits<T>::scale();
-            float b = float(*ptr++) * GLTypeTraits<T>::scale();
-            float a = float(*ptr) * GLTypeTraits<T>::scale();
+            float d = float(*ptr++) * GLTypeTraits<T>::scale(ia->_normalized);
+            float g = float(*ptr++) * GLTypeTraits<T>::scale(ia->_normalized);
+            float b = float(*ptr++) * GLTypeTraits<T>::scale(ia->_normalized);
+            float a = float(*ptr) * GLTypeTraits<T>::scale(ia->_normalized);
             return osg::Vec4(d, g, b, a);
         }
     };
@@ -1191,10 +1405,10 @@ namespace
         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() );
-            *ptr++ = (T)( c.g() / GLTypeTraits<T>::scale() );
-            *ptr++ = (T)( c.b() / GLTypeTraits<T>::scale() );
-            *ptr++ = (T)( c.a() / GLTypeTraits<T>::scale() );
+            *ptr++ = (T)( c.r() / GLTypeTraits<T>::scale(iw->_normalized) );
+            *ptr++ = (T)( c.g() / GLTypeTraits<T>::scale(iw->_normalized) );
+            *ptr++ = (T)( c.b() / GLTypeTraits<T>::scale(iw->_normalized) );
+            *ptr++ = (T)( c.a() / GLTypeTraits<T>::scale(iw->_normalized) );
         }
     };
 
@@ -1204,9 +1418,9 @@ namespace
         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 b = float(*ptr) * GLTypeTraits<T>::scale();
-            float g = float(*ptr++) * GLTypeTraits<T>::scale();
-            float d = float(*ptr++) * GLTypeTraits<T>::scale();
+            float b = float(*ptr) * GLTypeTraits<T>::scale(ia->_normalized);
+            float g = float(*ptr++) * GLTypeTraits<T>::scale(ia->_normalized);
+            float d = float(*ptr++) * GLTypeTraits<T>::scale(ia->_normalized);
             return osg::Vec4(d, g, b, 1.0f);
         }
     };
@@ -1217,9 +1431,9 @@ namespace
         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.b() / GLTypeTraits<T>::scale() );
-            *ptr++ = (T)( c.g() / GLTypeTraits<T>::scale() );
-            *ptr++ = (T)( c.r() / GLTypeTraits<T>::scale() );
+            *ptr++ = (T)( c.b() / GLTypeTraits<T>::scale(iw->_normalized) );
+            *ptr++ = (T)( c.g() / GLTypeTraits<T>::scale(iw->_normalized) );
+            *ptr++ = (T)( c.r() / GLTypeTraits<T>::scale(iw->_normalized) );
         }
     };
 
@@ -1229,10 +1443,10 @@ namespace
         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 b = float(*ptr++) * GLTypeTraits<T>::scale();
-            float g = float(*ptr++) * GLTypeTraits<T>::scale();
-            float d = float(*ptr++) * GLTypeTraits<T>::scale();
-            float a = float(*ptr) * GLTypeTraits<T>::scale();
+            float b = float(*ptr++) * GLTypeTraits<T>::scale(ia->_normalized);
+            float g = float(*ptr++) * GLTypeTraits<T>::scale(ia->_normalized);
+            float d = float(*ptr++) * GLTypeTraits<T>::scale(ia->_normalized);
+            float a = float(*ptr) * GLTypeTraits<T>::scale(ia->_normalized);
             return osg::Vec4(d, g, b, a);
         }
     };
@@ -1243,10 +1457,10 @@ namespace
         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.b() / GLTypeTraits<T>::scale() );
-            *ptr++ = (T)( c.g() / GLTypeTraits<T>::scale() );
-            *ptr++ = (T)( c.r() / GLTypeTraits<T>::scale() );
-            *ptr++ = (T)( c.a() / GLTypeTraits<T>::scale() );
+            *ptr++ = (T)( c.b() / GLTypeTraits<T>::scale(iw->_normalized) );
+            *ptr++ = (T)( c.g() / GLTypeTraits<T>::scale(iw->_normalized) );
+            *ptr++ = (T)( c.r() / GLTypeTraits<T>::scale(iw->_normalized) );
+            *ptr++ = (T)( c.a() / GLTypeTraits<T>::scale(iw->_normalized) );
         }
     };
 
@@ -1441,8 +1655,9 @@ namespace
 }
     
 ImageUtils::PixelReader::PixelReader(const osg::Image* image) :
-_image(image)
+_image     (image)
 {
+    _normalized = ImageUtils::isNormalized(image);
     _colMult = _image->getPixelSizeInBits() / 8;
     _rowMult = _image->getRowSizeInBytes();
     _imageSize = _image->getImageSizeInBytes();
@@ -1531,6 +1746,7 @@ namespace
 ImageUtils::PixelWriter::PixelWriter(osg::Image* image) :
 _image(image)
 {
+    _normalized = ImageUtils::isNormalized(image);
     _colMult = _image->getPixelSizeInBits() / 8;
     _rowMult = _image->getRowSizeInBytes();
     _imageSize = _image->getImageSizeInBytes();
diff --git a/src/osgEarth/Instancing.vert.glsl b/src/osgEarth/Instancing.vert.glsl
new file mode 100644
index 0000000..92d34b5
--- /dev/null
+++ b/src/osgEarth/Instancing.vert.glsl
@@ -0,0 +1,31 @@
+#version 130
+#extension GL_EXT_gpu_shader4 : enable
+#extension GL_ARB_draw_instanced: enable
+
+#pragma vp_entryPoint "oe_di_setInstancePosition"
+#pragma vp_location   "vertex_model"
+#pragma vp_order      "0.0"
+
+uniform samplerBuffer oe_di_postex_TBO;
+uniform int			  oe_di_postex_TBO_size;
+
+// Stage-global containing object ID
+uint oe_index_objectid;
+
+void oe_di_setInstancePosition(inout vec4 VertexMODEL)
+{ 
+    int index = 4 * gl_InstanceID;
+
+    vec4 m0 = texelFetch(oe_di_postex_TBO, index);
+    vec4 m1 = texelFetch(oe_di_postex_TBO, index+1); 
+    vec4 m2 = texelFetch(oe_di_postex_TBO, index+2); 
+    vec4 m3 = texelFetch(oe_di_postex_TBO, index+3);
+
+    // decode the ObjectID from the last column:
+    
+    oe_index_objectid = uint(m3[0]) + (uint(m3[1]) << 8u) + (uint(m3[2]) << 16u) + (uint(m3[3]) << 24u);
+    
+    // rebuild positioning matrix and transform the vert. (Note, the matrix is actually
+    // transposed so we have to reverse the multiplication order.)
+    VertexMODEL = VertexMODEL * mat4(m0, m1, m2, vec4(0,0,0,1));
+}
diff --git a/src/osgEarth/IntersectionPicker b/src/osgEarth/IntersectionPicker
new file mode 100644
index 0000000..705f681
--- /dev/null
+++ b/src/osgEarth/IntersectionPicker
@@ -0,0 +1,118 @@
+/* -*-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.
+ *
+ * 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_INTERSECTION_PICKER_H
+#define OSGEARTH_INTERSECTION_PICKER_H
+
+#include <osgEarth/Common>
+#include <osgEarth/ObjectIndex>
+#include <osgEarth/PrimitiveIntersector>
+#include <osgViewer/View>
+#include <osgEarthFeatures/Feature>
+
+namespace osgEarth
+{
+    /**
+     * Utility for picking objects from the scene.
+     */
+    class OSGEARTH_EXPORT IntersectionPicker
+    {
+    public:
+        typedef osgEarth::PrimitiveIntersector::Intersection Hit;
+        typedef osgEarth::PrimitiveIntersector::Intersections Hits;
+
+        enum Limit {
+            NO_LIMIT,
+            LIMIT_ONE_PER_DRAWABLE,
+            LIMIT_ONE,
+            LIMIT_NEAREST
+        };
+
+    public:
+        /** 
+         * Constructs a picker that will pick data from the given view,
+         * and restrict its search to the given graph.
+         *
+         * @param view          View under which to pick
+         * @param graph         Subgraph within which to restrict the pick
+         * @param traversalMask Node mask to apply to the pick visitor
+         * @param buffer        Pick buffer around the click (pixels)
+         */
+        IntersectionPicker( 
+            osgViewer::View* view,
+            osg::Node*       graph         =0L, 
+            unsigned         traversalMask =~0,
+            float            buffer        =5.0f,
+            Limit            limit         =LIMIT_NEAREST);
+
+        /** dtor */
+        virtual ~IntersectionPicker() { }
+
+        /**
+         * Sets the node mask to apply to the pick visitor. This lets you
+         * limit the subgraph that the picker searches.
+         */
+        void setTraversalMask(unsigned mask);
+
+        /**
+         * Sets the search buffer around the mouse.
+         */
+        void setBuffer(float buffer);
+
+        /**
+         * Sets the hit limit mode.
+         */
+        void setLimit(const Limit& limit);
+
+        /**
+         * Picks geometry under the specified viewport coordinates. The results
+         * are stores in "results". You can typically get the mouseX and mouseY
+         * from osgGA::GUIEventAdapter getX() and getY().
+         */
+        bool pick( float mouseX, float mouseY, Hits& results ) const;
+
+        /**
+         * Finds and returns the lowest node of type "T" in a hit, or 0L if no such
+         * node exists.
+         */
+        template<typename T>
+        T* getNode( const Hit& hit ) const {
+            for( osg::NodePath::const_reverse_iterator i = hit.nodePath.rbegin(); i != hit.nodePath.rend(); ++i ) {
+               T* node = dynamic_cast<T*>(*i);
+               if ( node ) return node;
+            }
+            return 0L;
+        }
+
+        /**
+         * Given a set of pick results, extract the object IDs within.
+         * Results true is the output connection is populated.
+         */
+        bool getObjectIDs(const Hits& results, std::set<ObjectID>& out_objectIDs) const;
+
+    protected:
+        osgViewer::View*              _view;
+        osg::ref_ptr<osg::Node>       _root;
+        osg::NodePath                 _path;
+        unsigned                      _travMask;
+        float                         _buffer;
+        Limit                         _limit;
+    };
+}
+
+#endif // OSGEARTH_INTERSECTION_PICKER_H
diff --git a/src/osgEarth/IntersectionPicker.cpp b/src/osgEarth/IntersectionPicker.cpp
new file mode 100644
index 0000000..939f76a
--- /dev/null
+++ b/src/osgEarth/IntersectionPicker.cpp
@@ -0,0 +1,208 @@
+/* -*-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.
+ *
+ * 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/IntersectionPicker>
+#include <osgEarth/PrimitiveIntersector>
+#include <osgEarth/Registry>
+
+#define LC "[Picker] "
+
+using namespace osgEarth;
+
+
+IntersectionPicker::IntersectionPicker( osgViewer::View* view, osg::Node* root, unsigned travMask, float buffer, Limit limit ) :
+_view    ( view ),
+_root    ( root ),
+_travMask( travMask ),
+_buffer  ( buffer ),
+_limit   ( limit )
+{
+    if ( root )
+        _path = root->getParentalNodePaths()[0];
+}
+
+void
+IntersectionPicker::setLimit(const IntersectionPicker::Limit& value)
+{
+    _limit = value;
+}
+
+void
+IntersectionPicker::setTraversalMask(unsigned value)
+{
+    _travMask = value;
+}
+
+void
+IntersectionPicker::setBuffer(float value)
+{
+    _buffer = value;
+}
+
+bool
+IntersectionPicker::pick( float x, float y, Hits& results ) const
+{
+    float local_x, local_y = 0.0;
+    const osg::Camera* camera = _view->getCameraContainingPosition(x, y, local_x, local_y);
+    if ( !camera )
+        camera = _view->getCamera();
+
+    osg::ref_ptr<osgEarth::PrimitiveIntersector> picker;
+
+    double buffer_x = _buffer, buffer_y = _buffer;
+    if ( camera->getViewport() )
+    {
+        double aspectRatio = camera->getViewport()->width()/camera->getViewport()->height();
+        buffer_x *= aspectRatio;
+        buffer_y /= aspectRatio;
+    }
+
+    osg::Matrix windowMatrix;
+
+    if ( _root.valid() )
+    {
+        osg::Matrix modelMatrix;
+
+        if (camera->getViewport())
+        {
+            windowMatrix = camera->getViewport()->computeWindowMatrix();
+            modelMatrix.preMult( windowMatrix );
+        }
+
+        modelMatrix.preMult( camera->getProjectionMatrix() );
+        modelMatrix.preMult( camera->getViewMatrix() );
+
+        osg::NodePath prunedNodePath( _path.begin(), _path.end()-1 );
+        modelMatrix.preMult( osg::computeWorldToLocal(prunedNodePath) );
+
+        osg::Matrix modelInverse;
+        modelInverse.invert(modelMatrix);
+
+        osg::Vec3d startLocal(local_x, local_y, 0.0);
+        osg::Vec3d startModel = startLocal * modelInverse;
+
+        osg::Vec3d endLocal(local_x, local_y, 1.0);
+        osg::Vec3d endModel = endLocal * modelInverse;
+
+        osg::Vec3d bufferLocal(local_x + buffer_x, local_y + buffer_y, 0.0);
+        osg::Vec3d bufferModel = bufferLocal * modelInverse;
+
+        double buffer = osg::maximum((bufferModel - startModel).length(), 5.0);  //TODO: Setting a minimum of 4.0 may need revisited
+
+        OE_DEBUG
+            << "local_x:" << local_x << ", local_y:" << local_y
+            << ", buffer_x:" << buffer_x << ", buffer_y:" << buffer_y
+            << ", bm.x:" << bufferModel.x() << ", bm.y:" << bufferModel.y()
+            << ", bm.z:" << bufferModel.z()
+            << ", BUFFER: " << buffer
+            << std::endl;
+
+        picker = new osgEarth::PrimitiveIntersector(osgUtil::Intersector::MODEL, startModel, endModel, buffer);
+    }
+    else
+    {
+        picker = new osgEarth::PrimitiveIntersector(camera->getViewport() ? osgUtil::Intersector::WINDOW : osgUtil::Intersector::PROJECTION, local_x, local_y, _buffer);
+    }
+
+    picker->setIntersectionLimit( (osgUtil::Intersector::IntersectionLimit)_limit );
+    osgUtil::IntersectionVisitor iv(picker.get());
+
+    //picker->setIntersectionLimit( osgUtil::Intersector::LIMIT_ONE_PER_DRAWABLE );
+
+    // in MODEL mode, we need to window and proj matrixes in order to support some of the 
+    // features in osgEarth (like Annotation::OrthoNode).
+    if ( _root.valid() )
+    {
+        iv.pushWindowMatrix( new osg::RefMatrix(windowMatrix) );
+        iv.pushProjectionMatrix( new osg::RefMatrix(camera->getProjectionMatrix()) );
+        iv.pushViewMatrix( new osg::RefMatrix(camera->getViewMatrix()) );
+    }
+
+    iv.setTraversalMask( _travMask );
+
+    if ( _root.valid() )
+        _path.back()->accept(iv);
+    else
+        const_cast<osg::Camera*>(camera)->accept(iv);
+
+    if (picker->containsIntersections())
+    {
+        results = picker->getIntersections();
+        return true;
+    }
+    else
+    {
+        results.clear();
+        return false;
+    }
+}
+
+bool
+IntersectionPicker::getObjectIDs(const Hits& results, std::set<ObjectID>& out_objectIDs) const
+{
+    ObjectIndex* index = Registry::objectIndex();
+
+    for(Hits::const_iterator hit = results.begin(); hit != results.end(); ++hit)
+    {
+        bool found = false;
+
+        // check for the uniform.
+        const osg::NodePath& path = hit->nodePath;
+        for(osg::NodePath::const_reverse_iterator n = path.rbegin(); n != path.rend(); ++n )
+        {
+            osg::Node* node = *n;
+            if ( node && node->getStateSet() )
+            {
+                osg::Uniform* u = node->getStateSet()->getUniform( index->getObjectIDUniformName() );
+                if ( u )
+                {
+                    ObjectID oid;
+                    if ( u->get(oid) )
+                    {
+                        out_objectIDs.insert( oid );
+                        found = true;
+                    }
+                }
+            }
+        }
+
+        if ( !found )
+        {
+            // check the geometry.
+            const osg::Geometry* geom = hit->drawable ? hit->drawable->asGeometry() : 0L;
+            if ( geom )
+            {
+                const ObjectIDArray* ids = dynamic_cast<const ObjectIDArray*>( geom->getVertexAttribArray(index->getObjectIDAttribLocation()) );
+                if ( ids )
+                {
+                    for(unsigned i=0; i < hit->indexList.size(); ++i)
+                    {
+                        unsigned index = hit->indexList[i];
+                        if ( index < ids->size() )
+                        {
+                            ObjectID oid = (*ids)[index];
+                            out_objectIDs.insert( oid );
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    return !out_objectIDs.empty();
+}
diff --git a/src/osgEarth/JsonUtils b/src/osgEarth/JsonUtils
index e032f70..4275ab8 100644
--- a/src/osgEarth/JsonUtils
+++ b/src/osgEarth/JsonUtils
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarth/JsonUtils.cpp b/src/osgEarth/JsonUtils.cpp
index 5fe1d9b..5825d88 100644
--- a/src/osgEarth/JsonUtils.cpp
+++ b/src/osgEarth/JsonUtils.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarth/Layer b/src/osgEarth/Layer
index bb9902e..417c0dd 100644
--- a/src/osgEarth/Layer
+++ b/src/osgEarth/Layer
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarth/Layer.cpp b/src/osgEarth/Layer.cpp
index 958db78..e716a25 100644
--- a/src/osgEarth/Layer.cpp
+++ b/src/osgEarth/Layer.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -25,4 +25,3 @@ Layer::Layer()
 {
     _uid = Registry::instance()->createUID();
 }
-
diff --git a/src/osgEarth/LineFunctor b/src/osgEarth/LineFunctor
index 6d7c04d..894e33d 100644
--- a/src/osgEarth/LineFunctor
+++ b/src/osgEarth/LineFunctor
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarth/LocalTangentPlane b/src/osgEarth/LocalTangentPlane
index 972223c..8e5a839 100644
--- a/src/osgEarth/LocalTangentPlane
+++ b/src/osgEarth/LocalTangentPlane
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarth/LocalTangentPlane.cpp b/src/osgEarth/LocalTangentPlane.cpp
index 94a840e..b29fc31 100644
--- a/src/osgEarth/LocalTangentPlane.cpp
+++ b/src/osgEarth/LocalTangentPlane.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarth/Locators b/src/osgEarth/Locators
index 2a18b83..d39ef4f 100644
--- a/src/osgEarth/Locators
+++ b/src/osgEarth/Locators
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -63,6 +63,7 @@ namespace osgEarth
         virtual bool isLinear() const { return true; }
 
         virtual bool createScaleBiasMatrix(const GeoExtent& window, osg::Matrixd& out_m) const;
+        virtual bool createScaleBiasMatrix(const GeoExtent& window, osg::Matrixf& out_m) const;
 
     public: // better-sounding functions.
 
diff --git a/src/osgEarth/Locators.cpp b/src/osgEarth/Locators.cpp
index 61ebb81..2968aad 100644
--- a/src/osgEarth/Locators.cpp
+++ b/src/osgEarth/Locators.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -174,6 +174,23 @@ GeoLocator::createScaleBiasMatrix(const GeoExtent& window, osg::Matrixd& out) co
     return true;
 }
 
+bool
+GeoLocator::createScaleBiasMatrix(const GeoExtent& window, osg::Matrixf& out) const
+{
+    float scalex = window.width() / _dataExtent.width();
+    float scaley = window.height() / _dataExtent.height();
+    float biasx  = (window.xMin()-_dataExtent.xMin()) / _dataExtent.width();
+    float biasy  = (window.yMin()-_dataExtent.yMin()) / _dataExtent.height();
+
+    out(0,0) = scalex;
+    out(1,1) = scaley;
+    out(3,0) = biasx;
+    out(3,1) = biasy;
+
+    return true;
+}
+
+
 /****************************************************************************/
 
 
diff --git a/src/osgEarth/Map b/src/osgEarth/Map
index a8e9f34..0e1ea26 100644
--- a/src/osgEarth/Map
+++ b/src/osgEarth/Map
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -296,6 +296,7 @@ namespace osgEarth
             const TileKey& key,
             bool           expressHeightsAsHAE =true) const;
         
+#if 0
         /**
          * Populates a heightfield with data from the elevation layers
          * in the map. The method will create a new heightfield if you
@@ -312,6 +313,7 @@ namespace osgEarth
             bool                            expressHeightsAsHAE =true,
             ElevationSamplePolicy           samplePolicy        =SAMPLE_FIRST_VALID,
             ProgressCallback*               progress            =0L) const;
+#endif
 
         /**
          * Sets the Cache for this Map. Set to NULL for no cache.
diff --git a/src/osgEarth/Map.cpp b/src/osgEarth/Map.cpp
index 02aed66..8964df4 100644
--- a/src/osgEarth/Map.cpp
+++ b/src/osgEarth/Map.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -305,7 +305,8 @@ Map::setName( const std::string& name ) {
 Revision
 Map::getDataModelRevision() const
 {
-    Threading::ScopedReadLock lock( const_cast<Map*>(this)->_mapDataMutex );
+    //Don't really need this here.
+    //Threading::ScopedReadLock lock( const_cast<Map*>(this)->_mapDataMutex );
     return _dataModelRevision;
 }
 
@@ -962,25 +963,41 @@ Map::calculateProfile()
                         << std::endl;
                 }
             }
-
-            if ( !_profile.valid() )
-            {
-                // by default, set a geocentric map to use global-geodetic WGS84.
-                _profile = osgEarth::Registry::instance()->getGlobalGeodeticProfile();
-            }
         }
-
         else if ( _mapOptions.coordSysType() == MapOptions::CSTYPE_GEOCENTRIC_CUBE )
         {
-            //If the map type is a Geocentric Cube, set the profile to the cube profile.
-            _profile = osgEarth::Registry::instance()->getCubeProfile();
+            if ( userProfile.valid() )
+            {
+                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() )
             {
-                _profile = userProfile.get();
+                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;
+                }
             }
         }
 
@@ -1008,13 +1025,31 @@ Map::calculateProfile()
             }
         }
 
-        // convert the profile to Plate Carre if necessary.
-        if (_profile.valid() &&
-            _profile->getSRS()->isGeographic() && 
-            getMapOptions().coordSysType() == MapOptions::CSTYPE_PROJECTED )
+        // 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() ) )
         {
-            OE_INFO << LC << "Projected display with geographic SRS; activating Plate Carre mode" << std::endl;
-            _profile = _profile->overrideSRS( _profile->getSRS()->createPlateCarreGeographicSRS() );
+            // 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.
@@ -1043,13 +1078,19 @@ Map::calculateProfile()
             for( ImageLayerVector::iterator i = _imageLayers.begin(); i != _imageLayers.end(); i++ )
             {
                 ImageLayer* layer = i->get();
-                layer->setTargetProfileHint( _profile.get() );
+                if ( layer->getEnabled() == true )
+                {
+                    layer->setTargetProfileHint( _profile.get() );
+                }
             }
 
             for( ElevationLayerVector::iterator i = _elevationLayers.begin(); i != _elevationLayers.end(); i++ )
             {
                 ElevationLayer* layer = i->get();
-                layer->setTargetProfileHint( _profile.get() );
+                if ( layer->getEnabled() )
+                {
+                    layer->setTargetProfileHint( _profile.get() );
+                }
             }
         }
 
@@ -1075,12 +1116,13 @@ Map::createReferenceHeightField(const TileKey& key,
     return HeightFieldUtils::createReferenceHeightField(key.getExtent(), size, size, expressHeightsAsHAE);
 }
 
-
+#if 0
 bool
 Map::populateHeightField(osg::ref_ptr<osg::HeightField>& hf,
                          const TileKey&                  key,
                          bool                            convertToHAE,
                          ElevationSamplePolicy           samplePolicy, // deprecated (unused)
+                         bool                            fallbackIfPossible,
                          ProgressCallback*               progress) const
 {
     Threading::ScopedReadLock lock( const_cast<Map*>(this)->_mapDataMutex );
@@ -1097,8 +1139,10 @@ Map::populateHeightField(osg::ref_ptr<osg::HeightField>& hf,
         key,
         convertToHAE ? _profileNoVDatum.get() : 0L,
         interp,
+        fallbackIfPossible,
         progress );
 }
+#endif
 
 const SpatialReference*
 Map::getWorldSRS() const
diff --git a/src/osgEarth/MapCallback b/src/osgEarth/MapCallback
index 7ca2135..6daaae8 100644
--- a/src/osgEarth/MapCallback
+++ b/src/osgEarth/MapCallback
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarth/MapCallback.cpp b/src/osgEarth/MapCallback.cpp
index d24f8a1..9f34ba7 100644
--- a/src/osgEarth/MapCallback.cpp
+++ b/src/osgEarth/MapCallback.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarth/MapFrame b/src/osgEarth/MapFrame
index 296cad2..166c2ff 100644
--- a/src/osgEarth/MapFrame
+++ b/src/osgEarth/MapFrame
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -22,8 +22,8 @@
 
 #include <osgEarth/Common>
 #include <osgEarth/Containers>
-#include <osgEarth/Map>
 #include <osgEarth/MapInfo>
+#include <osgEarth/Map>
 
 namespace osgEarth
 {
@@ -36,26 +36,45 @@ namespace osgEarth
     {
     public:
         /**
-         * Constructs a new map frame.
+         * Constructs a new empty map frame that's not connected to any map.
          */
-        MapFrame(
-            const Map*         map, 
-            Map::ModelParts    parts =Map::TERRAIN_LAYERS,
-            const std::string& name  ="" );
+        MapFrame();
+
+        MapFrame(const MapFrame& rhs);
+
+        MapFrame(Map::ModelParts parts);
+
+        MapFrame(const Map* map);
+
+        MapFrame(const Map* map, Map::ModelParts parts);
+
+        bool isValid() const;
+
+        /**
+         * Constructs a new map frame and connects it to a map.
+         */
+        //MapFrame(
+        //    const Map*         map, 
+        //    Map::ModelParts    parts =Map::TERRAIN_LAYERS,
+        //    const std::string& name  ="" );
 
         /**
          * A copy constructor with a new name (no sync happens)
          */
-        MapFrame(
-            const MapFrame& frame, 
-            const std::string& name ="" );
+       // MapFrame(
+        //    const MapFrame& frame, 
+        //    const std::string& name ="" );
 
         /** dtor */
         virtual ~MapFrame() { }
 
         /**
-         * Synchronizes this frame with the source map model (only if necessary). Returns
-         * true is new data was synced; false if nothing changed.
+         * Sets the map to which this frame is connected.
+         */
+        void setMap(const Map* map);
+
+        /**
+         * Synchronizes this frame to the last-synced map. Returns true if changes occurred.
          */
         bool sync();
 
@@ -117,9 +136,8 @@ namespace osgEarth
         bool populateHeightField(
             osg::ref_ptr<osg::HeightField>& hf,
             const TileKey&                  key,
-            bool                            expressHeightsAsHAE =true,
-            ElevationSamplePolicy           samplePolicy        =SAMPLE_FIRST_VALID,
-            ProgressCallback*               progress            =0L) const;
+            bool                            expressHeightsAsHAE,
+            ProgressCallback*               progress) const;
 
     private:
         bool _initialized;
diff --git a/src/osgEarth/MapFrame.cpp b/src/osgEarth/MapFrame.cpp
index 14bf35e..3533389 100644
--- a/src/osgEarth/MapFrame.cpp
+++ b/src/osgEarth/MapFrame.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -22,45 +22,82 @@ using namespace osgEarth;
 
 #define LC "[MapFrame] "
 
+MapFrame::MapFrame() :
+_initialized    ( false ),
+_highestMinLevel( 0 ),
+_mapInfo       ( 0L )
+{
+    //nop
+}
+
+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 )
+{
+    //no sync required here; we copied the arrays etc
+}
 
-MapFrame::MapFrame( const Map* map, Map::ModelParts parts, const std::string& name ) :
+MapFrame::MapFrame(const Map* map) :
 _initialized    ( false ),
 _map            ( map ),
-_name           ( name ),
 _mapInfo        ( map ),
-_parts          ( parts ),
+_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();
+}
 
-MapFrame::MapFrame( const MapFrame& src, const std::string& name ) :
-_initialized         ( src._initialized ),
-_map                 ( src._map.get() ),
-_name                ( name ),
-_mapInfo             ( src._mapInfo ),
-_parts               ( src._parts ),
-_highestMinLevel     ( src._highestMinLevel ),
-_mapDataModelRevision( src._mapDataModelRevision ),
-_imageLayers         ( src._imageLayers ),
-_elevationLayers     ( src._elevationLayers ),
-_modelLayers         ( src._modelLayers ),
-_maskLayers          ( src._maskLayers )
+bool
+MapFrame::isValid() const
 {
-    //no sync required here; we copied the arrays etc
+    return _map.valid();
 }
 
+void
+MapFrame::setMap(const Map* map)
+{
+    _imageLayers.clear();
+    _elevationLayers.clear();
+    _modelLayers.clear();
+    _maskLayers.clear();
+
+    _map = map;
+    if ( map )
+        _mapInfo = MapInfo(map);
+
+    _initialized = false;
+    _highestMinLevel = 0;
+
+    sync();
+}
 
 bool
 MapFrame::sync()
 {
     bool changed = false;
 
-    if ( _map.valid() )
+    osg::ref_ptr<const Map> map;
+    if ( _map.lock(map) )
     {
         changed = _map->sync( *this );
-
         if ( changed )
         {
             refreshComputedValues();
@@ -81,15 +118,23 @@ MapFrame::sync()
 bool
 MapFrame::needsSync() const
 {
-    return
-        (_map.valid()) &&
-        (_map->getDataModelRevision() != _mapDataModelRevision || !_initialized);
+    if ( !isValid() )
+        return false;
+
+    osg::ref_ptr<const Map> map;
+    return 
+        _map.lock(map) &&
+        (map->getDataModelRevision() != _mapDataModelRevision || !_initialized);
 }
 
 UID
 MapFrame::getUID() const
 {
-    return _map.valid() ? _map->getUID() : (UID)0;
+    osg::ref_ptr<const Map> map;
+    if ( _map.lock(map) )
+        return map->getUID();
+    else
+        return (UID)0;
 }
 
 void
@@ -121,25 +166,29 @@ bool
 MapFrame::populateHeightField(osg::ref_ptr<osg::HeightField>& hf,
                               const TileKey&                  key,
                               bool                            convertToHAE,
-                              ElevationSamplePolicy           samplePolicy,
                               ProgressCallback*               progress) const
 {
-    if ( !_map.valid() ) 
-        return false;
+    osg::ref_ptr<const Map> map;
+    if ( _map.lock(map) )
+    {        
+        ElevationInterpolation interp = map->getMapOptions().elevationInterpolation().get();    
 
-    ElevationInterpolation interp = _map->getMapOptions().elevationInterpolation().get();    
+        if ( !hf.valid() )
+        {
+            hf = map->createReferenceHeightField(key, convertToHAE);
+        }
 
-    if ( !hf.valid() )
+        return _elevationLayers.populateHeightField(
+            hf.get(),
+            key,
+            convertToHAE ? map->getProfileNoVDatum() : 0L,
+            interp,
+            progress );
+    }
+    else
     {
-        hf = _map->createReferenceHeightField(key, convertToHAE);
+        return false;
     }
-
-    return _elevationLayers.populateHeightField(
-        hf.get(),
-        key,
-        convertToHAE ? _map->getProfileNoVDatum() : 0L,
-        interp,
-        progress );
 }
 
 
@@ -191,7 +240,7 @@ bool
 MapFrame::isCached( const TileKey& key ) const
 {
     // is there a map cache at all?
-    if ( _map->getCache() == 0L )
+    if ( _map.valid() && _map->getCache() == 0L )
         return false;
 
     //Check to see if the tile will load fast
diff --git a/src/osgEarth/MapInfo b/src/osgEarth/MapInfo
index dc2b696..5b5b680 100644
--- a/src/osgEarth/MapInfo
+++ b/src/osgEarth/MapInfo
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarth/MapInfo.cpp b/src/osgEarth/MapInfo.cpp
index 27ca05a..dc79e96 100644
--- a/src/osgEarth/MapInfo.cpp
+++ b/src/osgEarth/MapInfo.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarth/MapModelChange b/src/osgEarth/MapModelChange
index 6dd7f64..8710887 100644
--- a/src/osgEarth/MapModelChange
+++ b/src/osgEarth/MapModelChange
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarth/MapNode b/src/osgEarth/MapNode
index ca26ac6..73f0b64 100644
--- a/src/osgEarth/MapNode
+++ b/src/osgEarth/MapNode
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
@@ -25,6 +28,7 @@
 #include <osgEarth/MapNodeOptions>
 #include <osgEarth/SpatialReference>
 #include <osgEarth/TerrainEngineNode>
+#include <osgEarth/Extension>
 
 namespace osgEarth
 {
@@ -162,6 +166,26 @@ namespace osgEarth
         Config& externalConfig() { return _externalConf; }
         const Config& externalConfig() const { return _externalConf; }
 
+        /**
+         * Adds an Extension and calls its startup method with this MapNode.
+         */
+        void addExtension(Extension* extension, const osgDB::Options* options =0L);
+
+        /**
+         * Removes an extension, and calls its shutdown method with this MapNode.
+         */
+        void removeExtension(Extension* extension);
+
+        /**
+         * Removes all extensions, calling each one's shutdown method this this MapNode.
+         */
+        void clearExtensions();
+
+        /**
+         * Access the extensions vector.
+         */
+        const std::vector< osg::ref_ptr<Extension> >& getExtensions() const { return _extensions; }
+
 
     public: // special purpose
 
@@ -202,6 +226,7 @@ namespace osgEarth
         bool                     _terrainEngineInitialized;
         osg::Group*              _terrainEngineContainer;
 
+        std::vector< osg::ref_ptr<Extension> > _extensions;
 
     public: // MapCallback proxy
 
diff --git a/src/osgEarth/MapNode.cpp b/src/osgEarth/MapNode.cpp
index 03df8f0..7db970c 100644
--- a/src/osgEarth/MapNode.cpp
+++ b/src/osgEarth/MapNode.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
@@ -81,6 +84,8 @@ namespace
 
         osg::observer_ptr<MapNode> _mapNode;
     };
+
+    typedef std::vector< osg::ref_ptr<Extension> > Extensions;
 }
 
 //---------------------------------------------------------------------------
@@ -292,11 +297,11 @@ MapNode::init()
     // model layer will still work OK though.
     _models = new osg::Group();
     _models->setName( "osgEarth::MapNode.modelsGroup" );
+    _models->getOrCreateStateSet()->setRenderBinDetails(2, "RenderBin");
     addChild( _models.get() );
 
     // a decorator for overlay models:
     _overlayDecorator = new OverlayDecorator();
-    _overlayDecorator->setOverlayGraphTraversalMask( terrainOptions.secondaryTraversalMask().value() );
 
     // install the Draping technique for overlays:
     {
@@ -342,6 +347,7 @@ MapNode::init()
     _map->addMapCallback( _mapCallback.get()  );
 
     osg::StateSet* stateset = getOrCreateStateSet();
+
     if ( _mapNodeOptions.enableLighting().isSet() )
     {
         stateset->addUniform(Registry::shaderFactory()->createUniformForGLMode(
@@ -353,6 +359,26 @@ MapNode::init()
             _mapNodeOptions.enableLighting().value() ? 1 : 0);
     }
 
+    // Add in some global uniforms
+    stateset->addUniform( new osg::Uniform("oe_isGeocentric", _map->isGeocentric()) );
+    if ( _map->isGeocentric() )
+    {
+        OE_INFO << LC << "Adding ellipsoid uniforms.\n";
+
+        // for a geocentric map, use an ellipsoid unit-frame transform and its inverse:
+        osg::Vec3d ellipFrameInverse(
+            _map->getSRS()->getEllipsoid()->getRadiusEquator(),
+            _map->getSRS()->getEllipsoid()->getRadiusEquator(),
+            _map->getSRS()->getEllipsoid()->getRadiusPolar());
+        stateset->addUniform( new osg::Uniform("oe_ellipsoidFrameInverse", osg::Vec3f(ellipFrameInverse)) );
+
+        osg::Vec3d ellipFrame = osg::componentDivide(osg::Vec3d(1.0,1.0,1.0), ellipFrameInverse);
+        stateset->addUniform( new osg::Uniform("oe_ellipsoidFrame", osg::Vec3f(ellipFrame)) );
+    }
+
+    // install the default rendermode uniform:
+    stateset->addUniform( new osg::Uniform("oe_isPickCamera", false) );
+
     dirtyBound();
 
     // register for event traversals so we can deal with blacklisted filenames
@@ -373,6 +399,8 @@ MapNode::~MapNode()
     {
         this->onModelLayerRemoved( itr->get() );
     }
+
+    this->clearExtensions();
 }
 
 osg::BoundingSphere
@@ -431,6 +459,56 @@ MapNode::getTerrainEngine() const
     return _terrainEngine;
 }
 
+void
+MapNode::addExtension(Extension* extension, const osgDB::Options* options)
+{
+    if ( extension )
+    {
+        _extensions.push_back( extension );
+
+        // set the IO options is they were provided:
+        if ( options )
+            extension->setDBOptions( options );
+        
+        // start it.
+        ExtensionInterface<MapNode>* extensionIF = ExtensionInterface<MapNode>::get(extension);
+        if ( extensionIF )
+        {
+            extensionIF->connect( this );
+        }
+    }
+}
+
+void
+MapNode::removeExtension(Extension* extension)
+{
+    Extensions::iterator i = std::find(_extensions.begin(), _extensions.end(), extension);
+    if ( i != _extensions.end() )
+    {
+        ExtensionInterface<MapNode>* extensionIF = ExtensionInterface<MapNode>::get( i->get() );
+        if ( extensionIF )
+        {
+            extensionIF->disconnect( this );
+        }
+        _extensions.erase( i );
+    }
+}
+
+void
+MapNode::clearExtensions()
+{
+    for(Extensions::iterator i = _extensions.begin(); i != _extensions.end(); ++i)
+    {
+        ExtensionInterface<MapNode>* extensionIF = ExtensionInterface<MapNode>::get( i->get() );
+        if ( extensionIF )
+        {
+            extensionIF->disconnect( this );
+        }
+    }
+
+    _extensions.clear();
+}
+
 osg::Group*
 MapNode::getModelLayerGroup() const
 {
@@ -479,7 +557,8 @@ MapNode::onModelLayerAdded( ModelLayer* layer, unsigned int index )
     }
 
     // create the scene graph:
-    osg::Node* node = layer->getOrCreateSceneGraph( _map.get(), _map->getDBOptions(), 0L );
+    //osg::Node* node = layer->getOrCreateSceneGraph( _map.get(), _map->getDBOptions(), 0L );
+    osg::Node* node = layer->getOrCreateSceneGraph( _map.get(), 0L );
 
     if ( node )
     {
@@ -656,13 +735,16 @@ MapNode::traverse( osg::NodeVisitor& nv )
             const SpatialReference* srs = getMapSRS();
             if ( srs && !srs->isProjected() )
             {
-                GeoPoint ecef;
-                ecef.fromWorld( srs, eye );
-                cullData->_cameraAltitude = ecef.alt();
+                GeoPoint lla;
+                lla.fromWorld( srs, eye );
+                cullData->_cameraAltitude = lla.alt();
+                cullData->_cameraAltitudeUniform->set( (float)lla.alt() );
+                //OE_INFO << lla.alt() << std::endl;
             }
             else
             {
                 cullData->_cameraAltitude = eye.z();
+                cullData->_cameraAltitudeUniform->set( (float)eye.z() );
             }
 
             // window matrix:
diff --git a/src/osgEarth/MapNodeObserver b/src/osgEarth/MapNodeObserver
index d7c58a8..073f414 100644
--- a/src/osgEarth/MapNodeObserver
+++ b/src/osgEarth/MapNodeObserver
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarth/MapNodeOptions b/src/osgEarth/MapNodeOptions
index ee79b10..3e17046 100644
--- a/src/osgEarth/MapNodeOptions
+++ b/src/osgEarth/MapNodeOptions
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/osgEarth/MapNodeOptions.cpp b/src/osgEarth/MapNodeOptions.cpp
index e9ab176..7004e17 100644
--- a/src/osgEarth/MapNodeOptions.cpp
+++ b/src/osgEarth/MapNodeOptions.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarth/MapOptions b/src/osgEarth/MapOptions
index 350a3db..73b2dc0 100644
--- a/src/osgEarth/MapOptions
+++ b/src/osgEarth/MapOptions
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/osgEarth/MapOptions.cpp b/src/osgEarth/MapOptions.cpp
index 2d135e1..cc6005b 100644
--- a/src/osgEarth/MapOptions.cpp
+++ b/src/osgEarth/MapOptions.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarth/MaskLayer b/src/osgEarth/MaskLayer
index ba6c293..a832048 100644
--- a/src/osgEarth/MaskLayer
+++ b/src/osgEarth/MaskLayer
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -136,6 +136,7 @@ namespace osgEarth
         Revision                      _maskSourceRev;
         osg::ref_ptr<osg::Vec3dArray> _boundary;
         osg::ref_ptr<osgDB::Options>  _dbOptions;
+        OpenThreads::Mutex            _mutex;
 
         void copyOptions();
     };
diff --git a/src/osgEarth/MaskLayer.cpp b/src/osgEarth/MaskLayer.cpp
index 273c5a7..6c3a914 100644
--- a/src/osgEarth/MaskLayer.cpp
+++ b/src/osgEarth/MaskLayer.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -118,6 +118,7 @@ MaskLayer::initialize( const osgDB::Options* dbOptions, const Map* map )
 osg::Vec3dArray*
 MaskLayer::getOrCreateMaskBoundary( float heightScale, const SpatialReference *srs, ProgressCallback* progress )
 {
+    OpenThreads::ScopedLock< OpenThreads::Mutex > lock( _mutex );
     if ( _maskSource.valid() )
     {
         // if the model source has changed, regenerate the node.
diff --git a/src/osgEarth/MaskNode b/src/osgEarth/MaskNode
index 868c87b..e0fab45 100644
--- a/src/osgEarth/MaskNode
+++ b/src/osgEarth/MaskNode
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarth/MaskNode.cpp b/src/osgEarth/MaskNode.cpp
index b4f9672..cee7e96 100644
--- a/src/osgEarth/MaskNode.cpp
+++ b/src/osgEarth/MaskNode.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarth/MaskSource b/src/osgEarth/MaskSource
index b54357a..1f9fea8 100644
--- a/src/osgEarth/MaskSource
+++ b/src/osgEarth/MaskSource
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarth/MaskSource.cpp b/src/osgEarth/MaskSource.cpp
index 422f2db..239059d 100644
--- a/src/osgEarth/MaskSource.cpp
+++ b/src/osgEarth/MaskSource.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarth/MemCache b/src/osgEarth/MemCache
index b1cd4a7..ff16446 100644
--- a/src/osgEarth/MemCache
+++ b/src/osgEarth/MemCache
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarth/MemCache.cpp b/src/osgEarth/MemCache.cpp
index a475ed1..e930841 100644
--- a/src/osgEarth/MemCache.cpp
+++ b/src/osgEarth/MemCache.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarth/MimeTypes.cpp b/src/osgEarth/MimeTypes.cpp
index f7061af..c1312b7 100644
--- a/src/osgEarth/MimeTypes.cpp
+++ b/src/osgEarth/MimeTypes.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarth/ModelLayer b/src/osgEarth/ModelLayer
index 3106b29..e716a04 100644
--- a/src/osgEarth/ModelLayer
+++ b/src/osgEarth/ModelLayer
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -23,6 +23,7 @@
 #include <osgEarth/Common>
 #include <osgEarth/AlphaEffect>
 #include <osgEarth/Layer>
+#include <osgEarth/CachePolicy>
 #include <osgEarth/Config>
 #include <osgEarth/ModelSource>
 #include <osgEarth/MaskSource>
@@ -111,6 +112,12 @@ 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; }
+
 
     public:
         virtual Config getConfig() const;
@@ -129,6 +136,7 @@ namespace osgEarth
         optional<MaskSourceOptions>  _maskOptions;
         optional<unsigned>           _maskMinLevel;
         optional<bool>               _terrainPatch;
+        optional<CachePolicy>        _cachePolicy;
     };
 
     /**
@@ -224,16 +232,26 @@ namespace osgEarth
          * Creates the scene graph representing this model layer for the given Map,
          * or returns one that already exists.
          */
-        osg::Node* getOrCreateSceneGraph( 
-            const Map*            map, 
-            const osgDB::Options* dbOptions,
-            ProgressCallback*     progress );
+        osg::Node* getOrCreateSceneGraph(const Map* map, ProgressCallback* progress);
 
         /**
          * Gets a scene graph what was previously created with getOrCreateSceneGraph.
          */
         osg::Node* getSceneGraph(const UID& mapUID) const;
+        
+        /**
+         * This layer's caching policy
+         */
+        void setCachePolicy(const CachePolicy& cachePolicy);
+        const CachePolicy& getCachePolicy() const;
 
+    public: // deprecated
+        
+        /** @deprecated */
+        osg::Node* getOrCreateSceneGraph( 
+            const Map*            map, 
+            const osgDB::Options* dbOptions,
+            ProgressCallback*     progress ) { return getOrCreateSceneGraph(map, progress); }
 
     public: // properties
 
@@ -272,6 +290,7 @@ namespace osgEarth
         ModelLayerCallbackList        _callbacks;
         osg::ref_ptr<AlphaEffect>     _alphaEffect;
         osg::ref_ptr<osg::Vec3dArray> _maskBoundary;
+        osg::ref_ptr<osgDB::Options>  _dbOptions;
 
         typedef fast_map<UID, osg::observer_ptr<osg::Node> > Graphs;
         Graphs _graphs;
@@ -283,6 +302,8 @@ namespace osgEarth
         void copyOptions();
 
         void setLightingEnabledNoLock(bool value);
+        
+        void initializeCachePolicy(const osgDB::Options* options);
     };
 
     typedef std::vector< osg::ref_ptr<ModelLayer> > ModelLayerVector;
diff --git a/src/osgEarth/ModelLayer.cpp b/src/osgEarth/ModelLayer.cpp
index 751599f..956cd54 100644
--- a/src/osgEarth/ModelLayer.cpp
+++ b/src/osgEarth/ModelLayer.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -38,7 +38,7 @@ namespace
     {
         NodeModelSource( osg::Node* node ) : _node(node) { }
 
-        osg::Node* createNodeImplementation(const Map* map, const osgDB::Options* dbOptions, ProgressCallback* progress) {
+        osg::Node* createNodeImplementation(const Map* map, ProgressCallback* progress) {
             return _node.get();
         }
 
@@ -73,6 +73,7 @@ ModelLayerOptions::setDefaults()
     _opacity.init     ( 1.0f );
     _maskMinLevel.init( 0 );
     _terrainPatch.init( false );
+    _cachePolicy.init ( CachePolicy() );
 }
 
 Config
@@ -86,7 +87,9 @@ ModelLayerOptions::getConfig() const
     conf.updateIfSet( "lighting",       _lighting );
     conf.updateIfSet( "opacity",        _opacity );
     conf.updateIfSet( "mask_min_level", _maskMinLevel );
-    conf.updateIfSet( "patch",          _terrainPatch );    
+    conf.updateIfSet( "patch",          _terrainPatch );  
+
+    conf.updateObjIfSet( "cache_policy", _cachePolicy );  
 
     // Merge the ModelSource options
     if ( driver().isSet() )
@@ -110,6 +113,8 @@ ModelLayerOptions::fromConfig( const Config& conf )
     conf.getIfSet( "mask_min_level", _maskMinLevel );
     conf.getIfSet( "patch",          _terrainPatch );
 
+    conf.getObjIfSet( "cache_policy", _cachePolicy );  
+
     if ( conf.hasValue("driver") )
         driver() = ModelSourceOptions(conf);
 
@@ -172,12 +177,18 @@ ModelLayer::initialize(const osgDB::Options* dbOptions)
     if ( !_modelSource.valid() && _initOptions.driver().isSet() )
     {
         OE_INFO << LC << "Initializing model layer \"" << getName() << "\", driver=\"" << _initOptions.driver()->getDriver() << "\"" << std::endl;
+        
+        // set up the db options and caching policy first
+        _dbOptions = Registry::instance()->cloneOrCreateOptions(dbOptions);
+        initializeCachePolicy( _dbOptions.get() );
 
         // the model source:
         _modelSource = ModelSourceFactory::create( *_initOptions.driver() );
         if ( _modelSource.valid() )
         {
-            _modelSource->initialize( dbOptions );
+            _modelSource->setName( this->getName() );
+
+            _modelSource->initialize( _dbOptions.get() );
 
             // the mask, if there is one:
             if ( !_maskSource.valid() && _initOptions.maskOptions().isSet() )
@@ -187,7 +198,7 @@ ModelLayer::initialize(const osgDB::Options* dbOptions)
                 _maskSource = MaskSourceFactory::create( *_initOptions.maskOptions() );
                 if ( _maskSource.valid() )
                 {
-                    _maskSource->initialize( dbOptions );
+                    _maskSource->initialize( _dbOptions.get() );
                 }
                 else
                 {
@@ -198,6 +209,37 @@ ModelLayer::initialize(const osgDB::Options* dbOptions)
     }
 }
 
+void
+ModelLayer::initializeCachePolicy(const osgDB::Options* options)
+{
+    // Start with the cache policy passed in by the Map.
+    optional<CachePolicy> cp;
+    CachePolicy::fromOptions(options, cp);
+
+    // if this layer specifies cache policy info, that will override 
+    // whatever the map passed in:
+    if ( _initOptions.cachePolicy().isSet() )
+        cp->mergeAndOverride( _initOptions.cachePolicy() );
+
+    // finally resolve with global overrides:
+    Registry::instance()->resolveCachePolicy( cp );
+
+    setCachePolicy( cp.get() );
+}
+
+void
+ModelLayer::setCachePolicy( const CachePolicy& cp )
+{
+    _runtimeOptions.cachePolicy() = cp;
+    _runtimeOptions.cachePolicy()->apply( _dbOptions.get() );
+}
+
+const CachePolicy&
+ModelLayer::getCachePolicy() const
+{
+    return _runtimeOptions.cachePolicy().value();
+}
+
 osg::Node*
 ModelLayer::getSceneGraph(const UID& mapUID) const
 {
@@ -207,9 +249,8 @@ ModelLayer::getSceneGraph(const UID& mapUID) const
 }
 
 osg::Node*
-ModelLayer::getOrCreateSceneGraph(const Map*            map,
-                                  const osgDB::Options* dbOptions,
-                                  ProgressCallback*     progress )
+ModelLayer::getOrCreateSceneGraph(const Map*        map,
+                                  ProgressCallback* progress )
 {
     // exclusive lock for cache lookup/update.
     Threading::ScopedMutexLock lock( _mutex );
@@ -225,31 +266,17 @@ ModelLayer::getOrCreateSceneGraph(const Map*            map,
 
     if ( _modelSource.valid() )
     {
-        node = _modelSource->createNode( map, dbOptions, progress );
+        node = _modelSource->createNode( map, progress );
 
         if ( node )
         {
-            if ( _runtimeOptions.visible().isSet() )
-            {
-                node->setNodeMask( *_runtimeOptions.visible() ? ~0 : 0 );
-            }
-
             if ( _runtimeOptions.lightingEnabled().isSet() )
             {
                 setLightingEnabledNoLock( *_runtimeOptions.lightingEnabled() );
             }
 
-            if ( _modelSource->getOptions().depthTestEnabled() == false )
-            {
-                osg::StateSet* ss = node->getOrCreateStateSet();
-                ss->setAttributeAndModes( new osg::Depth( osg::Depth::ALWAYS ) );
-                ss->setRenderBinDetails( 99999, "RenderBin" ); //TODO: configure this bin ...
-            }
-
             _modelSource->sync( _modelSourceRev );
 
-            // save an observer reference to the node so we can change the visibility/lighting/etc.
-            //_nodeSet.insert( node );
 
             // add a parent group for shaders/effects to attach to without overwriting any model programs directly
             osg::Group* group = new osg::Group();
@@ -257,6 +284,20 @@ ModelLayer::getOrCreateSceneGraph(const Map*            map,
             _alphaEffect->attach( group->getOrCreateStateSet() );
             node = group;
 
+            // Toggle visibility if necessary
+            if ( _runtimeOptions.visible().isSet() )
+            {
+                node->setNodeMask( *_runtimeOptions.visible() ? ~0 : 0 );
+            }
+
+            // Handle disabling depth testing
+            if ( _modelSource->getOptions().depthTestEnabled() == false )
+            {
+                osg::StateSet* ss = node->getOrCreateStateSet();
+                ss->setAttributeAndModes( new osg::Depth( osg::Depth::ALWAYS ) );
+                ss->setRenderBinDetails( 99999, "RenderBin" ); //TODO: configure this bin ...
+            }
+
             // save it.
             _graphs[map->getUID()] = node;
         }
diff --git a/src/osgEarth/ModelSource b/src/osgEarth/ModelSource
index 8b8caf8..e8c54f9 100644
--- a/src/osgEarth/ModelSource
+++ b/src/osgEarth/ModelSource
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -90,18 +90,16 @@ namespace osgEarth
         virtual ~ModelSource();
 
         osg::Node* createNode(
-            const Map*            map,
-            const osgDB::Options* dbOptions,
-            ProgressCallback*     progress );
+            const Map*        map,
+            ProgressCallback* progress );
 
         /**
          * Subclass implements this method to create a scene graph within the
          * context of the provided Map.
          */
         virtual osg::Node* createNodeImplementation(
-            const Map*            map,
-            const osgDB::Options* dbOptions,
-            ProgressCallback*     progress ) =0;
+            const Map*        map,
+            ProgressCallback* progress ) =0;
 
         /**
          * Add a post processing operation that will run (possibly in a pager
diff --git a/src/osgEarth/ModelSource.cpp b/src/osgEarth/ModelSource.cpp
index 7107f3c..9eb16ac 100644
--- a/src/osgEarth/ModelSource.cpp
+++ b/src/osgEarth/ModelSource.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -85,11 +85,10 @@ ModelSource::~ModelSource()
 
 
 osg::Node* 
-ModelSource::createNode(const Map*            map,
-                        const osgDB::Options* dbOptions,
-                        ProgressCallback*     progress )
+ModelSource::createNode(const Map*        map,
+                        ProgressCallback* progress )
 {
-    osg::Node* node = createNodeImplementation(map, dbOptions, progress);
+    osg::Node* node = createNodeImplementation(map, progress);
     if ( node )
     {
         firePostProcessors( node );
diff --git a/src/osgEarth/NativeProgramAdapter b/src/osgEarth/NativeProgramAdapter
new file mode 100644
index 0000000..9478884
--- /dev/null
+++ b/src/osgEarth/NativeProgramAdapter
@@ -0,0 +1,149 @@
+/* -*-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.
+ *
+ * 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_NATIVE_PROGRAM_ADAPTER
+#define OSGEARTH_NATIVE_PROGRAM_ADAPTER
+
+#include <osg/State>
+#include <osg/Uniform>
+#include <osg/GL2Extensions>
+#include <osg/Referenced>
+#include <osg/MixinVector>
+#include <osgEarth/Notify>
+#include <vector>
+
+#undef  LC
+#define LC "[NativeProgramAdapter] "
+
+//#undef  OE_DEBUG
+//#define OE_DEBUG OE_INFO
+
+namespace osgEarth
+{
+    /**
+     * Wraps a native glProgram handle so we can earch it form uniforms and
+     * apply values to them.
+     */
+    class NativeProgramAdapter : public osg::Referenced
+    {
+    public:
+        /** Create a program adapter under the current state that wraps the provided glProgram handle. */
+        NativeProgramAdapter(const osg::State* state, GLint handle, const char* prefix = 0L)
+        {
+            OE_DEBUG << LC << "Create adapter for glProgram " << handle << "\n";
+
+            _handle = handle;
+            _ext    = osg::GL2Extensions::Get( state->getContextID(), true );
+
+            GLint   numUniforms = 0;
+            GLsizei maxLen      = 0;
+
+            _ext->glGetProgramiv( _handle, GL_ACTIVE_UNIFORMS,      &numUniforms );
+            _ext->glGetProgramiv( _handle, GL_ACTIVE_UNIFORM_MAX_LENGTH, &maxLen );
+
+            if ( (numUniforms > 0) && (maxLen > 1) )
+            {
+                GLint   size = 0;
+                GLenum  type = 0;
+                GLchar* name = new GLchar[maxLen];
+
+                for( GLint i = 0; i < numUniforms; ++i )
+                {
+                    _ext->glGetActiveUniform( _handle, i, maxLen, 0, &size, &type, name );      
+                    if ( !prefix || (::strlen(name) >= ::strlen(prefix) && ::strncmp(name, prefix, ::strlen(prefix)) == 0) )
+                    {
+                        GLint loc = _ext->glGetUniformLocation( _handle, name );
+                        if ( loc != -1 )
+                        {
+                            _uniformLocations[osg::Uniform::getNameID(reinterpret_cast<const char*>(name))] = loc;
+                        
+                            OE_DEBUG << LC << "    Uniform = \"" << name << "\", location = " << loc << "\n";
+                        }
+                    }
+                }
+            }
+            else
+            {
+                OE_DEBUG << LC << "    No uniforms found.\n";
+            }
+        }
+
+        void apply(const osg::State* state)
+        {
+            reinterpret_cast<const StateEx*>(state)->applyUniformsToProgram(*this);
+        }
+
+    private:
+        GLint  _handle;
+        typedef std::map<unsigned, GLint> UniformMap;
+        UniformMap _uniformLocations;
+        const osg::GL2Extensions* _ext;
+
+        /** Apply the uniform to this program, optionally calling glUseProgram if necessary. */
+        bool apply(const osg::Uniform* uniform, bool useProgram) const
+        {
+            UniformMap::const_iterator location = _uniformLocations.find( uniform->getNameID() );
+            if ( location != _uniformLocations.end() )
+            {        
+                if ( useProgram )
+                    _ext->glUseProgram( _handle );
+                uniform->apply( _ext, location->second );
+                return true;
+            }
+            else return false;
+        }
+
+        struct StateEx : public osg::State 
+        {
+            void applyUniformsToProgram(const NativeProgramAdapter& adapter) const
+            {
+                bool useProgram = true;
+                for(UniformMap::const_iterator i = _uniformMap.begin(); i != _uniformMap.end(); ++i)
+                {
+                    const UniformStack& as = i->second;
+                    if ( !as.uniformVec.empty() )
+                    {                    
+                        const osg::Uniform* uniform = as.uniformVec.back().first;
+                        if (adapter.apply(uniform, useProgram))
+                            useProgram = false;
+                    }
+                }
+            }
+        };
+
+        friend struct StateEx;
+    };
+
+    /**
+     * Collection of program adapters.
+     */
+    class NativeProgramAdapterCollection: public osg::MixinVector< osg::ref_ptr<NativeProgramAdapter> >
+    {
+    public:
+        void apply(const osg::State* state) const
+        {
+            for(const_iterator i = begin(); i != end(); ++i )
+                i->get()->apply( state );
+        }
+    };
+
+}
+
+#undef LC
+
+#endif // OSGEARTH_NATIVE_PROGRAM_ADAPTER
\ No newline at end of file
diff --git a/src/osgEarth/NodeUtils b/src/osgEarth/NodeUtils
index b9e1cdd..acf4c92 100644
--- a/src/osgEarth/NodeUtils
+++ b/src/osgEarth/NodeUtils
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/osgEarth/NodeUtils.cpp b/src/osgEarth/NodeUtils.cpp
index 08e1a4f..a3c147e 100644
--- a/src/osgEarth/NodeUtils.cpp
+++ b/src/osgEarth/NodeUtils.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarth/Notify b/src/osgEarth/Notify
index 6bc82f6..d589f5c 100644
--- a/src/osgEarth/Notify
+++ b/src/osgEarth/Notify
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarth/Notify.cpp b/src/osgEarth/Notify.cpp
index b6d2331..1f4c6ab 100644
--- a/src/osgEarth/Notify.cpp
+++ b/src/osgEarth/Notify.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarth/ObjectIndex b/src/osgEarth/ObjectIndex
new file mode 100644
index 0000000..8db7011
--- /dev/null
+++ b/src/osgEarth/ObjectIndex
@@ -0,0 +1,213 @@
+/* -*-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_OBJECT_INDEX_H
+#define OSGEARTH_OBJECT_INDEX_H
+
+#include <osgEarth/Common>
+#include <osgEarth/ThreadingUtils>
+#include <osgEarth/ShaderLoader>
+#include <osg/Version>
+#include <osg/Drawable>
+#include <osg/Array>
+#include <OpenThreads/Atomic>
+#include <algorithm>
+
+#define OSGEARTH_OBJECTID_EMPTY   (ObjectID)0
+#define OSGEARTH_OBJECTID_TERRAIN (ObjectID)1
+
+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.
+     */
+    template<typename T>
+    class ObjectIndexBuilder
+    {
+    public:
+        /**
+         * Inserts the object into the index, and tags the drawable with its object id.
+         * Returns the ID of the object.
+         */
+        virtual ObjectID tagDrawable(osg::Drawable* drawable, T* object) =0;
+
+        /**
+         * Inserts the object into the index, and tags all the drawalbes under the
+         * specified node with the object ID. Returns the Object ID.
+         */
+        virtual ObjectID tagAllDrawables(osg::Node* node, T* object) =0;
+
+        /**
+         * Inserts the object into the index, and tags the Node with a uniform containing
+         * the object id. Returns the Object ID.
+         */
+        virtual ObjectID tagNode(osg::Node* node, T* object) =0;
+    };
+
+
+    /**
+     * Index for tracking objects in the scene graph using vertex
+     * attributes and uniforms.
+     */
+    class OSGEARTH_EXPORT ObjectIndex : public osg::Referenced,
+                                        public ObjectIndexBuilder<osg::Referenced>
+    {
+    public:
+        /** constructs a new index */
+        ObjectIndex();
+
+        /**
+         * Adds an object to the index, and returns a new globally unique
+         * ID for that object. You can then use that UID to tag scene elements
+         * with one of the tag* functions. If the object already exists in the
+         * index, this method will return the UID assigned to it.
+         */
+        ObjectID insert(osg::Referenced* object);
+
+        /**
+         * Finds the object corresponding to a unique ID and places it in "output";
+         * Returns true if found, false if not.
+         */
+        template<typename T>
+        osg::ref_ptr<T> get(ObjectID id) const {
+            Threading::ScopedMutexLock lock(_mutex);
+            return dynamic_cast<T*>( getImpl(id) );
+        }   
+
+        /**
+         * Removes the object corresponding the the unique ID form the index.
+         */
+        void remove(ObjectID id);
+
+        /**
+         * Removes a collection of objects from the index all at once.
+         */
+        template<typename ForwardIter>
+        void remove(ForwardIter i0, ForwardIter i1) {
+            _mutex.lock();
+            for(ForwardIter i = i0; i != i1; ++i) removeImpl( *i );
+            _mutex.unlock();
+        }
+
+        /**
+         * The vertex attribute binding location to use when indexing geoemtry.
+         * Warning: Changing this after tagging objects will cause undefined results.
+         */
+        void setObjectIDAtrribLocation(int value);
+        int getObjectIDAttribLocation() const { return _attribLocation; }
+
+        /**
+         * The name of the uniform used to tag nodes when tagNode() is called.
+         */
+        const std::string& getObjectIDUniformName() const { return _oidUniformName; }
+
+        /**
+         * The name of the ObjectID vertex attribute in the ObjectIndex shaders.
+         */
+        const std::string& getObjectIDAttribName() const { return _attribName; }
+
+        /**
+         * Convenience fuction to install shader components that will set the vertex-stage
+         * ObjectID variable whenever detected in the geometry. This will automatically use
+         * the ObjectID AttribLocation as set in this object.
+         *
+         * Returns false if the method fails for any reason (e.g., vp is NULL)
+         */
+        bool loadShaders(VirtualProgram* vp) const;
+
+        /**
+         * The ShaderPackage that includes the index initialization shaders. installShaders()
+         * calls this internally to get the virtual program components.
+         */
+        const ShaderPackage& getShaderPackage() const { return _shaders; }
+
+
+    public: // ObjectIndexBuilder<osg::Referenced>
+
+        /**
+         * Inserts the object into the index, and tags the drawable with its object id.
+         * Returns the ID of the object.
+         */
+        ObjectID tagDrawable(osg::Drawable* drawable, osg::Referenced* object);
+
+        /**
+         * Inserts the object into the index, and tags all the drawalbes under the
+         * specified node with the object ID. Returns the Object ID.
+         */
+        ObjectID tagAllDrawables(osg::Node* node, osg::Referenced* object);
+
+        /**
+         * Inserts the object into the index, and tags the Node with a uniform containing
+         * the object id. Returns the Object ID.
+         */
+        ObjectID tagNode(osg::Node* node, osg::Referenced* object);
+
+
+    public: // Raw tagging methods.
+
+        /**
+         * Tags the vertices in a drawable with the object identifier.
+         */
+        void tagDrawable(osg::Drawable* drawable, ObjectID id) const;
+
+        /**
+         * Tags the vertices in all Drawables until a node with the object identifier.
+         */
+        void tagAllDrawables(osg::Node* node, ObjectID id) const;
+
+        /**
+         * Tags a node with an object identifier. This simply puts a uniform on the
+         * node and does NOT tag any actual vertices. This is only useful if you want
+         * to tag an entire model and are not planning to merge geometries.
+         */
+        void tagNode(osg::Node* node, ObjectID id) const;
+
+
+    protected:
+        virtual ~ObjectIndex() { }
+        
+        typedef std::map<ObjectID, osg::ref_ptr<osg::Referenced> > IndexMap;
+
+        IndexMap                 _index;
+        int                      _attribLocation;
+        std::string              _oidUniformName;
+        mutable Threading::Mutex _mutex;
+        OpenThreads::Atomic      _idGen;
+        ShaderPackage            _shaders;
+        std::string              _attribName;
+
+        ObjectID insertImpl(osg::Referenced*);
+        void removeImpl(ObjectID id);
+        osg::Referenced* getImpl(ObjectID id) const;
+    };
+
+} // namespace osgEarth
+
+#endif // OSGEARTH_OBJECT_INDEX_H
diff --git a/src/osgEarth/ObjectIndex.cpp b/src/osgEarth/ObjectIndex.cpp
new file mode 100644
index 0000000..85a46fa
--- /dev/null
+++ b/src/osgEarth/ObjectIndex.cpp
@@ -0,0 +1,234 @@
+/* -*-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/ObjectIndex>
+#include <osgEarth/Registry>
+#include <osg/NodeVisitor>
+#include <osg/Uniform>
+#include <osg/Geode>
+#include <osg/Geometry>
+
+using namespace osgEarth;
+
+#define LC "[ObjectIndex] "
+
+//#undef OE_DEBUG
+//#define OE_DEBUG OE_NOTICE
+
+// Object IDs under this reserved
+#define STARTING_OBJECT_ID 10
+
+namespace
+{
+    const char* indexVertexInit =
+        "#version 330\n"
+
+        "#pragma vp_entryPoint \"oe_index_setObjectID\" \n"
+        "#pragma vp_location   \"vertex_model\" \n"
+        "#pragma vp_order      \"first\" \n"
+
+        "uniform uint oe_index_objectid_uniform; \n"   // override objectid if > 0
+        "in uint      oe_index_objectid_attr; \n"      // Vertex attribute containing the object ID.
+        "uint         oe_index_objectid; \n"           // Stage global containing the Object ID.
+
+        "void oe_index_setObjectID(inout vec4 vertex) \n"
+        "{ \n"
+        "    if ( oe_index_objectid_uniform > 0u ) \n"
+        "        oe_index_objectid = oe_index_objectid_uniform; \n"
+        "    else if ( oe_index_objectid_attr > 0u ) \n"
+        "        oe_index_objectid = oe_index_objectid_attr; \n"
+        "    else \n"
+        "        oe_index_objectid = 0u; \n"
+        "} \n";
+}
+
+ObjectIndex::ObjectIndex() :
+_idGen( STARTING_OBJECT_ID )
+{
+    _attribName     = "oe_index_objectid_attr";
+    _attribLocation = osg::Drawable::SECONDARY_COLORS;
+    _oidUniformName = "oe_index_objectid_uniform";
+
+    // set up the shader package.
+    _shaders.add( "ObjectIndex.vert.glsl", indexVertexInit );
+}
+
+bool
+ObjectIndex::loadShaders(VirtualProgram* vp) const
+{
+    if ( vp )
+    {
+        _shaders.loadAll( vp );
+        vp->addBindAttribLocation( getObjectIDAttribName(), getObjectIDAttribLocation() );
+    }
+    return vp != 0L;
+}
+
+void
+ObjectIndex::setObjectIDAtrribLocation(int value)
+{
+    if ( _index.size() == 0 )
+    {
+        _attribLocation = value;
+    } 
+    else
+    {
+        OE_WARN << LC << "Illegal: Cannot change the attrib location once index is in use.\n";
+    }
+}
+
+ObjectID
+ObjectIndex::insert(osg::Referenced* object)
+{
+    Threading::ScopedMutexLock excl( _mutex );
+    return insertImpl( object );
+}
+
+ObjectID
+ObjectIndex::insertImpl(osg::Referenced* object)
+{
+    // internal: assume mutex is locked
+    ObjectID id = ++_idGen;
+    _index[id] = object;
+    OE_DEBUG << LC << "Insert " << id << "; size = " << _index.size() << "\n";
+    return id;
+}
+
+osg::Referenced*
+ObjectIndex::getImpl(ObjectID id) const
+{
+    // assume the mutex is locked
+    IndexMap::const_iterator i = _index.find(id);
+    return i != _index.end() ? i->second.get() : 0L;
+}
+
+void
+ObjectIndex::remove(ObjectID id)
+{
+    Threading::ScopedMutexLock excl(_mutex);
+    removeImpl(id);
+}
+
+void
+ObjectIndex::removeImpl(ObjectID id)
+{
+    // internal - assume mutex is locked
+    _index.erase( id );
+    OE_DEBUG << "Remove " << id << "; size = " << _index.size() << "\n";
+
+}
+
+ObjectID
+ObjectIndex::tagDrawable(osg::Drawable* drawable, osg::Referenced* object)
+{
+    Threading::ScopedMutexLock lock(_mutex);
+    ObjectID oid = insertImpl(object);
+    tagDrawable(drawable, oid);
+    return oid;
+}
+
+void
+ObjectIndex::tagDrawable(osg::Drawable* drawable, ObjectID id) const
+{
+    if ( drawable == 0L )
+        return;
+
+    osg::Geometry* geom = drawable->asGeometry();
+    if ( !geom )
+        return;
+
+    // add a new integer attributer to store the feautre ID per vertex.
+    ObjectIDArray* ids = new ObjectIDArray();
+    geom->setVertexAttribArray    (_attribLocation, ids);
+    geom->setVertexAttribBinding  (_attribLocation, osg::Geometry::BIND_PER_VERTEX);
+    geom->setVertexAttribNormalize(_attribLocation, false);
+    
+#if OSG_VERSION_GREATER_OR_EQUAL(3,1,8)
+    ids->setPreserveDataType(true);
+#endif
+
+    // The tag is actually FeatureID + 1, to preserve "0" as an "empty" value.
+    // TODO: use a ObjectID generator and mapping instead.
+    ids->assign( geom->getVertexArray()->getNumElements(), id );
+}
+
+namespace
+{
+    struct FindAndTagDrawables : public osg::NodeVisitor
+    {
+        FindAndTagDrawables(const ObjectIndex* index, ObjectID id) : _index(index), _id(id)
+        {
+            setTraversalMode(TRAVERSE_ALL_CHILDREN);
+            setNodeMaskOverride(~0);
+        }
+
+        void apply(osg::Geode& geode)
+        {
+            for(unsigned i=0; i<geode.getNumDrawables(); ++i)
+            {
+                _index->tagDrawable( geode.getDrawable(i), _id );
+            }
+            traverse( geode );
+        }
+
+        const ObjectIndex* _index;
+        ObjectID           _id;
+    };
+}
+
+ObjectID
+ObjectIndex::tagAllDrawables(osg::Node* node, osg::Referenced* object)
+{
+    Threading::ScopedMutexLock lock(_mutex);
+    ObjectID oid = insertImpl(object);
+    tagAllDrawables(node, oid);
+    return oid;
+}
+
+void
+ObjectIndex::tagAllDrawables(osg::Node* node, ObjectID id) const
+{
+    if ( node )
+    {
+        FindAndTagDrawables visitor(this, id);
+        node->accept( visitor );
+    }
+}
+
+ObjectID
+ObjectIndex::tagNode(osg::Node* node, osg::Referenced* object)
+{
+    Threading::ScopedMutexLock lock(_mutex);
+    ObjectID oid = insertImpl(object);
+    tagNode(node, oid);
+    return oid;
+}
+
+void
+ObjectIndex::tagNode(osg::Node* node, ObjectID id) const
+{
+    if ( node )
+    {
+        osg::StateSet* stateSet = node->getOrCreateStateSet();
+        stateSet->addUniform( new osg::Uniform(_oidUniformName.c_str(), id) );
+    }
+}
diff --git a/src/osgEarth/OverlayDecorator b/src/osgEarth/OverlayDecorator
index 193274e..69e92da 100644
--- a/src/osgEarth/OverlayDecorator
+++ b/src/osgEarth/OverlayDecorator
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/osgEarth/OverlayDecorator.cpp b/src/osgEarth/OverlayDecorator.cpp
index 9d05838..c50ff5c 100755
--- a/src/osgEarth/OverlayDecorator.cpp
+++ b/src/osgEarth/OverlayDecorator.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/osgEarth/OverlayNode.cpp b/src/osgEarth/OverlayNode.cpp
index e6ad651..6d25e16 100644
--- a/src/osgEarth/OverlayNode.cpp
+++ b/src/osgEarth/OverlayNode.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarth/PhongLightingEffect b/src/osgEarth/PhongLightingEffect
index 8a23169..6c14caf 100644
--- a/src/osgEarth/PhongLightingEffect
+++ b/src/osgEarth/PhongLightingEffect
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/osgEarth/PhongLightingEffect.cpp b/src/osgEarth/PhongLightingEffect.cpp
index 615b2b2..b40adf0 100644
--- a/src/osgEarth/PhongLightingEffect.cpp
+++ b/src/osgEarth/PhongLightingEffect.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
@@ -24,6 +27,11 @@
 #include <osgEarth/StringUtils>
 #include <osgEarth/VirtualProgram>
 
+// GL_LIGHTING is not always defined on GLES so define it.
+#ifndef GL_LIGHTING
+    #define GL_LIGHTING 0x0B50
+#endif
+
 using namespace osgEarth;
 
 namespace
@@ -100,14 +108,15 @@ namespace
 
         "uniform bool oe_mode_GL_LIGHTING; \n"
         "varying vec3 oe_phong_vertexView3; \n"
-        "varying vec3 oe_Normal; \n"
+
+        "vec3 oe_global_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 = normalize(oe_Normal); \n"
+        "    vec3 N = normalize(oe_global_Normal); \n"
         
         "    vec4 ambient = gl_FrontLightProduct[0].ambient; \n"
 
@@ -173,8 +182,8 @@ 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 );
-        vp->setFunction( "oe_phong_fragment", Phong_Fragment, ShaderComp::LOCATION_FRAGMENT_LIGHTING );
+        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() );
     }
diff --git a/src/osgEarth/Picker b/src/osgEarth/Picker
new file mode 100644
index 0000000..d6b15df
--- /dev/null
+++ b/src/osgEarth/Picker
@@ -0,0 +1,62 @@
+/* -*-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.
+ *
+ * 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_PICKER_H
+#define OSGEARTH_PICKER_H 1
+
+#include <osgEarth/Common>
+#include <osgEarth/ObjectIndex>
+#include <osgGA/GUIEventHandler>
+
+namespace osgEarth
+{
+    /**
+     * Base class for an object picker. Header only (no export)
+     */
+    class Picker : public osgGA::GUIEventHandler
+    {
+    public:
+        struct Callback : public osg::Referenced
+        {
+            // Called when an ID is hit
+            virtual void onHit(ObjectID id) { }
+
+            // Called when a query results in nothing
+            virtual void onMiss() { }
+
+            // Called to ask whether to perform a query based on events
+            virtual bool accept(const osgGA::GUIEventAdapter& ea, const osgGA::GUIActionAdapter& aa) { return false; }
+        };
+
+    public:
+        
+        /**
+         * Initiate a pick. The picker will invoke the callback when the pick is complete.
+         * Returns true is the pick was successfully queued.
+         */
+        virtual bool pick(osg::View* view, float mouseX, float mouseY, Callback* callback) = 0;
+
+    protected:
+
+        virtual ~Picker() { }
+    };
+
+} // namespace osgEarth
+
+#endif // OSGEARTH_PICKER
diff --git a/src/osgEarth/Pickers b/src/osgEarth/Pickers
deleted file mode 100644
index aa0765d..0000000
--- a/src/osgEarth/Pickers
+++ /dev/null
@@ -1,112 +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_PICKING_UTILS_H
-#define OSGEARTH_PICKING_UTILS_H
-
-#include <osgEarth/Common>
-#include <osgEarth/PrimitiveIntersector>
-#include <osgViewer/View>
-
-namespace osgEarth
-{
-
-    /**
-     * Utility for picking objects from the scene.
-     */
-    class OSGEARTH_EXPORT Picker
-    {
-    public:
-        typedef osgEarth::PrimitiveIntersector::Intersection Hit;
-        typedef osgEarth::PrimitiveIntersector::Intersections Hits;
-
-        enum Limit {
-            NO_LIMIT,
-            LIMIT_ONE_PER_DRAWABLE,
-            LIMIT_ONE,
-            LIMIT_NEAREST
-        };
-
-    public:
-        /** 
-         * Constructs a picker that will pick data from the given view,
-         * and restrict its search to the given graph.
-         *
-         * @param view          View under which to pick
-         * @param graph         Subgraph within which to restrict the pick
-         * @param traversalMask Node mask to apply to the pick visitor
-         * @param buffer        Pick buffer around the click (pixels)
-         */
-        Picker( 
-            osgViewer::View* view,
-            osg::Node*       graph         =0L, 
-            unsigned         traversalMask =~0,
-            float            buffer        =5.0f,
-            Limit            limit         =LIMIT_NEAREST);
-
-        /** dtor */
-        virtual ~Picker() { }
-
-        /**
-         * Sets the node mask to apply to the pick visitor. This lets you
-         * limit the subgraph that the picker searches.
-         */
-        void setTraversalMask(unsigned mask);
-
-        /**
-         * Sets the search buffer around the mouse.
-         */
-        void setBuffer(float buffer);
-
-        /**
-         * Sets the hit limit mode.
-         */
-        void setLimit(const Limit& limit);
-
-        /**
-         * Picks geometry under the specified viewport coordinates. The results
-         * are stores in "results". You can typically get the mouseX and mouseY
-         * from osgGA::GUIEventAdapter getX() and getY().
-         */
-        bool pick( float mouseX, float mouseY, Hits& results ) const;
-
-        /**
-         * Finds and returns the lowest node of type "T" in a hit, or 0L if no such
-         * node exists.
-         */
-        template<typename T>
-        T* getNode( const Hit& hit ) const {
-            for( osg::NodePath::const_reverse_iterator i = hit.nodePath.rbegin(); i != hit.nodePath.rend(); ++i ) {
-               T* node = dynamic_cast<T*>(*i);
-               if ( node ) return node;
-            }
-            return 0L;
-        }
-
-
-    protected:
-        osgViewer::View*              _view;
-        osg::ref_ptr<osg::Node>       _root;
-        osg::NodePath                 _path;
-        unsigned                      _travMask;
-        float                         _buffer;
-        Limit                         _limit;
-    };
-}
-
-#endif // OSGEARTH_PICKING_UTILS_H
diff --git a/src/osgEarth/Pickers.cpp b/src/osgEarth/Pickers.cpp
deleted file mode 100644
index 24f4578..0000000
--- a/src/osgEarth/Pickers.cpp
+++ /dev/null
@@ -1,149 +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 <osgEarth/Pickers>
-#include <osgEarth/PrimitiveIntersector>
-
-#define LC "[Picker] "
-
-using namespace osgEarth;
-
-Picker::Picker( osgViewer::View* view, osg::Node* root, unsigned travMask, float buffer, Limit limit ) :
-_view    ( view ),
-_root    ( root ),
-_travMask( travMask ),
-_buffer  ( buffer ),
-_limit   ( limit )
-{
-    if ( root )
-        _path = root->getParentalNodePaths()[0];
-}
-
-void
-Picker::setLimit(const Picker::Limit& value)
-{
-    _limit = value;
-}
-
-void
-Picker::setTraversalMask(unsigned value)
-{
-    _travMask = value;
-}
-
-void
-Picker::setBuffer(float value)
-{
-    _buffer = value;
-}
-
-bool
-Picker::pick( float x, float y, Hits& results ) const
-{
-    float local_x, local_y = 0.0;
-    const osg::Camera* camera = _view->getCameraContainingPosition(x, y, local_x, local_y);
-    if ( !camera )
-        camera = _view->getCamera();
-
-    osg::ref_ptr<osgEarth::PrimitiveIntersector> picker;
-
-    double buffer_x = _buffer, buffer_y = _buffer;
-    if ( camera->getViewport() )
-    {
-        double aspectRatio = camera->getViewport()->width()/camera->getViewport()->height();
-        buffer_x *= aspectRatio;
-        buffer_y /= aspectRatio;
-    }
-
-    osg::Matrix windowMatrix;
-
-    if ( _root.valid() )
-    {
-        osg::Matrix modelMatrix;
-
-        if (camera->getViewport())
-        {
-            windowMatrix = camera->getViewport()->computeWindowMatrix();
-            modelMatrix.preMult( windowMatrix );
-        }
-
-        modelMatrix.preMult( camera->getProjectionMatrix() );
-        modelMatrix.preMult( camera->getViewMatrix() );
-
-        osg::NodePath prunedNodePath( _path.begin(), _path.end()-1 );
-        modelMatrix.preMult( osg::computeWorldToLocal(prunedNodePath) );
-
-        osg::Matrix modelInverse;
-        modelInverse.invert(modelMatrix);
-
-        osg::Vec3d startLocal(local_x, local_y, 0.0);
-        osg::Vec3d startModel = startLocal * modelInverse;
-
-        osg::Vec3d endLocal(local_x, local_y, 1.0);
-        osg::Vec3d endModel = endLocal * modelInverse;
-
-        osg::Vec3d bufferLocal(local_x + buffer_x, local_y + buffer_y, 0.0);
-        osg::Vec3d bufferModel = bufferLocal * modelInverse;
-
-        double buffer = osg::maximum((bufferModel - startModel).length(), 5.0);  //TODO: Setting a minimum of 4.0 may need revisited
-
-        OE_DEBUG
-            << "local_x:" << local_x << ", local_y:" << local_y
-            << ", buffer_x:" << buffer_x << ", buffer_y:" << buffer_y
-            << ", bm.x:" << bufferModel.x() << ", bm.y:" << bufferModel.y()
-            << ", bm.z:" << bufferModel.z()
-            << ", BUFFER: " << buffer
-            << std::endl;
-
-        picker = new osgEarth::PrimitiveIntersector(osgUtil::Intersector::MODEL, startModel, endModel, buffer);
-    }
-    else
-    {
-        picker = new osgEarth::PrimitiveIntersector(camera->getViewport() ? osgUtil::Intersector::WINDOW : osgUtil::Intersector::PROJECTION, local_x, local_y, _buffer);
-    }
-
-    //picker->setIntersectionLimit( (osgUtil::Intersector::IntersectionLimit)_limit );
-    osgUtil::IntersectionVisitor iv(picker.get());
-
-    // in MODEL mode, we need to window and proj matrixes in order to support some of the 
-    // features in osgEarth (like Annotation::OrthoNode).
-    if ( _root.valid() )
-    {
-        iv.pushWindowMatrix( new osg::RefMatrix(windowMatrix) );
-        iv.pushProjectionMatrix( new osg::RefMatrix(camera->getProjectionMatrix()) );
-        iv.pushViewMatrix( new osg::RefMatrix(camera->getViewMatrix()) );
-    }
-
-    iv.setTraversalMask( _travMask );
-
-    if ( _root.valid() )
-        _path.back()->accept(iv);
-    else
-        const_cast<osg::Camera*>(camera)->accept(iv);
-
-    if (picker->containsIntersections())
-    {
-        results = picker->getIntersections();
-        return true;
-    }
-    else
-    {
-        results.clear();
-        return false;
-    }
-}
diff --git a/src/osgEarth/PrimitiveIntersector b/src/osgEarth/PrimitiveIntersector
index 7fe0241..b1d6da5 100644
--- a/src/osgEarth/PrimitiveIntersector
+++ b/src/osgEarth/PrimitiveIntersector
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarth/PrimitiveIntersector.cpp b/src/osgEarth/PrimitiveIntersector.cpp
index 17e78b0..f58fa50 100644
--- a/src/osgEarth/PrimitiveIntersector.cpp
+++ b/src/osgEarth/PrimitiveIntersector.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -33,7 +33,7 @@ namespace
 {
 struct PrimitiveIntersection
 {
-    PrimitiveIntersection(unsigned int index, const osg::Vec3d& normal, float r1, const osg::Vec3d* v1, float r2, const osg::Vec3d* v2, float r3, const osg::Vec3d* v3):
+    PrimitiveIntersection(unsigned int index, const osg::Vec3d& normal, float r1, const osg::Vec3* v1, float r2, const osg::Vec3* v2, float r3, const osg::Vec3* v3):
         _index(index),
         _normal(normal),
         _r1(r1),
@@ -44,13 +44,13 @@ struct PrimitiveIntersection
         _v3(v3) {}
 
     unsigned int        _index;
-    const osg::Vec3d     _normal;
+    const osg::Vec3d    _normal;
     float               _r1;
-    const osg::Vec3d*    _v1;
+    const osg::Vec3*    _v1;
     float               _r2;
-    const osg::Vec3d*    _v2;
+    const osg::Vec3*    _v2;
     float               _r3;
-    const osg::Vec3d*    _v3;
+    const osg::Vec3*    _v3;
 
 protected:
 
@@ -97,7 +97,7 @@ struct PrimitiveIntersectorFunctor
     }
 
     //POINT
-    inline void operator () (const osg::Vec3d& p, bool treatVertexDataAsTemporary)
+    inline void operator () (const osg::Vec3& p, bool treatVertexDataAsTemporary)
     {
         if (_limitOneIntersection && _hit) return;
 
@@ -108,14 +108,13 @@ struct PrimitiveIntersectorFunctor
         osg::Vec3d v3 = p - _thickness;
         osg::Vec3d v4 = p + n;
 
-        //this->operator()(v1, v2, v3, v4, treatVertexDataAsTemporary);
-        this->triNoBuffer(v1, v2, v3, treatVertexDataAsTemporary);
+        this->triNoBuffer(v1, v2, v3, &p, 0L, 0L, treatVertexDataAsTemporary);
         --_index;
-        this->triNoBuffer(v1, v3, v4, treatVertexDataAsTemporary);
+        this->triNoBuffer(v1, v3, v4, &p, 0L, 0L, treatVertexDataAsTemporary);
     }
 
     //LINE
-    inline void operator () (const osg::Vec3d& v1, const osg::Vec3d& v2, bool treatVertexDataAsTemporary)
+    inline void operator () (const osg::Vec3& v1, const osg::Vec3& v2, bool treatVertexDataAsTemporary)
      {
         if (_limitOneIntersection && _hit) return;
 
@@ -129,17 +128,17 @@ struct PrimitiveIntersectorFunctor
         osg::Vec3d vq3 = v2 - ln*thickness;
         osg::Vec3d vq4 = v1 - ln*thickness;
 
-        //this->operator()(vq1, vq2, vq3, vq4, treatVertexDataAsTemporary);
-        this->triNoBuffer(vq1, vq2, vq3, treatVertexDataAsTemporary);
-        if (_limitOneIntersection && _hit) return;
+        this->triNoBuffer(vq1, vq2, vq3, &v1, &v2, 0L, treatVertexDataAsTemporary);
+        if (_limitOneIntersection && _hit)
+            return;
 
         --_index;
-        this->triNoBuffer(vq1, vq3, vq4, treatVertexDataAsTemporary);
+        this->triNoBuffer(vq1, vq3, vq4, &v1, &v2, 0L, treatVertexDataAsTemporary);
     }
 
     //QUAD
     inline void operator () (
-        const osg::Vec3d& v1, const osg::Vec3d& v2, const osg::Vec3d& v3, const osg::Vec3d& v4, bool treatVertexDataAsTemporary
+        const osg::Vec3d& v1, const osg::Vec3& v2, const osg::Vec3& v3, const osg::Vec3& v4, bool treatVertexDataAsTemporary
         )
     {
         if (_limitOneIntersection && _hit) return;
@@ -153,48 +152,52 @@ struct PrimitiveIntersectorFunctor
 
     //TRIANGLE (buffered)
     inline void operator () (
-        const osg::Vec3d& v1, const osg::Vec3d& v2, const osg::Vec3d& v3, bool treatVertexDataAsTemporary
+        const osg::Vec3& v1, const osg::Vec3& v2, const osg::Vec3& v3, bool treatVertexDataAsTemporary
         )
     {
         if (_limitOneIntersection && _hit) return;
 
         // first do a simple test against the unbuffered triangle:
-        this->triNoBuffer(v1, v2, v3, treatVertexDataAsTemporary);
+        this->triNoBuffer(v1, v2, v3, &v1, &v2, &v3, treatVertexDataAsTemporary);
         if (_limitOneIntersection && _hit) return;
 
+#if 0
         // now buffer each edge and test against that.
         float thickness = _thickness.length();
         osg::Vec3d ln, buf;
 
         osg::Vec3d v12 = v2-v1; ln = _d ^ v12; ln.normalize(); buf = ln*thickness;
         --_index;
-        this->triNoBuffer(v1+buf, v2+buf, v2-buf, treatVertexDataAsTemporary);
+        this->triNoBuffer(v1+buf, v2+buf, v2-buf, &v1, &v2, &v3, treatVertexDataAsTemporary);
         if (_limitOneIntersection && _hit) return;
 
         --_index;
-        this->triNoBuffer(v1+buf, v3-buf, v1-buf, treatVertexDataAsTemporary);
+        this->triNoBuffer(v1+buf, v3-buf, v1-buf, &v1, &v2, &v3, treatVertexDataAsTemporary);
         if (_limitOneIntersection && _hit) return;
 
         osg::Vec3d v23 = v3-v1; ln = _d ^ v23; ln.normalize(); buf = ln*thickness;
         --_index;
-        this->triNoBuffer(v2+buf, v3+buf, v3-buf, treatVertexDataAsTemporary );
+        this->triNoBuffer(v2+buf, v3+buf, v3-buf, &v1, &v2, &v3, treatVertexDataAsTemporary );
         if (_limitOneIntersection && _hit) return;
 
         --_index;
-        this->triNoBuffer(v2+buf, v3-buf, v2-buf, treatVertexDataAsTemporary );
+        this->triNoBuffer(v2+buf, v3-buf, v2-buf, &v1, &v2, &v3, treatVertexDataAsTemporary );
         if (_limitOneIntersection && _hit) return;
 
         osg::Vec3d v31 = v1-v3; ln = _d ^ v31; ln.normalize(); buf = ln*thickness;
         --_index;
-        this->triNoBuffer(v3+buf, v1+buf, v1-buf, treatVertexDataAsTemporary);
+        this->triNoBuffer(v3+buf, v1+buf, v1-buf, &v1, &v2, &v3, treatVertexDataAsTemporary);
         if (_limitOneIntersection && _hit) return;
 
         --_index;
-        this->triNoBuffer(v3+buf, v1-buf, v3-buf, treatVertexDataAsTemporary);
+        this->triNoBuffer(v3+buf, v1-buf, v3-buf, &v1, &v2, &v3, treatVertexDataAsTemporary);
+#endif
     }
 
     //TRIANGLE (no buffer applied)
-    inline void triNoBuffer(const osg::Vec3d& v1, const osg::Vec3d& v2, const osg::Vec3d& v3, bool treatVertexDataAsTemporary)
+    inline void triNoBuffer(const osg::Vec3d& v1, const osg::Vec3d& v2, const osg::Vec3d& v3, 
+                            const osg::Vec3* v1Ptr, const osg::Vec3* v2Ptr, const osg::Vec3* v3Ptr,
+                            bool treatVertexDataAsTemporary)
     {
         ++_index;
 
@@ -297,7 +300,7 @@ struct PrimitiveIntersectorFunctor
         }
         else
         {
-            _intersections.insert(std::pair<const float,PrimitiveIntersection>(r,PrimitiveIntersection(_index-1,normal,r1,&v1,r2,&v2,r3,&v3)));
+            _intersections.insert(std::pair<const float,PrimitiveIntersection>(r,PrimitiveIntersection(_index-1,normal,r1,v1Ptr,r2,v2Ptr,r3,v3Ptr)));
         }
         _hit = true;
     }
@@ -493,10 +496,10 @@ void PrimitiveIntersector::intersect(osgUtil::IntersectionVisitor& iv, osg::Draw
 
             if (geometry)
             {
-                osg::Vec3dArray* vertices = dynamic_cast<osg::Vec3dArray*>(geometry->getVertexArray());
+                osg::Vec3Array* vertices = dynamic_cast<osg::Vec3Array*>(geometry->getVertexArray());
                 if (vertices)
                 {
-                    osg::Vec3d* first = &(vertices->front());
+                    osg::Vec3* first = &(vertices->front());
                     if (triHit._v1)
                     {
                         hit.indexList.push_back(triHit._v1-first);
diff --git a/src/osgEarth/Profile b/src/osgEarth/Profile
index 7307249..89a9a3a 100644
--- a/src/osgEarth/Profile
+++ b/src/osgEarth/Profile
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -139,6 +139,9 @@ namespace osgEarth
         static const Profile* create(
             const ProfileOptions& options );
 
+        static const Profile* createNamed(
+            const std::string& name);
+
         /**
          * Returns true if the profile is properly initialized.
          */
diff --git a/src/osgEarth/Profile.cpp b/src/osgEarth/Profile.cpp
index 3d7ef9e..034a563 100644
--- a/src/osgEarth/Profile.cpp
+++ b/src/osgEarth/Profile.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -228,7 +228,7 @@ Profile::create( const ProfileOptions& options )
     // Check for a "well known named" profile:
     if ( options.namedProfile().isSet() )
     {
-        result = osgEarth::Registry::instance()->getNamedProfile( options.namedProfile().value() );
+        result = Profile::createNamed(options.namedProfile().get());
     }
 
     // Next check for a user-defined extents:
@@ -280,6 +280,28 @@ Profile::create( const ProfileOptions& options )
     return result;
 }
 
+const Profile*
+Profile::createNamed(const std::string& name)
+{
+    // TODO: move the named profiles from Registry into here.
+    if ( ciEquals(name, "plate-carre") || ciEquals(name, "eqc-wgs84") )
+    {
+        // Yes I know this is not really Plate Carre but it will stand in for now.
+        return Profile::create(
+            "+proj=eqc +units=m +no_defs",
+            -20037508, -10001966,
+             20037508,  10001966,
+            "", // vdatum
+            2, 1 );
+
+    }
+
+    else
+    {
+        return osgEarth::Registry::instance()->getNamedProfile( name );
+    }
+}
+
 /****************************************************************************/
 
 
@@ -718,6 +740,13 @@ Profile::getEquivalentLOD( const Profile* rhsProfile, unsigned rhsLOD ) const
     if (rhsProfile->isHorizEquivalentTo( this ) ) 
         return rhsLOD;
 
+    // Special check for geodetic to mercator or vise versa, they should match up in LOD.
+    if (rhsProfile->isEquivalentTo(Registry::instance()->getSphericalMercatorProfile()) && isEquivalentTo(Registry::instance()->getGlobalGeodeticProfile()) ||
+        rhsProfile->isEquivalentTo(Registry::instance()->getGlobalGeodeticProfile()) && isEquivalentTo(Registry::instance()->getSphericalMercatorProfile()))
+    {
+        return rhsLOD;
+    }
+
     double rhsWidth, rhsHeight;
     rhsProfile->getTileDimensions( rhsLOD, rhsWidth, rhsHeight );    
 
@@ -741,10 +770,10 @@ Profile::getEquivalentLOD( const Profile* rhsProfile, unsigned rhsLOD ) const
     while( true )
     {
         double prevDelta = delta;
-
-        currLOD++;
+        
         double w, h;
         getTileDimensions(currLOD, w, h);
+
         delta = osg::absolute( h - rhsTargetHeight );
         if (delta < prevDelta)
         {
@@ -756,7 +785,7 @@ Profile::getEquivalentLOD( const Profile* rhsProfile, unsigned rhsLOD ) const
             // We are further away from the previous lod so stop.
             break;
         }        
-        destLOD = currLOD;
+        currLOD++;
     }
     return destLOD;
 }
diff --git a/src/osgEarth/Profiler b/src/osgEarth/Profiler
new file mode 100644
index 0000000..cf5fd7a
--- /dev/null
+++ b/src/osgEarth/Profiler
@@ -0,0 +1,70 @@
+/* -*-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_PROFILER_H
+#define OSGEARTH_PROFILER_H 1
+
+#include <osgEarth/Common>
+#include <osgEarth/Config>
+
+namespace osgEarth
+{
+    /**
+    * Poor man's profiler.
+    */
+    class OSGEARTH_EXPORT Profiler
+    {
+    public:
+        /**
+        * Starts a task with the given name.
+        */
+        static void start(const std::string& name);
+
+        /**
+        * Ends a task with the given name.
+        */
+        static void end(const std::string& name);
+
+        /**
+        * Dumps the stats to the console.
+        */
+        static void dump();
+    };
+
+    class /*OSGEARTH_EXPORT*/ ScopedProfiler
+    {
+    public:
+        ScopedProfiler(const std::string& name) :
+            _name(name)
+        {
+            Profiler::start(_name);
+        }
+
+        ~ScopedProfiler()
+        {
+            Profiler::end(_name);
+        }
+
+        std::string _name;
+    };
+}
+
+#endif // OSGEARTH_UNITS_H
diff --git a/src/osgEarth/Profiler.cpp b/src/osgEarth/Profiler.cpp
new file mode 100644
index 0000000..060fe9d
--- /dev/null
+++ b/src/osgEarth/Profiler.cpp
@@ -0,0 +1,75 @@
+/* -*-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.
+ *
+ * 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/Profiler>
+
+#include <map>
+#include <osg/Timer>
+
+using namespace osgEarth;
+
+typedef std::map< std::string, osg::Timer_t > StartTimeMap;
+static StartTimeMap S_START_TIMES;
+
+typedef std::map< std::string, double > ElapsedTimeMap;
+static ElapsedTimeMap S_ELAPSED_TIMES;
+
+typedef std::map< std::string, unsigned int > CallMap;
+static CallMap S_CALL_COUNT;
+
+void Profiler::start(const std::string& name)
+{
+    S_START_TIMES[name] = osg::Timer::instance()->tick();
+}
+
+void Profiler::end(const std::string& name)
+{
+    osg::Timer_t end = osg::Timer::instance()->tick();
+    StartTimeMap::iterator startItr = S_START_TIMES.find(name);
+    if (startItr == S_START_TIMES.end())
+    {
+        OE_WARN << "Can't find start time " << name << std::endl;
+        return;
+    }
+
+    double dt = osg::Timer::instance()->delta_s(startItr->second, end);
+    ElapsedTimeMap::iterator elapsedItr = S_ELAPSED_TIMES.find(name);
+    if (elapsedItr == S_ELAPSED_TIMES.end())
+    {
+        S_ELAPSED_TIMES[name] = 0.0;
+    }
+    S_ELAPSED_TIMES[name] += dt;
+
+    // Increment the number of calls for the task
+    CallMap::iterator callItr = S_CALL_COUNT.find(name);
+    if (callItr == S_CALL_COUNT.end())
+    {
+        S_CALL_COUNT[name] = 0;
+    }
+    S_CALL_COUNT[name] += 1;
+
+}
+
+void Profiler::dump()
+{
+    for (ElapsedTimeMap::iterator itr = S_ELAPSED_TIMES.begin(); itr != S_ELAPSED_TIMES.end(); ++itr)
+    {
+        OE_NOTICE << itr->first << ": calls=" << S_CALL_COUNT[itr->first] << "  time=" << itr->second << "s" << std::endl;
+    }
+}
\ No newline at end of file
diff --git a/src/osgEarth/Progress b/src/osgEarth/Progress
index 88acd55..60e069b 100644
--- a/src/osgEarth/Progress
+++ b/src/osgEarth/Progress
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -21,7 +21,7 @@
 #define OSGEARTH_PROGRESS_H 1
 
 #include <osgEarth/Common>
-#include <map>
+#include <osgEarth/Containers>
 
 namespace osgEarth
 {
@@ -109,14 +109,14 @@ namespace osgEarth
         /**
          * Access user stats
          */
-        std::map<std::string,double>& stats() { return _stats; }
+        fast_map<std::string,double>& stats() { return _stats; }
 
     protected:
         volatile unsigned _numStages;
         std::string       _message;
         mutable  bool     _needsRetry;
         mutable  bool     _canceled;
-        mutable  std::map<std::string, double> _stats;
+        mutable  fast_map<std::string, double> _stats;
     };
 
 
diff --git a/src/osgEarth/Progress.cpp b/src/osgEarth/Progress.cpp
index 2f28216..2dcb63f 100644
--- a/src/osgEarth/Progress.cpp
+++ b/src/osgEarth/Progress.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarth/Random b/src/osgEarth/Random
index 1941b06..6baaf9d 100644
--- a/src/osgEarth/Random
+++ b/src/osgEarth/Random
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/osgEarth/Random.cpp b/src/osgEarth/Random.cpp
index fc84266..4f96ce6 100644
--- a/src/osgEarth/Random.cpp
+++ b/src/osgEarth/Random.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarth/Registry b/src/osgEarth/Registry
index 81331b0..428360f 100644
--- a/src/osgEarth/Registry
+++ b/src/osgEarth/Registry
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -23,19 +23,19 @@
 #include <osgEarth/Common>
 #include <osgEarth/CachePolicy>
 #include <osgEarth/SharedSARepo>
-#include <osgEarth/Units>
 #include <osgEarth/ShaderGenerator>
 #include <OpenThreads/ReentrantMutex>
 #include <OpenThreads/ScopedLock>
 #include <osgEarth/ThreadingUtils>
 #include <osg/Referenced>
-#include <osgDB/ReaderWriter>
-#include <osgText/Font>
 #include <set>
 
 #define GDAL_SCOPED_LOCK \
     OpenThreads::ScopedLock<OpenThreads::ReentrantMutex> _slock( osgEarth::Registry::instance()->getGDALMutex() )\
 
+namespace osgText {
+    class Font;
+}
 
 namespace osgEarth
 {    
@@ -47,6 +47,8 @@ namespace osgEarth
     class URIReadCallback;
     class ColorFilterRegistry;
     class StateSetCache;
+    class ObjectIndex;
+    class Units;
     
     typedef SharedSARepo<osg::Program> ProgramSharedRepo;
 
@@ -151,6 +153,12 @@ namespace osgEarth
         static ShaderGeneratorProxy shaderGenerator() { return instance()->getShaderGenerator(); }
 
         /**
+         * Global object index.
+         */
+        ObjectIndex* getObjectIndex() const;
+        static ObjectIndex* objectIndex() { return instance()->getObjectIndex(); }
+
+        /**
          * A default StateSetCache to use by any process that uses one.
          * A StateSetCache assist in stateset sharing across multiple nodes.
          * Note: A registry-wide SSC is only supported in OSG 3.1.4+. See
@@ -191,13 +199,13 @@ namespace osgEarth
         /**
          * Gets the default set of osgDB::Options to use.
          */
-        const osgDB::Options* getDefaultOptions() const { return _defaultOptions.get(); }
+        const osgDB::Options* getDefaultOptions() const;
 
         /**
          * Clones an options structure (fixing the archive caching), or creates
          * a new one.
          */
-        osgDB::Options* cloneOrCreateOptions( const osgDB::Options* options =0L ) const;
+        static osgDB::Options* cloneOrCreateOptions( const osgDB::Options* options =0L );
 
         /**
          * Registers a Units definition.
@@ -216,6 +224,7 @@ namespace osgEarth
          * For debugging - tracks activities in progress.
          */
         void startActivity(const std::string& name);
+        void startActivity(const std::string& name, const std::string& text);
         void endActivity(const std::string& name);
         void getActivities(std::set<std::string>& output);
 
@@ -289,12 +298,20 @@ namespace osgEarth
         std::string _terrainEngineDriver;
         std::string _cacheDriver;
 
-        std::set<std::string> _activities;
+        typedef std::pair<std::string,std::string> Activity;
+        struct ActivityLess {
+            bool operator()(const Activity& lhs, const Activity& rhs) const {
+                return lhs.first < rhs.first;
+            }
+        };
+        std::set<Activity,ActivityLess> _activities;
         mutable Threading::Mutex _activityMutex;
         
         ProgramSharedRepo _programRepo;
 
         optional<bool> _unRefImageDataAfterApply;
+
+        osg::ref_ptr<ObjectIndex> _objectIndex;
     };
 }
 
diff --git a/src/osgEarth/Registry.cpp b/src/osgEarth/Registry.cpp
index 4b35d56..c99250e 100644
--- a/src/osgEarth/Registry.cpp
+++ b/src/osgEarth/Registry.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -29,9 +29,15 @@
 #include <osgEarth/HTTPClient>
 #include <osgEarth/StringUtils>
 #include <osgEarth/TerrainEngineNode>
+#include <osgEarth/ObjectIndex>
+
+#include <osgEarth/Units>
 #include <osg/Notify>
 #include <osg/Version>
 #include <osgDB/Registry>
+#include <osgDB/Options>
+#include <osgText/Font>
+
 #include <gdal_priv.h>
 #include <ogr_api.h>
 #include <stdlib.h>
@@ -86,6 +92,9 @@ _cacheDriver        ( "filesystem" )
     // Default unref-after apply policy:
     _unRefImageDataAfterApply = true;
 
+    // Default object index for tracking scene object by UID.
+    _objectIndex = new ObjectIndex();
+
     // activate KMZ support
     osgDB::Registry::instance()->addArchiveExtension  ( "kmz" );
     osgDB::Registry::instance()->addFileExtensionAlias( "kmz", "kml" );
@@ -475,8 +484,14 @@ Registry::createUID()
     return (UID)( _uidGen++ );
 }
 
+const osgDB::Options*
+Registry::getDefaultOptions() const 
+{
+    return _defaultOptions.get();
+}
+
 osgDB::Options*
-Registry::cloneOrCreateOptions( const osgDB::Options* input ) const
+Registry::cloneOrCreateOptions(const osgDB::Options* input)
 {
     osgDB::Options* newOptions = 
         input ? static_cast<osgDB::Options*>(input->clone(osg::CopyOp::SHALLOW_COPY)) : 
@@ -546,25 +561,47 @@ Registry::getProgramSharedRepo()
     return &_programRepo;
 }
 
+ObjectIndex*
+Registry::getObjectIndex() const
+{
+    return _objectIndex.get();
+}
+
 void
 Registry::startActivity(const std::string& activity)
 {
     Threading::ScopedMutexLock lock(_activityMutex);
-    _activities.insert(activity);
+    _activities.insert(Activity(activity,std::string()));
+}
+
+void
+Registry::startActivity(const std::string& activity,
+                        const std::string& value)
+{
+    Threading::ScopedMutexLock lock(_activityMutex);
+    _activities.insert(Activity(activity,value));
 }
 
 void
 Registry::endActivity(const std::string& activity)
 {
     Threading::ScopedMutexLock lock(_activityMutex);
-    _activities.erase(activity);
+    _activities.erase(Activity(activity,std::string()));
 }
 
 void
 Registry::getActivities(std::set<std::string>& output)
 {
     Threading::ScopedMutexLock lock(_activityMutex);
-    output = _activities;
+    for(std::set<Activity,ActivityLess>::const_iterator i = _activities.begin();
+        i != _activities.end();
+        ++i)
+    {
+        if ( ! i->second.empty() )
+            output.insert( i->first + ": " + i->second );
+        else
+            output.insert( i->first );
+    }
 }
 
 std::string 
diff --git a/src/osgEarth/Revisioning b/src/osgEarth/Revisioning
index ae2ff7f..027edd8 100644
--- a/src/osgEarth/Revisioning
+++ b/src/osgEarth/Revisioning
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarth/Revisioning.cpp b/src/osgEarth/Revisioning.cpp
index 5dd5a65..2a3d071 100644
--- a/src/osgEarth/Revisioning.cpp
+++ b/src/osgEarth/Revisioning.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -78,9 +78,10 @@ DirtyNotifier::setDirty()
     {
         for( std::vector< osg::observer_ptr<DirtyCounter> >::iterator i = _parents.begin(); i != _parents.end(); )
         {
-            if ( i->valid() )
+            osg::ref_ptr<DirtyCounter> parent;
+            if ( i->lock(parent) )
             {
-                i->get()->_owner->setDirty();
+                parent->_owner->setDirty();
                 ++i;
             }
             else
diff --git a/src/osgEarth/ShaderFactory b/src/osgEarth/ShaderFactory
index 8627f53..52261ff 100644
--- a/src/osgEarth/ShaderFactory
+++ b/src/osgEarth/ShaderFactory
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
@@ -35,10 +38,6 @@ namespace osgEarth
      * but this is advanced usage and rarely necessary. If you think you need to alter
      * the built-in mains, consider whether you can accomplish your goal by using a
      * VirtualProgram instead!
-     *
-     * The only exception, currently, is if you are looking to replace the built-in
-     * lighting shaders. However we intend to decouple this functionality in the 
-     * future and remove the installLightingShaders() method from the ShaderFactory.
      */
     class OSGEARTH_EXPORT ShaderFactory : public osg::Referenced
     {
diff --git a/src/osgEarth/ShaderFactory.cpp b/src/osgEarth/ShaderFactory.cpp
index 23e0605..44ed8a2 100755
--- a/src/osgEarth/ShaderFactory.cpp
+++ b/src/osgEarth/ShaderFactory.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -241,9 +241,12 @@ ShaderFactory::createFragmentShaderMain(const FunctionLocationMap& functions) co
 
     buf << 
         "varying vec4 osg_FrontColor; \n"
+        "varying vec3 oe_Normal; \n"
+        "vec3 oe_global_Normal; \n" // stage-global
         "void main(void) \n"
         "{ \n"
-        INDENT "vec4 color = osg_FrontColor; \n";
+        INDENT "vec4 color = osg_FrontColor; \n"
+        INDENT "oe_global_Normal = normalize(oe_Normal); \n";
 
     int coloringPass = _fragStageOrder == FRAGMENT_STAGE_ORDER_COLORING_LIGHTING ? 0 : 1;
     int lightingPass = 1-coloringPass;
diff --git a/src/osgEarth/ShaderGenerator b/src/osgEarth/ShaderGenerator
index 3e9179c..3001c1f 100644
--- a/src/osgEarth/ShaderGenerator
+++ b/src/osgEarth/ShaderGenerator
@@ -41,6 +41,13 @@ namespace osg
     class TextureRectangle;
     class Texture2DArray;
     class Texture2DMultisample;
+    class TextureCubeMap;
+    class PointSprite;
+}
+
+namespace osgSim
+{
+    class LightPointNode;
 }
 
 namespace osgEarth
@@ -49,7 +56,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 hold
+     * You can use this class directly, but they osgEarth Registry holds
      * a system-wide implementation that the user can replace. So the best
      * way to use this class is:
      *
@@ -114,6 +121,17 @@ namespace osgEarth
          */
         void setProgramName(const std::string& name) const { }
 
+        /**
+         * Whether to automatically duplicate (by cloning) subgraphs with
+         * more than one parent during the traversal process. Since a
+         * shader program is unique based on its traversed NodePath, graphs
+         * with multi-parenting can run into problems.
+         * Default is false.
+         * @untested
+         */
+        void setDuplicateSharedSubgraphs(bool value);
+        bool getDuplicateSharedSubgraphs() const { return _duplicateSharedSubgraphs; }
+
     public:
         /**
          * User callback that lets you selectly reject shader generation for
@@ -148,6 +166,11 @@ namespace osgEarth
         virtual void apply( osg::ProxyNode& );
         virtual void apply( osg::ClipNode& );
 
+    public: // types not in osg::NodeVisitor
+
+        virtual void applyNonCoreNodeIfNecessary( osg::Node& );
+        
+        virtual void apply( osgSim::LightPointNode& );
 
     protected: // high-level entry points:
 
@@ -160,16 +183,21 @@ namespace osgEarth
         virtual bool processText(const osg::StateSet* stateSet, osg::ref_ptr<osg::StateSet>& replacement);
 
 
+
     protected: // overridable texture handlers:
 
-        struct GenBuffers
+        struct OSGEARTH_EXPORT GenBuffers
         {
-            std::stringstream vertHead, vertBody;
-            std::stringstream fragHead, fragBody;
-            osg::StateSet*    stateSet;
+            std::stringstream _vertHead, _vertBody;
+            std::stringstream _fragHead, _fragBody;
+            osg::StateSet*    _stateSet;
+            unsigned          _version;
+
+            bool requireVersion(unsigned glslVersion, unsigned glesVersion=100u);
+            GenBuffers();
         };
 
-        virtual bool apply(osg::Texture* tex, osg::TexGen* texgen, osg::TexEnv* texenv, osg::TexMat* texmat, int unit, GenBuffers& buf);
+        virtual bool apply(osg::Texture* tex, osg::TexGen* texgen, osg::TexEnv* texenv, osg::TexMat* texmat, osg::PointSprite* sprite, int unit, GenBuffers& buf);
 
         virtual bool apply(osg::TexEnv* texenv, int unit, GenBuffers& buf);
 
@@ -187,6 +215,18 @@ namespace osgEarth
 
         virtual bool apply(osg::Texture2DArray* tex, int unit, GenBuffers& buf);
 
+        virtual bool apply(osg::TextureCubeMap* tex, int unit, GenBuffers& buf);
+
+        virtual bool apply(osg::PointSprite* sprite, int unit, GenBuffers& buf);
+        
+        virtual bool apply(osg::StateSet::AttributeList& attrs, GenBuffers& buf);
+
+        virtual bool apply(osg::StateAttribute* attr, GenBuffers& buf);
+        
+        // This method will check whether setDuplicateSharedNodes has been set,
+        // and if so, it will clone a node that has multiple parents such that
+        // each parent has a complete separate copy of the child.
+        virtual void duplicateSharedNode(osg::Node& child);
 
     protected:
 
@@ -195,6 +235,7 @@ namespace osgEarth
         bool _active;
 
         std::string _name;
+        bool _duplicateSharedSubgraphs;
 
         typedef std::vector<osg::ref_ptr<AcceptCallback> > AcceptCallbackVector;
         AcceptCallbackVector _acceptCallbacks;
diff --git a/src/osgEarth/ShaderGenerator.cpp b/src/osgEarth/ShaderGenerator.cpp
index d910ace..9d68dee 100644
--- a/src/osgEarth/ShaderGenerator.cpp
+++ b/src/osgEarth/ShaderGenerator.cpp
@@ -1,7 +1,7 @@
 
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -24,6 +24,7 @@
 #include <osgEarth/Registry>
 #include <osgEarth/ShaderFactory>
 #include <osgEarth/StringUtils>
+#include <osgEarth/URI>
 
 #include <osg/Drawable>
 #include <osg/Geode>
@@ -35,15 +36,19 @@
 #include <osg/TextureRectangle>
 #include <osg/Texture2DMultisample>
 #include <osg/Texture2DArray>
+#include <osg/TextureBuffer>
+#include <osg/TextureCubeMap>
 #include <osg/TexEnv>
 #include <osg/TexGen>
 #include <osg/TexMat>
 #include <osg/ClipNode>
+#include <osg/PointSprite>
 #include <osg/ValueObject>
 #include <osgDB/FileNameUtils>
 #include <osgDB/FileUtils>
 #include <osgDB/ReadFile>
 #include <osgText/Text>
+#include <osgSim/LightPointNode>
 
 #define LC "[ShaderGenerator] "
 
@@ -51,6 +56,10 @@
 
 #define SHADERGEN_HINT_IGNORE "osgEarth.ShaderGenerator.ignore"
 
+// Set this to detect whether a Geode's drawables all have the same VP
+// profile, and if so, promote that VP to the Geode's state set.
+#define PROMOTE_EQUIVALENT_DRAWABLE_VP_TO_GEODE 1
+
 using namespace osgEarth;
 
 //------------------------------------------------------------------------
@@ -115,18 +124,26 @@ struct OSGEarthShaderGenPseudoLoader : public osgDB::ReaderWriter
 
         std::string stripped = osgDB::getNameLessExtension(filename);
 
-        OE_INFO << LC << "Loading " << stripped << " and generating shaders." << std::endl;
+        OE_INFO << LC << "Loading " << stripped << " from PLOD/Proxy and generating shaders." << std::endl;
         
-        osg::ref_ptr<osg::Node> node = osgDB::readNodeFile(stripped, options);
-        if ( node.valid() )
+        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() );
+
+            return ReadResult( node.release() );
         }
 
-        return node.valid() ? ReadResult(node.release()) : ReadResult::ERROR_IN_READING_FILE;
+        else
+        {
+            OE_WARN << LC << "Error loading \"" << stripped << "\": " << result.errorDetail() << "\n";
+            return ReadResult::ERROR_IN_READING_FILE;
+        }
     }
 };
 
@@ -149,7 +166,10 @@ namespace
         {
             if (_stateset->getMode(mode) & osg::StateAttribute::ON)
             {
-                _stateset->setAttribute(_sa, osg::StateAttribute::ON);
+                if ( _sa->isTextureAttribute() )
+                    _stateset->setTextureAttribute(_unit, _sa, osg::StateAttribute::ON);
+                else
+                    _stateset->setAttribute(_sa, osg::StateAttribute::ON);
             }
         }
 
@@ -157,7 +177,10 @@ namespace
         {
             if (_stateset->getTextureMode(_unit, mode) & osg::StateAttribute::ON)
             {
-                _stateset->setTextureAttribute(_unit, _sa, osg::StateAttribute::ON);
+                if ( _sa->isTextureAttribute() )
+                    _stateset->setTextureAttribute(_unit, _sa, osg::StateAttribute::ON);
+                else
+                    _stateset->setAttribute(_sa, osg::StateAttribute::ON);
             }
         }
 
@@ -218,8 +241,8 @@ namespace
                     const osg::State::AttributePair& pair = as.attributeVec.back();
                     osg::StateAttribute* sa = const_cast<osg::StateAttribute*>(pair.first);
                     ActiveAttributeCollector collector(stateset, sa);
-                    bool modeless = !sa->getModeUsage(collector);
-                    if (modeless || isModeless(sa))
+                    bool modeless = isModeless(sa) || !sa->getModeUsage(collector);
+                    if (modeless)
                     {
                         // if getModeUsage returns false, there are no modes associated with
                         // this attr, so just add it (it can't be forcably disabled)
@@ -239,9 +262,9 @@ namespace
                         const osg::State::AttributePair& pair = as.attributeVec.back();
                         osg::StateAttribute* sa = const_cast<osg::StateAttribute*>(pair.first);
                         ActiveAttributeCollector collector(stateset, sa, unit);
-                        bool modeless = !sa->getModeUsage(collector);
-                        if (modeless || isModeless(sa))
-                        {
+						bool modeless = isModeless(sa) || !sa->getModeUsage(collector);
+						if (modeless)
+						{
                             // if getModeUsage returns false, there are no modes associated with
                             // this attr, so just add it (it can't be forcably disabled)
                             stateset->setTextureAttribute(unit, sa, osg::StateAttribute::ON);
@@ -260,7 +283,8 @@ namespace
 #if OSG_VERSION_LESS_THAN(3,3,1)            
             return
                 dynamic_cast<osg::Texture2DArray*>(sa) ||
-                dynamic_cast<osg::Texture2DMultisample*>(sa);
+                dynamic_cast<osg::Texture2DMultisample*>(sa) ||
+				dynamic_cast<osg::TextureBuffer*>(sa);
 #else
             return false;
 #endif
@@ -283,7 +307,30 @@ namespace
     }
 }
 
-//------------------------------------------------------------------------
+//...........................................................................
+
+ShaderGenerator::GenBuffers::GenBuffers() :
+_version( GLSL_VERSION )
+{
+    //nop
+}
+
+bool
+ShaderGenerator::GenBuffers::requireVersion(unsigned glslVersion, unsigned glesVersion)
+{
+    unsigned versionToCheck = Registry::capabilities().isGLES() ? glesVersion : glslVersion;
+
+    if ( !Registry::capabilities().supportsGLSL(versionToCheck) )
+        return false;
+
+    if ( versionToCheck > _version )
+        _version = versionToCheck;
+
+    return true;
+}
+
+
+//...........................................................................
 
 ShaderGenerator::ShaderGenerator()
 {
@@ -292,24 +339,27 @@ ShaderGenerator::ShaderGenerator()
     setNodeMaskOverride( ~0 );
     _state = new StateEx();
     _active = true;
+    _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)
+osg::NodeVisitor         (),
+_active                  (rhs._active),
+_duplicateSharedSubgraphs(rhs._duplicateSharedSubgraphs)
 {
-    _visitorType      = rhs._visitorType;
-    _traversalMode    = rhs._traversalMode;
-    _traversalMask    = rhs._traversalMask;
-    _nodeMaskOverride = rhs._nodeMaskOverride;
+    _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)
+osg::NodeVisitor         (rhs, copy),
+_active                  (rhs._active),
+_duplicateSharedSubgraphs(rhs._duplicateSharedSubgraphs)
 {
     _state = new StateEx();
 }
@@ -332,6 +382,12 @@ ShaderGenerator::ignore(const osg::Object* object)
 }
 
 void
+ShaderGenerator::setDuplicateSharedSubgraphs(bool value)
+{
+    _duplicateSharedSubgraphs = value;
+}
+
+void
 ShaderGenerator::addAcceptCallback(AcceptCallback* cb)
 {
     _acceptCallbacks.push_back( cb );
@@ -387,8 +443,24 @@ ShaderGenerator::optimizeStateSharing(osg::Node* node, StateSetCache* cache)
         cache->optimize(node);
 }
 
+void
+ShaderGenerator::duplicateSharedNode(osg::Node& node)
+{
+    if ( node.getNumParents() > 1 )
+    {
+        for(int i=1; i<(int)node.getNumParents(); ++i)
+        {
+            osg::Group* parent = node.getParent(i);
+            osg::Node* replicant = osg::clone(
+                &node, 
+                osg::CopyOp::DEEP_COPY_NODES | osg::CopyOp::DEEP_COPY_DRAWABLES | osg::CopyOp::DEEP_COPY_ARRAYS);
+            parent->replaceChild(&node, replicant);
+        }
+    }
+}
+
 void 
-ShaderGenerator::apply( osg::Node& node )
+ShaderGenerator::apply(osg::Node& node)
 {
     if ( !_active )
         return;
@@ -396,6 +468,11 @@ ShaderGenerator::apply( osg::Node& node )
     if ( ignore(&node) )
         return;
 
+    if ( _duplicateSharedSubgraphs )
+        duplicateSharedNode(node);
+
+    applyNonCoreNodeIfNecessary( node );
+
     osg::ref_ptr<osg::StateSet> stateset = node.getStateSet();
     if ( stateset.valid() )
     {
@@ -424,6 +501,9 @@ ShaderGenerator::apply( osg::Geode& node )
 
     if ( ignore(&node) )
         return;
+    
+    if ( _duplicateSharedSubgraphs )
+        duplicateSharedNode(node);
 
     osg::ref_ptr<osg::StateSet> stateset = node.getStateSet();
     if ( stateset.valid() )
@@ -434,6 +514,7 @@ ShaderGenerator::apply( osg::Geode& node )
     unsigned numDrawables = node.getNumDrawables();
     bool traverseDrawables = true;
 
+#ifdef PROMOTE_EQUIVALENT_DRAWABLE_VP_TO_GEODE
     // This block checks whether all the geode's drawables are equivalent,
     // i.e., they are the same type (geometry or text) and none of them
     // have their own state sets. IF that's the case, we can create a 
@@ -473,6 +554,7 @@ ShaderGenerator::apply( osg::Geode& node )
             }
         }
     }
+#endif
 
     // Drawables have state sets, so let's traverse them.
     if ( traverseDrawables )
@@ -605,11 +687,49 @@ ShaderGenerator::apply(osg::ClipNode& node)
     osg::StateSet* stateSet = cloneOrCreateStateSet(&node);
     VirtualProgram* vp = VirtualProgram::getOrCreate(stateSet);
     if ( vp->referenceCount() == 1 ) vp->setName( _name );
-    vp->setFunction( "oe_sg_set_clipvertex", s_clip_source, ShaderComp::LOCATION_VERTEX_VIEW );
+    vp->setFunction( "oe_sg_set_clipvertex", s_clip_source, ShaderComp::LOCATION_VERTEX_VIEW, 0.95f );
 
     apply( static_cast<osg::Group&>(node) );
 }
 
+void
+ShaderGenerator::applyNonCoreNodeIfNecessary(osg::Node& node)
+{
+    if ( dynamic_cast<osgSim::LightPointNode*>(&node) )
+    {
+        apply( static_cast<osgSim::LightPointNode&>(node) );
+    }
+}
+
+void
+ShaderGenerator::apply(osgSim::LightPointNode& node)
+{
+    if ( node.getPointSprite() )
+    {
+        osg::ref_ptr<osg::StateSet> stateset;
+        
+        // if the node has state, clone it so we can add our temp attribute.
+        stateset = node.getStateSet() ?
+            osg::clone(node.getStateSet(), osg::CopyOp::SHALLOW_COPY) :
+            new osg::StateSet();
+
+        // add a temporary point sprite so the generator will make sprite code.
+        osg::ref_ptr<osg::PointSprite> sprite = new osg::PointSprite();
+        stateset->setTextureAttributeAndModes(0, sprite.get());
+
+        _state->pushStateSet( stateset.get() );
+
+        osg::ref_ptr<osg::StateSet> replacement;
+        if ( processGeometry(stateset.get(), replacement) )
+        {
+            // remove the temporary sprite.
+            replacement->removeTextureAttribute(0, sprite.get());
+            node.setStateSet(replacement.get() );
+        }
+
+        _state->popStateSet();
+    }
+}
 
 bool
 ShaderGenerator::processText(const osg::StateSet* ss, osg::ref_ptr<osg::StateSet>& replacement)
@@ -635,7 +755,9 @@ ShaderGenerator::processText(const osg::StateSet* ss, osg::ref_ptr<osg::StateSet
     
     // give the VP a name if it needs one.
     if ( vp->getName().empty() )
+    {
         vp->setName( _name );
+    }
 
     std::string vertSrc =
         "#version " GLSL_VERSION_STR "\n" GLSL_PRECISION "\n"
@@ -655,8 +777,8 @@ ShaderGenerator::processText(const osg::StateSet* ss, osg::ref_ptr<osg::StateSet
         INDENT "color.a *= texel.a; \n"
         "}\n";
 
-    vp->setFunction( VERTEX_FUNCTION,   vertSrc, ShaderComp::LOCATION_VERTEX_VIEW );
-    vp->setFunction( FRAGMENT_FUNCTION, fragSrc, ShaderComp::LOCATION_FRAGMENT_COLORING );
+    vp->setFunction( VERTEX_FUNCTION,   vertSrc, ShaderComp::LOCATION_VERTEX_VIEW, 0.5f );
+    vp->setFunction( FRAGMENT_FUNCTION, fragSrc, ShaderComp::LOCATION_FRAGMENT_COLORING, 0.5f );
     replacement->getOrCreateUniform( SAMPLER_TEXT, osg::Uniform::SAMPLER_2D )->set( 0 );
 
     return replacement.valid();
@@ -683,46 +805,41 @@ ShaderGenerator::processGeometry(const osg::StateSet*         original,
 
     // Copy or create a new stateset (that we may or may not use depending on
     // what we find). Never modify an existing stateset!
-    osg::ref_ptr<osg::StateSet> new_stateset = 
+    osg::ref_ptr<osg::StateSet> newStateSet =
         original ? osg::clone(original, osg::CopyOp::SHALLOW_COPY) :
         new osg::StateSet();
 
     // likewise, create a VP that we might populate.
-    osg::ref_ptr<VirtualProgram> vp = VirtualProgram::cloneOrCreate(original, new_stateset);
+    osg::ref_ptr<VirtualProgram> vp = VirtualProgram::cloneOrCreate(original, newStateSet);
 
     // we'll set this to true if the new stateset goes into effect and
     // needs to be returned.
-    bool need_new_stateset = false;
+    bool needNewStateSet = false;
+    bool needVertexFunction = false;
+    bool needFragmentFunction = false;
     
     // give the VP a name if it needs one.
     if ( vp->getName().empty() )
+    {
         vp->setName( _name );
+    }
 
     // Check whether the lighting state has changed and install a mode uniform.
     // TODO: fix this
     if ( original && original->getMode(GL_LIGHTING) != osg::StateAttribute::INHERIT )
     {
-        need_new_stateset = true;
-
+        needNewStateSet = true;
         osg::StateAttribute::GLModeValue value = current->getMode(GL_LIGHTING);
-        new_stateset->addUniform( Registry::shaderFactory()->createUniformForGLMode(GL_LIGHTING, value) );
+        newStateSet->addUniform( Registry::shaderFactory()->createUniformForGLMode(GL_LIGHTING, value) );
     }
+    
+    // start generating the shader source.
+    GenBuffers buf;
+    buf._stateSet = newStateSet.get();
 
     // if the stateset changes any texture attributes, we need a new virtual program:
     if (current->getTextureAttributeList().size() > 0)
     {
-        // start generating the shader source.
-        GenBuffers buf;
-        buf.stateSet = new_stateset;
-
-        // compatibility strings make it work in GL or GLES.
-        buf.vertHead << "#version " GLSL_VERSION_STR "\n" GLSL_PRECISION;
-        buf.fragHead << "#version " GLSL_VERSION_STR "\n" GLSL_PRECISION;
-
-        // function declarations:
-        buf.vertBody << "void " VERTEX_FUNCTION "(inout vec4 vertex_view)\n{\n";
-        buf.fragBody << "void " FRAGMENT_FUNCTION "(inout vec4 color)\n{\n";
-
         bool wroteTexelDecl = false;
 
         // Loop over all possible texture image units.
@@ -732,7 +849,7 @@ ShaderGenerator::processGeometry(const osg::StateSet*         original,
         {
             if ( !wroteTexelDecl )
             {
-                buf.fragBody << INDENT << MEDIUMP "vec4 texel; \n";
+                buf._fragBody << INDENT << MEDIUMP "vec4 texel; \n";
                 wroteTexelDecl = true;
             }
 
@@ -743,60 +860,96 @@ ShaderGenerator::processGeometry(const osg::StateSet*         original,
                 osg::TexGen* texgen = dynamic_cast<osg::TexGen*>(current->getTextureAttribute(unit, osg::StateAttribute::TEXGEN));
                 osg::TexEnv* texenv = dynamic_cast<osg::TexEnv*>(current->getTextureAttribute(unit, osg::StateAttribute::TEXENV));
                 osg::TexMat* texmat = dynamic_cast<osg::TexMat*>(current->getTextureAttribute(unit, osg::StateAttribute::TEXMAT));
-
-                if ( apply(tex, texgen, texenv, texmat, unit, buf) == true )
+                osg::PointSprite* sprite = dynamic_cast<osg::PointSprite*>(current->getTextureAttribute(unit, osg::StateAttribute::POINTSPRITE));
+                
+                if ( apply(tex, texgen, texenv, texmat, sprite, unit, buf) == true )
                 {
-                   need_new_stateset = true;
+                    needNewStateSet = true;
                 }
             }
         }
+    }
+
+    // Process the state attributes.
+    osg::StateSet::AttributeList& attrs = current->getAttributeList();
+    if ( apply(attrs, buf) )
+    {
+        needNewStateSet = true;
+    }
 
-        if ( need_new_stateset )
+    if ( needNewStateSet )
+    {
+        std::string version = Stringify() << buf._version;
+
+        std::string vertHeadSource;
+        vertHeadSource = buf._vertHead.str();
+
+        std::string vertBodySource;
+        vertBodySource = buf._vertBody.str();
+
+
+        if ( !vertHeadSource.empty() || !vertBodySource.empty() )
+        {
+            std::string vertSource = Stringify()
+                << "#version " << version << "\n" GLSL_PRECISION "\n"
+                << vertHeadSource
+                << "void " VERTEX_FUNCTION "(inout vec4 vertex_view)\n{\n"
+                << vertBodySource
+                << "}\n";
+
+            vp->setFunction(VERTEX_FUNCTION, vertSource, ShaderComp::LOCATION_VERTEX_VIEW, 0.5f);
+        }
+
+
+        std::string fragHeadSource;
+        fragHeadSource = buf._fragHead.str();
+
+        std::string fragBodySource;
+        fragBodySource = buf._fragBody.str();
+
+        if ( !fragHeadSource.empty() || !fragBodySource.empty() )
         {
-            // close out functions:
-            buf.vertBody << "}\n";
-            buf.fragBody << "}\n";
-
-            // Extract the shader source strings (win compat method)
-            std::string vertBodySrc, vertSrc, fragBodySrc, fragSrc;
-            vertBodySrc = buf.vertBody.str();
-            buf.vertHead << vertBodySrc;
-            vertSrc = buf.vertHead.str();
-            fragBodySrc = buf.fragBody.str();
-            buf.fragHead << fragBodySrc;
-            fragSrc = buf.fragHead.str();
-
-            // inject the shaders:
-            vp->setFunction( VERTEX_FUNCTION,   vertSrc, ShaderComp::LOCATION_VERTEX_VIEW );
-            vp->setFunction( FRAGMENT_FUNCTION, fragSrc, ShaderComp::LOCATION_FRAGMENT_COLORING );
+            std::string fragSource = Stringify()
+                << "#version " << version << "\n" GLSL_PRECISION "\n"
+                << fragHeadSource
+                << "void " FRAGMENT_FUNCTION "(inout vec4 color)\n{\n"
+                << fragBodySource
+                << "}\n";
+
+            vp->setFunction(FRAGMENT_FUNCTION, fragSource, ShaderComp::LOCATION_FRAGMENT_COLORING, 0.5f);
         }
     }
 
-    if ( need_new_stateset )
+    if ( needNewStateSet )
     {
-        replacement = new_stateset.get();
+        replacement = newStateSet.get();
     }
     return replacement.valid();
 }
 
 
 bool
-ShaderGenerator::apply(osg::Texture* tex, 
-                       osg::TexGen*  texgen,
-                       osg::TexEnv*  texenv,
-                       osg::TexMat*  texmat,
-                       int           unit,
-                       GenBuffers&   buf)
+ShaderGenerator::apply(osg::Texture*     tex, 
+                       osg::TexGen*      texgen,
+                       osg::TexEnv*      texenv,
+                       osg::TexMat*      texmat,
+                       osg::PointSprite* sprite,
+                       int               unit,
+                       GenBuffers&       buf)
 {
    bool ok = true;
 
-   buf.vertHead << "varying " MEDIUMP "vec4 " TEX_COORD << unit << ";\n";
-   buf.fragHead << "varying " MEDIUMP "vec4 " TEX_COORD << unit << ";\n";
+   buf._vertHead << "varying " MEDIUMP "vec4 " TEX_COORD << unit << ";\n";
+   buf._fragHead << "varying " MEDIUMP "vec4 " TEX_COORD << unit << ";\n";
 
    apply( texgen, unit, buf );
    apply( texmat, unit, buf );
 
-   if ( dynamic_cast<osg::Texture1D*>(tex) )
+   if ( sprite )
+   {
+      apply(sprite, unit, buf);
+   }
+   else if ( dynamic_cast<osg::Texture1D*>(tex) )
    {
       apply(static_cast<osg::Texture1D*>(tex), unit, buf);
    }
@@ -816,6 +969,10 @@ ShaderGenerator::apply(osg::Texture* tex,
    {
       apply(static_cast<osg::Texture2DArray*>(tex), unit, buf);
    }
+   else if ( dynamic_cast<osg::TextureCubeMap*>(tex) )
+   {
+       apply(static_cast<osg::TextureCubeMap*>(tex), unit, buf);
+   }
    else
    {
       OE_WARN << LC << "Unsupported texture type: " << tex->className() << std::endl;
@@ -844,7 +1001,7 @@ ShaderGenerator::apply(osg::TexEnv* texenv, int unit, GenBuffers& buf)
         if ( blendingMode == osg::TexEnv::BLEND )
         {
             std::string texEnvColorUniform = Stringify() << TEXENV_COLOR << unit;
-            buf.stateSet
+            buf._stateSet
                 ->getOrCreateUniform(texEnvColorUniform, osg::Uniform::FLOAT_VEC4)
                 ->set( texenv->getColor() );
         }
@@ -854,27 +1011,27 @@ ShaderGenerator::apply(osg::TexEnv* texenv, int unit, GenBuffers& buf)
     switch( blendingMode )
     {
     case osg::TexEnv::REPLACE:
-        buf.fragBody
+        buf._fragBody
             << INDENT "color = texel; \n";
         break;
     case osg::TexEnv::MODULATE:
-        buf.fragBody
+        buf._fragBody
             << INDENT "color = color * texel; \n";
         break;
     case osg::TexEnv::DECAL:
-        buf.fragBody
+        buf._fragBody
             << INDENT "color.rgb = color.rgb * (1.0 - texel.a) + (texel.rgb * texel.a); \n";
         break;
     case osg::TexEnv::BLEND:
-        buf.fragHead
+        buf._fragHead
             << "uniform " MEDIUMP "vec4 " TEXENV_COLOR << unit << "\n;";
-        buf.fragBody
+        buf._fragBody
             << INDENT "color.rgb = color.rgb * (1.0 - texel.rgb) + (" << TEXENV_COLOR << unit << ".rgb * texel.rgb); \n"
             << INDENT "color.a   = color.a * texel.a; \n";
         break;
     case osg::TexEnv::ADD:
     default:
-        buf.fragBody
+        buf._fragBody
             << INDENT "color.rgb = color.rgb + texel.rgb; \n"
             << INDENT "color.a   = color.a * texel.a; \n";
     }
@@ -900,7 +1057,7 @@ ShaderGenerator::apply(osg::TexGen* texgen, int unit, GenBuffers& buf)
         switch( texgen->getMode() )
         {
         case osg::TexGen::OBJECT_LINEAR:
-            buf.vertBody
+            buf._vertBody
                 << INDENT "{\n"
                 << INDENT TEX_COORD << unit << " = "
                 <<      "gl_Vertex.x*gl_ObjectPlaneS[" <<unit<< "] + "
@@ -911,7 +1068,7 @@ ShaderGenerator::apply(osg::TexGen* texgen, int unit, GenBuffers& buf)
             break;
 
         case osg::TexGen::EYE_LINEAR:
-            buf.vertBody
+            buf._vertBody
                 << INDENT "{\n"
                 << INDENT TEX_COORD << unit << " = "
                 <<      "vertex_view.x*gl_EyePlaneS[" <<unit<< "] + "
@@ -922,9 +1079,9 @@ ShaderGenerator::apply(osg::TexGen* texgen, int unit, GenBuffers& buf)
             break;
 
         case osg::TexGen::SPHERE_MAP:
-            buf.vertHead
+            buf._vertHead
                 << "varying vec3 oe_Normal;\n";
-            buf.vertBody 
+            buf._vertBody 
                 << 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, oe_Normal);\n"
@@ -935,9 +1092,9 @@ ShaderGenerator::apply(osg::TexGen* texgen, int unit, GenBuffers& buf)
             break;
 
         case osg::TexGen::REFLECTION_MAP:
-            buf.vertHead
+            buf._vertHead
                 << "varying vec3 oe_Normal;\n";
-            buf.vertBody
+            buf._vertBody
                 << INDENT "{\n"
                 << INDENT "vec3 view_vec = normalize(vertex_view.xyz/vertex_view.w);\n"
                 << INDENT TEX_COORD << unit << " = vec4(reflect(view_vec, oe_Normal), 1.0); \n"
@@ -945,9 +1102,9 @@ ShaderGenerator::apply(osg::TexGen* texgen, int unit, GenBuffers& buf)
             break;
 
         case osg::TexGen::NORMAL_MAP:
-            buf.vertHead
+            buf._vertHead
                 << "varying vec3 oe_Normal;\n";
-            buf.vertBody
+            buf._vertBody
                 << INDENT "{\n"
                 << INDENT TEX_COORD << unit << " = vec4(oe_Normal, 1.0); \n"
                 << INDENT "}\n";
@@ -964,7 +1121,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._vertBody
                 << INDENT << TEX_COORD << unit << " = gl_MultiTexCoord" << unit << ";\n";
         }
         else
@@ -974,7 +1131,7 @@ ShaderGenerator::apply(osg::TexGen* texgen, int unit, GenBuffers& buf)
                 << "requires a custom vertex attribute (osg_MultiTexCoord" << unit << ")."
                 << std::endl;
 
-            buf.vertBody 
+            buf._vertBody 
                 << INDENT << TEX_COORD << unit << " = osg_MultiTexCoord" << unit << ";\n";
         }
     }
@@ -989,11 +1146,10 @@ 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 << ";\n";
-        buf.vertBody << INDENT << TEX_COORD << unit << " = " << texMatUniform << " * " << TEX_COORD<<unit << ";\n";
+        buf._vertHead << "uniform mat4 " << texMatUniform << ";\n";
+        buf._vertBody << INDENT << TEX_COORD << unit << " = " << texMatUniform << " * " << TEX_COORD<<unit << ";\n";
 
-        buf.stateSet
+        buf._stateSet
             ->getOrCreateUniform(texMatUniform, osg::Uniform::FLOAT_MAT4)
             ->set( texmat->getMatrix() );
     }
@@ -1004,9 +1160,9 @@ ShaderGenerator::apply(osg::TexMat* texmat, int unit, GenBuffers& buf)
 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.stateSet->getOrCreateUniform( Stringify() << SAMPLER << unit, osg::Uniform::SAMPLER_1D )->set( unit );
+    buf._fragHead << "uniform sampler1D " SAMPLER << unit << ";\n";
+    buf._fragBody << INDENT "texel = texture1D(" SAMPLER << unit << ", " TEX_COORD << unit << ".x);\n";
+    buf._stateSet->getOrCreateUniform( Stringify() << SAMPLER << unit, osg::Uniform::SAMPLER_1D )->set( unit );
 
     return true;
 }
@@ -1014,9 +1170,9 @@ ShaderGenerator::apply(osg::Texture1D* tex, int unit, GenBuffers& buf)
 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.stateSet->getOrCreateUniform( Stringify() << SAMPLER << unit, osg::Uniform::SAMPLER_2D )->set( unit );
+    buf._fragHead << "uniform sampler2D " SAMPLER << unit << ";\n";
+    buf._fragBody << INDENT "texel = texture2D(" SAMPLER << unit << ", " TEX_COORD << unit << ".xy);\n";
+    buf._stateSet->getOrCreateUniform( Stringify() << SAMPLER << unit, osg::Uniform::SAMPLER_2D )->set( unit );
 
     return true;
 }
@@ -1024,9 +1180,9 @@ ShaderGenerator::apply(osg::Texture2D* tex, int unit, GenBuffers& buf)
 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.stateSet->getOrCreateUniform( Stringify() << SAMPLER << unit, osg::Uniform::SAMPLER_3D )->set( unit );
+    buf._fragHead << "uniform sampler3D " SAMPLER << unit << ";\n";
+    buf._fragBody << INDENT "texel = texture3D(" SAMPLER << unit << ", " TEX_COORD << unit << ".xyz);\n";
+    buf._stateSet->getOrCreateUniform( Stringify() << SAMPLER << unit, osg::Uniform::SAMPLER_3D )->set( unit );
 
     return true;
 }
@@ -1034,11 +1190,11 @@ 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._vertHead << "#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.stateSet->getOrCreateUniform( Stringify() << SAMPLER << unit, osg::Uniform::SAMPLER_2D )->set( unit );
+    buf._fragHead << "uniform sampler2DRect " SAMPLER << unit << ";\n";
+    buf._fragBody << INDENT "texel = texture2DRect(" SAMPLER << unit << ", " TEX_COORD << unit << ".xy);\n";
+    buf._stateSet->getOrCreateUniform( Stringify() << SAMPLER << unit, osg::Uniform::SAMPLER_2D )->set( unit );
 
     return true;
 }
@@ -1046,11 +1202,59 @@ ShaderGenerator::apply(osg::TextureRectangle* tex, int unit, GenBuffers& buf)
 bool
 ShaderGenerator::apply(osg::Texture2DArray* tex, int unit, GenBuffers& buf)
 {    
-    buf.fragHead <<  "#extension GL_EXT_texture_array : enable \n";    
+    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._stateSet->getOrCreateUniform( Stringify() << SAMPLER << unit, osg::Uniform::SAMPLER_2D_ARRAY )->set( unit );         
+
+    return true;
+}
+
+bool
+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._stateSet->getOrCreateUniform( sampler, osg::Uniform::SAMPLER_CUBE )->set( unit );         
+
+    return true;
+}
+
+bool
+ShaderGenerator::apply(osg::PointSprite* tex, int unit, GenBuffers& buf)
+{
+    if ( !buf.requireVersion(120) ) return false;
 
-    buf.fragHead << "uniform sampler2DArray " SAMPLER << unit << ";\n";
-    buf.fragBody << INDENT "texel = texture2DArray(" SAMPLER << unit << ", " TEX_COORD << unit << ".xyz);\n";
-    buf.stateSet->getOrCreateUniform( Stringify() << SAMPLER << unit, osg::Uniform::SAMPLER_2D_ARRAY )->set( unit );         
+    std::string sampler = Stringify() << SAMPLER << unit;
+    buf._fragHead << "uniform sampler2D " << sampler << ";\n";
+    buf._fragBody << INDENT << "texel = texture2D(" << sampler << ", gl_PointCoord);\n";
+    buf._stateSet->getOrCreateUniform( sampler, osg::Uniform::SAMPLER_2D )->set( unit );
 
     return true;
 }
+
+bool
+ShaderGenerator::apply(osg::StateSet::AttributeList& attrs, GenBuffers& buf)
+{
+    bool addedSomething = false;
+
+    for(osg::StateSet::AttributeList::iterator i = attrs.begin(); i != attrs.end(); ++i)
+    {
+        osg::StateAttribute* attr = i->second.first.get();
+        if ( apply(attr, buf) )
+        {
+            addedSomething = true;
+        }
+    }
+
+    return addedSomething;
+}
+
+bool
+ShaderGenerator::apply(osg::StateAttribute* attr, GenBuffers& buf)
+{
+    // NOP for now.
+    return false;
+}
diff --git a/src/osgEarth/ShaderLoader b/src/osgEarth/ShaderLoader
new file mode 100644
index 0000000..97f602c
--- /dev/null
+++ b/src/osgEarth/ShaderLoader
@@ -0,0 +1,149 @@
+/* -*-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.
+ *
+ * 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_SHADER_LOADER_H
+#define OSGEARTH_SHADER_LOADER_H 1
+
+#include <osgEarth/Common>
+#include <osgDB/Options>
+#include <map>
+
+namespace osgEarth
+{
+    class VirtualProgram;
+
+    /**
+     * Functions to help load shader code.
+     */
+    class OSGEARTH_EXPORT ShaderPackage
+    {
+    public:
+        /**
+         * Adds a function from this package to the VirtualProgram.
+         */
+        bool load(
+            VirtualProgram*       vp,
+            const std::string&    filename,
+            const osgDB::Options* dbOptions =0L ) const;
+
+        /**
+         * Removes a function loaded by load from the VirtualProgram.
+         */
+        bool unload(
+            VirtualProgram*       vp,
+            const std::string&    filename,
+            const osgDB::Options* dbOptions =0L ) const;
+
+        /**
+         * Adds all the functions in this package to the VirtualProgram.
+         */
+        bool loadAll(
+            VirtualProgram*       vp,
+            const osgDB::Options* dbOptions =0L ) const;
+
+        /**
+         * Removes all the functions in this package from the VirtualProgram.
+         */
+        bool unloadAll(
+            VirtualProgram*       vp,
+            const osgDB::Options* dbOptions =0L ) const;
+
+        /**
+         * Defs or undefs a GLSL #define proprocessor macro.
+         * Don't include the "#" in the defineName.
+         */
+        void define(
+            const std::string& defineName,
+            bool               defOrUndef);
+
+        /**
+         * Replaces the specified string with another string in the loaded
+         * shader source. Nested replacements are not supported.
+         */
+        void replace(
+            const std::string& pattern,
+            const std::string& value);
+
+    public:
+        typedef std::map<std::string, std::string> SourceMap;
+        typedef std::map<std::string, std::string> ReplaceMap;
+        typedef std::map<std::string, bool>        DefineMap;
+
+        const SourceMap& context() const { return _sources; }
+
+        void add(const std::string& filename, const std::string& inlineSource) {
+            _sources[filename] = inlineSource; }
+
+
+    protected:        
+        SourceMap _sources;
+        DefineMap _defines;
+        ReplaceMap _replaces;
+        friend class ShaderLoader;
+    };
+
+    /**
+     * Base class for local shader file/source pairs.
+     */
+    class OSGEARTH_EXPORT ShaderLoader
+    {
+    public:
+        /**
+         * Loads shader source from the specified filename, and calls 
+         * setFunction() on the virtual program to install the shader.
+         * The shader much include #pragma definitions for both its
+         * entry point and its location, e.g.:
+         *
+         *   #pragma vp_entryPoint "oe_my_shader"
+         *   #pargma vp_location   "VERTEX_VIEW"
+         */
+        static bool load(
+            VirtualProgram*       vp,
+            const std::string&    filename,
+            const ShaderPackage&  package,
+            const osgDB::Options* dbOptions =0L );
+
+        /**
+         * Removes a function loaded by load from the VirtualProgram.
+         */
+        static bool unload(
+            VirtualProgram*       vp,
+            const std::string&    filename,
+            const ShaderPackage&  package,
+            const osgDB::Options* dbOptions =0L );
+
+        /**
+         * Loads shader source from the specified filename. If the
+         * file can't be found in the OSG file path, use the source
+         * provided in backupSource.
+         */
+        static std::string load(
+            const std::string&    filename,
+            const std::string&    backupSource,
+            const osgDB::Options* dbOptions =0L );
+
+        static std::string load(
+            const std::string&    filename,
+            const ShaderPackage&  package,
+            const osgDB::Options* dbOptions =0L );
+    };
+
+} // namespace osgEarth
+
+#endif // OSGEARTH_SHADER_LOADER
+
diff --git a/src/osgEarth/ShaderLoader.cpp b/src/osgEarth/ShaderLoader.cpp
new file mode 100644
index 0000000..b72f2b0
--- /dev/null
+++ b/src/osgEarth/ShaderLoader.cpp
@@ -0,0 +1,391 @@
+/* -*-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.
+ *
+ * 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/ShaderLoader>
+#include <osgEarth/URI>
+#include <osgEarth/VirtualProgram>
+#include <osgDB/FileUtils>
+
+#undef  LC
+#define LC "[ShaderLoader] "
+
+using namespace osgEarth;
+
+namespace
+{
+    // find the value of a quoted pragma, e.g.:
+    //   #pragma oe_key "value"
+    // returns the string "value" (without the quotes).
+    std::string getQuotedPragmaValue(const std::string& source,
+                                     const std::string& key)
+    {
+        std::string::size_type includePos = source.find("#pragma " + key);
+        if ( includePos == std::string::npos )
+            return "";
+
+        std::string::size_type openQuotePos = source.find('\"', includePos);
+        if ( openQuotePos == std::string::npos )
+            return "";
+
+        std::string::size_type closeQuotePos = source.find('\"', openQuotePos+1);
+        if ( closeQuotePos == std::string::npos )
+            return "";
+
+        std::string statement = source.substr( includePos, (closeQuotePos-includePos)+1 );
+
+        std::string value = source.substr( openQuotePos+1, (closeQuotePos-openQuotePos)-1 );
+
+        return value;
+    }
+}
+
+typedef std::map<std::string,std::string> StringMap;
+
+
+std::string
+ShaderLoader::load(const std::string&    filename,
+                   const ShaderPackage&  package,
+                   const osgDB::Options* dbOptions)
+{
+    std::string output;
+    bool useInlineSource = false;
+    
+    std::string inlineSource;
+    ShaderPackage::SourceMap::const_iterator source = package._sources.find(filename); //.context().find(filename);
+    if ( source != package._sources.end() ) //.context().end() )
+        inlineSource = source->second;
+
+    std::string path = osgDB::findDataFile(filename, dbOptions);
+    if ( path.empty() )
+    {
+        output = inlineSource;
+        useInlineSource = true;
+        if ( inlineSource.empty() )
+        {
+            OE_WARN << LC << "Inline source for \"" << filename << "\" is empty, and no external file could be found.\n";
+        }
+    }
+    else
+    {
+        std::string externalSource = URI(path).getString(dbOptions);
+        if (!externalSource.empty())
+        {
+            OE_DEBUG << LC << "Loaded external shader " << filename << " from " << path << "\n";
+            output = externalSource;
+        }
+        else
+        {
+            output = inlineSource;
+            useInlineSource = true;
+        }
+    }
+
+    // replace common tokens:
+    osgEarth::replaceIn(output, "$GLSL_VERSION_STR", GLSL_VERSION_STR);
+    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 )
+    {
+        // reinstate preprocessor macros since GCC doesn't like them in the inlines shaders.
+        // The token was inserted in the CMakeModules/ConfigureShaders.cmake.in script.
+        osgEarth::replaceIn(output, "$__HASHTAG__", "#");
+    }
+
+    // Process any "#pragma include" statements
+    while(true)
+    {
+        std::string::size_type includePos = output.find("#pragma include");
+        if ( includePos == std::string::npos )
+            break;
+
+        std::string::size_type openQuotePos = output.find('\"', includePos);
+        if ( openQuotePos == std::string::npos )
+            break;
+
+        std::string::size_type closeQuotePos = output.find('\"', openQuotePos+1);
+        if ( closeQuotePos == std::string::npos )
+            break;
+
+        std::string includeStatement = output.substr( includePos, (closeQuotePos-includePos)+1 );
+
+        std::string fileToInclude = output.substr( openQuotePos+1, (closeQuotePos-openQuotePos)-1 );
+        
+        // load the source of the included file, and append a newline so we
+        // don't break the MULTILINE macro if the last line of the include
+        // file is a comment.
+        std::string fileSource = Stringify()
+            << load(fileToInclude, package, dbOptions)
+            << "\n";
+
+        osgEarth::replaceIn(output, includeStatement, fileSource);
+    }
+
+    // Process any "#pragma define" statements
+    while(true)
+    {
+        std::string::size_type definePos = output.find("#pragma vp_define");
+        if ( definePos == std::string::npos )
+            break;        
+
+        std::string::size_type openQuotePos = output.find('\"', definePos);
+        if ( openQuotePos == std::string::npos )
+            break;
+
+        std::string::size_type closeQuotePos = output.find('\"', openQuotePos+1);
+        if ( closeQuotePos == std::string::npos )
+            break;
+        
+        std::string defineStatement = output.substr( definePos, (closeQuotePos-definePos)+1 );
+
+        std::string varName = output.substr( openQuotePos+1, (closeQuotePos-openQuotePos)-1 );
+
+        ShaderPackage::DefineMap::const_iterator d = package._defines.find( varName );
+
+        bool defineIt =
+            d != package._defines.end() &&
+            d->second == true;
+
+        std::string newStatement = Stringify()
+            << (defineIt? "#define " : "#undef ")
+            << varName;
+
+        osgEarth::replaceIn( output, defineStatement, newStatement );
+    }
+
+    // Finally, do 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;
+}
+
+std::string
+ShaderLoader::load(const std::string&    filename,
+                   const std::string&    inlineSource,
+                   const osgDB::Options* dbOptions )
+{
+    std::string output;
+    bool useInlineSource = false;
+
+    std::string path = osgDB::findDataFile(filename, dbOptions);
+    if ( path.empty() )
+    {
+        output = inlineSource;
+        useInlineSource = true;
+    }
+    else
+    {
+        std::string externalSource = URI(path).getString(dbOptions);
+        if (!externalSource.empty())
+        {
+            OE_DEBUG << LC << "Loaded external shader " << filename << " from " << path << "\n";
+            output = externalSource;
+        }
+        else
+        {
+            output = inlineSource;
+            useInlineSource = true;
+        }
+    }
+
+    // replace common tokens:
+    osgEarth::replaceIn(output, "$GLSL_VERSION_STR", GLSL_VERSION_STR);
+    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 )
+    {
+        // reinstate preprocessor macros since GCC doesn't like them in the inlines shaders.
+        // The token was inserted in the CMakeModules/ConfigureShaders.cmake.in script.
+        osgEarth::replaceIn(output, "$__HASHTAG__", "#");
+    }
+
+    return output;
+}
+
+bool
+ShaderLoader::load(VirtualProgram*       vp,
+                   const std::string&    filename,
+                   const ShaderPackage&  package,
+                   const osgDB::Options* dbOptions)
+{
+    if ( !vp )
+    {
+        OE_WARN << LC << "Illegal: null VirtualProgram\n";
+        return false;
+    }
+
+    std::string source = load(filename, package, dbOptions);
+    if ( source.empty() )
+    {
+        OE_WARN << LC << "Failed to load shader source from \"" << filename << "\"\n";
+        return false;
+    }
+    
+    std::string loc = getQuotedPragmaValue(source, "vp_location");
+    if ( loc.empty() )
+    {
+        OE_WARN << LC << "Illegal: shader \"" << filename << "\" missing required #pragma vp_location\n";
+        return false;
+    }
+
+    ShaderComp::FunctionLocation location;
+    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, "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 \"" << loc << "\"\n";
+        return false;
+    }
+
+    // If entry point is set, this is a function; otherwise a simple library.
+    std::string entryPoint = getQuotedPragmaValue(source, "vp_entryPoint");
+    if ( !entryPoint.empty() )
+    {
+        // order is optional.
+        std::string orderStr = getQuotedPragmaValue(source, "vp_order");
+        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
+    {
+        // install as a simple shader.
+        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 );
+    }
+
+    return true;
+}
+
+bool
+ShaderLoader::unload(VirtualProgram*       vp,
+                     const std::string&    filename,
+                     const ShaderPackage&  package,
+                     const osgDB::Options* dbOptions)
+{
+    if ( !vp )
+    {
+        // fail quietly
+        return false;
+    }
+
+    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 = getQuotedPragmaValue(source, "vp_entryPoint");
+    if ( !entryPoint.empty() )
+    {
+        vp->removeShader( entryPoint );
+    }
+    else
+    {
+        vp->removeShader( filename );
+    }
+    return true;
+}
+
+//...................................................................
+
+void
+ShaderPackage::define(const std::string& name,
+                      bool               defOrUndef)
+{
+    _defines[name] = defOrUndef;
+}
+
+void
+ShaderPackage::replace(const std::string& pattern,
+                       const std::string& value)
+{
+    _replaces[pattern] = value;
+}
+
+bool
+ShaderPackage::load(VirtualProgram*       vp,
+                    const std::string&    filename,
+                    const osgDB::Options* dbOptions) const
+{
+    return ShaderLoader::load(vp, filename, *this, dbOptions);
+}
+
+bool
+ShaderPackage::unload(VirtualProgram*       vp,
+                      const std::string&    filename,
+                      const osgDB::Options* dbOptions) const
+{
+    return ShaderLoader::unload(vp, filename, *this, dbOptions);
+}
+
+bool
+ShaderPackage::loadAll(VirtualProgram*       vp,
+                       const osgDB::Options* dbOptions) const
+{
+    int oks = 0;
+    for(SourceMap::const_iterator i = _sources.begin(); i != _sources.end(); ++i)
+    {
+        oks += load( vp, i->first ) ? 1 : 0;
+    }
+    return oks == _sources.size();
+}
+
+bool
+ShaderPackage::unloadAll(VirtualProgram*       vp,
+                          const osgDB::Options* dbOptions) const
+{
+    int oks = 0;
+    for(SourceMap::const_iterator i = _sources.begin(); i != _sources.end(); ++i)
+    {
+        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 81265f6..d5b00c5 100644
--- a/src/osgEarth/ShaderUtils
+++ b/src/osgEarth/ShaderUtils
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -26,6 +26,8 @@
 #include <osg/Light>
 #include <osg/Material>
 #include <osg/observer_ptr>
+#include <osgDB/Options>
+#include <map>
 
 namespace osgEarth
 {
@@ -40,7 +42,6 @@ namespace osgEarth
         SHADERPOLICY_INHERIT
     };
 
-
     /**
     * Container for light uniforms
     */
diff --git a/src/osgEarth/ShaderUtils.cpp b/src/osgEarth/ShaderUtils.cpp
index 7c37559..48e1ef5 100644
--- a/src/osgEarth/ShaderUtils.cpp
+++ b/src/osgEarth/ShaderUtils.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -18,10 +18,13 @@
  */
 #include <osgEarth/ShaderUtils>
 #include <osgEarth/ShaderFactory>
+#include <osgEarth/VirtualProgram>
 #include <osgEarth/Registry>
 #include <osgEarth/Capabilities>
 #include <osgEarth/CullingUtils>
+#include <osgEarth/URI>
 #include <osg/ComputeBoundsVisitor>
+#include <osgDB/FileUtils>
 #include <list>
 
 using namespace osgEarth;
@@ -130,7 +133,8 @@ namespace
     }
 }
 
-//------------------------------------------------------------------------
+#undef LC
+#define LC "[ShaderUtils] "
 
 namespace
 {
@@ -313,6 +317,22 @@ void osg_LightSourceParameters::setUniformsFromOsgLight(const osg::Light* light,
                                                       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()));
+        }
     }
 }
 
@@ -762,15 +782,10 @@ RangeUniformCullCallback::operator()(osg::Node* node, osg::NodeVisitor* nv)
 
 //------------------------------------------------------------------------
 
-namespace
-{
-    
-}
-
 void
 DiscardAlphaFragments::install(osg::StateSet* ss, float minAlpha) const
 {
-    if ( ss && minAlpha < 1.0f )
+    if ( ss && minAlpha < 1.0f && Registry::capabilities().supportsGLSL() )
     {
         VirtualProgram* vp = VirtualProgram::getOrCreate(ss);
         if ( vp )
@@ -785,7 +800,7 @@ DiscardAlphaFragments::install(osg::StateSet* ss, float minAlpha) const
                 "oe_discardalpha_frag",
                 code,
                 ShaderComp::LOCATION_FRAGMENT_COLORING,
-                0L, 2.0f);
+                0L, 0.95f);
         }
     }
 }
diff --git a/src/osgEarth/Shaders b/src/osgEarth/Shaders
new file mode 100644
index 0000000..01a4f13
--- /dev/null
+++ b/src/osgEarth/Shaders
@@ -0,0 +1,43 @@
+/* -*-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_SHADERS
+#define OSGEARTH_SHADERS 1
+
+#include <osgEarth/ShaderLoader>
+
+namespace osgEarth
+{
+    class OSGEARTH_EXPORT Shaders : public ShaderPackage
+	{
+    public:
+        Shaders();
+
+        std::string AlphaEffectFragment;
+        std::string DepthOffsetVertex;
+        std::string DrapingVertex, DrapingFragment;
+        std::string GPUClampingVertex, GPUClampingFragment, GPUClampingVertexLib;
+        std::string InstancingVertex;
+	};	
+
+} // namespace osgEarth
+
+#endif // OSGEARTH_SHADERS
diff --git a/src/osgEarth/Shaders.cpp.in b/src/osgEarth/Shaders.cpp.in
new file mode 100644
index 0000000..9b7c70e
--- /dev/null
+++ b/src/osgEarth/Shaders.cpp.in
@@ -0,0 +1,42 @@
+// ***DO NOT EDIT THIS FILE - IT IS AUTOMATICALLY GENERATED BY CMAKE***
+
+#include <osgEarth/Shaders>
+
+namespace osgEarth
+{
+    Shaders::Shaders()
+    {
+        // AlphaEffect
+        AlphaEffectFragment = "AlphaEffect.frag.glsl";
+        _sources[AlphaEffectFragment] = OE_MULTILINE(@AlphaEffect.frag.glsl@);
+
+
+        // Depth Offset
+        DepthOffsetVertex = "DepthOffset.vert.glsl";
+       _sources[DepthOffsetVertex] = OE_MULTILINE(@DepthOffset.vert.glsl@);
+
+
+       // Draping
+       DrapingVertex = "Draping.vert.glsl";
+       _sources[DrapingVertex] = OE_MULTILINE(@Draping.vert.glsl@);
+
+       DrapingFragment = "Draping.frag.glsl";
+       _sources[DrapingFragment] = OE_MULTILINE(@Draping.frag.glsl@);
+
+
+        // GPU Clamping
+        GPUClampingVertex = "GPUClamping.vert.glsl";
+        _sources[GPUClampingVertex] = OE_MULTILINE(@GPUClamping.vert.glsl@);
+
+        GPUClampingFragment = "GPUClamping.frag.glsl";
+        _sources[GPUClampingFragment] = OE_MULTILINE(@GPUClamping.frag.glsl@);
+
+        GPUClampingVertexLib = "GPUClamping.vert.lib.glsl";
+        _sources[GPUClampingVertexLib] = OE_MULTILINE(@GPUClamping.vert.lib.glsl@);
+
+
+        // DrawInstanced
+        InstancingVertex = "Instancing.vert.glsl";
+        _sources[InstancingVertex] = OE_MULTILINE(@Instancing.vert.glsl@);
+    }
+};
\ No newline at end of file
diff --git a/src/osgEarth/SharedSARepo b/src/osgEarth/SharedSARepo
index e9edfa6..94e8c40 100644
--- a/src/osgEarth/SharedSARepo
+++ b/src/osgEarth/SharedSARepo
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/osgEarth/SparseTexture2DArray b/src/osgEarth/SparseTexture2DArray
deleted file mode 100644
index cdcba2a..0000000
--- a/src/osgEarth/SparseTexture2DArray
+++ /dev/null
@@ -1,79 +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_SPARSE_TEXTURE_2D_ARRAY_H
-#define OSGEARTH_SPARSE_TEXTURE_2D_ARRAY_H 1
-
-#include <osgEarth/Common>
-
-// this class is only supported in newer OSG versions.
-#if OSG_VERSION_GREATER_OR_EQUAL( 2, 9, 8 )
-
-#include <osg/Texture2DArray>
-
-namespace osgEarth
-{
-    /**
-     * Specialized version of osg::Texture2DArray that allows NULL image layers.
-     * This is an internal class - no export macro.
-     */
-    class SparseTexture2DArray : public osg::Texture2DArray
-    {
-    public:
-        SparseTexture2DArray() : osg::Texture2DArray() { }
-
-        SparseTexture2DArray( const SparseTexture2DArray& rhs, const osg::CopyOp& copyop =osg::CopyOp::DEEP_COPY_ALL )
-            : osg::Texture2DArray( rhs, copyop ) { }
-
-        META_StateAttribute( osgEarth, SparseTexture2DArray, TEXTURE );
-
-        /** dtor */
-        virtual ~SparseTexture2DArray() { }
-
-
-    public:
-        virtual void computeInternalFormat() const;
-
-        virtual void apply( osg::State& state ) const;
-
-    protected:
-
-#if OSG_VERSION_LESS_THAN(2,9,7)
-        bool isSafeToUnrefImageData(const osg::State& state) const {
-            return (_unrefImageDataAfterApply && areAllTextureObjectsLoaded());
-        }
-#elif OSG_VERSION_LESS_THAN(2,9,10)
-        bool isSafeToUnrefImageData(const osg::State& state) const {
-            return (_unrefImageDataAfterApply && state.getMaxTexturePoolSize()==0 && areAllTextureObjectsLoaded());
-        }
-#endif
-
-    private:
-        int firstValidImageIndex() const;
-
-        osg::Image* firstValidImage() const;
-
-        // replaces the same func in the superclass
-        void applyTexImage2DArray_subload( osg::State& state, osg::Image* image, GLsizei inwidth, GLsizei inheight, GLsizei indepth, GLint inInternalFormat, GLsizei& numMipmapLevels) const;
-    };
-}
-
-#endif // OSG_VERSION_GREATER_OR_EQUAL( 2, 9, 8 )
-
-#endif // OSGEARTH_TILE_FACTORY_H
diff --git a/src/osgEarth/SparseTexture2DArray.cpp b/src/osgEarth/SparseTexture2DArray.cpp
deleted file mode 100644
index 272951f..0000000
--- a/src/osgEarth/SparseTexture2DArray.cpp
+++ /dev/null
@@ -1,406 +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 <osgEarth/SparseTexture2DArray>
-
-// this class is only supported in newer OSG versions.
-#if OSG_VERSION_GREATER_OR_EQUAL( 2, 9, 8 )
-
-using namespace osgEarth;
-
-int
-SparseTexture2DArray::firstValidImageIndex() const 
-{
-    for( int i=0; i<(int)_images.size(); ++i )
-        if ( _images[i].valid() )
-            return i;
-    return -1;
-}
-
-osg::Image* 
-SparseTexture2DArray::firstValidImage() const
-{
-    int i = firstValidImageIndex();
-    return i >= 0 ? _images[i].get() : 0L;
-}
-
-void 
-SparseTexture2DArray::computeInternalFormat() const
-{
-    osg::Image* image = firstValidImage();
-    if ( image )
-        computeInternalFormatWithImage( *image );
-    else
-        computeInternalFormatType();
-}
-
-void 
-SparseTexture2DArray::apply( osg::State& state ) const
-{
-    // get the contextID (user defined ID of 0 upwards) for the 
-    // current OpenGL context.
-    const unsigned int contextID = state.getContextID();
-
-    Texture::TextureObjectManager* tom = Texture::getTextureObjectManager(contextID).get();
-    //ElapsedTime elapsedTime(&(tom->getApplyTime()));
-    tom->getNumberApplied()++;
-
-    const Extensions* extensions = getExtensions(contextID,true);
-
-    // if not supported, then return
-    if (!extensions->isTexture2DArraySupported() || !extensions->isTexture3DSupported())
-    {
-        OSG_WARN<<"Warning: Texture2DArray::apply(..) failed, 2D texture arrays are not support by OpenGL driver."<<std::endl;
-        return;
-    }
-
-    // get the texture object for the current contextID.
-    TextureObject* textureObject = getTextureObject(contextID);
-
-    if (textureObject && _textureDepth>0)
-    {
-        const osg::Image* image = firstValidImage();
-        if (image && getModifiedCount(0, contextID) != image->getModifiedCount())
-        {
-            // compute the internal texture format, this set the _internalFormat to an appropriate value.
-            computeInternalFormat();
-
-            GLsizei new_width, new_height, new_numMipmapLevels;
-
-            // compute the dimensions of the texture.
-            computeRequiredTextureDimensions(state, *image, new_width, new_height, new_numMipmapLevels);
-
-            if (!textureObject->match(GL_TEXTURE_2D_ARRAY_EXT, new_numMipmapLevels, _internalFormat, new_width, new_height, 1, _borderWidth))
-            {
-                Texture::releaseTextureObject(contextID, _textureObjectBuffer[contextID].get());
-                _textureObjectBuffer[contextID] = 0;
-                textureObject = 0;
-            }
-        }
-    }
-
-    // if we already have an texture object, then 
-    if (textureObject)
-    {
-        // bind texture object
-        textureObject->bind();
-
-        // if texture parameters changed, then reset them
-        if (getTextureParameterDirty(state.getContextID())) applyTexParameters(GL_TEXTURE_2D_ARRAY_EXT,state);
-
-        // if subload is specified, then use it to subload the images to GPU memory
-        //if (_subloadCallback.valid())
-        //{
-        //    _subloadCallback->subload(*this,state);
-        //}
-        //else
-        {
-            // for each image of the texture array do
-            for (GLsizei n=0; n < _textureDepth; n++)
-            {
-                osg::Image* image = _images[n].get();
-
-                // if image content is modified, then upload it to the GPU memory
-                // GW: this means we have to "dirty" an image before setting it!
-                if (image && getModifiedCount(n,contextID) != image->getModifiedCount())
-                {
-                    applyTexImage2DArray_subload(state, image, _textureWidth, _textureHeight, n, _internalFormat, _numMipmapLevels);
-                    getModifiedCount(n,contextID) = image->getModifiedCount();
-                }
-            }
-        }
-    }
-
-    // nothing before, but we have valid images, so do manual upload and create texture object manually
-    else if ( firstValidImage() != 0L ) // if (imagesValid())
-    {
-        // compute the internal texture format, this set the _internalFormat to an appropriate value.
-        computeInternalFormat();
-
-        // compute the dimensions of the texture.
-        osg::Image* firstImage = firstValidImage();
-        computeRequiredTextureDimensions(state, *firstImage, _textureWidth, _textureHeight, _numMipmapLevels);
-
-        // create texture object
-        textureObject = generateTextureObject(
-            this, contextID,GL_TEXTURE_2D_ARRAY_EXT,_numMipmapLevels,_internalFormat,_textureWidth,_textureHeight,_textureDepth,0);
-
-        // bind texture
-        textureObject->bind();
-        applyTexParameters(GL_TEXTURE_2D_ARRAY_EXT, state);
-
-        _textureObjectBuffer[contextID] = textureObject;
-
-        // First we need to allocate the texture memory
-        int sourceFormat = _sourceFormat ? _sourceFormat : _internalFormat;
-
-        if( isCompressedInternalFormat( sourceFormat ) && 
-            sourceFormat == _internalFormat &&
-            extensions->isCompressedTexImage3DSupported() )
-        {
-            extensions->glCompressedTexImage3D( GL_TEXTURE_2D_ARRAY_EXT, 0, _internalFormat,
-                _textureWidth, _textureHeight, _textureDepth, _borderWidth,
-                firstImage->getImageSizeInBytes() * _textureDepth,
-                0);
-        }
-        else
-        {   
-            // Override compressed source format with safe GL_RGBA value which not generate error
-            // We can safely do this as source format is not important when source data is NULL
-            if( isCompressedInternalFormat( sourceFormat ) )
-                sourceFormat = GL_RGBA;
-
-            extensions->glTexImage3D( GL_TEXTURE_2D_ARRAY_EXT, 0, _internalFormat,
-                _textureWidth, _textureHeight, _textureDepth, _borderWidth,
-                sourceFormat, _sourceType ? _sourceType : GL_UNSIGNED_BYTE,
-                0); 
-        }
-
-        // For certain we have to manually allocate memory for mipmaps if images are compressed
-        // if not allocated OpenGL will produce errors on mipmap upload.
-        // I have not tested if this is neccessary for plain texture formats but 
-        // common sense suggests its required as well.
-        if( _min_filter != LINEAR && _min_filter != NEAREST && firstImage->isMipmap() )
-            allocateMipmap( state );
-
-        // now for each layer we upload it into the memory
-        for (GLsizei n=0; n<_textureDepth; n++)
-        {
-            // if image is valid then upload it to the texture memory
-            osg::Image* image = _images[n].get();
-            if (image)
-            {
-                // now load the image data into the memory, this will also check if image do have valid properties
-                applyTexImage2DArray_subload(state, image, _textureWidth, _textureHeight, n, _internalFormat, _numMipmapLevels);
-                getModifiedCount(n,contextID) = image->getModifiedCount();
-            }
-        }
-
-        const Texture::Extensions* texExtensions = Texture::getExtensions(contextID,true);
-        // source images have no mipmamps but we could generate them...  
-        if( _min_filter != LINEAR && _min_filter != NEAREST && !firstImage->isMipmap() &&  
-            _useHardwareMipMapGeneration && texExtensions->isGenerateMipMapSupported() )
-        {
-            _numMipmapLevels = osg::Image::computeNumberOfMipmapLevels( _textureWidth, _textureHeight );
-            generateMipmap( state );
-        }
-
-        textureObject->setAllocated(_numMipmapLevels,_internalFormat,_textureWidth,_textureHeight,_textureDepth,0);
-
-        // unref image data?
-        if (isSafeToUnrefImageData(state))
-        {
-            SparseTexture2DArray* non_const_this = const_cast<SparseTexture2DArray*>(this);
-            for (int n=0; n<_textureDepth; n++)
-            {                
-                if (_images[n].valid() && _images[n]->getDataVariance()==STATIC)
-                {
-                    non_const_this->_images[n] = NULL;
-                }
-            }
-        }
-
-    }
-
-    // No images present, but dimensions are set. So create empty texture
-    else if ( (_textureWidth > 0) && (_textureHeight > 0) && (_textureDepth > 0) && (_internalFormat!=0) )
-    {
-        // generate texture 
-        _textureObjectBuffer[contextID] = textureObject = generateTextureObject(
-            this, contextID, GL_TEXTURE_2D_ARRAY_EXT,_numMipmapLevels,_internalFormat,_textureWidth,_textureHeight,_textureDepth,0);
-
-        textureObject->bind();
-        applyTexParameters(GL_TEXTURE_2D_ARRAY_EXT,state);
-
-        extensions->glTexImage3D( GL_TEXTURE_2D_ARRAY_EXT, 0, _internalFormat,
-            _textureWidth, _textureHeight, _textureDepth,
-            _borderWidth,
-            _sourceFormat ? _sourceFormat : _internalFormat,
-            _sourceType ? _sourceType : GL_UNSIGNED_BYTE,
-            0); 
-
-    }
-
-    // nothing before, so just unbind the texture target
-    else
-    {
-        glBindTexture( GL_TEXTURE_2D_ARRAY_EXT, 0 );
-    }
-
-    // if texture object is now valid and we have to allocate mipmap levels, then
-    if (textureObject != 0 && _texMipmapGenerationDirtyList[contextID])
-    {
-        generateMipmap(state);
-    }
-}
-
-// replaces the same func in the superclass
-void
-SparseTexture2DArray::applyTexImage2DArray_subload(osg::State& state, osg::Image* image,
-                                                   GLsizei inwidth, GLsizei inheight, GLsizei indepth, 
-                                                   GLint inInternalFormat, GLsizei& numMipmapLevels) const
-{
-    //// if we don't have a valid image we can't create a texture!
-    //if (!imagesValid())
-    //    return;
-
-    // get the contextID (user defined ID of 0 upwards) for the 
-    // current OpenGL context.
-    const unsigned int contextID = state.getContextID();
-    const Extensions* extensions = getExtensions(contextID,true);    
-    const Texture::Extensions* texExtensions = Texture::getExtensions(contextID,true);
-    GLenum target = GL_TEXTURE_2D_ARRAY_EXT;
-
-    // compute the internal texture format, this set the _internalFormat to an appropriate value.
-    computeInternalFormat();
-
-    // select the internalFormat required for the texture.
-    // bool compressed = isCompressedInternalFormat(_internalFormat);
-    bool compressed_image = isCompressedInternalFormat((GLenum)image->getPixelFormat());
-
-    // if the required layer is exceeds the maximum allowed layer sizes
-    if (indepth > extensions->maxLayerCount())
-    {
-        // we give a warning and do nothing
-        OSG_WARN<<"Warning: Texture2DArray::applyTexImage2DArray_subload(..) the given layer number exceeds the maximum number of supported layers."<<std::endl;
-        return;        
-    }
-
-    //Rescale if resize hint is set or NPOT not supported or dimensions exceed max size
-    if( _resizeNonPowerOfTwoHint || !texExtensions->isNonPowerOfTwoTextureSupported(_min_filter)
-        || inwidth > extensions->max2DSize()
-        || inheight > extensions->max2DSize())
-        image->ensureValidSizeForTexturing(extensions->max2DSize());
-
-    // image size or format has changed, this is not allowed, hence return
-    if (image->s()!=inwidth || 
-        image->t()!=inheight || 
-        image->getInternalTextureFormat()!=inInternalFormat ) 
-    {
-        OSG_WARN<<"Warning: Texture2DArray::applyTexImage2DArray_subload(..) given image do have wrong dimension or internal format."<<std::endl;
-        return;        
-    }    
-
-    glPixelStorei(GL_UNPACK_ALIGNMENT,image->getPacking());
-
-    bool useHardwareMipmapGeneration = 
-        !image->isMipmap() && _useHardwareMipMapGeneration && texExtensions->isGenerateMipMapSupported();
-
-    // if no special mipmapping is required, then
-    if( _min_filter == LINEAR || _min_filter == NEAREST || useHardwareMipmapGeneration )
-    {
-        if( _min_filter == LINEAR || _min_filter == NEAREST )
-            numMipmapLevels = 1;
-        else //Hardware Mipmap Generation
-            numMipmapLevels = image->getNumMipmapLevels();
-
-        // upload non-compressed image
-        if ( !compressed_image )
-        {
-            extensions->glTexSubImage3D( target, 0,
-                0, 0, indepth,
-                inwidth, inheight, 1,
-                (GLenum)image->getPixelFormat(),
-                (GLenum)image->getDataType(),
-                image->data() );
-        }
-
-        // if we support compression and image is compressed, then
-        else if (extensions->isCompressedTexImage3DSupported())
-        {
-            // OSG_WARN<<"glCompressedTexImage3D "<<inwidth<<", "<<inheight<<", "<<indepth<<std::endl;
-
-            GLint blockSize, size;
-            getCompressedSize(_internalFormat, inwidth, inheight, 1, blockSize,size);
-
-            extensions->glCompressedTexSubImage3D(target, 0,
-                0, 0, indepth,  
-                inwidth, inheight, 1, 
-                (GLenum)image->getPixelFormat(),
-                size, 
-                image->data());
-        }
-
-        // we want to use mipmapping, so enable it
-    }else
-    {
-        // image does not provide mipmaps, so we have to create them
-        if( !image->isMipmap() )
-        {
-            numMipmapLevels = 1;
-            OSG_WARN<<"Warning: Texture2DArray::applyTexImage2DArray_subload(..) mipmap layer not passed, and auto mipmap generation turned off or not available. Check texture's min/mag filters & hardware mipmap generation."<<std::endl;
-
-            // the image object does provide mipmaps, so upload the in the certain levels of a layer
-        }else
-        {
-            numMipmapLevels = image->getNumMipmapLevels();
-
-            int width  = image->s();
-            int height = image->t();
-
-            if( !compressed_image )
-            {
-
-                for( GLsizei k = 0 ; k < numMipmapLevels  && (width || height ) ;k++)
-                {
-                    if (width == 0)
-                        width = 1;
-                    if (height == 0)
-                        height = 1;
-
-                    extensions->glTexSubImage3D( target, k, 0, 0, indepth,
-                        width, height, 1, 
-                        (GLenum)image->getPixelFormat(),
-                        (GLenum)image->getDataType(),
-                        image->getMipmapData(k));
-
-                    width >>= 1;
-                    height >>= 1;
-                }
-            }
-            else if (extensions->isCompressedTexImage3DSupported())
-            {
-                GLint blockSize,size;
-                for( GLsizei k = 0 ; k < numMipmapLevels  && (width || height) ;k++)
-                {
-                    if (width == 0)
-                        width = 1;
-                    if (height == 0)
-                        height = 1;
-
-                    getCompressedSize(image->getInternalTextureFormat(), width, height, 1, blockSize,size);
-
-                    //                    state.checkGLErrors("before extensions->glCompressedTexSubImage3D(");
-
-                    extensions->glCompressedTexSubImage3D(target, k, 0, 0, indepth,
-                        width, height, 1,
-                        (GLenum)image->getPixelFormat(),
-                        size,
-                        image->getMipmapData(k));
-
-                    //                    state.checkGLErrors("after extensions->glCompressedTexSubImage3D(");
-
-                    width >>= 1;
-                    height >>= 1;
-                }
-            }
-        }
-    }
-}
-
-#endif // OSG_VERSION_GREATER_OR_EQUAL( 2, 9, 8 )
diff --git a/src/osgEarth/SpatialReference b/src/osgEarth/SpatialReference
index 25ff5d4..9353346 100644
--- a/src/osgEarth/SpatialReference
+++ b/src/osgEarth/SpatialReference
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -113,6 +113,11 @@ namespace osgEarth
             const SpatialReference* outputSRS,
             double                  latitude =0.0) const;
 
+        static double transformUnits(
+            const Distance&         distance,
+            const SpatialReference* outputSRS,
+            double                  latitude =0.0);
+
 
     public: // World transformations.
 
@@ -295,7 +300,12 @@ namespace osgEarth
         /** Creates a copy of this SRS, but flags the new SRS so that it will operate in
             Plate Carre mode for the purposes of world coordinate conversion. The SRS is
             otherwise mathematically equivalent to its vanilla counterpart. */
-        const SpatialReference* createPlateCarreGeographicSRS() const;
+        //const SpatialReference* createPlateCarreGeographicSRS() const;
+
+        /** Create an equirectangular projected SRS corresponding to the geographic SRS
+            contained in this spatial reference. This is an approximation of a Plate Carre
+            SRS but using equatorial meters. */
+        const SpatialReference* createEquirectangularSRS() const;
 
         /** Creates a new CSN based on this spatial reference. */
         osg::CoordinateSystemNode* createCoordinateSystemNode() const;
diff --git a/src/osgEarth/SpatialReference.cpp b/src/osgEarth/SpatialReference.cpp
index ab5e39c..1dfd47f 100644
--- a/src/osgEarth/SpatialReference.cpp
+++ b/src/osgEarth/SpatialReference.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -260,6 +260,7 @@ SpatialReference::create( const Key& key, bool useCache )
             "WGS84" );
 
         srs->_is_plate_carre = true;
+        srs->_is_geographic  = false;
     }
 
     // custom srs for the unified cube
@@ -782,12 +783,10 @@ SpatialReference::createUTMFromLonLat( const Angular& lon, const Angular& lat )
     return create( horiz, getVertInitString() );
 }
 
-const SpatialReference* 
-SpatialReference::createPlateCarreGeographicSRS() const
+const SpatialReference*
+SpatialReference::createEquirectangularSRS() const
 {
-    SpatialReference* pc = create( getKey(), false );
-    if ( pc ) pc->_is_plate_carre = true;
-    return pc;
+    return SpatialReference::create("+proj=eqc +units=m +no_defs", getVertInitString());
 }
 
 bool
@@ -1317,6 +1316,29 @@ SpatialReference::transformUnits(double                  input,
     }
 }
 
+double
+SpatialReference::transformUnits(const Distance&         distance,
+                                 const SpatialReference* outSRS,
+                                 double                  latitude)
+{
+    if ( distance.getUnits().isLinear() && outSRS->isGeographic() )
+    {
+        double metersPerEquatorialDegree = (outSRS->getEllipsoid()->getRadiusEquator() * 2.0 * osg::PI) / 360.0;
+        double inputDegrees = distance.as(Units::METERS) / (metersPerEquatorialDegree * cos(osg::DegreesToRadians(latitude)));
+        return Units::DEGREES.convertTo( outSRS->getUnits(), inputDegrees );
+    }
+    else if ( distance.getUnits().isAngular() && outSRS->isProjected() )
+    {
+        double metersPerEquatorialDegree = (outSRS->getEllipsoid()->getRadiusEquator() * 2.0 * osg::PI) / 360.0;
+        double inputMeters = distance.as(Units::DEGREES) * (metersPerEquatorialDegree * cos(osg::DegreesToRadians(latitude)));
+        return Units::METERS.convertTo( outSRS->getUnits(), inputMeters );
+    }
+    else // both projected or both geographic.
+    {
+        return distance.as( outSRS->getUnits() );
+    }
+}
+
 bool
 SpatialReference::transformExtentToMBR(const SpatialReference* to_srs,
                                        double&                 in_out_xmin,
@@ -1476,7 +1498,7 @@ SpatialReference::_init()
     _is_user_defined = false; 
     _is_contiguous = true;
     _is_cube = false;
-    if ( _is_ecef )
+    if ( _is_ecef || _is_plate_carre )
         _is_geographic = false;
     else
         _is_geographic = OSRIsGeographic( _handle ) != 0;
diff --git a/src/osgEarth/StateSetCache b/src/osgEarth/StateSetCache
index a60bd1d..f0e1f40 100644
--- a/src/osgEarth/StateSetCache
+++ b/src/osgEarth/StateSetCache
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/osgEarth/StateSetCache.cpp b/src/osgEarth/StateSetCache.cpp
index 063c131..2226765 100644
--- a/src/osgEarth/StateSetCache.cpp
+++ b/src/osgEarth/StateSetCache.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarth/StateSetLOD b/src/osgEarth/StateSetLOD
index b14b4ff..c6ba867 100644
--- a/src/osgEarth/StateSetLOD
+++ b/src/osgEarth/StateSetLOD
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/osgEarth/StateSetLOD.cpp b/src/osgEarth/StateSetLOD.cpp
index 03cd95d..321e6d0 100644
--- a/src/osgEarth/StateSetLOD.cpp
+++ b/src/osgEarth/StateSetLOD.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/osgEarth/StringUtils b/src/osgEarth/StringUtils
index a1bc9fb..6d4c34f 100644
--- a/src/osgEarth/StringUtils
+++ b/src/osgEarth/StringUtils
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -82,6 +82,13 @@ namespace osgEarth
         const std::string& rhs,
         const std::locale& local = std::locale() );
 
+    /**
+     * Case-insensitive STL comparator
+     */
+    struct OSGEARTH_EXPORT CIStringComp {
+        bool operator()(const std::string& lhs, const std::string& rhs) const;
+    };
+
 
     extern OSGEARTH_EXPORT std::string joinStrings( const StringVector& input, char delim );
 
diff --git a/src/osgEarth/StringUtils.cpp b/src/osgEarth/StringUtils.cpp
index e9d9b87..0ffbcf6 100644
--- a/src/osgEarth/StringUtils.cpp
+++ b/src/osgEarth/StringUtils.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -20,6 +20,7 @@
 #include <osgEarth/StringUtils>
 #include <osgDB/FileNameUtils>
 #include <cctype>
+#include <cstring>
 
 using namespace osgEarth;
 
@@ -452,6 +453,17 @@ osgEarth::ciEquals(const std::string& lhs, const std::string& rhs, const std::lo
     return true;
 }
 
+#if defined(WIN32) && !defined(__CYGWIN__)
+#  define STRICMP ::stricmp
+#else
+#  define STRICMP ::strcasecmp
+#endif
+
+bool CIStringComp::operator()(const std::string& lhs, const std::string& rhs) const
+{
+    return STRICMP( lhs.c_str(), rhs.c_str() ) < 0;
+}
+
 bool
 osgEarth::startsWith( const std::string& ref, const std::string& pattern, bool caseSensitive, const std::locale& loc )
 {
diff --git a/src/osgEarth/TaskService b/src/osgEarth/TaskService
index e42d55b..9732182 100644
--- a/src/osgEarth/TaskService
+++ b/src/osgEarth/TaskService
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -145,6 +145,9 @@ namespace osgEarth
 
         void setDone();
 
+        bool isFull() const;
+        bool isEmpty() const;
+
         unsigned int getMaxSize() const { return _maxSize;}
 
         void setStamp( int value ) { _stamp = value; }
@@ -156,9 +159,8 @@ namespace osgEarth
     private:
         TaskRequestPriorityMap _requests;
         OpenThreads::Mutex _mutex;
-        OpenThreads::Mutex _addMutex;
-        OpenThreads::Condition _cond;
-        OpenThreads::Condition _addCond;
+        OpenThreads::Condition _notFull;
+        OpenThreads::Condition _notEmpty;
         volatile bool _done;
         unsigned int _maxSize;
 
diff --git a/src/osgEarth/TaskService.cpp b/src/osgEarth/TaskService.cpp
index b0024fb..bfb9739 100644
--- a/src/osgEarth/TaskService.cpp
+++ b/src/osgEarth/TaskService.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -89,6 +89,18 @@ TaskRequestQueue::cancel()
     _requests.clear();
 }
 
+bool
+TaskRequestQueue::isFull() const
+{
+    return _maxSize > 0 && (_maxSize == _requests.size());
+}
+
+bool
+TaskRequestQueue::isEmpty() const
+{
+    return !_done && _requests.empty();
+}
+
 unsigned int
 TaskRequestQueue::getNumRequests() const
 {
@@ -105,54 +117,57 @@ TaskRequestQueue::add( TaskRequest* request )
     if ( !request->getProgressCallback() )
         request->setProgressCallback( new ProgressCallback() );
 
-    // Lock on the add mutex so no one else can add.
-    ScopedLock<Mutex> lock( _addMutex );
-
-    if (_maxSize > 0)
     {
-        if (_requests.size() == _maxSize)    
-        {        
-            //OE_NOTICE << "Waiting on add...." << std::endl;
-            _addCond.wait( &_addMutex );        
-            //OE_NOTICE << "I have awakend!" << std::endl;
+        // Lock on the add mutex so no one else can add.
+        ScopedLock<Mutex> lock( _mutex );
+
+        while(isFull())
+        {
+            _notFull.wait(&_mutex);
         }
-    }
 
+        // Check to make sure the bounded queue is working correctly.
+        if (_maxSize > 0 && _requests.size() > _maxSize)
+        {
+            OE_NOTICE << "ERROR:  TaskRequestQueue requests " << getNumRequests() << " > max size of " << _maxSize << std::endl;
+        }
 
-    // Now lock on the main mutex to protect the queue
-    ScopedLock<Mutex> lock2( _mutex );
-    // insert by priority.
-    _requests.insert( std::pair<float,TaskRequest*>(request->getPriority(), request) );
+        // insert by priority.
+        _requests.insert( std::pair<float,TaskRequest*>(request->getPriority(), request) );
+    }
 
     //OE_NOTICE << "There are now " << _requests.size() << " tasks" << std::endl;
 
     // since there is data in the queue, wake up one waiting task thread.
-    _cond.signal(); 
+    _notEmpty.signal(); 
 }
 
 TaskRequest* 
 TaskRequestQueue::get()
 {
-    ScopedLock<Mutex> lock(_mutex);
+    
+    osg::ref_ptr<TaskRequest> next;
+    {
+        ScopedLock<Mutex> lock(_mutex);
 
-    while ( !_done && _requests.empty() )
-    {                
-        _cond.wait( &_mutex );        
-    }
+        while ( isEmpty() )
+        {                
+            _notEmpty.wait( &_mutex );        
+        }
 
-    if ( _done )
-    {
-        return 0L;
-    }
+        if ( _done )
+        {
+            return 0L;
+        }
 
-    osg::ref_ptr<TaskRequest> next = _requests.begin()->second.get(); //_requests.front();
-    _requests.erase( _requests.begin() ); //_requests.pop_front();
+        next = _requests.begin()->second.get(); //_requests.front();
+        _requests.erase( _requests.begin() ); //_requests.pop_front();
+    }
 
     // I'm done, someone else take a turn:
     // (technically this shouldn't be necessary since add() bumps the semaphore once
     // for each request in the queue)    
-    _cond.signal();    
-    _addCond.signal();
+    _notFull.signal();    
 
     return next.release();
 }
@@ -169,8 +184,10 @@ TaskRequestQueue::setDone()
     //_cond.broadcast();
 
     // alternative to buggy win32 broadcast (OSG pre-r10457 on windows)
-    for(int i=0; i<128; i++)
-        _cond.signal();
+    for(int i=0; i<128; i++) {
+        _notFull.signal();
+        _notEmpty.signal();
+    }
 }
 
 //------------------------------------------------------------------------
diff --git a/src/osgEarth/Terrain b/src/osgEarth/Terrain
index fca0b3b..5bfd682 100644
--- a/src/osgEarth/Terrain
+++ b/src/osgEarth/Terrain
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarth/Terrain.cpp b/src/osgEarth/Terrain.cpp
index c9ed037..0fe1674 100644
--- a/src/osgEarth/Terrain.cpp
+++ b/src/osgEarth/Terrain.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -128,8 +128,7 @@ Terrain::getHeight(osg::Node*              patch,
     lsi->setIntersectionLimit(osgUtil::Intersector::LIMIT_NEAREST);
 
     osgUtil::IntersectionVisitor iv( lsi );
-    iv.setTraversalMask( ~_terrainOptions.secondaryTraversalMask().value() );
-
+ 
     if ( patch )
         patch->accept( iv );
     else
@@ -174,10 +173,6 @@ Terrain::getWorldCoordsUnderMouse(osg::View* view, float x, float y, osg::Vec3d&
     osg::NodePath nodePath;
     nodePath.push_back( _graph.get() );
 
-    // fine but computeIntersections won't travers a masked Drawable, a la quadtree.
-    unsigned traversalMask = ~_terrainOptions.secondaryTraversalMask().value();
-
-
 #if 0
     // Old code, uses the computeIntersections method directly but sufferes from floating point precision problems.
     if ( view2->computeIntersections( x, y, nodePath, intersections, traversalMask ) )
@@ -228,7 +223,6 @@ Terrain::getWorldCoordsUnderMouse(osg::View* view, float x, float y, osg::Vec3d&
     picker->setIntersectionLimit( osgUtil::Intersector::LIMIT_NEAREST );
 
     osgUtil::IntersectionVisitor iv(picker.get());
-    iv.setTraversalMask(traversalMask);
     nodePath.back()->accept(iv);
 
     if (picker->containsIntersections())
@@ -259,10 +253,7 @@ Terrain::getWorldCoordsUnderMouse(osg::View* view,
     osg::NodePath path;
     path.push_back( _graph.get() );
 
-    // fine but computeIntersections won't travers a masked Drawable, a la quadtree.
-    unsigned mask = ~_terrainOptions.secondaryTraversalMask().value();
-
-    if ( view2->computeIntersections( x, y, path, results, mask ) )
+    if ( view2->computeIntersections( x, y, path, results ) )
     {
         // find the first hit under the mouse:
         osgUtil::LineSegmentIntersector::Intersection first = *(results.begin());
diff --git a/src/osgEarth/TerrainEffect b/src/osgEarth/TerrainEffect
index a97348e..0a20136 100644
--- a/src/osgEarth/TerrainEffect
+++ b/src/osgEarth/TerrainEffect
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarth/TerrainEngineNode b/src/osgEarth/TerrainEngineNode
index 1208553..c4e6100 100644
--- a/src/osgEarth/TerrainEngineNode
+++ b/src/osgEarth/TerrainEngineNode
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -23,6 +23,7 @@
 #include <osgEarth/MapFrame>
 #include <osgEarth/Terrain>
 #include <osgEarth/TerrainEffect>
+#include <osgEarth/TerrainTileNode>
 #include <osgEarth/TextureCompositor>
 #include <osgEarth/ShaderUtils>
 #include <osg/CoordinateSystemNode>
@@ -36,12 +37,28 @@ namespace osgEarth
     class TerrainEffect;
 
     /**
+     * Interface for querying terrain engine feature requirements.
+     */
+    class TerrainEngineRequirements
+    {
+    public:
+        virtual bool elevationTexturesRequired() const =0;
+        virtual bool normalTexturesRequired() const =0;
+        virtual bool parentTexturesRequired() const =0;
+
+    public:
+        virtual ~TerrainEngineRequirements() { }
+    };
+
+    /**
      * TerrainEngineNode is the base class and interface for map engine implementations.
      *
      * A map engine lives under a MapNode and is responsible for generating the
      * actual geometry representing the Earth.
      */
-    class OSGEARTH_EXPORT TerrainEngineNode : public osg::CoordinateSystemNode
+    class OSGEARTH_EXPORT TerrainEngineNode : public osg::CoordinateSystemNode,
+                                              public TerrainTileNodeBroker,
+                                              public TerrainEngineRequirements
     {
     public:
         /** Gets the map that this engine is rendering. */
@@ -54,8 +71,8 @@ namespace osgEarth
         /** Gets the property set in use by this map engine. */
         virtual const TerrainOptions& getTerrainOptions() const =0;
 
-        /** Accesses the compositor that controls the rendering of image layers */
-        TextureCompositor* getTextureCompositor() const;
+        /** Accesses the object that keeps track of GPU resources in use */
+        TextureCompositor* getResources() const;
 
         /** Adds a terrain effect */
         void addEffect( TerrainEffect* effect );
@@ -78,6 +95,33 @@ namespace osgEarth
         void invalidateRegion(const GeoExtent& extent) {
             invalidateRegion(extent, 0u, INT_MAX);
         }
+
+        /** Whether the implementation should generate normal map rasters. */
+        void requireNormalTextures();
+        
+        /** Whether the implementation should generate elevation map rasters. */
+        void requireElevationTextures();
+
+        /** Whether the implementation should generate parent color textures. */
+        void requireParentTextures();
+
+        /** Access the stateset used to render the terrain. */
+        virtual osg::StateSet* getTerrainStateSet() { return getOrCreateStateSet(); }
+
+        /** Access the stateset used to render payload data. */
+        virtual osg::StateSet* getPayloadStateSet() { return getOrCreateStateSet(); }
+
+    public: // TerrainEngineRequirements
+
+        bool normalTexturesRequired() const { return _requireNormalTextures; }
+        bool elevationTexturesRequired() const { return _requireElevationTextures; }
+        bool parentTexturesRequired() const { return _requireParentTextures; }
+
+    public:
+        // adds a callback that will be fired while the terrain engine is building
+        // a tile node in a background thread.
+        void addTileNodeCallback(TerrainTileNodeCallback* cb);
+        void removeTileNodeCallback(TerrainTileNodeCallback* cb);
             
 
     public: // Runtime properties
@@ -90,6 +134,10 @@ namespace osgEarth
           * #deprecated */
         float getVerticalScale() const { return _verticalScale; }
 
+    public: // deprecated
+        
+        /** @derepcated Use getResources() instead. */
+        TextureCompositor* getTextureCompositor() const { return getResources(); }
 
     protected:
         TerrainEngineNode();
@@ -115,11 +163,26 @@ namespace osgEarth
         virtual void postInitialize( const Map* map, const TerrainOptions& options );
 
         // signals that a redraw is needed because something changed.
-        virtual void dirty();
+        virtual void requestRedraw();
+
+        // Request that the terrain tiles be rebuilt.
+        virtual void dirtyTerrain();
 
         // allow subclasses direct access for convenience.
         osg::ref_ptr<TextureCompositor> _texCompositor;
 
+        bool _requireElevationTextures;
+        bool _requireNormalTextures;
+        bool _requireParentTextures;
+
+        // called by addTileNodeCallback to notify any already-existing nodes
+        // of the new callback.
+        virtual void notifyExistingNodes(TerrainTileNodeCallback* cb) { }
+
+    public: // TerrainTileNodeBroker
+
+        void notifyOfTerrainTileNodeCreation(const TileKey& key, osg::Node* node);
+
     public: // utility
 
         /**
@@ -143,11 +206,7 @@ namespace osgEarth
         struct ImageLayerController : public ImageLayerCallback
         {
             ImageLayerController( const Map* map, TerrainEngineNode* engine );
-
-            void onVisibleChanged( TerrainLayer* layer );
-            void onOpacityChanged( ImageLayer* layer );
-            void onColorFiltersChanged( ImageLayer* layer );      
-            void onVisibleRangeChanged( ImageLayer* layer );
+            void onColorFiltersChanged( ImageLayer* layer ); 
 
         private:
             MapFrame           _mapf;
@@ -172,6 +231,10 @@ namespace osgEarth
         typedef std::vector<osg::ref_ptr<TerrainEffect> > TerrainEffectVector;
         TerrainEffectVector effects_;
 
+        typedef std::vector<osg::ref_ptr<TerrainTileNodeCallback> > TerrainTileNodeCallbackVector;
+        TerrainTileNodeCallbackVector _tileNodeCallbacks;
+        mutable Threading::Mutex _tileNodeCallbacksMutex;
+
     public:
 
         /** Access a typed effect. */
diff --git a/src/osgEarth/TerrainEngineNode.cpp b/src/osgEarth/TerrainEngineNode.cpp
index bf6b440..d359d3e 100644
--- a/src/osgEarth/TerrainEngineNode.cpp
+++ b/src/osgEarth/TerrainEngineNode.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -63,7 +63,7 @@ namespace osgEarth
 
 TerrainEngineNode::ImageLayerController::ImageLayerController(const Map*         map,
                                                               TerrainEngineNode* engine) :
-_mapf  ( map, Map::IMAGE_LAYERS, "TerrainEngineNode.ImageLayerController" ),
+_mapf  ( map, Map::IMAGE_LAYERS ),
 _engine( engine )
 {
     //nop
@@ -95,49 +95,28 @@ TerrainEngineNode::removeEffect(TerrainEffect* effect)
 
 
 TextureCompositor*
-TerrainEngineNode::getTextureCompositor() const
+TerrainEngineNode::getResources() const
 {
     return _texCompositor.get();
 }
 
-
-// this handler adjusts the uniform set when a terrain layer's "enabed" state changes
-void
-TerrainEngineNode::ImageLayerController::onVisibleChanged( TerrainLayer* layer )
-{
-    _engine->dirty();
-}
-
-
-// this handler adjusts the uniform set when a terrain layer's "opacity" value changes
-void
-TerrainEngineNode::ImageLayerController::onOpacityChanged( ImageLayer* layer )
-{
-    _engine->dirty();
-}
-
-void
-TerrainEngineNode::ImageLayerController::onVisibleRangeChanged( ImageLayer* layer )
-{
-    _engine->dirty();
-}
-
 void
 TerrainEngineNode::ImageLayerController::onColorFiltersChanged( ImageLayer* layer )
 {
     _engine->updateTextureCombining();
-    _engine->dirty();
 }
 
 
-
 //------------------------------------------------------------------------
 
 
 TerrainEngineNode::TerrainEngineNode() :
-_verticalScale         ( 1.0f ),
-_initStage             ( INIT_NONE ),
-_dirtyCount            ( 0 )
+_verticalScale           ( 1.0f ),
+_initStage               ( INIT_NONE ),
+_dirtyCount              ( 0 ),
+_requireElevationTextures( false ),
+_requireNormalTextures   ( false ),
+_requireParentTextures   ( false )
 {
     // register for event traversals so we can properly reset the dirtyCount
     ADJUST_EVENT_TRAV_COUNT( this, 1 );
@@ -149,7 +128,7 @@ TerrainEngineNode::~TerrainEngineNode()
     //Remove any callbacks added to the image layers
     if (_map.valid())
     {
-        MapFrame mapf( _map.get(), Map::IMAGE_LAYERS, "TerrainEngineNode::~TerrainEngineNode" );
+        MapFrame mapf( _map.get(), Map::IMAGE_LAYERS );
         for( ImageLayerVector::const_iterator i = mapf.imageLayers().begin(); i != mapf.imageLayers().end(); ++i )
         {
             i->get()->removeCallback( _imageLayerController.get() );
@@ -159,7 +138,29 @@ TerrainEngineNode::~TerrainEngineNode()
 
 
 void
-TerrainEngineNode::dirty()
+TerrainEngineNode::requireNormalTextures()
+{
+    _requireNormalTextures = true;
+    dirtyTerrain();
+}
+
+void
+TerrainEngineNode::requireElevationTextures()
+{
+    _requireElevationTextures = true;
+    dirtyTerrain();
+}
+
+void
+TerrainEngineNode::requireParentTextures()
+{
+    _requireParentTextures = true;
+    dirtyTerrain();
+}
+
+
+void
+TerrainEngineNode::requestRedraw()
 {
     if ( 0 == _dirtyCount++ )
     {
@@ -169,6 +170,12 @@ TerrainEngineNode::dirty()
     }
 }
 
+void
+TerrainEngineNode::dirtyTerrain()
+{
+    requestRedraw();
+}
+
 
 void
 TerrainEngineNode::preInitialize( const Map* map, const TerrainOptions& options )
@@ -218,7 +225,7 @@ TerrainEngineNode::postInitialize( const Map* map, const TerrainOptions& options
         _imageLayerController = new ImageLayerController( _map.get(), this );
 
         // register the layer Controller it with all pre-existing image layers:
-        MapFrame mapf( _map.get(), Map::IMAGE_LAYERS, "TerrainEngineNode::initialize" );
+        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() );
@@ -279,7 +286,7 @@ TerrainEngineNode::onMapModelChanged( const MapModelChange& change )
     }
 
     // notify that a redraw is required.
-    dirty();
+    requestRedraw();
 }
 
 namespace
@@ -325,6 +332,37 @@ TerrainEngineNode::traverse( osg::NodeVisitor& nv )
     osg::CoordinateSystemNode::traverse( nv );
 }
 
+void
+TerrainEngineNode::addTileNodeCallback(TerrainTileNodeCallback* cb)
+{
+    Threading::ScopedMutexLock lock(_tileNodeCallbacksMutex);
+    _tileNodeCallbacks.push_back( cb );
+    notifyExistingNodes( cb );
+}
+
+void
+TerrainEngineNode::removeTileNodeCallback(TerrainTileNodeCallback* cb)
+{
+    Threading::ScopedMutexLock lock(_tileNodeCallbacksMutex);
+    for(TerrainTileNodeCallbackVector::iterator i = _tileNodeCallbacks.begin(); i != _tileNodeCallbacks.end(); ++i)
+    {
+        if ( i->get() == cb )
+        {
+            _tileNodeCallbacks.erase( i );
+            break;
+        }
+    }
+}
+
+void
+TerrainEngineNode::notifyOfTerrainTileNodeCreation(const TileKey& key, osg::Node* node)
+{
+    Threading::ScopedMutexLock lock(_tileNodeCallbacksMutex);
+    for(unsigned i=0; i<_tileNodeCallbacks.size(); ++i)
+        _tileNodeCallbacks[i]->operator()(key, node);
+}
+
+
 //------------------------------------------------------------------------
 
 #undef LC
diff --git a/src/osgEarth/TerrainLayer b/src/osgEarth/TerrainLayer
index d9645f2..15724ad 100644
--- a/src/osgEarth/TerrainLayer
+++ b/src/osgEarth/TerrainLayer
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -305,20 +305,13 @@ namespace osgEarth
         /**
          * Convenience function to check for cache_only mode
          */
-        bool isCacheOnly() const {
-            return 
-                _runtimeOptions->cachePolicy().isSet() &&
-                _runtimeOptions->cachePolicy()->usage() == CachePolicy::USAGE_CACHE_ONLY;
-        }
+        bool isCacheOnly() const;
         
         /**
          * Convenience check for no-cache mode.
          */
-        bool isNoCache() const {
-            return 
-                _runtimeOptions->cachePolicy().isSet() &&
-                _runtimeOptions->cachePolicy()->usage() == CachePolicy::USAGE_NO_CACHE;
-        }
+        bool isNoCache() const;
+
 
     public: // Layer interface
 
@@ -403,7 +396,8 @@ namespace osgEarth
 
     protected:
 
-        virtual void initTileSource();
+        /** Creates the driver the supplies the actual data. Internal function. */
+        virtual TileSource* createTileSource();
 
         void applyProfileOverrides();
 
@@ -411,10 +405,9 @@ namespace osgEarth
 
     protected:
 
-        osg::ref_ptr<TileSource>       _tileSource;
         osg::ref_ptr<const Profile>    _targetProfileHint;
-        bool                           _tileSourceInitAttempted;
-        bool                           _tileSourceInitFailed;
+        mutable bool                   _tileSourceInitAttempted;
+        //bool                           _tileSourceInitFailed;
         unsigned                       _tileSize;  
         osg::ref_ptr<osgDB::Options>   _dbOptions;
         osg::ref_ptr<MemCache>         _memCache;
@@ -433,9 +426,15 @@ namespace osgEarth
         std::string                    _referenceURI;
         TerrainLayerOptions            _initOptions;
         TerrainLayerOptions*           _runtimeOptions;
+
         mutable Threading::Mutex       _initTileSourceMutex;
+        osg::ref_ptr<TileSource>       _tileSource;
 
         osg::ref_ptr<Cache>            _cache;
+        
+        // cache policy that may be automatically set by the layer and will 
+        // override the runtime options policy if set.
+        optional<CachePolicy>          _effectiveCachePolicy;
 
         // maps profile signature to cache bin pointer.
         struct CacheBinInfo
@@ -465,7 +464,7 @@ namespace osgEarth
         void setCache( Cache* cache );
 
         // read the tile source's cache policy hint and apply as necessary
-        void refreshTileSourceCachePolicyHint();
+        void refreshTileSourceCachePolicyHint(TileSource*);
     };
 
     typedef std::vector<osg::ref_ptr<TerrainLayer> > TerrainLayerVector;
diff --git a/src/osgEarth/TerrainLayer.cpp b/src/osgEarth/TerrainLayer.cpp
index 8555a04..8ac8c7a 100644
--- a/src/osgEarth/TerrainLayer.cpp
+++ b/src/osgEarth/TerrainLayer.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -186,7 +186,6 @@ void
 TerrainLayer::init()
 {
     _tileSourceInitAttempted = false;
-    _tileSourceInitFailed    = false;
     _tileSize                = 256;
     _dbOptions               = Registry::instance()->cloneOrCreateOptions();
     
@@ -195,10 +194,20 @@ TerrainLayer::init()
 
     // 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 memCacheSize = _initOptions.driver()->L2CacheSize().get();
-    if ( memCacheSize > 0 )
+    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 )
     {
-        _memCache = new MemCache(memCacheSize);
+        l2CacheSize = as<int>( std::string(l2env), 0 );
+        OE_INFO << LC << "L2 cache size set from environment = " << l2CacheSize << "\n";
+    }
+
+    // Initialize the l2 cache if it's size is > 0
+    if ( l2CacheSize > 0 )
+    {
+        _memCache = new MemCache( l2CacheSize );
     }
 }
 
@@ -230,13 +239,12 @@ TerrainLayer::setCache( Cache* cache )
                 Config driverConf = _runtimeOptions->driver()->getConfig();
                 Config hashConf   = driverConf - layerConf;
 
-                OE_DEBUG << LC << "Hash JSON is: " << hashConf.toJSON(false) << std::endl;
-
                 // 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" );
                 
                 // need this, b/c data is vdatum-transformed before caching.
                 if ( layerConf.hasValue("vdatum") )
@@ -261,12 +269,30 @@ TerrainLayer::setCachePolicy( const CachePolicy& cp )
 {
     _runtimeOptions->cachePolicy() = cp;
     _runtimeOptions->cachePolicy()->apply( _dbOptions.get() );
+
+    // if an effective policy was previously set, clear it out
+    _effectiveCachePolicy.unset();
 }
 
 const CachePolicy&
 TerrainLayer::getCachePolicy() const
 {
-    return _runtimeOptions->cachePolicy().value();
+    // An effective policy, if set, overrides the runtime policy.
+    return
+        _effectiveCachePolicy.isSet() ? _effectiveCachePolicy.get() :
+        _runtimeOptions->cachePolicy().get();
+}
+
+bool
+TerrainLayer::isCacheOnly() const
+{
+    return getCachePolicy().usage() == CachePolicy::USAGE_CACHE_ONLY;
+}
+
+bool
+TerrainLayer::isNoCache() const
+{
+    return getCachePolicy().usage() == CachePolicy::USAGE_NO_CACHE;
 }
 
 void
@@ -286,15 +312,15 @@ TerrainLayer::setTargetProfileHint( const Profile* profile )
     // If the tilesource was already initialized, re-read the 
     // cache policy hint since it may change due to the target
     // profile change.
-    refreshTileSourceCachePolicyHint();
+    refreshTileSourceCachePolicyHint( getTileSource() );
 }
 
 void
-TerrainLayer::refreshTileSourceCachePolicyHint()
+TerrainLayer::refreshTileSourceCachePolicyHint(TileSource* ts)
 {
-    if ( _tileSource.valid() && !_initOptions.cachePolicy().isSet() )
+    if ( ts && !_initOptions.cachePolicy().isSet() )
     {
-        CachePolicy hint = _tileSource->getCachePolicyHint( _targetProfileHint.get() );
+        CachePolicy hint = ts->getCachePolicyHint( _targetProfileHint.get() );
 
         if ( hint.usage().isSetTo(CachePolicy::USAGE_NO_CACHE) )
         {
@@ -307,38 +333,52 @@ TerrainLayer::refreshTileSourceCachePolicyHint()
 TileSource* 
 TerrainLayer::getTileSource() const
 {
-    if ( _tileSourceInitFailed )
-        return 0L;
-
-    if ((_tileSource.valid() && !_tileSourceInitAttempted) ||
-        (!_tileSource.valid() && !isCacheOnly()))
+    if ( !_tileSourceInitAttempted )
     {
-        Threading::ScopedMutexLock lock(_initTileSourceMutex);
-        
-        // double-check pattern
-        if ((_tileSource.valid() && !_tileSourceInitAttempted) ||
-            (!_tileSource.valid() && !isCacheOnly()))
+        // Lock and double check:
+        Threading::ScopedMutexLock lock( _initTileSourceMutex );
+        if ( !_tileSourceInitAttempted )
         {
-            // Initialize the tile source once.
-            const_cast<TerrainLayer*>(this)->initTileSource();
-
-            // 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!
-            const_cast<TerrainLayer*>(this)->refreshTileSourceCachePolicyHint();
-
-            // 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.
-            if ( _tileSource.valid() )
+            // Continue with thread-safe initialization.
+            TerrainLayer* this_nc = const_cast<TerrainLayer*>(this);
+
+            osg::ref_ptr<TileSource> ts;
+            if ( !isCacheOnly() )
             {
-                CachePolicy& cp = _runtimeOptions->cachePolicy().mutable_value();
-                if ( !cp.minTime().isSet() && !cp.maxAge().isSet() )
+                // Initialize the tile source once and only once.
+                ts = this_nc->createTileSource();
+            }
+
+            if ( ts.valid() )
+            {
+                // 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!
+                this_nc->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.
+                if ( ts )
                 {
-                    cp.minTime() = _tileSource->getLastModifiedTime();
+                    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.
+                        this_nc->_effectiveCachePolicy = cp;
+                        this_nc->_effectiveCachePolicy->minTime() = ts->getLastModifiedTime();
+                        OE_INFO << LC << "cache min valid time reported by driver = " << DateTime(*cp.minTime()).asRFC1123() << "\n";
+                    }
+                    OE_INFO << LC << "cache policy = " << getCachePolicy().usageString() << std::endl;
                 }
-                OE_INFO << LC << "cache policy = " << getCachePolicy().usageString() << std::endl;
+
+                if ( !_tileSource.valid() )
+                    this_nc->_tileSource = ts.release();
             }
+
+            // finally, set this whether we succeeded or failed
+            _tileSourceInitAttempted = true;
         }
     }
 
@@ -444,9 +484,13 @@ TerrainLayer::getCacheBin(const Profile* profile, const std::string& binId)
                 {
                     //todo: check the profile too
                     if ( *meta._sourceDriver != tileSource->getOptions().getDriver() )
-                    {
-                        OE_WARN << LC << "Cache has an incompatible driver or profile... disabling"
-                            << std::endl;
+                    {                     
+                        OE_WARN << LC 
+                            << "Layer \"" << getName() << "\" is requesting a \""
+                            << tileSource->getOptions().getDriver() << " cache, but a \""
+                            << *meta._sourceDriver << "\" cache exists at the specified location. "
+                            << "The cache will ignored for this layer.\n";
+
                         setCachePolicy( CachePolicy::NO_CACHE );
                         return 0L;
                     }
@@ -539,61 +583,69 @@ TerrainLayer::getCacheBinMetadata( const Profile* profile, CacheBinMetadata& out
     return false;
 }
 
-void
-TerrainLayer::initTileSource()
+TileSource*
+TerrainLayer::createTileSource()
 {
-    _tileSourceInitAttempted = true;
+    osg::ref_ptr<TileSource> ts;
 
-    OE_DEBUG << LC << "Initializing tile source ..." << std::endl;
+    if ( _tileSource.valid() )
+    {
+        // this will happen if the layer was created with an explicit TileSource instance.
+        ts = _tileSource.get();
+    }
 
-    // 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 ( !_tileSource.valid() )
+    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() )
         {
-            _tileSource = TileSourceFactory::create(*_runtimeOptions->driver());
+            OE_INFO << LC << "Creating TileSource, driver = \"" << _runtimeOptions->driver()->getDriver() << "\"\n";
+            ts = TileSourceFactory::create( *_runtimeOptions->driver() );
+            if ( !ts.valid() )
+            {
+                OE_WARN << LC << "Failed to create TileSource for driver \"" << _runtimeOptions->driver()->getDriver() << "\"\n";
+            }
         }
     }
 
     // Initialize the profile with the context information:
-    if ( _tileSource.valid() )
+    if ( ts.valid() )
     {
         // set up the URI options.
         if ( !_dbOptions.valid() )
         {
             _dbOptions = Registry::instance()->cloneOrCreateOptions();
-            if ( _cache.valid() ) _cache->apply( _dbOptions.get() );
+            if ( _cache.valid() )
+                _cache->apply( _dbOptions.get() );
             _initOptions.cachePolicy()->apply( _dbOptions.get() );
             URIContext( _runtimeOptions->referrer() ).apply( _dbOptions.get() );
         }
 
-
         // report on a manual override profile:
-        if ( _tileSource->getProfile() )
+        if ( ts->getProfile() )
         {
-            OE_INFO << LC << "set profile to: " 
-                << _tileSource->getProfile()->toString() << std::endl;
+            OE_INFO << LC << "Override profile: "  << ts->getProfile()->toString() << std::endl;
         }
 
         // Open the tile source (if it hasn't already been started)
-        TileSource::Status status = _tileSource->getStatus();
+        TileSource::Status status = ts->getStatus();
         if ( status != TileSource::STATUS_OK )
         {
-            status = _tileSource->open(TileSource::MODE_READ, _dbOptions.get());
+            status = ts->open(TileSource::MODE_READ, _dbOptions.get());
         }
 
         if ( status == TileSource::STATUS_OK )
         {
-            _tileSize = _tileSource->getPixelsPerTile();
+            _tileSize = ts->getPixelsPerTile();
 
 #if 0 //debugging 
             // dump out data extents:
-            if ( _tileSource->getDataExtents().size() > 0 )
+            if ( ts->getDataExtents().size() > 0 )
             {
                 OE_INFO << LC << "Data extents reported:" << std::endl;
-                for(DataExtentList::const_iterator i = _tileSource->getDataExtents().begin();
-                    i != _tileSource->getDataExtents().end(); ++i)
+                for(DataExtentList::const_iterator i = ts->getDataExtents().begin();
+                    i != ts->getDataExtents().end(); ++i)
                 {
                     const DataExtent& de = *i;
                     OE_INFO << "    "
@@ -608,18 +660,19 @@ TerrainLayer::initTileSource()
         else
         {
             OE_WARN << LC << "Could not initialize driver" << std::endl;
-            _tileSource = NULL;
-            _tileSourceInitFailed = true;
+            ts = NULL;
+            //_tileSourceInitFailed = true;
             _runtimeOptions->enabled() = true;
         }
     }
 
     // Set the profile from the TileSource if possible:
-    if ( _tileSource.valid() )
+    if ( ts.valid() )
     {
-        if ( !_profile.valid() && !_tileSourceInitFailed )
+        if ( !_profile.valid() )
         {
-            _profile = _tileSource->getProfile();
+            OE_DEBUG << LC << "Get Profile from tile source" << std::endl;
+            _profile = ts->getProfile();
         }
 
 
@@ -639,6 +692,8 @@ TerrainLayer::initTileSource()
         OE_NOTICE << LC << "Could not initialize TileSource " << _name << ", but a cache exists. Setting layer to cache-only mode." << std::endl;
         setCachePolicy( CachePolicy::CACHE_ONLY );
     }
+
+    return ts.release();
 }
 
 void
diff --git a/src/osgEarth/TerrainOptions b/src/osgEarth/TerrainOptions
index 679b512..b132248 100644
--- a/src/osgEarth/TerrainOptions
+++ b/src/osgEarth/TerrainOptions
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
@@ -61,6 +64,16 @@ namespace osgEarth
         const optional<float>& heightFieldSampleRatio() const { return _heightFieldSampleRatio; }
 
         /**
+         * Size of each dimension of each terrain tile, in verts. Ideally this 
+         * will be a power of 2 plus 1, i.e.: a number X with that X = (2^Y)+1
+         * where Y is an integer >= 1.
+         *
+         * Default = 17. 
+         */
+        optional<int>& tileSize() { return _tileSize; }
+        const optional<int>& tileSize() const { return _tileSize; }
+
+        /**
          * The minimum tile LOD range as a factor of the tile's radius.
          * Default = 6.0.
          */
@@ -68,7 +81,8 @@ namespace osgEarth
         const optional<float>& minTileRangeFactor() const { return _minTileRangeFactor; }
 
         /**
-         * The image-layer fading attenuation distance
+         * Distance (m) over which to ramp min_range and max_range blending
+         * Default = 0.
          */
         optional<float>& attenuationDistance() { return _attenuationDistance; }
         const optional<float>& attentuationDistance() const { return _attenuationDistance; }
@@ -153,19 +167,19 @@ namespace osgEarth
         const optional<bool>& enableMercatorFastPath() const { return _mercatorFastPath; }
 
         /**
-         * Traversal mask to use for primary geometry -- geometry that comprises the visible
-         * geometry and should participate in intersection, shadowing, etc.
+         * The minimum level of detail at which to generate elevation-based normal maps,
+         * assuming normal maps have been activated. This mitigates the overhead of 
+         * calculating normal maps for very high altitude scenes where they are no
+         * of much use. Default is 0 (zero).
          */
-        optional<unsigned>& primaryTraversalMask() { return _primaryTraversalMask; }
-        const optional<unsigned>& primaryTraversalMask() const { return _primaryTraversalMask; }
+        optional<unsigned>& minNormalMapLOD() { return _minNormalMapLOD; }
+        const optional<unsigned>& minNormalMapLOD() const { return _minNormalMapLOD; }
 
         /**
-         * Traversal mask to use for secondary geometry -- geometry that exists for
-         * secondary purpose (e.g. terrain skirts) that should not participate in 
-         * intersection, shadowing, etc.
+         * debugging mode
          */
-        optional<unsigned>& secondaryTraversalMask() { return _secondaryTraversalMask; }
-        const optional<unsigned>& secondaryTraversalMask() const { return _secondaryTraversalMask; }
+        optional<bool>& debug() { return _debug; }
+        const optional<bool>& debug() const { return _debug; }
    
     public:
         virtual Config getConfig() const;
@@ -182,6 +196,7 @@ namespace osgEarth
         optional<float> _verticalScale;
         optional<float> _verticalOffset;
         optional<float> _heightFieldSampleRatio;
+        optional<int>   _tileSize;
         optional<float> _minTileRangeFactor;        
         optional<bool> _combineLayers;
         optional<unsigned> _minLOD;
@@ -199,6 +214,8 @@ namespace osgEarth
         optional<ElevationInterpolation> _elevationInterpolation;
         optional<unsigned> _primaryTraversalMask;
         optional<unsigned> _secondaryTraversalMask;
+        optional<unsigned> _minNormalMapLOD;
+        optional<bool> _debug;
     };
 }
 
diff --git a/src/osgEarth/TerrainOptions.cpp b/src/osgEarth/TerrainOptions.cpp
index ec06378..9f5bbc7 100644
--- a/src/osgEarth/TerrainOptions.cpp
+++ b/src/osgEarth/TerrainOptions.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -27,6 +27,7 @@ using namespace osgEarth;
 
 TerrainOptions::TerrainOptions( const ConfigOptions& options ) :
 DriverConfigOptions( options ),
+_tileSize( 17 ),
 _verticalScale( 1.0f ),
 _verticalOffset( 0.0f ),
 _heightFieldSampleRatio( 1.0f ),
@@ -36,7 +37,7 @@ _maxLOD( 23 ),
 _minLOD( 0 ),
 _firstLOD( 0 ),
 _enableLighting( false ),
-_attenuationDistance( 1000000 ),
+_attenuationDistance( 0.0f ),
 _lodTransitionTimeSeconds( 0.5f ),
 _enableMipmapping( true ),
 _clusterCulling( true ),
@@ -44,8 +45,8 @@ _enableBlending( true ),
 _mercatorFastPath( true ),
 _minFilter( osg::Texture::LINEAR_MIPMAP_LINEAR ),
 _magFilter( osg::Texture::LINEAR),
-_primaryTraversalMask  ( 0xFFFFFFFF ),
-_secondaryTraversalMask( 0x80000000 )
+_minNormalMapLOD( 0u ),
+_debug( false )
 {
     fromConfig( _conf );
 }
@@ -61,6 +62,7 @@ TerrainOptions::getConfig() const
     else
         conf.updateIfSet( "sample_ratio", _heightFieldSampleRatio );
 
+    conf.updateIfSet( "tile_size", _tileSize );
     conf.updateIfSet( "vertical_scale", _verticalScale );
     conf.updateIfSet( "vertical_offset", _verticalOffset );
     conf.updateIfSet( "min_tile_range_factor", _minTileRangeFactor );    
@@ -74,8 +76,8 @@ TerrainOptions::getConfig() const
     conf.updateIfSet( "cluster_culling", _clusterCulling );
     conf.updateIfSet( "blending", _enableBlending );
     conf.updateIfSet( "mercator_fast_path", _mercatorFastPath );
-    conf.updateIfSet( "primary_traversal_mask", _primaryTraversalMask );
-    conf.updateIfSet( "secondary_traversal_mask", _secondaryTraversalMask );
+    conf.updateIfSet( "min_normal_map_lod", _minNormalMapLOD );
+    conf.updateIfSet( "debug", _debug );
 
     //Save the filter settings
 	conf.updateIfSet("mag_filter","LINEAR",                _magFilter,osg::Texture::LINEAR);
@@ -102,6 +104,7 @@ TerrainOptions::fromConfig( const Config& conf )
     else
         conf.getIfSet( "sample_ratio", _heightFieldSampleRatio );
 
+    conf.getIfSet( "tile_size", _tileSize );
     conf.getIfSet( "vertical_scale", _verticalScale );
     conf.getIfSet( "vertical_offset", _verticalOffset );
     conf.getIfSet( "min_tile_range_factor", _minTileRangeFactor );    
@@ -115,8 +118,8 @@ TerrainOptions::fromConfig( const Config& conf )
     conf.getIfSet( "cluster_culling", _clusterCulling );
     conf.getIfSet( "blending", _enableBlending );
     conf.getIfSet( "mercator_fast_path", _mercatorFastPath );
-    conf.getIfSet( "primary_traversal_mask", _primaryTraversalMask );
-    conf.getIfSet( "secondary_traversal_mask", _secondaryTraversalMask );
+    conf.getIfSet( "min_normal_map_lod", _minNormalMapLOD );
+    conf.getIfSet( "debug", _debug );
 
     //Load the filter settings
 	conf.getIfSet("mag_filter","LINEAR",                _magFilter,osg::Texture::LINEAR);
diff --git a/src/osgEarth/TerrainTileNode b/src/osgEarth/TerrainTileNode
new file mode 100644
index 0000000..a6c0d8b
--- /dev/null
+++ b/src/osgEarth/TerrainTileNode
@@ -0,0 +1,88 @@
+/* -*-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.
+ *
+ * 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_TERRAIN_TILE_NODE_H
+#define OSGEARTH_TERRAIN_TILE_NODE_H 1
+
+#include <osgEarth/Common>
+#include <osgEarth/TileKey>
+#include <osg/MatrixTransform>
+#include <osg/Texture>
+
+namespace osgEarth
+{
+    /**
+     * Base class for a terrain engine's representation of a tile.
+     * This is largely for internal use and subject to change, so
+     * be careful relying on the structure of this object.
+     */
+    class /*header-only*/ TerrainTileNode // : public osg::MatrixTransform
+    {
+    public:
+        virtual const TileKey& getKey() const =0;
+
+        virtual osg::Texture* getElevationTexture() const =0;
+
+        virtual osg::RefMatrixf* getElevationTextureMatrix() const =0;
+
+        virtual osg::Texture* getNormalTexture() const =0;
+
+        virtual osg::RefMatrixf* getNormalTextureMatrix() const =0;
+
+        virtual osg::Group* getPayloadGroup() const =0;
+
+        virtual osg::Group* getOrCreatePayloadGroup() =0;
+
+    protected:
+        virtual ~TerrainTileNode() { }
+    };
+
+    /**
+     * Interface for notification of the creation of TerrainTileNodes by
+     * the terrain engine.
+     */
+    class /*header-only*/ TerrainTileNodeBroker
+    {
+    public:
+        /**
+         * Terrain engine calls this when it finishes creating a single tile node
+         * for a given tile key. (Typically that will happen in a pager thread.)
+         */
+        virtual void notifyOfTerrainTileNodeCreation(const TileKey& key, osg::Node* node) =0;
+
+    public:
+        virtual ~TerrainTileNodeBroker() { }
+    };
+
+    /**
+     * Callback you can install on a TerrainEngineNode to get a first look at
+     * a TerrainTileNode when it's created.
+     */
+    class /*header-only*/ TerrainTileNodeCallback : public osg::Referenced
+    {
+    public:
+        // Careful: runs in a terrain engine background thread.
+        virtual void operator()(const TileKey& key, osg::Node* tileNode) =0;
+
+    protected:
+        virtual ~TerrainTileNodeCallback() { }
+    };
+};
+
+#endif // OSGEARTH_TILE_NODE_H
diff --git a/src/osgEarth/Tessellator b/src/osgEarth/Tessellator
index 28f284a..ae44390 100644
--- a/src/osgEarth/Tessellator
+++ b/src/osgEarth/Tessellator
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/osgEarth/Tessellator.cpp b/src/osgEarth/Tessellator.cpp
index 8f0a2c8..b22d2ab 100644
--- a/src/osgEarth/Tessellator.cpp
+++ b/src/osgEarth/Tessellator.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/osgEarth/TextureCompositor b/src/osgEarth/TextureCompositor
index f03b99f..8cfbec8 100644
--- a/src/osgEarth/TextureCompositor
+++ b/src/osgEarth/TextureCompositor
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -39,16 +39,29 @@ namespace osgEarth
         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().
+         * 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);
+        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;
diff --git a/src/osgEarth/TextureCompositor.cpp b/src/osgEarth/TextureCompositor.cpp
index 872e65a..90f3549 100755
--- a/src/osgEarth/TextureCompositor.cpp
+++ b/src/osgEarth/TextureCompositor.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -32,7 +32,8 @@ TextureCompositor::TextureCompositor()
 }
 
 bool
-TextureCompositor::reserveTextureImageUnit(int& out_unit)
+TextureCompositor::reserveTextureImageUnit(int&        out_unit,
+                                           const char* requestor)
 {
     out_unit = -1;
     unsigned maxUnits = osgEarth::Registry::instance()->getCapabilities().getMaxGPUTextureUnits();
@@ -44,6 +45,10 @@ TextureCompositor::reserveTextureImageUnit(int& out_unit)
         {
             _reservedUnits.insert( i );
             out_unit = i;
+            if ( requestor )
+            {
+                OE_INFO << LC << "Texture unit " << i << " reserved for " << requestor << "\n";
+            }
             return true;
         }
     }
@@ -56,3 +61,19 @@ 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 9f63fd8..34230d4 100644
--- a/src/osgEarth/ThreadingUtils
+++ b/src/osgEarth/ThreadingUtils
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -303,233 +303,6 @@ namespace osgEarth { namespace Threading
 
 #endif
 
-    /** Template for per-thread data storage */
-    template<typename T>
-    struct PerThread
-    {
-        T& get() {
-            ScopedMutexLock lock(_mutex);
-            return _data[getCurrentThreadId()];
-        }
-        //const T& get() const {
-        //    ScopedMutexLock lock(_mutex);
-        //    return _data[getCurrentThreadId()];
-        //}
-    private:
-        std::map<unsigned,T> _data;
-        Mutex                _mutex;
-    };
-
-    /** Template for thread safe per-object data storage */
-    template<typename KEY, typename DATA>
-    struct PerObjectMap
-    {
-        DATA& get(KEY k)
-        {
-            {
-                osgEarth::Threading::ScopedReadLock readLock(_mutex);
-                typename std::map<KEY,DATA>::iterator i = _data.find(k);
-                if ( i != _data.end() )
-                    return i->second;
-            }
-            {
-                osgEarth::Threading::ScopedWriteLock lock(_mutex);
-                typename std::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);
-            _data.erase( k );
-        }
-
-    private:
-        std::map<KEY,DATA>                  _data;
-        osgEarth::Threading::ReadWriteMutex _mutex;
-    };
-
-    /** Template for thread safe per-object data storage */
-    template<typename KEY, typename DATA>
-    struct PerObjectRefMap
-    {
-        DATA* get(KEY k)
-        {
-            osgEarth::Threading::ScopedReadLock lock(_mutex);
-            typename std::map<KEY,osg::ref_ptr<DATA > >::const_iterator i = _data.find(k);
-            if ( i != _data.end() )
-                return i->second.get();
-
-            return 0L;
-        }
-
-        DATA* getOrCreate(KEY k, DATA* newDataIfNeeded)
-        {
-            osg::ref_ptr<DATA> _refReleaser = newDataIfNeeded;
-            {
-                osgEarth::Threading::ScopedReadLock lock(_mutex);
-                typename std::map<KEY,osg::ref_ptr<DATA> >::const_iterator i = _data.find(k);
-                if ( i != _data.end() )
-                    return i->second.get();
-            }
-
-            {
-                osgEarth::Threading::ScopedWriteLock lock(_mutex);
-                typename std::map<KEY,osg::ref_ptr<DATA> >::iterator i = _data.find(k);
-                if ( i != _data.end() )
-                    return i->second.get();
-
-                _data[k] = newDataIfNeeded;
-                return newDataIfNeeded;
-            }
-        }
-
-        void remove(KEY k)
-        {
-            osgEarth::Threading::ScopedWriteLock exclusive(_mutex);
-            _data.erase( k );
-        }
-
-        void remove(DATA* data)
-        {
-            osgEarth::Threading::ScopedWriteLock exclusive(_mutex);
-            for( typename std::map<KEY,osg::ref_ptr<DATA> >::iterator i = _data.begin(); i != _data.end(); ++i )
-            {
-                if ( i->second.get() == data )
-                {
-                    _data.erase( i );
-                    break;
-                }
-            }
-        }
-
-    private:
-        std::map<KEY,osg::ref_ptr<DATA> >    _data;
-        osgEarth::Threading::ReadWriteMutex  _mutex;
-    };
-
-    /** Template for thread safe per-object data storage */
-    template<typename KEY, typename DATA>
-    struct PerObjectObsMap
-    {
-        DATA* get(KEY k)
-        {
-            osgEarth::Threading::ScopedReadLock lock(_mutex);
-            typename std::map<KEY, osg::observer_ptr<DATA> >::const_iterator i = _data.find(k);
-            if ( i != _data.end() )
-                return i->second.get();
-
-            return 0L;
-        }
-
-        DATA* getOrCreate(KEY k, DATA* newDataIfNeeded)
-        {
-            osg::ref_ptr<DATA> _refReleaser = newDataIfNeeded;
-            {
-                osgEarth::Threading::ScopedReadLock lock(_mutex);
-                typename std::map<KEY,osg::observer_ptr<DATA> >::const_iterator i = _data.find(k);
-                if ( i != _data.end() )
-                    return i->second.get();
-            }
-
-            {
-                osgEarth::Threading::ScopedWriteLock lock(_mutex);
-                typename std::map<KEY,osg::observer_ptr<DATA> >::iterator i = _data.find(k);
-                if ( i != _data.end() )
-                    return i->second.get();
-
-                _data[k] = newDataIfNeeded;
-                return newDataIfNeeded;
-            }
-        }
-
-        void remove(KEY k)
-        {
-            osgEarth::Threading::ScopedWriteLock exclusive(_mutex);
-            _data.erase( k );
-        }
-
-        void remove(DATA* data)
-        {
-            osgEarth::Threading::ScopedWriteLock exclusive(_mutex);
-            for( typename std::map<KEY,osg::observer_ptr<DATA> >::iterator i = _data.begin(); i != _data.end(); ++i )
-            {
-                if ( i->second.get() == data )
-                {
-                    _data.erase( i );
-                    break;
-                }
-            }
-        }
-
-    private:
-        std::map<KEY,osg::observer_ptr<DATA> >    _data;
-        osgEarth::Threading::ReadWriteMutex       _mutex;
-    };
-
-
-    /** Template for thread safe observer set */
-    template<typename T>
-    struct ThreadSafeObserverSet
-    {
-        typedef void (*Functor)(T*);
-        typedef void (*ConstFunctor)(const T*);
-
-        void iterate( const Functor& f )
-        {
-            osgEarth::Threading::ScopedWriteLock lock(_mutex);
-            for( typename std::set<T>::iterator i = _data.begin(); i != _data.end(); )
-            {
-                if ( i->valid() )
-                {
-                    f( i->get() );
-                    ++i;
-                }
-                else
-                {
-                    i = _data.erase( i );
-                }
-            }
-        }
-
-        void iterate( const ConstFunctor& f )
-        {
-            osgEarth::Threading::ScopedReadLock lock(_mutex);
-            for( typename std::set<T>::iterator i = _data.begin(); i != _data.end(); )
-            {
-                if ( i->valid() )
-                {
-                    f( i->get() );
-                    ++i;
-                }
-                else
-                {
-                    i = _data.erase( i );
-                }
-            }
-        }
-
-        void insert(T* obj)
-        {
-            osgEarth::Threading::ScopedWriteLock lock(_mutex);
-            _data.insert( obj );
-        }
-
-        void remove(T* obj)
-        {
-            osgEarth::Threading::ScopedWriteLock lock(_mutex);
-            _data.erase( obj );
-        }
-
-    private:
-        std::set<osg::observer_ptr<T> >      _data;
-        osgEarth::Threading::ReadWriteMutex  _mutex;
-    };
-
 } } // namepsace osgEarth::Threading
 
 
diff --git a/src/osgEarth/ThreadingUtils.cpp b/src/osgEarth/ThreadingUtils.cpp
index a291663..8c798c3 100644
--- a/src/osgEarth/ThreadingUtils.cpp
+++ b/src/osgEarth/ThreadingUtils.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -38,6 +38,8 @@ unsigned osgEarth::Threading::getCurrentThreadId()
   return (unsigned)::GetCurrentThreadId();
 #elif __APPLE__
   return ::syscall(SYS_thread_selfid);
+#elif __ANDROID__
+  return gettid();
 #else
   return (unsigned)::syscall(SYS_gettid);
 #endif
diff --git a/src/osgEarth/TileHandler b/src/osgEarth/TileHandler
index 7fe49b4..96293b9 100644
--- a/src/osgEarth/TileHandler
+++ b/src/osgEarth/TileHandler
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/osgEarth/TileHandler.cpp b/src/osgEarth/TileHandler.cpp
index 918a2dc..edc52fa 100644
--- a/src/osgEarth/TileHandler.cpp
+++ b/src/osgEarth/TileHandler.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/osgEarth/TileKey b/src/osgEarth/TileKey
index 4221d59..e09500e 100644
--- a/src/osgEarth/TileKey
+++ b/src/osgEarth/TileKey
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -69,7 +69,7 @@ namespace osgEarth
             return
                 valid() && rhs.valid() && 
                 _lod==rhs._lod && _x==rhs._x && _y==rhs._y && 
-                _profile->isHorizEquivalentTo(rhs._profile);
+                _profile->isHorizEquivalentTo(rhs._profile.get());
         }
 
         /** Compare two tilekeys for inequality */
@@ -169,11 +169,6 @@ namespace osgEarth
         unsigned int getTileX() const { return _x; }
         unsigned int getTileY() const { return _y; }
 
-        //static inline int getLOD(const osgTerrain::TileID& id)
-        //{
-        //    return id.level;
-        //}
-
         /**
          * Maps this tile key to another tile key in order to account in
          * a resolution difference. For example: we are requesting data for
diff --git a/src/osgEarth/TileKey.cpp b/src/osgEarth/TileKey.cpp
index f8f3483..be2a5b8 100644
--- a/src/osgEarth/TileKey.cpp
+++ b/src/osgEarth/TileKey.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarth/TileNode.cpp b/src/osgEarth/TileNode.cpp
new file mode 100644
index 0000000..e69de29
diff --git a/src/osgEarth/TileSource b/src/osgEarth/TileSource
index 7299309..e670475 100644
--- a/src/osgEarth/TileSource
+++ b/src/osgEarth/TileSource
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -46,6 +46,8 @@
 namespace osgEarth
 {
     class ProgressCallback;
+    class Map;
+    class MapFrame;
 
     /**
      * Configuration options for a tile source driver.
@@ -91,6 +93,10 @@ namespace osgEarth
         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; }
+
     public:
         /** For backwards-compatibility; use minValidValue() instead 
          *  @deprecated */
@@ -124,6 +130,7 @@ namespace osgEarth
         optional<int>            _L2CacheSize;
         optional<bool>           _bilinearReprojection;
         optional<unsigned>       _maxDataLevel;
+        optional<bool>           _coverage;
     };
 
 
@@ -265,6 +272,17 @@ namespace osgEarth
         DataExtentList& getDataExtents() { return _dataExtents; }
 
         /**
+         * Call when you modify the data extents list.
+         */
+        void dirtyDataExtents();
+
+        /**
+         * Gets the union of all the data extents
+         */
+        const GeoExtent& getDataExtentsUnion() const;
+
+
+        /**
          * Creates an image for the given TileKey. The TileKey's profile must match
          * the profile of the TileSource.
          */
@@ -329,16 +347,16 @@ namespace osgEarth
             return _options.noDataValue().value(); }
 
         /**
-         * Gets the nodata min value
+         * Gets the minimum valid value. Any value below this is treated as "nodata"
          */
-        virtual float getNoDataMinValue() {
-            return _options.noDataMinValue().value(); }
+        virtual float getMinValidValue() {
+            return _options.minValidValue().value(); }
 
         /**
-         * Gets the nodata max value
+         * Gets the maximum valid value. Any value above this is considered "nodata"
          */
-        virtual float getNoDataMaxValue() {
-            return _options.noDataMaxValue().value(); }
+        virtual float getMaxValidValue() {
+            return _options.maxValidValue().value(); }
 
         /**
          * Gets the preferred extension for this TileSource
@@ -374,6 +392,32 @@ namespace osgEarth
         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.
          */
@@ -465,6 +509,12 @@ namespace osgEarth
          */
         void setStatus( Status status );
 
+        /**
+         * Accesses the map frame that synchronizes with a map if one is set.
+         * Note; the map frame might be empty/invalid.
+         */
+        MapFrame& getMapFrame() const;
+
 
     protected: // deprecated
 
@@ -495,8 +545,12 @@ namespace osgEarth
         osg::ref_ptr<MemCache> _memCache;
 
         DataExtentList _dataExtents;
+        GeoExtent      _dataExtentsUnion;
         Status         _status;
         Mode           _mode;
+
+        void setMap(const Map* map);
+        MapFrame* _frame;
     };
 
 
diff --git a/src/osgEarth/TileSource.cpp b/src/osgEarth/TileSource.cpp
index 0fe36b0..a41f28c 100644
--- a/src/osgEarth/TileSource.cpp
+++ b/src/osgEarth/TileSource.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -25,6 +25,7 @@
 #include <osgEarth/Registry>
 #include <osgEarth/ThreadingUtils>
 #include <osgEarth/MemCache>
+#include <osgEarth/MapFrame>
 #include <osgDB/FileUtils>
 #include <osgDB/FileNameUtils>
 #include <osgDB/ReadFile>
@@ -150,7 +151,8 @@ _noDataValue          ( (float)SHRT_MIN ),
 _minValidValue        ( -32000.0f ),
 _maxValidValue        (  32000.0f ),
 _L2CacheSize          ( 16 ),
-_bilinearReprojection ( true )
+_bilinearReprojection ( true ),
+_coverage             ( false )
 { 
     fromConfig( _conf );
 }
@@ -163,13 +165,12 @@ TileSourceOptions::getConfig() const
     conf.updateIfSet( "tile_size", _tileSize );
     conf.updateIfSet( "nodata_value", _noDataValue );
     conf.updateIfSet( "min_valid_value", _minValidValue );
-    conf.updateIfSet( "nodata_min", _minValidValue ); // backcompat
     conf.updateIfSet( "max_valid_value", _maxValidValue );
-    conf.updateIfSet( "nodata_max", _maxValidValue ); // backcompat
     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.updateObjIfSet( "profile", _profileOptions );
     return conf;
 }
@@ -188,12 +189,15 @@ TileSourceOptions::fromConfig( const Config& conf )
 {
     conf.getIfSet( "tile_size", _tileSize );
     conf.getIfSet( "nodata_value", _noDataValue );
-    conf.getIfSet( "nodata_min", _minValidValue );
-    conf.getIfSet( "nodata_max", _maxValidValue );
+    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.getObjIfSet( "profile", _profileOptions );
 }
 
@@ -213,19 +217,27 @@ const TileSource::Mode TileSource::MODE_CREATE = 0x04;
 TileSource::TileSource(const TileSourceOptions& options) :
 _options( options ),
 _status ( Status::Error("Not initialized") ),
-_mode   ( 0 )
+_mode   ( 0 ),
+_frame  ( 0L )
 {
     this->setThreadSafeRefUnref( true );
 
-    int l2CacheSize = 0;
+    _frame = new MapFrame();
+
+    // 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 );
     }
-    else if ( *options.L2CacheSize() > 0 )
+
+    // Initialize the l2 cache if it's size is > 0
+    if ( l2CacheSize > 0 )
     {
-        _memCache = new MemCache( *options.L2CacheSize() );
+        _memCache = new MemCache( l2CacheSize );
     }
 
     if (_options.blacklistFilename().isSet())
@@ -256,6 +268,11 @@ TileSource::~TileSource()
     {
         _blacklist->write(_blacklistFilename);
     }
+
+    if ( _frame )
+    {
+        delete _frame;
+    }
 }
 
 void
@@ -312,6 +329,45 @@ TileSource::getPixelsPerTile() const
     return _options.tileSize().value();
 }
 
+
+void TileSource::dirtyDataExtents()
+{
+    _dataExtentsUnion = GeoExtent::INVALID;
+}
+
+const GeoExtent& TileSource::getDataExtentsUnion() const
+{
+    if (_dataExtentsUnion.isInvalid() && _dataExtents.size() > 0)
+    {
+        static Threading::Mutex s_mutex;
+        Threading::ScopedMutexLock lock(s_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;
+}
+
+void
+TileSource::setMap(const Map* map)
+{
+    _frame->setMap( map );
+}
+
+MapFrame&
+TileSource::getMapFrame() const
+{
+    return *_frame;
+}
+
 osg::Image*
 TileSource::createImage(const TileKey&        key,
                         ImageOperation*       prepOp, 
@@ -481,15 +537,45 @@ TileSource::hasDataInExtent( const GeoExtent& extent ) const
     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.
+    //sematics: "might have data"
 
-    //If no data extents are provided, just return true
-    if (_dataExtents.size() == 0) 
+    // 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();
 
@@ -499,10 +585,17 @@ TileSource::hasData(const osgEarth::TileKey& key) const
         lod = getProfile()->getEquivalentLOD( key.getProfile(), key.getLevelOfDetail() );        
     }
 
-    // Check the explicit max data override:
+    // 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();
@@ -522,6 +615,77 @@ TileSource::hasData(const osgEarth::TileKey& key) const
 }
 
 bool
+TileSource::getBestAvailableTileKey(const osgEarth::TileKey& key,
+                                    osgEarth::TileKey&       output) const
+{
+    // 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 >= 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 <= 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.
@@ -588,6 +752,8 @@ TileSourceFactory::create(const TileSourceOptions& options)
         OE_WARN << LC << "Failed to load TileSource driver \"" << driver << "\"" << std::endl;
     }
 
+    OE_DEBUG << LC << "Tile source Profile = " << (result->getProfile() ? result->getProfile()->toString() : "NULL") << std::endl;
+
     // apply an Override Profile if provided.
     if ( result && options.profile().isSet() )
     {
diff --git a/src/osgEarth/TileVisitor b/src/osgEarth/TileVisitor
index 20ea8b6..b599c06 100644
--- a/src/osgEarth/TileVisitor
+++ b/src/osgEarth/TileVisitor
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/osgEarth/TileVisitor.cpp b/src/osgEarth/TileVisitor.cpp
index 45837d6..dcef395 100644
--- a/src/osgEarth/TileVisitor.cpp
+++ b/src/osgEarth/TileVisitor.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -245,7 +245,7 @@ void MultithreadedTileVisitor::run(const Profile* mapProfile)
 {                   
     // Start up the task service
     OE_INFO << "Starting " << _numThreads << std::endl;
-    _taskService = new TaskService( "MTTileHandler", _numThreads, 100000 );
+    _taskService = new TaskService( "MTTileHandler", _numThreads, 1000 );
 
     // Produce the tiles
     TileVisitor::run( mapProfile );
@@ -360,7 +360,7 @@ void MultiprocessTileVisitor::setBatchSize( unsigned int batchSize )
 void MultiprocessTileVisitor::run(const Profile* mapProfile)
 {                             
     // Start up the task service          
-    _taskService = new TaskService( "MPTileHandler", _numProcesses, 100000 );
+    _taskService = new TaskService( "MPTileHandler", _numProcesses, 1000 );
     
     // Produce the tiles
     TileVisitor::run( mapProfile );
diff --git a/src/osgEarth/TimeControl b/src/osgEarth/TimeControl
index 180a65e..82dcc76 100644
--- a/src/osgEarth/TimeControl
+++ b/src/osgEarth/TimeControl
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/osgEarth/TimeControl.cpp b/src/osgEarth/TimeControl.cpp
index d5af577..6f594c4 100644
--- a/src/osgEarth/TimeControl.cpp
+++ b/src/osgEarth/TimeControl.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarth/TraversalData b/src/osgEarth/TraversalData
index fce9b75..95ace91 100644
--- a/src/osgEarth/TraversalData
+++ b/src/osgEarth/TraversalData
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -69,6 +69,7 @@ namespace osgEarth
         osg::ref_ptr<osg::StateSet>      _stateSet;
         osg::ref_ptr<osg::Uniform>       _windowMatrixUniform;
         double                           _cameraAltitude;
+        osg::ref_ptr<osg::Uniform>       _cameraAltitudeUniform;
 
     protected:
         virtual ~MapNodeCullData() { }
diff --git a/src/osgEarth/TraversalData.cpp b/src/osgEarth/TraversalData.cpp
index 9a4528b..0f31324 100644
--- a/src/osgEarth/TraversalData.cpp
+++ b/src/osgEarth/TraversalData.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -29,4 +29,6 @@ MapNodeCullData::MapNodeCullData()
     _stateSet->addUniform( _windowMatrixUniform.get() );
 
     _cameraAltitude = 0.0;
+    _cameraAltitudeUniform = new osg::Uniform(osg::Uniform::FLOAT, "oe_CameraAltitude");
+    _stateSet->addUniform( _cameraAltitudeUniform.get() );
 }
diff --git a/src/osgEarth/URI b/src/osgEarth/URI
index 0e1c887..dd86a24 100644
--- a/src/osgEarth/URI
+++ b/src/osgEarth/URI
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -244,6 +244,10 @@ namespace osgEarth
 
         bool operator < ( const URI& rhs ) const { return _fullURI < rhs._fullURI; }
 
+        bool operator == ( const URI& rhs ) const { return _fullURI.compare(rhs._fullURI) == 0; }
+
+        bool operator != ( const URI& rhs ) const { return _fullURI.compare(rhs._fullURI) != 0; }
+
     public:
         /** Copier */
         URI( const URI& rhs ) : _baseURI(rhs._baseURI), _fullURI(rhs._fullURI), _context(rhs._context) { }
diff --git a/src/osgEarth/URI.cpp b/src/osgEarth/URI.cpp
index fdb2ef7..e065bb0 100644
--- a/src/osgEarth/URI.cpp
+++ b/src/osgEarth/URI.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -193,9 +193,14 @@ namespace
     // extracts a CacheBin from the dboptions; if one cannot be found, fall back on the
     // default CacheBin of a Cache found in the dboptions; failing that, call back on
     // the default CacheBin of the registry-wide cache.
-    CacheBin* s_getCacheBin( const osgDB::Options* dbOptions )
+    CacheBin* s_getCacheBin(const osgDB::Options* dbOptions)
     {
-        const osgDB::Options* o = dbOptions ? dbOptions : Registry::instance()->getDefaultOptions();
+        const osgDB::Options* o = dbOptions;
+        
+        if ( o == 0L )
+        {
+            o = Registry::instance()->getDefaultOptions();
+        }
 
         CacheBin* bin = CacheBin::get( o );
         if ( !bin )
@@ -405,14 +410,14 @@ namespace
             bool gotResultFromCallback = false;
 
             // check if there's an alias map, and if so, attempt to resolve the alias:
-            URIAliasMap* aliasMap = URIAliasMap::from( localOptions );
+            URIAliasMap* aliasMap = URIAliasMap::from( localOptions.get() );
             if ( aliasMap )
             {
                 uri = aliasMap->resolve(inputURI.full(), inputURI.context());
             }
 
             // check if there's a URI cache in the options.
-            URIResultCache* memCache = URIResultCache::from( localOptions );
+            URIResultCache* memCache = URIResultCache::from( localOptions.get() );
             if ( memCache )
             {
                 URIResultCache::Record rec;
@@ -434,7 +439,7 @@ namespace
                     if ( cb )
                     {
                         // if this returns "not implemented" we fill fall back
-                        result = reader.fromCallback( cb, uri.full(), localOptions );
+                        result = reader.fromCallback( cb, uri.full(), localOptions.get() );
 
                         if ( result.code() != ReadResult::RESULT_NOT_IMPLEMENTED )
                         {
@@ -457,14 +462,14 @@ namespace
 
                     // establish the caching policy.
                     optional<CachePolicy> cp;
-                    CachePolicy::fromOptions(localOptions, cp);
+                    CachePolicy::fromOptions(localOptions.get(), cp);
                     Registry::instance()->resolveCachePolicy( cp );                    
 
                     // get a cache bin if we need it:
                     CacheBin* bin = 0L;
                     if ( (cp->usage() != CachePolicy::USAGE_NO_CACHE) && callbackCachingOK )
                     {
-                        bin = s_getCacheBin( dbOptions );
+                        bin = s_getCacheBin( localOptions.get() );
                     }                    
 
 
@@ -511,13 +516,13 @@ namespace
                                 ReadResult remoteResult = reader.fromHTTP( uri.full(), remoteOptions.get(), progress, result.lastModifiedTime() );
                                 if (remoteResult.code() == ReadResult::RESULT_NOT_MODIFIED)
                                 {                                    
-                                    OE_DEBUG << uri.full() << " not modified, using cached result" << std::endl;
+                                    OE_DEBUG << LC << uri.full() << " not modified, using cached result" << std::endl;
                                     // Touch the cached item to update it's last modified timestamp so it doesn't expire again immediately.
                                     bin->touch( uri.cacheKey() );
                                 }
                                 else
                                 {
-                                    OE_DEBUG << "Got remote result for " << uri.full() << std::endl;
+                                    OE_DEBUG << LC << "Got remote result for " << uri.full() << std::endl;
                                     result = remoteResult;                                    
                                 }
                             }
@@ -525,7 +530,7 @@ namespace
                             // write the result to the cache if possible:
                             if ( result.succeeded() && !result.isFromCache() && bin && cp->isCacheWriteable() )
                             {
-                                OE_DEBUG << "Writing " << uri.cacheKey() << " to cache" << std::endl;
+                                OE_DEBUG << LC << "Writing " << uri.cacheKey() << " to cache" << std::endl;
                                 bin->write( uri.cacheKey(), result.getObject(), result.metadata() );
                             }
                         }
diff --git a/src/osgEarth/Units b/src/osgEarth/Units
index cea9f47..7b3bad4 100644
--- a/src/osgEarth/Units
+++ b/src/osgEarth/Units
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -21,6 +21,7 @@
 
 #include <osgEarth/Common>
 #include <osgEarth/Config>
+#include <ostream>
 
 namespace osgEarth
 {
@@ -180,9 +181,7 @@ namespace osgEarth
         static void registerAll(class Registry* registry);
         friend class Registry;
     };
-
-    struct Linear;
-
+    
     template<typename T>
     class qualified_double
     {
@@ -191,10 +190,18 @@ namespace osgEarth
 
         qualified_double<T>( const T& rhs ) : _value(rhs._value), _units(rhs._units) { }
 
+        // parses the qualified number from a parseable string (e.g., "123km")
+        qualified_double<T>(const std::string& parseable, const Units& defaultUnits) {
+            Units::parse( parseable, _value, _units, defaultUnits );
+        }
+
+        // loads the qualified number from an old-school config (e.g., { value="123" units="km" } )
         qualified_double<T>( const Config& conf, const Units& defaultUnits ) {
-            _value = conf.value<double>("value", 0.0);
-            if ( !Units::parse( conf.value("units"), _units ) )
-                _units = defaultUnits;
+            if ( conf.hasValue("value") ) {
+                _value = conf.value<double>("value", 0.0);
+                if ( !Units::parse( conf.value("units"), _units ) )
+                    _units = defaultUnits;
+            }
         }
 
         void set( double value, const Units& units ) {
@@ -255,6 +262,11 @@ namespace osgEarth
             return _units.convertTo( convertTo, _value );
         }
 
+        T to(const Units& convertTo) const {
+            return T( as(convertTo), convertTo );
+        }
+
+        double       getValue() const { return _value; }
         const Units& getUnits() const { return _units; }
 
         Config getConfig() const {
@@ -264,40 +276,56 @@ namespace osgEarth
             return conf;
         }
 
-    private:
+        std::string asString() const {
+            return Stringify() << _value << _units.getAbbr();
+        }
+
+        virtual std::string asParseableString() const {
+            return asString();
+        }
+
+    protected:
         double _value;
         Units  _units;
     };
 
-    struct Linear : public qualified_double<Linear> {
-        Linear() : qualified_double<Linear>(0, Units::METERS) { }
-        Linear(double value ) : qualified_double<Linear>(value, Units::METERS) { }
-        Linear(double value, const Units& units) : qualified_double<Linear>(value, units) { }
-        Linear(const Config& conf) : qualified_double<Linear>(conf, Units::METERS) { }
+    struct Distance : public qualified_double<Distance> {
+        Distance() : qualified_double<Distance>(0, Units::METERS) { }
+        Distance(double value ) : qualified_double<Distance>(value, Units::METERS) { }
+        Distance(double value, const Units& units) : qualified_double<Distance>(value, units) { }
+        Distance(const Config& conf) : qualified_double<Distance>(conf, Units::METERS) { } 
+        Distance(const std::string& str) : qualified_double<Distance>(str, Units::METERS) { }
     };
-    typedef Linear Distance;
-
-    struct Angular : public qualified_double<Angular> {
-        Angular() : qualified_double<Angular>(0, Units::DEGREES) { }
-        Angular(double value) : qualified_double<Angular>(value, Units::DEGREES) { }
-        Angular(double value, const Units& units) : qualified_double<Angular>(value, units) { }
-        Angular(const Config& conf) : qualified_double<Angular>(conf, Units::DEGREES) { }
+    typedef Distance Linear; // backwards compat
+
+    struct Angle : public qualified_double<Angle> {
+        Angle() : qualified_double<Angle>(0, Units::DEGREES) { }
+        Angle(double value) : qualified_double<Angle>(value, Units::DEGREES) { }
+        Angle(double value, const Units& units) : qualified_double<Angle>(value, units) { }
+        Angle(const Config& conf) : qualified_double<Angle>(conf, Units::DEGREES) { }
+        Angle(const std::string& str) : qualified_double<Angle>(str, Units::DEGREES) { }
+        std::string asParseableString() const {
+            if ( _units == Units::DEGREES ) return Stringify() << _value;
+            else return asString();
+        }
     };
-    typedef Angular Angle;
-
-    struct Temporal : public qualified_double<Temporal> {
-        Temporal() : qualified_double<Temporal>(0, Units::SECONDS) { }
-        Temporal(double value) : qualified_double<Temporal>(value, Units::SECONDS) { }
-        Temporal(double value, const Units& units) : qualified_double<Temporal>(value, units) { }
-        Temporal(const Config& conf) : qualified_double<Temporal>(conf, Units::SECONDS) { }
+    typedef Angle Angular; // backwards compat
+
+    struct Duration : public qualified_double<Duration> {
+        Duration() : qualified_double<Duration>(0, Units::SECONDS) { }
+        Duration(double value) : qualified_double<Duration>(value, Units::SECONDS) { }
+        Duration(double value, const Units& units) : qualified_double<Duration>(value, units) { }
+        Duration(const Config& conf) : qualified_double<Duration>(conf, Units::SECONDS) { }
+        Duration(const std::string& str) : qualified_double<Duration>(str, Units::SECONDS) { }
     };
-    typedef Temporal Duration;
+    typedef Duration Temporal; // backwards compat
 
     struct Speed : public qualified_double<Speed> {
         Speed() : qualified_double<Speed>(0, Units::METERS_PER_SECOND) { }
         Speed(double value) : qualified_double<Speed>(value, Units::METERS_PER_SECOND) { }
         Speed(double value, const Units& units) : qualified_double<Speed>(value, units) { }
         Speed(const Config& conf) : qualified_double<Speed>(conf, Units::METERS_PER_SECOND) { }
+        Speed(const std::string& str) : qualified_double<Speed>(str, Units::METERS_PER_SECOND) { }
     };
 
     struct ScreenSize : public qualified_double<ScreenSize> {
@@ -305,8 +333,133 @@ namespace osgEarth
         ScreenSize(double value) : qualified_double<ScreenSize>(value, Units::PIXELS) { }
         ScreenSize(double value, const Units& units) : qualified_double<ScreenSize>(value, units) { }
         ScreenSize(const Config& conf) : qualified_double<ScreenSize>(conf, Units::PIXELS) { }
+        ScreenSize(const std::string& str) : qualified_double<ScreenSize>(str, Units::PIXELS) { }
     };
-
+    
+
+    // Config specializations:
+
+    template <> inline
+    void Config::addIfSet<Distance>( const std::string& key, const optional<Distance>& opt ) {
+        if ( opt.isSet() ) { add(key, opt->asParseableString()); }
+    }
+    template<> inline
+    void Config::updateIfSet<Distance>( const std::string& key, const optional<Distance>& opt ) {
+        if ( opt.isSet() ) { update(key, opt->asParseableString()); }
+    }
+    template<> inline
+    bool Config::getIfSet<Distance>( const std::string& key, optional<Distance>& output ) const {
+        if ( hasValue(key) ) { output = Distance(value(key)); return true; }
+        else return false;
+    }
+    template<> inline
+    Distance as( const std::string& str, const Distance& default_value ) {
+        double val;
+        Units units;
+        if ( Units::parse(str, val, units, Units::METERS) )
+            return Distance(val, units);
+        else
+            return default_value;
+    }
+    
+
+    template <> inline
+    void Config::addIfSet<Angle>( const std::string& key, const optional<Angle>& opt ) {
+        if ( opt.isSet() ) { add(key, opt->asParseableString()); }
+    }
+    template<> inline
+    void Config::updateIfSet<Angle>( const std::string& key, const optional<Angle>& opt ) {
+        if ( opt.isSet() ) { update(key, opt->asParseableString()); }
+    }
+    template<> inline
+    bool Config::getIfSet<Angle>( const std::string& key, optional<Angle>& output ) const {
+        if ( hasValue(key) ) { output = Angle(value(key)); return true; }
+        else return false;
+    }
+    template<> inline
+    Angle as( const std::string& str, const Angle& default_value ) {
+        double val;
+        Units units;
+        if ( Units::parse(str, val, units, Units::DEGREES) )
+            return Angle(val, units);
+        else
+            return default_value;
+    }
+
+    template <> inline
+    void Config::addIfSet<Duration>( const std::string& key, const optional<Duration>& opt ) {
+        if ( opt.isSet() ) { add(key, opt->asParseableString()); }
+    }
+
+    template<> inline
+    void Config::updateIfSet<Duration>( const std::string& key, const optional<Duration>& opt ) {
+        if ( opt.isSet() ) { update(key, opt->asParseableString()); }
+    }
+
+    template<> inline
+    bool Config::getIfSet<Duration>( const std::string& key, optional<Duration>& output ) const {
+        if ( hasValue(key) ) { output = Duration(value(key)); return true; }
+        else return false;
+    }
+    template<> inline
+    Duration as( const std::string& str, const Duration& default_value ) {
+        double val;
+        Units units;
+        if ( Units::parse(str, val, units, Units::SECONDS) )
+            return Duration(val, units);
+        else
+            return default_value;
+    }
+
+    template <> inline
+    void Config::addIfSet<Speed>( const std::string& key, const optional<Speed>& opt ) {
+        if ( opt.isSet() ) { add(key, opt->asParseableString()); }
+    }
+
+    template<> inline
+    void Config::updateIfSet<Speed>( const std::string& key, const optional<Speed>& opt ) {
+        if ( opt.isSet() ) { update(key, opt->asParseableString()); }
+    }
+
+    template<> inline
+    bool Config::getIfSet<Speed>( const std::string& key, optional<Speed>& output ) const {
+        if ( hasValue(key) ) { output = Speed(value(key)); return true; }
+        else return false;
+    }
+    template<> inline
+    Speed as( const std::string& str, const Speed& default_value ) {
+        double val;
+        Units units;
+        if ( Units::parse(str, val, units, Units::METERS_PER_SECOND) )
+            return Speed(val, units);
+        else
+            return default_value;
+    }
+
+    template <> inline
+    void Config::addIfSet<ScreenSize>( const std::string& key, const optional<ScreenSize>& opt ) {
+        if ( opt.isSet() ) { add(key, opt->asParseableString()); }
+    }
+
+    template<> inline
+    void Config::updateIfSet<ScreenSize>( const std::string& key, const optional<ScreenSize>& opt ) {
+        if ( opt.isSet() ) { update(key, opt->asParseableString()); }
+    }
+
+    template<> inline
+    bool Config::getIfSet<ScreenSize>( const std::string& key, optional<ScreenSize>& output ) const {
+        if ( hasValue(key) ) { output = ScreenSize(value(key)); return true; }
+        else return false;
+    }
+    template<> inline
+    ScreenSize as( const std::string& str, const ScreenSize& default_value ) {
+        double val;
+        Units units;
+        if ( Units::parse(str, val, units, Units::PIXELS) )
+            return ScreenSize(val, units);
+        else
+            return default_value;
+    }
 }
 
 #endif // OSGEARTH_UNITS_H
diff --git a/src/osgEarth/Units.cpp b/src/osgEarth/Units.cpp
index 733e7d7..ba6799b 100644
--- a/src/osgEarth/Units.cpp
+++ b/src/osgEarth/Units.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarth/Utils b/src/osgEarth/Utils
index 870fa2f..188e255 100644
--- a/src/osgEarth/Utils
+++ b/src/osgEarth/Utils
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
@@ -203,6 +206,17 @@ namespace osgEarth
         void apply(osg::Geode& geode);
         void apply(osg::Geometry& geom);
     };
+
+    /**
+     * Allocates and merges buffer objects for each Drawable in the scene graph.
+     */
+    class OSGEARTH_EXPORT AllocateAndMergeBufferObjectsVisitor : public osg::NodeVisitor
+    {
+    public:
+        AllocateAndMergeBufferObjectsVisitor();
+        virtual ~AllocateAndMergeBufferObjectsVisitor() { }
+        void apply(osg::Geode& geode);
+    };
 }
 
 #endif // OSGEARTH_UTILS_H
diff --git a/src/osgEarth/Utils.cpp b/src/osgEarth/Utils.cpp
index 66fe815..579d5b3 100644
--- a/src/osgEarth/Utils.cpp
+++ b/src/osgEarth/Utils.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -392,6 +392,9 @@ SetDataVarianceVisitor::apply(osg::Geode& geode)
 
 //-----------------------------------------------------------------------------
 
+#undef  LC
+#define LC "[GeometryValidator] "
+
 namespace
 {
     template<typename DE>
@@ -424,17 +427,60 @@ GeometryValidator::GeometryValidator()
 void
 GeometryValidator::apply(osg::Geometry& geom)
 {
+    if ( geom.getVertexArray() == 0L )
+    {
+        OE_NOTICE << LC << "NULL vertex array!!\n";
+        return;
+    }
+
     unsigned numVerts = geom.getVertexArray()->getNumElements();
+    if ( numVerts == 0 )
+    {
+        OE_NOTICE << LC << "No verts!! name=" << geom.getName() << "\n";
+        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();
+        if ( a == NULL )
+        {
+            OE_NOTICE << LC << "Found a NULL array\n";
+        }
+        else if ( a->getBinding() == a->BIND_OVERALL && a->getNumElements() != 1 )
+        {
+            OE_NOTICE << LC << "Found an array with BIND_OVERALL and size <> 1\n";
+        }
+        else if ( a->getBinding() == a->BIND_PER_VERTEX && a->getNumElements() != numVerts )
+        {
+            OE_NOTICE << LC << "Found BIND_PER_VERTEX with wrong number of elements (expecting " << numVerts << "; found " << a->getNumElements() << ")\n";
+        }
+
+        _vbos.insert( a->getVertexBufferObject() );
+    }
+
+    if ( _vbos.size() != 1 )
+    {
+        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_WARN << "Color: BIND_OVERALL with wrong number of elements" << std::endl;
+            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_WARN << "Color: BIND_PER_VERTEX with color.size != verts.size" << std::endl;
+            OE_NOTICE << "Color: BIND_PER_VERTEX with colors.size != verts.size" << std::endl;
         }
     }
 
@@ -442,46 +488,86 @@ GeometryValidator::apply(osg::Geometry& geom)
     {
         if ( geom.getNormalBinding() == osg::Geometry::BIND_OVERALL && geom.getNormalArray()->getNumElements() != 1 )
         {
-            OE_WARN << "Normal: BIND_OVERALL with wrong number of elements" << std::endl;
+            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_WARN << "Normal: BIND_PER_VERTEX with color.size != verts.size" << std::endl;
+            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;
 
     for( osg::Geometry::PrimitiveSetList::const_iterator p = plist.begin(); p != plist.end(); ++p )
     {
         osg::PrimitiveSet* pset = p->get();
 
-        osg::DrawElementsUByte* de_byte = dynamic_cast<osg::DrawElementsUByte*>(pset);
-        if ( de_byte )
+        osg::DrawArrays* da = dynamic_cast<osg::DrawArrays*>(pset);
+        if ( da )
         {
-            if ( numVerts > 0xFF )
+            if ( da->getFirst() >= numVerts )
             {
-                OE_WARN << "DrawElementsUByte used when numVerts > 255 (" << numVerts << ")" << std::endl;
+                OE_NOTICE << LC << "DrawArrays: first > numVerts\n";
             }
+            if ( da->getFirst()+da->getCount() > numVerts )
+            {
+                OE_NOTICE << LC << "DrawArrays: first/count out of bounds\n";
+            }
+            if ( da->getCount() < 1 )
+            {
+                OE_NOTICE << LC << "DrawArrays: count is zero\n";
+            }
+        }
+
+        bool isDe = pset->getDrawElements() != 0L;
+
+        osg::DrawElementsUByte* de_byte = dynamic_cast<osg::DrawElementsUByte*>(pset);
+        if ( de_byte )
+        {
             validateDE(de_byte, 0xFF, numVerts );
+            _ebos.insert( de_byte->getElementBufferObject() );
         }
 
         osg::DrawElementsUShort* de_short = dynamic_cast<osg::DrawElementsUShort*>(pset);
         if ( de_short )
         {
-            if ( numVerts > 0xFFFF )
-            {
-                OE_WARN << "DrawElementsUShort used when numVerts > 65535 (" << numVerts << ")" << std::endl;
-            }
             validateDE(de_short, 0xFFFF, numVerts );
+            _ebos.insert( de_short->getElementBufferObject() );
         }
 
         osg::DrawElementsUInt* de_int = dynamic_cast<osg::DrawElementsUInt*>(pset);
         if ( de_int )
         {
             validateDE(de_int, 0xFFFFFFFF, numVerts );
+            _ebos.insert( de_int->getElementBufferObject() );
+        }
+
+        if ( pset->getNumIndices() == 0 )
+        {
+            OE_NOTICE << LC << "Primset: num elements = 0; class=" << pset->className() << ", name=" << pset->getName() << "\n";
+        }
+        else if ( pset->getType() >= GL_TRIANGLES && pset->getNumIndices() < 3 )
+        {
+            OE_NOTICE << LC << "Primset: not enough indicies for surface prim type\n";
+        }
+        else if ( pset->getType() >= GL_LINE_STRIP && pset->getNumIndices() < 2 )
+        {
+            OE_NOTICE << LC << "Primset: not enough indicies for linear prim type\n";
+        }
+        else if ( isDe && pset->getType() == GL_LINES && pset->getNumIndices() % 2 != 0 )
+        {
+            OE_NOTICE << LC << "Primset: non-even index count for GL_LINES\n";
         }
     }
+
+    if ( _ebos.size() != 1 )
+    {
+        OE_NOTICE << LC << "Found a Geometry that uses more than one EBO (non-optimal sharing)\n";
+    }
 }
 
 void
@@ -491,6 +577,40 @@ GeometryValidator::apply(osg::Geode& geode)
     {
         osg::Geometry* geom = geode.getDrawable(i)->asGeometry();
         if ( geom )
+        {
             apply( *geom );
+
+            if ( geom->getVertexArray() == 0L )
+            {
+                OE_NOTICE << "removing " << geom->getName() << " b/c of null vertex array\n";
+                geode.removeDrawable( geom );
+                --i;
+            }
+        }
     }
-}
\ No newline at end of file
+}
+//------------------------------------------------------------------------
+
+AllocateAndMergeBufferObjectsVisitor::AllocateAndMergeBufferObjectsVisitor()
+{
+    setVisitorType(NODE_VISITOR);
+    setTraversalMode(TRAVERSE_ALL_CHILDREN);
+    setNodeMaskOverride(~0);
+}
+
+void
+AllocateAndMergeBufferObjectsVisitor::apply(osg::Geode& geode)
+{
+    for(unsigned i=0; i<geode.getNumDrawables(); ++i)
+    {
+        osg::Geometry* geom = geode.getDrawable(i)->asGeometry();
+        if ( geom )
+        {
+            // We disable vbo's and then re-enable them to enable sharing of all the arrays.
+            geom->setUseDisplayList( false );
+            geom->setUseVertexBufferObjects( false );
+            geom->setUseVertexBufferObjects( true );
+        }
+    }
+    traverse(geode);
+}
diff --git a/src/osgEarth/Version b/src/osgEarth/Version
index c7f4465..3fa7278 100644
--- a/src/osgEarth/Version
+++ b/src/osgEarth/Version
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
@@ -25,7 +28,7 @@
 extern "C" {
 
 #define OSGEARTH_MAJOR_VERSION    2
-#define OSGEARTH_MINOR_VERSION    6
+#define OSGEARTH_MINOR_VERSION    7
 #define OSGEARTH_PATCH_VERSION    0
 #define OSGEARTH_SOVERSION        0
 #define OSGEARTH_RC_VERSION       0
diff --git a/src/osgEarth/Version.cpp b/src/osgEarth/Version.cpp
index 2eac968..5aa8f46 100644
--- a/src/osgEarth/Version.cpp
+++ b/src/osgEarth/Version.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarth/VerticalDatum b/src/osgEarth/VerticalDatum
index 158aa63..a4c7cb5 100644
--- a/src/osgEarth/VerticalDatum
+++ b/src/osgEarth/VerticalDatum
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -82,13 +82,13 @@ namespace osgEarth
          * Converts an MSL value (height relative to a mean sea level model) to the
          * corresponding HAE value (height above the model's reference ellipsoid)
          */
-        double msl2hae( double lat_deg, double lon_deg, double msl ) const;
+        virtual double msl2hae( double lat_deg, double lon_deg, double msl ) const;
 
         /**
          * Converts an HAE value (height above the model's reference ellipsoid) to the
          * corresponding MSL value (height relative to a mean sea level model)
          */
-        double hae2msl( double lat_deg, double lon_deg, double hae ) const;
+        virtual double hae2msl(double lat_deg, double lon_deg, double hae) const;
 
 
     public: // properties
diff --git a/src/osgEarth/VerticalDatum.cpp b/src/osgEarth/VerticalDatum.cpp
index 3a916e4..835d013 100644
--- a/src/osgEarth/VerticalDatum.cpp
+++ b/src/osgEarth/VerticalDatum.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -102,7 +102,7 @@ VerticalDatum::transform(const VerticalDatum* from,
     }
 
     Units fromUnits = from ? from->getUnits() : Units::METERS;
-    Units toUnits = to ? to->getUnits() : fromUnits;
+    Units toUnits = to ? to->getUnits() : Units::METERS;
 
     in_out_z = fromUnits.convertTo(toUnits, in_out_z);
 
@@ -161,7 +161,10 @@ VerticalDatum::transform(const VerticalDatum* from,
         {
             double lat = sw.y() + ystep*double(r);
             float& h = hf->getHeight(c, r);
-            VerticalDatum::transform( from, to, lat, lon, h );
+            if (h != NO_DATA_VALUE)
+            {
+                VerticalDatum::transform( from, to, lat, lon, h );
+            }
         }
     }
 
diff --git a/src/osgEarth/Viewpoint b/src/osgEarth/Viewpoint
index f59107f..2320c23 100644
--- a/src/osgEarth/Viewpoint
+++ b/src/osgEarth/Viewpoint
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -19,15 +19,18 @@
 #ifndef OSGEARTH_VIEWPOINT
 #define OSGEARTH_VIEWPOINT
 
-#include <osgEarth/Common>
 #include <osgEarth/Config>
-#include <osgEarth/SpatialReference>
+#include <osgEarth/GeoData>
+#include <osgEarth/Units>
+#include <osgEarth/optional>
+#include <osg/Node>
+#include <osg/observer_ptr>
 
 namespace osgEarth
 {
     /**
-     * Viewpoint data object. Stores a view configuration as a focal point
-     * and the camera's position relative to that focal point.
+     * Viewpoint stores a focal point (or a focal node) and camera parameters
+     * relative to that point.
      */
     class OSGEARTH_EXPORT Viewpoint
     {
@@ -38,145 +41,73 @@ namespace osgEarth
         Viewpoint();
 
         /**
-         * Constructs a new viewpoint.
-         *
-         * @param focal_point
-         *      The location at which the camera points. Express this coordinate
-         *      relative to the spatial reference system of the map, or you 
-         *      can specify the SRS in the last argument.
-         *
-         * @param heading_deg
-         *      Heading (in clockwise degrees) of the camera relative to the 
-         *      tangent plane of the focal point.
-         *
-         * @param pitch_deg
-         *      Pitch (in degrees) of the camera relative to the tangent plane
-         *      of the focal point.
-         *
-         * @param range
-         *      Straight-line distance from the camera to the focal point.
-         *
-         * @param srs (optional)
-         *      Spatial reference defining the focal point.
-         */
-        Viewpoint(
-            const osg::Vec3d& focal_point,
-            double heading_deg,
-            double pitch_deg,
-            double range,
-            const osgEarth::SpatialReference* srs =NULL );
-
-        Viewpoint(
-            double x_or_lon, double y_or_lat, double z,
-            double heading_deg,
-            double pitch_deg,
-            double range,
-            const osgEarth::SpatialReference* srs =NULL );
-
-        /**
-         * Constructs a new viewpoint.
-         *
-         * @param name
-         *      Name of this viewpoint.
-         *
-         * @param focal_point
-         *      The location at which the camera points. Express this coordinate
-         *      relative to the spatial reference system of the map, or you 
-         *      can specify the SRS in the last argument.
-         *
-         * @param heading_deg
-         *      Heading (in clockwise degrees) of the camera relative to the 
-         *      tangent plane of the focal point.
-         *
-         * @param pitch_deg
-         *      Pitch (in degrees) of the camera relative to the tangent plane
-         *      of the focal point.
-         *
-         * @param range
-         *      Straight-line distance from the camera to the focal point.
-         *
-         * @param srs (optional)
-         *      Spatial reference defining the focal point.
+         * Copy constructor.
          */
-        Viewpoint(
-            const std::string& name,
-            const osg::Vec3d& focal_point,
-            double heading_deg,
-            double pitch_deg,
-            double range,
-            const osgEarth::SpatialReference* srs =NULL );
-
-        Viewpoint(
-            const std::string& name,
-            double x_or_lon, double y_or_lat, double z,
-            double heading_deg,
-            double pitch_deg,
-            double range,
-            const osgEarth::SpatialReference* srs =NULL );
+        Viewpoint(const Viewpoint& rhs);
 
         /**
          * Deserialize a Config into this viewpoint object.
          */
-        Viewpoint( const Config& conf );
-
-        /**
-         * Copy constructor.
-         */
-        Viewpoint( const Viewpoint& rhs );
-
-        /** dtor */
-        virtual ~Viewpoint() { }
+        Viewpoint(const Config& conf);
 
     public:
 
         /**
-         * Returns true if this viewpoint contains valid information.
+         * Returns true if this viewpoint contains either a valid focal point or
+         * a valid tracked node.
          */
         bool isValid() const;
 
         /**
          * Gets or sets the name of the viewpoint.
          */
-        void setName( const std::string& name );
-        const std::string& getName() const;
+        optional<std::string>& name()             { return _name; }
+        const optional<std::string>& name() const { return _name; }
 
         /**
-         * Gets or sets the location at which the camera is pointing.
+         * The geospatial location at which the camera is pointing. If the Node (below)
+         * is set, this is ignored.
          */
-        const osg::Vec3d& getFocalPoint() const;
-        void setFocalPoint( const osg::Vec3d& point );
-
-        double x() const;
-        double& x();
-
-        double y() const;
-        double& y();
+        optional<GeoPoint>& focalPoint()             { return _point; }
+        const optional<GeoPoint>& focalPoint() const { return _point; }
 
-        double z() const;
-        double& z();
+        /**
+         * The node in the scene graph at which the camera is pointing. The "getter" follows
+         * the observer pattern; i.e. you must copy its value into a ref_ptr before safely
+         * accessing it. getNode() returns true if the safe-reference was successful.
+         *
+         * If a node is set, the Focal Point is ignored.
+         * Node will not serialize into a Config.
+         */
+        void setNode(osg::Node* value)                    { _node = value; }
+        bool getNode(osg::ref_ptr<osg::Node>& safe) const { return _node.lock(safe); }
+        bool nodeIsSet() const                            { return _node.valid(); }
 
         /**
-         * Gets the heading (in degrees) of the camera relative to the focal point.
+         * Heading (in degrees) of the camera relative to the focal point.
          */
-        double getHeading() const;
-        void setHeading( double heading_deg );
+        optional<Angle>& heading()             { return _heading; }
+        const optional<Angle>& heading() const { return _heading; }
 
         /**
-         * Gets the pitch (in degrees) of the camera relative to the focal point.
+         * The pitch of the camera relative to the focal point.
          */
-        double getPitch() const;
-        void setPitch( double pitch_deg );
+        optional<Angle>& pitch()             { return _pitch; }
+        const optional<Angle>& pitch() const { return _pitch; }
 
         /**
-         * Gets the distance from the camera to the focal point.
+         * The distance from the camera to the focal point.
          */
-        double getRange() const;
-        void setRange( double range );
+        optional<Distance>& range()             { return _range; }
+        const optional<Distance>& range() const { return _range; }
 
         /**
-         * Gets the spatial reference system of the focal point, if defined.
+         * Local offsets from the focal point, in meters. For example if the Viewpoint
+         * is looking at a Node, this will shift the focal point so it is offset from
+         * the node in a cartesian coordinate system where X=east, Y=north, Z=up.
          */
-        const osgEarth::SpatialReference* getSRS() const;
+        optional<osg::Vec3d>& positionOffset()             { return _posOffset; }
+        const optional<osg::Vec3d>& positionOffset() const { return _posOffset; }
 
         /**
          * Returns a printable string with the viewpoint data
@@ -184,18 +115,53 @@ namespace osgEarth
         std::string toString() const;
 
         /**
-         * Serialize this viewpoint to a config.
+         * Serialize this viewpoint to a config object.
          */
         Config getConfig() const;
 
+
+    public: // Backwards compatibility functions. Please don't use these in view code.
+
+        /** SRS is WGS84, angles are in degrees, range is in meters. */
+
+        /** @deprecated */
+        Viewpoint(double lon, double lat, double heading, double pitch, double range);
+
+        /** @deprecated */
+        Viewpoint(const char* name, double lon, double lat, double z, double heading, double pitch, double range);
+        
+        /** @deprecated */
+        void setHeading(double value) { _heading->set(value, Units::DEGREES); }
+
+        /** @deprecated */
+        void setPitch  (double value) { _pitch->set(value, Units::DEGREES); }
+
+        /** @deprecated */
+        void setRange  (double value) { _range->set(value, Units::METERS); }
+        
+        /** @deprecated */
+        double getHeading() const { return _heading->as(Units::DEGREES); }
+
+        /** @deprecated */
+        double getPitch()   const { return _pitch->as(Units::DEGREES); }
+
+        /** @deprecated */
+        double getRange()   const { return _range->as(Units::METERS); }
+
+    public:
+
+        /** dtor */
+        ~Viewpoint() { }
+
     private:
-        std::string _name;
-        osg::Vec3d _focal_point;
-        double _heading_deg;
-        double _pitch_deg;
-        double _range;
-        osg::ref_ptr<const osgEarth::SpatialReference> _srs;
-        bool _is_valid;
+        optional<std::string> _name;
+        optional<GeoPoint>    _point;
+        optional<Angle>       _heading;
+        optional<Angle>       _pitch;
+        optional<Distance>    _range;
+        optional<osg::Vec3d>  _posOffset;
+
+        osg::observer_ptr<osg::Node> _node;
     };
 
 } // namespace osgEarth
diff --git a/src/osgEarth/Viewpoint.cpp b/src/osgEarth/Viewpoint.cpp
index 4999ca1..2672ba6 100644
--- a/src/osgEarth/Viewpoint.cpp
+++ b/src/osgEarth/Viewpoint.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -23,122 +23,77 @@ using namespace osgEarth;
 
 
 Viewpoint::Viewpoint() :
-_is_valid( false ),
-_heading_deg(0.0),
-_pitch_deg(0.0),
-_range(1.0)
+_heading( Angle(       0.0, Units::DEGREES) ),
+_pitch  ( Angle(     -30.0, Units::DEGREES) ),
+_range  ( Distance(10000.0, Units::METERS)  )
 {
     //NOP
 }
 
-Viewpoint::Viewpoint(const osg::Vec3d& focal_point,
-                     double heading_deg,
-                     double pitch_deg, 
-                     double range,
-                     const osgEarth::SpatialReference* srs ) :
-_focal_point( focal_point ),
-_heading_deg( heading_deg ),
-_pitch_deg( pitch_deg ),
-_range( range ),
-_srs( srs ),
-_is_valid( true )
-{
-    //NOP
-}
-Viewpoint::Viewpoint(double x, double y, double z,
-                     double heading_deg,
-                     double pitch_deg, 
-                     double range,
-                     const osgEarth::SpatialReference* srs ) :
-_focal_point( x, y, z ),
-_heading_deg( heading_deg ),
-_pitch_deg( pitch_deg ),
-_range( range ),
-_srs( srs ),
-_is_valid( true )
+Viewpoint::Viewpoint( const Viewpoint& rhs ) :
+_name     ( rhs._name ),
+_point    ( rhs._point ),
+_heading  ( rhs._heading ),
+_pitch    ( rhs._pitch ),
+_range    ( rhs._range ),
+_posOffset( rhs._posOffset),
+_node     ( rhs._node.get() )
 {
     //NOP
 }
 
-Viewpoint::Viewpoint(const std::string& name,
-                     const osg::Vec3d& focal_point,
-                     double heading_deg,
-                     double pitch_deg, 
-                     double range,
-                     const osgEarth::SpatialReference* srs ) :
-_name( name ),
-_focal_point( focal_point ),
-_heading_deg( heading_deg ),
-_pitch_deg( pitch_deg ),
-_range( range ),
-_srs( srs ),
-_is_valid( true )
+Viewpoint::Viewpoint(const char* name, double lon, double lat, double z, double h, double p, double range)
 {
-    //NOP
+    if (name) _name = name;
+    _point->set( SpatialReference::get("wgs84"), lon, lat, z, ALTMODE_ABSOLUTE );
+    _heading->set( h, Units::DEGREES );
+    _pitch->set( p, Units::DEGREES );
+    _range->set( range, Units::METERS );
 }
 
-Viewpoint::Viewpoint(const std::string& name,
-                     double x, double y, double z,
-                     double heading_deg,
-                     double pitch_deg, 
-                     double range,
-                     const osgEarth::SpatialReference* srs ) :
-_name( name ),
-_focal_point( x, y, z ),
-_heading_deg( heading_deg ),
-_pitch_deg( pitch_deg ),
-_range( range ),
-_srs( srs ),
-_is_valid( true )
+Viewpoint::Viewpoint(const Config& conf)
 {
-    //NOP
-}
+    conf.getIfSet( "name",    _name );
+    conf.getIfSet( "heading", _heading );
+    conf.getIfSet( "pitch",   _pitch );
+    conf.getIfSet( "range",   _range );
 
-Viewpoint::Viewpoint( const Viewpoint& rhs ) :
-_name( rhs._name ),
-_focal_point( rhs._focal_point ),
-_heading_deg( rhs._heading_deg ),
-_pitch_deg( rhs._pitch_deg ),
-_range( rhs._range ),
-_srs( rhs._srs.get() ),
-_is_valid( rhs._is_valid )
-{
-    //NOP
-}
+    // piecewise point.
+    std::string horiz = conf.value("srs");
+    if ( horiz.empty() )
+        horiz = "wgs84";
 
-Viewpoint::Viewpoint( const Config& conf )
-{
-    _name = conf.value("name");
+    const std::string vert = conf.value("vdatum");
 
+    // try to parse an SRS, defaulting to WGS84 if not able to do so
+    osg::ref_ptr<const SpatialReference> srs = SpatialReference::create(horiz, vert);
+
+    // try x/y/z variant:
     if ( conf.hasValue("x") )
     {
-        _focal_point.set(
+        _point = GeoPoint(
+            srs.get(),
             conf.value<double>("x", 0.0),
             conf.value<double>("y", 0.0),
-            conf.value<double>("z", 0.0) );
+            conf.value<double>("z", 0.0),
+            ALTMODE_ABSOLUTE );
     }
     else if ( conf.hasValue("lat") )
     {
-        _focal_point.set(
-            conf.value<double>("long", 0.0),
-            conf.value<double>("lat", 0.0),
-            conf.value<double>("height", 0.0) );
-
-        if ( !conf.hasValue("srs") )
-            _srs = SpatialReference::create("wgs84");
+        _point = GeoPoint(
+            srs.get(),
+            conf.value<double>("long",   0.0),
+            conf.value<double>("lat",    0.0),
+            conf.value<double>("height", 0.0),
+            ALTMODE_ABSOLUTE );
     }
 
-    _heading_deg = conf.value<double>("heading", 0.0);
-    _pitch_deg   = conf.value<double>("pitch",   0.0);
-    _range       = conf.value<double>("range",   0.0);
-    _is_valid    = _range > 0.0;
-
-    const std::string horiz = conf.value("srs");
-    const std::string vert  = conf.value("vdatum");
-
-    if ( !horiz.empty() )
+    double xOffset = conf.value("x_offset", 0.0);
+    double yOffset = conf.value("y_offset", 0.0);
+    double zOffset = conf.value("z_offset", 0.0);
+    if ( xOffset != 0.0 || yOffset != 0.0 || zOffset != 0.0 )
     {
-        _srs = SpatialReference::create(horiz, vert);
+        _posOffset->set(xOffset, yOffset, zOffset);
     }
 }
 
@@ -149,137 +104,75 @@ Viewpoint::getConfig() const
 {
     Config conf( "viewpoint" );
 
-    if ( _is_valid )
+    conf.addIfSet( "name",    _name );
+    conf.addIfSet( "heading", _heading );
+    conf.addIfSet( "pitch",   _pitch );
+    conf.addIfSet( "range",   _range );
+    
+    if ( _point.isSet() )
     {
-        if ( !_name.empty() )
-            conf.set("name", _name);
-
-        if ( getSRS() && getSRS()->isGeographic() )
+        if ( _point->getSRS()->isGeographic() )
         {
-            conf.set("lat",    _focal_point.y());
-            conf.set("long",   _focal_point.x());
-            conf.set("height", _focal_point.z());
+            conf.set("long",   _point->x());
+            conf.set("lat",    _point->y());
+            conf.set("height", _point->z());
         }
         else
         {
-            conf.set("x", _focal_point.x());
-            conf.set("y", _focal_point.y());
-            conf.set("z", _focal_point.z());
+            conf.set("x", _point->x());
+            conf.set("y", _point->y());
+            conf.set("z", _point->z());
         }
 
-        conf.set("heading", _heading_deg);
-        conf.set("pitch",   _pitch_deg);
-        conf.set("range",   _range);
+        conf.set("srs", _point->getSRS()->getHorizInitString());
 
-        if ( _srs.valid() )
-        {
-            conf.set("srs", _srs->getHorizInitString());
-            if ( _srs->getVerticalDatum() )
-                conf.set("vdatum", _srs->getVertInitString());
-        }
+        if ( _point->getSRS()->getVerticalDatum() )
+            conf.set("vdatum", _point->getSRS()->getVertInitString());
+    }
+
+    if ( _posOffset.isSet() )
+    {
+        conf.set("x_offset", _posOffset->x());
+        conf.set("y_offset", _posOffset->y());
+        conf.set("z_offset", _posOffset->z());
     }
 
     return conf;
 }
 
 bool
-Viewpoint::isValid() const {
-    return _is_valid;
-}
-
-const std::string&
-Viewpoint::getName() const {
-    return _name;
-}
-
-void
-Viewpoint::setName( const std::string& name ) {
-    _name = name;
-}
-
-const osg::Vec3d&
-Viewpoint::getFocalPoint() const {
-    return _focal_point;
-}
-
-void
-Viewpoint::setFocalPoint( const osg::Vec3d& value ) {
-    _focal_point = value;
-}
-
-double
-Viewpoint::x() const {
-    return _focal_point.x();
-}
-
-double&
-Viewpoint::x() {
-    return _focal_point.x();
-}
-
-double
-Viewpoint::y() const {
-    return _focal_point.y();
-}
-
-double&
-Viewpoint::y() {
-    return _focal_point.y();
-}
-
-double
-Viewpoint::z() const {
-    return _focal_point.z();
-}
-
-double&
-Viewpoint::z() {
-    return _focal_point.z();
-}
-
-double
-Viewpoint::getHeading() const {
-    return _heading_deg;
-}
-
-void
-Viewpoint::setHeading( double value ) {
-    _heading_deg = value;
-}
-
-double
-Viewpoint::getPitch() const {
-    return _pitch_deg;
-}
-
-void
-Viewpoint::setPitch( double value ) {
-    _pitch_deg = value;
-}
-
-double
-Viewpoint::getRange() const {
-    return _range;
-}
-
-void
-Viewpoint::setRange( double value ) {
-    _range = value;
-}
-
-const SpatialReference*
-Viewpoint::getSRS() const {
-    return _srs.get();
+Viewpoint::isValid() const
+{
+    return
+        (_point.isSet() && _point->isValid()) ||
+        (_node.valid());
 }
 
 std::string
 Viewpoint::toString() const
 {
-    return Stringify()
-        << "x=" << _focal_point.x()
-        << ", y=" << _focal_point.y()
-        << ", z=" << _focal_point.z()
-        << ", h=" << _heading_deg
-        << ", p=" << _pitch_deg
-        << ", d=" << _range;
+    if ( _point.isSet() )
+    {
+        return Stringify()
+            << "x="   << _point->x()
+            << ", y=" << _point->y()
+            << ", z=" << _point->z()
+            << ", h=" << _heading->to(Units::DEGREES).asParseableString()
+            << ", p=" << _pitch->to(Units::DEGREES).asParseableString()
+            << ", d=" << _range->asParseableString()
+            << ", xo=" << _posOffset->x()
+            << ", yo=" << _posOffset->y()
+            << ", zo=" << _posOffset->z();
+    }
+    else
+    {
+        return Stringify()
+            << "attached to node; "
+            << ", h=" << _heading->to(Units::DEGREES).asParseableString()
+            << ", p=" << _pitch->to(Units::DEGREES).asParseableString()
+            << ", d=" << _range->asParseableString()
+            << ", xo=" << _posOffset->x()
+            << ", yo=" << _posOffset->y()
+            << ", zo=" << _posOffset->z();
+    }
 }
diff --git a/src/osgEarth/VirtualProgram b/src/osgEarth/VirtualProgram
index 4efcbbc..296c928 100644
--- a/src/osgEarth/VirtualProgram
+++ b/src/osgEarth/VirtualProgram
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
@@ -27,14 +30,17 @@
 #include <osg/Shader>
 #include <osg/Program>
 #include <osg/StateAttribute>
+#include <osg/buffered_value>
 #include <string>
 #include <map>
 
 #ifdef OSG_GLES2_AVAILABLE
+#    define GLSL_VERSION                 100
 #    define GLSL_VERSION_STR             "100"
 #    define GLSL_DEFAULT_PRECISION_FLOAT "precision highp float;"
 #else
-#    define GLSL_VERSION_STR             "110" 
+#    define GLSL_VERSION                 120
+#    define GLSL_VERSION_STR             "120" 
 #    define GLSL_DEFAULT_PRECISION_FLOAT ""
 #endif
 
@@ -205,7 +211,7 @@ namespace osgEarth
         META_StateAttribute( osgEarth, VirtualProgram, SA_TYPE);
 
         /** dtor */
-        virtual ~VirtualProgram() { }
+        virtual ~VirtualProgram();
 
         /** 
          * Compare this program against another (used for state-sorting)
@@ -255,6 +261,17 @@ namespace osgEarth
         osg::Program* getTemplate() { return _template.get(); }
         const osg::Program* getTemplate() const { return _template.get(); }
 
+        /** Enable logging to the console, for debugging. */
+        void setShaderLogging( bool log );
+
+        /** Enable logging to a file, for deugging. */
+        void setShaderLogging( bool log, const std::string& filepath );
+
+        /** Gets whether the accept callbacks vary per frame */
+        bool getAcceptCallbacksVaryPerFrame() const;
+
+        /** Sets whether the accept callbacks vary per frame */
+        void setAcceptCallbacksVaryPerFrame(bool acceptCallbacksVaryPerFrame);
 
     public: // StateAttribute
         virtual void compileGLObjects(osg::State& state) const;
@@ -268,7 +285,7 @@ namespace osgEarth
     public:
         typedef std::vector< osg::ref_ptr<osg::Shader> > ShaderVector;
 
-    public:
+    public:        
         struct ShaderEntry
         {
             ShaderEntry();
@@ -285,12 +302,15 @@ namespace osgEarth
             unsigned                   _frameLastUsed;
         };
 
-        typedef std::map< std::string, ShaderEntry > ShaderMap;
-        typedef std::map< std::string, std::string > AttribAliasMap;
+        typedef unsigned                              ShaderID;
+        typedef vector_map<ShaderID, ShaderEntry>     ShaderMap;
+        typedef std::map< std::string, std::string >  AttribAliasMap;
         typedef std::pair< std::string, std::string > AttribAlias;
-        typedef std::vector< AttribAlias > AttribAliasVector;
-        //typedef std::map< ShaderVector, osg::ref_ptr<osg::Program> > ProgramMap;
+        typedef std::vector< AttribAlias >            AttribAliasVector;
+
         typedef osgEarth::fast_map< ShaderVector, ProgramEntry > ProgramMap;
+        typedef std::pair< const osg::StateAttribute*, osg::StateAttribute::OverrideValue > AttributePair;
+        typedef std::vector< AttributePair > AttrStack;
 
     public:
         /**
@@ -305,9 +325,12 @@ namespace osgEarth
         // thread-safe functions map getter
         void getFunctions( ShaderComp::FunctionLocationMap& out ) const;
 
-        // thread-safe shader map getter
+        // thread-safe shader map copy
         void getShaderMap( ShaderMap& out ) const;
 
+        // thread-safe shader accumulator
+        void addShadersToAccumulationMap(VirtualProgram::ShaderMap& accumMap, const osg::State& state) const;
+
     protected:
         // holds "template" data that should be installed in every auto-generated
         // Program, like uniform buffer bindings, etc.
@@ -325,22 +348,59 @@ namespace osgEarth
         // _dataModelMutex protects access to this member.
         ShaderMap _shaderMap;
 
+        // per-context cached shader map for thread-safe reuse without constant reallocation.
+        struct ApplyVars
+        {
+            ShaderMap         accumShaderMap;
+            ShaderVector      keyVector;
+            AttribBindingList accumAttribBindings;
+            AttribAliasMap    accumAttribAliases;
+        };
+        mutable osg::buffered_object<ApplyVars> _apply;
+
         // protects access to the data members, which may be accessed by other VPs in the state stack.
-        mutable Threading::ReadWriteMutex _dataModelMutex;
+        mutable Threading::Mutex _dataModelMutex;
 
         // The program cache holds an osg::Program instance for each collection of shaders
         // that comprises this VP. There can be multiple programs in the cache if the VP is
         // shared in the scene graph.
-        mutable ProgramMap                _programCache;
-        mutable Threading::ReadWriteMutex _programCacheMutex;
+        mutable ProgramMap       _programCache;
+        mutable Threading::Mutex _programCacheMutex;
 
         mutable optional<bool> _active;
         bool _inherit;
         bool _inheritSet;
 
+        bool _logShaders;
+        std::string _logPath;
+
+        // whether to use the "attribute stack memory" feature. default = true.
+        // see below.
+        bool _useStackMemory;
+
+        bool _acceptCallbacksVaryPerFrame;
+
+        // Mechnism for remembering whether a VP has been applied during the same frame
+        // and with the same attribute stack.
+        struct AttrStackMemory
+        {
+            void remember(const osg::State& state, const AttrStack& rhs, osg::Program* p);
+            osg::Program* recall(const osg::State& state, const AttrStack& rhs);
+
+            struct Item
+            {
+                int frameNumber;
+                AttrStack attrStack;
+                osg::ref_ptr<osg::Program> program;
+            };
+            osg::buffered_object<Item> _item;
+        };
+        mutable AttrStackMemory _vpStackMemory;
+
         bool hasLocalFunctions() const;
 
-        void accumulateFunctions(            const osg::State&                state,
+        void accumulateFunctions(            
+            const osg::State&                state,
             ShaderComp::FunctionLocationMap& result ) const;
 
         const AttribAliasMap& getAttribAliases() const { return _attribAliases; }
@@ -351,7 +411,8 @@ namespace osgEarth
             unsigned           mask,
             ShaderMap&         accumShaderMap,
             AttribBindingList& accumAttribBindings,
-            AttribAliasMap&    accumAttribAliases);
+            AttribAliasMap&    accumAttribAliases,
+            bool&              acceptCallbacksPresent);
         
         bool readProgramCache(
             const ShaderVector& vec,
@@ -361,6 +422,8 @@ namespace osgEarth
         void removeExpiredProgramsFromCache(
             osg::State& state,
             unsigned frameNumber);
+
+        bool checkSharing();
     };
 
 } // namespace osgEarth
diff --git a/src/osgEarth/VirtualProgram.cpp b/src/osgEarth/VirtualProgram.cpp
index 3f7c885..225c36f 100644
--- a/src/osgEarth/VirtualProgram.cpp
+++ b/src/osgEarth/VirtualProgram.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -22,11 +22,16 @@
 #include <osgEarth/Capabilities>
 #include <osgEarth/ShaderFactory>
 #include <osgEarth/ShaderUtils>
+#include <osgEarth/StringUtils>
 #include <osgEarth/Containers>
 #include <osg/Shader>
 #include <osg/Program>
 #include <osg/State>
 #include <osg/Notify>
+#include <osg/Version>
+#include <osg/GL2Extensions>
+#include <osg/GLExtensions>
+#include <fstream>
 #include <sstream>
 #include <OpenThreads/Thread>
 
@@ -41,6 +46,12 @@ using namespace osgEarth::ShaderComp;
 //#define DEBUG_APPLY_COUNTS
 //#define DEBUG_ACCUMULATION
 
+#define USE_STACK_MEMORY 1
+
+#define MAX_PROGRAM_CACHE_SIZE 128
+
+#define MAKE_SHADER_ID(X) osgEarth::hashString( X )
+
 //------------------------------------------------------------------------
 
 namespace
@@ -87,31 +98,17 @@ namespace
         have a const way to do this. It has getAttributeVec() but that is non-const (it creates
         the vector if it doesn't exist); Newer versions have getAttributeMap(), but that does not
         go back to OSG 3.0. */
-    class StateHack : public osg::State 
+    struct StateEx : public osg::State 
     {
-    public:        
-        typedef std::pair<const osg::StateAttribute*,osg::StateAttribute::OverrideValue> AttributePair;
-        typedef std::vector<AttributePair> AttributeVec;
-
-        const AttributeVec* getAttributeVec( const osg::StateAttribute* attribute ) const
-        {
-            osg::State::AttributeMap::const_iterator i = _attributeMap.find( attribute->getTypeMemberPair() );
-            return i != _attributeMap.end() ? &(i->second.attributeVec) : 0L;
-        }
-
-        static const AttributeVec* GetAttributeVec( const osg::State& state, const osg::StateAttribute* attribute ) 
-        {
-            const StateHack* sh = reinterpret_cast< const StateHack* >( &state );
-            return sh->getAttributeVec( attribute );
-        }
-
-        static const AttributeVec* GetAttributeVec(const osg::State& state, osg::StateAttribute::Type type)
-        {
-            const StateHack* sh = reinterpret_cast< const StateHack* >( &state );
-            osg::State::AttributeMap::const_iterator i = sh->_attributeMap.find( std::make_pair(type, 0) );
-            return i != sh->_attributeMap.end() ? &(i->second.attributeVec) : 0L;            
+        static const VirtualProgram::AttrStack* getProgramStack(const osg::State& state)
+        {            
+            static osg::StateAttribute::TypeMemberPair pair(VirtualProgram::SA_TYPE, 0);
+            const StateEx* sh = reinterpret_cast<const StateEx*>( &state );
+            AttributeMap::const_iterator i = sh->_attributeMap.find( pair );
+            return i != sh->_attributeMap.end() ? &(i->second.attributeVec) : 0L;
         }
     };
+
     typedef std::map<std::string, std::string> HeaderMap;
 
     // removes leading and trailing whitespace, and replaces all other
@@ -184,6 +181,14 @@ namespace
                     header = line;
                 }
 
+                else if ( tokens[0] == "#pragma")
+                {
+                    // Discards all pragmas, since the double-quotes in them are illegal in at least
+                    // GLSL ES compiler (on Android). We should consider doing this for all GLSL
+                    // since technically quoting characters are not part of the GLSL grammar at all.
+                    continue;
+                }
+
                 else
                 {
                     body << (*line_iter) << "\n";
@@ -221,7 +226,7 @@ namespace
     * new entry.
     */
     void addToAccumulatedMap(VirtualProgram::ShaderMap&         accumShaderMap,
-                             const std::string&                 shaderID,
+                             const VirtualProgram::ShaderID&    shaderID,
                              const VirtualProgram::ShaderEntry& newEntry)
     {        
 
@@ -423,7 +428,7 @@ namespace
         // based on its accumlated function set.
         for( VirtualProgram::ShaderMap::iterator i = accumShaderMap.begin(); i != accumShaderMap.end(); ++i )
         {
-            outputKeyVector.push_back( i->second._shader.get() );
+            outputKeyVector.push_back( i->data()._shader.get() );
         }
 
         // finally, add the mains (AFTER building the key vector .. we don't want or
@@ -450,6 +455,68 @@ namespace
 
 //------------------------------------------------------------------------
 
+void
+VirtualProgram::AttrStackMemory::remember(const osg::State&                 state,
+                                          const VirtualProgram::AttrStack&  rhs,
+                                          osg::Program*                     program)
+{
+    if ( state.getFrameStamp() )
+    {
+        unsigned contextID = state.getContextID();
+        Item& item = _item[contextID];
+        item.frameNumber = state.getFrameStamp()->getFrameNumber();
+        item.attrStack = rhs;
+        item.program = program;
+    }
+}
+
+osg::Program*
+VirtualProgram::AttrStackMemory::recall(const osg::State&                 state,
+                                        const VirtualProgram::AttrStack&  rhs)
+{
+    osg::Program* program = 0L;
+
+    const osg::FrameStamp* fs = state.getFrameStamp();
+    if ( fs )
+    {
+        unsigned contextID = state.getContextID();
+        Item& item = _item[contextID];
+
+        // check frame number:
+        if ( item.program.valid() && item.frameNumber == fs->getFrameNumber() )
+        {
+            // same, how compare the attribute stacks:
+            if ( item.attrStack.size() == rhs.size() )
+            {
+                bool arraysMatch = true;
+                for(int i=0; i<item.attrStack.size() && arraysMatch; ++i)
+                {
+                    if (item.attrStack[i] != rhs[i])
+                    {
+                        arraysMatch = false;
+                    }
+                }
+
+                if (arraysMatch)
+                {
+                    program = item.program.get();
+                }
+            }
+        }
+
+        if ( !program )
+        {
+            item.frameNumber = fs->getFrameNumber();
+            item.attrStack = rhs;
+            item.program = 0L;
+        }
+    }
+
+    return program;
+}
+
+//------------------------------------------------------------------------
+
 VirtualProgram::ShaderEntry::ShaderEntry() :
 _overrideValue(0)
 {
@@ -550,7 +617,10 @@ VirtualProgram::VirtualProgram( unsigned mask ) :
 _mask              ( mask ),
 _active            ( true ),
 _inherit           ( true ),
-_inheritSet        ( false )
+_inheritSet        ( false ),
+_logShaders        ( false ),
+_logPath           ( "" ),
+_acceptCallbacksVaryPerFrame( false )
 {
     // Note: we cannot set _active here. Wait until apply().
     // It will cause a conflict in the Registry.
@@ -580,9 +650,21 @@ _mask              ( rhs._mask ),
 _functions         ( rhs._functions ),
 _inherit           ( rhs._inherit ),
 _inheritSet        ( rhs._inheritSet ),
+_logShaders        ( rhs._logShaders ),
+_logPath           ( rhs._logPath ),
 _template          ( osg::clone(rhs._template.get()) )
+{    
+    // Attribute bindings.
+    const osg::Program::AttribBindingList &abl = rhs.getAttribBindingList();
+    for( osg::Program::AttribBindingList::const_iterator attribute = abl.begin(); attribute != abl.end(); ++attribute )
+    {
+        addBindAttribLocation( attribute->first, attribute->second );
+    }
+}
+
+VirtualProgram::~VirtualProgram()
 {
-    //nop
+    //NOP
 }
 
 int
@@ -598,8 +680,9 @@ VirtualProgram::compare(const osg::StateAttribute& sa) const
 
     // compare the shader maps. Need to lock them while comparing.
     {
-        Threading::ScopedReadLock shared( _dataModelMutex );
-
+        //Threading::ScopedReadLock shared( _dataModelMutex );
+        Threading::ScopedMutexLock lock( _dataModelMutex );
+        
         if ( _shaderMap.size() < rhs._shaderMap.size() ) return -1;
         if ( _shaderMap.size() > rhs._shaderMap.size() ) return 1;
 
@@ -608,11 +691,14 @@ VirtualProgram::compare(const osg::StateAttribute& sa) const
 
         while( lhsIter != _shaderMap.end() )
         {
-            int keyCompare = lhsIter->first.compare( rhsIter->first );
-            if ( keyCompare != 0 ) return keyCompare;
+            //int keyCompare = lhsIter->key().compare( rhsIter->key() );
+            //if ( keyCompare != 0 ) return keyCompare;
 
-            const ShaderEntry& lhsEntry = lhsIter->second;
-            const ShaderEntry& rhsEntry = rhsIter->second;
+            if ( lhsIter->key() < rhsIter->key() ) return -1;
+            if ( lhsIter->key() > rhsIter->key() ) return  1;
+
+            const ShaderEntry& lhsEntry = lhsIter->data(); //lhsIter->second;
+            const ShaderEntry& rhsEntry = rhsIter->data(); //rhsIter->second;
 
             if ( lhsEntry < rhsEntry ) return -1;
             if ( rhsEntry < lhsEntry ) return  1;
@@ -632,7 +718,8 @@ VirtualProgram::compare(const osg::StateAttribute& sa) const
 void
 VirtualProgram::addBindAttribLocation( const std::string& name, GLuint index )
 {
-    Threading::ScopedWriteLock exclusive( _dataModelMutex );
+    //Threading::ScopedWriteLock exclusive( _dataModelMutex );
+    _dataModelMutex.lock();
 
 #ifdef USE_ATTRIB_ALIASES
     _attribAliases[name] = Stringify() << "oe_attrib_" << index;
@@ -640,12 +727,15 @@ VirtualProgram::addBindAttribLocation( const std::string& name, GLuint index )
 #else
     _attribBindingList[name] = index;
 #endif
+
+    _dataModelMutex.unlock();
 }
 
 void
 VirtualProgram::removeBindAttribLocation( const std::string& name )
 {
-    Threading::ScopedWriteLock exclusive( _dataModelMutex );
+    //Threading::ScopedWriteLock exclusive( _dataModelMutex );
+    _dataModelMutex.lock();
 
 #ifdef USE_ATTRIB_ALIASES
     std::map<std::string,std::string>::iterator i = _attribAliases.find(name);
@@ -654,6 +744,8 @@ VirtualProgram::removeBindAttribLocation( const std::string& name )
 #else
     _attribBindingList.erase(name);
 #endif
+
+    _dataModelMutex.unlock();
 }
 
 void
@@ -665,22 +757,20 @@ VirtualProgram::compileGLObjects(osg::State& state) const
 void
 VirtualProgram::resizeGLObjectBuffers(unsigned maxSize)
 {
-    Threading::ScopedWriteLock exclusive( _programCacheMutex );
-
-    //  OE_WARN << LC << "Resize VP " << getName() << std::endl;
+    _programCacheMutex.lock();
 
     for (ProgramMap::iterator i = _programCache.begin(); i != _programCache.end(); ++i)
     {
         i->second._program->resizeGLObjectBuffers(maxSize);
     }
+
+    _programCacheMutex.unlock();
 }
 
 void
 VirtualProgram::releaseGLObjects(osg::State* state) const
 {
-    Threading::ScopedWriteLock exclusive( _programCacheMutex );
-
-    //  OE_WARN << LC << "Release VP " << getName() << std::endl;
+    _programCacheMutex.lock();
 
     for (ProgramMap::const_iterator i = _programCache.begin(); i != _programCache.end(); ++i)
     {
@@ -689,15 +779,16 @@ VirtualProgram::releaseGLObjects(osg::State* state) const
     }
 
     _programCache.clear();
+
+    _programCacheMutex.unlock();
 }
 
 osg::Shader*
 VirtualProgram::getShader( const std::string& shaderID ) const
 {
-    Threading::ScopedReadLock readonly( _dataModelMutex );
-
-    ShaderMap::const_iterator i = _shaderMap.find(shaderID);
-    return i != _shaderMap.end() ? i->second._shader.get() : 0L;
+    Threading::ScopedMutexLock readonly( _dataModelMutex );
+    const ShaderEntry* entry = _shaderMap.find( MAKE_SHADER_ID(shaderID) );
+    return entry ? entry->_shader.get() : 0L;
 }
 
 
@@ -715,6 +806,8 @@ VirtualProgram::setShader(const std::string&                 shaderID,
         setInheritShaders( true );
     }
 
+    checkSharing();
+
     // set the name to the ID:
     shader->setName( shaderID );
 
@@ -724,12 +817,15 @@ VirtualProgram::setShader(const std::string&                 shaderID,
 
     // lock the data model and insert the new shader.
     {
-        Threading::ScopedWriteLock exclusive( _dataModelMutex );
+        _dataModelMutex.lock();
+        //Threading::ScopedWriteLock exclusive( _dataModelMutex );
 
-        ShaderEntry& entry = _shaderMap[shaderID];
+        ShaderEntry& entry = _shaderMap[MAKE_SHADER_ID(shaderID)];
         entry._shader        = shader;
         entry._overrideValue = ov;
         entry._accept        = 0L;
+
+        _dataModelMutex.unlock();
     }
 
     return shader;
@@ -761,12 +857,16 @@ VirtualProgram::setShader(osg::Shader*                       shader,
 
     // lock the data model while changing it.
     {
-        Threading::ScopedWriteLock exclusive( _dataModelMutex );
-        
-        ShaderEntry& entry = _shaderMap[shader->getName()];
+        _dataModelMutex.lock();
+
+        checkSharing();
+
+        ShaderEntry& entry = _shaderMap[MAKE_SHADER_ID(shader->getName())];
         entry._shader        = shader;
         entry._overrideValue = ov;
         entry._accept        = 0L;
+
+        _dataModelMutex.unlock();
     }
 
     return shader;
@@ -797,7 +897,9 @@ VirtualProgram::setFunction(const std::string&           functionName,
 
     // lock the functions map while iterating and then modifying it:
     {
-        Threading::ScopedWriteLock exclusive( _dataModelMutex );
+        _dataModelMutex.lock();
+
+        checkSharing();
 
         OrderedFunctionMap& ofm = _functions[location];
 
@@ -821,7 +923,7 @@ VirtualProgram::setFunction(const std::string&           functionName,
         ShaderComp::Function function;
         function._name   = functionName;
         function._accept = accept;
-        ofm.insert( OrderedFunction(ordering, function) ); //std::make_pair(ordering, function) );
+        ofm.insert( OrderedFunction(ordering, function) );
 
         // create and add the new shader function.
         osg::Shader::Type type = (int)location <= (int)LOCATION_VERTEX_CLIP ?
@@ -833,11 +935,13 @@ VirtualProgram::setFunction(const std::string&           functionName,
         // pre-processes the shader's source to include GLES uniforms as necessary
         ShaderPreProcessor::run( shader );
 
-        ShaderEntry& entry = _shaderMap[functionName];
+        ShaderEntry& entry = _shaderMap[MAKE_SHADER_ID(functionName)];
         entry._shader        = shader;
         entry._overrideValue = osg::StateAttribute::ON;
         entry._accept        = accept;
 
+        _dataModelMutex.unlock();
+
     } // release lock
 }
 
@@ -845,35 +949,43 @@ void
 VirtualProgram::setFunctionMinRange(const std::string& name, float minRange)
 {
     // lock the functions map while making changes:
-    Threading::ScopedWriteLock exclusive( _dataModelMutex );
+    _dataModelMutex.lock();
+
+    checkSharing();
 
     ShaderComp::Function* function;
     if ( findFunction(name, _functions, &function) )
     {
         function->_minRange = minRange;
     }
+
+    _dataModelMutex.unlock();
 }
 
 void 
 VirtualProgram::setFunctionMaxRange(const std::string& name, float maxRange)
 {
     // lock the functions map while making changes:
-    Threading::ScopedWriteLock exclusive( _dataModelMutex );
+    _dataModelMutex.lock();
+
+    checkSharing();
 
     ShaderComp::Function* function;
     if ( findFunction(name, _functions, &function) )
     {
         function->_maxRange = maxRange;
     }
+
+    _dataModelMutex.unlock();
 }
 
 void
 VirtualProgram::removeShader( const std::string& shaderID )
 {
     // lock the functions map while making changes:
-    Threading::ScopedWriteLock exclusive( _dataModelMutex );
+    Threading::ScopedMutexLock exclusive( _dataModelMutex );
 
-    _shaderMap.erase( shaderID );
+    _shaderMap.erase( MAKE_SHADER_ID(shaderID) );
 
     for(FunctionLocationMap::iterator i = _functions.begin(); i != _functions.end(); ++i )
     {
@@ -906,8 +1018,9 @@ VirtualProgram::setInheritShaders( bool value )
 
         // clear the program cache please
         {
-            Threading::ScopedWriteLock exclusive(_programCacheMutex);
+            _programCacheMutex.lock();
             _programCache.clear();
+            _programCacheMutex.unlock();
         }
 
         _inheritSet = true;
@@ -926,6 +1039,8 @@ VirtualProgram::apply( osg::State& state ) const
     {
         _active = Registry::capabilities().supportsGLSL();
     }
+    
+    const unsigned contextID = state.getContextID();
 
     if (_shaderMap.empty() && !_inheritSet)
     {
@@ -934,117 +1049,182 @@ VirtualProgram::apply( osg::State& state ) const
         // Sine we have no way of knowing whether the user created the VP or OSG created it
         // as the default fallback, we use the "_inheritSet" flag to differeniate. This
         // prevents any shader leakage from a VP-enabled node.
-        const unsigned int contextID = state.getContextID();
-        const osg::GL2Extensions* extensions = osg::GL2Extensions::Get(contextID,true);
-        if( ! extensions->isGlslSupported() ) return;
 
-        extensions->glUseProgram( 0 );
-        state.setLastAppliedProgramObject(0);
+        // The following "if" helps performance a bit (based on profiler results) but a user
+        // reported state corruption in the OSG stats display. The underlying cause is likely
+        // in external code, but leave it commented out for now -gw 20150721
+
+        //if ( state.getLastAppliedProgramObject() != 0L )
+        {
+            const osg::GL2Extensions* extensions = osg::GL2Extensions::Get(contextID,true);
+            extensions->glUseProgram( 0 );
+            state.setLastAppliedProgramObject(0);
+        }
         return;
     }
 
-    // first, find and collect all the VirtualProgram attributes:
-    ShaderMap         accumShaderMap;
-    AttribBindingList accumAttribBindings;
-    AttribAliasMap    accumAttribAliases;
+    osg::ref_ptr<osg::Program> program;
     
-    // Build the active shader map up to this point:
-    if ( _inherit )
+    // Negate osg::State's last-attribute-applied tracking for 
+    // VirtualProgram, since it cannot detect a VP that is reached from
+    // different node/attribute paths. We replace this with the 
+    // stack-memory construct below which will "remember" whether 
+    // the VP has already been applied during the current frame using
+    // the same an identical attribute stack.
+    state.haveAppliedAttribute(this->SA_TYPE);
+
+#ifdef USE_STACK_MEMORY
+    bool programRecalled = false;
+    const AttrStack* stack = StateEx::getProgramStack(state);
+    if ( stack)
     {
-        accumulateShaders(state, _mask, accumShaderMap, accumAttribBindings, accumAttribAliases);
+        program = _vpStackMemory.recall(state, *stack);
+        programRecalled = program.valid();
     }
+#endif // USE_STACK_MEMORY
+    
+    // We need to tracks whether there are any accept callbacks, because if so
+    // we cannot store the program in stack memory -- the accept callback can
+    // exclude shaders based on any condition.
+    bool acceptCallbacksVary = _acceptCallbacksVaryPerFrame;
 
-    // next add the local shader components to the map, respecting the override values:
+    if ( !program.valid() )
     {
-        Threading::ScopedReadLock readonly(_dataModelMutex);
+        // Access the resuable shader map for this context. Bypasses reallocation overhead.
+        ApplyVars& local = _apply[contextID];
 
-        for( ShaderMap::const_iterator i = _shaderMap.begin(); i != _shaderMap.end(); ++i )
+        local.accumShaderMap.clear();
+        local.accumAttribBindings.clear();
+        local.accumAttribAliases.clear();
+        local.keyVector.clear();
+    
+        // If we are inheriting, build the active shader map up to this point
+        // (but not including this VP).
+        if ( _inherit )
         {
-            if ( i->second.accept(state) )
+            accumulateShaders(
+                state,
+                _mask,
+                local.accumShaderMap,
+                local.accumAttribBindings,
+                local.accumAttribAliases,
+                acceptCallbacksVary);
+        }
+        
+        // Next, add the shaders from this VP.
+        {
+            //Threading::ScopedReadLock readonly(_dataModelMutex);
+            _dataModelMutex.lock();
+
+            for( ShaderMap::const_iterator i = _shaderMap.begin(); i != _shaderMap.end(); ++i )
             {
-                addToAccumulatedMap( accumShaderMap, i->first, i->second );
+                if ( i->data().accept(state) )
+                {
+                    addToAccumulatedMap( local.accumShaderMap, i->key(), i->data() );
+                }
             }
-        }
 
-        const AttribBindingList& abl = this->getAttribBindingList();
-        accumAttribBindings.insert( abl.begin(), abl.end() );
+            const AttribBindingList& abl = this->getAttribBindingList();
+            local.accumAttribBindings.insert( abl.begin(), abl.end() );
 
-#ifdef USE_ATTRIB_ALIASES
-        const AttribAliasMap& aliases = this->getAttribAliases();
-        accumAttribAliases.insert( aliases.begin(), aliases.end() );
-#endif
-    }
+    #ifdef USE_ATTRIB_ALIASES
+            const AttribAliasMap& aliases = this->getAttribAliases();
+            local.accumAttribAliases.insert( aliases.begin(), aliases.end() );
+    #endif
+            
+            _dataModelMutex.unlock();
+        }
 
+        // next, assemble a list of the shaders in the map so we can use it as our
+        // program cache key.
+        // (Note: at present, the "cache key" does not include any information on the vertex
+        // attribute bindings. Technically it should, but in practice this might not be an
+        // issue; it is unlikely one would have two identical shader programs with different
+        // bindings.)
+        for( ShaderMap::iterator i = local.accumShaderMap.begin(); i != local.accumShaderMap.end(); ++i )
+        {
+            local.keyVector.push_back( i->data()._shader.get() );
+        }
 
-    // next, assemble a list of the shaders in the map so we can use it as our
-    // program cache key.
-    // (Note: at present, the "cache key" does not include any information on the vertex
-    // attribute bindings. Technically it should, but in practice this might not be an
-    // issue; it is unlikely one would have two identical shader programs with different
-    // bindings.)
-    ShaderVector vec;
-    vec.reserve( accumShaderMap.size() );
-    for( ShaderMap::iterator i = accumShaderMap.begin(); i != accumShaderMap.end(); ++i )
-    {
-        ShaderEntry& entry = i->second;
-        if ( i->second.accept(state) )
+        // current frame number, for shader program expiry.
+        unsigned frameNumber = state.getFrameStamp() ? state.getFrameStamp()->getFrameNumber() : 0;
+
+        // look up the program:
         {
-            vec.push_back( entry._shader.get() );
+            _programCacheMutex.lock();
+            const_cast<VirtualProgram*>(this)->readProgramCache(local.keyVector, frameNumber, program);
+            _programCacheMutex.unlock();
         }
-    }
 
-    // current frame number, for shader program expiry.
-    unsigned frameNumber = state.getFrameStamp() ? state.getFrameStamp()->getFrameNumber() : 0;
+        // if not found, lock and build it:
+        if ( !program.valid() )
+        {
+            // build a new set of accumulated functions, to support the creation of main()
+            ShaderComp::FunctionLocationMap accumFunctions;
+            accumulateFunctions( state, accumFunctions );
 
-    // see if there's already a program associated with this list:
-    osg::ref_ptr<osg::Program> program;
+            // now double-check the program cache, and failing that, build the
+            // new shader Program.
+            {
+                Threading::ScopedMutexLock lock(_programCacheMutex);
 
-    // look up the program:
-    {
-        Threading::ScopedReadLock shared( _programCacheMutex );
-        const_cast<VirtualProgram*>(this)->readProgramCache(vec, frameNumber, program);
-    }
+                // double-check: look again to negate race conditions
+                const_cast<VirtualProgram*>(this)->readProgramCache(local.keyVector, frameNumber, program);
+                if ( !program.valid() )
+                {
+                    local.keyVector.clear();
 
-    // if not found, lock and build it:
-    if ( !program.valid() )
-    {
-        // build a new set of accumulated functions, to support the creation of main()
-        ShaderComp::FunctionLocationMap accumFunctions;
-        accumulateFunctions( state, accumFunctions );
+                    //OE_NOTICE << LC << "Building new Program for VP " << getName() << std::endl;
 
-        // now double-check the program cache, and failing that, build the
-        // new shader Program.
-        {
-            Threading::ScopedWriteLock exclusive( _programCacheMutex );
+                    program = buildProgram(
+                        getName(),
+                        state,
+                        accumFunctions,
+                        local.accumShaderMap, 
+                        local.accumAttribBindings, 
+                        local.accumAttribAliases, 
+                        _template.get(),
+                        local.keyVector);
 
-            // double-check: look again to negate race conditions
-            const_cast<VirtualProgram*>(this)->readProgramCache(vec, frameNumber, program);
-            if ( !program.valid() )
-            {
-                ShaderVector keyVector;
-
-                //OE_NOTICE << LC << "Building new Program for VP " << getName() << std::endl;
-
-                program = buildProgram(
-                    getName(),
-                    state,
-                    accumFunctions,
-                    accumShaderMap, 
-                    accumAttribBindings, 
-                    accumAttribAliases, 
-                    _template.get(),
-                    keyVector);
-
-                // global sharing.
-                Registry::programSharedRepo()->share( program );
-
-                // finally, put own new program in the cache.
-                ProgramEntry& pe = _programCache[keyVector];
-                pe._program = program.get();
-                pe._frameLastUsed = frameNumber;
-
-                // purge expired programs.
-                const_cast<VirtualProgram*>(this)->removeExpiredProgramsFromCache(state, frameNumber);
+                    if ( _logShaders && program.valid() )
+                    {
+                        std::stringstream buf;
+                        for (unsigned i=0; i < program->getNumShaders(); i++)
+                        {
+                            buf << program->getShader(i)->getShaderSource() << std::endl << std::endl;
+                        }
+
+                        if ( _logPath.length() > 0 )
+                        {
+                            std::fstream outStream;
+                            outStream.open(_logPath.c_str(), std::ios::out);
+                            if (outStream.fail())
+                            {
+                                OE_WARN << LC << "Unable to open " << _logPath << " for logging shaders." << std::endl;
+                            }
+                            else
+                            {
+                                outStream << buf.str();
+                                outStream.close();
+                            }
+                        }
+                        else
+                        {
+                          OE_NOTICE << LC << "Shader source: " << getName() << std::endl << "===============" << std::endl << buf.str() << std::endl << "===============" << std::endl;
+                        }
+                    }
+
+                    // global sharing.
+                    Registry::programSharedRepo()->share( program );
+
+                    // finally, put own new program in the cache.
+                    ProgramEntry& pe = _programCache[local.keyVector];
+                    pe._program = program.get();
+                    pe._frameLastUsed = frameNumber;
+
+                    // purge expired programs.
+                    const_cast<VirtualProgram*>(this)->removeExpiredProgramsFromCache(state, frameNumber);
+                }
             }
         }
     }
@@ -1052,16 +1232,31 @@ VirtualProgram::apply( osg::State& state ) const
     // finally, apply the program attribute.
     if ( program.valid() )
     {
-        const unsigned int contextID = state.getContextID();
-        const osg::GL2Extensions* extensions = osg::GL2Extensions::Get(contextID,true);
+#ifdef USE_STACK_MEMORY
+        // remember this program selection in case this VP is applied again
+        // during the same frame.
+        if (programRecalled == false        &&   // recalled a program? not necessary
+            getDataVariance() != DYNAMIC    &&   // DYNAMIC variance? might change during ST cull; no memory
+            !acceptCallbacksVary            &&   // accept callbacks vary per frame? cannot use memory
+            stack != 0L )
+        {
+            _vpStackMemory.remember(state, *stack, program.get());
+        }
+#endif // USE_STACK_MEMORY
 
-        osg::Program::PerContextProgram* pcp = program->getPCP( contextID );
+        osg::Program::PerContextProgram* pcp;
+
+#if OSG_VERSION_GREATER_OR_EQUAL(3,3,4)
+        pcp = program->getPCP( state );
+#else
+        pcp = program->getPCP( contextID );
+#endif
         bool useProgram = state.getLastAppliedProgramObject() != pcp;
 
 #ifdef DEBUG_APPLY_COUNTS
+        if ( state.getFrameStamp() && state.getFrameStamp()->getFrameNumber()%60==0)
         {
-            // debugging
-
+            // debugging            
             static int s_framenum = 0;
             static Threading::Mutex s_mutex;
             static std::map< const VirtualProgram*, std::pair<int,int> > s_counts;
@@ -1071,12 +1266,12 @@ VirtualProgram::apply( osg::State& state ) const
             int framenum = state.getFrameStamp()->getFrameNumber();
             if ( framenum > s_framenum )
             {
-                OE_NOTICE << LC << "Applies in last frame: " << std::endl;
+                OE_NOTICE << LC << "Use program in last frame: " << std::endl;
                 for(std::map<const VirtualProgram*,std::pair<int,int> >::iterator i = s_counts.begin(); i != s_counts.end(); ++i)
                 {
                     std::pair<int,int>& counts = i->second;
                     OE_NOTICE << LC << "  " 
-                        << i->first->getName() << " : " << counts.second << "/" << counts.first << std::endl;
+                        << i->first->getName() << " : attemped=" << counts.first << ", applied=" << counts.second << "\n";
                 }
                 s_framenum = framenum;
                 s_counts.clear();
@@ -1103,9 +1298,10 @@ VirtualProgram::apply( osg::State& state ) const
             else
             {
                 // program not usable, fallback to fixed function.
+                const osg::GL2Extensions* extensions = osg::GL2Extensions::Get(contextID, true);
                 extensions->glUseProgram( 0 );
                 state.setLastAppliedProgramObject(0);
-                OE_WARN << LC << "Program link failure!" << std::endl;
+                OE_DEBUG << LC << "Program link failure!" << std::endl;
             }
         }
 
@@ -1123,7 +1319,7 @@ VirtualProgram::apply( osg::State& state ) const
 void
 VirtualProgram::removeExpiredProgramsFromCache(osg::State& state, unsigned frameNumber)
 {
-    if ( frameNumber > 0 )
+    if ( frameNumber > 0 && _programCache.size() > MAX_PROGRAM_CACHE_SIZE )
     {
         // ASSUME a mutex lock on the cache.
         for(ProgramMap::iterator k=_programCache.begin(); k!=_programCache.end(); )
@@ -1150,38 +1346,42 @@ VirtualProgram::readProgramCache(const ShaderVector& vec, unsigned frameNumber,
     ProgramMap::iterator p = _programCache.find( vec );
     if ( p != _programCache.end() )
     {
-        //OE_NOTICE << "found. fn=" << frameNumber << ", flu=" << p->second._frameLastUsed << std::endl;
-
-        // check for expiry..
-        if ( frameNumber == 0 || (frameNumber - p->second._frameLastUsed <= 2) )
-        {
-            // update as current..
-            p->second._frameLastUsed = frameNumber;
-            program = p->second._program.get();
-        }
-        else
-        {
-            // remove it; it's too old.
-            _programCache.erase( p );
-        }
+        // update as current..
+        p->second._frameLastUsed = frameNumber;
+        program = p->second._program.get();
     }
     return program.valid();
 }
 
+
+bool
+VirtualProgram::checkSharing()
+{
+  if ( ::getenv("OSGEARTH_SHARED_VP_WARNING") && getNumParents() > 1)
+  {
+      OE_WARN << LC << "Modified VirtualProgram may be shared." << std::endl;
+      return true;
+  }
+
+  return false;
+}
+
 void
 VirtualProgram::getFunctions( FunctionLocationMap& out ) const
 {
     // make a safe copy of the functions map.
-    Threading::ScopedReadLock shared( _dataModelMutex );
+    _dataModelMutex.lock();
     out = _functions;
+    _dataModelMutex.unlock();
 }
 
 void
 VirtualProgram::getShaderMap( ShaderMap& out ) const
 {
     // make a safe copy of the functions map.
-    Threading::ScopedReadLock shared( _dataModelMutex );
+    _dataModelMutex.lock();
     out = _shaderMap;
+    _dataModelMutex.unlock();
 }
 
 void
@@ -1192,7 +1392,7 @@ VirtualProgram::accumulateFunctions(const osg::State&                state,
     // the user functions (including those in this program).
     if ( _inherit )
     {
-        const StateHack::AttributeVec* av = StateHack::GetAttributeVec( state, this );
+        const AttrStack* av = StateEx::getProgramStack(state);
         if ( av && av->size() > 0 )
         {
             // find the closest VP that doesn't inherit:
@@ -1242,7 +1442,8 @@ VirtualProgram::accumulateFunctions(const osg::State&                state,
 
     // add the local ones too:
     {
-        Threading::ScopedReadLock readonly( _dataModelMutex );
+        //Threading::ScopedReadLock readonly( _dataModelMutex );
+        _dataModelMutex.lock();
 
         for( FunctionLocationMap::const_iterator j = _functions.begin(); j != _functions.end(); ++j )
         {
@@ -1266,6 +1467,8 @@ VirtualProgram::accumulateFunctions(const osg::State&                state,
                 }
             }
         }
+
+        _dataModelMutex.unlock();
     }
 }
 
@@ -1276,9 +1479,12 @@ VirtualProgram::accumulateShaders(const osg::State&  state,
                                   unsigned           mask,
                                   ShaderMap&         accumShaderMap,
                                   AttribBindingList& accumAttribBindings,
-                                  AttribAliasMap&    accumAttribAliases)
+                                  AttribAliasMap&    accumAttribAliases,
+                                  bool&              acceptCallbacksVary)
 {
-    const StateHack::AttributeVec* av = StateHack::GetAttributeVec(state, VirtualProgram::SA_TYPE);
+    acceptCallbacksVary = false;
+
+    const AttrStack* av = StateEx::getProgramStack(state);
     if ( av && av->size() > 0 )
     {
         // find the deepest VP that doesn't inherit:
@@ -1296,17 +1502,14 @@ VirtualProgram::accumulateShaders(const osg::State&  state,
             const VirtualProgram* vp = dynamic_cast<const VirtualProgram*>( (*av)[i].first );
             if ( vp && (vp->_mask && mask) )
             {
-                ShaderMap vpShaderMap;
-                vp->getShaderMap( vpShaderMap );
-
-                for( ShaderMap::const_iterator i = vpShaderMap.begin(); i != vpShaderMap.end(); ++i )
+                if (vp->getAcceptCallbacksVaryPerFrame())
                 {
-                    if ( i->second.accept(state) )
-                    {
-                        addToAccumulatedMap( accumShaderMap, i->first, i->second );
-                    }
+                    acceptCallbacksVary = true;
                 }
 
+                // thread-safely adds the other vp's shaders to our accumulation map
+                vp->addShadersToAccumulationMap( accumShaderMap, state );
+
                 const AttribBindingList& abl = vp->getAttribBindingList();
                 accumAttribBindings.insert( abl.begin(), abl.end() );
 
@@ -1319,6 +1522,24 @@ VirtualProgram::accumulateShaders(const osg::State&  state,
     }
 }
 
+void
+VirtualProgram::addShadersToAccumulationMap(VirtualProgram::ShaderMap& accumMap,
+                                            const osg::State&          state) const
+{
+    //Threading::ScopedReadLock shared( _dataModelMutex );
+    _dataModelMutex.lock();
+
+    for( ShaderMap::const_iterator i = _shaderMap.begin(); i != _shaderMap.end(); ++i )
+    {
+        if ( i->data().accept(state) )
+        {
+            addToAccumulatedMap(accumMap, i->key(), i->data());
+        }
+    }
+
+    _dataModelMutex.unlock();
+}
+
 
 void
 VirtualProgram::getShaders(const osg::State&                        state,
@@ -1327,9 +1548,10 @@ VirtualProgram::getShaders(const osg::State&                        state,
     ShaderMap         shaders;
     AttribBindingList bindings;
     AttribAliasMap    aliases;
+    bool              acceptCallbacksVary;
 
     // build the collection:
-    accumulateShaders(state, ~0, shaders, bindings, aliases);
+    accumulateShaders(state, ~0, shaders, bindings, aliases, acceptCallbacksVary);
 
     // pre-allocate space:
     output.reserve( shaders.size() );
@@ -1337,6 +1559,27 @@ VirtualProgram::getShaders(const osg::State&                        state,
     // copy to output.
     for(ShaderMap::iterator i = shaders.begin(); i != shaders.end(); ++i)
     {
-        output.push_back( i->second._shader.get() );
+        output.push_back( i->data()._shader.get() );
     }
 }
+
+void VirtualProgram::setShaderLogging( bool log )
+{
+    setShaderLogging(log, "");
+}
+
+void VirtualProgram::setShaderLogging( bool log, const std::string& filepath )
+{
+    _logShaders = log;
+    _logPath = filepath;
+}
+
+bool VirtualProgram::getAcceptCallbacksVaryPerFrame() const
+{
+    return _acceptCallbacksVaryPerFrame;
+}
+
+void VirtualProgram::setAcceptCallbacksVaryPerFrame(bool acceptCallbacksVaryPerFrame)
+{
+    _acceptCallbacksVaryPerFrame = acceptCallbacksVaryPerFrame;
+}
diff --git a/src/osgEarth/XmlUtils b/src/osgEarth/XmlUtils
index 6175df1..0ae38c8 100644
--- a/src/osgEarth/XmlUtils
+++ b/src/osgEarth/XmlUtils
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -79,6 +79,13 @@ namespace osgEarth
         XmlElement* getSubElement( const std::string& name ) const;
         
         XmlNodeList getSubElements( const std::string& name ) const;
+
+        /**
+         * Finds the first element matching the name. This will match the
+         * current element (this), or the first matching element in this
+         * nodes subhierarchy.
+         */
+        const XmlElement* findElement(const std::string& name) const;
         
         std::string getText() const;
         
diff --git a/src/osgEarth/XmlUtils.cpp b/src/osgEarth/XmlUtils.cpp
index a1f507c..86e2b6f 100644
--- a/src/osgEarth/XmlUtils.cpp
+++ b/src/osgEarth/XmlUtils.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -58,14 +58,12 @@ XmlElement::XmlElement( const Config& conf )
 
     for( ConfigSet::const_iterator j = conf.children().begin(); j != conf.children().end(); j++ )
     {
-        if ( j->isSimple() )
-        {
-            attrs[j->key()] = j->value();
-        }
-        else if ( j->children().size() > 0 )
-        {
-            children.push_back( new XmlElement(*j) );
-        }
+        //if ( j->isSimple() )
+        //{
+        //    attrs[j->key()] = j->value();
+        //}
+
+        children.push_back( new XmlElement(*j) );
     }
 }
 
@@ -127,6 +125,47 @@ XmlElement::getSubElement( const std::string& name ) const
     return NULL;
 }
 
+const XmlElement*
+XmlElement::findElement(const std::string& name) const
+{
+    const XmlElement* result = 0L;
+
+    if ( this->getName() == name )
+    {
+        result = this;
+    }
+    else
+    {
+        // first check the subelements (breadth first search)
+        for(XmlNodeList::const_iterator i = getChildren().begin();
+            i != getChildren().end() && result == 0L;
+            i++ )
+        {
+            if ( i->get()->isElement() )
+            {
+                XmlElement* e = (XmlElement*)i->get();
+                if (osgEarth::ciEquals(name, e->getName()))
+                {
+                    result = e;
+                }
+            }
+        }
+
+        // not found? traverse the subelements.
+        if ( result == 0L )
+        {
+            for(XmlNodeList::const_iterator i = getChildren().begin();
+                i != getChildren().end() && result == 0L;
+                i++ )
+            {
+                XmlElement* e = (XmlElement*)i->get();
+                result = e->findElement( name );
+            }
+        }
+    }
+
+    return result;
+}
 
 std::string
 XmlElement::getText() const
diff --git a/src/osgEarth/optional b/src/osgEarth/optional
index 97a9b63..43b86d0 100644
--- a/src/osgEarth/optional
+++ b/src/osgEarth/optional
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/osgEarthAnnotation/AnnotationData b/src/osgEarthAnnotation/AnnotationData
index 98394f6..a168785 100644
--- a/src/osgEarthAnnotation/AnnotationData
+++ b/src/osgEarthAnnotation/AnnotationData
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/osgEarthAnnotation/AnnotationData.cpp b/src/osgEarthAnnotation/AnnotationData.cpp
index 63c2dcf..685f85f 100644
--- a/src/osgEarthAnnotation/AnnotationData.cpp
+++ b/src/osgEarthAnnotation/AnnotationData.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/osgEarthAnnotation/AnnotationEditing b/src/osgEarthAnnotation/AnnotationEditing
index ff6deef..d324089 100644
--- a/src/osgEarthAnnotation/AnnotationEditing
+++ b/src/osgEarthAnnotation/AnnotationEditing
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -25,15 +25,23 @@
 #include <osgEarthAnnotation/CircleNode>
 #include <osgEarthAnnotation/EllipseNode>
 #include <osgEarthAnnotation/RectangleNode>
-#include <osgEarth/Draggers>
+#include <osgEarthAnnotation/Draggers>
 #include <osgEarth/MapNode>
 
-namespace osgEarth { namespace Annotation {
+namespace osgEarth { namespace Annotation
+{
+    class OSGEARTHANNO_EXPORT AnnotationEditor : public osg::Group
+    {
+    protected:
+        AnnotationEditor();
+
+        virtual ~AnnotationEditor() { }
+    };
 
      /**
      * An editor node that allows you to move the position of LocalizedNode annotations    
      */
-    class OSGEARTHANNO_EXPORT LocalizedNodeEditor : public osg::Group
+    class OSGEARTHANNO_EXPORT LocalizedNodeEditor : public AnnotationEditor
     {
     public:
         /**
diff --git a/src/osgEarthAnnotation/AnnotationEditing.cpp b/src/osgEarthAnnotation/AnnotationEditing.cpp
index f2a70f8..bc59344 100644
--- a/src/osgEarthAnnotation/AnnotationEditing.cpp
+++ b/src/osgEarthAnnotation/AnnotationEditing.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
@@ -45,6 +48,16 @@ public:
 };
 
 /**********************************************************************/
+AnnotationEditor::AnnotationEditor() :
+osg::Group()
+{
+    // editor geometry should always be visible.
+    osg::StateSet* stateSet = this->getOrCreateStateSet();
+    stateSet->setMode(GL_DEPTH_TEST, 0);
+    stateSet->setRenderBinDetails(99, "RenderBin");
+}
+
+/**********************************************************************/
 LocalizedNodeEditor::LocalizedNodeEditor(LocalizedNode* node):
 _node( node )
 {
@@ -208,6 +221,7 @@ public:
 
           //Figure out the distance between the center of the circle and this new location
           GeoPoint center = _node->getPosition();
+
           double distance = GeoMath::distance(osg::DegreesToRadians( center.y() ), osg::DegreesToRadians( center.x() ), 
                                               osg::DegreesToRadians( position.y() ), osg::DegreesToRadians( position.x() ),
                                               em->getRadiusEquator());
@@ -219,13 +233,13 @@ public:
           //Compute the new angular rotation based on how they moved the point
           if (_major)
           {
-              _node->setRotationAngle( Angular( -bearing, Units::RADIANS ) );
-              _node->setRadiusMajor( Linear(distance, Units::METERS ) );
+              _node->setRotationAngle( Angle( bearing-osg::PI_2, Units::RADIANS ) );
+              _node->setRadiusMajor( Distance(distance, Units::METERS ) );
           }
-          else
+          else // minor
           {
-              _node->setRotationAngle( Angular( osg::PI_2 - bearing, Units::RADIANS ) );
-              _node->setRadiusMinor( Linear(distance, Units::METERS ) );
+              _node->setRotationAngle( Angle( bearing, Units::RADIANS ) );
+              _node->setRadiusMinor( Distance(distance, Units::METERS ) );
           }
           _editor->updateDraggers();
      }
@@ -281,15 +295,30 @@ EllipseNodeEditor::updateDraggers()
 
         double rotation = ellipse->getRotationAngle().as( Units::RADIANS );
 
-        double lat, lon;
-        GeoMath::destination(osg::DegreesToRadians( location.y() ), osg::DegreesToRadians( location.x() ), osg::PI_2 - rotation, minorR, lat, lon, em->getRadiusEquator());        
+        double latRad, lonRad;
 
-        GeoPoint minorLocation(location.getSRS(), osg::RadiansToDegrees( lon ), osg::RadiansToDegrees( lat ));
+        // minor dragger: end of the rotated +Y axis:
+        GeoMath::destination(
+            osg::DegreesToRadians( location.y() ), osg::DegreesToRadians( location.x() ), 
+            rotation, 
+            minorR, 
+            latRad, lonRad, 
+            em->getRadiusEquator());        
+
+        GeoPoint minorLocation(location.getSRS(), osg::RadiansToDegrees( lonRad ), osg::RadiansToDegrees( latRad ));
         minorLocation.z() = 0;       
-        _minorDragger->setPosition( minorLocation, false);
+        _minorDragger->setPosition( minorLocation, false );
 
-        GeoMath::destination(osg::DegreesToRadians( location.y() ), osg::DegreesToRadians( location.x() ), -rotation, majorR, lat, lon, em->getRadiusEquator());                
-        GeoPoint majorLocation(location.getSRS(), osg::RadiansToDegrees( lon ), osg::RadiansToDegrees( lat ));
+        // major dragger: end of the rotated +X axis
+        GeoMath::destination(
+            osg::DegreesToRadians( location.y() ), 
+            osg::DegreesToRadians( location.x() ), 
+            rotation + osg::PI_2, 
+            majorR, 
+            latRad, lonRad, 
+            em->getRadiusEquator());                
+
+        GeoPoint majorLocation(location.getSRS(), osg::RadiansToDegrees( lonRad ), osg::RadiansToDegrees( latRad ));
         majorLocation.z() = 0;
         _majorDragger->setPosition( majorLocation, false);
     }
diff --git a/src/osgEarthAnnotation/AnnotationNode b/src/osgEarthAnnotation/AnnotationNode
index 4f21995..d2aab2b 100644
--- a/src/osgEarthAnnotation/AnnotationNode
+++ b/src/osgEarthAnnotation/AnnotationNode
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
@@ -117,6 +120,11 @@ namespace osgEarth { namespace Annotation
          */
         bool hasDecoration( const std::string& name ) const;
 
+        /**
+         * Text associated with this annotation; might be the name, might be actual text
+         */
+        virtual const std::string& getText() const { return this->getName(); }
+
     public: // MapNodeObserver
 
         virtual void setMapNode( MapNode* mapNode );
diff --git a/src/osgEarthAnnotation/AnnotationNode.cpp b/src/osgEarthAnnotation/AnnotationNode.cpp
index 055158d..d5f593f 100644
--- a/src/osgEarthAnnotation/AnnotationNode.cpp
+++ b/src/osgEarthAnnotation/AnnotationNode.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
@@ -67,6 +70,8 @@ _activeDs   ( 0L )
 
     // always blend.
     this->getOrCreateStateSet()->setMode( GL_BLEND, osg::StateAttribute::ON );
+    // always draw after the terrain.
+    this->getOrCreateStateSet()->setRenderBinDetails( 1, "DepthSortedBin" );
 }
 
 AnnotationNode::AnnotationNode(MapNode* mapNode, const Config& conf) :
@@ -76,6 +81,8 @@ _autoclamp  ( false ),
 _depthAdj   ( false ),
 _activeDs   ( 0L )
 {
+    this->setName( conf.value("name") );
+
     if ( conf.hasValue("lighting") )
     {
         bool lighting = conf.value<bool>("lighting", false);
@@ -98,7 +105,9 @@ _activeDs   ( 0L )
         // blend by default.
         this->getOrCreateStateSet()->setMode( GL_BLEND, osg::StateAttribute::ON );
     }
-
+    
+    // always draw after the terrain.
+    this->getOrCreateStateSet()->setRenderBinDetails( 1, "DepthSortedBin" );
 }
 
 AnnotationNode::~AnnotationNode()
@@ -406,7 +415,7 @@ AnnotationNode::applyGeneralSymbology(const Style& style)
                 (render->lighting() == true? osg::StateAttribute::ON : osg::StateAttribute::OFF) | osg::StateAttribute::OVERRIDE );
         }
 
-        if ( render->depthOffset().isSet() ) // && !_depthAdj )
+        if ( render->depthOffset().isSet() )
         {
             _doAdapter.setDepthOffsetOptions( *render->depthOffset() );
             setDepthAdjustment( true );
@@ -419,11 +428,23 @@ AnnotationNode::applyGeneralSymbology(const Style& style)
                 (render->backfaceCulling() == true? osg::StateAttribute::ON : osg::StateAttribute::OFF) | osg::StateAttribute::OVERRIDE );
         }
 
+#ifndef OSG_GLES2_AVAILABLE
         if ( render->clipPlane().isSet() )
         {
             GLenum mode = GL_CLIP_PLANE0 + render->clipPlane().value();
             getOrCreateStateSet()->setMode(mode, 1);
         }
+#endif
+
+        if ( render->order().isSet() || render->renderBin().isSet() )
+        {
+            osg::StateSet* ss = getOrCreateStateSet();
+            int binNumber = render->order().isSet() ? (int)render->order()->eval() : ss->getBinNumber();
+            std::string binName =
+                render->renderBin().isSet() ? render->renderBin().get() :
+                ss->useRenderBinDetails() ? ss->getBinName() : "RenderBin";
+            ss->setRenderBinDetails(binNumber, binName);
+        }
 
         if ( render->minAlpha().isSet() )
         {
diff --git a/src/osgEarthAnnotation/AnnotationRegistry b/src/osgEarthAnnotation/AnnotationRegistry
index a48ba08..1e72670 100644
--- a/src/osgEarthAnnotation/AnnotationRegistry
+++ b/src/osgEarthAnnotation/AnnotationRegistry
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/osgEarthAnnotation/AnnotationRegistry.cpp b/src/osgEarthAnnotation/AnnotationRegistry.cpp
index b04b6ad..6f29388 100644
--- a/src/osgEarthAnnotation/AnnotationRegistry.cpp
+++ b/src/osgEarthAnnotation/AnnotationRegistry.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,16 +8,23 @@
 * 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.
+* 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/AnnotationRegistry>
 #include <osgEarth/Decluttering>
+#include <osgEarth/Registry>
+#include <osgEarth/ObjectIndex>
+
+#define LC "[AnnotationRegistry] "
 
 using namespace osgEarth;
 using namespace osgEarth::Annotation;
@@ -146,6 +153,8 @@ AnnotationRegistry::createOne(MapNode*              mapNode,
                 Decluttering::setEnabled( anno->getOrCreateStateSet(), true );
             }
 
+            Registry::objectIndex()->tagNode( anno, anno );
+
             return anno;
         }
     }
diff --git a/src/osgEarthAnnotation/AnnotationSettings b/src/osgEarthAnnotation/AnnotationSettings
index 1c5d5ad..efc7b09 100644
--- a/src/osgEarthAnnotation/AnnotationSettings
+++ b/src/osgEarthAnnotation/AnnotationSettings
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/osgEarthAnnotation/AnnotationSettings.cpp b/src/osgEarthAnnotation/AnnotationSettings.cpp
index 5245763..8f7ee6d 100644
--- a/src/osgEarthAnnotation/AnnotationSettings.cpp
+++ b/src/osgEarthAnnotation/AnnotationSettings.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/osgEarthAnnotation/AnnotationUtils b/src/osgEarthAnnotation/AnnotationUtils
index e1a3c35..6bf70f2 100644
--- a/src/osgEarthAnnotation/AnnotationUtils
+++ b/src/osgEarthAnnotation/AnnotationUtils
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
@@ -27,6 +30,7 @@
 #include <osg/Drawable>
 #include <osg/Geometry>
 #include <osgText/TextBase>
+#include <osgUtil/CullVisitor>
 
 namespace osgEarth { namespace Annotation
 {
@@ -101,7 +105,8 @@ namespace osgEarth { namespace Annotation
          */
         struct OrthoNodeAutoTransform : public osg::AutoTransform
         {
-            void acceptCullNoTraverse( osg::CullStack* cs );
+            /** override */
+            virtual void accept(osg::NodeVisitor& nv);
 
             bool okToIntersect() const { return !_firstTimeToInitEyePoint; }
         };
@@ -154,6 +159,22 @@ namespace osgEarth { namespace Annotation
         AnnotationUtils() { }
     };
 
+    
+    /**
+     * A bounding sphere calculator that forces the center point
+     * to (0,0,0) and adjusts the radius accordingly. This forces the bounding
+     * center to the "control point" of the annotation - useful for culling.
+     */
+    class ControlPointCallback : public osg::Node::ComputeBoundingSphereCallback
+    {
+    public:
+        osg::BoundingSphere computeBound(const osg::Node& node) const
+        {
+            osg::BoundingSphere nodebs = node.computeBound();
+            return osg::BoundingSphere(osg::Vec3(0,0,0), nodebs.center().length() + nodebs.radius());
+        }
+    };
+
 } } // namespace osgEarth::Annotation
 
 #endif //OSGEARTH_ANNOTATION_ANNOTATION_UTILS_H
diff --git a/src/osgEarthAnnotation/AnnotationUtils.cpp b/src/osgEarthAnnotation/AnnotationUtils.cpp
index 213fcff..90ddee8 100755
--- a/src/osgEarthAnnotation/AnnotationUtils.cpp
+++ b/src/osgEarthAnnotation/AnnotationUtils.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
@@ -24,7 +27,7 @@
 #include <osgEarth/Registry>
 #include <osgEarth/VirtualProgram>
 #include <osgEarth/Capabilities>
-
+#include <osgEarth/CullingUtils>
 
 #include <osgText/Text>
 #include <osg/Depth>
@@ -270,120 +273,147 @@ AnnotationUtils::createHighlightUniform()
 // Transform::accept since we don't want to traverse the child graph from
 // this call.
 void
-AnnotationUtils::OrthoNodeAutoTransform::acceptCullNoTraverse( osg::CullStack* cs )
+AnnotationUtils::OrthoNodeAutoTransform::accept(osg::NodeVisitor& nv)
 {
-    osg::Viewport::value_type width = _previousWidth;
-    osg::Viewport::value_type height = _previousHeight;
-
-    osg::Viewport* viewport = cs->getViewport();
-    if (viewport)
+    if ( nv.validNodeMask(*this) )
     {
-        width = viewport->width();
-        height = viewport->height();
-    }
+        if ( nv.getVisitorType() == nv.CULL_VISITOR )
+        {
+            osgUtil::CullVisitor* cv = Culling::asCullVisitor(nv);
 
-    osg::Vec3d eyePoint = cs->getEyeLocal(); 
-    osg::Vec3d localUp = cs->getUpLocal(); 
-    osg::Vec3d position = getPosition();
+            // check for a reference camera for size scaling.
+            osg::Vec3f refCamScale(1,1,1);
+            osg::Camera* cam = cv->getCurrentCamera();
+            if ( cam && cam->isRenderToTextureCamera() )
+            {
+                const osg::Viewport* vp = cam->getViewport();
+                if ( vp )
+                {
+                    osg::Camera* refCam = dynamic_cast<osg::Camera*>(cam->getUserData());
+                    if ( refCam && refCam->getViewport() )
+                    {
+                        refCamScale.set(
+                            vp->width() / refCam->getViewport()->width(),
+                            vp->height() / refCam->getViewport()->height(),
+                            1.0f );
+                    }
+                }
+            }
 
-    const osg::Matrix& projection = *(cs->getProjectionMatrix());
+            osg::Viewport::value_type width = _previousWidth;
+            osg::Viewport::value_type height = _previousHeight;
 
-    bool doUpdate = _firstTimeToInitEyePoint;
-    if (!_firstTimeToInitEyePoint)
-    {
-        osg::Vec3d dv = _previousEyePoint-eyePoint;
-        if (dv.length2()>getAutoUpdateEyeMovementTolerance()*(eyePoint-getPosition()).length2())
-        {
-            doUpdate = true;
-        }
-        osg::Vec3d dupv = _previousLocalUp-localUp;
-        // rotating the camera only affects ROTATE_TO_*
-        if (_autoRotateMode &&
-            dupv.length2()>getAutoUpdateEyeMovementTolerance())
-        {
-            doUpdate = true;
-        }
-        else if (width!=_previousWidth || height!=_previousHeight)
-        {
-            doUpdate = true;
-        }
-        else if (projection != _previousProjection) 
-        {
-            doUpdate = true;
-        }                
-        else if (position != _previousPosition) 
-        { 
-            doUpdate = true; 
-        } 
-    }
-    _firstTimeToInitEyePoint = false;
+            osg::Viewport* viewport = cv->getViewport();
+            if (viewport)
+            {
+                width = viewport->width();
+                height = viewport->height();
+            }
 
-    if (doUpdate)
-    {            
-        if (getAutoScaleToScreen())
-        {
-            double size = 1.0/cs->pixelSize(getPosition(),0.48f);
+            osg::Vec3d eyePoint = cv->getEyeLocal(); 
+            osg::Vec3d localUp = cv->getUpLocal(); 
+            osg::Vec3d position = getPosition();
+
+            const osg::Matrix& projection = *(cv->getProjectionMatrix());
 
-            if (_autoScaleTransitionWidthRatio>0.0)
+            bool doUpdate = _firstTimeToInitEyePoint;
+            if (!_firstTimeToInitEyePoint)
             {
-                if (_minimumScale>0.0)
+                osg::Vec3d dv = _previousEyePoint-eyePoint;
+                if (dv.length2()>getAutoUpdateEyeMovementTolerance()*(eyePoint-getPosition()).length2())
                 {
-                    double j = _minimumScale;
-                    double i = (_maximumScale<DBL_MAX) ? 
-                        _minimumScale+(_maximumScale-_minimumScale)*_autoScaleTransitionWidthRatio :
-                    _minimumScale*(1.0+_autoScaleTransitionWidthRatio);
-                    double c = 1.0/(4.0*(i-j));
-                    double b = 1.0 - 2.0*c*i;
-                    double a = j + b*b / (4.0*c);
-                    double k = -b / (2.0*c);
-
-                    if (size<k) size = _minimumScale;
-                    else if (size<i) size = a + b*size + c*(size*size);
+                    doUpdate = true;
                 }
-
-                if (_maximumScale<DBL_MAX)
+                osg::Vec3d dupv = _previousLocalUp-localUp;
+                // rotating the camera only affects ROTATE_TO_*
+                if (_autoRotateMode &&
+                    dupv.length2()>getAutoUpdateEyeMovementTolerance())
+                {
+                    doUpdate = true;
+                }
+                else if (width!=_previousWidth || height!=_previousHeight)
                 {
-                    double n = _maximumScale;
-                    double m = (_minimumScale>0.0) ?
-                        _maximumScale+(_minimumScale-_maximumScale)*_autoScaleTransitionWidthRatio :
-                    _maximumScale*(1.0-_autoScaleTransitionWidthRatio);
-                    double c = 1.0 / (4.0*(m-n));
-                    double b = 1.0 - 2.0*c*m;
-                    double a = n + b*b/(4.0*c);
-                    double p = -b / (2.0*c);
-
-                    if (size>p) size = _maximumScale;
-                    else if (size>m) size = a + b*size + c*(size*size);
-                }        
+                    doUpdate = true;
+                }
+                else if (projection != _previousProjection) 
+                {
+                    doUpdate = true;
+                }                
+                else if (position != _previousPosition) 
+                { 
+                    doUpdate = true; 
+                } 
             }
+            _firstTimeToInitEyePoint = false;
 
-            setScale(size);
-        }
+            if (doUpdate)
+            {            
+                if (getAutoScaleToScreen())
+                {
+                    double size = 1.0/cv->pixelSize(getPosition(),0.48f);
+
+                    if (_autoScaleTransitionWidthRatio>0.0)
+                    {
+                        if (_minimumScale>0.0)
+                        {
+                            double j = _minimumScale;
+                            double i = (_maximumScale<DBL_MAX) ? 
+                                _minimumScale+(_maximumScale-_minimumScale)*_autoScaleTransitionWidthRatio :
+                            _minimumScale*(1.0+_autoScaleTransitionWidthRatio);
+                            double c = 1.0/(4.0*(i-j));
+                            double b = 1.0 - 2.0*c*i;
+                            double a = j + b*b / (4.0*c);
+                            double k = -b / (2.0*c);
+
+                            if (size<k) size = _minimumScale;
+                            else if (size<i) size = a + b*size + c*(size*size);
+                        }
+
+                        if (_maximumScale<DBL_MAX)
+                        {
+                            double n = _maximumScale;
+                            double m = (_minimumScale>0.0) ?
+                                _maximumScale+(_minimumScale-_maximumScale)*_autoScaleTransitionWidthRatio :
+                            _maximumScale*(1.0-_autoScaleTransitionWidthRatio);
+                            double c = 1.0 / (4.0*(m-n));
+                            double b = 1.0 - 2.0*c*m;
+                            double a = n + b*b/(4.0*c);
+                            double p = -b / (2.0*c);
+
+                            if (size>p) size = _maximumScale;
+                            else if (size>m) size = a + b*size + c*(size*size);
+                        }        
+                    }
+
+                    setScale(size * refCamScale.x());
+                }
 
-        if (_autoRotateMode==ROTATE_TO_SCREEN)
-        {
-            osg::Vec3d translation;
-            osg::Quat rotation;
-            osg::Vec3d scale;
-            osg::Quat so;
+                if (_autoRotateMode==ROTATE_TO_SCREEN)
+                {
+                    osg::Vec3d translation;
+                    osg::Quat rotation;
+                    osg::Vec3d scale;
+                    osg::Quat so;
 
-            cs->getModelViewMatrix()->decompose( translation, rotation, scale, so );
+                    cv->getModelViewMatrix()->decompose( translation, rotation, scale, so );
 
-            setRotation(rotation.inverse());
-        }
-        // GW: removed other unused auto-rotate modes
+                    setRotation(rotation.inverse());
+                }
+                // GW: removed other unused auto-rotate modes
 
-        _previousEyePoint = eyePoint;
-        _previousLocalUp = localUp;
-        _previousWidth = width;
-        _previousHeight = height;
-        _previousProjection = projection;
-        _previousPosition = position;
+                _previousEyePoint = eyePoint;
+                _previousLocalUp = localUp;
+                _previousWidth = width;
+                _previousHeight = height;
+                _previousProjection = projection;
+                _previousPosition = position;
 
-        _matrixDirty = true;
-    }
+                _matrixDirty = true;
+            }
+        }
 
-    // GW: the stock AutoTransform calls Transform::accept here; we do NOT
+        osg::Transform::accept( nv );
+    }
 }
 
 osg::Node* 
diff --git a/src/osgEarthAnnotation/CMakeLists.txt b/src/osgEarthAnnotation/CMakeLists.txt
index 533f639..2b257c3 100644
--- a/src/osgEarthAnnotation/CMakeLists.txt
+++ b/src/osgEarthAnnotation/CMakeLists.txt
@@ -18,6 +18,7 @@ set(LIB_PUBLIC_HEADERS
     CircleNode
     Common
     Decoration
+    Draggers
     EllipseNode
     Export
     FeatureNode
@@ -45,6 +46,7 @@ set(LIB_COMMON_FILES
     AnnotationUtils.cpp
     CircleNode.cpp
     Decoration.cpp
+    Draggers.cpp
     EllipseNode.cpp
     FeatureNode.cpp
     FeatureEditing.cpp
diff --git a/src/osgEarthAnnotation/CircleNode b/src/osgEarthAnnotation/CircleNode
index 5613516..7bcd31d 100644
--- a/src/osgEarthAnnotation/CircleNode
+++ b/src/osgEarthAnnotation/CircleNode
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/osgEarthAnnotation/CircleNode.cpp b/src/osgEarthAnnotation/CircleNode.cpp
index 8487c79..f5401be 100644
--- a/src/osgEarthAnnotation/CircleNode.cpp
+++ b/src/osgEarthAnnotation/CircleNode.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/osgEarthAnnotation/Common b/src/osgEarthAnnotation/Common
index 43f7e5c..a876573 100644
--- a/src/osgEarthAnnotation/Common
+++ b/src/osgEarthAnnotation/Common
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthAnnotation/Decoration b/src/osgEarthAnnotation/Decoration
index ef10947..e6b4a44 100644
--- a/src/osgEarthAnnotation/Decoration
+++ b/src/osgEarthAnnotation/Decoration
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/osgEarthAnnotation/Decoration.cpp b/src/osgEarthAnnotation/Decoration.cpp
index c1ce9ff..e923685 100644
--- a/src/osgEarthAnnotation/Decoration.cpp
+++ b/src/osgEarthAnnotation/Decoration.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/osgEarthAnnotation/Draggers b/src/osgEarthAnnotation/Draggers
new file mode 100644
index 0000000..53d90cc
--- /dev/null
+++ b/src/osgEarthAnnotation/Draggers
@@ -0,0 +1,175 @@
+/* -*-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_DRAGGERS_H
+#define OSGEARTH_DRAGGERS_H
+
+#include <osgEarthAnnotation/Common>
+#include <osgEarth/GeoData>
+#include <osgEarth/TileKey>
+#include <osgEarth/MapNodeObserver>
+#include <osg/MatrixTransform>
+#include <osg/ShapeDrawable>
+#include <osgGA/GUIEventHandler>
+#include <osgManipulator/Projector>
+#include <osgEarth/Terrain>
+
+namespace osgEarth {
+    class MapNode;
+    class Terrain;
+}
+
+namespace osgEarth { namespace Annotation
+{
+    class OSGEARTHANNO_EXPORT Dragger : public osg::MatrixTransform, public MapNodeObserver
+    {
+    public:
+        /**
+        * Callback that is fired when the position changes
+        */
+        struct PositionChangedCallback : public osg::Referenced
+        {
+        public:
+            virtual void onPositionChanged(const Dragger* sender, const osgEarth::GeoPoint& position) {};
+            virtual ~PositionChangedCallback() { }
+        };
+
+        typedef std::list< osg::ref_ptr<PositionChangedCallback> > PositionChangedCallbackList;
+
+        enum DragMode
+        {
+          DRAGMODE_HORIZONTAL,
+          DRAGMODE_VERTICAL
+        };
+
+        Dragger( MapNode* mapNode, int modKeyMask=0, const DragMode& defaultMode=DRAGMODE_HORIZONTAL );
+
+        /** dtor */
+        virtual ~Dragger();
+
+        bool getDragging() const;
+
+        bool getHovered() const;
+
+        const osgEarth::GeoPoint& getPosition() const;
+
+        void setPosition( const osgEarth::GeoPoint& position, bool fireEvents=true);
+
+        void setModKeyMask(int mask) { _modKeyMask = mask; }
+
+        int getModKeyMask() const { return _modKeyMask; }
+
+        void setDefaultDragMode(const DragMode& mode) { _defaultMode = mode; }
+
+        DragMode& getDefaultDragMode() { return _defaultMode; }
+
+        void setVerticalMinimum(double min) { _verticalMinimum = min; }
+
+        double getVerticalMinimim() const { return _verticalMinimum; }
+
+
+        void updateTransform(osg::Node* patch = 0);
+
+        virtual void enter();
+
+        virtual void leave();
+
+        virtual void setColor( const osg::Vec4f& color ) =0;
+
+        virtual void setPickColor( const osg::Vec4f& color ) =0;
+
+        void addPositionChangedCallback( PositionChangedCallback* callback );
+
+        void removePositionChangedCallback( PositionChangedCallback* callback );
+
+        virtual void traverse(osg::NodeVisitor& nv);        
+
+        virtual void reclamp( const TileKey& key, osg::Node* tile, const Terrain* terrain );
+
+
+    public: // MapNodeObserver
+
+        virtual void setMapNode( MapNode* mapNode );
+
+        virtual MapNode* getMapNode() { return _mapNode.get(); }
+
+
+    protected:
+        virtual bool handle(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa);
+        void setHover( bool hovered);
+        void firePositionChanged();
+
+        osg::ref_ptr< TerrainCallback > _autoClampCallback;
+
+        osg::observer_ptr< MapNode > _mapNode;
+        osgEarth::GeoPoint _position;
+        bool _dragging;
+        bool _hovered;
+        PositionChangedCallbackList _callbacks;
+
+        osg::ref_ptr<  osgManipulator::LineProjector >  _projector;
+        osgManipulator::PointerInfo  _pointer;
+        osg::Vec3d _startProjectedPoint;
+        bool _elevationDragging;
+        int _modKeyMask;
+        DragMode _defaultMode;
+        double _verticalMinimum;
+    };
+
+    /**********************************************************/
+    class OSGEARTHANNO_EXPORT SphereDragger : public Dragger
+    {
+    public:
+        SphereDragger(MapNode* mapNode);
+
+        /** dtor */
+        virtual ~SphereDragger() { }
+
+        const osg::Vec4f& getColor() const;
+
+        void setColor(const osg::Vec4f& color);
+
+        const osg::Vec4f& getPickColor() const;
+
+        void setPickColor(const osg::Vec4f& pickColor);
+
+        float getSize() const;
+        void setSize(float size);
+
+        virtual void enter();
+
+        virtual void leave();
+
+    protected:
+
+        void updateColor();
+
+        osg::MatrixTransform* _scaler;
+        osg::ShapeDrawable* _shapeDrawable;
+        osg::Vec4f _pickColor;
+        osg::Vec4f _color;
+        float _size;
+    };
+
+
+} } // namespace osgEarth::Annotation
+
+#endif // OSGEARTH_DRAGGERS_H
diff --git a/src/osgEarthAnnotation/Draggers.cpp b/src/osgEarthAnnotation/Draggers.cpp
new file mode 100644
index 0000000..500ba0a
--- /dev/null
+++ b/src/osgEarthAnnotation/Draggers.cpp
@@ -0,0 +1,492 @@
+/* -*-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 <osgEarthAnnotation/Draggers>
+#include <osgEarth/MapNode>
+#include <osgEarth/IntersectionPicker>
+
+#include <osg/AutoTransform>
+#include <osgViewer/View>
+
+#include <osg/io_utils>
+
+#include <osgGA/EventVisitor>
+
+#include <osgManipulator/Dragger>
+
+#define LC "[Dragger] "
+
+
+using namespace osgEarth;
+using namespace osgEarth::Annotation;
+
+struct ClampDraggerCallback : public TerrainCallback
+{
+    ClampDraggerCallback( Dragger* dragger ):
+        _dragger( dragger )
+    {
+        //nop
+    }
+
+    void onTileAdded( const TileKey& key, osg::Node* tile, osgEarth::TerrainCallbackContext& context )
+    {    
+        _dragger->reclamp( key, tile, context.getTerrain() );
+    }
+
+    Dragger* _dragger;
+};
+
+/**********************************************************/
+Dragger::Dragger( MapNode* mapNode, int modKeyMask, const DragMode& defaultMode ):
+_position( mapNode->getMapSRS(), 0,0,0, ALTMODE_RELATIVE),
+_dragging(false),
+_hovered(false),
+_modKeyMask(modKeyMask),
+_defaultMode(defaultMode),
+_elevationDragging(false),
+_verticalMinimum(0.0)
+{    
+    setNumChildrenRequiringEventTraversal( 1 );
+
+    _autoClampCallback = new ClampDraggerCallback( this );
+    _projector = new osgManipulator::LineProjector;
+
+    setMapNode( mapNode );
+}
+
+Dragger::~Dragger()
+{
+    setMapNode( 0L );
+}
+
+void
+Dragger::setMapNode( MapNode* mapNode )
+{
+    MapNode* oldMapNode = getMapNode();
+
+    if ( oldMapNode != mapNode )
+    {
+        if ( oldMapNode && _autoClampCallback.valid() )
+        {
+            oldMapNode->getTerrain()->removeTerrainCallback( _autoClampCallback.get() );
+        }
+
+        _mapNode = mapNode;
+
+        if ( _mapNode.valid() && _autoClampCallback.valid() )
+        {            
+            _mapNode->getTerrain()->addTerrainCallback( _autoClampCallback.get() );
+        }
+    }
+}
+
+bool Dragger::getDragging() const
+{
+    return _dragging;
+}
+
+bool Dragger::getHovered() const
+{
+    return _hovered;
+}
+
+const GeoPoint& Dragger::getPosition() const
+{
+    return _position;
+}
+
+void Dragger::setPosition( const GeoPoint& position, bool fireEvents)
+{
+    if (_position != position)
+    {
+        _position = position;
+        updateTransform();
+
+        if ( fireEvents )
+            firePositionChanged();
+    }
+}
+
+void Dragger::firePositionChanged()
+{
+    for( PositionChangedCallbackList::iterator i = _callbacks.begin(); i != _callbacks.end(); i++ )
+    {
+        i->get()->onPositionChanged(this, _position);
+    }
+}
+
+void Dragger::updateTransform(osg::Node* patch)
+{
+    if ( getMapNode() )
+    {
+        osg::Matrixd matrix;
+        
+        GeoPoint mapPoint( _position );
+        mapPoint = mapPoint.transform( _mapNode->getMapSRS() );
+        if (!mapPoint.makeAbsolute( getMapNode()->getTerrain() ))
+        {
+            OE_INFO << LC << "Failed to clamp dragger" << std::endl;
+            return;            
+        }
+
+        mapPoint.createLocalToWorld( matrix );
+        setMatrix( matrix );
+    }
+}
+
+void Dragger::enter()
+{
+}
+
+void Dragger::leave()
+{        
+}    
+
+void Dragger::addPositionChangedCallback( PositionChangedCallback* callback )
+{
+    _callbacks.push_back( callback );
+}
+
+void Dragger::removePositionChangedCallback( PositionChangedCallback* callback )
+{
+    PositionChangedCallbackList::iterator i = std::find( _callbacks.begin(), _callbacks.end(), callback);
+    if (i != _callbacks.end())
+    {
+        _callbacks.erase( i );
+    }    
+}
+
+void Dragger::traverse(osg::NodeVisitor& nv)
+{
+    if (nv.getVisitorType() == osg::NodeVisitor::EVENT_VISITOR)
+    {
+        osgGA::EventVisitor* ev = static_cast<osgGA::EventVisitor*>(&nv);
+        for(osgGA::EventQueue::Events::iterator itr = ev->getEvents().begin();
+            itr != ev->getEvents().end();
+            ++itr)
+        {
+            osgGA::GUIEventAdapter* ea = dynamic_cast<osgGA::GUIEventAdapter*>(itr->get());
+            if ( ea && handle(*ea, *(ev->getActionAdapter())))
+                ea->setHandled(true);
+        }
+    }
+    osg::MatrixTransform::traverse(nv);
+}
+
+bool Dragger::handle(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa)
+{
+    if (ea.getHandled()) return false;
+
+    osgViewer::View* view = dynamic_cast<osgViewer::View*>(&aa);
+    if (!view) return false;
+    if (!_mapNode.valid()) return false;
+
+    if (ea.getEventType() == osgGA::GUIEventAdapter::PUSH)
+    {
+        IntersectionPicker picker( view, this );
+        IntersectionPicker::Hits hits;
+
+        if ( picker.pick( ea.getX(), ea.getY(), hits ) )
+        {
+            _dragging = true;
+
+            //Check for and handle vertical dragging if necessary
+            bool pressedAlt = _modKeyMask && (ea.getModKeyMask() & _modKeyMask) > 0;
+            _elevationDragging = (_defaultMode == Dragger::DRAGMODE_VERTICAL && !pressedAlt) || (_defaultMode == Dragger::DRAGMODE_HORIZONTAL && pressedAlt);
+
+            if (_elevationDragging)
+            {
+              _pointer.reset();
+
+              // set movement range
+              // TODO: values 0.0 and 300000.0 are rather experimental
+              GeoPoint posStart(_position.getSRS(), _position.x(), _position.y(), 0.0, ALTMODE_ABSOLUTE);
+              osg::Vec3d posStartXYZ;
+              posStart.toWorld(posStartXYZ);
+
+              GeoPoint posEnd(_position.getSRS(), _position.x(), _position.y(), 300000.0, ALTMODE_ABSOLUTE);
+              osg::Vec3d posEndXYZ;
+              posEnd.toWorld(posEndXYZ);
+
+              _projector->setLine(posStartXYZ, posEndXYZ);
+
+              // set camera
+              osgUtil::LineSegmentIntersector::Intersections intersections;
+              osg::Node::NodeMask intersectionMask = 0xffffffff;
+              osgViewer::View* view = dynamic_cast<osgViewer::View*>(&aa);
+              if (view->computeIntersections(ea.getX(),ea.getY(),intersections, intersectionMask))
+              {
+                  for (osgUtil::LineSegmentIntersector::Intersections::iterator hitr = intersections.begin(); hitr != intersections.end(); ++hitr)
+                  {
+                      _pointer.addIntersection(hitr->nodePath, hitr->getLocalIntersectPoint());
+                  }
+
+                  bool draggerFound = false;
+                  for (osgManipulator::PointerInfo::IntersectionList::iterator piit = _pointer._hitList.begin(); piit != _pointer._hitList.end(); ++piit)
+                  {
+                      for (osg::NodePath::iterator itr = piit->first.begin(); itr != piit->first.end(); ++itr)
+                      {
+                          Dragger* dragger = dynamic_cast<Dragger*>(*itr);
+                          if (dragger==this)
+                          {
+                            draggerFound = true;
+                              osg::Camera *rootCamera = view->getCamera();
+                              osg::NodePath nodePath = _pointer._hitList.front().first;
+                              osg::NodePath::reverse_iterator ritr;
+                              for (ritr = nodePath.rbegin(); ritr != nodePath.rend(); ++ritr)
+                              {
+                                  osg::Camera* camera = dynamic_cast<osg::Camera*>(*ritr);
+                                  if (camera && (camera->getReferenceFrame()!=osg::Transform::RELATIVE_RF || camera->getParents().empty()))
+                                  {
+                                       rootCamera = camera;
+                                       break;
+                                  }
+                              }
+                              _pointer.setCamera(rootCamera);
+                              _pointer.setMousePosition(ea.getX(), ea.getY());
+
+                              break;
+                          }
+                      }
+
+                      if (draggerFound)
+                        break;
+                  }
+              }
+            }
+
+            aa.requestRedraw();
+            return true;
+        }
+    }
+    else if (ea.getEventType() == osgGA::GUIEventAdapter::RELEASE)
+    {
+        _elevationDragging = false;
+
+        if ( _dragging )
+        {
+            _dragging = false;
+            firePositionChanged();
+        }
+
+        aa.requestRedraw();
+    }
+    else if (ea.getEventType() == osgGA::GUIEventAdapter::DRAG)
+    {
+        if (_elevationDragging) 
+        {
+            _pointer._hitIter = _pointer._hitList.begin();
+            _pointer.setMousePosition(ea.getX(), ea.getY());
+
+            if (_projector->project(_pointer, _startProjectedPoint)) 
+            {
+                //Get the absolute mapPoint that they've drug it to.
+                GeoPoint projectedPos;
+                projectedPos.fromWorld(_position.getSRS(), _startProjectedPoint);
+
+                // make sure point is not dragged down below
+                // TODO: think of a better solution / HeightAboveTerrain performance issues?
+                if (projectedPos.z() >= _verticalMinimum)
+                {
+                    //If the current position is relative, we need to convert the absolute world point to relative.
+                    //If the point is absolute then just emit the absolute point.
+                    if (_position.altitudeMode() == ALTMODE_RELATIVE)
+                    {
+                        projectedPos.transformZ(ALTMODE_RELATIVE, getMapNode()->getTerrain());
+                    }
+
+                    setPosition( projectedPos );
+                    aa.requestRedraw();
+                }
+            }
+
+            return true;
+        }
+        
+        if (_dragging)
+        {
+            osg::Vec3d world;
+            if ( getMapNode() && getMapNode()->getTerrain()->getWorldCoordsUnderMouse(view, ea.getX(), ea.getY(), world) )
+            {
+                //Get the absolute mapPoint that they've drug it to.
+                GeoPoint mapPoint;
+                mapPoint.fromWorld( getMapNode()->getMapSRS(), world );
+                //_mapNode->getMap()->worldPointToMapPoint(world, mapPoint);
+
+                //If the current position is relative, we need to convert the absolute world point to relative.
+                //If the point is absolute then just emit the absolute point.
+                if (_position.altitudeMode() == ALTMODE_RELATIVE)
+                {
+                    mapPoint.alt() = _position.alt();
+                    mapPoint.altitudeMode() = ALTMODE_RELATIVE;
+                }
+
+                setPosition( mapPoint );
+                aa.requestRedraw();
+                return true;
+            }
+        }
+    }   
+    else if (ea.getEventType() == osgGA::GUIEventAdapter::MOVE)
+    {
+        IntersectionPicker picker( view, this );
+        IntersectionPicker::Hits hits;
+
+        if ( picker.pick( ea.getX(), ea.getY(), hits ) )
+        {
+            setHover( true );
+        }
+        else
+        {
+            setHover( false );
+        }        
+        aa.requestRedraw();
+    }
+    return false;
+}
+
+void Dragger::setHover( bool hovered)
+{
+    if (_hovered != hovered)
+    {
+        bool wasHovered = _hovered;
+        _hovered = hovered;
+        if (wasHovered)
+        {
+            leave();            
+        }
+        else
+        {
+            enter();
+        }
+    }
+}
+
+void Dragger::reclamp( const TileKey& key, osg::Node* tile, const Terrain* terrain )
+{            
+    GeoPoint p;
+    _position.transform( key.getExtent().getSRS(), p );
+    // first verify that the control position intersects the tile:
+    if ( key.getExtent().contains( p.x(), p.y() ) )
+    {
+        updateTransform( tile );
+    }
+}
+
+
+
+/**********************************************************/
+
+SphereDragger::SphereDragger(MapNode* mapNode):
+Dragger(mapNode),
+_pickColor(1.0f, 1.0f, 0.0f, 1.0f),
+_color(0.0f, 1.0f, 0.0f, 1.0f),
+_size( 5.0f )
+{
+    //Disable culling
+    setCullingActive( false );
+
+    //Build the handle
+    osg::Sphere* shape = new osg::Sphere(osg::Vec3(0,0,0), 1.0f);   
+    osg::Geode* geode = new osg::Geode();
+    _shapeDrawable = new osg::ShapeDrawable( shape );    
+    _shapeDrawable->setDataVariance( osg::Object::DYNAMIC );
+    geode->addDrawable( _shapeDrawable );          
+
+    geode->getOrCreateStateSet()->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF);
+    geode->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
+
+    _scaler = new osg::MatrixTransform;
+    _scaler->setMatrix( osg::Matrixd::scale( _size, _size, _size ));
+    _scaler->addChild( geode );
+
+    osg::AutoTransform* at = new osg::AutoTransform;
+    at->setAutoScaleToScreen( true );
+    at->addChild( _scaler );
+    addChild( at );
+
+    updateColor();
+}
+
+const osg::Vec4f& SphereDragger::getColor() const
+{
+    return _color;
+}
+
+void SphereDragger::setColor(const osg::Vec4f& color)
+{
+    if (_color != color)
+    {
+        _color = color;
+        updateColor();
+    }
+}
+
+const osg::Vec4f& SphereDragger::getPickColor() const
+{
+    return _pickColor;
+}
+
+void SphereDragger::setPickColor(const osg::Vec4f& pickColor)
+{
+    if (_pickColor != pickColor)
+    {
+        _pickColor = pickColor;
+        updateColor();
+    }
+}
+
+float SphereDragger::getSize() const
+{
+    return _size;
+}
+
+void SphereDragger::setSize(float size)
+{
+    if (_size != size)
+    {
+        _size = size;
+        _scaler->setMatrix( osg::Matrixd::scale( _size, _size, _size ));
+    }
+}
+
+void SphereDragger::enter()
+{
+    updateColor();
+}
+
+void SphereDragger::leave()
+{
+    updateColor();
+}
+
+void SphereDragger::updateColor()
+{
+    if (getHovered())
+    {
+        _shapeDrawable->setColor( _pickColor );
+    }        
+    else
+    {
+        _shapeDrawable->setColor( _color );
+    }
+}
+
diff --git a/src/osgEarthAnnotation/EllipseNode b/src/osgEarthAnnotation/EllipseNode
index fce1e8e..1e4481c 100644
--- a/src/osgEarthAnnotation/EllipseNode
+++ b/src/osgEarthAnnotation/EllipseNode
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
@@ -149,11 +152,6 @@ namespace osgEarth { namespace Annotation
          */
         void setPie(const bool& pie);
 
-        /**
-         * Gets draped property
-         */
-        bool isDraped() const { return _draped; }
-
     public:
 
         EllipseNode(MapNode* mapNode, const Config& conf, const osgDB::Options* dbOptions);
@@ -172,7 +170,6 @@ namespace osgEarth { namespace Annotation
         void rebuild();
         
         Style _style;
-        bool _draped;
         Angle _rotationAngle;
         Distance _radiusMajor;
         Distance _radiusMinor;
diff --git a/src/osgEarthAnnotation/EllipseNode.cpp b/src/osgEarthAnnotation/EllipseNode.cpp
index 035f38b..96ca915 100644
--- a/src/osgEarthAnnotation/EllipseNode.cpp
+++ b/src/osgEarthAnnotation/EllipseNode.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
@@ -225,7 +228,6 @@ EllipseNode::EllipseNode(MapNode*              mapNode,
                          const Config&         conf,
                          const osgDB::Options* dbOptions) :
 LocalizedNode( mapNode, conf ),
-_draped      ( false ),
 _numSegments ( 0 )
 {
     _xform = new osg::MatrixTransform();
diff --git a/src/osgEarthAnnotation/Export b/src/osgEarthAnnotation/Export
index 53b9d98..099f37b 100644
--- a/src/osgEarthAnnotation/Export
+++ b/src/osgEarthAnnotation/Export
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthAnnotation/FeatureEditing b/src/osgEarthAnnotation/FeatureEditing
index ad15f61..b7d560c 100644
--- a/src/osgEarthAnnotation/FeatureEditing
+++ b/src/osgEarthAnnotation/FeatureEditing
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -20,15 +20,15 @@
 #ifndef OSGEARTHANNO_FEATURE_EDITING_H
 #define OSGEARTHANNO_FEATURE_EDITING_H 1
 
-#include <osgEarthAnnotation/Common>
+#include <osgEarthAnnotation/AnnotationEditing>
 #include <osgEarth/MapNode>
 #include <osgGA/GUIEventHandler>
 #include <osgViewer/View>
 
 #include <osgEarthAnnotation/FeatureNode>
 
-namespace osgEarth { namespace Annotation {    
-
+namespace osgEarth { namespace Annotation
+{    
     /**
      * AddPointHandler is a GUIEventHandler that allows you to append points to a Feature's Geometry
      */
@@ -74,7 +74,7 @@ namespace osgEarth { namespace Annotation {
     /**
      * Node you can add to your scene graph to edit the verts of a Feature's Geometry
      */
-    class OSGEARTHANNO_EXPORT FeatureEditor : public osg::Group
+    class OSGEARTHANNO_EXPORT FeatureEditor : public AnnotationEditor
     {
     public:
          /**
diff --git a/src/osgEarthAnnotation/FeatureEditing.cpp b/src/osgEarthAnnotation/FeatureEditing.cpp
index 9e74df4..aaf8f68 100644
--- a/src/osgEarthAnnotation/FeatureEditing.cpp
+++ b/src/osgEarthAnnotation/FeatureEditing.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,17 +8,20 @@
 * 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.
+* 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/FeatureEditing>
-#include <osgEarth/Draggers>
+#include <osgEarthAnnotation/Draggers>
 
 using namespace osgEarth;
 using namespace osgEarth::Annotation;
@@ -59,7 +62,7 @@ AddPointHandler::addPoint( float x, float y, osgViewer::View* view )
         GeoPoint mapPoint;
         mapPoint.fromWorld( mapNode->getMapSRS(), world );
 
-        Feature* feature = _featureNode->getFeature();
+        Feature* feature = _featureNode->getFeatures().front();
 
         if ( feature )            
         {
@@ -122,7 +125,8 @@ public:
 
       virtual void onPositionChanged(const Dragger* sender, const osgEarth::GeoPoint& position)
       {
-          (*_featureNode->getFeature()->getGeometry())[_point] =  osg::Vec3d(position.x(), position.y(), 0);
+          Feature* feature = _featureNode->getFeatures().front();
+          (*feature->getGeometry())[_point] =  osg::Vec3d(position.x(), position.y(), 0);
           _featureNode->init();
       }
 
@@ -196,9 +200,9 @@ FeatureEditor::init()
 {
     removeChildren( 0, getNumChildren() );
 
-    Feature* feature = _featureNode->getFeature();
+    Feature* feature = _featureNode->getFeatures().front();
     //Create a dragger for each point
-    for (unsigned int i = 0; i < _featureNode->getFeature()->getGeometry()->size(); i++)
+    for (unsigned int i = 0; i < feature->getGeometry()->size(); i++)
     {
         SphereDragger* dragger = new SphereDragger( _featureNode->getMapNode() );
         dragger->setColor( _color );
diff --git a/src/osgEarthAnnotation/FeatureNode b/src/osgEarthAnnotation/FeatureNode
index 73c0144..5362ab0 100644
--- a/src/osgEarthAnnotation/FeatureNode
+++ b/src/osgEarthAnnotation/FeatureNode
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
@@ -43,44 +46,72 @@ namespace osgEarth { namespace Annotation
         META_AnnotationNode(osgEarthAnnotation, FeatureNode);
 
         /**
-         * Constructs a new Feautre Node.
+         * Constructs a new FeatureNode from a single Feature
          */
         FeatureNode( 
             MapNode* mapNode, 
             Feature* feature,
+            const Style& style = Style(),
             const GeometryCompilerOptions& options = GeometryCompilerOptions() );
 
         /**
-         * Constructs a new Feautre Node.
-         * @deprecated - please use the ctor above.
+         * Constructs a new FeatureNode from a FeatureList
          */
         FeatureNode( 
             MapNode* mapNode, 
-            Feature* feature, 
-            bool     draped,
+            FeatureList& features,
+            const Style& style = Style(),
             const GeometryCompilerOptions& options = GeometryCompilerOptions() );
 
         virtual ~FeatureNode() { }
 
+         /**
+         * Gets the list of features
+         */
+        FeatureList& getFeatures() { return _features; };
+
+        /**
+         * Utility that lets you work on this FeatureNode as a single Feature instead of a list
+         */
+        Feature* getFeature();
+
         /**
-         * The feature that this node will render.
+         * Sets the contents of this FeatureNode to a single feature.
+         */
+        void setFeature(Feature* feature);
+
+        /**
+         * Get whether cluster culling is enabled on this FeatureNode
+         */
+        bool getClusterCulling() const;
+
+        /*
+         * Sets whether cluster culling is enabled on this FeatureNode.
          */
-        void setFeature( Feature* feature );
-        Feature* getFeature() { return _feature.get(); }
-        const Feature* getFeature() const { return _feature.get(); }
+        void setClusterCulling( bool clusterCulling );
 
-        /** Whether the feature is draped on the terrain
-         *  @deprecated - check the style instead */
-        bool isDraped() const { return _draped; }
 
+        /**
+         * 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.
+         */
         void init();
+        void dirty() { init(); }
 
     public: // AnnotationNode
 
         virtual osg::Group* getAttachPoint();
 
-         virtual const Style& getStyle() const;
+        /**
+         * Gets the Style for this FeatureNode.
+         */
+        virtual const Style& getStyle() const;
 
+        /**
+         * Sets the style for this FeatureNode.  
+         * Note:  Do NOT use embedded feature styles if you need to change the style of the FeatureNode at runtime using this method.
+         *        You will need to set the style of the features themselves and call init on this FeatureNode if you use embedded styles.
+         */
         virtual void setStyle(const Style& style);
 
     public: // MapNodeObserver
@@ -93,16 +124,27 @@ namespace osgEarth { namespace Annotation
         virtual Config getConfig() const;
 
     protected:
-        osg::ref_ptr<Feature>        _feature;
+        FeatureList                  _features;
         GeometryCompilerOptions      _options;
-        bool                         _draped; // to remove
         osg::Group*                  _attachPoint;
         osg::Polytope                _featurePolytope;
+        Style                        _style;
+        bool                         _needsRebuild;
+        bool                         _clusterCulling;
+        GeoExtent                    _extent;
+        osg::NodeCallback*           _clusterCullingCallback;
+
+        osg::ref_ptr< osg::Node >    _compiled;
 
         FeatureNode() { }
         FeatureNode(const FeatureNode& rhs, const osg::CopyOp& op) { }
         
         virtual void reclamp( const TileKey& key, osg::Node* tile, const Terrain* );
+
+        void build();
+
+        void updateClusterCulling();
+
         
     private:
         void clampMesh( osg::Node* terrainModel );
diff --git a/src/osgEarthAnnotation/FeatureNode.cpp b/src/osgEarthAnnotation/FeatureNode.cpp
index c0c993b..b7c28d8 100644
--- a/src/osgEarthAnnotation/FeatureNode.cpp
+++ b/src/osgEarthAnnotation/FeatureNode.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
@@ -32,6 +35,7 @@
 #include <osgEarth/NodeUtils>
 #include <osgEarth/Utils>
 #include <osgEarth/Registry>
+#include <osgEarth/CullingUtils>
 
 #include <osg/BoundingSphere>
 #include <osg/Polytope>
@@ -44,32 +48,60 @@ using namespace osgEarth::Annotation;
 using namespace osgEarth::Features;
 using namespace osgEarth::Symbology;
 
-
-FeatureNode::FeatureNode(MapNode* mapNode, 
-                         Feature* feature, 
-                         bool     draped,
+FeatureNode::FeatureNode(MapNode* mapNode,
+                         Feature* feature,
+                         const Style& style,
                          const GeometryCompilerOptions& options ) :
 AnnotationNode( mapNode ),
-_feature      ( feature ),
-_draped       ( draped ),
-_options      ( options )
+_style        ( style ),
+_options      ( options ),
+_needsRebuild (true),
+_clusterCulling(true),
+_clusterCullingCallback(0)
 {
-    init();
+    if (_style.empty() && feature->style().isSet())
+    {
+        _style = *feature->style();
+    }
+
+    _features.push_back( feature );
+    build();
 }
 
-FeatureNode::FeatureNode(MapNode* mapNode,
-                         Feature* feature,
-                         const GeometryCompilerOptions& options ) :
+FeatureNode::FeatureNode(MapNode* mapNode, 
+                         FeatureList& features,
+                         const Style& style,
+                         const GeometryCompilerOptions& options):
 AnnotationNode( mapNode ),
-_feature      ( feature ),
-_draped       ( false ),
-_options      ( options )
+_style        ( style ),
+_options      ( options ),
+_needsRebuild (true),
+_clusterCulling(true),
+_clusterCullingCallback(0)
+{
+    _features.insert( _features.end(), features.begin(), features.end() );
+    build();
+}
+
+bool
+FeatureNode::getClusterCulling() const
+{
+    return _clusterCulling;
+}
+
+void
+FeatureNode::setClusterCulling( bool clusterCulling)
 {
-    init();
+    if (_clusterCulling != clusterCulling)
+    {
+        _clusterCulling = clusterCulling;
+        updateClusterCulling();
+    }
 }
 
+
 void
-FeatureNode::init()
+FeatureNode::build()
 {
     // if there's a decoration, clear it out first.
     this->clearDecoration();
@@ -81,15 +113,17 @@ FeatureNode::init()
     if ( !getMapNode() )
         return;
 
-    if ( !_feature.valid() )
+    if ( _features.empty() )
         return;
 
+    const Style &style = getStyle();
+
     // compilation options.
     GeometryCompilerOptions options = _options;
     
     // figure out what kind of altitude manipulation we need to perform.
     AnnotationUtils::AltitudePolicy ap;
-    AnnotationUtils::getAltitudePolicy( *_feature->style(), ap );
+    AnnotationUtils::getAltitudePolicy( style, ap );
 
     // If we're doing auto-clamping on the CPU, shut off compiler map clamping
     // clamping since it would be redundant.
@@ -99,23 +133,57 @@ FeatureNode::init()
         options.ignoreAltitudeSymbol() = true;
     }
 
-    // prep the compiler:
-    GeometryCompiler compiler( options );
-    Session* session = new Session( getMapNode()->getMap() );
-    GeoExtent extent(_feature->getSRS(), _feature->getGeometry()->getBounds());
-    osg::ref_ptr<FeatureProfile> profile = new FeatureProfile( extent );
-    FilterContext context( session, profile.get(), extent );
+    osg::Node* node = _compiled.get();
+    if (_needsRebuild || !_compiled.valid() )
+    {
+        // Clone the Features before rendering as the GeometryCompiler and it's filters can change the coordinates
+        // of the geometry when performing localization or converting to geocentric.
+        _extent = GeoExtent::INVALID;
+
+        FeatureList clone;
+        for(FeatureList::iterator itr = _features.begin(); itr != _features.end(); ++itr)
+        {
+            Feature* feature = new Feature( *itr->get(), osg::CopyOp::DEEP_COPY_ALL);
+            GeoExtent featureExtent(feature->getSRS(), feature->getGeometry()->getBounds());
+
+            if (_extent.isInvalid())
+            {
+                _extent = featureExtent;
+            }
+            else
+            {
+                _extent.expandToInclude( featureExtent );
+            }
+            clone.push_back( feature );
+        }
+
+        // prep the compiler:
+        GeometryCompiler compiler( options );
+        Session* session = new Session( getMapNode()->getMap() );
+
+        FilterContext context( session, new FeatureProfile( _extent ), _extent );
+
+        _compiled = compiler.compile( clone, style, context );
+        node = _compiled.get();
+        _needsRebuild = false;
+
+        // Compute the world bounds
+        osg::BoundingSphered bounds;
+        for( FeatureList::iterator itr = _features.begin(); itr != _features.end(); ++itr)
+        {
+            osg::BoundingSphered bs;
+            itr->get()->getWorldBound(getMapNode()->getMapSRS(), bs);
+            bounds.expandBy(bs);
+        }
+        // The polytope will ensure we only clamp to intersecting tiles:
+        Feature::getWorldBoundingPolytope(bounds, getMapNode()->getMapSRS(), _featurePolytope);
 
-    // Clone the Feature before rendering as the GeometryCompiler and it's filters can change the coordinates
-    // of the geometry when performing localization or converting to geocentric.
-    osg::ref_ptr< Feature > clone = new Feature(*_feature.get(), osg::CopyOp::DEEP_COPY_ALL);
+    }
 
-    osg::Node* node = compiler.compile( clone.get(), *clone->style(), context );
     if ( node )
     {
-        if ( _feature->style().isSet() &&
-            AnnotationUtils::styleRequiresAlphaBlending( *_feature->style() ) &&
-            _feature->style()->get<ExtrusionSymbol>() )
+        if ( AnnotationUtils::styleRequiresAlphaBlending( style ) &&
+             getStyle().get<ExtrusionSymbol>() )
         {
             node = AnnotationUtils::installTwoPassAlpha( node );
         }
@@ -140,7 +208,7 @@ FeatureNode::init()
             clampable->addChild( _attachPoint );
             this->addChild( clampable );
 
-            const RenderSymbol* render = _feature->style()->get<RenderSymbol>();
+            const RenderSymbol* render = style.get<RenderSymbol>();
             if ( render && render->depthOffset().isSet() )
             {
                 clampable->setDepthOffsetOptions( *render->depthOffset() );
@@ -155,24 +223,23 @@ FeatureNode::init()
             if ( ap.sceneClamping )
             {
                 // save for later when we need to reclamp the mesh on the CPU
-                _altitude = _feature->style()->get<AltitudeSymbol>();
-
-                // The polytope will ensure we only clamp to intersecting tiles:
-                _feature->getWorldBoundingPolytope( getMapNode()->getMapSRS(), _featurePolytope );
+                _altitude = style.get<AltitudeSymbol>();
 
                 // activate the terrain callback:
                 setCPUAutoClamping( true );
 
                 // set default lighting based on whether we are extruding:
-                setLightingIfNotSet( _feature->style()->has<ExtrusionSymbol>() );
+                setLightingIfNotSet( style.has<ExtrusionSymbol>() );
 
                 // do an initial clamp to get started.
                 clampMesh( getMapNode()->getTerrain()->getGraph() );
             } 
 
-            applyGeneralSymbology( *_feature->style() );
+            applyGeneralSymbology( style );
         }
     }
+
+    updateClusterCulling();
 }
 
 void
@@ -181,34 +248,62 @@ FeatureNode::setMapNode( MapNode* mapNode )
     if ( getMapNode() != mapNode )
     {
         AnnotationNode::setMapNode( mapNode );
-        init();
+        _needsRebuild = true;
+        build();
     }
 }
 
+const Style& FeatureNode::getStyle() const
+{
+    return _style;
+}
+
 void
-FeatureNode::setFeature( Feature* feature )
+FeatureNode::setStyle(const Style& style)
 {
-    _feature = feature;
-    init();
+    // Try to compare the styles and see if we can get away with not compiling the geometry again.
+    Style a = _style;
+    Style b = style;
+   
+    // If the only thing that has changed is the AltitudeSymbol, we don't need to worry about rebuilding the entire geometry again.
+    a.remove<AltitudeSymbol>();
+    b.remove<AltitudeSymbol>();
+    if (a.getConfig().toJSON() == b.getConfig().toJSON())
+    {
+        _needsRebuild = false;
+    }
+    else
+    {
+        _needsRebuild = true;
+    }
+    _style = style;
+    build();
 }
 
-const Style& FeatureNode::getStyle() const
+Feature* FeatureNode::getFeature()
 {
-    if ( _feature.valid() )
+    if (_features.size() == 1)
     {
-        return *_feature->style();
+        return _features.front();
     }
-    return AnnotationNode::getStyle();
+    return 0;
 }
 
-void
-FeatureNode::setStyle(const Style& style)
+void FeatureNode::setFeature(Feature* feature)
 {
-    if ( _feature.valid() )
+    _features.clear();
+    if (feature)
     {
-        _feature->style() = style;
-        init();
+        _features.push_back( feature );
     }
+    _needsRebuild = true;
+    build();
+}
+
+void FeatureNode::init()
+{
+    _needsRebuild = true;
+    build();
 }
 
 osg::Group*
@@ -248,10 +343,10 @@ FeatureNode::clampMesh( osg::Node* terrainModel )
 
         if (_altitude.valid())
         {
-            NumericExpression scale(_altitude->verticalScale().value());
-            NumericExpression offset(_altitude->verticalOffset().value());
-            scale = _feature->eval(scale);
-            offset = _feature->eval(offset);
+            NumericExpression scaleExpr(_altitude->verticalScale().value());
+            NumericExpression offsetExpr(_altitude->verticalOffset().value());
+            scale = scaleExpr.eval();
+            offset = offsetExpr.eval();
             relative = _altitude->clamping() == AltitudeSymbol::CLAMP_RELATIVE_TO_TERRAIN;
         }
 
@@ -262,6 +357,38 @@ FeatureNode::clampMesh( osg::Node* terrainModel )
     }
 }
 
+void
+FeatureNode::updateClusterCulling()
+{
+    // install a cluster culler.
+    if ( getMapNode()->isGeocentric() && _clusterCulling && !_clusterCullingCallback)
+    {
+        const GeoExtent& ccExtent = _extent;
+        if ( ccExtent.isValid() )
+        {
+            // if the extent is more than 90 degrees, bail
+            GeoExtent geodeticExtent = ccExtent.transform( ccExtent.getSRS()->getGeographicSRS() );
+            if ( geodeticExtent.width() < 90.0 && geodeticExtent.height() < 90.0 )
+            {
+                // get the geocentric tile center:
+                osg::Vec3d tileCenter;
+                ccExtent.getCentroid( tileCenter.x(), tileCenter.y() );
+
+                osg::Vec3d centerECEF;
+                ccExtent.getSRS()->transform( tileCenter, getMapNode()->getMapSRS()->getECEF(), centerECEF );
+                _clusterCullingCallback = ClusterCullingFactory::create2( this, centerECEF );
+                if ( _clusterCullingCallback )
+                    this->addCullCallback( _clusterCullingCallback );
+            }
+        }
+    }
+    else if (!_clusterCulling && _clusterCullingCallback)
+    {
+        this->removeCullCallback( _clusterCullingCallback );
+        _clusterCullingCallback = 0;
+    }
+}
+
 
 //-------------------------------------------------------------------
 
@@ -289,44 +416,51 @@ AnnotationNode( mapNode, conf )
 
     optional<GeoInterpolation> geoInterp;
 
-    Style style;
-    conf.getObjIfSet( "style", style );
+    conf.getObjIfSet( "style", _style );
 
     if ( srs.valid() && geom.valid() )
     {
-        //_draped = conf.value<bool>("draped",false);
-        Feature* feature = new Feature(geom.get(), srs.get(), style);
+        Feature* feature = new Feature(geom.get(), srs.get() );
 
         conf.getIfSet( "geointerp", "greatcircle", feature->geoInterp(), GEOINTERP_GREAT_CIRCLE );
         conf.getIfSet( "geointerp", "rhumbline",   feature->geoInterp(), GEOINTERP_RHUMB_LINE );
 
-        setFeature( feature );
+        _features.push_back( feature );
+        build();
     }
 }
 
 Config
 FeatureNode::getConfig() const
 {
+    
     Config conf("feature");
 
-    if ( _feature.valid() && _feature->getGeometry() )
+    if ( !_features.empty() )
     {
+        // Write out a single feature for now.
+
+        Feature* feature = _features.begin()->get();
+
         conf.set("name", getName());
 
         Config geomConf("geometry");
-        geomConf.value() = GeometryUtils::geometryToWKT( _feature->getGeometry() );
+        geomConf.value() = GeometryUtils::geometryToWKT( feature->getGeometry() );
         conf.add(geomConf);
 
-        std::string srs = _feature->getSRS() ? _feature->getSRS()->getHorizInitString() : "";
+        std::string srs = feature->getSRS() ? feature->getSRS()->getHorizInitString() : "";
         if ( !srs.empty() ) conf.set("srs", srs);
 
-        std::string vsrs = _feature->getSRS() ? _feature->getSRS()->getVertInitString() : "";
+        std::string vsrs = feature->getSRS() ? feature->getSRS()->getVertInitString() : "";
         if ( !vsrs.empty() ) conf.set("vdatum", vsrs);
 
-        if ( _feature->geoInterp().isSet() )
-            conf.set("geointerp", _feature->geoInterp() == GEOINTERP_GREAT_CIRCLE? "greatcircle" : "rhumbline");
+        if ( feature->geoInterp().isSet() )
+            conf.set("geointerp", feature->geoInterp() == GEOINTERP_GREAT_CIRCLE? "greatcircle" : "rhumbline");
+    }
 
-        conf.addObjIfSet( "style", _feature->style() );
+    if (!_style.empty() )
+    {
+        conf.addObj( "style", _style );
     }
 
     return conf;
diff --git a/src/osgEarthAnnotation/HighlightDecoration b/src/osgEarthAnnotation/HighlightDecoration
index de6e7c3..14e4ea0 100644
--- a/src/osgEarthAnnotation/HighlightDecoration
+++ b/src/osgEarthAnnotation/HighlightDecoration
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/osgEarthAnnotation/HighlightDecoration.cpp b/src/osgEarthAnnotation/HighlightDecoration.cpp
index 787f85b..18d993d 100644
--- a/src/osgEarthAnnotation/HighlightDecoration.cpp
+++ b/src/osgEarthAnnotation/HighlightDecoration.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
@@ -75,7 +78,7 @@ HighlightDecoration::apply(AnnotationNode& node, bool enable)
             VirtualProgram* vp = VirtualProgram::getOrCreate( ss );
             if ( vp->getShader(FRAG_FUNCTION) == 0L )
             {
-                vp->setFunction(FRAG_FUNCTION, fragSource, ShaderComp::LOCATION_FRAGMENT_COLORING);
+                vp->setFunction(FRAG_FUNCTION, fragSource, ShaderComp::LOCATION_FRAGMENT_COLORING, 0.6f);
                 ss->addUniform( _colorUniform.get() );
             }
             _colorUniform->set(_color);
diff --git a/src/osgEarthAnnotation/ImageOverlay b/src/osgEarthAnnotation/ImageOverlay
index acdf514..5947c65 100644
--- a/src/osgEarthAnnotation/ImageOverlay
+++ b/src/osgEarthAnnotation/ImageOverlay
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthAnnotation/ImageOverlay.cpp b/src/osgEarthAnnotation/ImageOverlay.cpp
index ff4c7e0..15456b6 100644
--- a/src/osgEarthAnnotation/ImageOverlay.cpp
+++ b/src/osgEarthAnnotation/ImageOverlay.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
@@ -265,7 +268,9 @@ ImageOverlay::init()
         if (_image.valid())
         {
             //Create the texture
-            _texture = new osg::Texture2D(_image.get());        
+            _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);    
diff --git a/src/osgEarthAnnotation/ImageOverlayEditor b/src/osgEarthAnnotation/ImageOverlayEditor
index c878a9b..71ce102 100644
--- a/src/osgEarthAnnotation/ImageOverlayEditor
+++ b/src/osgEarthAnnotation/ImageOverlayEditor
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -21,14 +21,15 @@
 
 #include <osgEarthAnnotation/Common>
 #include <osgEarthAnnotation/ImageOverlay>
-#include <osgEarth/Draggers>
+#include <osgEarthAnnotation/AnnotationEditing>
+#include <osgEarthAnnotation/Draggers>
 #include <osgEarth/MapNode>
 #include <osg/MatrixTransform>
 #include <osgGA/GUIEventHandler>
 
 namespace osgEarth { namespace Annotation
 {
-    class OSGEARTHANNO_EXPORT ImageOverlayEditor : public osg::Group
+    class OSGEARTHANNO_EXPORT ImageOverlayEditor : public AnnotationEditor
     {
     public:
         typedef std::map< ImageOverlay::ControlPoint, osg::ref_ptr< Dragger > >  ControlPointDraggerMap;
diff --git a/src/osgEarthAnnotation/ImageOverlayEditor.cpp b/src/osgEarthAnnotation/ImageOverlayEditor.cpp
index eb5b76e..4c35787 100644
--- a/src/osgEarthAnnotation/ImageOverlayEditor.cpp
+++ b/src/osgEarthAnnotation/ImageOverlayEditor.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
@@ -71,9 +74,10 @@ struct OverlayCallback : public ImageOverlay::ImageOverlayCallback
 
 
 
-ImageOverlayEditor::ImageOverlayEditor(ImageOverlay* overlay, bool singleVert):
-_overlay  (overlay),
-_singleVert( singleVert )
+ImageOverlayEditor::ImageOverlayEditor(ImageOverlay* overlay, bool singleVert) :
+AnnotationEditor(),
+_overlay        ( overlay ),
+_singleVert     ( singleVert )
 {   
     _overlayCallback = new OverlayCallback(this);
     _overlay->addCallback( _overlayCallback.get() );
diff --git a/src/osgEarthAnnotation/LabelNode b/src/osgEarthAnnotation/LabelNode
index fa32e2b..260ad79 100644
--- a/src/osgEarthAnnotation/LabelNode
+++ b/src/osgEarthAnnotation/LabelNode
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
@@ -96,6 +99,7 @@ namespace osgEarth { namespace Annotation
          */
         void setText( const std::string& text );
         const std::string& text() const { return _text; }
+        virtual const std::string& getText() const { return text(); }
 
         /**
          * Gets a copy of the text style.
diff --git a/src/osgEarthAnnotation/LabelNode.cpp b/src/osgEarthAnnotation/LabelNode.cpp
index ae8baa6..05e586b 100644
--- a/src/osgEarthAnnotation/LabelNode.cpp
+++ b/src/osgEarthAnnotation/LabelNode.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
@@ -88,6 +91,11 @@ void
 LabelNode::init( const Style& style )
 {
     _geode = new osg::Geode();
+
+    // ensure that (0,0,0) is the bounding sphere control/center point.
+    // useful for things like horizon culling.
+    _geode->setComputeBoundingSphereCallback(new ControlPointCallback());
+
     getAttachPoint()->addChild( _geode.get() );
 
     osg::StateSet* stateSet = _geode->getOrCreateStateSet();
diff --git a/src/osgEarthAnnotation/LocalGeometryNode b/src/osgEarthAnnotation/LocalGeometryNode
index 7b2b5c1..9088a5f 100644
--- a/src/osgEarthAnnotation/LocalGeometryNode
+++ b/src/osgEarthAnnotation/LocalGeometryNode
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/osgEarthAnnotation/LocalGeometryNode.cpp b/src/osgEarthAnnotation/LocalGeometryNode.cpp
index d2814b2..0097c61 100644
--- a/src/osgEarthAnnotation/LocalGeometryNode.cpp
+++ b/src/osgEarthAnnotation/LocalGeometryNode.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/osgEarthAnnotation/LocalizedNode b/src/osgEarthAnnotation/LocalizedNode
index 119fbe6..f6aac2e 100644
--- a/src/osgEarthAnnotation/LocalizedNode
+++ b/src/osgEarthAnnotation/LocalizedNode
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
@@ -20,8 +23,8 @@
 #define OSGEARTH_ANNO_LOCALIZED_NODE_H 1
 
 #include <osgEarthAnnotation/AnnotationNode>
-#include <osgEarth/OverlayNode>
 #include <osgEarth/GeoData>
+#include <osgEarth/Horizon>
 #include <osg/MatrixTransform>
 
 namespace osgEarth { namespace Annotation
@@ -92,7 +95,7 @@ namespace osgEarth { namespace Annotation
         /**
          * Local scale factor.
          */
-        void setScale( const osg::Vec3f& scale );
+        virtual void setScale( const osg::Vec3f& scale );
         const osg::Vec3f& getScale() const { return _scale; }
 
         /**
@@ -122,12 +125,13 @@ namespace osgEarth { namespace Annotation
 
     protected:
         bool                               _initComplete;
-        bool                               _horizonCulling;
         GeoPoint                           _mapPosition;
         osg::Vec3f                         _scale;
         osg::Vec3d                         _localOffset;
         osg::Quat                          _localRotation;
-        osg::ref_ptr<osg::NodeCallback>    _cullByHorizonCB;
+        
+        bool                               _horizonCullingRequested;
+        osg::ref_ptr<HorizonCullCallback>  _horizonCuller;
 
         friend class Decoration;
         
diff --git a/src/osgEarthAnnotation/LocalizedNode.cpp b/src/osgEarthAnnotation/LocalizedNode.cpp
index ebe77eb..76deb19 100644
--- a/src/osgEarthAnnotation/LocalizedNode.cpp
+++ b/src/osgEarthAnnotation/LocalizedNode.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
@@ -35,7 +38,7 @@ LocalizedNode::LocalizedNode(MapNode*        mapNode,
                              const GeoPoint& position) :
 PositionedAnnotationNode( mapNode ),
 _initComplete           ( false ),
-_horizonCulling         ( true ),
+_horizonCullingRequested( true ),
 _scale                  ( 1.0f, 1.0f, 1.0f ),
 _mapPosition            ( position )
 {
@@ -46,7 +49,9 @@ _mapPosition            ( position )
 void
 LocalizedNode::init()
 {
-    this->getOrCreateStateSet()->setRenderingHint( osg::StateSet::TRANSPARENT_BIN );
+    this->getOrCreateStateSet()->setRenderingHint( osg::StateSet::TRANSPARENT_BIN );    
+    _horizonCuller = new HorizonCullCallback();
+    this->addCullCallback( _horizonCuller.get() );
 }
 
 
@@ -66,7 +71,7 @@ LocalizedNode::computeBound() const
         if ( !_initComplete )
         {
             const_cast<LocalizedNode*>(this)->_initComplete = true;
-            const_cast<LocalizedNode*>(this)->setHorizonCulling( _horizonCulling );
+            const_cast<LocalizedNode*>(this)->setHorizonCulling( _horizonCullingRequested );
             const_cast<LocalizedNode*>(this)->setPosition      ( _mapPosition );
         }
     }
@@ -82,11 +87,7 @@ LocalizedNode::setMapNode( MapNode* mapNode )
         PositionedAnnotationNode::setMapNode( mapNode );
 
         // The horizon culler depends on the map node, so reinitialize it:
-        if ( _horizonCulling )
-        {
-            setHorizonCulling( false );
-            setHorizonCulling( true );
-        }
+        setHorizonCulling( _horizonCullingRequested );
 
         // re-apply the position since the map has changed
         setPosition( _mapPosition );
@@ -208,24 +209,20 @@ LocalizedNode::getLocalRotation() const
 void
 LocalizedNode::setHorizonCulling( bool value )
 {
-    _horizonCulling = value;
+    _horizonCullingRequested = value;
 
-    if ( _initComplete && getMapNode() && getMapNode()->isGeocentric() )
+    if ( _initComplete )
     {
-        if ( _horizonCulling && !_cullByHorizonCB.valid() )
+        if ( getMapNode() )
         {
-            _cullByHorizonCB = new CullNodeByHorizon(
-                getTransform(),
-                getMapNode()->getMapSRS()->getEllipsoid() );
-
-            addCullCallback( _cullByHorizonCB.get() );
+            _horizonCuller->setHorizon(
+                Horizon(*getMapNode()->getMapSRS()->getEllipsoid()) );
         }
 
-        else if ( !_horizonCulling && _cullByHorizonCB.valid() )
-        {
-            removeCullCallback( _cullByHorizonCB.get() );
-            _cullByHorizonCB = 0L;
-        }
+        _horizonCuller->setEnabled(
+            _horizonCullingRequested &&
+            getMapNode() &&
+            getMapNode()->isGeocentric() );
     }
 }
 
@@ -288,7 +285,7 @@ LocalizedNode::applyAltitudePolicy(osg::Node* node, const Style& style)
 LocalizedNode::LocalizedNode(MapNode* mapNode, const Config& conf) :
 PositionedAnnotationNode( mapNode, conf ),
 _initComplete           ( false ),
-_horizonCulling         ( true ),
+_horizonCullingRequested( true ),
 _scale                  ( 1.0f, 1.0f, 1.0f )
 {
     if ( conf.hasChild( "position" ) )
diff --git a/src/osgEarthAnnotation/ModelNode b/src/osgEarthAnnotation/ModelNode
index dceba6a..c97aaf3 100644
--- a/src/osgEarthAnnotation/ModelNode
+++ b/src/osgEarthAnnotation/ModelNode
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
@@ -56,6 +59,10 @@ namespace osgEarth { namespace Annotation
         const Style& getStyle() const { return _style; }
 
 
+    public: // LocalizedNode
+        
+        virtual void setScale( const osg::Vec3f& scale );
+
     public:
 
         /**
diff --git a/src/osgEarthAnnotation/ModelNode.cpp b/src/osgEarthAnnotation/ModelNode.cpp
index 27c53ee..28c7718 100644
--- a/src/osgEarthAnnotation/ModelNode.cpp
+++ b/src/osgEarthAnnotation/ModelNode.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
@@ -56,6 +59,16 @@ ModelNode::setStyle(const Style& style)
     init();
 }
 
+void
+ModelNode::setScale(const osg::Vec3f& scale)
+{
+    osg::StateSet* stateSet = getStateSet();
+    if ( stateSet && stateSet->getBinName() == osgEarth::AUTO_SCALE_BIN )
+    {
+        stateSet->setRenderBinToInherit();
+    }
+    LocalizedNode::setScale( scale );
+}
 
 void
 ModelNode::init()
diff --git a/src/osgEarthAnnotation/OrthoNode b/src/osgEarthAnnotation/OrthoNode
index 8895041..363c212 100644
--- a/src/osgEarthAnnotation/OrthoNode
+++ b/src/osgEarthAnnotation/OrthoNode
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
@@ -22,6 +25,7 @@
 #include <osgEarthAnnotation/AnnotationNode>
 #include <osgEarth/Decluttering>
 #include <osgEarth/SpatialReference>
+#include <osgEarth/Horizon>
 #include <osgEarth/CullingUtils>
 #include <osg/AutoTransform>
 #include <osg/MatrixTransform>
@@ -84,9 +88,9 @@ namespace osgEarth { namespace Annotation
         const osg::Vec3d& getLocalOffset() const;
 
         /**
-         * Enables or disable automatic horizon culling
+         * Enables or disable automatic horizon culling for geocentric maps
          */
-        void setHorizonCulling( bool value );
+        void setHorizonCulling(bool value);
         bool getHorizonCulling() const;
 
         /**
@@ -125,13 +129,13 @@ namespace osgEarth { namespace Annotation
         osg::AutoTransform*            _autoxform;
         osg::MatrixTransform*          _matxform;
         osg::Group*                    _attachPoint;
-        bool                           _horizonCulling;
         bool                           _occlusionCulling;
         optional< double >             _occlusionCullingMaxAltitude;
         GeoPoint                       _mapPosition;
         osg::Vec3d                     _localOffset;
+        bool                           _horizonCullingRequested;
 
-        osg::ref_ptr< CullNodeByHorizon > _horizonCuller;
+        osg::ref_ptr< HorizonCullCallback >      _horizonCuller;
         osg::ref_ptr< OcclusionCullingCallback > _occlusionCuller;
 
         void init();
diff --git a/src/osgEarthAnnotation/OrthoNode.cpp b/src/osgEarthAnnotation/OrthoNode.cpp
index 6a36c69..0c66c5b 100644
--- a/src/osgEarthAnnotation/OrthoNode.cpp
+++ b/src/osgEarthAnnotation/OrthoNode.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
@@ -37,104 +40,32 @@
 using namespace osgEarth;
 using namespace osgEarth::Annotation;
 
-//------------------------------------------------------------------------
-
-namespace
-{
-    struct OrthoOQNode : public osg::OcclusionQueryNode
-    {
-        OrthoOQNode( const std::string& name ) 
-        {
-            setName( name );
-            setVisibilityThreshold(1);
-            setDebugDisplay(true);
-            setCullingActive(false);
-        }
-
-        virtual osg::BoundingSphere computeBound() const
-        {
-            {
-                // Need to make this routine thread-safe. Typically called by the update
-                //   Visitor, or just after the update traversal, but could be called by
-                //   an application thread or by a non-osgViewer application.
-                Threading::ScopedMutexLock lock( _computeBoundMutex );
-
-                // This is the logical place to put this code, but the method is const. Cast
-                //   away constness to compute the bounding box and modify the query geometry.
-                OrthoOQNode* nonConstThis = const_cast<OrthoOQNode*>( this );
-
-                osg::ref_ptr<osg::Vec3Array> v = new osg::Vec3Array(1);
-                (*v)[0].set( _xform->getMatrix().getTrans() );
-
-                osg::Geometry* geom = static_cast< osg::Geometry* >( nonConstThis->_queryGeode->getDrawable( 0 ) );
-                geom->setVertexArray( v.get() );
-                geom->getPrimitiveSetList().clear();
-                geom->addPrimitiveSet( new osg::DrawArrays(GL_POINTS,0,1) );
-                nonConstThis->getQueryStateSet()->setAttributeAndModes(new osg::Point(15), 1);
-                nonConstThis->getQueryStateSet()->setBinNumber(INT_MAX);
-
-                geom = static_cast< osg::Geometry* >( nonConstThis->_debugGeode->getDrawable( 0 ) );
-                geom->setVertexArray( v.get() );
-                geom->getPrimitiveSetList().clear();
-                geom->addPrimitiveSet( new osg::DrawArrays(GL_POINTS,0,1) );
-                nonConstThis->getDebugStateSet()->setAttributeAndModes(new osg::Point(15), 1);
-                osg::Depth* d = new osg::Depth( osg::Depth::LEQUAL, 0.f, 1.f, false );
-                nonConstThis->getDebugStateSet()->setAttributeAndModes( d, osg::StateAttribute::ON | osg::StateAttribute::PROTECTED);
-                (*dynamic_cast<osg::Vec4Array*>(geom->getColorArray()))[0].set(1,0,0,1);
-            }
-
-            return Group::computeBound();
-        }
-
-        osg::MatrixTransform* _xform;
-    };
-}
-
-//------------------------------------------------------------------------
-
 
 OrthoNode::OrthoNode(MapNode*        mapNode,
                      const GeoPoint& position ) :
 
 PositionedAnnotationNode( mapNode ),
-_horizonCulling         ( false ),
-_occlusionCulling       ( false )
+_occlusionCulling       ( false ),
+_horizonCullingRequested( true )
 {
     init();
-    setHorizonCulling( true );
     setPosition( position );
 }
 
 
 OrthoNode::OrthoNode() :
-_horizonCulling  ( false ),
-_occlusionCulling( false )
+_occlusionCulling       ( false ),
+_horizonCullingRequested( true )
 {
     init();
-    setHorizonCulling( true );
 }
 
-//#define TRY_OQ 1
-#undef TRY_OQ
 
 void
 OrthoNode::init()
 {
     _switch = new osg::Switch();
-
-    // install it, but deactivate it until we can get it to work.
-#ifdef TRY_OQ
-    OrthoOQNode* oq = new OrthoOQNode("");
-    oq->setQueriesEnabled(true);
-    _oq = oq;
-    _oq->addChild( _switch );
-    this->addChild( _oq );
-#else
     this->addChild( _switch );
-#endif
-
-    //_oq->addChild( _switch );
-    //this->addChild( _oq );
 
     _autoxform = new AnnotationUtils::OrthoNodeAutoTransform();
     _autoxform->setAutoRotateMode( osg::AutoTransform::ROTATE_TO_SCREEN );
@@ -144,19 +75,27 @@ OrthoNode::init()
 
     _matxform = new osg::MatrixTransform();
     _switch->addChild( _matxform );
-
-#ifdef TRY_OQ
-    oq->_xform = _matxform;
-#endif
-
     _switch->setSingleChildOn( 0 );
 
     _attachPoint = new osg::Group();
-
     _autoxform->addChild( _attachPoint );
     _matxform->addChild( _attachPoint );
 
     this->getOrCreateStateSet()->setMode( GL_LIGHTING, 0 );
+
+    // Callback to cull ortho nodes that are not visible over the geocentric horizon
+    _horizonCuller = new HorizonCullCallback();
+    setHorizonCulling( _horizonCullingRequested );
+
+    _attachPoint->addCullCallback( _horizonCuller.get() );
+}
+
+osg::BoundingSphere
+OrthoNode::computeBound() const
+{
+    osg::BoundingSphere bs = PositionedAnnotationNode::computeBound();
+    //OE_NOTICE << "BOUND RADIUS = " << bs.radius() << "\n";
+    return bs;
 }
 
 void
@@ -183,18 +122,22 @@ OrthoNode::traverse( osg::NodeVisitor& nv )
         // If decluttering is enabled, update the auto-transform but not its children.
         // This is necessary to support picking/selection. An optimization would be to
         // disable this pass when picking is not in use
-        if ( declutter )
-        {
-            static_cast<AnnotationUtils::OrthoNodeAutoTransform*>(_autoxform)->acceptCullNoTraverse( cv );
-        }
+        //if ( !declutter )
+        //{
+        //    static_cast<AnnotationUtils::OrthoNodeAutoTransform*>(_autoxform)->accept( nv, false );
+        //}
 
         // turn off small feature culling
-        cv->setSmallFeatureCullingPixelSize(0.0f);
+        // (note: pretty sure this does nothing here -gw)
+        cv->setSmallFeatureCullingPixelSize(-1.0f);
 
         AnnotationNode::traverse( nv );
 
-        if ( this->getCullingActive() == false )
-            this->setCullingActive( true );
+        if ( _autoxform->getCullingActive() == false )
+        {
+            _autoxform->setCullingActive( true );
+            this->dirtyBound();
+        }
     }
     
     // For an intersection visitor, ALWAYS traverse the autoxform instead of the 
@@ -217,12 +160,6 @@ OrthoNode::traverse( osg::NodeVisitor& nv )
     }
 }
 
-osg::BoundingSphere
-OrthoNode::computeBound() const
-{
-    return osg::BoundingSphere(_matxform->getMatrix().getTrans(), 1000.0);
-}
-
 void
 OrthoNode::setMapNode( MapNode* mapNode )
 {
@@ -239,11 +176,8 @@ OrthoNode::setMapNode( MapNode* mapNode )
         }
 
         // same goes for the horizon culler:
-        if ( _horizonCulling || (oldMapNode == 0L && mapNode->isGeocentric()) )
-        {
-            setHorizonCulling( false );
-            setHorizonCulling( true );
-        }
+        _horizonCuller->setHorizon( Horizon(*mapNode->getMapSRS()->getEllipsoid()) );
+        setHorizonCulling( _horizonCullingRequested );
 
         // re-apply the position since the map has changed
         setPosition( getPosition() );
@@ -287,16 +221,6 @@ OrthoNode::applyStyle(const Style& style)
     if ( text && text->declutter().isSet() )
     {
         Decluttering::setEnabled( this->getOrCreateStateSet(), (text->declutter() == true) );
-        //if ( text->declutter() == true )
-        //{
-        //    this->getOrCreateStateSet()->setRenderBinDetails(
-        //        0,
-        //        OSGEARTH_DECLUTTER_BIN );
-        //}
-        //else
-        //{
-        //    this->getOrCreateStateSet()->setRenderBinToInherit();
-        //}
     }
 
 
@@ -315,16 +239,6 @@ OrthoNode::applyStyle(const Style& style)
     if ( icon && icon->declutter().isSet() )
     {
         Decluttering::setEnabled( this->getOrCreateStateSet(), (icon->declutter() == true) );
-        //if ( icon->declutter() == true )
-        //{
-        //    this->getOrCreateStateSet()->setRenderBinDetails(
-        //        0,
-        //        OSGEARTH_DECLUTTER_BIN );
-        //}
-        //else
-        //{
-        //    this->getOrCreateStateSet()->setRenderBinToInherit();
-        //}
     }
 
     // check for occlusion culling
@@ -365,10 +279,6 @@ OrthoNode::updateTransforms( const GeoPoint& p, osg::Node* patch )
         _matxform->setMatrix( local2world );
         
         osg::Vec3d world = local2world.getTrans();
-        if (_horizonCuller.valid())
-        {
-            _horizonCuller->_world = world;
-        }
 
         if (_occlusionCuller.valid())
         {                                
@@ -408,32 +318,18 @@ OrthoNode::getLocalOffset() const
 bool
 OrthoNode::getHorizonCulling() const
 {
-    return _horizonCulling;
+    return _horizonCullingRequested;
 }
 
 void
-OrthoNode::setHorizonCulling( bool value )
+OrthoNode::setHorizonCulling(bool value)
 {
-    if ( _horizonCulling != value )
-    {
-        _horizonCulling = value;
-
-        if ( _horizonCulling && getMapNode() && getMapNode()->isGeocentric() )
-        {
-            osg::Vec3d world = _autoxform->getPosition();
+    _horizonCullingRequested = value;
 
-            _horizonCuller = new CullNodeByHorizon(world, getMapNode()->getMapSRS()->getEllipsoid());
-            addCullCallback( _horizonCuller.get()  );
-        }
-        else
-        {
-            if (_horizonCuller.valid())
-            {
-                removeCullCallback( _horizonCuller.get() );
-                _horizonCuller = 0;
-            }
-        }
-    }
+    _horizonCuller->setEnabled(
+        _horizonCullingRequested &&
+        getMapNode() &&
+        getMapNode()->isGeocentric() );
 }
 
 osg::Vec3d
diff --git a/src/osgEarthAnnotation/PlaceNode b/src/osgEarthAnnotation/PlaceNode
index 6732ed8..afa6905 100644
--- a/src/osgEarthAnnotation/PlaceNode
+++ b/src/osgEarthAnnotation/PlaceNode
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
@@ -99,7 +102,7 @@ namespace osgEarth { namespace Annotation
          * Text label content
          */
         void setText( const std::string& text );
-        const std::string& getText() const { return _text; }
+        virtual const std::string& getText() const { return _text; }
 
         /**
          * Style (for text and placement)
diff --git a/src/osgEarthAnnotation/PlaceNode.cpp b/src/osgEarthAnnotation/PlaceNode.cpp
index b47dd48..2967813 100644
--- a/src/osgEarthAnnotation/PlaceNode.cpp
+++ b/src/osgEarthAnnotation/PlaceNode.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
@@ -84,6 +87,11 @@ PlaceNode::init()
     getAttachPoint()->removeChildren(0, getAttachPoint()->getNumChildren());
 
     _geode = new osg::Geode();
+
+    // ensure that (0,0,0) is the bounding sphere control/center point.
+    // useful for things like horizon culling.
+    _geode->setComputeBoundingSphereCallback(new ControlPointCallback());
+
     osg::Drawable* text = 0L;
 
     // If there's no explicit text, look to the text symbol for content.
@@ -112,6 +120,10 @@ PlaceNode::init()
             {
                 imageURI = URI( icon->url()->eval(), icon->url()->uriContext() );
             }
+            else if (icon->getImage())
+            {
+                _image = icon->getImage();
+            }
         }
 
         if ( !imageURI.empty() )
diff --git a/src/osgEarthAnnotation/RectangleNode b/src/osgEarthAnnotation/RectangleNode
index 3e98423..fefe7f7 100644
--- a/src/osgEarthAnnotation/RectangleNode
+++ b/src/osgEarthAnnotation/RectangleNode
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/osgEarthAnnotation/RectangleNode.cpp b/src/osgEarthAnnotation/RectangleNode.cpp
index f0a1b70..3ac110d 100644
--- a/src/osgEarthAnnotation/RectangleNode.cpp
+++ b/src/osgEarthAnnotation/RectangleNode.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/osgEarthAnnotation/ScaleDecoration b/src/osgEarthAnnotation/ScaleDecoration
index b789335..dbb5e47 100644
--- a/src/osgEarthAnnotation/ScaleDecoration
+++ b/src/osgEarthAnnotation/ScaleDecoration
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/osgEarthAnnotation/TrackNode b/src/osgEarthAnnotation/TrackNode
index 59b8995..371cdac 100644
--- a/src/osgEarthAnnotation/TrackNode
+++ b/src/osgEarthAnnotation/TrackNode
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/osgEarthAnnotation/TrackNode.cpp b/src/osgEarthAnnotation/TrackNode.cpp
index 958e59a..1c2c10e 100644
--- a/src/osgEarthAnnotation/TrackNode.cpp
+++ b/src/osgEarthAnnotation/TrackNode.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/osgEarthDrivers/CMakeLists.txt b/src/osgEarthDrivers/CMakeLists.txt
index 7cabbc4..9c4e18a 100644
--- a/src/osgEarthDrivers/CMakeLists.txt
+++ b/src/osgEarthDrivers/CMakeLists.txt
@@ -1,5 +1,5 @@
 #---------------------------------------------------
-# OSG CMAKE SUPPORT 
+# OSG CMAKE SUPPORT
 # (C) by Michael Wagner, mtw at shared-reality.com 2005
 # (C) Eric Wing, Luigi Calori and Robert Osfield 2006-2007
 #---------------------------------------------------
@@ -20,7 +20,7 @@ SET(TARGET_DEFAULT_PREFIX "osgdb_")
 SET(TARGET_DEFAULT_LABEL_PREFIX "Plugin")
 
 #OpenThreads, osg, osgDB and osgUtil are included elsewhere.
-SET(TARGET_COMMON_LIBRARIES 
+SET(TARGET_COMMON_LIBRARIES
     osgEarth
 )
 
@@ -31,79 +31,11 @@ SET(OSGEARTH_PLUGINS_FOLDER Plugins)
 #
 #  NodeKit/Psudo loader plugins
 #
-ADD_SUBDIRECTORY(earth)
-ADD_SUBDIRECTORY(kml)
 
-ADD_SUBDIRECTORY(wcs)
-ADD_SUBDIRECTORY(wms)
-ADD_SUBDIRECTORY(tilecache)
-ADD_SUBDIRECTORY(tileservice)
-ADD_SUBDIRECTORY(yahoo)
-ADD_SUBDIRECTORY(arcgis_map_cache)
-ADD_SUBDIRECTORY(arcgis)
-ADD_SUBDIRECTORY(tms)
-ADD_SUBDIRECTORY(vpb)
-ADD_SUBDIRECTORY(osg)
-ADD_SUBDIRECTORY(agglite)
-ADD_SUBDIRECTORY(model_simple)
-ADD_SUBDIRECTORY(debug)
-ADD_SUBDIRECTORY(cache_filesystem)
-ADD_SUBDIRECTORY(refresh)
-ADD_SUBDIRECTORY(xyz)
-ADD_SUBDIRECTORY(bing)
-ADD_SUBDIRECTORY(tileindex)
 
-ADD_SUBDIRECTORY(noise)
-ADD_SUBDIRECTORY(splat_mask)
-ADD_SUBDIRECTORY(colorramp)
+SUBDIRLIST(PLUGIN_DIRS ${CMAKE_CURRENT_SOURCE_DIR})
 
-IF(GDAL_FOUND)
-  ADD_SUBDIRECTORY(gdal)
-  ADD_SUBDIRECTORY(feature_ogr)
-  ADD_SUBDIRECTORY(feature_wfs)  
-  ADD_SUBDIRECTORY(feature_tfs)
-  ADD_SUBDIRECTORY(model_feature_stencil)
-  ADD_SUBDIRECTORY(model_feature_geom)
-  ADD_SUBDIRECTORY(mask_feature)
-  ADD_SUBDIRECTORY(label_overlay)
-  ADD_SUBDIRECTORY(label_annotation)
-ENDIF(GDAL_FOUND)
-
-IF(SQLITE3_FOUND)
-  ADD_SUBDIRECTORY(mbtiles)
-ENDIF(SQLITE3_FOUND)
-
-IF(LEVELDB_FOUND)
-  ADD_SUBDIRECTORY(cache_leveldb)
-ENDIF(LEVELDB_FOUND)
-
-ADD_SUBDIRECTORY(engine_mp)
-ADD_SUBDIRECTORY(engine_byo)
-
-IF(V8_FOUND)
-  ADD_SUBDIRECTORY(script_engine_v8)
-ELSEIF(NOT WITH_EXTERNAL_DUKTAPE OR DUKTAPE_FOUND)
-  ADD_SUBDIRECTORY(script_engine_duktape)
-ENDIF(V8_FOUND)
-
-IF(JAVASCRIPTCORE_FOUND)
-  ADD_SUBDIRECTORY(script_engine_javascriptcore)
-ENDIF(JAVASCRIPTCORE_FOUND)
-
-ADD_SUBDIRECTORY(vdatum_egm84)
-ADD_SUBDIRECTORY(vdatum_egm96)
-ADD_SUBDIRECTORY(vdatum_egm2008)
-
-ADD_SUBDIRECTORY(sky_gl)
-ADD_SUBDIRECTORY(sky_simple)
-IF(SILVERLINING_FOUND)
-  ADD_SUBDIRECTORY(sky_silverlining)
-ENDIF(SILVERLINING_FOUND)
-
-ADD_SUBDIRECTORY(ocean_simple)
-IF(TRITON_FOUND)
-  ADD_SUBDIRECTORY(ocean_triton)
-ENDIF(TRITON_FOUND)
-
-# temporary
-ADD_SUBDIRECTORY(template_matclass)
+FOREACH(subdir ${PLUGIN_DIRS})
+    # MESSAGE("Adding driver ${subdir}")
+    ADD_SUBDIRECTORY(${subdir})
+ENDFOREACH()
\ No newline at end of file
diff --git a/src/osgEarthDrivers/agglite/AGGLiteOptions b/src/osgEarthDrivers/agglite/AGGLiteOptions
index 4db521e..76da64f 100644
--- a/src/osgEarthDrivers/agglite/AGGLiteOptions
+++ b/src/osgEarthDrivers/agglite/AGGLiteOptions
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -79,7 +79,7 @@ namespace osgEarth { namespace Drivers
             conf.getIfSet( "gamma", _gamma );
         }
 
-        optional<bool> _optimizeLineSampling;
+        optional<bool>   _optimizeLineSampling;
         optional<double> _gamma;
     };
 
diff --git a/src/osgEarthDrivers/agglite/AGGLiteRasterizerTileSource.cpp b/src/osgEarthDrivers/agglite/AGGLiteRasterizerTileSource.cpp
index 4c56f6a..dfcac4c 100644
--- a/src/osgEarthDrivers/agglite/AGGLiteRasterizerTileSource.cpp
+++ b/src/osgEarthDrivers/agglite/AGGLiteRasterizerTileSource.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -26,6 +26,7 @@
 #include <osgEarthSymbology/AGG.h>
 #include <osgEarth/Registry>
 #include <osgEarth/FileUtils>
+#include <osgEarth/ImageUtils>
 
 #include <osg/Notify>
 #include <osgDB/FileNameUtils>
@@ -48,6 +49,57 @@ using namespace osgEarth::Symbology;
 using namespace osgEarth::Drivers;
 using namespace OpenThreads;
 
+namespace
+{
+    struct float32
+    {
+        float32() : value(NO_DATA_VALUE) { }
+        float32(float v) : value(v) { }
+
+        float value;
+    };
+
+    struct span_coverage32
+    {
+        static void render(unsigned char* ptr, 
+                           int x,
+                           unsigned count, 
+                           const unsigned char* covers, 
+                           const float32& c)
+        {
+            unsigned char* p = ptr + (x << 2);
+            float* f = (float*)p;
+            do
+            {
+                unsigned char cover = *covers++;
+                int hasData = cover > 127;
+                *f++ = hasData ? c.value : NO_DATA_VALUE;
+            }
+            while(--count);
+        }
+
+        static void hline(unsigned char* ptr, 
+                          int x,
+                          unsigned count, 
+                          const float32& c)
+        {
+            unsigned char* p = ptr + (x << 2);
+            float* f = (float*)p;
+            do {
+                *f++ = c.value;
+            }
+            while(--count);
+        }
+
+        static float32 get(unsigned char* ptr, int x)
+        {
+            unsigned char* p = ptr + (x << 2);
+            float* f = (float*)p;
+            return float32(*f);
+        }
+    };
+}
+
 /********************************************************************/
 
 class AGGLiteRasterizerTileSource : public FeatureTileSource
@@ -66,16 +118,42 @@ public:
     }
 
     //override
+    osg::Image* allocateImage()
+    {
+        osg::Image* image = 0L;
+        if ( _options.coverage() == true )
+        {
+            image = new osg::Image();
+            image->allocateImage(getPixelsPerTile(), getPixelsPerTile(), 1, GL_LUMINANCE, GL_FLOAT);
+            image->setInternalTextureFormat(GL_LUMINANCE32F_ARB);
+            ImageUtils::markAsUnNormalized(image, true);
+        }
+        return image;
+    }
+
+    //override
     bool preProcess(osg::Image* image, osg::Referenced* buildData)
     {
         agg::rendering_buffer rbuf( image->data(), image->s(), image->t(), image->s()*4 );
-        agg::renderer<agg::span_abgr32> ren(rbuf);
-        ren.clear(agg::rgba8(0,0,0,0));
+
+        // 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) );
+        }
+        else
+        {
+            agg::renderer<agg::span_abgr32, agg::rgba8> ren(rbuf);
+            ren.clear(agg::rgba8(0,0,0,0));
+        }
         return true;
     }
 
     //override
     bool renderFeaturesForStyle(
+        Session*           session,
         const Style&       style,
         const FeatureList& features,
         osg::Referenced*   buildData,
@@ -83,11 +161,12 @@ public:
         osg::Image*        image )
     {
         // A processing context to use with the filters:
-        FilterContext context;
+        FilterContext context( session );
         context.setProfile( getFeatureSource()->getFeatureProfile() );
 
         const LineSymbol*    masterLine = style.getSymbol<LineSymbol>();
         const PolygonSymbol* masterPoly = style.getSymbol<PolygonSymbol>();
+        const CoverageSymbol* masterCov = style.getSymbol<CoverageSymbol>();
 
         // sort into bins, making a copy for lines that require buffering.
         FeatureList polygons;
@@ -97,9 +176,13 @@ public:
         {
             if ( f->get()->getGeometry() )
             {
+                bool hasPoly = false;
+                bool hasLine = false;
+
                 if ( masterPoly || f->get()->style()->has<PolygonSymbol>() )
                 {
                     polygons.push_back( f->get() );
+                    hasPoly = true;
                 }
 
                 if ( masterLine || f->get()->style()->has<LineSymbol>() )
@@ -110,6 +193,16 @@ public:
                         newFeature->setGeometry( newFeature->getGeometry()->cloneAs(Geometry::TYPE_RING) );
                     }
                     lines.push_back( newFeature );
+                    hasLine = true;
+                }
+
+                // if there are no geometry symbols but there is a coverage symbol, default to polygons.
+                if ( !hasLine && !hasPoly )
+                {
+                    if ( masterCov || f->get()->style()->has<CoverageSymbol>() )
+                    {
+                        polygons.push_back( f->get() );
+                    }
                 }
             }
         }
@@ -179,12 +272,11 @@ public:
                             {
                                 // linear to angular? approximate degrees per meter at the 
                                 // latitude of the tile's centroid.
-                                lineWidth = masterLine->stroke()->widthUnits()->convertTo(Units::METERS, lineWidth);
-                                double circ = featureSRS->getEllipsoid()->getRadiusEquator() * 2.0 * osg::PI;
-                                double x, y;
-                                context.profile()->getExtent().getCentroid(x, y);
-                                double radians = (lineWidth/circ) * cos(osg::DegreesToRadians(y));
-                                lineWidth = osg::RadiansToDegrees(radians);
+                                double lineWidthM = masterLine->stroke()->widthUnits()->convertTo(Units::METERS, lineWidth);
+                                double mPerDegAtEquatorInv = 360.0/(featureSRS->getEllipsoid()->getRadiusEquator() * 2.0 * osg::PI);
+                                double lon, lat;
+                                imageExtent.getCentroid(lon, lat);
+                                lineWidth = lineWidthM * mPerDegAtEquatorInv * cos(osg::DegreesToRadians(lat));
                             }
                         }
 
@@ -214,11 +306,14 @@ public:
         agg::rendering_buffer rbuf( image->data(), image->s(), image->t(), image->s()*4 );
 
         // Create the renderer and the rasterizer
-        agg::renderer<agg::span_abgr32> ren(rbuf);
         agg::rasterizer ras;
 
         // Setup the rasterizer
-        ras.gamma(_options.gamma().get());
+        if ( _options.coverage() == true )
+            ras.gamma(1.0);
+        else
+            ras.gamma(_options.gamma().get());
+
         ras.filling_rule(agg::fill_even_odd);
 
         // construct an extent for cropping the geometry to our tile.
@@ -232,6 +327,12 @@ public:
         cropPoly->push_back( osg::Vec3d( cropExtent.xMax(), cropExtent.yMax(), 0 ));
         cropPoly->push_back( osg::Vec3d( cropExtent.xMin(), cropExtent.yMax(), 0 ));
 
+        // If there's a coverage symbol, make a copy of the expressions so we can evaluate them
+        optional<NumericExpression> covValue;
+        const CoverageSymbol* covsym = style.get<CoverageSymbol>();
+        if (covsym && covsym->valueExpression().isSet())
+            covValue = covsym->valueExpression().get();
+
         // render the polygons
         for(FeatureList::iterator i = polygons.begin(); i != polygons.end(); i++)
         {
@@ -244,9 +345,18 @@ public:
                 const PolygonSymbol* poly =
                     feature->style().isSet() && feature->style()->has<PolygonSymbol>() ? feature->style()->get<PolygonSymbol>() :
                     masterPoly;
+
+                if ( _options.coverage() == true && covValue.isSet() )
+                {
+                    float value = (float)feature->eval(covValue.mutable_value(), &context);
+                    rasterizeCoverage(croppedGeometry.get(), value, frame, ras, rbuf);
+                }
+                else
+                {
+                    osg::Vec4f color = poly->fill()->color();
+                    rasterize(croppedGeometry.get(), color, frame, ras, rbuf);
+                }
                 
-                const osg::Vec4 color = poly ? static_cast<osg::Vec4>(poly->fill()->color()) : osg::Vec4(1,1,1,1);
-                rasterize(croppedGeometry.get(), color, frame, ras, ren);
             }
         }
 
@@ -262,9 +372,16 @@ public:
                 const LineSymbol* line =
                     feature->style().isSet() && feature->style()->has<LineSymbol>() ? feature->style()->get<LineSymbol>() :
                     masterLine;
-                
-                const osg::Vec4 color = line ? static_cast<osg::Vec4>(line->stroke()->color()) : osg::Vec4(1,1,1,1);
-                rasterize(croppedGeometry.get(), color, frame, ras, ren);
+
+                if ( _options.coverage() == true && covValue.isSet() )
+                {
+                    float value = (float)feature->eval(covValue.mutable_value(), &context);
+                    rasterizeCoverage(croppedGeometry.get(), value, frame, ras, rbuf);
+                }
+                else
+                {   osg::Vec4f color = line ? static_cast<osg::Vec4>(line->stroke()->color()) : osg::Vec4(1,1,1,1);
+                    rasterize(croppedGeometry.get(), color, frame, ras, rbuf);
+                }
             }
         }
 
@@ -274,30 +391,57 @@ public:
     //override
     bool postProcess( osg::Image* image, osg::Referenced* data )
     {
-        //convert from ABGR to RGBA
-        unsigned char* pixel = image->data();
-        for(int i=0; i<image->s()*image->t()*4; i+=4, pixel+=4)
+        if ( _options.coverage() == false )
         {
-            std::swap( pixel[0], pixel[3] );
-            std::swap( pixel[1], pixel[2] );
+            //convert from ABGR to RGBA
+            unsigned char* pixel = image->data();
+            for(int i=0; i<image->s()*image->t()*4; i+=4, pixel+=4)
+            {
+                std::swap( pixel[0], pixel[3] );
+                std::swap( pixel[1], pixel[2] );
+            }
         }
+
         return true;
     }
 
-    // rasterizes a geometry.
+    // rasterizes a geometry to color
     void rasterize(const Geometry* geometry, const osg::Vec4& color, RenderFrame& frame, 
-                   agg::rasterizer& ras, agg::renderer<agg::span_abgr32>& ren)
+                   agg::rasterizer& ras, agg::rendering_buffer& buffer)
     {
-        osg::Vec4 c = color;
-        unsigned int a = (unsigned int)(127+(c.a()*255)/2); // scale alpha up
-        agg::rgba8 fgColor( (unsigned int)(c.r()*255), (unsigned int)(c.g()*255), (unsigned int)(c.b()*255), a );
+        unsigned a = (unsigned)(127.0f+(color.a()*255.0f)/2.0f); // scale alpha up
+        agg::rgba8 fgColor = agg::rgba8( (unsigned)(color.r()*255.0f), (unsigned)(color.g()*255.0f), (unsigned)(color.b()*255.0f), a );
+        
+        ConstGeometryIterator gi( geometry );
+        while( gi.hasMore() )
+        {
+            const Geometry* g = gi.next();
 
-        ras.filling_rule( agg::fill_even_odd );
+            for( Geometry::const_iterator p = g->begin(); p != g->end(); p++ )
+            {
+                const osg::Vec3d& p0 = *p;
+                double x0 = frame.xf*(p0.x()-frame.xmin);
+                double y0 = frame.yf*(p0.y()-frame.ymin);
 
+                if ( p == g->begin() )
+                    ras.move_to_d( x0, y0 );
+                else
+                    ras.line_to_d( x0, y0 );
+            }
+        }
+        agg::renderer<agg::span_abgr32, agg::rgba8> ren(buffer);
+        ras.render(ren, fgColor);
+
+        ras.reset();
+    }
+
+
+    void rasterizeCoverage(const Geometry* geometry, float value, RenderFrame& frame, 
+                           agg::rasterizer& ras, agg::rendering_buffer& buffer)
+    {
         ConstGeometryIterator gi( geometry );
         while( gi.hasMore() )
         {
-            c = color;
             const Geometry* g = gi.next();
 
             for( Geometry::const_iterator p = g->begin(); p != g->end(); p++ )
@@ -312,7 +456,9 @@ public:
                     ras.line_to_d( x0, y0 );
             }
         }
-        ras.render(ren, fgColor);
+        
+        agg::renderer<span_coverage32, float32> ren(buffer);
+        ras.render(ren, value);
         ras.reset();
     }
 
diff --git a/src/osgEarthDrivers/arcgis/ArcGISOptions b/src/osgEarthDrivers/arcgis/ArcGISOptions
index 660c76c..e096d4e 100644
--- a/src/osgEarthDrivers/arcgis/ArcGISOptions
+++ b/src/osgEarthDrivers/arcgis/ArcGISOptions
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/arcgis/Extent.h b/src/osgEarthDrivers/arcgis/Extent.h
index d96ec02..93dbe39 100644
--- a/src/osgEarthDrivers/arcgis/Extent.h
+++ b/src/osgEarthDrivers/arcgis/Extent.h
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/arcgis/MapService.h b/src/osgEarthDrivers/arcgis/MapService.h
index a37d133..47cf262 100644
--- a/src/osgEarthDrivers/arcgis/MapService.h
+++ b/src/osgEarthDrivers/arcgis/MapService.h
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/arcgis/ReaderWriterArcGIS.cpp b/src/osgEarthDrivers/arcgis/ReaderWriterArcGIS.cpp
index 460c7fe..3196cb9 100644
--- a/src/osgEarthDrivers/arcgis/ReaderWriterArcGIS.cpp
+++ b/src/osgEarthDrivers/arcgis/ReaderWriterArcGIS.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/osgEarthDrivers/arcgis_map_cache/ReaderWriterArcGISMapCache.cpp b/src/osgEarthDrivers/arcgis_map_cache/ReaderWriterArcGISMapCache.cpp
index c3aa81f..a4a8035 100644
--- a/src/osgEarthDrivers/arcgis_map_cache/ReaderWriterArcGISMapCache.cpp
+++ b/src/osgEarthDrivers/arcgis_map_cache/ReaderWriterArcGISMapCache.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/bing/BingOptions b/src/osgEarthDrivers/bing/BingOptions
index 93cdea2..8770036 100644
--- a/src/osgEarthDrivers/bing/BingOptions
+++ b/src/osgEarthDrivers/bing/BingOptions
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/cache_filesystem/FileSystemCache b/src/osgEarthDrivers/cache_filesystem/FileSystemCache
index d87d873..d9dc8eb 100644
--- a/src/osgEarthDrivers/cache_filesystem/FileSystemCache
+++ b/src/osgEarthDrivers/cache_filesystem/FileSystemCache
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/cache_filesystem/FileSystemCache.cpp b/src/osgEarthDrivers/cache_filesystem/FileSystemCache.cpp
index 0ad470e..7e4eb0b 100644
--- a/src/osgEarthDrivers/cache_filesystem/FileSystemCache.cpp
+++ b/src/osgEarthDrivers/cache_filesystem/FileSystemCache.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -216,7 +216,11 @@ namespace
     bool
     FileSystemCacheBin::binValidForReading(bool silent)
     {
-        if ( !_binPathExists )
+        if ( !_rw.valid() )
+        {
+            _ok = false;
+        }
+        else if ( !_binPathExists )
         {
             if ( osgDB::fileExists(_binPath) )
             {
@@ -241,7 +245,11 @@ namespace
     bool
     FileSystemCacheBin::binValidForWriting(bool silent)
     {
-        if ( !_binPathExists )
+        if ( !_rw.valid() )
+        {
+            _ok = false;
+        }
+        else if ( !_binPathExists )
         {
             osgEarth::makeDirectoryForFile( _metaPath );
 
diff --git a/src/osgEarthDrivers/cache_leveldb/CMakeLists.txt b/src/osgEarthDrivers/cache_leveldb/CMakeLists.txt
index 4ee4733..7b70ddb 100644
--- a/src/osgEarthDrivers/cache_leveldb/CMakeLists.txt
+++ b/src/osgEarthDrivers/cache_leveldb/CMakeLists.txt
@@ -1,3 +1,4 @@
+IF(LEVELDB_FOUND)
 
 INCLUDE_DIRECTORIES( ${LEVELDB_INCLUDE_DIR} )
 
@@ -23,3 +24,5 @@ SET(LIB_NAME cache_leveldb)
 SET(LIB_PUBLIC_HEADERS LevelDBCacheOptions)
 INCLUDE(ModuleInstallOsgEarthDriverIncludes OPTIONAL)
 
+ENDIF(LEVELDB_FOUND)
+
diff --git a/src/osgEarthDrivers/cache_leveldb/LevelDBCache b/src/osgEarthDrivers/cache_leveldb/LevelDBCache
index 09cf10c..f7e3d42 100644
--- a/src/osgEarthDrivers/cache_leveldb/LevelDBCache
+++ b/src/osgEarthDrivers/cache_leveldb/LevelDBCache
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/cache_leveldb/LevelDBCache.cpp b/src/osgEarthDrivers/cache_leveldb/LevelDBCache.cpp
index 42e99af..f21e9a0 100644
--- a/src/osgEarthDrivers/cache_leveldb/LevelDBCache.cpp
+++ b/src/osgEarthDrivers/cache_leveldb/LevelDBCache.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -98,7 +98,10 @@ LevelDBCacheImpl::~LevelDBCacheImpl()
 {
     if ( _db )
     {
-        delete _db;
+        // problem. This destructor causes a lockup sometimes. Perhaps try
+        // upgrading to a newer version of LDB. In the meantime, that's why it
+        // is commented out
+        //delete _db;
         _db = 0L;
     }
 }
diff --git a/src/osgEarthDrivers/cache_leveldb/LevelDBCacheBin b/src/osgEarthDrivers/cache_leveldb/LevelDBCacheBin
index 2269b92..f3405a5 100644
--- a/src/osgEarthDrivers/cache_leveldb/LevelDBCacheBin
+++ b/src/osgEarthDrivers/cache_leveldb/LevelDBCacheBin
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -94,19 +94,23 @@ namespace osgEarth { namespace Drivers { namespace LevelDBCache
             osgDB::Options*      _op;
             Reader(osgDB::ReaderWriter* rw, osgDB::Options* op) : _rw(rw), _op(op) { }
             virtual osgDB::ReaderWriter::ReadResult read(std::istream& in) const = 0;
+            virtual std::string name() const = 0;
         };
 
         struct ImageReader : public Reader {
             ImageReader(osgDB::ReaderWriter* rw, osgDB::Options* op) : Reader(rw, op) { }
             osgDB::ReaderWriter::ReadResult read(std::istream& in) const { return _rw->readImage(in, _op); }
+            std::string name() const { return "ImageReader"; }
         };
         struct NodeReader : public Reader {
             NodeReader(osgDB::ReaderWriter* rw, osgDB::Options* op) : Reader(rw, op) { }
             osgDB::ReaderWriter::ReadResult read(std::istream& in) const { return _rw->readNode(in, _op); }
+            std::string name() const { return "NodeReader"; }
         };
         struct ObjectReader : public Reader {
             ObjectReader(osgDB::ReaderWriter* rw, osgDB::Options* op) : Reader(rw, op) { }
             osgDB::ReaderWriter::ReadResult read(std::istream& in) const { return _rw->readObject(in, _op); }
+            std::string name() const { return "ObjectReader"; }
         };
 
         ReadResult read(const std::string& key, const Reader& reader);
diff --git a/src/osgEarthDrivers/cache_leveldb/LevelDBCacheBin.cpp b/src/osgEarthDrivers/cache_leveldb/LevelDBCacheBin.cpp
index 17de068..779af30 100644
--- a/src/osgEarthDrivers/cache_leveldb/LevelDBCacheBin.cpp
+++ b/src/osgEarthDrivers/cache_leveldb/LevelDBCacheBin.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -281,13 +281,18 @@ LevelDBCacheBin::read(const std::string& key, const Reader& reader)
     osgDB::ReaderWriter::ReadResult r = reader.read(datastream);
     if ( !r.success() )
     {
-        OE_WARN << LC << "Read failure - bad key?" << std::endl;
+        OE_WARN << LC << "Cache read failure!"
+            << "\n reader = " << reader.name()
+            << "\n error detail = " << r.message()
+            << "\n data value = " << datavalue
+            << "\n";
+
         return ReadResult(ReadResult::RESULT_READER_ERROR);
     }
         
     if ( _debug )
     {
-        OE_NOTICE << LC << "Read (" << key << ") from bin " << getID() << std::endl;
+        OE_NOTICE << LC << "Bin " << getID() << ": read (" << key << ")\n";
     }
 
     // if there's a size limit, we need to 'touch' the record.
@@ -335,16 +340,31 @@ LevelDBCacheBin::write(const std::string& key, const osg::Object* object, const
 
     if ( dynamic_cast<const osg::Image*>(object) )
     {
+        if ( (_rw->supportedFeatures() & _rw->FEATURE_WRITE_IMAGE) == 0 )
+        {
+            OE_WARN << LC << "Internal: tried to write image to " << _rw->className() << "\n";
+            return false;
+        }
         r = _rw->writeImage( *static_cast<const osg::Image*>(object), datastream, _rwOptions.get() );
         objWriteOK = r.success();
     }
     else if ( dynamic_cast<const osg::Node*>(object) )
     {
+        if ( (_rw->supportedFeatures() & _rw->FEATURE_WRITE_NODE) == 0 )
+        {
+            OE_WARN << LC << "Internal: tried to write node to " << _rw->className() << "\n";
+            return false;
+        }
         r = _rw->writeNode( *static_cast<const osg::Node*>(object), datastream, _rwOptions.get() );
         objWriteOK = r.success();
     }
     else
     {
+        if ( (_rw->supportedFeatures() & _rw->FEATURE_WRITE_OBJECT) == 0 )
+        {
+            OE_WARN << LC << "Internal: tried to write an object to " << _rw->className() << "\n";
+            return false;
+        }
         r = _rw->writeObject( *object, datastream );
         objWriteOK = r.success();
     }
@@ -378,7 +398,7 @@ LevelDBCacheBin::write(const std::string& key, const osg::Object* object, const
             
             if ( _debug )
             {
-                OE_NOTICE << LC << "Wrote (" << dataKey(key) << ") to bin " << getID() << std::endl;
+                OE_NOTICE << LC << "Bin " << getID() << ": wrote (" << key << ")\n";
             }
         }
     }
@@ -386,8 +406,8 @@ LevelDBCacheBin::write(const std::string& key, const osg::Object* object, const
         
     if ( !objWriteOK )
     {
-        OE_WARN << LC << "FAILED to write \"" << key << "\" to bin " << getID()
-            << "; msg = \"" << r.message() << "\"" << std::endl;
+        OE_WARN << LC << "Bin " << getID() << ": FAILED to write (" << key << "); msg = \"" 
+            << r.message() << "\"\n";
     }
 
     return objWriteOK;
@@ -521,7 +541,7 @@ LevelDBCacheBin::touch(const std::string& key)
     }
     else if ( _debug )
     {
-        OE_NOTICE << LC << "Touched (" << key << ") in bin " << getID() << std::endl;
+        OE_NOTICE << LC << "Bin " << getID() << ": touch (" << key << ")\n";
     }
     return status.ok();
 }
diff --git a/src/osgEarthDrivers/cache_leveldb/LevelDBCacheDriver.cpp b/src/osgEarthDrivers/cache_leveldb/LevelDBCacheDriver.cpp
index 0bd0b37..6e5b3cd 100644
--- a/src/osgEarthDrivers/cache_leveldb/LevelDBCacheDriver.cpp
+++ b/src/osgEarthDrivers/cache_leveldb/LevelDBCacheDriver.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/cache_leveldb/LevelDBCacheOptions b/src/osgEarthDrivers/cache_leveldb/LevelDBCacheOptions
index 43152eb..7287595 100644
--- a/src/osgEarthDrivers/cache_leveldb/LevelDBCacheOptions
+++ b/src/osgEarthDrivers/cache_leveldb/LevelDBCacheOptions
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/cache_leveldb/Tracker b/src/osgEarthDrivers/cache_leveldb/Tracker
index ebf29b5..c49bb01 100644
--- a/src/osgEarthDrivers/cache_leveldb/Tracker
+++ b/src/osgEarthDrivers/cache_leveldb/Tracker
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/colorramp/ColorRampOptions b/src/osgEarthDrivers/colorramp/ColorRampOptions
index 08a872c..dd042b0 100644
--- a/src/osgEarthDrivers/colorramp/ColorRampOptions
+++ b/src/osgEarthDrivers/colorramp/ColorRampOptions
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/colorramp/ColorRampTileSource.cpp b/src/osgEarthDrivers/colorramp/ColorRampTileSource.cpp
index 7b0b804..29a938e 100644
--- a/src/osgEarthDrivers/colorramp/ColorRampTileSource.cpp
+++ b/src/osgEarthDrivers/colorramp/ColorRampTileSource.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -131,7 +131,7 @@ public:
         }
         return NULL;
     }
-
+    
 
 private:
     const ColorRampOptions _options;
diff --git a/src/osgEarthDrivers/debug/DebugOptions b/src/osgEarthDrivers/debug/DebugOptions
index c90b1a9..3877d26 100644
--- a/src/osgEarthDrivers/debug/DebugOptions
+++ b/src/osgEarthDrivers/debug/DebugOptions
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -51,7 +51,7 @@ namespace osgEarth { namespace Drivers
         Config getConfig() const {
             Config conf = TileSourceOptions::getConfig();
             conf.updateIfSet( "color", _colorCode );
-            conf.updateIfSet( "invertY", _invertY );
+            conf.updateIfSet( "inverty", _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( "inverty", _invertY);
         }
 
         optional<std::string> _colorCode;
diff --git a/src/osgEarthDrivers/debug/DebugTileSource.cpp b/src/osgEarthDrivers/debug/DebugTileSource.cpp
index 3fbe049..18109b0 100644
--- a/src/osgEarthDrivers/debug/DebugTileSource.cpp
+++ b/src/osgEarthDrivers/debug/DebugTileSource.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/earth/EarthFileSerializer b/src/osgEarthDrivers/earth/EarthFileSerializer
index ff139e5..a8f6453 100644
--- a/src/osgEarthDrivers/earth/EarthFileSerializer
+++ b/src/osgEarthDrivers/earth/EarthFileSerializer
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/earth/EarthFileSerializer1.cpp b/src/osgEarthDrivers/earth/EarthFileSerializer1.cpp
index 35caa0f..58f5d04 100644
--- a/src/osgEarthDrivers/earth/EarthFileSerializer1.cpp
+++ b/src/osgEarthDrivers/earth/EarthFileSerializer1.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/earth/EarthFileSerializer2.cpp b/src/osgEarthDrivers/earth/EarthFileSerializer2.cpp
index 63f5c20..f08cb9a 100644
--- a/src/osgEarthDrivers/earth/EarthFileSerializer2.cpp
+++ b/src/osgEarthDrivers/earth/EarthFileSerializer2.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -19,6 +19,7 @@
 #include "EarthFileSerializer"
 #include <osgEarth/FileUtils>
 #include <osgEarth/MapFrame>
+#include <osgEarth/Extension>
 
 using namespace osgEarth_osgearth;
 using namespace osgEarth;
@@ -111,16 +112,33 @@ EarthFileSerializer2::deserialize( const Config& conf, const std::string& refere
         osgDB::Registry::instance()->getDataFilePathList().push_back( path );
     }
 
-    MapNode* mapNode = new MapNode( map, mapNodeOptions );
+    osg::ref_ptr<MapNode> mapNode = new MapNode( map, mapNodeOptions );
+
+    // External configs. Support both "external" and "extensions" tags.
 
-    // External configs:
     Config ext = conf.child( "external" );
+    if ( ext.empty() )
+        ext = conf.child( "extensions" );
+
     if ( !ext.empty() )
     {
+        // save the configuration in case we need to write it back out later
         mapNode->externalConfig() = ext;
+
+        // locate and install any registered extensions.
+        ConfigSet extensions = ext.children();
+        for(ConfigSet::const_iterator i = extensions.begin(); i != extensions.end(); ++i)
+        {
+            std::string name = i->key();
+            Extension* extension = Extension::create( name, *i );
+            if ( extension )
+            {
+                mapNode->addExtension( extension );
+            }
+        }
     }
 
-    return mapNode;
+    return mapNode.release();
 }
 
 
diff --git a/src/osgEarthDrivers/earth/ReaderWriterOsgEarth.cpp b/src/osgEarthDrivers/earth/ReaderWriterOsgEarth.cpp
index 8b996e9..c1e3bbc 100644
--- a/src/osgEarthDrivers/earth/ReaderWriterOsgEarth.cpp
+++ b/src/osgEarthDrivers/earth/ReaderWriterOsgEarth.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -57,6 +57,26 @@ using namespace osgEarth;
 #   endif
 #endif
 
+namespace
+{
+    void recursiveUniqueKeyMerge(Config& lhs, const Config& rhs)
+    {
+        if ( rhs.value() != lhs.value() )
+        {
+            lhs.value() = rhs.value();
+        }
+
+        for(ConfigSet::const_iterator rhsChild = rhs.children().begin(); rhsChild != rhs.children().end(); ++rhsChild)
+        {
+            Config* lhsChild = lhs.mutable_child(rhsChild->key());
+            if ( lhsChild )
+                recursiveUniqueKeyMerge( *lhsChild, *rhsChild );
+            else
+                lhs.add( *rhsChild );
+        }
+    }
+}
+
 
 class ReaderWriterEarth : public osgDB::ReaderWriter
 {
@@ -139,13 +159,6 @@ class ReaderWriterEarth : public osgDB::ReaderWriter
                 return ReadResult( new MapNode() );
             }
 
-            else if ( fileName == "__cube.earth" )
-            {
-                MapOptions options;
-                options.coordSysType() = MapOptions::CSTYPE_GEOCENTRIC_CUBE;
-                return ReadResult( new MapNode( new Map(options) ) );
-            }
-
             else
             {
                 std::string fullFileName = fileName;
@@ -205,7 +218,32 @@ class ReaderWriterEarth : public osgDB::ReaderWriter
                 else
                 {
                     if ( conf.value("version") != "2" )
-                        OE_INFO << LC << "No valid earth file version; assuming version='2'" << std::endl;
+                        OE_DEBUG << LC << "No valid earth file version; assuming version='2'" << std::endl;
+
+                    // attempt to parse a "default options" JSON string:
+                    std::string defaultConfStr;
+                    if ( options )
+                    {
+                        defaultConfStr = options->getPluginStringData("osgEarth.defaultOptions");
+                        if ( !defaultConfStr.empty() )
+                        {
+                            Config optionsConf("options");
+                            if (optionsConf.fromJSON(defaultConfStr))
+                            {
+                                //OE_NOTICE << "\n\nOriginal = \n" << conf.toJSON(true) << "\n";
+                                Config* original = conf.mutable_child("options");
+                                if ( original )
+                                {
+                                    recursiveUniqueKeyMerge(optionsConf, *original);
+                                }
+                                if ( !optionsConf.empty() )
+                                {
+                                    conf.set("options", optionsConf);
+                                }
+                                //OE_NOTICE << "\n\nMerged = \n" << conf.toJSON(true) << "\n";
+                            }
+                        }
+                    }
 
                     EarthFileSerializer2 ser;
                     mapNode = ser.deserialize( conf, refURI );
diff --git a/src/osgEarthDrivers/engine_byo/BYOTerrainEngineDriver.cpp b/src/osgEarthDrivers/engine_byo/BYOTerrainEngineDriver.cpp
index 1622358..61bdaa9 100644
--- a/src/osgEarthDrivers/engine_byo/BYOTerrainEngineDriver.cpp
+++ b/src/osgEarthDrivers/engine_byo/BYOTerrainEngineDriver.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/engine_byo/BYOTerrainEngineNode b/src/osgEarthDrivers/engine_byo/BYOTerrainEngineNode
index f87d0e4..d5d1ed3 100644
--- a/src/osgEarthDrivers/engine_byo/BYOTerrainEngineNode
+++ b/src/osgEarthDrivers/engine_byo/BYOTerrainEngineNode
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/engine_byo/BYOTerrainEngineNode.cpp b/src/osgEarthDrivers/engine_byo/BYOTerrainEngineNode.cpp
index 88d1ef3..184a54b 100644
--- a/src/osgEarthDrivers/engine_byo/BYOTerrainEngineNode.cpp
+++ b/src/osgEarthDrivers/engine_byo/BYOTerrainEngineNode.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/osgEarthDrivers/engine_byo/BYOTerrainEngineOptions b/src/osgEarthDrivers/engine_byo/BYOTerrainEngineOptions
index c2751eb..7d6198b 100644
--- a/src/osgEarthDrivers/engine_byo/BYOTerrainEngineOptions
+++ b/src/osgEarthDrivers/engine_byo/BYOTerrainEngineOptions
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/engine_byo/Common b/src/osgEarthDrivers/engine_byo/Common
index 7f55bec..faf4a3f 100644
--- a/src/osgEarthDrivers/engine_byo/Common
+++ b/src/osgEarthDrivers/engine_byo/Common
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/engine_mp/CMakeLists.txt b/src/osgEarthDrivers/engine_mp/CMakeLists.txt
index 0ac03e2..44c0886 100644
--- a/src/osgEarthDrivers/engine_mp/CMakeLists.txt
+++ b/src/osgEarthDrivers/engine_mp/CMakeLists.txt
@@ -1,7 +1,23 @@
 
 SET(TARGET_COMMON_LIBRARIES ${TARGET_COMMON_LIBRARIES} osgEarthSymbology)
 
+set(TARGET_GLSL
+    MPEngine.vert.model.glsl
+    MPEngine.vert.view.glsl
+    MPEngine.frag.glsl)
+
+set(TARGET_IN
+    MPShaders.cpp.in)
+
+set(SHADERS_CPP "${CMAKE_CURRENT_BINARY_DIR}/AutoGenShaders.cpp")
+
+configure_shaders(
+    MPShaders.cpp.in
+    ${SHADERS_CPP}
+    ${TARGET_GLSL} )
+
 SET(TARGET_SRC
+    HeightFieldCache.cpp
     KeyNodeFactory.cpp
     MPGeometry.cpp
     MPTerrainEngineNode.cpp
@@ -15,14 +31,17 @@ SET(TARGET_SRC
     TileNodeRegistry.cpp
     TileModelFactory.cpp
     TilePagedLOD.cpp
+    ${SHADERS_CPP}
 )
 
 SET(TARGET_H
     Common
     DynamicLODScaleCallback
+    HeightFieldCache
     FileLocationCallback
     KeyNodeFactory
     MPGeometry
+    MPShaders
     MPTerrainEngineNode
     MPTerrainEngineOptions
     QuickReleaseGLObjects
@@ -37,7 +56,7 @@ SET(TARGET_H
     TilePagedLOD
 )
 
-SETUP_PLUGIN(osgearth_engine_mp)
+setup_plugin(osgearth_engine_mp)
 
 # to install public driver includes:
 SET(LIB_NAME engine_mp)
diff --git a/src/osgEarthDrivers/engine_mp/Common b/src/osgEarthDrivers/engine_mp/Common
index 5520d0e..0736efc 100644
--- a/src/osgEarthDrivers/engine_mp/Common
+++ b/src/osgEarthDrivers/engine_mp/Common
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/engine_mp/DynamicLODScaleCallback b/src/osgEarthDrivers/engine_mp/DynamicLODScaleCallback
index 341a1a4..d05eda1 100644
--- a/src/osgEarthDrivers/engine_mp/DynamicLODScaleCallback
+++ b/src/osgEarthDrivers/engine_mp/DynamicLODScaleCallback
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/osgEarthDrivers/engine_mp/FileLocationCallback b/src/osgEarthDrivers/engine_mp/FileLocationCallback
index ec6aa62..eaa6518 100644
--- a/src/osgEarthDrivers/engine_mp/FileLocationCallback
+++ b/src/osgEarthDrivers/engine_mp/FileLocationCallback
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/osgEarthDrivers/engine_mp/HeightFieldCache b/src/osgEarthDrivers/engine_mp/HeightFieldCache
new file mode 100644
index 0000000..ac4a9d2
--- /dev/null
+++ b/src/osgEarthDrivers/engine_mp/HeightFieldCache
@@ -0,0 +1,99 @@
+/* -*-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_DRIVERS_MP_TERRAIN_ENGINE_HFCACHE
+#define OSGEARTH_DRIVERS_MP_TERRAIN_ENGINE_HFCACHE 1
+
+#include "Common"
+#include "TileNode"
+#include "TileNodeRegistry"
+#include "MPTerrainEngineOptions"
+#include <osgEarth/Map>
+#include <osgEarth/Progress>
+#include <osgEarth/ThreadingUtils>
+#include <osgEarth/Containers>
+#include <osgEarth/HeightFieldUtils>
+#include <osgEarth/MapFrame>
+#include <osgEarth/MapInfo>
+
+namespace osgEarth { namespace Drivers { namespace MPTerrainEngine
+{
+    using namespace osgEarth;
+
+    /** Key into the height field cache */
+    struct HFKey 
+    {
+        TileKey               _key;
+        Revision              _revision;
+        ElevationSamplePolicy _samplePolicy;
+
+        bool operator < (const HFKey& rhs) const {
+            if ( _key < rhs._key ) return true;
+            if ( rhs._key < _key ) return false;
+            if ( _revision < rhs._revision ) return true;
+            if ( _revision > rhs._revision ) return false;
+            return _samplePolicy < rhs._samplePolicy;
+        }
+    };
+
+    /** value in the height field cache */
+    struct HFValue
+    {
+        osg::ref_ptr<osg::HeightField> _hf;
+        bool                           _isFallback;
+    };        
+
+    /** caches hightfields for fast neighor lookup */
+    class HeightFieldCache : public osg::Referenced //, public Revisioned
+    {
+    public:
+        HeightFieldCache(const MPTerrainEngineOptions& options);
+
+        void setTileSize(int tileSize)
+        {
+            _tileSize = tileSize;
+        }
+
+        bool getOrCreateHeightField( 
+                const MapFrame&                 frame,
+                const TileKey&                  key,
+                const osg::HeightField*         parent_hf,
+                osg::ref_ptr<osg::HeightField>& out_hf,
+                bool&                           out_isFallback,
+                ElevationSamplePolicy           samplePolicy,
+                ElevationInterpolation          interp,
+                ProgressCallback*               progress );
+
+        void clear()
+        {
+            _cache.clear();
+        }
+
+    private:
+        mutable LRUCache<HFKey,HFValue> _cache;
+        int                             _firstLOD;
+        int                             _tileSize;
+        bool                            _useParentAsReferenceHF;
+    };
+
+} } } // namespace osgEarth::Drivers::MPTerrainEngine
+
+#endif // OSGEARTH_DRIVERS_MP_TERRAIN_ENGINE_HFCACHE
diff --git a/src/osgEarthDrivers/engine_mp/HeightFieldCache.cpp b/src/osgEarthDrivers/engine_mp/HeightFieldCache.cpp
new file mode 100644
index 0000000..d180d89
--- /dev/null
+++ b/src/osgEarthDrivers/engine_mp/HeightFieldCache.cpp
@@ -0,0 +1,145 @@
+/* -*-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 "HeightFieldCache"
+
+using namespace osgEarth::Drivers::MPTerrainEngine;
+using namespace osgEarth;
+
+#define LC "[MP.HeightFieldCache] "
+
+
+HeightFieldCache::HeightFieldCache(const MPTerrainEngineOptions& options) :
+_cache   ( true, 128 ),
+_tileSize( options.tileSize().get() )
+{
+    _useParentAsReferenceHF = (options.elevationSmoothing() == true);
+}
+
+
+bool
+HeightFieldCache::getOrCreateHeightField(const MapFrame&                 frame,
+                                         const TileKey&                  key,
+                                         const osg::HeightField*         parent_hf,
+                                         osg::ref_ptr<osg::HeightField>& out_hf,
+                                         bool&                           out_isFallback,
+                                         ElevationSamplePolicy           samplePolicy,
+                                         ElevationInterpolation          interp,
+                                         ProgressCallback*               progress )
+{
+    // default
+    out_isFallback = false;
+    
+    // check the quick cache.
+    HFKey cachekey;
+    cachekey._key          = key;
+    cachekey._revision     = frame.getRevision();
+    cachekey._samplePolicy = samplePolicy;
+
+    if (progress)
+        progress->stats()["hfcache_try_count"] += 1;
+
+    LRUCache<HFKey,HFValue>::Record rec;
+    if ( _cache.get(cachekey, rec) )
+    {
+        // Found it in the cache.
+        out_hf         = rec.value()._hf.get();
+        out_isFallback = rec.value()._isFallback;
+
+        if (progress)
+        {
+            progress->stats()["hfcache_hit_count"] += 1;
+            progress->stats()["hfcache_hit_rate"] = progress->stats()["hfcache_hit_count"]/progress->stats()["hfcache_try_count"];
+        }
+    }
+
+    else
+    {
+        // Not in the cache, so we need to create a HF.
+        TileKey parentKey = key.createParentKey();
+
+        // Elevation "smoothing" uses the parent HF as the starting point for building
+        // a new tile. This will cause lower-resolution data to propagate down the tree
+        // and fill in any gaps in higher-resolution data. The result will be an elevation
+        // grid that is "smoother" but not neccessarily as accurate.
+        if ( _useParentAsReferenceHF && parent_hf && parentKey.valid() )
+        {
+            out_hf = HeightFieldUtils::createSubSample(
+                parent_hf,
+                parentKey.getExtent(),
+                key.getExtent(),
+                interp );
+        }
+
+        // If we are not smoothing, or we have no parent data, start with a basic
+        // MSL=0 reference heightfield instead.
+        if ( !out_hf.valid() )
+        {
+            out_hf = HeightFieldUtils::createReferenceHeightField( key.getExtent(), _tileSize, _tileSize );
+        }
+
+        // Next, populate it with data from the Map. The map will overwrite our starting
+        // data with real data from the elevation stack.
+        bool populated = frame.populateHeightField(
+            out_hf,
+            key,
+            true, // convertToHAE
+            progress );
+
+        // If the map failed to provide any suitable data sources at all, replace the
+        // heightfield with data from its parent (if available). 
+        if ( !populated )
+        {
+            if ( parentKey.valid() && parent_hf )
+            {        
+                out_hf = HeightFieldUtils::createSubSample(
+                    parent_hf,
+                    parentKey.getExtent(),
+                    key.getExtent(),
+                    interp );
+            }
+
+            if ( !out_hf.valid() )
+            {
+                // NOTE: This is probably no longer be possible, but check anyway for completeness.
+                return false;
+            }
+        }
+
+        // ONLY cache the new heightfield if a parent HF existed. Otherwise the new HF
+        // may contain invalid data. This can happen if this task runs to completion
+        // while the tile's parent expires from the scene graph. In that case the result
+        // of this task will be discarded. Therefore we should not cache the result here.
+        // This was causing intermittent rare "flat tiles" to appear in the terrain.
+        if ( parent_hf )
+        {
+            // cache it.
+            HFValue cacheval;
+            cacheval._hf = out_hf.get();
+            cacheval._isFallback = !populated;
+            _cache.insert( cachekey, cacheval );
+        }
+
+        out_isFallback = !populated;
+    }
+
+    return true;
+}
diff --git a/src/osgEarthDrivers/engine_mp/KeyNodeFactory b/src/osgEarthDrivers/engine_mp/KeyNodeFactory
index 8c623d4..0a9cb6e 100644
--- a/src/osgEarthDrivers/engine_mp/KeyNodeFactory
+++ b/src/osgEarthDrivers/engine_mp/KeyNodeFactory
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/osgEarthDrivers/engine_mp/KeyNodeFactory.cpp b/src/osgEarthDrivers/engine_mp/KeyNodeFactory.cpp
index e308271..3c2cc00 100644
--- a/src/osgEarthDrivers/engine_mp/KeyNodeFactory.cpp
+++ b/src/osgEarthDrivers/engine_mp/KeyNodeFactory.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/osgEarthDrivers/engine_mp/MPEngine.frag.glsl b/src/osgEarthDrivers/engine_mp/MPEngine.frag.glsl
new file mode 100644
index 0000000..d7dbb9c
--- /dev/null
+++ b/src/osgEarthDrivers/engine_mp/MPEngine.frag.glsl
@@ -0,0 +1,37 @@
+#version $GLSL_VERSION_STR
+$GLSL_DEFAULT_PRECISION_FLOAT
+
+#pragma vp_entryPoint "oe_mp_apply_coloring"
+#pragma vp_location   "fragment_coloring"
+#pragma vp_order      "0.5"
+#pragma vp_define     "MP_USE_BLENDING"
+
+uniform bool oe_isPickCamera;
+uniform vec4 oe_terrain_color;
+uniform sampler2D oe_layer_tex;
+uniform int oe_layer_uid;
+uniform int oe_layer_order;
+uniform float oe_layer_opacity;
+
+varying vec4 oe_layer_texc;
+varying float oe_layer_rangeOpacity;
+
+void oe_mp_apply_coloring(inout vec4 color)
+{
+    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);
+    texel.a = mix(texel.a, texel.a*oe_layer_opacity*oe_layer_rangeOpacity, applyImagery);
+
+#ifdef MP_USE_BLENDING
+    float firstLayer = oe_layer_order == 0 ? 1.0 : 0.0;
+    color = mix(texel, texel*texel.a + color*(1.0-texel.a), firstLayer);    
+#else
+    color = texel;
+#endif
+
+    // disable primary coloring for pick cameras.
+    float pick = oe_isPickCamera ? 1.0 : 0.0;
+    color = mix(color, vec4(0), pick);
+}
diff --git a/src/osgEarthDrivers/engine_mp/MPEngine.vert.model.glsl b/src/osgEarthDrivers/engine_mp/MPEngine.vert.model.glsl
new file mode 100644
index 0000000..ec99ca8
--- /dev/null
+++ b/src/osgEarthDrivers/engine_mp/MPEngine.vert.model.glsl
@@ -0,0 +1,15 @@
+#version $GLSL_VERSION_STR
+$GLSL_DEFAULT_PRECISION_FLOAT
+
+#pragma vp_entryPoint "oe_mp_vertModel"
+#pragma vp_location   "vertex_model"
+#pragma vp_order      "-FLT_MAX"
+
+varying vec4 oe_layer_texc;
+varying vec4 oe_layer_tilec;
+
+void oe_mp_vertModel(inout vec4 vertexModel)
+{
+    oe_layer_texc  = gl_MultiTexCoord$MP_PRIMARY_UNIT;
+    oe_layer_tilec = gl_MultiTexCoord$MP_SECONDARY_UNIT;
+}
diff --git a/src/osgEarthDrivers/engine_mp/MPEngine.vert.view.glsl b/src/osgEarthDrivers/engine_mp/MPEngine.vert.view.glsl
new file mode 100644
index 0000000..aee7c17
--- /dev/null
+++ b/src/osgEarthDrivers/engine_mp/MPEngine.vert.view.glsl
@@ -0,0 +1,27 @@
+#version $GLSL_VERSION_STR
+$GLSL_DEFAULT_PRECISION_FLOAT
+
+#pragma vp_entryPoint "oe_mp_vertView"
+#pragma vp_location   "vertex_view"
+#pragma vp_order      "0.5"
+
+uniform float oe_layer_minRange;
+uniform float oe_layer_maxRange;
+uniform float oe_layer_attenuationRange;
+
+varying float oe_layer_rangeOpacity;
+
+void oe_mp_vertView(inout vec4 vertexView)
+{
+    float range = -vertexView.z;
+
+    float attenMin    = oe_layer_minRange - oe_layer_attenuationRange;
+    float attenMax    = oe_layer_maxRange + oe_layer_attenuationRange;
+
+    oe_layer_rangeOpacity =
+        oe_layer_minRange >= oe_layer_maxRange                   ? 1.0 :
+        range >= oe_layer_minRange && range < oe_layer_maxRange  ? 1.0 :
+        range < oe_layer_minRange                                ? clamp((range-attenMin)/oe_layer_attenuationRange, 0.0, 1.0) :
+        range > oe_layer_maxRange                                ? clamp((attenMax-range)/oe_layer_attenuationRange, 0.0, 1.0) :
+        0.0;
+}
diff --git a/src/osgEarthDrivers/engine_mp/MPGeometry b/src/osgEarthDrivers/engine_mp/MPGeometry
index 23a58ca..32a02d2 100644
--- a/src/osgEarthDrivers/engine_mp/MPGeometry
+++ b/src/osgEarthDrivers/engine_mp/MPGeometry
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
@@ -44,6 +47,11 @@ namespace osgEarth { namespace Drivers { namespace MPTerrainEngine
          */
         struct Layer
         {
+            Layer()
+            {
+                _texMatUniformID = ~0;
+            }
+
             osgEarth::UID                  _layerID;
             osg::ref_ptr<const ImageLayer> _imageLayer;
             osg::ref_ptr<osg::Texture>     _tex;
@@ -53,6 +61,10 @@ namespace osgEarth { namespace Drivers { namespace MPTerrainEngine
             float                          _alphaThreshold;
             bool                           _opaque;
 
+            // for shared layers only:
+            osg::Matrixf                   _texMat;          // yes, must be a float matrix
+            unsigned                       _texMatUniformID; // uniform location ID
+
             // in support of std::find
             inline bool operator == (const osgEarth::UID& rhs) const {
                 return _layerID == rhs;
@@ -71,6 +83,8 @@ namespace osgEarth { namespace Drivers { namespace MPTerrainEngine
         unsigned _opacityUniformNameID;
         unsigned _texMatParentUniformNameID;
         unsigned _tileKeyUniformNameID;
+        unsigned _minRangeUniformNameID;
+        unsigned _maxRangeUniformNameID;
 
         // Data stored for each graphics context:
         struct PerContextData {
@@ -87,9 +101,12 @@ namespace osgEarth { namespace Drivers { namespace MPTerrainEngine
 
         int _imageUnit;          // image unit for primary texture
         int _imageUnitParent;    // image unit for secondary (parent) texture
+        int _elevUnit;           // image unit for elevation texture
 
         bool _supportsGLSL;
 
+        osg::ref_ptr<osg::Texture> _elevTex;
+
     public:
         
         // construct a new MPGeometry.
diff --git a/src/osgEarthDrivers/engine_mp/MPGeometry.cpp b/src/osgEarthDrivers/engine_mp/MPGeometry.cpp
index 763dfff..50ad265 100644
--- a/src/osgEarthDrivers/engine_mp/MPGeometry.cpp
+++ b/src/osgEarthDrivers/engine_mp/MPGeometry.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
@@ -25,6 +28,7 @@
 #include <osgEarth/Capabilities>
 
 #include <osgUtil/IncrementalCompileOperation>
+#include <osg/Version>
 
 using namespace osg;
 using namespace osgEarth::Drivers::MPTerrainEngine;
@@ -46,17 +50,21 @@ _imageUnit       ( imageUnit )
 
     _imageUnitParent = _imageUnit + 1; // temp
 
+    _elevUnit = _imageUnit + 2; // temp
+
     // establish uniform name IDs.
     _tileKeyUniformNameID      = osg::Uniform::getNameID( "oe_tile_key" );
     _birthTimeUniformNameID    = osg::Uniform::getNameID( "oe_tile_birthtime" );
     _uidUniformNameID          = osg::Uniform::getNameID( "oe_layer_uid" );
     _orderUniformNameID        = osg::Uniform::getNameID( "oe_layer_order" );
     _opacityUniformNameID      = osg::Uniform::getNameID( "oe_layer_opacity" );
-    _texMatParentUniformNameID = osg::Uniform::getNameID( "oe_layer_parent_matrix" );
+    _texMatParentUniformNameID = osg::Uniform::getNameID( "oe_layer_parent_texmat" );
+    _minRangeUniformNameID     = osg::Uniform::getNameID( "oe_layer_minRange" );
+    _maxRangeUniformNameID     = osg::Uniform::getNameID( "oe_layer_maxRange" );
 
     // we will set these later (in TileModelCompiler)
-    this->setUseVertexBufferObjects(false);
     this->setUseDisplayList(false);
+    this->setUseVertexBufferObjects(true);
 }
 
 
@@ -93,13 +101,22 @@ 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();
-        ext = osg::GL2Extensions::Get( contextID, true );
+#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();
     }
 
@@ -111,6 +128,8 @@ MPGeometry::renderPrimitiveSets(osg::State& state,
     GLint uidLocation           = -1;
     GLint orderLocation         = -1;
     GLint texMatParentLocation  = -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.
@@ -159,6 +178,24 @@ MPGeometry::renderPrimitiveSets(osg::State& state,
     }
 #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
+    //}
+    
+
+    // track the active image unit.
+    int activeImageUnit = -1;
+
+    // remember whether we applied a parent texture.
+    bool usedTexParent = false;
+    bool useMinVisibleRange = false;
+    bool useMaxVisibleRange = false;
+
     if ( _layers.size() > 0 )
     {
         float prev_opacity        = -1.0f;
@@ -168,7 +205,7 @@ MPGeometry::renderPrimitiveSets(osg::State& state,
         // in !renderColor mode b/c these textures could be used by vertex shaders
         // to alter the geometry.
         int sharedLayers = 0;
-        if ( _supportsGLSL )
+        if ( pcp )
         {
             for(unsigned i=0; i<_layers.size(); ++i)
             {
@@ -182,18 +219,43 @@ MPGeometry::renderPrimitiveSets(osg::State& state,
                     int sharedUnit = layer._imageLayer->shareImageUnit().get();
                     {
                         state.setActiveTextureUnit( sharedUnit );
+
                         state.setTexCoordPointer( sharedUnit, layer._texCoords.get() );
                         // bind the texture for this layer to the active share unit.
                         layer._tex->apply( state );
 
-                        // no texture LOD blending for shared layers for now. maybe later.
+                        // Shared layers need a texture matrix since the terrain engine doesn't
+                        // provide a "current texture coordinate set" uniform (i.e. oe_layer_texc)
+                        GLint texMatLocation = 0;
+                        texMatLocation = pcp->getUniformLocation( layer._texMatUniformID );
+                        if ( texMatLocation >= 0 )
+                        {
+                            ext->glUniformMatrix4fv( texMatLocation, 1, GL_FALSE, layer._texMat.ptr() );
+                        }
                     }
                 }
+
+                // check for min/rax range usage.
+                const ImageLayerOptions& layerOptions = layer._imageLayer->getImageLayerOptions();
+
+                if ( layerOptions.minVisibleRange().isSet() )
+                    useMinVisibleRange = true;
+                if ( layerOptions.maxVisibleRange().isSet() )
+                    useMaxVisibleRange = true;
             }
         }
 
-        // track the active image unit.
-        int activeImageUnit = -1;
+        // look up the minRange uniform if necessary
+        if ( useMinVisibleRange && pcp )
+        {
+            minRangeLocation = pcp->getUniformLocation( _minRangeUniformNameID );
+        }
+        
+        // look up the maxRange uniform if necessary
+        if ( useMaxVisibleRange && pcp )
+        {
+            maxRangeLocation = pcp->getUniformLocation( _maxRangeUniformNameID );
+        }
 
         if (renderColor)
         {
@@ -202,7 +264,9 @@ MPGeometry::renderPrimitiveSets(osg::State& state,
             for(first = _layers.size()-1; first > 0; --first)
             {
                 const Layer& layer = _layers[first];
-                if (layer._opaque && 
+                if (layer._opaque &&
+                    //Color filters can modify the opacity
+                    layer._imageLayer->getColorFilters().empty() &&
                     layer._imageLayer->getVisible() &&
                     layer._imageLayer->getOpacity() >= 1.0f)
                 {
@@ -228,7 +292,7 @@ MPGeometry::renderPrimitiveSets(osg::State& state,
                     layer._tex->apply( state );
 
                     // in FFP mode, we need to enable the GL mode for texturing:
-                    if (!_supportsGLSL)
+                    if ( !pcp ) //!_supportsGLSL)
                     {
                         state.applyMode(GL_TEXTURE_2D, true);
                     }
@@ -239,6 +303,7 @@ MPGeometry::renderPrimitiveSets(osg::State& state,
                         state.setActiveTextureUnit( _imageUnitParent );
                         activeImageUnit = _imageUnitParent;
                         layer._texParent->apply( state );
+                        usedTexParent = true;
                     }
 
                     // bind the texture coordinates for this layer.
@@ -277,6 +342,18 @@ MPGeometry::renderPrimitiveSets(osg::State& state,
                         {
                             ext->glUniformMatrix4fv( texMatParentLocation, 1, GL_FALSE, layer._texMatParent.ptr() );
                         }
+
+                        // assign the min range
+                        if ( minRangeLocation >= 0 )
+                        {
+                            ext->glUniform1f( minRangeLocation, layer._imageLayer->getImageLayerOptions().minVisibleRange().get() );
+                        }
+
+                        // assign the max range
+                        if ( maxRangeLocation >= 0 )
+                        {
+                            ext->glUniform1f( maxRangeLocation, layer._imageLayer->getImageLayerOptions().maxVisibleRange().get() );
+                        }
                     }
 
                     // draw the primitive sets.
@@ -302,12 +379,15 @@ MPGeometry::renderPrimitiveSets(osg::State& state,
     // if we didn't draw anything, draw the raw tiles anyway with no texture.
     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 );
+        if ( pcp )
+        {
+            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 );
+        }
 
         // draw the primitives themselves.
         for(unsigned int primitiveSetNum=0; primitiveSetNum!=_primitives.size(); ++primitiveSetNum)
@@ -324,6 +404,16 @@ MPGeometry::renderPrimitiveSets(osg::State& state,
         if ( renderColor )
         {
             glBindTexture( GL_TEXTURE_2D, 0 );
+
+            // if a parent texture was applied, need to disable both.
+            if ( usedTexParent )
+            {
+                state.setActiveTextureUnit(
+                    activeImageUnit != _imageUnitParent ? _imageUnitParent :
+                    _imageUnit );
+
+                glBindTexture( GL_TEXTURE_2D, 0);
+            }
         }
     }
 }
@@ -348,6 +438,22 @@ MPGeometry:: COMPUTE_BOUND() const
         // update the uniform.
         Threading::ScopedMutexLock exclusive(_frameSyncMutex);
         _tileKeyValue.w() = bbox.radius();
+        
+#if 0
+        // make sure everyone's got a vbo.
+        MPGeometry* ncthis = const_cast<MPGeometry*>(this);
+
+        for(std::vector<Layer>::iterator i = _layers.begin(); i != _layers.end(); ++i)
+        {
+            if ( i->_texCoords.valid() && i->_texCoords->getVertexBufferObject() == 0L )
+                i->_texCoords->setVertexBufferObject( ncthis->getVertexArray()->getVertexBufferObject() );
+        }
+
+        if ( _tileCoords.valid() && _tileCoords->getVertexBufferObject() == 0L )
+        {
+            _tileCoords->setVertexBufferObject( ncthis->getVertexArray()->getVertexBufferObject() );
+        }
+#endif
     }
     return bbox;
 }
@@ -398,19 +504,8 @@ MPGeometry::releaseGLObjects(osg::State* state) const
 {
     osg::Geometry::releaseGLObjects( state );
 
-    for(unsigned i=0; i<_layers.size(); ++i)
-    {
-        const Layer& layer = _layers[i];
-
-        //Moved to TileModel since that's where the texture is created. Releasing it
-        // here could break texture sharing.
-        //if ( layer._tex.valid() )
-        //    layer._tex->releaseGLObjects( state );
-
-        // Check the refcount since texcoords can be cached/shared.
-        if ( layer._texCoords.valid() && layer._texCoords->referenceCount() == 1 )
-            layer._texCoords->releaseGLObjects( state );
-    }
+    // Note: don't release the textures here; instead we release them in the
+    // TileModel where they were created. -gw
 }
 
 
@@ -432,72 +527,27 @@ MPGeometry::resizeGLObjectBuffers(unsigned maxSize)
     }
 }
 
-namespace
-{
-    void compileBufferObject(BufferObject* bo, unsigned contextID)
-    {
-        if ( bo )
-        {
-            GLBufferObject* glBufferObject = bo->getOrCreateGLBufferObject(contextID);
-            if (glBufferObject && glBufferObject->isDirty())
-            {
-                glBufferObject->compileBuffer();
-            }
-        }
-    }
-    void compileBufferObject(osg::Array* a, unsigned contextID)
-    {
-        if ( a )
-        {
-            compileBufferObject( a->getBufferObject(), contextID );
-        }
-    }
-}
-
 
 void 
 MPGeometry::compileGLObjects( osg::RenderInfo& renderInfo ) const
 {
-    //osg::Geometry::compileGLObjects( renderInfo );
-    
     State& state = *renderInfo.getState();
-    unsigned contextID = state.getContextID();
-    GLBufferObject::Extensions* extensions = GLBufferObject::getExtensions(contextID, true);
-    if (!extensions)
-        return;
-
-    MPGeometry* ncthis = const_cast<MPGeometry*>(this);
-
-    //ncthis->validate();
-
-    compileBufferObject(ncthis->getVertexArray(), contextID);
-    compileBufferObject(ncthis->getNormalArray(), contextID);
-
-    for(unsigned i=0; i<getVertexAttribArrayList().size(); ++i) 
-    {
-        osg::Array* a = GET_ARRAY( getVertexAttribArrayList()[i] ).get();
-        compileBufferObject( a, contextID );
-    }
     
-    for(PrimitiveSetList::const_iterator i = _primitives.begin(); i != _primitives.end(); ++i )
-    {
-        compileBufferObject( i->get()->getBufferObject(), contextID );
-    }
-    
-    // compile the layer-specific things:
+    // compile the image textures:
     for(unsigned i=0; i<_layers.size(); ++i)
     {
         const Layer& layer = _layers[i];
-
-        compileBufferObject( layer._texCoords.get(), contextID );
-
         if ( layer._tex.valid() )
-            layer._tex->apply( *renderInfo.getState() );
+            layer._tex->apply( state );
+    }
+
+    // compile the elevation texture:
+    if ( _elevTex.valid() )
+    {
+        _elevTex->apply( state );
     }
 
-    // unbind the BufferObjects
-    extensions->glBindBuffer(GL_ARRAY_BUFFER_ARB,0);
-    extensions->glBindBuffer(GL_ELEMENT_ARRAY_BUFFER_ARB,0);
+    osg::Geometry::compileGLObjects( renderInfo );
 }
 
 
diff --git a/src/osgEarthDrivers/engine_mp/MPShaders b/src/osgEarthDrivers/engine_mp/MPShaders
new file mode 100644
index 0000000..5cd6c36
--- /dev/null
+++ b/src/osgEarthDrivers/engine_mp/MPShaders
@@ -0,0 +1,39 @@
+// ***DO NOT EDIT THIS FILE - IT IS AUTOMATICALLY GENERATED BY CMAKE***
+/* -*-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 <osgEarth/ShaderLoader>
+
+namespace osgEarth { namespace Drivers { namespace MPTerrainEngine
+{
+    class Shaders : public osgEarth::ShaderPackage
+	{
+    public:
+        Shaders();
+        
+    public:
+        std::string
+            VertexModel,
+            VertexView,
+            Fragment;
+	};
+	
+} } } // namespace osgEarth::Drivers::MPTerrainEngine
diff --git a/src/osgEarthDrivers/engine_mp/MPShaders.cpp.in b/src/osgEarthDrivers/engine_mp/MPShaders.cpp.in
new file mode 100644
index 0000000..18e5e7c
--- /dev/null
+++ b/src/osgEarthDrivers/engine_mp/MPShaders.cpp.in
@@ -0,0 +1,19 @@
+// ***DO NOT EDIT THIS FILE - IT IS AUTOMATICALLY GENERATED BY CMAKE***
+
+#include <osgEarthDrivers/engine_mp/MPShaders>
+
+#define MULTILINE(...) #__VA_ARGS__
+
+using namespace osgEarth::Drivers::MPTerrainEngine;
+
+Shaders::Shaders()
+{
+    VertexModel = "MPEngine.vert.model.glsl";
+    _sources[VertexModel] = OE_MULTILINE(@MPEngine.vert.model.glsl@);
+
+    VertexView = "MPEngine.vert.view.glsl";
+    _sources[VertexView] = OE_MULTILINE(@MPEngine.vert.view.glsl@);
+
+    Fragment = "MPEngine.frag.glsl";
+    _sources[Fragment] = OE_MULTILINE(@MPEngine.frag.glsl@);
+}
diff --git a/src/osgEarthDrivers/engine_mp/MPTerrainEngineDriver.cpp b/src/osgEarthDrivers/engine_mp/MPTerrainEngineDriver.cpp
index 77ba70c..3b4a33e 100644
--- a/src/osgEarthDrivers/engine_mp/MPTerrainEngineDriver.cpp
+++ b/src/osgEarthDrivers/engine_mp/MPTerrainEngineDriver.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -146,14 +146,21 @@ namespace osgEarth { namespace Drivers { namespace MPTerrainEngine
                             progress->stats()["http_get_time"] / progress->stats()["http_get_count"];
 
                         OE_NOTICE << "tile: " << tileDef << std::endl;
-                        for(std::map<std::string,double>::iterator i = progress->stats().begin();
+                        for(fast_map<std::string,double>::iterator i = progress->stats().begin();
                             i != progress->stats().end();
                             ++i)
                         {
                             std::stringstream buf;
-                            buf << i->first << " = " << std::setprecision(4) << i->second;
                             if ( osgEarth::endsWith(i->first, "_time") )
-                                buf << " (" << (int)((i->second/tileLoadTime)*100) << "%)";
+                            {
+                                buf 
+                                    << i->first << " = " << std::setprecision(4) << (i->second*1000.0) << "ms ("
+                                    << (int)((i->second/tileLoadTime)*100) << "%)";
+                            }
+                            else
+                            {
+                                buf << i->first << " = " << std::setprecision(4) << i->second;
+                            }
                             OE_NOTICE << "   " << buf.str() << std::endl;
                         }
 
@@ -170,8 +177,6 @@ namespace osgEarth { namespace Drivers { namespace MPTerrainEngine
                         static std::deque<double> tileLoadTimes[3];
                         static int    samples[3]       = { 64, 256, 1024 };
                         static double runningTotals[3] = { 0.0, 0.0, 0.0 };
-                        //static int s0 = 60, s1 = 256, s2 = 1024;
-                        //static double runningTileLoadTime = 0.0;
                         static Threading::Mutex averageMutex;
 
                         averageMutex.lock();
@@ -186,9 +191,9 @@ namespace osgEarth { namespace Drivers { namespace MPTerrainEngine
                             }
                         }
                         OE_NOTICE << "(samples)time : "
-                            << "(" << samples[0] << "): " << (tileLoadTimes[0].size() == samples[0] ? (runningTotals[0]/(double)samples[0]) : -1.0) << "; "
-                            << "(" << samples[1] << "): " << (tileLoadTimes[1].size() == samples[1] ? (runningTotals[1]/(double)samples[1]) : -1.0) << "; "
-                            << "(" << samples[2] << "): " << (tileLoadTimes[2].size() == samples[2] ? (runningTotals[2]/(double)samples[2]) : -1.0) << "; "
+                            << "(" << samples[0] << "): " << (tileLoadTimes[0].size() == samples[0] ? (runningTotals[0]/(double)samples[0])*1000.0 : -1.0) << "ms; "
+                            << "(" << samples[1] << "): " << (tileLoadTimes[1].size() == samples[1] ? (runningTotals[1]/(double)samples[1])*1000.0 : -1.0) << "ms; "
+                            << "(" << samples[2] << "): " << (tileLoadTimes[2].size() == samples[2] ? (runningTotals[2]/(double)samples[2])*1000.0 : -1.0) << "ms; "
                             << std::endl;
 
                         averageMutex.unlock();
diff --git a/src/osgEarthDrivers/engine_mp/MPTerrainEngineNode b/src/osgEarthDrivers/engine_mp/MPTerrainEngineNode
index 1e172c5..e51515e 100644
--- a/src/osgEarthDrivers/engine_mp/MPTerrainEngineNode
+++ b/src/osgEarthDrivers/engine_mp/MPTerrainEngineNode
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -24,6 +24,7 @@
 #include <osgEarth/Map>
 #include <osgEarth/Revisioning>
 #include <osgEarth/ThreadingUtils>
+#include <osgEarth/Containers>
 
 #include "MPTerrainEngineOptions"
 #include "KeyNodeFactory"
@@ -34,6 +35,7 @@
 #include <osg/Geode>
 #include <osg/NodeCallback>
 #include <osg/Uniform>
+#include <osgUtil/RenderBin>
 
 using namespace osgEarth;
 
@@ -65,6 +67,12 @@ namespace osgEarth { namespace Drivers { namespace MPTerrainEngine
             unsigned         minLevel,
             unsigned         maxLevel);
 
+        /** Access the stateset used to render the terrain. */
+        osg::StateSet* getTerrainStateSet();
+
+        /** Access the stateset used to render payload data. */
+        osg::StateSet* getPayloadStateSet();
+
     public: // internal TerrainEngineNode
 
         virtual void preInitialize( const Map* map, const TerrainOptions& options );
@@ -103,6 +111,8 @@ namespace osgEarth { namespace Drivers { namespace MPTerrainEngine
     protected:
         // override from TerrainEngineNode
         virtual void updateTextureCombining() { updateState(); }
+        
+        virtual void notifyExistingNodes(TerrainTileNodeCallback* cb);
 
     private:
         void init();
@@ -110,7 +120,7 @@ namespace osgEarth { namespace Drivers { namespace MPTerrainEngine
 
         // Reloads all the tiles in the terrain due to a data model change
         void refresh(bool force =false);
-        void createTerrain();
+        virtual void dirtyTerrain();
 
         void addImageLayer( ImageLayer* layer );
         void addElevationLayer( ElevationLayer* layer );
@@ -124,6 +134,7 @@ namespace osgEarth { namespace Drivers { namespace MPTerrainEngine
         
         void updateState(); 
 
+
     private:
         MPTerrainEngineOptions _terrainOptions;
 
@@ -146,7 +157,7 @@ namespace osgEarth { namespace Drivers { namespace MPTerrainEngine
         osg::ref_ptr<TileNodeRegistry> _liveTiles;      // tiles in the scene graph.
         osg::ref_ptr<TileNodeRegistry> _deadTiles;        // tiles that used to be in the scene graph.
 
-        Threading::PerThread< osg::ref_ptr<KeyNodeFactory> > _perThreadKeyNodeFactories;
+        PerThread< osg::ref_ptr<KeyNodeFactory> > _perThreadKeyNodeFactories;
         KeyNodeFactory* getKeyNodeFactory();
 
         osg::Timer _timer;
@@ -154,11 +165,16 @@ namespace osgEarth { namespace Drivers { namespace MPTerrainEngine
         double     _tileCreationTime;
         int        _primaryUnit;
         int        _secondaryUnit;
+        int        _elevationTextureUnit;
 
         osg::Uniform* _verticalScaleUniform;
 
         osg::ref_ptr< TileModelFactory > _tileModelFactory;
 
+        Threading::Mutex _renderBinMutex;
+        osg::ref_ptr<osgUtil::RenderBin> _terrainRenderBinPrototype;
+        osg::ref_ptr<osgUtil::RenderBin> _payloadRenderBinPrototype;
+
         MPTerrainEngineNode( const MPTerrainEngineNode& rhs, const osg::CopyOp& op =osg::CopyOp::DEEP_COPY_ALL ) { }
     };
 
diff --git a/src/osgEarthDrivers/engine_mp/MPTerrainEngineNode.cpp b/src/osgEarthDrivers/engine_mp/MPTerrainEngineNode.cpp
index ad4d70d..a750e2a 100644
--- a/src/osgEarthDrivers/engine_mp/MPTerrainEngineNode.cpp
+++ b/src/osgEarthDrivers/engine_mp/MPTerrainEngineNode.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
@@ -22,6 +25,7 @@
 #include "TileModelFactory"
 #include "TileModelCompiler"
 #include "TilePagedLOD"
+#include "MPShaders"
 
 #include <osgEarth/HeightFieldUtils>
 #include <osgEarth/ImageUtils>
@@ -31,6 +35,9 @@
 #include <osgEarth/ShaderFactory>
 #include <osgEarth/MapModelChange>
 #include <osgEarth/Progress>
+#include <osgEarth/ShaderLoader>
+#include <osgEarth/Utils>
+#include <osgEarth/ObjectIndex>
 
 #include <osg/TexEnv>
 #include <osg/TexEnvCombine>
@@ -39,12 +46,18 @@
 #include <osg/Depth>
 #include <osg/BlendFunc>
 #include <osgDB/DatabasePager>
+#include <osgUtil/RenderBin>
+#include <osgUtil/RenderLeaf>
 
 #define LC "[MPTerrainEngineNode] "
 
 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
@@ -67,6 +80,49 @@ namespace
                 node->onMapModelChanged( change );
         }
     };
+
+
+    // Render bin for terrain surface geometry
+    class TerrainBin : public osgUtil::RenderBin
+    {
+    public:
+        TerrainBin()
+        {
+            this->setStateSet( new osg::StateSet() );
+            this->setSortMode(SORT_FRONT_TO_BACK);
+        }
+
+        osg::Object* clone(const osg::CopyOp& copyop) const
+        {
+            return new TerrainBin(*this, copyop);
+        }
+
+        TerrainBin(const TerrainBin& rhs, const osg::CopyOp& copy) :
+            osgUtil::RenderBin(rhs, copy)
+        {
+        }
+    };
+
+
+    // Render bin for terrain payload geometry
+    class PayloadBin : public osgUtil::RenderBin
+    {
+    public:
+        PayloadBin()
+        {
+            this->setStateSet( new osg::StateSet() );
+        }
+
+        osg::Object* clone(const osg::CopyOp& copyop) const
+        {
+            return new PayloadBin(*this, copyop);
+        }
+
+        PayloadBin(const PayloadBin& rhs, const osg::CopyOp& copy) :
+            osgUtil::RenderBin(rhs, copy)
+        {
+        }
+    };
 }
 
 //---------------------------------------------------------------------------
@@ -145,12 +201,29 @@ _tileCount            ( 0 ),
 _tileCreationTime     ( 0.0 ),
 _primaryUnit          ( -1 ),
 _secondaryUnit        ( -1 ),
+_elevationTextureUnit ( -1 ),
 _batchUpdateInProgress( false ),
 _refreshRequired      ( false ),
 _stateUpdateRequired  ( false )
 {
+    // unique ID for this engine:
     _uid = Registry::instance()->createUID();
 
+    // 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() );
+    }
+
     // install an elevation callback so we can update elevation data
     _elevationCallback = new ElevationChangedCallback( this );
 }
@@ -159,6 +232,9 @@ MPTerrainEngineNode::~MPTerrainEngineNode()
 {
     unregisterEngine( _uid );
 
+    osgUtil::RenderBin::removeRenderBinPrototype( _terrainRenderBinPrototype.get() );
+    osgUtil::RenderBin::removeRenderBinPrototype( _payloadRenderBinPrototype.get() );
+
     if ( _update_mapf )
     {
         delete _update_mapf;
@@ -180,7 +256,7 @@ MPTerrainEngineNode::postInitialize( const Map* map, const TerrainOptions& optio
     // 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, "mp-update" );
+    _update_mapf = new MapFrame( map, Map::ENTIRE_MODEL );
 
     // merge in the custom options:
     _terrainOptions.merge( options );
@@ -198,9 +274,22 @@ MPTerrainEngineNode::postInitialize( const Map* map, const TerrainOptions& optio
     {
         _deadTiles = new TileNodeRegistry("dead");
     }
+
+    // reserve GPU resources. Must do this before initializing the model factory.
+    if ( _primaryUnit < 0 )
+    {
+        getResources()->reserveTextureImageUnit( _primaryUnit, "MP Engine Primary" );
+    }
+
+    // "Secondary" unit serves double duty; it's used for parent textures BUT it's also
+    // used at the "slot" for the tile coordinates.
+    if ( _secondaryUnit < 0 )
+    {
+        getResources()->reserveTextureImageUnit( _secondaryUnit, "MP Engine Secondary" );
+    }
     
     // initialize the model factory:
-    _tileModelFactory = new TileModelFactory(_liveTiles.get(), _terrainOptions );
+    _tileModelFactory = new TileModelFactory(_liveTiles.get(), _terrainOptions, this);
 
     // handle an already-established map profile:
     if ( _update_mapf->getProfile() )
@@ -227,17 +316,12 @@ MPTerrainEngineNode::postInitialize( const Map* map, const TerrainOptions& optio
 
     _batchUpdateInProgress = false;
 
-    // install some terrain-wide uniforms
-    this->getOrCreateStateSet()->getOrCreateUniform(
-        "oe_min_tile_range_factor",
-        osg::Uniform::FLOAT)->set( *_terrainOptions.minTileRangeFactor() );
-
-    // set up the initial shaders
-    updateState();
-
     // register this instance to the osgDB plugin can find it.
     registerEngine( this );
 
+    // set up the initial shaders and reserve the texture image units.
+    updateState();
+
     // now that we have a map, set up to recompute the bounds
     dirtyBound();
 
@@ -278,7 +362,9 @@ MPTerrainEngineNode::invalidateRegion(const GeoExtent& extent,
 void
 MPTerrainEngineNode::refresh(bool forceDirty)
 {
-    if ( _batchUpdateInProgress )
+    // if we're in the middle of a batch update OR if the terrain has not
+    // been fully initialized for rendering, mark it for later.
+    if ( _batchUpdateInProgress || _update_mapf == 0L )
     {
         _refreshRequired = true;
     }
@@ -292,11 +378,7 @@ MPTerrainEngineNode::refresh(bool forceDirty)
         }
         else
         {
-            // remove the old one:
-            this->removeChild( _terrain );
-
-            // and create a new one.
-            createTerrain();
+            dirtyTerrain();
         }
 
         _refreshRequired = false;
@@ -306,42 +388,88 @@ MPTerrainEngineNode::refresh(bool forceDirty)
 void
 MPTerrainEngineNode::onMapInfoEstablished( const MapInfo& mapInfo )
 {
-    createTerrain();
+    if ( _update_mapf != 0L )
+    {
+        dirtyTerrain();
+    }
 }
 
-bool reg = false;
+osg::StateSet*
+MPTerrainEngineNode::getTerrainStateSet()
+{
+#ifdef USE_RENDER_BINS
+    return _terrainRenderBinPrototype->getStateSet();
+#else
+    return _terrain ? _terrain->getOrCreateStateSet() : 0L;
+#endif
+}
+
+namespace
+{
+    struct NotifyExistingNodesOp : public TileNodeRegistry::Operation
+    {
+        TerrainTileNodeCallback* _cb;
+
+        NotifyExistingNodesOp(TerrainTileNodeCallback* cb) : _cb(cb) { }
+
+        void operator()(TileNodeRegistry::TileNodeMap& tiles)
+        {
+            //OE_INFO << LC << "Gonna notify " << tiles.size() << " existing nodes...\n";
+
+            for(TileNodeRegistry::TileNodeMap::iterator i = tiles.begin();
+                i != tiles.end();
+                ++i)
+            {
+                _cb->operator()(i->first, i->second.get());
+            }
+        }
+    };
+}
 
 void
-MPTerrainEngineNode::createTerrain()
+MPTerrainEngineNode::notifyExistingNodes(TerrainTileNodeCallback* cb)
 {
-    // scrub the heightfield cache.
-    if (_tileModelFactory)
-        _tileModelFactory->getHeightFieldCache()->clear();
+    NotifyExistingNodesOp op( cb );
+    _liveTiles->run( op );
+}
 
-    // New terrain
-    _terrain = new TerrainNode( _deadTiles.get() );
-    this->addChild( _terrain );
 
-    // Enable blending on the terrain node; this will result in the underlying
-    // "empty" globe being transparent instead of white.
-    if (_terrainOptions.enableBlending().value())
-    {
-        _terrain->getOrCreateStateSet()->setMode(GL_BLEND , osg::StateAttribute::ON);
-    }
+osg::StateSet*
+MPTerrainEngineNode::getPayloadStateSet()
+{
+    return _payloadRenderBinPrototype->getStateSet();
+}
 
-    // reserve GPU space.
-    if ( _primaryUnit < 0 )
+void
+MPTerrainEngineNode::dirtyTerrain()
+{
+    // scrub the heightfield cache.
+    if (_tileModelFactory)
     {
-        this->getTextureCompositor()->reserveTextureImageUnit( _primaryUnit );
+        _tileModelFactory->clearCaches();
     }
-    if ( _secondaryUnit < 0 )
+
+    // remove existing:
+    if ( _terrain )
     {
-        this->getTextureCompositor()->reserveTextureImageUnit( _secondaryUnit );
+        this->removeChild( _terrain );
     }
 
+    // New terrain
+    _terrain = new TerrainNode( _deadTiles.get() );
+
+#ifdef USE_RENDER_BINS
+    _terrain->getOrCreateStateSet()->setRenderBinDetails( 0, _terrainRenderBinPrototype->getName() );
+    _terrain->getOrCreateStateSet()->setNestRenderBins(false);
+#else
+    _terrain->getOrCreateStateSet()->setRenderBinDetails(0, "SORT_FRONT_TO_BACK");
+#endif
+
+    this->addChild( _terrain );
+
     // Factory to create the root keys:
     KeyNodeFactory* factory = getKeyNodeFactory();
-
+    
     // Build the first level of the terrain.
     // Collect the tile keys comprising the root tiles of the terrain.
     std::vector< TileKey > keys;
@@ -351,15 +479,17 @@ MPTerrainEngineNode::createTerrain()
     OE_INFO << LC << "Creating " << keys.size() << " root keys.." << std::endl;
 
     TilePagedLOD* root = new TilePagedLOD( _uid, _liveTiles, _deadTiles );
-    //osg::Group* root = new osg::Group();
     _terrain->addChild( root );
 
     osg::ref_ptr<osgDB::Options> dbOptions = Registry::instance()->cloneOrCreateOptions();
 
+    // Accumulate data from low to high resolution when necessary:
+    bool accumulate = true;
+
     unsigned child = 0;
     for( unsigned i=0; i<keys.size(); ++i )
     {
-        osg::ref_ptr<osg::Node> node = factory->createNode( keys[i], true, true, 0L );
+        osg::ref_ptr<osg::Node> node = factory->createNode( keys[i], accumulate, true, 0L );
         if ( node.valid() )
         {
             root->addChild( node.get() );
@@ -376,6 +506,9 @@ MPTerrainEngineNode::createTerrain()
     _rootTilesRegistered = false;
 
     updateState();
+
+    // Call the base class
+    TerrainEngineNode::dirtyTerrain();
 }
 
 namespace
@@ -470,10 +603,11 @@ MPTerrainEngineNode::getKeyNodeFactory()
             _liveTiles.get(),
             _deadTiles.get(),
             _terrainOptions,
-            _uid );
+            _uid,
+            this );
     }
 
-    return knf.release();
+    return knf.get();
 }
 
 osg::Node*
@@ -487,7 +621,14 @@ MPTerrainEngineNode::createNode(const TileKey&    key,
 
     OE_DEBUG << LC << "Create node for \"" << key.str() << "\"" << std::endl;
 
-    return getKeyNodeFactory()->createNode( key, true, true, progress );
+    bool accumulate    = true;  // use parent data to help build tiles if neccesary
+    bool setupChildren = true;  // prepare the tile for subdivision
+
+    // create the node:
+    osg::ref_ptr<osg::Node> node = getKeyNodeFactory()->createNode(key, accumulate, setupChildren, progress);
+
+    // release the reference and return it.
+    return node.release();
 }
 
 osg::Node*
@@ -507,9 +648,61 @@ MPTerrainEngineNode::createStandaloneNode(const TileKey&    key,
 osg::Node*
 MPTerrainEngineNode::createTile( const TileKey& key )
 {
-    // make a node, but don't include any subtile information and don't
-    // accumulate data from parents.
-    return getKeyNodeFactory()->createNode( key, false, false, 0L );
+    osg::ref_ptr<TileModel> model = new TileModel( _update_mapf->getRevision(), _update_mapf->getMapInfo() );
+    model->_tileKey = key;
+    model->_tileLocator = GeoLocator::createForKey(key, _update_mapf->getMapInfo());
+
+    // Build the heightfield
+
+    const MapInfo& mapInfo = _update_mapf->getMapInfo();
+
+    const osgEarth::ElevationInterpolation& interp = _update_mapf->getMapOptions().elevationInterpolation().get();
+
+    // Request a heightfield from the map, falling back on lower resolution tiles
+    osg::ref_ptr<osg::HeightField> hf;    
+
+    TileKey sampleKey = key;
+    bool populated = false;
+    if (_update_mapf->elevationLayers().size() > 0)
+    {
+        while (!populated)
+        {
+            populated = _update_mapf->populateHeightField(hf, sampleKey, true, 0L);
+            if (!populated)
+            {
+                // Fallback on the parent
+                sampleKey = sampleKey.createParentKey();
+                if (!sampleKey.valid())
+                {
+                    return 0;
+                }
+            }
+        }       
+    }
+
+    if (!populated)
+    {
+        // We have no heightfield so just create a reference heightfield.
+        int tileSize = _terrainOptions.tileSize().get();
+        hf = HeightFieldUtils::createReferenceHeightField( key.getExtent(), tileSize, tileSize );
+        sampleKey = key;
+    }
+
+    model->_elevationData = TileModel::ElevationData(
+        hf,
+        GeoLocator::createForKey( sampleKey, mapInfo ),
+        false );        
+
+    bool optimizeTriangleOrientation = getMap()->getMapOptions().elevationInterpolation() != INTERP_TRIANGULATE;
+
+    osg::ref_ptr<TileModelCompiler> compiler = new TileModelCompiler(
+        _update_mapf->terrainMaskLayers(),
+        _update_mapf->modelLayers(),
+        _primaryUnit,
+        optimizeTriangleOrientation,
+        _terrainOptions );
+
+    return compiler->compile(model.get(), *_update_mapf, 0L);
 }
 
 
@@ -581,7 +774,7 @@ MPTerrainEngineNode::onMapModelChanged( const MapModelChange& change )
 void
 MPTerrainEngineNode::addImageLayer( ImageLayer* layerAdded )
 {
-    if ( layerAdded )
+    if ( layerAdded && layerAdded->getEnabled() )
     {
         // for a shared layer, allocate a shared image unit if necessary.
         if ( layerAdded->isShared() )
@@ -590,7 +783,7 @@ MPTerrainEngineNode::addImageLayer( ImageLayer* layerAdded )
             if ( !unit.isSet() )
             {
                 int temp;
-                if ( getTextureCompositor()->reserveTextureImageUnit(temp) )
+                if ( getResources()->reserveTextureImageUnit(temp, "MP Engine Shared Layer") )
                 {
                     unit = temp;
                     OE_INFO << LC << "Image unit " << temp << " assigned to shared layer " << layerAdded->getName() << std::endl;
@@ -600,6 +793,18 @@ MPTerrainEngineNode::addImageLayer( ImageLayer* layerAdded )
                     OE_WARN << LC << "Insufficient GPU image units to share layer " << layerAdded->getName() << std::endl;
                 }
             }
+
+            optional<std::string>& texUniformName = layerAdded->shareTexUniformName();
+            if ( !texUniformName.isSet() )
+            {
+                texUniformName = Stringify() << "oe_layer_" << layerAdded->getUID() << "_tex";
+            }
+
+            optional<std::string>& texMatUniformName = layerAdded->shareTexMatUniformName();
+            if ( !texMatUniformName.isSet() )
+            {
+                texMatUniformName = Stringify() << "oe_layer_" << layerAdded->getUID() << "_texmat";
+            }
         }
     }
 
@@ -613,11 +818,11 @@ MPTerrainEngineNode::removeImageLayer( ImageLayer* layerRemoved )
     if ( layerRemoved )
     {
         // for a shared layer, release the shared image unit.
-        if ( layerRemoved->isShared() )
+        if ( layerRemoved->getEnabled() && layerRemoved->isShared() )
         {
             if ( layerRemoved->shareImageUnit().isSet() )
             {
-                getTextureCompositor()->releaseTextureImageUnit( *layerRemoved->shareImageUnit() );
+                getResources()->releaseTextureImageUnit( *layerRemoved->shareImageUnit() );
                 layerRemoved->shareImageUnit().unset();
             }
         }
@@ -635,7 +840,7 @@ MPTerrainEngineNode::moveImageLayer( unsigned int oldIndex, unsigned int newInde
 void
 MPTerrainEngineNode::addElevationLayer( ElevationLayer* layer )
 {
-    if ( !layer )
+    if ( layer == 0L || layer->getEnabled() == false )
         return;
 
     layer->addCallback( _elevationCallback.get() );
@@ -646,6 +851,9 @@ MPTerrainEngineNode::addElevationLayer( ElevationLayer* layer )
 void
 MPTerrainEngineNode::removeElevationLayer( ElevationLayer* layerRemoved )
 {
+    if ( layerRemoved->getEnabled() == false )
+        return;
+
     layerRemoved->removeCallback( _elevationCallback.get() );
 
     refresh();
@@ -674,7 +882,12 @@ MPTerrainEngineNode::updateState()
     }
     else
     {
-        osg::StateSet* terrainStateSet = _terrain->getOrCreateStateSet();
+        if ( _elevationTextureUnit < 0 && elevationTexturesRequired() )
+        {
+            getResources()->reserveTextureImageUnit( _elevationTextureUnit, "MP Engine Elevation" );
+        }
+
+        osg::StateSet* terrainStateSet = getTerrainStateSet();
         
         // required for multipass tile rendering to work
         terrainStateSet->setAttributeAndModes(
@@ -696,73 +909,37 @@ MPTerrainEngineNode::updateState()
             vp->addBindAttribLocation( "oe_terrain_attr",  osg::Drawable::ATTRIBUTE_6 );
             vp->addBindAttribLocation( "oe_terrain_attr2", osg::Drawable::ATTRIBUTE_7 );
 
-            // Vertex shader:
-            std::string vs = Stringify() <<
-                "#version " GLSL_VERSION_STR "\n"
-                GLSL_DEFAULT_PRECISION_FLOAT "\n"
-                "varying vec4 oe_layer_texc;\n"
-                "varying vec4 oe_layer_tilec;\n"
-                "void oe_mp_setup_coloring(inout vec4 VertexModel) \n"
-                "{ \n"
-                "    oe_layer_texc  = gl_MultiTexCoord" << _primaryUnit << ";\n"
-                "    oe_layer_tilec = gl_MultiTexCoord" << _secondaryUnit << ";\n"
-                "}\n";
-
-            bool useTerrainColor = _terrainOptions.color().isSet();
-
-            bool useBlending = _terrainOptions.enableBlending() == true;
-
-            // Fragment Shader for normal blending:
-            std::string fs = Stringify() <<
-                "#version " GLSL_VERSION_STR "\n"
-                GLSL_DEFAULT_PRECISION_FLOAT "\n"
-                "varying vec4 oe_layer_texc; \n"
-                "uniform sampler2D oe_layer_tex; \n"
-                "uniform int oe_layer_uid; \n"
-                "uniform int oe_layer_order; \n"
-                "uniform float oe_layer_opacity; \n"
-                << (useTerrainColor ?
-                "uniform vec4 oe_terrain_color; \n" : ""
-                ) <<
-                "void oe_mp_apply_coloring(inout vec4 color) \n"
-                "{ \n"
-                << (useTerrainColor ?
-                "    color = oe_terrain_color; \n" : ""
-                ) <<
-                "    vec4 texel; \n"
-                "    if ( oe_layer_uid >= 0 ) { \n"
-                "        texel = texture2D(oe_layer_tex, oe_layer_texc.st); \n"
-                "        texel.a *= oe_layer_opacity; \n"
-                "    } \n"
-                "    else { \n"
-                "        texel = color; \n"
-                "    }\n"
-                << (useBlending ?
-                "    if ( oe_layer_order == 0 ) { \n"
-                "        color = texel*texel.a + color*(1.0-texel.a); \n" // simulate src_alpha, 1-src_alpha blens
-                "    } \n"
-                "    else \n" : ""
-                ) <<
-                "        color = texel; \n"
-                "} \n";
-
-            // Color filter frag function:
-            std::string fs_colorfilters =
-                "#version " GLSL_VERSION_STR "\n"
-                GLSL_DEFAULT_PRECISION_FLOAT "\n"
-                "uniform int oe_layer_uid; \n"
-                "__COLOR_FILTER_HEAD__"
-                "void oe_mp_apply_filters(inout vec4 color) \n"
-                "{ \n"
-                    "__COLOR_FILTER_BODY__"
-                "} \n";
-
-            vp->setFunction( "oe_mp_setup_coloring", vs, ShaderComp::LOCATION_VERTEX_MODEL, 0.0 );
-            vp->setFunction( "oe_mp_apply_coloring", fs, ShaderComp::LOCATION_FRAGMENT_COLORING, 0.0 );
+            Shaders package;
+
+            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.VertexModel );
+            package.load( vp, package.VertexView );
+            package.load( vp, package.Fragment );
+            
 
+            // terrain background color; negative means use the vertex color.
+            Color terrainColor = _terrainOptions.color().getOrUse( Color(1,1,1,-1) );
+            terrainStateSet->addUniform(new osg::Uniform("oe_terrain_color", terrainColor));
+
+            
             // assemble color filter code snippets.
             bool haveColorFilters = false;
             {
+                // Color filter frag function:
+                std::string fs_colorfilters =
+                    "#version " GLSL_VERSION_STR "\n"
+                    GLSL_DEFAULT_PRECISION_FLOAT "\n"
+                    "uniform int oe_layer_uid; \n"
+                    "$COLOR_FILTER_HEAD"
+                    "void oe_mp_apply_filters(inout vec4 color) \n"
+                    "{ \n"
+                        "$COLOR_FILTER_BODY"
+                    "} \n";
+
                 std::stringstream cf_head;
                 std::stringstream cf_body;
                 const char* I = "    ";
@@ -802,10 +979,14 @@ MPTerrainEngineNode::updateState()
                     cf_head_str = cf_head.str();
                     cf_body_str = cf_body.str();
 
-                    replaceIn( fs_colorfilters, "__COLOR_FILTER_HEAD__", cf_head_str );
-                    replaceIn( fs_colorfilters, "__COLOR_FILTER_BODY__", cf_body_str );
+                    replaceIn( fs_colorfilters, "$COLOR_FILTER_HEAD", cf_head_str );
+                    replaceIn( fs_colorfilters, "$COLOR_FILTER_BODY", cf_body_str );
 
-                    vp->setFunction( "oe_mp_apply_filters", fs_colorfilters, ShaderComp::LOCATION_FRAGMENT_COLORING, 0.0 );
+                    vp->setFunction(
+                        "oe_mp_apply_filters",
+                        fs_colorfilters,
+                        ShaderComp::LOCATION_FRAGMENT_COLORING,
+                        0.5f );
                 }
             }
 
@@ -814,14 +995,24 @@ MPTerrainEngineNode::updateState()
                 "oe_layer_tex", osg::Uniform::SAMPLER_2D )->set( _primaryUnit );
 
             // binding for the secondary texture (for LOD blending)
-            terrainStateSet->getOrCreateUniform(
-                "oe_layer_tex_parent", osg::Uniform::SAMPLER_2D )->set( _secondaryUnit );
+            if ( parentTexturesRequired() )
+            {
+                terrainStateSet->getOrCreateUniform(
+                    "oe_layer_tex_parent", osg::Uniform::SAMPLER_2D )->set( _secondaryUnit );
 
-            // binding for the default secondary texture matrix
-            osg::Matrixf parent_mat;
-            parent_mat(0,0) = 0.0f;
-            terrainStateSet->getOrCreateUniform(
-                "oe_layer_parent_matrix", osg::Uniform::FLOAT_MAT4 )->set( parent_mat );
+                // binding for the default secondary texture matrix
+                osg::Matrixf parent_mat;
+                parent_mat(0,0) = 0.0f;
+                terrainStateSet->getOrCreateUniform(
+                    "oe_layer_parent_matrix", osg::Uniform::FLOAT_MAT4 )->set( parent_mat );
+            }
+
+            // uniform for accessing the elevation texture sampler.
+            if ( elevationTexturesRequired() )
+            {
+                terrainStateSet->getOrCreateUniform(
+                    "oe_terrain_tex", osg::Uniform::SAMPLER_2D)->set( _elevationTextureUnit );
+            }
 
             // uniform that controls per-layer opacity
             terrainStateSet->getOrCreateUniform(
@@ -838,11 +1029,31 @@ MPTerrainEngineNode::updateState()
             terrainStateSet->getOrCreateUniform(
                 "oe_layer_order", osg::Uniform::INT )->set( 0 );
 
-            // base terrain color.
-            if ( useTerrainColor )
+            // 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->getOrCreateUniform(
+                "oe_min_tile_range_factor",
+                osg::Uniform::FLOAT)->set( *_terrainOptions.minTileRangeFactor() );
+
+            // special object ID that denotes the terrain surface.
+            terrainStateSet->addUniform( new osg::Uniform(
+                Registry::objectIndex()->getObjectIDUniformName().c_str(), OSGEARTH_OBJECTID_TERRAIN) );
+
+            // assign the uniforms for each shared layer.
+            int numImageLayers = _update_mapf->imageLayers().size();
+            for( int i=0; i<numImageLayers; ++i )
             {
-                terrainStateSet->getOrCreateUniform(
-                    "oe_terrain_color", osg::Uniform::FLOAT_VEC4 )->set( *_terrainOptions.color() );
+                ImageLayer* layer = _update_mapf->getImageLayerAt(i);
+                if ( layer->getEnabled() && layer->isShared() )
+                {
+                    terrainStateSet->addUniform( new osg::Uniform(
+                        layer->shareTexUniformName()->c_str(),
+                        layer->shareImageUnit().get() ) );
+                        
+                }
             }
         }
 
diff --git a/src/osgEarthDrivers/engine_mp/MPTerrainEngineOptions b/src/osgEarthDrivers/engine_mp/MPTerrainEngineOptions
index 47a4c7f..ff1455c 100644
--- a/src/osgEarthDrivers/engine_mp/MPTerrainEngineOptions
+++ b/src/osgEarthDrivers/engine_mp/MPTerrainEngineOptions
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -37,14 +37,13 @@ namespace osgEarth { namespace Drivers { namespace MPTerrainEngine
         MPTerrainEngineOptions( const ConfigOptions& options =ConfigOptions() ) : TerrainOptions( options ),
             _skirtRatio        ( 0.05 ),
             _quickRelease      ( true ),
-            _lodFallOff        ( 0.0 ),
             _normalizeEdges    ( false ),
             _rangeMode         ( osg::LOD::DISTANCE_FROM_EYE_POINT ),
             _tilePixelSize     ( 256 ),
             _color             ( Color::White ),
             _incrementalUpdate ( false ),
-            _optimizeTiles     ( false )
-        {
+            _smoothing         ( false )
+         {
             setDriver( "mp" );
             fromConfig( _conf );
         }
@@ -62,7 +61,8 @@ namespace osgEarth { namespace Drivers { namespace MPTerrainEngine
         optional<bool>& quickReleaseGLObjects() { return _quickRelease; }
         const optional<bool>& quickReleaseGLObjects() const { return _quickRelease; }
 
-        /** Whether to average normal vectors on tile boundaries */
+        /** 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; }
 
@@ -78,33 +78,33 @@ namespace osgEarth { namespace Drivers { namespace MPTerrainEngine
         optional<Color>& color() { return _color; }
         const optional<Color>& color() const { return _color; }
 
-        /** Whether to update tiles incrementally as needed when the map model changes (true),
-          * or to rebuild the entire terrain when enything changes (false) */
-        optional<bool>& incrementalUpdate() { return _incrementalUpdate; }
-        const optional<bool>& incrementalUpdate() const { return _incrementalUpdate; }
+        /**
+         * Whether to create smooth transitions between elevation datasets at differing LODs
+         * by inheriting low-resolution data from parent tiles and filling in the new data.
+         * When smoothing is on, elevation may not be "true" in all locations, since the engine
+         * will interpolate values from low LODs. Defaults to FALSE.
+         */
+        optional<bool>& elevationSmoothing() { return _smoothing; }
+        const optional<bool>& elevationSmoothing() const { return _smoothing; }
 
-        /** TODO: document this very obscure feature */
-        optional<float>& lodFallOff() { return _lodFallOff; }
-        const optional<float>& lodFallOff() const { return _lodFallOff; }
+    public:
 
-        /** Whether to run the mesh consolidation and vertex cache optimizations on terrain tiles.
-          * They run faster, but take a lot longer to build. */
-        optional<bool>& optimizeTiles() { return _optimizeTiles; }
-        const optional<bool>& optimizeTiles() const { return _optimizeTiles; }
+        /** @deprecated */
+        optional<bool>& incrementalUpdate() { return _incrementalUpdate; }
+        const optional<bool>& incrementalUpdate() const { return _incrementalUpdate; }
 
     protected:
         virtual Config getConfig() const {
             Config conf = TerrainOptions::getConfig();
             conf.updateIfSet( "skirt_ratio", _skirtRatio );
             conf.updateIfSet( "quick_release_gl_objects", _quickRelease );
-            conf.updateIfSet( "lod_fall_off", _lodFallOff );
             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( "optimize_tiles", _optimizeTiles );
+            conf.updateIfSet( "elevation_smoothing", _smoothing );
 
             return conf;
         }
@@ -118,7 +118,6 @@ namespace osgEarth { namespace Drivers { namespace MPTerrainEngine
         void fromConfig( const Config& conf ) {
             conf.getIfSet( "skirt_ratio", _skirtRatio );
             conf.getIfSet( "quick_release_gl_objects", _quickRelease );
-            conf.getIfSet( "lod_fall_off", _lodFallOff );
             conf.getIfSet( "normalize_edges", _normalizeEdges );
             conf.getIfSet( "tile_pixel_size", _tilePixelSize );
 
@@ -126,8 +125,8 @@ namespace osgEarth { namespace Drivers { namespace MPTerrainEngine
             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( "optimize_tiles", _optimizeTiles );
-        }
+            conf.getIfSet( "elevation_smoothing", _smoothing );
+       }
 
         optional<float>               _skirtRatio;
         optional<bool>                _quickRelease;
@@ -137,7 +136,7 @@ namespace osgEarth { namespace Drivers { namespace MPTerrainEngine
         optional<float>               _tilePixelSize;
         optional<Color>               _color;
         optional<bool>                _incrementalUpdate;
-        optional<bool>                _optimizeTiles;
+        optional<bool>                _smoothing;
     };
 
 } } } // namespace osgEarth::Drivers::MPTerrainEngine
diff --git a/src/osgEarthDrivers/engine_mp/QuickReleaseGLObjects b/src/osgEarthDrivers/engine_mp/QuickReleaseGLObjects
index ff839dd..7b5150e 100644
--- a/src/osgEarthDrivers/engine_mp/QuickReleaseGLObjects
+++ b/src/osgEarthDrivers/engine_mp/QuickReleaseGLObjects
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/osgEarthDrivers/engine_mp/SingleKeyNodeFactory b/src/osgEarthDrivers/engine_mp/SingleKeyNodeFactory
index ce786f0..c9ab6d7 100644
--- a/src/osgEarthDrivers/engine_mp/SingleKeyNodeFactory
+++ b/src/osgEarthDrivers/engine_mp/SingleKeyNodeFactory
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
@@ -42,7 +45,8 @@ namespace osgEarth { namespace Drivers { namespace MPTerrainEngine
             TileNodeRegistry*                   liveTiles,
             TileNodeRegistry*                   deadTiles,
             const MPTerrainEngineOptions&       options,
-            UID                                 engineUID );
+            UID                                 engineUID,
+            TerrainTileNodeBroker*              tileNodeBroker );
 
         /** dtor */
         virtual ~SingleKeyNodeFactory() { }
@@ -67,7 +71,10 @@ namespace osgEarth { namespace Drivers { namespace MPTerrainEngine
             ProgressCallback* progress );
 
     protected:
-        osg::Node* createTile(TileModel* model, bool setupChildren);
+        osg::Node* createTile(
+            TileModel*        model,
+            bool              setupChildrenIfNecessary,
+            ProgressCallback* progress);
 
         MapFrame                            _frame;
         osg::ref_ptr<TileModelFactory>      _modelFactory;
@@ -76,6 +83,8 @@ namespace osgEarth { namespace Drivers { namespace MPTerrainEngine
         osg::ref_ptr<TileNodeRegistry>      _deadTiles;
         const MPTerrainEngineOptions&       _options;
         UID                                 _engineUID;
+        TerrainTileNodeBroker*              _tileNodeBroker;
+        bool                                _debug;
 
         unsigned getMinimumRequiredLevel();
     };
diff --git a/src/osgEarthDrivers/engine_mp/SingleKeyNodeFactory.cpp b/src/osgEarthDrivers/engine_mp/SingleKeyNodeFactory.cpp
index 343f257..bdc069f 100644
--- a/src/osgEarthDrivers/engine_mp/SingleKeyNodeFactory.cpp
+++ b/src/osgEarthDrivers/engine_mp/SingleKeyNodeFactory.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
@@ -26,12 +29,77 @@
 #include <osgEarth/HeightFieldUtils>
 #include <osgEarth/Progress>
 #include <osgEarth/Containers>
+#include <osgEarth/Horizon>
+
+#include <osgUtil/CullVisitor>
 
 using namespace osgEarth::Drivers::MPTerrainEngine;
 using namespace osgEarth;
 
 #define LC "[SingleKeyNodeFactory] "
 
+namespace
+{
+    /**
+     * Culling callback for terrain tiles.
+     *
+     * It performs a horizon-visibility test against the highest four corners of
+     * the tile's bounding box.
+
+     * This is an alternative to the old cluster culling callback.
+     */
+    struct HorizonTileCuller : public osg::NodeCallback
+    {
+        Horizon    _horizonPrototype;
+        osg::Vec3d _points[4];
+
+        HorizonTileCuller(const SpatialReference* srs, const osg::BoundingBox& bbox, const osg::Matrix& m)
+        {
+            _horizonPrototype.setEllipsoid(*srs->getEllipsoid());
+
+            // 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.)
+            // may be visible even if it doesn't pass the horizon-cone test. In such
+            // cases we need a more conservative ellipsoid.
+            double zMin = bbox.corner(0).z();
+            if ( zMin < 0.0 )
+            {
+                _horizonPrototype.setEllipsoid(osg::EllipsoidModel(
+                    srs->getEllipsoid()->getRadiusEquator() + zMin,
+                    srs->getEllipsoid()->getRadiusPolar()   + zMin));
+            }
+            
+
+            // consider the uppermost 4 points of the tile-aligned bounding box.
+            // (the last four corners of the bbox are the "zmax" corners.)
+            for(unsigned i=0; i<4; ++i)
+            {
+                _points[i] = bbox.corner(4+i) * m;
+            }
+        }
+
+        void operator()(osg::Node* node, osg::NodeVisitor* nv)
+        {
+            // Clone the horizon object to support multiple cull threads
+            // (since we call setEye with the current node visitor eye point)
+            Horizon horizon(_horizonPrototype);
+
+            // Since each terrain tile has an aboslute reference frame, 
+            // there is no need to transform the eyepoint:
+            horizon.setEye(nv->getViewPoint()); // * osg::computeLocalToWorld(nv->getNodePath()));
+            
+            for(unsigned i=0; i<4; ++i)
+            {                   
+                if ( !horizon.occludes(_points[i]) )
+                {
+                    traverse(node, nv);
+                    break;
+                }
+            }
+        }
+    };
+}
+
 
 SingleKeyNodeFactory::SingleKeyNodeFactory(const Map*                    map,
                                            TileModelFactory*             modelFactory,
@@ -39,16 +107,18 @@ SingleKeyNodeFactory::SingleKeyNodeFactory(const Map*                    map,
                                            TileNodeRegistry*             liveTiles,
                                            TileNodeRegistry*             deadTiles,
                                            const MPTerrainEngineOptions& options,
-                                           UID                           engineUID ) :
+                                           UID                           engineUID,
+                                           TerrainTileNodeBroker*        tileNodeBroker ) :
 _frame           ( map ),
 _modelFactory    ( modelFactory ),
 _modelCompiler   ( modelCompiler ),
 _liveTiles       ( liveTiles ),
 _deadTiles       ( deadTiles ),
 _options         ( options ),
-_engineUID       ( engineUID )
+_engineUID       ( engineUID ),
+_tileNodeBroker  ( tileNodeBroker )
 {
-    //nop
+    _debug = _options.debug() == true;
 }
 
 unsigned
@@ -74,7 +144,9 @@ namespace
 #endif
 
 osg::Node*
-SingleKeyNodeFactory::createTile(TileModel* model, bool setupChildrenIfNecessary)
+SingleKeyNodeFactory::createTile(TileModel*        model,
+                                 bool              setupChildrenIfNecessary,
+                                 ProgressCallback* progress)
 {
 #ifdef EXPERIMENTAL_TILE_NODE_CACHE
     osg::ref_ptr<TileNode> tileNode;
@@ -91,7 +163,8 @@ SingleKeyNodeFactory::createTile(TileModel* model, bool setupChildrenIfNecessary
     }
 #else
     // compile the model into a node:
-    TileNode* tileNode = _modelCompiler->compile( model, _frame );
+    TileNode* tileNode = _modelCompiler->compile(model, _frame, progress);
+    tileNode->setEngineUID( _engineUID );
 #endif
 
     // see if this tile might have children.
@@ -108,17 +181,38 @@ SingleKeyNodeFactory::createTile(TileModel* model, bool setupChildrenIfNecessary
         plod->setCenter  ( bs.center() );
         plod->addChild   ( tileNode );
         plod->setFileName( 1, Stringify() << tileNode->getKey().str() << "." << _engineUID << ".osgearth_engine_mp_tile" );
+        plod->setDebug   ( _debug );
 
         if ( _options.rangeMode().value() == osg::LOD::DISTANCE_FROM_EYE_POINT )
         {
             //Compute the min range based on the 2D size of the tile
             GeoExtent extent = model->_tileKey.getExtent();
-            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 radius = (ur - ll).length() / 2.0;
+            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())
+            {
+                GeoExtent equatorialExtent(
+                extent.getSRS(),
+                extent.west(),
+                -extent.height()/2.0,
+                extent.east(),
+                extent.height()/2.0 );
+                radius = equatorialExtent.getBoundingGeoCircle().getRadius();
+            }
+            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;
+            }
+          
             float minRange = (float)(radius * _options.minTileRangeFactor().value());
 
             plod->setRange( 0, minRange, FLT_MAX );
@@ -127,12 +221,19 @@ SingleKeyNodeFactory::createTile(TileModel* model, bool setupChildrenIfNecessary
         }
         else
         {
-            plod->setRange( 0, 0.0f, _options.tilePixelSize().value() );
-            plod->setRange( 1, _options.tilePixelSize().value(), FLT_MAX );
+            // the *2 is because we page in 4-tile sets, not individual tiles.
+            float size = 2.0f * _options.tilePixelSize().value();
+            plod->setRange( 0, 0.0f, size );
+            plod->setRange( 1, size, FLT_MAX );
             plod->setRangeMode( osg::LOD::PIXEL_SIZE_ON_SCREEN );
         }
-
-
+        
+        // Install a tile-aligned bounding box in the pager node itself so we can do
+        // visibility testing before paging in subtiles.
+        plod->setChildBoundingBoxAndMatrix(
+            1,
+            tileNode->getTerrainBoundingBox(),
+            tileNode->getMatrix() );
 
         // DBPager will set a priority based on the ratio range/maxRange.
         // This will offset that number with a full LOD #, giving LOD precedence.
@@ -149,6 +250,8 @@ SingleKeyNodeFactory::createTile(TileModel* model, bool setupChildrenIfNecessary
         // this one rejects back-facing tiles:
         if ( _frame.getMapInfo().isGeocentric() && _options.clusterCulling() == true )
         {
+
+#if 1
             osg::HeightField* hf =
                 model->_elevationData.getHeightField();
 
@@ -156,6 +259,14 @@ SingleKeyNodeFactory::createTile(TileModel* model, bool setupChildrenIfNecessary
                 hf,
                 tileNode->getKey().getProfile()->getSRS()->getEllipsoid(),
                 *_options.verticalScale() ) );
+#else
+            // This works, but isn't quite as tight at the cluster culler.
+            // Re-evaluate down the road.
+            result->addCullCallback( new HorizonTileCuller(
+                _frame.getMapInfo().getSRS(),
+                tileNode->getTerrainBoundingBox(),
+                tileNode->getMatrix(), tileNode->getKey() ) );
+#endif
         }
     }
     else
@@ -250,7 +361,9 @@ SingleKeyNodeFactory::createNode(const TileKey&    key,
 
         for( unsigned q=0; q<4; ++q )
         {
-            quad->addChild( createTile(model[q].get(), setupChildren) );
+            osg::ref_ptr<osg::Node> tile = createTile(model[q].get(), setupChildren, progress);
+            _tileNodeBroker->notifyOfTerrainTileNodeCreation( model[q]->_tileKey, tile.get() );
+            quad->addChild( tile.get() );
         }
     }
 
diff --git a/src/osgEarthDrivers/engine_mp/TerrainNode b/src/osgEarthDrivers/engine_mp/TerrainNode
index 2642c2b..51d3fab 100644
--- a/src/osgEarthDrivers/engine_mp/TerrainNode
+++ b/src/osgEarthDrivers/engine_mp/TerrainNode
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/osgEarthDrivers/engine_mp/TerrainNode.cpp b/src/osgEarthDrivers/engine_mp/TerrainNode.cpp
index fab316b..61c7617 100644
--- a/src/osgEarthDrivers/engine_mp/TerrainNode.cpp
+++ b/src/osgEarthDrivers/engine_mp/TerrainNode.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/osgEarthDrivers/engine_mp/TileGroup b/src/osgEarthDrivers/engine_mp/TileGroup
index bc0dd7b..c466c5a 100644
--- a/src/osgEarthDrivers/engine_mp/TileGroup
+++ b/src/osgEarthDrivers/engine_mp/TileGroup
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/osgEarthDrivers/engine_mp/TileGroup.cpp b/src/osgEarthDrivers/engine_mp/TileGroup.cpp
index 40886bf..d94174f 100644
--- a/src/osgEarthDrivers/engine_mp/TileGroup.cpp
+++ b/src/osgEarthDrivers/engine_mp/TileGroup.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
@@ -23,7 +26,6 @@
 #include <osg/NodeVisitor>
 
 using namespace osgEarth::Drivers::MPTerrainEngine;
-using namespace osgEarth;
 
 #define LC "[TileGroup] "
 
@@ -96,6 +98,13 @@ TileGroup::applyUpdate(osg::Node* node)
     {
         OE_DEBUG << LC << "Update received for tile " << _key.str() << std::endl;
 
+        InvalidTileNode* invalid = dynamic_cast<InvalidTileNode*>( node );
+        if ( invalid )
+        {
+            OE_WARN << LC << "Invalid node received (" << _key.str() << ")\n";
+            return;
+        }
+
         TileGroup* update = dynamic_cast<TileGroup*>( node );
         if ( !update )
         {
diff --git a/src/osgEarthDrivers/engine_mp/TileModel b/src/osgEarthDrivers/engine_mp/TileModel
index 9f92da6..cf80348 100644
--- a/src/osgEarthDrivers/engine_mp/TileModel
+++ b/src/osgEarthDrivers/engine_mp/TileModel
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
@@ -94,6 +97,11 @@ namespace osgEarth { namespace Drivers { namespace MPTerrainEngine
                 _neighbors.setNeighbor(xoffset, yoffset, hf);
             }
 
+            const HeightFieldNeighborhood& getNeighborhood() const 
+            {
+                return _neighbors;
+            }
+
             void setParent(osg::HeightField* hf)
             {
                 _parent = hf;
@@ -113,6 +121,52 @@ namespace osgEarth { namespace Drivers { namespace MPTerrainEngine
             HeightFieldNeighborhood _neighbors;
         };
 
+        class NormalData
+        {
+        public:
+            NormalData() : _fallbackData(true) { }
+            NormalData(const NormalData& rhs);
+
+            virtual ~NormalData() { }
+
+            NormalData(
+                osg::HeightField* hf,
+                GeoLocator*       locator,
+                bool              fallbackData);
+
+            osg::HeightField* getHeightField() const { return _hf.get(); }
+            GeoLocator* getLocator() const { return _locator.get(); }
+            bool isFallbackData() const { return _fallbackData; }
+
+            void setNeighbor(int xoffset, int yoffset, osg::HeightField* hf )
+            {
+                _neighbors.setNeighbor(xoffset, yoffset, hf);
+            }
+
+            void setParent(osg::HeightField* hf)
+            {
+                _parent = hf;
+            }
+
+            osg::HeightField* getParent() const
+            {
+                return _parent.get();
+            }
+
+            const HeightFieldNeighborhood& getNeighborhood() const 
+            {
+                return _neighbors;
+            }
+
+        public:
+            osg::ref_ptr<osg::HeightField> _hf;
+            osg::ref_ptr<GeoLocator>       _locator;
+            bool                           _fallbackData;
+            osg::ref_ptr<osg::HeightField> _parent;
+
+            HeightFieldNeighborhood _neighbors;
+        };
+
 
         class ColorData
         {
@@ -201,7 +255,7 @@ namespace osgEarth { namespace Drivers { namespace MPTerrainEngine
 
     public:
         TileModel( const osgEarth::Revision& mapModelRevision, const MapInfo& mapInfo )
-            : _revision(mapModelRevision), _mapInfo(mapInfo) { }
+            : _revision(mapModelRevision), _mapInfo(mapInfo), _useParentData(false) { }
         TileModel(const TileModel& rhs);
         virtual ~TileModel() { }
 
@@ -224,14 +278,25 @@ namespace osgEarth { namespace Drivers { namespace MPTerrainEngine
         /** Whether there is a heightfield */
         bool hasElevation() const { return _elevationData.getHeightField() != 0L; }
 
+        /** Whether there's a normal map */
+        bool hasNormalMap() const { return _normalData.getHeightField() != 0L; }
+
+        /** Whether to use parent data if it's available. */
+        bool useParentData() const { return _useParentData; }
+
         MapInfo                      _mapInfo;
         Revision                     _revision;
         TileKey                      _tileKey;
         osg::ref_ptr<GeoLocator>     _tileLocator;
         ColorDataByUID               _colorData;
         ElevationData                _elevationData;
+        NormalData                   _normalData;
         float                        _sampleRatio;
-        osg::ref_ptr<osg::StateSet>  _parentStateSet;
+        osg::ref_ptr<osg::Texture>   _elevationTexture;
+        osg::ref_ptr<osg::Texture>   _normalTexture;
+        bool                         _useParentData;
+        
+        osg::ref_ptr<osg::StateSet>        _parentStateSet;
         osg::observer_ptr<const TileModel> _parentModel;
 
         // convenience funciton to pull out a layer by its UID.
@@ -247,6 +312,10 @@ namespace osgEarth { namespace Drivers { namespace MPTerrainEngine
         bool requiresUpdateTraverse() const;
 
         void updateTraverse(osg::NodeVisitor&) const;
+
+        void generateElevationTexture();
+
+        void generateNormalTexture();
     };
 
 } } } // namespace osgEarth::Drivers::MPTerrainEngine
diff --git a/src/osgEarthDrivers/engine_mp/TileModel.cpp b/src/osgEarthDrivers/engine_mp/TileModel.cpp
index 53eda83..2dc2a8b 100644
--- a/src/osgEarthDrivers/engine_mp/TileModel.cpp
+++ b/src/osgEarthDrivers/engine_mp/TileModel.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
@@ -20,6 +23,7 @@
 #include <osgEarth/MapInfo>
 #include <osgEarth/HeightFieldUtils>
 #include <osgEarth/ImageUtils>
+#include <osgEarth/ImageToHeightFieldConverter>
 #include <osgEarth/Registry>
 #include <osg/Texture2D>
 #include <osg/Texture2DArray>
@@ -96,13 +100,13 @@ TileModel::ElevationData::getNormal(const osg::Vec3d&      ndc,
     osg::Vec3d south( hf_ndc.x(), hf_ndc.y()-yres, 0.0 );
     osg::Vec3d north( hf_ndc.x(), hf_ndc.y()+yres, 0.0 );
 
-    if (!HeightFieldUtils::getHeightAtNormalizedLocation(_neighbors, west.x(),  west.y(),  west.z(), interp))
+    if (!HeightFieldUtils::getHeightAtNormalizedLocation(_neighbors, west.x(),  west.y(),  (float&)west.z(), interp))
         west.z() = centerHeight;
-    if (!HeightFieldUtils::getHeightAtNormalizedLocation(_neighbors, east.x(),  east.y(),  east.z(), interp))
+    if (!HeightFieldUtils::getHeightAtNormalizedLocation(_neighbors, east.x(),  east.y(),  (float&)east.z(), interp))
         east.z() = centerHeight;
-    if (!HeightFieldUtils::getHeightAtNormalizedLocation(_neighbors, south.x(), south.y(), south.z(), interp))
+    if (!HeightFieldUtils::getHeightAtNormalizedLocation(_neighbors, south.x(), south.y(), (float&)south.z(), interp))
         south.z() = centerHeight;
-    if (!HeightFieldUtils::getHeightAtNormalizedLocation(_neighbors, north.x(), north.y(), north.z(), interp))
+    if (!HeightFieldUtils::getHeightAtNormalizedLocation(_neighbors, north.x(), north.y(), (float&)north.z(), interp))
         north.z() = centerHeight;
 
     osg::Vec3d westWorld, eastWorld, southWorld, northWorld;
@@ -119,6 +123,30 @@ TileModel::ElevationData::getNormal(const osg::Vec3d&      ndc,
 
 //------------------------------------------------------------------
 
+
+TileModel::NormalData::NormalData(osg::HeightField* hf,
+                                  GeoLocator*       locator,
+                                  bool              fallbackData) :
+_hf          ( hf ),
+_locator     ( locator ),
+_fallbackData( fallbackData )
+{
+    _neighbors._center = hf;
+}
+
+TileModel::NormalData::NormalData(const TileModel::NormalData& rhs) :
+_hf          ( rhs._hf.get() ),
+_locator     ( rhs._locator.get() ),
+_fallbackData( rhs._fallbackData ),
+_parent      ( rhs._parent )
+{
+    _neighbors._center = rhs._neighbors._center.get();
+    for(unsigned i=0; i<8; ++i)
+        _neighbors._neighbors[i] = rhs._neighbors._neighbors[i];
+}
+
+//------------------------------------------------------------------
+
 TileModel::ColorData::ColorData(const osgEarth::ImageLayer* layer,
                                 unsigned                    order,
                                 osg::Image*                 image,
@@ -134,7 +162,30 @@ _fallbackData( fallbackData )
 
     if (image->r() <= 1)
     {
-        _texture = new osg::Texture2D( image );
+        // won't work, needs to be pre-compositing
+        //if ( layer->isCoverage() )
+        //{
+        //    // for coverage data, quantize the alpha values to some arbitrary level for now
+        //    ImageUtils::PixelReader read(image);
+        //    ImageUtils::PixelWriter write(image);
+        //    for(int s=0; s<image->s(); ++s) {
+        //        for(int t=0; t<image->t(); ++t) {
+        //            osg::Vec4f rgba = read(s,t);
+        //            rgba.a() = rgba.a() < 0.2 ? 0.0 : 1.0;
+        //            write(rgba, s, t);
+        //        }
+        //    }
+        //}
+
+        //if ( layer->isCoverage() )
+        //{
+        //    osg::ref_ptr<osg::Image> imageWithMM = ImageUtils::buildNearestNeighborMipmaps(image);
+        //    _texture = new osg::Texture2D( imageWithMM.get() );
+        //}
+        //else
+        {
+            _texture = new osg::Texture2D( image );
+        }
     }
     else // image->r() > 1
     {
@@ -158,23 +209,34 @@ _fallbackData( fallbackData )
     if ( unRefPolicy.isSet() )
         _texture->setUnRefImageDataAfterApply( unRefPolicy.get() );
 
-    _texture->setMaxAnisotropy( 4.0f );
-    _texture->setResizeNonPowerOfTwoHint(false);
-    _texture->setFilter( osg::Texture::MAG_FILTER, magFilter );
-    _texture->setFilter( osg::Texture::MIN_FILTER, minFilter  );
     _texture->setWrap( osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE );
     _texture->setWrap( osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE );
+    _texture->setResizeNonPowerOfTwoHint(false);
 
-    layer->applyTextureCompressionMode( _texture.get() );
-
-    // Disable mip mapping for npot tiles
-    if (!ImageUtils::isPowerOfTwo( image ) || (!image->isMipmap() && ImageUtils::isCompressed(image)))
+    if ( layer->isCoverage() )
+    {
+        // coverages: no filtering or compression allowed.
+        _texture->setFilter( osg::Texture::MIN_FILTER, osg::Texture::NEAREST );
+        _texture->setFilter( osg::Texture::MAG_FILTER, osg::Texture::NEAREST);
+        _texture->setMaxAnisotropy( 1.0f );
+    }
+    else
     {
-        OE_DEBUG<<"Disabling mipmapping for non power of two tile size("<<image->s()<<", "<<image->t()<<")"<<std::endl;
-        _texture->setFilter( osg::Texture::MIN_FILTER, osg::Texture::LINEAR );
-    }    
+        _texture->setMaxAnisotropy( 4.0f );
+        _texture->setFilter( osg::Texture::MAG_FILTER, magFilter );
+        _texture->setFilter( osg::Texture::MIN_FILTER, minFilter  );
+
+        // Disable mip mapping for npot tiles
+        if (!ImageUtils::isPowerOfTwo( image ) || (!image->isMipmap() && ImageUtils::isCompressed(image)))
+        {
+            OE_DEBUG<<"Disabling mipmapping for non power of two tile size("<<image->s()<<", "<<image->t()<<")"<<std::endl;
+            _texture->setFilter( osg::Texture::MIN_FILTER, osg::Texture::LINEAR );
+        }    
+    }
 
     _hasAlpha = image && ImageUtils::hasTransparency(image);
+
+    layer->applyTextureCompressionMode( _texture.get() );    
 }
 
 TileModel::ColorData::ColorData(const TileModel::ColorData& rhs) :
@@ -216,7 +278,8 @@ _tileLocator     ( rhs._tileLocator.get() ),
 _colorData       ( rhs._colorData ),
 _elevationData   ( rhs._elevationData ),
 _sampleRatio     ( rhs._sampleRatio ),
-_parentStateSet  ( rhs._parentStateSet )
+_parentStateSet  ( rhs._parentStateSet ),
+_useParentData   ( rhs._useParentData )
 {
     //nop
 }
@@ -292,6 +355,61 @@ TileModel::setParentTileModel(const TileModel* parent)
 }
 
 void
+TileModel::generateElevationTexture()
+{
+    osg::Image* image = 0L;
+    osg::HeightField* hf = _elevationData.getHeightField();
+    if ( hf )
+    {
+        ImageToHeightFieldConverter conv;
+        image = conv.convert( hf, 32 );
+    }
+    else
+    {
+        // no heightfield; create one and initialize it to zero.
+        image = new osg::Image();
+        image->allocateImage(32, 32, 1, GL_LUMINANCE, GL_FLOAT);
+        ImageUtils::PixelWriter write(image);
+        for(int s=0; s<image->s(); ++s)
+            for(int t=0; t<image->t(); ++t)
+                write(osg::Vec4(0,0,0,0), s, t);        
+    }
+
+    _elevationTexture = new osg::Texture2D( image );
+
+    _elevationTexture->setInternalFormat(GL_LUMINANCE32F_ARB);
+    _elevationTexture->setSourceFormat(GL_LUMINANCE);
+    _elevationTexture->setFilter( osg::Texture::MAG_FILTER, osg::Texture::LINEAR );
+    _elevationTexture->setFilter( osg::Texture::MIN_FILTER, osg::Texture::LINEAR );
+    _elevationTexture->setWrap  ( osg::Texture::WRAP_S,     osg::Texture::CLAMP_TO_EDGE );
+    _elevationTexture->setWrap  ( osg::Texture::WRAP_T,     osg::Texture::CLAMP_TO_EDGE );
+    _elevationTexture->setResizeNonPowerOfTwoHint( false );
+    _elevationTexture->setMaxAnisotropy( 1.0f );
+}
+
+void
+TileModel::generateNormalTexture()
+{
+    osg::Image* image = HeightFieldUtils::convertToNormalMap(
+        _normalData.getNeighborhood(),
+        _tileKey.getProfile()->getSRS() );
+
+    _normalTexture = new osg::Texture2D( image );
+
+    _normalTexture->setInternalFormatMode(osg::Texture::USE_IMAGE_DATA_FORMAT);
+    _normalTexture->setFilter( osg::Texture::MAG_FILTER, osg::Texture::LINEAR );
+    //_normalTexture->setFilter( osg::Texture::MIN_FILTER, osg::Texture::LINEAR );
+    _normalTexture->setFilter( osg::Texture::MIN_FILTER, osg::Texture::LINEAR_MIPMAP_LINEAR );
+    _normalTexture->setWrap  ( osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE );
+    _normalTexture->setWrap  ( osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE );
+    _normalTexture->setResizeNonPowerOfTwoHint( false );
+    _normalTexture->setMaxAnisotropy( 1.0f );
+
+    // So the engine can automatically normalize across tiles.
+    _normalTexture->setUnRefImageDataAfterApply( false );
+}
+
+void
 TileModel::resizeGLObjectBuffers(unsigned maxSize)
 {
     for(ColorDataByUID::iterator i = _colorData.begin(); i != _colorData.end(); ++i )
@@ -303,4 +421,10 @@ TileModel::releaseGLObjects(osg::State* state) const
 {
     for(ColorDataByUID::const_iterator i = _colorData.begin(); i != _colorData.end(); ++i )
         i->second.releaseGLObjects( state );
+
+    if (_normalTexture.valid() && _normalTexture->referenceCount() == 1)
+        _normalTexture->releaseGLObjects(state);
+
+    if (_elevationTexture.valid() && _elevationTexture->referenceCount() == 1)
+        _elevationTexture->releaseGLObjects(state);
 }
diff --git a/src/osgEarthDrivers/engine_mp/TileModelCompiler b/src/osgEarthDrivers/engine_mp/TileModelCompiler
index de67647..bf2af7a 100644
--- a/src/osgEarthDrivers/engine_mp/TileModelCompiler
+++ b/src/osgEarthDrivers/engine_mp/TileModelCompiler
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
@@ -26,6 +29,7 @@
 
 #include <osgEarth/Map>
 #include <osgEarth/Locators>
+#include <osgEarth/Progress>
 
 #include <osg/Node>
 #include <osg/StateSet>
@@ -86,7 +90,10 @@ namespace osgEarth { namespace Drivers { namespace MPTerrainEngine
         /**
          * Compiles a tile model into a TileNode.
          */
-        TileNode* compile(const TileModel* model, const MapFrame& frame);
+        TileNode* compile(
+            TileModel*        model,
+            const MapFrame&   frame,
+            ProgressCallback* progress);
 
     protected:
         const MaskLayerVector&                    _maskLayers;
@@ -94,8 +101,8 @@ namespace osgEarth { namespace Drivers { namespace MPTerrainEngine
         int                                       _textureImageUnit;
         bool                                      _optimizeTriOrientation;
         const MPTerrainEngineOptions&             _options;
-        osg::ref_ptr<osg::Drawable::CullCallback> _cullByTraversalMask;
         CompilerCache                             _cache;
+        bool                                      _debug;
     };
 
 } } } // namespace osgEarth::Drivers::MPTerrainEngine
diff --git a/src/osgEarthDrivers/engine_mp/TileModelCompiler.cpp b/src/osgEarthDrivers/engine_mp/TileModelCompiler.cpp
index 4aed7d7..cb8aa06 100644
--- a/src/osgEarthDrivers/engine_mp/TileModelCompiler.cpp
+++ b/src/osgEarthDrivers/engine_mp/TileModelCompiler.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
@@ -26,6 +29,8 @@
 #include <osgEarth/HeightFieldUtils>
 #include <osgEarth/ImageUtils>
 #include <osgEarth/Utils>
+#include <osgEarth/ECEF>
+#include <osgEarth/ObjectIndex>
 #include <osgEarthSymbology/Geometry>
 #include <osgEarthSymbology/MeshConsolidator>
 
@@ -33,9 +38,11 @@
 #include <osg/Geometry>
 #include <osg/MatrixTransform>
 #include <osg/GL2Extensions>
+#include <osg/ComputeBoundsVisitor>
 #include <osgUtil/DelaunayTriangulator>
 #include <osgUtil/Optimizer>
 #include <osgUtil/MeshOptimizers>
+#include <osgText/Text>
 
 using namespace osgEarth::Drivers::MPTerrainEngine;
 using namespace osgEarth;
@@ -70,7 +77,6 @@ CompilerCache::TexCoordArrayCache::get(const osg::Vec4d& mat,
     return this->back().second;
 }
 
-
 //------------------------------------------------------------------------
 
 
@@ -99,7 +105,7 @@ namespace
     {
         osg::ref_ptr<osg::Vec3dArray> _boundary;
         osg::Vec3d                    _ndcMin, _ndcMax;
-        MPGeometry*                   _geom;
+        osg::ref_ptr<MPGeometry>      _geom;
         osg::ref_ptr<osg::Vec3Array>  _internal;
 
         MaskRecord(osg::Vec3dArray* boundary, osg::Vec3d& ndcMin, osg::Vec3d& ndcMax, MPGeometry* geom) 
@@ -107,8 +113,6 @@ namespace
     };
 
     typedef std::vector<MaskRecord> MaskRecordVector;
-
-
     typedef std::vector<int> Indices;
 
 
@@ -136,9 +140,11 @@ namespace
             renderTileCoords = 0L;
             ownsTileCoords   = false;
             stitchTileCoords = 0L;
-            stitchGeom       = 0L;
+            installParentData = false;
         }
 
+        osg::Matrixd local2world, world2local;
+
         const MapFrame& frame;
 
         bool                     useVBOs;
@@ -146,6 +152,7 @@ namespace
 
         const TileModel*              model;                   // the tile's data model
         osg::ref_ptr<const TileModel> parentModel;             // parent model reference
+        bool                          installParentData;       // whether to install parent colors/normals for blending
 
         const MaskLayerVector&   maskLayers;                    // map-global masking layer set
         const ModelLayerVector&  modelLayers;                   // model layers with masks set
@@ -187,7 +194,18 @@ namespace
         
         // for masking/stitching:
         MaskRecordVector         maskRecords;
-        MPGeometry*              stitchGeom;
+        //MPGeometry*              stitchGeom;
+
+        bool useUInt;
+        osg::DrawElements* newDrawElements(GLenum mode) {
+            osg::DrawElements* de = 0L;
+            if ( useUInt )
+                de = new osg::DrawElementsUInt(mode);
+            else
+                de = new osg::DrawElementsUShort(mode);
+            de->setName("TMC");
+            return de;
+        }
     };
 
 
@@ -230,10 +248,9 @@ namespace
 
             if (x_match && y_match)
             {
-                d.stitchGeom = new MPGeometry( d.model->_tileKey, d.frame, d.textureImageUnit );
-                //d.stitchGeom->setUseVertexBufferObjects(d.useVBOs);
-                d.surfaceGeode->addDrawable(d.stitchGeom);
-                d.maskRecords.push_back( MaskRecord(boundary, min_ndc, max_ndc, d.stitchGeom) );
+                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) );
             }
         }
     }
@@ -289,12 +306,12 @@ namespace
      * Calculates the sample rate and allocates all the vertex, normal, and color
      * arrays for the tile.
      */
-    void setupGeometryAttributes( Data& d, double sampleRatio )
+    void setupGeometryAttributes( Data& d, int tileSize )
     {
-        d.numRows = 8;
-        d.numCols = 8;
-        d.originalNumRows = 8;
-        d.originalNumCols = 8;        
+        d.numRows = 17;
+        d.numCols = 17;
+        d.originalNumRows = 17;
+        d.originalNumCols = 17;        
 
         // read the row/column count and skirt size from the model:
         osg::HeightField* hf = d.model->_elevationData.getHeightField();
@@ -311,16 +328,15 @@ namespace
         d.i_sampleFactor = 1.0f;
         d.j_sampleFactor = 1.0f;
 
-        if ( sampleRatio != 1.0f )
-        {            
-            d.numCols = osg::maximum((unsigned int) (float(d.originalNumCols)*sqrtf(sampleRatio)), 4u);
-            d.numRows = osg::maximum((unsigned int) (float(d.originalNumRows)*sqrtf(sampleRatio)), 4u);
+        if ( tileSize > 0 )
+        {
+            d.numCols = tileSize;
+            d.numRows = tileSize;
 
             d.i_sampleFactor = double(d.originalNumCols-1)/double(d.numCols-1);
             d.j_sampleFactor = double(d.originalNumRows-1)/double(d.numRows-1);
         }
 
-
         // calculate the total number of verts:
         d.numVerticesInSkirt   = d.createSkirt ? (2 * (d.numCols*2 + d.numRows*2 - 4)) : 0;
         d.numVerticesInSurface = d.numCols * d.numRows + d.numVerticesInSkirt;
@@ -355,6 +371,9 @@ namespace
         d.elevations = new osg::FloatArray();
         d.elevations->reserve( d.numVerticesInSurface );
         d.indices.resize( d.numVerticesInSurface, -1 );
+
+        // Uint required?
+        d.useUInt = d.numVerticesInSurface > 0xFFFF;
     }
 
 
@@ -367,6 +386,14 @@ namespace
         // array, saving on memory.
         d.renderLayers.reserve( d.model->_colorData.size() );
 
+        if ( d.maskRecords.size() > 0 )
+        {
+            if ( !d.stitchTileCoords.valid() )
+            {
+                d.stitchTileCoords = new osg::Vec2Array();
+            }
+        }
+
 #ifdef USE_TEXCOORD_CACHE
         // unit tile coords - [0..1] always across the tile.
         osg::Vec4d idmat;
@@ -447,13 +474,7 @@ namespace
 
                     if ( d.maskRecords.size() > 0 )
                     {
-                        r._stitchTexCoords = new osg::Vec2Array();
-                        //r._stitchTexCoords->setVertexBufferObject( d.stitchGeom->getOrCreateVertexBufferObject() );
-                        if ( !d.stitchTileCoords.valid() )
-                        {
-                            d.stitchTileCoords = new osg::Vec2Array();
-                            //d.stitchTileCoords->setVertexBufferObject( d.stitchGeom->getOrCreateVertexBufferObject() );
-                        }
+                        r._stitchTexCoords = new osg::Vec2Array();                        
                     }
                 }
 
@@ -465,21 +486,24 @@ namespace
                 }
 
                 // install the parent color data layer if necessary.
-                if ( d.parentModel.valid() )
-                {                    
-                    if (!d.parentModel->getColorData( r._layer.getUID(), r._layerParent ))
+                if ( d.installParentData )
+                {
+                    if ( d.parentModel.valid() )
+                    {                    
+                        if (!d.parentModel->getColorData( r._layer.getUID(), r._layerParent ))
+                        {
+                            // If we can't get the color data from the parent that means it doesn't exist, perhaps b/c of a min level setting
+                            // So we create a false layer parent with a transparent image so it will fade into the real data.
+                            r._layerParent = r._layer;
+                            r._layerParent._texture = new osg::Texture2D(ImageUtils::createEmptyImage());
+                            r._layerParent._hasAlpha = true;
+                        }
+                    }
+                    else
                     {
-                        // If we can't get the color data from the parent that means it doesn't exist, perhaps b/c of a min level setting
-                        // So we create a false layer parent with a transparent image so it will fade into the real data.
                         r._layerParent = r._layer;
-                        r._layerParent._texture = new osg::Texture2D(ImageUtils::createEmptyImage());
-                        r._layerParent._hasAlpha = true;
                     }
                 }
-                else
-                {
-                    r._layerParent = r._layer;
-                }
 
                 d.renderLayers.push_back( r );
 
@@ -499,7 +523,7 @@ namespace
      * Iterate over the sampling grid and calculate the vertex positions and normals
      * for each sampling point.
      */
-    void createSurfaceGeometry( Data& d )
+    void createSurfaceGeometry( Data& d, bool debug )
     {
         d.surfaceBound.init();
 
@@ -508,6 +532,31 @@ namespace
         osg::HeightField* hf            = d.model->_elevationData.getHeightField();
         GeoLocator*       hfLocator     = d.model->_elevationData.getLocator();
 
+        if ( debug )
+        {
+            // Debugging code to help identify all zero heightfields.
+            bool allZero = true;
+            // Check for an all 0 heightfield
+            for (unsigned int c = 0; c < hf->getNumColumns(); ++c)
+            {
+                for (unsigned int r = 0; r < hf->getNumRows(); ++r)
+                {
+                    float h = hf->getHeight(c, r);
+                    if (h != 0)
+                    {
+                        allZero = false;
+                        break;
+                    }
+                }
+            }
+
+            if (allZero)
+            {
+                OE_DEBUG << "ALL ZERO HEIGHTFIELD " << d.model->_tileKey.str() << std::endl;
+            }
+        }
+
+
         // populate vertex and tex coord arrays    
         for(unsigned j=0; j < d.numRows; ++j)
         {
@@ -576,8 +625,8 @@ namespace
 
                     osg::Vec3d model;
                     d.model->_tileLocator->unitToModel( ndc, model );
-
-                    (*d.surfaceVerts).push_back(model - d.centerModel);
+                    osg::Vec3d modelLTP = model * d.world2local;
+                    (*d.surfaceVerts).push_back(modelLTP);
 
                     // grow the bounding sphere:
                     d.surfaceBound.expandBy( (*d.surfaceVerts).back() );
@@ -612,9 +661,8 @@ namespace
                     osg::Vec3d ndc_plus_one(ndc.x(), ndc.y(), ndc.z() + 1.0);
                     osg::Vec3d model_up;
                     d.model->_tileLocator->unitToModel(ndc_plus_one, model_up);
-                    model_up = model_up - model;
+                    model_up = (model_up*d.world2local) - modelLTP;
                     model_up.normalize();
-
                     (*d.normals).push_back(model_up);
 
                     // Calculate and store the "old height", i.e the height value from
@@ -667,113 +715,131 @@ namespace
     {
         bool hasElev = d.model->hasElevation();
 
-		osg::ref_ptr<osgUtil::DelaunayTriangulator> trig=new osgUtil::DelaunayTriangulator();
+        osg::ref_ptr<osgUtil::DelaunayTriangulator> trig=new osgUtil::DelaunayTriangulator();
+
+        std::vector<osg::ref_ptr<osgUtil::DelaunayConstraint> > alldcs;
 
-		std::vector<osg::ref_ptr<osgUtil::DelaunayConstraint> > alldcs;
-	
-		osg::ref_ptr<osg::Vec3Array> coordsArray = new osg::Vec3Array;
-						
-		double minndcx = d.maskRecords[0]._ndcMin.x();
-		double minndcy = d.maskRecords[0]._ndcMin.y();
-		double maxndcx = d.maskRecords[0]._ndcMax.x();
-		double maxndcy = d.maskRecords[0]._ndcMax.y();
+        osg::ref_ptr<osg::Vec3Array> coordsArray = new osg::Vec3Array;
+
+        double minndcx = d.maskRecords[0]._ndcMin.x();
+        double minndcy = d.maskRecords[0]._ndcMin.y();
+        double maxndcx = d.maskRecords[0]._ndcMax.x();
+        double maxndcy = d.maskRecords[0]._ndcMax.y();
         for (int mrs = 1; mrs < d.maskRecords.size(); ++mrs)
-		{
-			if ( d.maskRecords[mrs]._ndcMin.x()< minndcx)
-			{
-				minndcx = d.maskRecords[mrs]._ndcMin.x();
-			}
-			if ( d.maskRecords[mrs]._ndcMin.y()< minndcy)
-			{
-				minndcy = d.maskRecords[mrs]._ndcMin.y();
-			}
-			if ( d.maskRecords[mrs]._ndcMax.x()> maxndcx)
-			{
-				maxndcx = d.maskRecords[mrs]._ndcMax.x();
-			}
-			if ( d.maskRecords[mrs]._ndcMax.y()> maxndcy)
-			{
-				maxndcy = d.maskRecords[mrs]._ndcMax.y();
-			}
-
-			
-		}
-       
-            int min_i = (int)floor(minndcx * (double)(d.numCols-1));
-            if (min_i < 0) min_i = 0;
-            if (min_i >= (int)d.numCols) min_i = d.numCols - 1;
+        {
+            if ( d.maskRecords[mrs]._ndcMin.x()< minndcx)
+            {
+                minndcx = d.maskRecords[mrs]._ndcMin.x();
+            }
+            if ( d.maskRecords[mrs]._ndcMin.y()< minndcy)
+            {
+                minndcy = d.maskRecords[mrs]._ndcMin.y();
+            }
+            if ( d.maskRecords[mrs]._ndcMax.x()> maxndcx)
+            {
+                maxndcx = d.maskRecords[mrs]._ndcMax.x();
+            }
+            if ( d.maskRecords[mrs]._ndcMax.y()> maxndcy)
+            {
+                maxndcy = d.maskRecords[mrs]._ndcMax.y();
+            }			
+        }
 
-            int min_j = (int)floor(minndcy * (double)(d.numRows-1));
-            if (min_j < 0) min_j = 0;
-            if (min_j >= (int)d.numRows) min_j = d.numRows - 1;
+        int min_i = (int)floor(minndcx * (double)(d.numCols-1));
+        if (min_i < 0) min_i = 0;
+        if (min_i >= (int)d.numCols) min_i = d.numCols - 1;
 
-            int max_i = (int)ceil(maxndcx * (double)(d.numCols-1));
-            if (max_i < 0) max_i = 0;
-            if (max_i >= (int)d.numCols) max_i = d.numCols - 1;
+        int min_j = (int)floor(minndcy * (double)(d.numRows-1));
+        if (min_j < 0) min_j = 0;
+        if (min_j >= (int)d.numRows) min_j = d.numRows - 1;
 
-            int max_j = (int)ceil(maxndcy * (double)(d.numRows-1));
-            if (max_j < 0) max_j = 0;
-            if (max_j >= (int)d.numRows) max_j = d.numRows - 1;
+        int max_i = (int)ceil(maxndcx * (double)(d.numCols-1));
+        if (max_i < 0) max_i = 0;
+        if (max_i >= (int)d.numCols) max_i = d.numCols - 1;
 
-            if (min_i >= 0 && max_i >= 0 && min_j >= 0 && max_j >= 0)
-            {
-                int num_i = max_i - min_i + 1;
-                int num_j = max_j - min_j + 1;
+        int max_j = (int)ceil(maxndcy * (double)(d.numRows-1));
+        if (max_j < 0) max_j = 0;
+        if (max_j >= (int)d.numRows) max_j = d.numRows - 1;
 
-                osg::ref_ptr<Polygon> maskSkirtPoly = new Polygon();
-                maskSkirtPoly->resize(num_i * 2 + num_j * 2 - 4);
+        if (min_i >= 0 && max_i >= 0 && min_j >= 0 && max_j >= 0)
+        {
+            int num_i = max_i - min_i + 1;
+            int num_j = max_j - min_j + 1;
 
-                for (int i = 0; i < num_i; i++)
-                {
-                    //int index = indices[min_j*numColumns + i + min_i];
-                    {
-                        osg::Vec3d ndc( ((double)(i + min_i))/(double)(d.numCols-1), ((double)min_j)/(double)(d.numRows-1), 0.0);
+            osg::ref_ptr<Polygon> maskSkirtPoly = new Polygon();
+            maskSkirtPoly->resize(num_i * 2 + num_j * 2 - 4);
 
-                        //if (elevationLayer)
-                        if ( hasElev )
-                        {
-                            float value = 0.0f;
-                            if ( d.model->_elevationData.getHeight( ndc, d.model->_tileLocator.get(), value, INTERP_BILINEAR ) )
-                                ndc.z() = value * d.heightScale + d.heightOffset;
-                        }
+            for (int i = 0; i < num_i; i++)
+            {
+                //int index = indices[min_j*numColumns + i + min_i];
+                {
+                    osg::Vec3d ndc( ((double)(i + min_i))/(double)(d.numCols-1), ((double)min_j)/(double)(d.numRows-1), 0.0);
 
-                        (*maskSkirtPoly)[i] = ndc;
+                    //if (elevationLayer)
+                    if ( hasElev )
+                    {
+                        float value = 0.0f;
+                        if ( d.model->_elevationData.getHeight( ndc, d.model->_tileLocator.get(), value, INTERP_BILINEAR ) )
+                            ndc.z() = value * d.heightScale + d.heightOffset;
                     }
 
-                    //index = indices[max_j*numColumns + i + min_i];
-                    {
-                        osg::Vec3d ndc( ((double)(i + min_i))/(double)(d.numCols-1), ((double)max_j)/(double)(d.numRows-1), 0.0);
+                    (*maskSkirtPoly)[i] = ndc;
+                }
 
-                        if ( hasElev )
-                        {
-                            float value = 0.0f;
-                            if ( d.model->_elevationData.getHeight( ndc, d.model->_tileLocator.get(), value, INTERP_BILINEAR ) )
-                                ndc.z() = value * d.heightScale + d.heightOffset;
-                        }
+                //index = indices[max_j*numColumns + i + min_i];
+                {
+                    osg::Vec3d ndc( ((double)(i + min_i))/(double)(d.numCols-1), ((double)max_j)/(double)(d.numRows-1), 0.0);
 
-                        (*maskSkirtPoly)[i + (2 * num_i + num_j - 3) - 2 * i] = ndc;
+                    if ( hasElev )
+                    {
+                        float value = 0.0f;
+                        if ( d.model->_elevationData.getHeight( ndc, d.model->_tileLocator.get(), value, INTERP_BILINEAR ) )
+                            ndc.z() = value * d.heightScale + d.heightOffset;
                     }
+
+                    (*maskSkirtPoly)[i + (2 * num_i + num_j - 3) - 2 * i] = ndc;
                 }
-                for (int j = 0; j < num_j - 2; j++)
+            }
+            for (int j = 0; j < num_j - 2; j++)
+            {
+                //int index = indices[(min_j + j + 1)*numColumns + max_i];
                 {
-                    //int index = indices[(min_j + j + 1)*numColumns + max_i];
+                    osg::Vec3d ndc( ((double)max_i)/(double)(d.numCols-1), ((double)(min_j + j + 1))/(double)(d.numRows-1), 0.0);
+
+                    if ( hasElev )
                     {
-                        osg::Vec3d ndc( ((double)max_i)/(double)(d.numCols-1), ((double)(min_j + j + 1))/(double)(d.numRows-1), 0.0);
+                        float value = 0.0f;
+                        if ( d.model->_elevationData.getHeight( ndc, d.model->_tileLocator.get(), value, INTERP_BILINEAR ) )
+                            ndc.z() = value * d.heightScale + d.heightOffset;
+                    }
 
-                        if ( hasElev )
-                        {
-                            float value = 0.0f;
-                            if ( d.model->_elevationData.getHeight( ndc, d.model->_tileLocator.get(), value, INTERP_BILINEAR ) )
-                                ndc.z() = value * d.heightScale + d.heightOffset;
-                        }
+                    (*maskSkirtPoly)[j + num_i] = ndc;
+                }
+
+                //index = indices[(min_j + j + 1)*numColumns + min_i];
+                {
+                    osg::Vec3d ndc( ((double)min_i)/(double)(d.numCols-1), ((double)(min_j + j + 1))/(double)(d.numRows-1), 0.0);
 
-                        (*maskSkirtPoly)[j + num_i] = ndc;
+                    if ( hasElev )
+                    {
+                        float value = 0.0f;
+                        if ( d.model->_elevationData.getHeight( ndc, d.model->_tileLocator.get(), value, INTERP_BILINEAR ) )
+                            ndc.z() = value * d.heightScale + d.heightOffset;
                     }
 
-                    //index = indices[(min_j + j + 1)*numColumns + min_i];
+                    (*maskSkirtPoly)[j + (2 * num_i + 2 * num_j - 5) - 2 * j] = ndc;
+                }
+            }
+
+            for (int j = 0; j < num_j; j++)
+            {
+                for (int i = 0; i < num_i; i++)
+                {
+                    //int index = indices[min_j*numColumns + i + min_i];
                     {
-                        osg::Vec3d ndc( ((double)min_i)/(double)(d.numCols-1), ((double)(min_j + j + 1))/(double)(d.numRows-1), 0.0);
+                        osg::Vec3d ndc( ((double)(i + min_i))/(double)(d.numCols-1), ((double)(j+min_j))/(double)(d.numRows-1), 0.0);
 
+                        //if (elevationLayer)
                         if ( hasElev )
                         {
                             float value = 0.0f;
@@ -781,53 +847,35 @@ namespace
                                 ndc.z() = value * d.heightScale + d.heightOffset;
                         }
 
-                        (*maskSkirtPoly)[j + (2 * num_i + 2 * num_j - 5) - 2 * j] = ndc;
-                    }
+                        coordsArray->push_back(ndc) ;
+                    }						
                 }
+            }
 
-			    for (int j = 0; j < num_j; j++)
-					for (int i = 0; i < num_i; i++)
-					{
-						//int index = indices[min_j*numColumns + i + min_i];
-						{
-							osg::Vec3d ndc( ((double)(i + min_i))/(double)(d.numCols-1), ((double)(j+min_j))/(double)(d.numRows-1), 0.0);
-
-							//if (elevationLayer)
-							if ( hasElev )
-							{
-								float value = 0.0f;
-								if ( d.model->_elevationData.getHeight( ndc, d.model->_tileLocator.get(), value, INTERP_BILINEAR ) )
-									ndc.z() = value * d.heightScale + d.heightOffset;
-							}
-
-							coordsArray->push_back(ndc) ;
-						}						
-					}
 
+            // Use delaunay triangulation for stitching:
+            for (MaskRecordVector::iterator mr = d.maskRecords.begin();mr != d.maskRecords.end();mr++)
+            {
 
-                // Use delaunay triangulation for stitching:
-                for (MaskRecordVector::iterator mr = d.maskRecords.begin();mr != d.maskRecords.end();mr++)
+                // Add the outter stitching bounds to the collection of vertices to be used for triangulation
+                //	coordsArray->insert(coordsArray->end(), (*mr)._internal->begin(), (*mr)._internal->end());
+                //Create local polygon representing mask
+                osg::ref_ptr<Polygon> maskPoly = new Polygon();
+                for (osg::Vec3dArray::iterator it = (*mr)._boundary->begin(); it != (*mr)._boundary->end(); ++it)
                 {
+                    osg::Vec3d local;
+                    d.geoLocator->convertModelToLocal(*it, local);
+                    maskPoly->push_back(local);
+                }
+                // Add mask bounds as a triangulation constraint
 
-					 // Add the outter stitching bounds to the collection of vertices to be used for triangulation
-				//	coordsArray->insert(coordsArray->end(), (*mr)._internal->begin(), (*mr)._internal->end());
-					//Create local polygon representing mask
-					osg::ref_ptr<Polygon> maskPoly = new Polygon();
-					for (osg::Vec3dArray::iterator it = (*mr)._boundary->begin(); it != (*mr)._boundary->end(); ++it)
-					{
-						osg::Vec3d local;
-						d.geoLocator->convertModelToLocal(*it, local);
-						maskPoly->push_back(local);
-					}
-					// Add mask bounds as a triangulation constraint
-										
-					osg::ref_ptr<osgUtil::DelaunayConstraint> newdc=new osgUtil::DelaunayConstraint;
-					osg::Vec3Array* maskConstraint = new osg::Vec3Array();
-					newdc->setVertexArray(maskConstraint);
-					
-					//Crop the mask to the stitching poly (for case where mask crosses tile edge)
-					osg::ref_ptr<Geometry> maskCrop;
-					maskPoly->crop(maskSkirtPoly.get(), maskCrop);
+                osg::ref_ptr<osgUtil::DelaunayConstraint> newdc=new osgUtil::DelaunayConstraint;
+                osg::Vec3Array* maskConstraint = new osg::Vec3Array();
+                newdc->setVertexArray(maskConstraint);
+
+                //Crop the mask to the stitching poly (for case where mask crosses tile edge)
+                osg::ref_ptr<Geometry> maskCrop;
+                maskPoly->crop(maskSkirtPoly.get(), maskCrop);
 
                 GeometryIterator i( maskCrop.get(), false );
                 while( i.hasMore() )
@@ -836,13 +884,13 @@ namespace
                     if (!part)
                         continue;
 
-						if (part->getType() == Geometry::TYPE_POLYGON)
-						{
-							osg::Vec3Array* partVerts = part->toVec3Array();
-							maskConstraint->insert(maskConstraint->end(), partVerts->begin(), partVerts->end());
-							newdc->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::LINE_LOOP, maskConstraint->size() - partVerts->size(), partVerts->size()));
-						}
-					}
+                    if (part->getType() == Geometry::TYPE_POLYGON)
+                    {
+                        osg::Vec3Array* partVerts = part->toVec3Array();
+                        maskConstraint->insert(maskConstraint->end(), partVerts->begin(), partVerts->end());
+                        newdc->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::LINE_LOOP, maskConstraint->size() - partVerts->size(), partVerts->size()));
+                    }
+                }
 
                 // Cropping strips z-values so need reassign
                 std::vector<int> isZSet;
@@ -857,6 +905,17 @@ namespace
                         {
                             (*it).z() = (*mit).z();
                             zSet += 1;
+
+                            // Remove duplicate point from coordsArray to avoid duplicate point warnings
+                            osg::Vec3Array::iterator caIt;
+                            for (caIt = coordsArray->begin(); caIt != coordsArray->end(); ++caIt)
+                            {
+                                if (osg::absolute((*caIt).x() - (*it).x()) < MATCH_TOLERANCE && osg::absolute((*caIt).y() - (*it).y()) < MATCH_TOLERANCE)
+                                    break;
+                            }
+                            if (caIt != coordsArray->end())
+                                coordsArray->erase(caIt);
+
                             break;
                         }
                     }
@@ -950,100 +1009,111 @@ namespace
                     if (!isZSet[count])
                         OE_WARN << LC << "Z-value not set for mask constraint vertex" << std::endl;
 
-						count++;
-					}
-					
-					alldcs.push_back(newdc);
+                    count++;
                 }
-                          							
-          
-				//coordsArray->insert(coordsArray->end(),maskSkirtPoly->begin(),maskSkirtPoly->end());
-				trig->setInputPointArray(coordsArray.get());
 
+                alldcs.push_back(newdc);
+            }
 
-				for (int dcnum =0; dcnum < alldcs.size();dcnum++)
-				{
-					trig->addInputConstraint(alldcs[dcnum].get());
-				}
-		       
-		         // Create array to hold vertex normals
-                osg::Vec3Array *norms=new osg::Vec3Array;
-                trig->setOutputNormalArray(norms);
 
+            //coordsArray->insert(coordsArray->end(),maskSkirtPoly->begin(),maskSkirtPoly->end());
+            trig->setInputPointArray(coordsArray.get());
 
-                // Triangulate vertices and remove triangles that lie within the contraint loop
-                trig->triangulate();
-				for (int dcnum =0; dcnum < alldcs.size();dcnum++)
-				{
-					
-					trig->removeInternalTriangles(alldcs[dcnum].get());
-				}             
 
+            for (int dcnum =0; dcnum < alldcs.size();dcnum++)
+            {
+                trig->addInputConstraint(alldcs[dcnum].get());
+            }
 
-				MaskRecordVector::iterator mr = d.maskRecords.begin();
-                // Set up new arrays to hold final vertices and normals
-                osg::Geometry* stitch_geom = (*mr)._geom;
-                osg::Vec3Array* stitch_verts = new osg::Vec3Array();
-                stitch_verts->reserve(trig->getInputPointArray()->size());
-                stitch_geom->setVertexArray(stitch_verts);
-                osg::Vec3Array* stitch_norms = new osg::Vec3Array(trig->getInputPointArray()->size());
-                stitch_geom->setNormalArray( stitch_norms );
-                stitch_geom->setNormalBinding( osg::Geometry::BIND_PER_VERTEX );
+            // Create array to hold vertex normals
+            osg::Vec3Array *norms=new osg::Vec3Array;
+            trig->setOutputNormalArray(norms);
 
 
-                //Initialize tex coords
-                if ( d.renderLayers.size() > 0 )
+            // Triangulate vertices and remove triangles that lie within the contraint loop
+            trig->triangulate();
+            for (int dcnum =0; dcnum < alldcs.size();dcnum++)
+            {
+
+                trig->removeInternalTriangles(alldcs[dcnum].get());
+            }             
+
+
+            MaskRecordVector::iterator mr = d.maskRecords.begin();
+            // Set up new arrays to hold final vertices and normals
+            osg::Geometry* stitch_geom = (*mr)._geom;
+            osg::Vec3Array* stitch_verts = new osg::Vec3Array();
+            stitch_verts->reserve(trig->getInputPointArray()->size());
+            stitch_geom->setVertexArray(stitch_verts);
+            osg::Vec3Array* stitch_norms = new osg::Vec3Array(trig->getInputPointArray()->size());
+            stitch_geom->setNormalArray( stitch_norms );
+            stitch_geom->setNormalBinding( osg::Geometry::BIND_PER_VERTEX );
+
+
+            //Initialize tex coords
+            if ( d.renderLayers.size() > 0 )
+            {
+                for (unsigned int i = 0; i < d.renderLayers.size(); ++i)
                 {
-                    for (unsigned int i = 0; i < d.renderLayers.size(); ++i)
-                    {
-                        d.renderLayers[i]._stitchTexCoords->reserve(trig->getInputPointArray()->size());
-                    }
+                    d.renderLayers[i]._stitchTexCoords->reserve(trig->getInputPointArray()->size());
                 }
-                d.stitchTileCoords->reserve(trig->getInputPointArray()->size());
+            }
+            d.stitchTileCoords->reserve(trig->getInputPointArray()->size());
 
-                // Iterate through point to convert to model coords, calculate normals, and set up tex coords
-                int norm_i = -1;
-                for (osg::Vec3Array::iterator it = trig->getInputPointArray()->begin(); it != trig->getInputPointArray()->end(); ++it)
+            // Iterate through point to convert to model coords, calculate normals, and set up tex coords
+            int norm_i = -1;
+            for (osg::Vec3Array::iterator it = trig->getInputPointArray()->begin(); it != trig->getInputPointArray()->end(); ++it)
+            {
+                // get model coords
+                osg::Vec3d model;
+                d.model->_tileLocator->convertLocalToModel(*it, model);
+                model = model * d.world2local;
+
+                stitch_verts->push_back(model);
+
+                // calc normals
+                osg::Vec3d local_one(*it);
+                local_one.z() += 1.0;
+                osg::Vec3d model_one;
+                d.model->_tileLocator->convertLocalToModel( local_one, model_one );
+                model_one = (model_one*d.world2local) - model;
+                model_one.normalize();
+                (*stitch_norms)[++norm_i] = model_one;
+
+                // set up text coords
+                if (d.renderLayers.size() > 0)
                 {
-                    // get model coords
-                    osg::Vec3d model;
-                    d.model->_tileLocator->convertLocalToModel(*it, model);
-                    model = model - d.centerModel;
-
-                    stitch_verts->push_back(model);
-
-                    // calc normals
-                    osg::Vec3d local_one(*it);
-                    local_one.z() += 1.0;
-                    osg::Vec3d model_one;
-                    d.model->_tileLocator->convertLocalToModel( local_one, model_one );
-                    model_one = model_one - model;
-                    model_one.normalize();
-                    (*stitch_norms)[++norm_i] = model_one;
-
-                    // set up text coords
-                    if (d.renderLayers.size() > 0)
+                    for (unsigned int i = 0; i < d.renderLayers.size(); ++i)
                     {
-                        for (unsigned int i = 0; i < d.renderLayers.size(); ++i)
+                        if (!d.renderLayers[i]._locator->isEquivalentTo( *d.geoLocator.get() )) //*masterTextureLocator.get()))
                         {
-                            if (!d.renderLayers[i]._locator->isEquivalentTo( *d.geoLocator.get() )) //*masterTextureLocator.get()))
-                            {
-                                osg::Vec3d color_ndc;
-                                osgTerrain::Locator::convertLocalCoordBetween(*d.geoLocator.get(), (*it), *d.renderLayers[i]._locator.get(), color_ndc);
-                                d.renderLayers[i]._stitchTexCoords->push_back(osg::Vec2(color_ndc.x(), color_ndc.y()));
-                            }
-                            else
-                            {
-                                d.renderLayers[i]._stitchTexCoords->push_back(osg::Vec2((*it).x(), (*it).y()));
-                            }
+                            osg::Vec3d color_ndc;
+                            osgTerrain::Locator::convertLocalCoordBetween(*d.geoLocator.get(), (*it), *d.renderLayers[i]._locator.get(), color_ndc);
+                            d.renderLayers[i]._stitchTexCoords->push_back(osg::Vec2(color_ndc.x(), color_ndc.y()));
+                        }
+                        else
+                        {
+                            d.renderLayers[i]._stitchTexCoords->push_back(osg::Vec2((*it).x(), (*it).y()));
                         }
                     }
-                    d.stitchTileCoords->push_back(osg::Vec2((*it).x(), (*it).y()));
                 }
+                d.stitchTileCoords->push_back(osg::Vec2((*it).x(), (*it).y()));
+            }
 
 
-                // Get triangles from triangulator and add as primative set to the geometry
-                stitch_geom->addPrimitiveSet(trig->getTriangles());
+            // Get triangles from triangulator and add as primative set to the geometry
+            osg::DrawElementsUInt* tris = trig->getTriangles();
+            if ( tris && tris->getNumIndices() >= 3 )
+            {
+                stitch_geom->addPrimitiveSet(tris);
+            }
+
+            // Finally, add it to the geode.
+            if (stitch_geom->getVertexArray() &&
+                stitch_geom->getVertexArray()->getNumElements() > 0 )
+            {
+                d.surfaceGeode->addDrawable(stitch_geom);
+            }
         }
     }
 
@@ -1055,9 +1125,6 @@ namespace
      */
     void createSkirtGeometry( Data& d, double skirtRatio )
     {
-        // surface normals will double as our skirt extrusion vectors
-        osg::Vec3Array* skirtVectors = d.normals;
-
         // find the skirt height
         double skirtHeight = d.surfaceBound.radius() * skirtRatio;
 
@@ -1067,30 +1134,43 @@ namespace
         osg::Vec4Array* skirtAttribs = static_cast<osg::Vec4Array*>(d.surface->getVertexAttribArray(osg::Drawable::ATTRIBUTE_6)); //new osg::Vec4Array();
         osg::Vec4Array* skirtAttribs2 = static_cast<osg::Vec4Array*>(d.surface->getVertexAttribArray(osg::Drawable::ATTRIBUTE_7)); //new osg::Vec4Array();
 
-        osg::ref_ptr<osg::DrawElementsUShort> elements = new osg::DrawElementsUShort(GL_TRIANGLE_STRIP);
+        // If we don't have any primitive sets on the surface then bail.  This might happen if a tile falls completely in a mask.
+        if (d.surface->getNumPrimitiveSets() == 0)
+        {
+            return;
+        }
 
+        // Use the existing DrawElements on the surface so we merge the skirts and the surface together.
+        osg::ref_ptr<osg::DrawElements> elements = dynamic_cast< osg::DrawElements* >(d.surface->getPrimitiveSet(0));
+        if (!elements)
+        {
+            OE_WARN << LC << "Couldn't find existing DrawElements" << std::endl;
+            return;
+        }
+       
+        int skirtIndex = 0;
         // bottom:
-        for( unsigned int c=0; c<d.numCols-1; ++c )
+        for( unsigned int c=0; c < d.numCols; ++c )
         {
             int orig_i = d.indices[c];
-
+          
             if (orig_i < 0)
             {
-                if ( elements->size() > 0 )
-                    d.surface->addPrimitiveSet( elements.get() );
-                elements = new osg::DrawElementsUShort(GL_TRIANGLE_STRIP);
+                skirtIndex = 0;
             }
             else
             {
+                const osg::Vec4f& surfaceAttribs = (*d.surfaceAttribs)[orig_i];
+                skirtAttribs->push_back( surfaceAttribs - osg::Vec4f(0,0,0,skirtHeight) );
+
+                osg::Vec3 skirtVector( surfaceAttribs.x(), surfaceAttribs.y(), surfaceAttribs.z() );
+
                 const osg::Vec3f& surfaceVert = (*d.surfaceVerts)[orig_i];
-                skirtVerts->push_back( surfaceVert - ((*skirtVectors)[orig_i])*skirtHeight );
+                skirtVerts->push_back( surfaceVert - skirtVector*skirtHeight );
 
                 const osg::Vec3f& surfaceNormal = (*d.normals)[orig_i];
                 skirtNormals->push_back( surfaceNormal );
 
-                const osg::Vec4f& surfaceAttribs = (*d.surfaceAttribs)[orig_i];
-                skirtAttribs->push_back( surfaceAttribs - osg::Vec4f(0,0,0,skirtHeight) );
-
                 const osg::Vec4f& surfaceAttribs2 = (*d.surfaceAttribs2)[orig_i];
                 skirtAttribs2->push_back( surfaceAttribs2 - osg::Vec4f(0,0,0,skirtHeight) );
 
@@ -1102,36 +1182,52 @@ namespace
                         {
                             const osg::Vec2& tc = (*d.renderLayers[i]._texCoords.get())[orig_i];
                             d.renderLayers[i]._texCoords->push_back( tc );
-                        }
+                        }                        
                     }
                 }
 
-                elements->addElement(orig_i);
-                elements->addElement(skirtVerts->size()-1);
+                const osg::Vec2& tilec = (*d.renderTileCoords.get())[orig_i];
+                d.renderTileCoords->push_back( tilec );
+
+                skirtIndex++;
+                if (skirtIndex > 1)
+                {
+                    int prev_i = d.indices[c-1];
+                    elements->addElement(prev_i);
+                    elements->addElement(skirtVerts->size() - 2);
+                    elements->addElement(orig_i);
+
+                    elements->addElement(skirtVerts->size() - 1);
+                    elements->addElement(orig_i);
+                    elements->addElement(skirtVerts->size() - 2);
+                }
             }
+            
         }
 
         // right:
-        for( unsigned int r=0; r<d.numRows-1; ++r )
+        skirtIndex = 0;
+        for( unsigned int r=0; r < d.numRows; ++r )
         {
             int orig_i = d.indices[r*d.numCols+(d.numCols-1)];
             if (orig_i < 0)
             {
-                if ( elements->size() > 0 )
-                    d.surface->addPrimitiveSet( elements.get() );
-                elements = new osg::DrawElementsUShort(GL_TRIANGLE_STRIP);
+                skirtIndex = 0;
             }
             else
             {
+                const osg::Vec4f& surfaceAttribs = (*d.surfaceAttribs)[orig_i];
+                skirtAttribs->push_back( surfaceAttribs - osg::Vec4f(0,0,0,skirtHeight) );
+
+                osg::Vec3 skirtVector( surfaceAttribs.x(), surfaceAttribs.y(), surfaceAttribs.z() );
+
                 const osg::Vec3f& surfaceVert = (*d.surfaceVerts)[orig_i];
-                skirtVerts->push_back( surfaceVert - ((*skirtVectors)[orig_i])*skirtHeight );
+                skirtVerts->push_back( surfaceVert - skirtVector*skirtHeight );
 
                 const osg::Vec3f& surfaceNormal = (*d.normals)[orig_i];
                 skirtNormals->push_back( surfaceNormal );
 
-                const osg::Vec4f& surfaceAttribs = (*d.surfaceAttribs)[orig_i];
-                skirtAttribs->push_back( surfaceAttribs - osg::Vec4f(0,0,0,skirtHeight) );
-
+            
                 const osg::Vec4f& surfaceAttribs2 = (*d.surfaceAttribs2)[orig_i];
                 skirtAttribs2->push_back( surfaceAttribs2 - osg::Vec4f(0,0,0,skirtHeight) );
 
@@ -1147,32 +1243,46 @@ namespace
                     }
                 }
 
-                elements->addElement(orig_i);
-                elements->addElement(skirtVerts->size()-1);
+                const osg::Vec2& tilec = (*d.renderTileCoords.get())[orig_i];
+                d.renderTileCoords->push_back( tilec );
+
+                skirtIndex++;
+                if (skirtIndex > 1)
+                {
+                    int prev_i = d.indices[(r-1)*d.numCols+(d.numCols-1)];
+                    elements->addElement(prev_i);
+                    elements->addElement(skirtVerts->size() - 2);
+                    elements->addElement(orig_i);
+
+                    elements->addElement(skirtVerts->size() - 1);
+                    elements->addElement(orig_i);
+                    elements->addElement(skirtVerts->size() - 2);
+                }
             }
         }
 
         // top:
-        for( int c=d.numCols-1; c>0; --c )
+        skirtIndex = 0;
+        for( int c=d.numCols-1; c >= 0; --c )
         {
             int orig_i = d.indices[(d.numRows-1)*d.numCols+c];
             if (orig_i < 0)
             {
-                if ( elements->size() > 0 )
-                    d.surface->addPrimitiveSet( elements.get() );
-                elements = new osg::DrawElementsUShort(GL_TRIANGLE_STRIP);
+                skirtIndex = 0;
             }
             else
             {
+                const osg::Vec4f& surfaceAttribs = (*d.surfaceAttribs)[orig_i];
+                skirtAttribs->push_back( surfaceAttribs - osg::Vec4f(0,0,0,skirtHeight) );
+
+                osg::Vec3 skirtVector( surfaceAttribs.x(), surfaceAttribs.y(), surfaceAttribs.z() );
+
                 const osg::Vec3f& surfaceVert = (*d.surfaceVerts)[orig_i];
-                skirtVerts->push_back( surfaceVert - ((*skirtVectors)[orig_i])*skirtHeight );
+                skirtVerts->push_back( surfaceVert - skirtVector*skirtHeight );
 
                 const osg::Vec3f& surfaceNormal = (*d.normals)[orig_i];
                 skirtNormals->push_back( surfaceNormal );
 
-                const osg::Vec4f& surfaceAttribs = (*d.surfaceAttribs)[orig_i];
-                skirtAttribs->push_back( surfaceAttribs - osg::Vec4f(0,0,0,skirtHeight) );
-
                 const osg::Vec4f& surfaceAttribs2 = (*d.surfaceAttribs2)[orig_i];
                 skirtAttribs2->push_back( surfaceAttribs2 - osg::Vec4f(0,0,0,skirtHeight) );
 
@@ -1188,32 +1298,46 @@ namespace
                     }
                 }
 
-                elements->addElement(orig_i);
-                elements->addElement(skirtVerts->size()-1);
+                const osg::Vec2& tilec = (*d.renderTileCoords.get())[orig_i];
+                d.renderTileCoords->push_back( tilec );
+
+                skirtIndex++;
+                if (skirtIndex > 1)
+                {
+                    int prev_i = d.indices[(d.numRows - 1)*d.numCols + c + 1];
+                    elements->addElement(prev_i);
+                    elements->addElement(skirtVerts->size() - 2);
+                    elements->addElement(orig_i);
+
+                    elements->addElement(skirtVerts->size() - 1);
+                    elements->addElement(orig_i);
+                    elements->addElement(skirtVerts->size() - 2);
+                }
             }
         }
 
         // left:
+        skirtIndex = 0;
         for( int r=d.numRows-1; r>=0; --r )
         {
             int orig_i = d.indices[r*d.numCols];
             if (orig_i < 0)
             {
-                if ( elements->size() > 0 )
-                    d.surface->addPrimitiveSet( elements.get() );
-                elements = new osg::DrawElementsUShort(GL_TRIANGLE_STRIP);
+                skirtIndex = 0;
             }
             else
             {
+                const osg::Vec4f& surfaceAttribs = (*d.surfaceAttribs)[orig_i];
+                skirtAttribs->push_back( surfaceAttribs - osg::Vec4f(0,0,0,skirtHeight) );
+
+                 osg::Vec3 skirtVector( surfaceAttribs.x(), surfaceAttribs.y(), surfaceAttribs.z() );
+
                 const osg::Vec3f& surfaceVert = (*d.surfaceVerts)[orig_i];
-                skirtVerts->push_back( surfaceVert - ((*skirtVectors)[orig_i])*skirtHeight );
+                skirtVerts->push_back( surfaceVert - skirtVector*skirtHeight );
 
                 const osg::Vec3f& surfaceNormal = (*d.normals)[orig_i];
                 skirtNormals->push_back( surfaceNormal );
 
-                const osg::Vec4f& surfaceAttribs = (*d.surfaceAttribs)[orig_i];
-                skirtAttribs->push_back( surfaceAttribs - osg::Vec4f(0,0,0,skirtHeight) );
-
                 const osg::Vec4f& surfaceAttribs2 = (*d.surfaceAttribs2)[orig_i];
                 skirtAttribs2->push_back( surfaceAttribs2 - osg::Vec4f(0,0,0,skirtHeight) );
 
@@ -1229,15 +1353,22 @@ namespace
                     }
                 }
 
-                elements->addElement(orig_i);
-                elements->addElement(skirtVerts->size()-1);
-            }
-        }
+                const osg::Vec2& tilec = (*d.renderTileCoords.get())[orig_i];
+                d.renderTileCoords->push_back( tilec );
 
-        // add the final prim set.
-        if ( elements->size() > 0 )
-        {
-            d.surface->addPrimitiveSet( elements.get() );
+                skirtIndex++;
+                if (skirtIndex > 1)
+                {
+                    int prev_i = d.indices[(r + 1)*d.numCols];
+                    elements->addElement(prev_i);
+                    elements->addElement(skirtVerts->size() - 2);
+                    elements->addElement(orig_i);
+
+                    elements->addElement(skirtVerts->size() - 1);
+                    elements->addElement(orig_i);
+                    elements->addElement(skirtVerts->size() - 2);
+                }
+            }
         }
     }
 
@@ -1251,12 +1382,14 @@ namespace
     {    
         bool swapOrientation = !(d.model->_tileLocator->orientationOpenGL());
 
-        bool recalcNormals   = d.model->hasElevation();
+        bool recalcNormals   =
+            d.model->hasElevation() && 
+            !d.model->hasNormalMap();
+
         unsigned numSurfaceNormals = d.numRows * d.numCols;
 
-        osg::DrawElements* elements = new osg::DrawElementsUShort(GL_TRIANGLES);
+        osg::DrawElements* elements = d.newDrawElements(GL_TRIANGLES);
         elements->reserveElements((d.numRows-1) * (d.numCols-1) * 6);
-        d.surface->insertPrimitiveSet(0, elements); // because we always want this first.
 
         if ( recalcNormals )
         {
@@ -1422,7 +1555,7 @@ namespace
 
                         osg::Vec3d model;
                         d.model->_tileLocator->unitToModel( ndc, model );
-                        osg::Vec3d v = model - d.centerModel;
+                        osg::Vec3d v = model * d.world2local; //model - d.centerModel;
                         boundaryVerts.push_back( v );
                         boundaryElevations.push_back( heightValue );
                     }
@@ -1510,7 +1643,7 @@ namespace
 
                         osg::Vec3d model;
                         d.model->_tileLocator->unitToModel( ndc, model );
-                        osg::Vec3d v = model - d.centerModel;
+                        osg::Vec3d v = model * d.world2local; //model - d.centerModel;
                         boundaryVerts.push_back( v );
                         boundaryElevations.push_back( heightValue );
                     }
@@ -1595,7 +1728,7 @@ namespace
 
                         osg::Vec3d model;
                         d.model->_tileLocator->unitToModel( ndc, model );
-                        osg::Vec3d v = model - d.centerModel;
+                        osg::Vec3d v = model * d.world2local; //model - d.centerModel;
                         boundaryVerts.push_back( v );
                         boundaryElevations.push_back( heightValue );
                     }
@@ -1681,7 +1814,7 @@ namespace
 
                         osg::Vec3d model;
                         d.model->_tileLocator->unitToModel( ndc, model );
-                        osg::Vec3d v = model - d.centerModel;
+                        osg::Vec3d v = model * d.world2local; //model - d.centerModel;
                         boundaryVerts.push_back( v );
                         boundaryElevations.push_back( heightValue ); 
                     }
@@ -1755,6 +1888,12 @@ namespace
                 nitr->normalize();
             }       
         }
+
+        // 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.
+        }
     }
 
 
@@ -1770,10 +1909,6 @@ namespace
         
         if ( d.renderTileCoords.valid() )
             d.surface->_tileCoords = d.renderTileCoords;
-            
-        // TODO: evaluate this suspicious code. -gw
-        if ( d.stitchTileCoords.valid() )
-            d.surface->_tileCoords = d.stitchTileCoords.get();
 
         // install the render data for each layer:
         for( RenderLayerVector::const_iterator r = d.renderLayers.begin(); r != d.renderLayers.end(); ++r )
@@ -1791,7 +1926,29 @@ namespace
             layer._opaque =
                 (r->_layer.getMapLayer()->getColorFilters().size() == 0 ) &&
                 (layer._tex.valid() && !r->_layer.hasAlpha()) &&
-                (!layer._texParent.valid() || !r->_layerParent.hasAlpha());
+                (!layer._texParent.valid() || !r->_layerParent.hasAlpha()) &&
+                (layer._imageLayer.valid() && layer._imageLayer->getMinVisibleRange() == 0.0f) &&
+                (layer._imageLayer.valid() && layer._imageLayer->getMaxVisibleRange() == FLT_MAX);
+
+            // texture matrix: scale/bias matrix of the texture. Currently we don't use
+            // this for rendering because the scale/bias is already baked into the 
+            // texture coordinates. BUT we still need it for sampling shared rasters etc.
+            if ( r->_layer._locator.valid() )
+            {
+                osg::Matrixd sbmatrix;
+
+                r->_layer._locator->createScaleBiasMatrix(
+                    d.model->_tileLocator->getDataExtent(),
+                    sbmatrix );
+
+                layer._texMat = sbmatrix;
+
+                // a shared layer needs access to a static uniform name.
+                if ( layer._imageLayer->isShared() )
+                {
+                    layer._texMatUniformID = osg::Uniform::getNameID( layer._imageLayer->shareTexMatUniformName().get() );
+                }
+            }
 
             // parent texture matrix: it's a scale/bias matrix encoding the difference
             // between the two locators.
@@ -1806,8 +1963,14 @@ namespace
                 layer._texMatParent = sbmatrix;
             }
 
-            // the surface:
-            layer._texCoords  = r->_texCoords;
+            // the texture coords:
+            layer._texCoords  = r->_texCoords.get();
+            if ( r->_texCoords.valid() )
+            {
+                int index = d.surface->getTexCoordArrayList().size();
+                d.surface->setTexCoordArray( index, r->_texCoords.get() );
+            }
+
             d.surface->_layers[order] = layer;
 
             // the mask geometries:
@@ -1815,155 +1978,84 @@ namespace
             {
                 layer._texCoords = r->_stitchTexCoords.get();
                 mr->_geom->_layers[order] = layer;
+                mr->_geom->_tileCoords = d.stitchTileCoords.get();
             }
         }
-    }
-
-
-    void allocateVBOs( Data& d )
-    {
-        d.surface->setUseVertexBufferObjects(false);
-        d.surface->setUseVertexBufferObjects(true);
-
-        if ( d.stitchGeom )
-        {
-            d.stitchGeom->setUseVertexBufferObjects(false);
-            d.stitchGeom->setUseVertexBufferObjects(true);
-        }
-    }
-
-
-    // Optimize the data. Convert all modes to GL_TRIANGLES and run the
-    // critical vertex cache optimizations.
-    void optimize( Data& d, bool runMeshOptimizers )
-    { 
-        // For vertex cache optimization to work, all the arrays must be in
-        // the geometry. MP doesn't store texture/tile coords in the geometry
-        // so we need to temporarily add them.
-        //
-        // Note: the MeshOptimizer will duplicate shared arrays if it finds any.
-        // We don't want that. And since we already have pointers to our texture
-        // arrays elsewhere, it mistakingly thinks there are shared (sine they
-        // have refcount>1). So we need to UNREF them, optimize, and then RE-REF
-        // them afterwards. :/ -gw
-
-#if OSG_MIN_VERSION_REQUIRED(3, 1, 8) // after osg::Geometry API changes
-
-        osg::Geometry::ArrayList* surface_tdl = &d.surface->getTexCoordArrayList();
-        osg::Geometry::ArrayList* stitch_tdl  = d.stitchGeom ? &d.stitchGeom->getTexCoordArrayList() : 0L;
-        int u=0;
-        for( RenderLayerVector::const_iterator r = d.renderLayers.begin(); r != d.renderLayers.end(); ++r )
-        {
-            if ( r->_texCoords.valid() && r->_ownsTexCoords )
-            {
-                r->_texCoords->setBinding( osg::Array::BIND_PER_VERTEX );
-                surface_tdl->push_back( r->_texCoords.get() );
-                r->_texCoords->unref_nodelete();
-            }
-            if ( stitch_tdl && r->_stitchTexCoords.valid() )
-            {
-                r->_stitchTexCoords->setBinding( osg::Array::BIND_PER_VERTEX );
-                stitch_tdl->push_back( r->_stitchTexCoords.get() );
-                r->_stitchTexCoords->unref_nodelete();
-            }
-        }
-        if ( d.renderTileCoords.valid() && d.ownsTileCoords )
-        {
-            d.renderTileCoords->setBinding( osg::Array::BIND_PER_VERTEX );
-            surface_tdl->push_back( d.renderTileCoords.get() );
-            d.renderTileCoords->unref_nodelete();
-        }
-        if ( stitch_tdl && d.stitchTileCoords.valid() && d.ownsTileCoords )
-        {
-            d.stitchTileCoords->setBinding( osg::Array::BIND_PER_VERTEX );
-            stitch_tdl->push_back( d.stitchTileCoords.get() );
-            d.stitchTileCoords->unref_nodelete();
-        }
-
-#else // OSG version < 3.1.8 (before osg::Geometry API changes)
 
-        osg::Geometry::ArrayDataList* surface_tdl = &d.surface->getTexCoordArrayList();
-        osg::Geometry::ArrayDataList* stitch_tdl = d.stitchGeom ? &d.stitchGeom->getTexCoordArrayList() : 0L;
-        int u=0;
-        for( RenderLayerVector::const_iterator r = d.renderLayers.begin(); r != d.renderLayers.end(); ++r )
+        // install the tile coordinates in the geometry.
+        if ( d.surface->_tileCoords.valid() )
         {
-            if ( r->_ownsTexCoords && r->_texCoords.valid() )
-            {
-                surface_tdl->push_back( osg::Geometry::ArrayData(r->_texCoords.get(), osg::Geometry::BIND_PER_VERTEX) );
-                r->_texCoords->unref_nodelete();
-            }
-            if ( stitch_tdl && r->_stitchTexCoords.valid() )
-            {
-                stitch_tdl->push_back( osg::Geometry::ArrayData(r->_stitchTexCoords.get(), osg::Geometry::BIND_PER_VERTEX) );
-                r->_stitchTexCoords->unref_nodelete();
-            }
+            int index = d.surface->getTexCoordArrayList().size();
+            d.surface->setTexCoordArray( index, d.surface->_tileCoords.get() );
         }
-        if ( d.renderTileCoords.valid() && d.ownsTileCoords )
-        {
-            surface_tdl->push_back( osg::Geometry::ArrayData(d.renderTileCoords.get(), osg::Geometry::BIND_PER_VERTEX) );
-            d.renderTileCoords->unref_nodelete();
-        }
-        if ( stitch_tdl && d.stitchTileCoords.valid() && d.ownsTileCoords )
-        {
-            stitch_tdl->push_back( osg::Geometry::ArrayData(d.stitchTileCoords.get(), osg::Geometry::BIND_PER_VERTEX) );
-            d.stitchTileCoords->unref_nodelete();
-        }
-
-#endif
 
-		if (runMeshOptimizers && d.maskRecords.size() < 1)
-		{
-			osgUtil::Optimizer o;
-            
-			o.optimize( d.surfaceGeode,
-				osgUtil::Optimizer::VERTEX_PRETRANSFORM |
-				osgUtil::Optimizer::INDEX_MESH |
-				osgUtil::Optimizer::VERTEX_POSTTRANSFORM );
-		}
+        // elevation texture.
+        d.surface->_elevTex = d.model->_elevationTexture.get();
+    }
 
-        // do this while all the objects are in the geometry.
-        allocateVBOs( d );
+    osg::Geode* makeBBox(const Data& d)
+    {        
+        osg::Geode* geode = new osg::Geode();
+        std::string sizeStr = "(empty)";
+        float zpos = 0.0f;
 
-        // re-ref all the things we un-ref'd earlier.
-        for( RenderLayerVector::const_iterator r = d.renderLayers.begin(); r != d.renderLayers.end(); ++r )
-        {
-            if ( r->_texCoords.valid() && r->_ownsTexCoords )
-            {
-                r->_texCoords->ref();
-            }
-            if ( stitch_tdl && r->_stitchTexCoords.valid() )
-            {
-                r->_stitchTexCoords->ref();
-            }
-        }
-        if ( d.renderTileCoords.valid() && d.ownsTileCoords )
-        {
-            d.renderTileCoords->ref();
-        }
-        if ( stitch_tdl && d.stitchTileCoords.valid() && d.ownsTileCoords )
+        osg::ComputeBoundsVisitor cbv;
+        d.surfaceGeode->accept( cbv );
+        const osg::BoundingBox& bbox = cbv.getBoundingBox();
+        if ( bbox.valid() )
         {
-            d.stitchTileCoords->ref();
+            osg::Geometry* geom = new osg::Geometry();
+            geom->setName("bbox");
+        
+            osg::Vec3Array* v = new osg::Vec3Array();
+            for(int i=0; i<8; ++i)
+                v->push_back(bbox.corner(i));
+            geom->setVertexArray(v);
+
+            osg::DrawElementsUByte* de = new osg::DrawElementsUByte(GL_LINES);
+            de->push_back(0); de->push_back(1);
+            de->push_back(1); de->push_back(3);
+            de->push_back(3); de->push_back(2);
+            de->push_back(2); de->push_back(0);
+            de->push_back(4); de->push_back(5);
+            de->push_back(5); de->push_back(7);
+            de->push_back(7); de->push_back(6);
+            de->push_back(6); de->push_back(4);
+            de->push_back(0); de->push_back(4);
+            de->push_back(1); de->push_back(5);
+            de->push_back(3); de->push_back(7);
+            de->push_back(2); de->push_back(6);
+            geom->addPrimitiveSet(de);
+
+            osg::Vec4Array* c= new osg::Vec4Array();
+            c->push_back(osg::Vec4(0,1,1,1));
+            geom->setColorArray(c);
+            geom->setColorBinding(geom->BIND_OVERALL);
+
+            geode->addDrawable(geom);
+
+            sizeStr = Stringify() << bbox.xMax()-bbox.xMin();
+            sizeStr = Stringify() << "min="<<bbox.zMin()<<"\nmax="<<bbox.zMax();
+            zpos = bbox.zMax();
         }
 
-        // clear the data out of the actual geometry now that we're done optimizing.
-        surface_tdl->clear();
-
-        if (stitch_tdl)
-            stitch_tdl->clear();
+        osgText::Text* t = new osgText::Text();
+        t->setText( Stringify() << d.model->_tileKey.str() << "\n" << sizeStr );
+        t->setFont( osgEarth::Registry::instance()->getDefaultFont() );
+        t->setCharacterSizeMode(t->SCREEN_COORDS);
+        t->setCharacterSize(36.0f);
+        t->setAlignment(t->CENTER_CENTER);
+        t->setColor(osg::Vec4(1,1,1,1));
+        t->setBackdropColor(osg::Vec4(0,0,0,1));
+        t->setBackdropType(t->OUTLINE);
+        t->setPosition(osg::Vec3(0,0,zpos));
+        geode->addDrawable(t);
+
+        geode->getOrCreateStateSet()->setAttributeAndModes(new osg::Program(),0);
+        geode->getOrCreateStateSet()->setMode(GL_LIGHTING,0);
+
+        return geode;
     }
-
-
-
-    struct CullByTraversalMask : public osg::Drawable::CullCallback
-    {
-        CullByTraversalMask( unsigned mask ) : _mask(mask) { }
-        unsigned _mask;
-
-        bool cull(osg::NodeVisitor* nv, osg::Drawable* drawable, osg::RenderInfo* renderInfo) const 
-        {
-            return ((unsigned)nv->getTraversalMask() & ((unsigned)nv->getNodeMaskOverride() | _mask)) == 0;
-        }
-    };
 }
 
 //------------------------------------------------------------------------
@@ -1979,38 +2071,43 @@ _optimizeTriOrientation( optimizeTriOrientation ),
 _options               ( options ),
 _textureImageUnit      ( texImageUnit )
 {
-    _cullByTraversalMask = new CullByTraversalMask(*options.secondaryTraversalMask());
+    _debug =
+        _options.debug() == true || 
+        ::getenv("OSGEARTH_MP_DEBUG") != 0L;
 }
 
 
 TileNode*
-TileModelCompiler::compile(const TileModel* model,
-                           const MapFrame&  frame)
+TileModelCompiler::compile(TileModel*        model,
+                           const MapFrame&   frame,
+                           ProgressCallback* progress)
 {
-    TileNode* tile = new TileNode( model->_tileKey, model );
 
     // Working data for the build.
     Data d(model, frame, _maskLayers, _modelLayers);
+    d.textureImageUnit = _textureImageUnit;
+
+    GeoPoint centroid;
+    model->_tileKey.getExtent().getCentroid(centroid);
+    centroid.toWorld(d.centerModel);
+    centroid.createLocalToWorld(d.local2world);
+    d.world2local.invert(d.local2world);
+
+    TileNode* tile = new TileNode( model->_tileKey, model, d.local2world );
 
+    d.installParentData = model->useParentData();
     d.parentModel = model->getParentTileModel();
     d.heightScale = *_options.verticalScale();
     d.heightOffset = *_options.verticalOffset();
 
-    // build the localization matrix for this tile:
-    model->_tileLocator->unitToModel( osg::Vec3(0.5f, 0.5f, 0.0), d.centerModel );
-    tile->setMatrix( osg::Matrix::translate(d.centerModel) );
-
     // A Geode/Geometry for the surface:
     d.surface = new MPGeometry( d.model->_tileKey, d.frame, _textureImageUnit );
+    d.surface->setName( "surface" );
     d.surfaceGeode = new osg::Geode();
-    d.surfaceGeode->addDrawable( d.surface );
-    d.surfaceGeode->setNodeMask( *_options.primaryTraversalMask() );
-
+   
     tile->addChild( d.surfaceGeode );
 
-    // A Geode/Geometry for the skirt. This is good for traversal masking (e.g. shadows)
-    // but bad since we're not combining the entire tile into a single geometry.
-    // TODO: make this optional?
+    // Create the skirt if the heightfield skirt ratio is > 0.0
     d.createSkirt = (_options.heightFieldSkirtRatio().value() > 0.0);
 
     // adjust the tile locator for geocentric mode:
@@ -2023,35 +2120,39 @@ TileModelCompiler::compile(const TileModel* model,
         setupMaskRecords( d );
 
     // allocate all the vertex, normal, and color arrays.
-    double sampleRatio = *_options.heightFieldSampleRatio();
-    if ( sampleRatio <= 0.0f )
-        sampleRatio = osg::clampBetween( model->_tileKey.getLOD()/20.0, 0.0625, 1.0 );
-
-    setupGeometryAttributes( d, sampleRatio );
+    setupGeometryAttributes( d, _options.tileSize().get() );
 
     // set up the list of layers to render and their shared arrays.
     setupTextureAttributes( d, _cache );
 
     // calculate the vertex and normals for the surface geometry.
-    createSurfaceGeometry( d );
+    createSurfaceGeometry( d, _debug );
 
     // build geometry for the masked areas, if applicable
     if ( d.maskRecords.size() > 0 )
         createMaskGeometry( d );
 
-    // build the skirts.
-    if ( d.createSkirt )
-        createSkirtGeometry( d, *_options.heightFieldSkirtRatio() );
+    
+
+    // at this point, make sure we actually built any surface geometry.
+    if (d.surface->getVertexArray() &&
+        d.surface->getVertexArray()->getNumElements() > 0 )
+    {
+        d.surfaceGeode->addDrawable( d.surface );
+
+        // tesselate the surface verts into triangles.
+        tessellateSurfaceGeometry( d, _optimizeTriOrientation, *_options.normalizeEdges() );
 
-    // tesselate the surface verts into triangles.
-    tessellateSurfaceGeometry( d, _optimizeTriOrientation, *_options.normalizeEdges() );
+        // build the skirts.
+        if ( d.createSkirt )
+            createSkirtGeometry( d, *_options.heightFieldSkirtRatio() );
 
-    // performance optimizations.
-    optimize( d, _options.optimizeTiles() == true );
+    }
 
     // installs the per-layer rendering data into the Geometry objects.
     installRenderData( d );
-    
+
+    // install a KdTree index if necessary
     if (osgDB::Registry::instance()->getBuildKdTreesHint()==osgDB::ReaderWriter::Options::BUILD_KDTREES &&
         osgDB::Registry::instance()->getKdTreeBuilder())
     {            
@@ -2067,11 +2168,20 @@ TileModelCompiler::compile(const TileModel* model,
     SetDataVarianceVisitor sdv( osg::Object::DYNAMIC );
     tile->accept( sdv );
 
-#if 0
-    //test: run the geometry validator to make sure geometry it legal
-    osgEarth::GeometryValidator validator;
-    tile->accept(validator);
-#endif
+    osg::ComputeBoundsVisitor cbv;
+    d.surfaceGeode->accept(cbv);
+    tile->setTerrainBoundingBox( cbv.getBoundingBox() );
+
+    // debugging tools.
+    if (_debug)
+    {
+        //test: run the geometry validator to make sure geometry it legal
+        osgEarth::GeometryValidator validator;
+        tile->accept(validator);
+
+        //test: show the tile bounding boxes
+        tile->addChild( makeBBox(d) );
+    }
 
     return tile;
 }
diff --git a/src/osgEarthDrivers/engine_mp/TileModelFactory b/src/osgEarthDrivers/engine_mp/TileModelFactory
index 021aeca..1370e4c 100644
--- a/src/osgEarthDrivers/engine_mp/TileModelFactory
+++ b/src/osgEarthDrivers/engine_mp/TileModelFactory
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
@@ -23,157 +26,18 @@
 #include "TileNode"
 #include "TileNodeRegistry"
 #include "MPTerrainEngineOptions"
-#include <osgEarth/Map>
+#include "HeightFieldCache"
 #include <osgEarth/Progress>
-#include <osgEarth/ThreadingUtils>
-#include <osgEarth/Containers>
-#include <osgEarth/HeightFieldUtils>
-#include <osgEarth/MapFrame>
-#include <osgEarth/MapInfo>
 #include <osg/Group>
 
+namespace osgEarth {
+    class TerrainEngineRequirements;
+}
+
 namespace osgEarth { namespace Drivers { namespace MPTerrainEngine
 {
     using namespace osgEarth;
 
-    /** Key into the height field cache */
-    struct HFKey {
-        TileKey               _key;
-        Revision              _revision;
-        ElevationSamplePolicy _samplePolicy;
-
-        bool operator < (const HFKey& rhs) const {
-            if ( _key < rhs._key ) return true;
-            if ( rhs._key < _key ) return false;
-            if ( _revision < rhs._revision ) return true;
-            if ( _revision > rhs._revision ) return false;
-            return _samplePolicy < rhs._samplePolicy;
-        }
-    };
-
-    /** value in the height field cache */
-    struct HFValue {
-        osg::ref_ptr<osg::HeightField> _hf;
-        bool                           _isFallback;
-    };        
-
-    class HeightFieldCache : public osg::Referenced, public Revisioned
-    {
-    public:
-        HeightFieldCache(TileNodeRegistry* tiles, const MPTerrainEngineOptions& options) :
-          _tiles  ( tiles ),
-          _cache  ( true, 128 )
-        {
-            _firstLOD = options.firstLOD().get();
-        }
-
-        bool getOrCreateHeightField( 
-                const MapFrame&                 frame,
-                const TileKey&                  key,
-                bool                            cummulative,  // whether to start with the parent as a template
-                osg::ref_ptr<osg::HeightField>& out_hf,
-                bool&                           out_isFallback,
-                ElevationSamplePolicy           samplePolicy,
-                ElevationInterpolation          interp,
-                ProgressCallback*               progress )
-        {                
-            // default
-            out_isFallback = false;
-
-            // check the quick cache.
-            HFKey cachekey;
-            cachekey._key          = key;
-            cachekey._revision     = frame.getRevision();
-            cachekey._samplePolicy = samplePolicy;
-
-            if (progress)
-                progress->stats()["hfcache_try_count"] += 1;
-
-            bool hit = false;
-            LRUCache<HFKey,HFValue>::Record rec;
-            if ( _cache.get(cachekey, rec) )
-            {
-                out_hf = rec.value()._hf.get();
-                out_isFallback = rec.value()._isFallback;
-
-                if (progress)
-                {
-                    progress->stats()["hfcache_hit_count"] += 1;
-                    progress->stats()["hfcache_hit_rate"] = progress->stats()["hfcache_hit_count"]/progress->stats()["hfcache_try_count"];
-                }
-
-                return true;
-            }
-
-            // Find the parent tile and start with its heightfield.
-            if ( cummulative )
-            {
-                osg::ref_ptr<TileNode> parentNode;
-                TileKey parentKey = key.createParentKey();
-                if ( _tiles->get(parentKey, parentNode) )
-                {
-                    const TileModel* parentModel = parentNode->getTileModel();
-                    if ( parentModel )
-                    {
-                        osg::HeightField* parentHF = parentModel->_elevationData.getHeightField();
-                        if ( parentHF )
-                        {
-                            out_hf = HeightFieldUtils::createSubSample(
-                                parentHF,
-                                parentKey.getExtent(),
-                                key.getExtent(),
-                                interp );
-                        }
-                    }
-                }
-
-                if ( !out_hf.valid() && ((int)key.getLOD())-1 > _firstLOD )
-                {
-                    // This most likely means that a parent tile expired while we were building the child.
-                    // No harm done in that case as this tile will soo be discarded as well.
-                    OE_DEBUG << "MP HFC: Unable to find tile " << key.str() << " in the live tile registry"
-                        << std::endl;
-                    return false;
-                }
-            }
-
-            bool populated = frame.populateHeightField(
-                out_hf,
-                key,
-                true, // convertToHAE
-                samplePolicy,
-                progress );
-     
-            // Treat Plate Carre specially by scaling the height values. (There is no need
-            // to do this with an empty heightfield)
-            const MapInfo& mapInfo = frame.getMapInfo();
-            if ( mapInfo.isPlateCarre() )
-            {
-                HeightFieldUtils::scaleHeightFieldToDegrees( out_hf.get() );
-            }
-
-            // cache it.
-            HFValue cacheval;
-            cacheval._hf = out_hf.get();
-            cacheval._isFallback = !populated;
-            _cache.insert( cachekey, cacheval );
-
-            out_isFallback = !populated;
-            return true;
-        }
-
-        void clear()
-        {
-            _cache.clear();
-        }
-
-    private:
-        mutable LRUCache<HFKey,HFValue> _cache;
-        TileNodeRegistry*               _tiles;
-        int                             _firstLOD;
-    };
-
-
     /**
      * For a given TileKey, this class builds a a corresponding TileNode from
      * the map's data model.
@@ -187,9 +51,10 @@ namespace osgEarth { namespace Drivers { namespace MPTerrainEngine
     public:
         TileModelFactory(
             TileNodeRegistry*             liveTiles,
-            const MPTerrainEngineOptions& terrainOptions );
+            const MPTerrainEngineOptions& terrainOptions,
+            TerrainEngineRequirements*    terrainRequirements);
 
-        HeightFieldCache* getHeightFieldCache() const;
+        void clearCaches();
 
         /** dtor */
         virtual ~TileModelFactory() { }
@@ -203,14 +68,25 @@ namespace osgEarth { namespace Drivers { namespace MPTerrainEngine
 
     private:        
 
-        osg::ref_ptr<TileNodeRegistry>   _liveTiles;
-        const MPTerrainEngineOptions&    _terrainOptions;
-        osg::ref_ptr< HeightFieldCache > _hfCache;
+        osg::ref_ptr<TileNodeRegistry> _liveTiles;
+        const MPTerrainEngineOptions&  _terrainOptions;
+        TerrainEngineRequirements*     _terrainReqs;
+        osg::ref_ptr<HeightFieldCache> _meshHFCache;
+        osg::ref_ptr<HeightFieldCache> _normalHFCache;
+        bool                           _debug;
         
         void buildElevation(
             const TileKey&    key,
             const MapFrame&   frame,
             bool              accumulate,
+            bool              buildTexture,
+            TileModel*        model,
+            ProgressCallback* progress);
+        
+        void buildNormalMap(
+            const TileKey&    key,
+            const MapFrame&   frame,
+            bool              accumulate,
             TileModel*        model,
             ProgressCallback* progress);
     };
diff --git a/src/osgEarthDrivers/engine_mp/TileModelFactory.cpp b/src/osgEarthDrivers/engine_mp/TileModelFactory.cpp
index 16e9e2a..763c57a 100644
--- a/src/osgEarthDrivers/engine_mp/TileModelFactory.cpp
+++ b/src/osgEarthDrivers/engine_mp/TileModelFactory.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
@@ -22,6 +25,7 @@
 #include <osgEarth/ImageUtils>
 #include <osgEarth/HeightFieldUtils>
 #include <osgEarth/Progress>
+#include <osgEarth/TerrainEngineNode>
 
 using namespace osgEarth::Drivers::MPTerrainEngine;
 using namespace osgEarth;
@@ -183,6 +187,7 @@ namespace
                             colorData = TileModel::ColorData(parentColorData);
                             colorData._order = _order;
                             colorData.setIsFallbackData( true );
+                            
                             ok = true;
                         }
                     }
@@ -205,24 +210,32 @@ namespace
 //------------------------------------------------------------------------
 
 TileModelFactory::TileModelFactory(TileNodeRegistry*             liveTiles,
-                                   const MPTerrainEngineOptions& terrainOptions ) :
+                                   const MPTerrainEngineOptions& terrainOptions,
+                                   TerrainEngineRequirements*    terrainReqs) :
 _liveTiles     ( liveTiles ),
-_terrainOptions( terrainOptions )
+_terrainOptions( terrainOptions ),
+_terrainReqs   ( terrainReqs )
 {
-    _hfCache = new HeightFieldCache(liveTiles, terrainOptions);
+    _meshHFCache = new HeightFieldCache( terrainOptions );
+
+    _normalHFCache = new HeightFieldCache( terrainOptions );
+    _normalHFCache->setTileSize( 257 );
+
+    _debug = terrainOptions.debug() == true;
 }
 
-HeightFieldCache*
-TileModelFactory::getHeightFieldCache() const
+void
+TileModelFactory::clearCaches()
 {
-    return _hfCache;
+    _meshHFCache->clear();
+    _normalHFCache->clear();
 }
 
-
 void
 TileModelFactory::buildElevation(const TileKey&    key,
                                  const MapFrame&   frame,
                                  bool              accumulate,
+                                 bool              buildTexture,
                                  TileModel*        model,
                                  ProgressCallback* progress)
 {     
@@ -237,7 +250,28 @@ TileModelFactory::buildElevation(const TileKey&    key,
 
     bool isFallback = false;
 
-    if (_hfCache->getOrCreateHeightField(frame, key, accumulate, hf, isFallback, SAMPLE_FIRST_VALID, interp, progress))
+    // look up the parent's heightfield to use as a template
+    osg::ref_ptr<osg::HeightField> parentHF;
+    TileKey parentKey = key.createParentKey();
+    if ( accumulate )
+    {
+        osg::ref_ptr<TileNode> parentNode;
+        if (_liveTiles->get(parentKey, parentNode))
+        {
+            parentHF = parentNode->getTileModel()->_elevationData.getHeightField();
+            if ( _debug && key.getLOD() > 0 && !parentHF.valid() )
+            {
+                OE_NOTICE << LC << "Could not find a parent tile HF for " << key.str() << "\n";
+            }
+        }
+        else
+        {
+            // Happens if the parent key expired after this task dispatched.
+        }
+    }
+
+    // Make a new heightfield:
+    if (_meshHFCache->getOrCreateHeightField(frame, key, parentHF.get(), hf, isFallback, SAMPLE_FIRST_VALID, interp, progress))
     {
         model->_elevationData = TileModel::ElevationData(
             hf,
@@ -253,13 +287,36 @@ TileModelFactory::buildElevation(const TileKey&    key,
                 {
                     if ( x != 0 || y != 0 )
                     {
-                        TileKey nk = key.createNeighborKey(x, y);
-                        if ( nk.valid() )
+                        TileKey neighborKey = key.createNeighborKey(x, y);
+                        if ( neighborKey.valid() )
                         {
-                            osg::ref_ptr<osg::HeightField> hf;
-                            if (_hfCache->getOrCreateHeightField(frame, nk, accumulate, hf, isFallback, SAMPLE_FIRST_VALID, interp, progress) )
+                            osg::ref_ptr<osg::HeightField> neighborParentHF;
+                            if ( accumulate )
+                            {
+                                TileKey neighborParentKey = neighborKey.createParentKey();
+                                if (neighborParentKey == parentKey)
+                                {
+                                    neighborParentHF = parentHF;
+                                }
+                                else
+                                {
+                                    osg::ref_ptr<TileNode> neighborParentNode;
+                                    if (_liveTiles->get(neighborParentKey, neighborParentNode))
+                                    {
+                                        neighborParentHF = neighborParentNode->getTileModel()->_elevationData.getHeightField();
+                                    }
+                                }
+                            }
+
+                            // only pull the tile if we have a valid parent HF for it -- otherwise
+                            // you might get a flat tile when upsampling data.
+                            if ( neighborParentHF.valid() )
                             {
-                                model->_elevationData.setNeighbor( x, y, hf.get() );
+                                osg::ref_ptr<osg::HeightField> hf;
+                                if (_meshHFCache->getOrCreateHeightField(frame, neighborKey, neighborParentHF.get(), hf, isFallback, SAMPLE_FIRST_VALID, interp, progress) )
+                                {
+                                    model->_elevationData.setNeighbor( x, y, hf.get() );
+                                }
                             }
                         }
                     }
@@ -267,15 +324,99 @@ TileModelFactory::buildElevation(const TileKey&    key,
             }
 
             // parent too.
-            if ( key.getLOD() > 0 )
+            if ( parentHF.valid() )
             {
-                osg::ref_ptr<osg::HeightField> hf;
-                if ( _hfCache->getOrCreateHeightField(frame, key.createParentKey(), accumulate, hf, isFallback, SAMPLE_FIRST_VALID, interp, progress) )
-                {
-                    model->_elevationData.setParent( hf.get() );
-                }
+                model->_elevationData.setParent( parentHF.get() );
             }
         }
+
+        if ( buildTexture )
+        {
+            model->generateElevationTexture();
+        }
+    }
+}
+
+#define EMPTY_NORMAL_MAP_SIZE 3
+
+void
+TileModelFactory::buildNormalMap(const TileKey&    key,
+                                 const MapFrame&   frame,
+                                 bool              accumulate,
+                                 TileModel*        model,
+                                 ProgressCallback* progress)
+{   
+    const MapInfo& mapInfo = frame.getMapInfo();
+
+    const osgEarth::ElevationInterpolation& interp =
+        frame.getMapOptions().elevationInterpolation().get();
+
+    // Request a heightfield from the map, falling back on lower resolution tiles
+    // if necessary (fallback=true)
+    osg::ref_ptr<osg::HeightField> hf;
+    osg::ref_ptr<osg::HeightField> parentHF;
+    osg::ref_ptr<const TileModel>  parentModel;
+
+    bool isFallback = false;
+
+    unsigned minNormalLOD =
+        _terrainOptions.minNormalMapLOD().isSet() ?
+        _terrainOptions.minNormalMapLOD().get() : 0u;
+
+    if ( key.getLOD() >= minNormalLOD )
+    {
+        // look up the parent's heightfield to use as a template
+    
+        TileKey parentKey = key.createParentKey();
+        if ( accumulate )
+        {
+            osg::ref_ptr<TileNode> parentNode;
+            if (_liveTiles->get(parentKey, parentNode))
+            {
+                parentModel = parentNode->getTileModel();
+                parentHF = parentModel->_normalData.getHeightField();
+                if ( parentHF->getNumColumns() == EMPTY_NORMAL_MAP_SIZE )
+                    parentHF = 0L;
+            }
+        }
+
+        // Make a new heightfield:
+        if (_normalHFCache->getOrCreateHeightField(frame, key, parentHF.get(), hf, isFallback, SAMPLE_FIRST_VALID, interp, progress))
+        {
+            if ( isFallback && parentModel.valid() )
+            {
+                model->_normalData = parentModel->_normalData;
+                model->_normalData._fallbackData = true;
+            }
+            else
+            {
+                model->_normalData = TileModel::NormalData(
+                    hf,
+                    GeoLocator::createForKey( key, mapInfo ),
+                    isFallback );
+            }
+        }
+    }
+
+    else
+    {
+        // 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 );
+
+        model->_normalData = TileModel::NormalData(
+            hf,
+            GeoLocator::createForKey( key, mapInfo ),
+            false );
+    }
+
+    if ( isFallback && parentModel.valid() )
+    {
+        model->_normalTexture = parentModel->_normalTexture.get();
+    }
+    else
+    {
+        model->generateNormalTexture();
     }
 }
 
@@ -290,6 +431,8 @@ TileModelFactory::createTileModel(const TileKey&           key,
 
     osg::ref_ptr<TileModel> model = new TileModel( frame.getRevision(), frame.getMapInfo() );
 
+    model->_useParentData = _terrainReqs->parentTexturesRequired();
+
     model->_tileKey = key;
     model->_tileLocator = GeoLocator::createForKey(key, frame.getMapInfo());
 
@@ -320,14 +463,20 @@ TileModelFactory::createTileModel(const TileKey&           key,
         progress->stats()["fetch_imagery_time"] += OE_STOP_TIMER(fetch_imagery);
 
     
-    OE_START_TIMER(fetch_elevation);
-
     // make an elevation layer.
-    buildElevation(key, frame, accumulate, model.get(), progress);
-
+    OE_START_TIMER(fetch_elevation);
+    buildElevation(key, frame, accumulate, _terrainReqs->elevationTexturesRequired(), model.get(), progress);
     if (progress)
         progress->stats()["fetch_elevation_time"] += OE_STOP_TIMER(fetch_elevation);
-
+    
+    // make a normal map layer (if necessary)
+    if ( _terrainReqs->normalTexturesRequired() )
+    {
+        OE_START_TIMER(fetch_normalmap);
+        buildNormalMap(key, frame, accumulate, model.get(), progress);
+        if (progress)
+            progress->stats()["fetch_normalmap_time"] += OE_STOP_TIMER(fetch_normalmap);
+    }
 
     // If nothing was added, not even a fallback heightfield, something went
     // horribly wrong. Leave without a tile model. Chances are that a parent tile
@@ -351,7 +500,9 @@ TileModelFactory::createTileModel(const TileKey&           key,
     // look up the parent model and cache it.
     osg::ref_ptr<TileNode> parentTile;
     if ( _liveTiles->get(key.createParentKey(), parentTile) )
+    {
         model->_parentModel = parentTile->getTileModel();
+    }
 
     out_model = model.release();
 }
diff --git a/src/osgEarthDrivers/engine_mp/TileNode b/src/osgEarthDrivers/engine_mp/TileNode
index 83bdf6c..032bfb5 100644
--- a/src/osgEarthDrivers/engine_mp/TileNode
+++ b/src/osgEarthDrivers/engine_mp/TileNode
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
@@ -21,39 +24,45 @@
 
 #include "Common"
 #include "TileModel"
-#include <osg/MatrixTransform>
+#include <osgEarth/TerrainTileNode>
 
 namespace osgEarth { namespace Drivers { namespace MPTerrainEngine
 {
-    using namespace osgEarth;
-    class TileNode;
-
-    /**
-     * Marker that gives access to a TileNode.
-     */
-    class TileNodeContainer
-    {
-    public:
-        virtual TileNode* getTileNode() = 0;
-    };
-
     /**
      * Node that represents a single terrain tile that was compiled from
      * a TileModel (and corresponds to one TileKey). The matrixtransform
      * localizes the TileNode within the terrain.
      */
-    class TileNode : public osg::MatrixTransform, public TileNodeContainer
+    class TileNode : public osg::MatrixTransform,
+                     public osgEarth::TerrainTileNode
     {
+    public: // osgEarth::TerrainTileNode
+        
+        const TileKey& getKey() const { return _key; }
+
+        osg::Texture* getElevationTexture() const;
+
+        osg::RefMatrixf* getElevationTextureMatrix() const;
+
+        osg::Texture* getNormalTexture() const;
+
+        osg::RefMatrixf* getNormalTextureMatrix() const;
+
+        osg::Group* getPayloadGroup() const;
+
+        osg::Group* getOrCreatePayloadGroup();
+
+
     public:
         /**
          * Constructs a new tile node
          */
-        TileNode( const TileKey& key, const TileModel* model );
+        TileNode(const TileKey& key, TileModel* model, const osg::Matrixd& matrix);
 
         /**
-         * The tilekey associated with this tile
+         * Assigned the UID of the engine that created this tile.
          */
-        const TileKey& getKey() const { return _key; }
+        void setEngineUID(UID uid) { _engineUID = uid; }
 
         /**
          * True if this is a valid tile node.
@@ -90,6 +99,18 @@ namespace osgEarth { namespace Drivers { namespace MPTerrainEngine
          */
         bool isOutOfDate() const { return _outOfDate; }
 
+        /**
+         * The tile-aligned bounding box of the terrain geometry.
+         */
+        void setTerrainBoundingBox(const osg::BoundingBox& bbox) { _terrainBBox = bbox; }
+        const osg::BoundingBox& getTerrainBoundingBox() const { return _terrainBBox; }
+
+    public:
+
+        // called by the TileNodeRegistry when a tilenode that this tile was waiting
+        // on has arrived.
+        void notifyOfArrival(TileNode* waitee);
+
 
     public: // TileNodeContainer
 
@@ -108,11 +129,16 @@ namespace osgEarth { namespace Drivers { namespace MPTerrainEngine
         virtual ~TileNode() { }
 
         TileKey                            _key;
-        osg::ref_ptr<const TileModel>      _model;
+        UID                                _engineUID;
+        osg::ref_ptr<TileModel>            _model;
         unsigned                           _lastTraversalFrame;
         Revision                           _maprevision;
         bool                               _outOfDate;
         bool                               _dirty;
+        osg::ref_ptr<osg::RefMatrixf>      _elevTexMat;
+        osg::ref_ptr<osg::RefMatrixf>      _normalTexMat;
+        osg::BoundingBox                   _terrainBBox;
+        osg::ref_ptr<osg::Group>           _payload;
     };
 
 
@@ -126,7 +152,7 @@ namespace osgEarth { namespace Drivers { namespace MPTerrainEngine
     class InvalidTileNode : public TileNode
     {
     public:
-        InvalidTileNode(const TileKey& key) : TileNode(key, 0L) { }
+        InvalidTileNode(const TileKey& key) : TileNode(key, 0L, osg::Matrix()) { }
         bool isValid() const { return false; }
     protected:
         virtual ~InvalidTileNode() { }
diff --git a/src/osgEarthDrivers/engine_mp/TileNode.cpp b/src/osgEarthDrivers/engine_mp/TileNode.cpp
index 7ca454f..b167654 100644
--- a/src/osgEarthDrivers/engine_mp/TileNode.cpp
+++ b/src/osgEarthDrivers/engine_mp/TileNode.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
@@ -22,6 +25,13 @@
 #include <osg/NodeCallback>
 #include <osg/NodeVisitor>
 #include <osg/Uniform>
+#include <osgEarth/VirtualProgram>
+#include <osgEarth/ShaderGenerator>
+#include <osgEarth/DrawInstanced>
+#include <osgEarth/Registry>
+#include <osgEarth/CullingUtils>
+#include <osgEarth/ImageUtils>
+#include <osgUtil/Optimizer>
 
 using namespace osgEarth::Drivers::MPTerrainEngine;
 using namespace osgEarth;
@@ -30,9 +40,7 @@ using namespace OpenThreads;
 #define LC "[TileNode] "
 
 
-//----------------------------------------------------------------------------
-
-TileNode::TileNode( const TileKey& key, const TileModel* model ) :
+TileNode::TileNode(const TileKey& key, TileModel* model, const osg::Matrixd& matrix) :
 _key               ( key ),
 _model             ( model ),
 _lastTraversalFrame( 0 ),
@@ -40,6 +48,7 @@ _dirty             ( false ),
 _outOfDate         ( false )
 {
     this->setName( key.str() );
+    this->setMatrix( matrix );
 
     // revisions are initially in sync:
     if ( model )
@@ -49,9 +58,80 @@ _outOfDate         ( false )
         {
             this->setNumChildrenRequiringUpdateTraversal(1);
         }
+        
+        if (model->_elevationTexture.valid() && model->_elevationData.getLocator())
+        {
+            osg::Matrixd elevMatrix;
+
+            model->_elevationData.getLocator()->createScaleBiasMatrix(
+                key.getExtent(),
+                elevMatrix );
+
+            _elevTexMat = new osg::RefMatrixf( osg::Matrixf(elevMatrix) );
+            
+            // just stick this here for now.
+            // TODO: use the proper unit binding.
+            osg::StateSet* stateSet = getOrCreateStateSet();
+
+            stateSet->setTextureAttribute(
+                2,
+                _model->_elevationTexture.get() );
+
+            stateSet->addUniform( new osg::Uniform(
+                "oe_terrain_tex_matrix",
+                osg::Matrixf(elevMatrix)) );
+        }
+
+        if (model->_normalTexture.valid() && model->_normalData.getLocator())
+        {
+            osg::Matrixf normalMatrix;
+
+            model->_normalData.getLocator()->createScaleBiasMatrix(
+                getKey().getExtent(),
+                normalMatrix );
+
+            // apply a small scale/bias that will center the sampling coords
+            // on the texels. This will prevent "seams" from forming between
+            // tiles then using a non-identity texture matrix.
+            float size = (float)_model->_normalTexture->getImage(0)->s();
+            osg::Matrixf samplingScaleBias =
+                osg::Matrixf::translate(0.5f/(size-1.0f), 0.5f/(size-1.0f), 0.0) *
+                osg::Matrixf::scale((size-1.0f)/size, (size-1.0f)/size, 1.0f);
+
+            normalMatrix.postMult( samplingScaleBias );
+
+            _normalTexMat = new osg::RefMatrixf(normalMatrix);
+        }
     }
 }
 
+osg::Texture*
+TileNode::getElevationTexture() const
+{
+    return _model.valid() ?
+        _model->_elevationTexture.get() :
+        0L;
+}
+
+osg::RefMatrixf*
+TileNode::getElevationTextureMatrix() const
+{
+    return _elevTexMat.get();
+}
+
+osg::Texture*
+TileNode::getNormalTexture() const
+{
+    return _model.valid() ?
+        _model->_normalTexture.get() :
+        0L;
+}
+
+osg::RefMatrixf*
+TileNode::getNormalTextureMatrix() const
+{
+    return _normalTexMat.get();
+}
 
 void
 TileNode::setLastTraversalFrame(unsigned frame)
@@ -59,6 +139,28 @@ TileNode::setLastTraversalFrame(unsigned frame)
     _lastTraversalFrame = frame;
 }
 
+osg::Group*
+TileNode::getPayloadGroup() const
+{
+    return _payload.get();
+}
+
+osg::Group*
+TileNode::getOrCreatePayloadGroup()
+{
+    if ( !_payload.valid() )
+    {
+        osg::StateSet* stateSet = new osg::StateSet();
+        std::string binName = Stringify() << "oe.PayloadBin." << _engineUID;
+        stateSet->setRenderBinDetails(1, binName);
+        stateSet->setNestRenderBins( false );
+
+        _payload = new osg::Group();
+        _payload->setStateSet( stateSet );
+        this->addChild( _payload.get() );
+    }
+    return _payload.get();
+}
 
 void
 TileNode::traverse( osg::NodeVisitor& nv )
@@ -67,12 +169,6 @@ TileNode::traverse( osg::NodeVisitor& nv )
     {
         if ( nv.getVisitorType() == nv.CULL_VISITOR )
         {
-            osg::ClusterCullingCallback* ccc = dynamic_cast<osg::ClusterCullingCallback*>(getCullCallback());
-            if (ccc)
-            {
-                if (ccc->cull(&nv,0,static_cast<osg::State *>(0))) return;
-            }
-
             // if this tile is marked dirty, bump the marker so the engine knows it
             // needs replacing.
             if ( _dirty || _model->_revision != _maprevision )
@@ -106,3 +202,86 @@ TileNode::resizeGLObjectBuffers(unsigned maxSize)
     if ( _model.valid() )
         const_cast<TileModel*>(_model.get())->resizeGLObjectBuffers( maxSize );
 }
+
+#define OE_TEST OE_DEBUG
+void
+TileNode::notifyOfArrival(TileNode* that)
+{
+    OE_TEST << LC << this->getKey().str()
+        << " was waiting on "
+        << that->getKey().str() << " and it arrived.\n";
+        
+    osg::Texture* thisTex = this->getNormalTexture();
+    osg::Texture* thatTex = that->getNormalTexture();
+    if ( !thisTex || !thatTex ) {
+        OE_TEST << LC << "bailed on " << getKey().str() << " - null normal texture\n";
+        return;
+    }
+
+    osg::RefMatrixf* thisTexMat = this->getNormalTextureMatrix();
+    osg::RefMatrixf* thatTexMat = that->getNormalTextureMatrix();
+    if ( !thisTexMat || !thatTexMat || !thisTexMat->isIdentity() || !thatTexMat->isIdentity() ) {
+        OE_TEST << LC << "bailed on " << getKey().str() << " - null texmat\n";
+        return;
+    }
+
+    osg::Image* thisImage = thisTex->getImage(0);
+    osg::Image* thatImage = thatTex->getImage(0);
+    if ( !thisImage || !thatImage ) {
+        OE_TEST << LC << "bailed on " << getKey().str() << " - null image\n";
+        return;
+    }
+
+    int width = thisImage->s();
+    int height = thisImage->t();
+    if ( width != thatImage->s() || height != thatImage->t() ) {
+        OE_TEST << LC << "bailed on " << getKey().str() << " - mismatched sizes\n";
+        return;
+    }
+
+    if (_model->_normalData.isFallbackData()) {
+        OE_TEST << LC << "bailed on " << getKey().str() << " - fallback data\n";
+        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);
+
+    bool thisDirty = false;
+
+    if ( that->getKey() == getKey().createNeighborKey(1,0) )
+    {
+        // neighbor is to the east:
+        for(int t=0; t<height; ++t)
+        {
+            writeThis(readThat(0,t), width-1, t);
+        }
+        thisDirty = true;
+    }
+
+    else if ( that->getKey() == getKey().createNeighborKey(0,1) )
+    {
+        // neighbor is to the south:
+        for(int s=0; s<width; ++s)
+        {
+            writeThis(readThat(s, height-1), s, 0);
+        }
+        thisDirty = true;
+    }
+
+    else
+    {
+        OE_INFO << LC << "Unhandled notify\n";
+    }
+
+    if ( thisDirty )
+    {
+        // so heavy handed. Wish we could just write the row
+        // or column that changed.
+        thisImage->dirty();
+    }
+}
diff --git a/src/osgEarthDrivers/engine_mp/TileNodeRegistry b/src/osgEarthDrivers/engine_mp/TileNodeRegistry
index dbc233a..7d78469 100644
--- a/src/osgEarthDrivers/engine_mp/TileNodeRegistry
+++ b/src/osgEarthDrivers/engine_mp/TileNodeRegistry
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
@@ -38,13 +41,17 @@ namespace osgEarth { namespace Drivers { namespace MPTerrainEngine
     public:
         typedef std::map< TileKey, osg::ref_ptr<TileNode> > TileNodeMap;
 
-        // Proprtype for a locked tileset operation (see run)
+        // Prototype for a locked tileset operation (see run)
         struct Operation {
-            virtual void operator()( TileNodeMap& tiles ) =0;
+            virtual void operator()(TileNodeMap& tiles) =0;
         };
-
         struct ConstOperation {
-            virtual void operator()( const TileNodeMap& tiles ) const =0;
+            virtual void operator()(const TileNodeMap& tiles) const =0;
+        };
+
+        // Operation that runs when another node enters the registry.
+        struct DeferredOperation {
+            virtual void operator()(TileNode* requestingNode, TileNode* expectedNode) const =0;
         };
 
     public:
@@ -90,7 +97,7 @@ namespace osgEarth { namespace Drivers { namespace MPTerrainEngine
         void add( TileNode* tile );
 
         /** Adds several tiles to the registry */
-        void add( const TileNodeVector& tiles );
+        //void add( const TileNodeVector& tiles );
 
         /** Removes a tile */
         void remove( TileNode* tile );
@@ -116,6 +123,11 @@ namespace osgEarth { namespace Drivers { namespace MPTerrainEngine
         /** Number of tiles in the registry. */
         unsigned size() const { return _tiles.size(); }
 
+        /** 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);
+
     protected:
 
         bool                              _revisioningEnabled;
@@ -124,6 +136,10 @@ namespace osgEarth { namespace Drivers { namespace MPTerrainEngine
         TileNodeMap                       _tiles;
         OpenThreads::Atomic               _frameNumber;
         mutable Threading::ReadWriteMutex _tilesMutex;
+
+        typedef std::vector<TileKey> TileKeyVector;
+        typedef std::map<TileKey, TileKeyVector> Notifications;
+        Notifications _notifications;
     };
 
 } } } // namespace osgEarth::Drivers::MPTerrainEngine
diff --git a/src/osgEarthDrivers/engine_mp/TileNodeRegistry.cpp b/src/osgEarthDrivers/engine_mp/TileNodeRegistry.cpp
index 0224f73..82f94ec 100644
--- a/src/osgEarthDrivers/engine_mp/TileNodeRegistry.cpp
+++ b/src/osgEarthDrivers/engine_mp/TileNodeRegistry.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
@@ -103,26 +106,37 @@ TileNodeRegistry::add( TileNode* tile )
         _tiles[ tile->getKey() ] = tile;
         if ( _revisioningEnabled )
             tile->setMapRevision( _maprev );
-        OE_TEST << LC << _name << ": tiles=" << _tiles.size() << std::endl;
-    }
-}
 
+        OE_TEST << LC << _name << ": tiles=" << _tiles.size() << std::endl;
 
-void
-TileNodeRegistry::add( const TileNodeVector& tiles )
-{
-    if ( tiles.size() > 0 )
-    {
-        Threading::ScopedWriteLock exclusive( _tilesMutex );
-        for( TileNodeVector::const_iterator i = tiles.begin(); i != tiles.end(); ++i )
+        // check for waiters.
+        Notifications::iterator i = _notifications.find(tile->getKey());
+        if ( i != _notifications.end() )
         {
-            _tiles[ i->get()->getKey() ] = i->get();
+            TileKeyVector& waiters = i->second;
+            for(unsigned j=0; j<waiters.size(); )
+            {
+                TileKey& waiter = waiters[j];
+                TileNodeMap::iterator k = _tiles.find(waiter);
+                if ( k != _tiles.end() )
+                {
+                    k->second->notifyOfArrival( tile );
+                    waiter = waiters.back();
+                    waiters.resize( waiters.size()-1 );
+                }
+                else
+                {
+                    ++j;
+                }
+            }
+            if ( waiters.size() == 0 )
+            {
+                _notifications.erase( i );
+            }
         }
-        OE_TEST << LC << _name << ": tiles=" << _tiles.size() << std::endl;
     }
 }
 
-
 void
 TileNodeRegistry::remove( TileNode* tile )
 {
@@ -207,3 +221,22 @@ TileNodeRegistry::empty() const
     // don't bother mutex-protecteding this.
     return _tiles.empty();
 }
+
+void
+TileNodeRegistry::listenFor(const TileKey& tileToWaitFor, TileNode* waiter)
+{
+    Threading::ScopedWriteLock lock( _tilesMutex );
+    TileNodeMap::iterator i = _tiles.find( tileToWaitFor );
+    if ( i != _tiles.end() )
+    {
+        OE_DEBUG << LC << waiter->getKey().str() << " listened for " << tileToWaitFor.str()
+            << ", but it was already in the repo.\n";
+
+        waiter->notifyOfArrival( i->second.get() );
+    }
+    else
+    {
+        OE_DEBUG << LC << waiter->getKey().str() << " listened for " << tileToWaitFor.str() << ".\n";
+        _notifications[tileToWaitFor].push_back( waiter->getKey() );
+    }
+}
diff --git a/src/osgEarthDrivers/engine_mp/TilePagedLOD b/src/osgEarthDrivers/engine_mp/TilePagedLOD
index f5272da..2842192 100644
--- a/src/osgEarthDrivers/engine_mp/TilePagedLOD
+++ b/src/osgEarthDrivers/engine_mp/TilePagedLOD
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
@@ -30,7 +33,8 @@ using namespace osgEarth;
 namespace osgEarth { namespace Drivers { namespace MPTerrainEngine
 {
     /**
-     * TilePagedLOD is an extension to osg::PagedLOD that supports the tile registry.
+     * TilePagedLOD is an extension to osg::PagedLOD that supports the tile
+     * registry and does LTP bbox culling on subtiles.
      */
     class TilePagedLOD : public osg::PagedLOD
     {
@@ -40,12 +44,24 @@ namespace osgEarth { namespace Drivers { namespace MPTerrainEngine
             TileNodeRegistry* liveTiles,
             TileNodeRegistry* deadTiles);
 
+        /**
+         * Sets a bounding box and localization matrix that will allow
+         * the PagedLOD to perform pre-emptive tight culling before loading 
+         * its child.
+         */
+        void setChildBoundingBoxAndMatrix(
+            int                     childNum,
+            const osg::BoundingBox& bbox,
+            const osg::Matrix&      world2local);
+
         /** The tilenode in this group */
         TileNode* getTileNode();
         void setTileNode(TileNode* tilenode);
 
         osgDB::Options* getOrCreateDBOptions();
 
+        void setDebug(bool value) { _debug = value; }
+
     public: // osg::Group
 
         /** called by the OSG DatabasePager when a paging result is ready. */
@@ -66,6 +82,8 @@ namespace osgEarth { namespace Drivers { namespace MPTerrainEngine
         osg::ref_ptr<TileNodeRegistry> _dead;
         UID                            _engineUID;
         Threading::Mutex               _updateMutex;
+        std::vector<osg::BoundingBox>  _childBBoxes;
+        std::vector<osg::Matrix>       _childBBoxMatrices;
 
         struct MyProgressCallback : public ProgressCallback
         {
@@ -75,6 +93,8 @@ namespace osgEarth { namespace Drivers { namespace MPTerrainEngine
             void update(unsigned frame);
         };
         osg::ref_ptr<MyProgressCallback> _progress;
+        optional<osg::BoundingBox>       _bbox;
+        bool                             _debug;
     };
 
 } } } // namespace osgEarth::Drivers::MPTerrainEngine
diff --git a/src/osgEarthDrivers/engine_mp/TilePagedLOD.cpp b/src/osgEarthDrivers/engine_mp/TilePagedLOD.cpp
index 53128f1..61f57f0 100644
--- a/src/osgEarthDrivers/engine_mp/TilePagedLOD.cpp
+++ b/src/osgEarthDrivers/engine_mp/TilePagedLOD.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
@@ -20,6 +23,7 @@
 #include "TileNodeRegistry"
 #include <osg/Version>
 #include <osgEarth/Registry>
+#include <osgEarth/CullingUtils>
 #include <cassert>
 
 using namespace osgEarth::Drivers::MPTerrainEngine;
@@ -100,7 +104,8 @@ TilePagedLOD::TilePagedLOD(const UID&        engineUID,
 osg::PagedLOD(),
 _engineUID( engineUID ),
 _live     ( live ),
-_dead     ( dead )
+_dead     ( dead ),
+_debug    ( false )
 {
     if ( live )
     {
@@ -130,6 +135,17 @@ TilePagedLOD::getOrCreateDBOptions()
     return static_cast<osgDB::Options*>(getDatabaseOptions());
 }
 
+void
+TilePagedLOD::setChildBoundingBoxAndMatrix(int                     childNum,
+                                           const osg::BoundingBox& bbox,
+                                           const osg::Matrix&      matrix)
+{
+    _childBBoxes.resize(childNum+1);
+    _childBBoxes[childNum] = bbox;
+    _childBBoxMatrices.resize(childNum+1);
+    _childBBoxMatrices[childNum] = matrix;
+}
+
 TileNode*
 TilePagedLOD::getTileNode()
 {
@@ -156,7 +172,7 @@ TilePagedLOD::addChild(osg::Node* node)
 {
     if ( node )
     {
-        // if we see an invalid tile marker, disable the paged lod.
+        // if we see an invalid tile marker, disable the paged lod slot.
         if ( dynamic_cast<InvalidTileNode*>(node) )
         {
             this->setFileName( 1, "" );
@@ -171,6 +187,11 @@ TilePagedLOD::addChild(osg::Node* node)
         if ( tilenode && _live.get() )
         {
             _live->add( tilenode );
+
+            // Listen for out east and south neighbors.
+            const TileKey& key = tilenode->getKey();
+            _live->listenFor( key.createNeighborKey(1, 0), tilenode );
+            _live->listenFor( key.createNeighborKey(0, 1), tilenode );
         }
 
         return osg::PagedLOD::addChild( node );
@@ -179,20 +200,160 @@ TilePagedLOD::addChild(osg::Node* node)
     return false;
 }
 
+
+// MOST of this is copied and pasted from OSG's osg::PagedLOD::traverse,
+// except where otherwise noted with an "osgEarth" comment.
 void
 TilePagedLOD::traverse(osg::NodeVisitor& nv)
 {
-    if (_progress.valid() && 
-        nv.getVisitorType() == nv.CULL_VISITOR && 
-        nv.getFrameStamp() )
+    // set the frame number of the traversal so that external nodes can find out how active this
+    // node is.
+    if (nv.getFrameStamp() &&
+        nv.getVisitorType()==osg::NodeVisitor::CULL_VISITOR)
     {
-        _progress->update( nv.getFrameStamp()->getFrameNumber() );
+        setFrameNumberOfLastTraversal(nv.getFrameStamp()->getFrameNumber());
+        
+        // osgEarth: update our progress tracker to prevent tile cancelation.
+        if (_progress.valid())
+        {
+            _progress->update( nv.getFrameStamp()->getFrameNumber() );
+        }
+    }
+
+    double timeStamp = nv.getFrameStamp()?nv.getFrameStamp()->getReferenceTime():0.0;
+    unsigned int frameNumber = nv.getFrameStamp()?nv.getFrameStamp()->getFrameNumber():0;
+    bool updateTimeStamp = nv.getVisitorType()==osg::NodeVisitor::CULL_VISITOR;
+
+    switch(nv.getTraversalMode())
+    {
+    case(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN):
+            std::for_each(_children.begin(),_children.end(),osg::NodeAcceptOp(nv));
+            break;
+        case(osg::NodeVisitor::TRAVERSE_ACTIVE_CHILDREN):
+        {
+            float required_range = 0;
+            if (_rangeMode==DISTANCE_FROM_EYE_POINT)
+            {
+                required_range = nv.getDistanceToViewPoint(getCenter(),true);
+            }
+            else
+            {
+                osg::CullStack* cullStack = dynamic_cast<osg::CullStack*>(&nv);
+                if (cullStack && cullStack->getLODScale()>0.0f)
+                {
+                    required_range = cullStack->clampedPixelSize(getBound()) / cullStack->getLODScale();
+                }
+                else
+                {
+                    // fallback to selecting the highest res tile by
+                    // finding out the max range
+                    for(unsigned int i=0;i<_rangeList.size();++i)
+                    {
+                        required_range = osg::maximum(required_range,_rangeList[i].first);
+                    }
+                }
+            }
+
+            int lastChildTraversed = -1;
+            bool needToLoadChild = false;
+            for(unsigned int i=0;i<_rangeList.size();++i)
+            {
+                if (_rangeList[i].first<=required_range && required_range<_rangeList[i].second)
+                {
+                    if (i<_children.size())
+                    {
+                        if (updateTimeStamp)
+                        {
+                            _perRangeDataList[i]._timeStamp=timeStamp;
+                            _perRangeDataList[i]._frameNumber=frameNumber;
+                        }
+
+                        _children[i]->accept(nv);
+                        lastChildTraversed = (int)i;
+                    }
+                    else
+                    {
+                        needToLoadChild = true;
+                    }
+                }
+            }
+
+            if (needToLoadChild)
+            {
+                unsigned int numChildren = _children.size();
+
+                // select the last valid child.
+                if (numChildren>0 && ((int)numChildren-1)!=lastChildTraversed)
+                {
+                    if (updateTimeStamp)
+                    {
+                        _perRangeDataList[numChildren-1]._timeStamp=timeStamp;
+                        _perRangeDataList[numChildren-1]._frameNumber=frameNumber;
+                    }
+                    _children[numChildren-1]->accept(nv);
+                }
+
+                // now request the loading of the next unloaded child.
+                if (!_disableExternalChildrenPaging &&
+                    nv.getDatabaseRequestHandler() &&
+                    numChildren<_perRangeDataList.size())
+                {
+                    // osgEarth: Perform a tile visibility check before requesting the new tile.
+                    // Intersect the tile's earth-aligned bounding box with the current culling frustum.
+                    bool tileIsVisible = true;
+
+                    if (nv.getVisitorType() == nv.CULL_VISITOR &&
+                        numChildren < _childBBoxes.size() &&
+                        _childBBoxes[numChildren].valid())
+                    {
+                        osgUtil::CullVisitor* cv = Culling::asCullVisitor( nv );
+                        // wish that CullStack::createOrReuseRefMatrix() was public
+                        osg::ref_ptr<osg::RefMatrix> mvm = new osg::RefMatrix(*cv->getModelViewMatrix());
+                        mvm->preMult( _childBBoxMatrices[numChildren] );
+                        cv->pushModelViewMatrix( mvm.get(), osg::Transform::RELATIVE_RF );
+                        tileIsVisible = !cv->isCulled( _childBBoxes[numChildren] );
+                        cv->popModelViewMatrix();
+                    }
+
+                    if ( tileIsVisible )
+                    {
+                        // [end:osgEarth]
+
+                        // compute priority from where abouts in the required range the distance falls.
+                        float priority = (_rangeList[numChildren].second-required_range)/(_rangeList[numChildren].second-_rangeList[numChildren].first);
+
+                        // invert priority for PIXEL_SIZE_ON_SCREEN mode
+                        if(_rangeMode==PIXEL_SIZE_ON_SCREEN)
+                        {
+                            priority = -priority;
+                        }
+
+                        // modify the priority according to the child's priority offset and scale.
+                        priority = _perRangeDataList[numChildren]._priorityOffset + priority * _perRangeDataList[numChildren]._priorityScale;
+
+                        if (_databasePath.empty())
+                        {
+                            nv.getDatabaseRequestHandler()->requestNodeFile(_perRangeDataList[numChildren]._filename,nv.getNodePath(),priority,nv.getFrameStamp(), _perRangeDataList[numChildren]._databaseRequest, _databaseOptions.get());
+                        }
+                        else
+                        {
+                            // prepend the databasePath to the child's filename.
+                            nv.getDatabaseRequestHandler()->requestNodeFile(_databasePath+_perRangeDataList[numChildren]._filename,nv.getNodePath(),priority,nv.getFrameStamp(), _perRangeDataList[numChildren]._databaseRequest, _databaseOptions.get());
+                        }
+                    }
+                }
+            }
+
+           break;
+        }
+        default:
+            break;
     }
-    
-    osg::PagedLOD::traverse(nv);
 }
 
 
+
+
 // The osgDB::DatabasePager will call this automatically to purge expired
 // tiles from the scene graph.
 bool
@@ -223,7 +384,20 @@ TilePagedLOD::removeExpiredChildren(double         expiryTime,
             ExpirationCollector collector( _live.get(), _dead.get() );
             nodeToRemove->accept( collector );
 
-            OE_DEBUG << LC << "Expired " << collector._count << std::endl;
+            if ( _debug )
+            {
+                TileNode* tileNode = getTileNode();
+                std::string key = tileNode ? tileNode->getKey().str() : "unk";
+                OE_NOTICE 
+                    << LC << "Tile " << key << " : expiring " << collector._count << " children; "
+                    << "TS = " << _perRangeDataList[cindex]._timeStamp
+                    << ", MET = " << minExpiryTime
+                    << ", ET = " << expiryTime
+                    << "; FN = " << _perRangeDataList[cindex]._frameNumber
+                    << ", MEF = " << minExpiryFrames
+                    << ", EF = " << expiryFrame
+                    << "\n";
+            }
 
             return Group::removeChildren(cindex,1);
         }
diff --git a/src/osgEarthDrivers/fastdxt/CMakeLists.txt b/src/osgEarthDrivers/fastdxt/CMakeLists.txt
new file mode 100644
index 0000000..132aa0b
--- /dev/null
+++ b/src/osgEarthDrivers/fastdxt/CMakeLists.txt
@@ -0,0 +1,39 @@
+OPTION(ENABLE_FASTDXT "Set to ON to build optional FastDXT image compressor." OFF)
+IF(ENABLE_FASTDXT)
+
+OPTION(CURL_IS_STATIC "on if curl is a static lib " ON)
+MARK_AS_ADVANCED(CURL_IS_STATIC)
+
+IF(WIN32)
+    SET(CMAKE_SHARED_LINKER_FLAGS_DEBUG "${CMAKE_SHARED_LINKER_FLAGS_DEBUG} /NODEFAULTLIB:MSVCRT")
+    IF(CURL_IS_STATIC)
+        ADD_DEFINITIONS(-DCURL_STATICLIB)
+        SET(TARGET_EXTERNAL_LIBRARIES ws2_32 winmm)
+    ENDIF(CURL_IS_STATIC)
+ENDIF(WIN32)
+
+SET(TARGET_H
+    dxt.h
+    libdxt.h
+    util.h
+)
+
+SET(TARGET_SRC
+    FastDXTImageProcessor.cpp
+    dxt.cpp
+    util.cpp
+    libdxt.cpp
+    intrinsic.cpp
+)
+
+IF( CMAKE_DEBUG_POSTFIX )
+    ADD_DEFINITIONS( -DOSGEARTH_DEBUG_POSTFIX=${CMAKE_DEBUG_POSTFIX} )
+ENDIF()
+
+IF( CMAKE_RELEASE_POSTFIX )
+    ADD_DEFINITIONS( -DOSGEARTH_RELEASE_POSTFIX=${CMAKE_RELEASE_POSTFIX} )
+ENDIF()
+
+SETUP_PLUGIN(fastdxt)
+
+ENDIF(ENABLE_FASTDXT)
\ No newline at end of file
diff --git a/src/osgEarthDrivers/fastdxt/FastDXTImageProcessor.cpp b/src/osgEarthDrivers/fastdxt/FastDXTImageProcessor.cpp
new file mode 100644
index 0000000..2398610
--- /dev/null
+++ b/src/osgEarthDrivers/fastdxt/FastDXTImageProcessor.cpp
@@ -0,0 +1,104 @@
+/* -*-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 <osg/Texture>
+#include <osgDB/Registry>
+#include <osg/Notify>
+#include <osgEarth/ImageUtils>
+#include <stdlib.h>
+#include "libdxt.h"
+#include <string.h>
+
+class FastDXTProcessor : public osgDB::ImageProcessor
+{
+public:
+    virtual void compress(osg::Image& image, osg::Texture::InternalFormatMode compressedFormat, bool generateMipMap, bool resizeToPowerOfTwo, CompressionMethod method, CompressionQuality quality)
+    {
+        //Resize the image to the nearest power of two
+        if (!osgEarth::ImageUtils::isPowerOfTwo( &image ))
+        {
+            unsigned int s = osg::Image::computeNearestPowerOfTwo( image.s() );
+            unsigned int t = osg::Image::computeNearestPowerOfTwo( image.t() );
+            image.scaleImage(s, t, image.r());
+        }
+
+        osg::Image* sourceImage = ℑ
+
+        //FastDXT only works on RGBA imagery so we must convert it
+        osg::ref_ptr< osg::Image > rgba;
+        if (image.getPixelFormat() != GL_RGBA)
+        {
+            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;
+            sourceImage = rgba.get();
+        }
+
+        int format;
+        GLint pixelFormat;
+        switch (compressedFormat)
+        {
+        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;
+            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;
+            break;
+        default:
+            OSG_WARN << "Unhandled compressed format" << compressedFormat << std::endl;
+            return;
+            break;
+        }
+
+        //Copy over the source data to an array
+        unsigned char *in = 0;
+        in = (unsigned char*)memalign(16, sourceImage->getTotalSizeInBytes());
+        memcpy(in, sourceImage->data(0,0), sourceImage->getTotalSizeInBytes());
+
+
+
+        //Allocate memory for the output
+        unsigned char* out = (unsigned char*)memalign(16, image.s()*image.t()*4);
+        memset(out, 0, image.s()*image.t()*4);
+
+        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;
+
+        //Allocate and copy over the output data to the correct size array.
+        unsigned char* data = (unsigned char*)malloc(outputBytes);
+        memcpy(data, out, outputBytes);
+        memfree(out);
+        memfree(in);
+        image.setImage(image.s(), image.t(), image.r(), pixelFormat, pixelFormat, GL_UNSIGNED_BYTE, data, osg::Image::USE_MALLOC_FREE);
+    }
+
+    virtual void generateMipMap(osg::Image& image, bool resizeToPowerOfTwo, CompressionMethod method)
+    {
+        OSG_WARN << "FastDXT: generateMipMap not implemented" << std::endl;
+    }
+};
+
+REGISTER_OSGIMAGEPROCESSOR(fastdxt, FastDXTProcessor)
diff --git a/src/osgEarthDrivers/fastdxt/dxt.cpp b/src/osgEarthDrivers/fastdxt/dxt.cpp
new file mode 100644
index 0000000..19b8e1f
--- /dev/null
+++ b/src/osgEarthDrivers/fastdxt/dxt.cpp
@@ -0,0 +1,542 @@
+// From:
+//    Real-Time DXT Compression
+//    May 20th 2006 J.M.P. van Waveren
+//    (c) 2006, Id Software, Inc.
+
+#include "dxt.h"
+#include "util.h"
+
+
+#define DXT_INTR 1
+
+void ExtractBlock( const byte *inPtr, int width, byte *colorBlock );
+void ExtractBlock_Intrinsics( const byte *inPtr, int width, byte *colorBlock );
+
+void GetMinMaxColors( const byte *colorBlock, byte *minColor, byte *maxColor );
+void GetMinMaxColorsByLuminance( const byte *colorBlock, byte *minColor, byte *maxColor );
+void GetMinMaxColorsByBBox( const byte *colorBlock, byte *minColor, byte *maxColor );
+void GetMinMaxColors_Intrinsics( const byte *colorBlock, byte *minColor, byte *maxColor );
+
+// for DXT5
+void GetMinMaxColorsAlpha(  byte *colorBlock, byte *minColor, byte *maxColor );
+
+
+word ColorTo565( const byte *color );
+
+void EmitByte( byte b, byte*& );
+void EmitWord( word s, byte*& );
+void EmitDoubleWord( dword i, byte*& );
+
+void EmitColorIndices( const byte *colorBlock, const byte *minColor, const byte *maxColor, byte *&outData );
+void EmitColorIndicesFast( const byte *colorBlock, const byte *minColor, const byte *maxColor, byte *&outData );
+void EmitColorIndices_Intrinsics( const byte *colorBlock, const byte *minColor, const byte *maxColor, byte *&outData);
+
+// Emit indices for DXT5
+void EmitAlphaIndices( const byte *colorBlock, const byte minAlpha, const byte maxAlpha, byte *&outData);
+void EmitAlphaIndicesFast( const byte *colorBlock, const byte minAlpha, const byte maxAlpha, byte *&outData);
+void EmitAlphaIndices_Intrinsics( const byte *colorBlock, const byte minAlpha, const byte maxAlpha, byte *&outData);
+
+
+void CompressImageDXT1( const byte *inBuf, byte *outBuf,
+			int width, int height, int &outputBytes )
+{
+  ALIGN16( byte *outData );
+  ALIGN16( byte block[64] );
+  ALIGN16( byte minColor[4] );
+  ALIGN16( byte maxColor[4] );
+
+  outData = outBuf;
+  for ( int j = 0; j < height; j += 4, inBuf += width * 4*4 ) {
+    for ( int i = 0; i < width; i += 4 ) {
+
+#if defined(DXT_INTR)
+	ExtractBlock_Intrinsics( inBuf + i * 4, width, block );
+#else
+	ExtractBlock( inBuf + i * 4, width, block );
+#endif
+
+#if defined(DXT_INTR)
+      GetMinMaxColors_Intrinsics( block, minColor, maxColor );
+#else
+      GetMinMaxColorsByBBox( block, minColor, maxColor );
+#endif
+
+      EmitWord( ColorTo565( maxColor ), outData );
+      EmitWord( ColorTo565( minColor ), outData );
+
+#if defined(DXT_INTR)
+      EmitColorIndices_Intrinsics( block, minColor, maxColor, outData );
+#else
+      EmitColorIndicesFast( block, minColor, maxColor, outData );
+#endif
+    }
+  }
+  outputBytes = (int) ( outData - outBuf );
+}
+
+void RGBAtoYCoCg(const byte *inBuf, byte *outBuf, int width, int height)
+{
+  for ( int j = 0; j < width*height; j++ ) {
+    byte R = inBuf[j*4+0];
+    byte G = inBuf[j*4+1];
+    byte B = inBuf[j*4+2];
+    byte A = inBuf[j*4+3];
+    int Co = R-B;
+    int t = B + (Co/2);
+    int Cg = G-t;
+    int Y = t + (Cg/2);
+
+    Co += 128;
+    Cg += 96;
+
+//     if (Co < 0 || Co > 255)
+//       fprintf(stderr, "Co: %d\n", Co);
+//     if (Cg < 0 || Cg > 255)
+//       fprintf(stderr, "Cg: %d\n", Cg);
+//     if ( Y < 0 ||  Y > 255)
+//       fprintf(stderr, "Y: %d\n", Y);
+
+    if (Co < 0)   Co=0;
+    if (Co > 255) Co=255;
+    if (Cg < 0)   Cg=0;
+    if (Cg > 255) Cg=255;
+
+    outBuf[j*4+0] = Co;
+    outBuf[j*4+1] = Cg;
+    outBuf[j*4+2] = 0;
+    outBuf[j*4+3] = Y;
+  }
+}
+
+void CompressImageDXT5YCoCg( const byte *inBuf, byte *outBuf, int width, int height,
+			     int &outputBytes )
+{
+  byte *tmpBuf;
+  tmpBuf = (byte*)memalign(16, width*height*4);
+  memset(tmpBuf, 0, width*height*4);
+  RGBAtoYCoCg(inBuf, tmpBuf, width, height);
+  CompressImageDXT5(tmpBuf, outBuf, width, height, outputBytes);
+  memfree(tmpBuf);
+}
+
+
+void CompressImageDXT5( const byte *inBuf, byte *outBuf, int width, int height,
+			int &outputBytes )
+{
+  ALIGN16( byte *outData );
+  
+  ALIGN16( byte block[64] );
+  ALIGN16( byte minColor[4] );
+  ALIGN16( byte maxColor[4] );
+  
+  outData = outBuf;
+  for ( int j = 0; j < height; j += 4, inBuf += width * 4*4 ) {
+    for ( int i = 0; i < width; i += 4 ) {
+      
+#if defined(DXT_INTR)
+      ExtractBlock_Intrinsics( inBuf + i * 4, width, block );
+#else
+      ExtractBlock( inBuf + i * 4, width, block );
+#endif
+      
+#if defined(DXT_INTR)
+      GetMinMaxColors_Intrinsics( block, minColor, maxColor );
+#else
+      GetMinMaxColorsAlpha( block, minColor, maxColor );
+#endif
+      
+      EmitByte( maxColor[3], outData);
+      EmitByte( minColor[3], outData);
+      
+#if defined(DXT_INTR)
+	  // Not Yet Implemented
+      //EmitAlphaIndices_Intrinsics( block, minColor[3], maxColor[3], outData );
+
+      EmitAlphaIndicesFast( block, minColor[3], maxColor[3], outData );
+#else
+      EmitAlphaIndicesFast( block, minColor[3], maxColor[3], outData );
+#endif
+      
+      EmitWord( ColorTo565( maxColor ), outData);
+      EmitWord( ColorTo565( minColor ), outData);
+      
+#if defined(DXT_INTR)
+      EmitColorIndices_Intrinsics( block, minColor, maxColor, outData );
+#else
+      EmitColorIndicesFast( block, minColor, maxColor, outData );
+#endif
+    }
+  }
+  outputBytes = int( outData - outBuf );
+}
+
+
+
+
+void ExtractBlock( const byte *inPtr, int width, byte *colorBlock )
+{
+  for ( int j = 0; j < 4; j++ ) {
+    memcpy( &colorBlock[j*4*4], inPtr, 4*4 );
+    inPtr += width * 4;    
+  }
+}
+
+word ColorTo565( const byte *color )
+{
+  return ( ( color[ 0 ] >> 3 ) << 11 ) |
+         ( ( color[ 1 ] >> 2 ) <<  5 ) |
+         (   color[ 2 ] >> 3 );
+}
+
+void EmitByte( byte b, byte *&outData)
+{
+  outData[0] = b;
+  outData += 1;
+}
+
+void EmitWord( word s, byte *&outData)
+{
+  outData[0] = ( s >> 0 ) & 255;
+  outData[1] = ( s >> 8 ) & 255;
+  outData += 2;
+}
+
+void EmitDoubleWord( dword i, byte *&outData)
+{
+  outData[0] = ( i >> 0 ) & 255;
+  outData[1] = ( i >> 8 ) & 255;
+  outData[2] = ( i >> 16 ) & 255;
+  outData[3] = ( i >> 24 ) & 255;
+  outData += 4;
+}
+
+
+int ColorDistance( const byte *c1, const byte *c2 )
+{
+  return ( ( c1[0] - c2[0] ) * ( c1[0] - c2[0] ) ) +
+    ( ( c1[1] - c2[1] ) * ( c1[1] - c2[1] ) ) +
+    ( ( c1[2] - c2[2] ) * ( c1[2] - c2[2] ) );
+}
+
+void SwapColors( byte *c1, byte *c2 )
+{
+  byte tm[3];
+  memcpy( tm, c1, 3 );
+  memcpy( c1, c2, 3 );
+  memcpy( c2, tm, 3 );
+}
+
+void GetMinMaxColors( const byte *colorBlock, byte *minColor, byte *maxColor )
+{
+  int maxDistance = -1;
+  for ( int i = 0; i < 64 - 4; i += 4 ) {
+    for ( int j = i + 4; j < 64; j += 4 ) {
+      int distance = ColorDistance( &colorBlock[i], &colorBlock[j] );
+      if ( distance > maxDistance ) {
+	maxDistance = distance;
+	memcpy( minColor, colorBlock+i, 3 );
+	memcpy( maxColor, colorBlock+j, 3 );
+      }
+    }
+  }
+  if ( ColorTo565( maxColor ) < ColorTo565( minColor ) ) {
+    SwapColors( minColor, maxColor );
+  }
+}
+
+
+int ColorLuminance( const byte *color )
+{
+  return ( color[0] + color[1] * 2 + color[2] );
+}
+
+
+void GetMinMaxColorsByLuminance( const byte *colorBlock, byte *minColor, byte *maxColor )
+{
+  int maxLuminance = -1, minLuminance = MAX_INT;
+  for (int i = 0; i < 16; i++ ) {
+    int luminance = ColorLuminance( colorBlock+i*4 );
+    if ( luminance > maxLuminance ) {
+      maxLuminance = luminance;
+      memcpy( maxColor, colorBlock+i*4, 3 );
+    }
+    if ( luminance < minLuminance ) {
+      minLuminance = luminance;
+      memcpy( minColor, colorBlock+i*4, 3 );
+    }
+  }
+  if ( ColorTo565( maxColor ) < ColorTo565( minColor ) ) {
+    SwapColors( minColor, maxColor );
+  }
+}
+
+void GetMinMaxColorsByBBox( const byte *colorBlock, byte *minColor, byte *maxColor )
+{
+  int i;
+  byte inset[3];
+  minColor[0] = minColor[1] = minColor[2] = 255;
+  maxColor[0] = maxColor[1] = maxColor[2] = 0;
+  for ( i = 0; i < 16; i++ ) {
+    if ( colorBlock[i*4+0] < minColor[0] ) { minColor[0] = colorBlock[i*4+0]; }
+    if ( colorBlock[i*4+1] < minColor[1] ) { minColor[1] = colorBlock[i*4+1]; }
+    if ( colorBlock[i*4+2] < minColor[2] ) { minColor[2] = colorBlock[i*4+2]; }
+    if ( colorBlock[i*4+0] > maxColor[0] ) { maxColor[0] = colorBlock[i*4+0]; }
+    if ( colorBlock[i*4+1] > maxColor[1] ) { maxColor[1] = colorBlock[i*4+1]; }
+    if ( colorBlock[i*4+2] > maxColor[2] ) { maxColor[2] = colorBlock[i*4+2]; }
+  }
+  inset[0] = ( maxColor[0] - minColor[0] ) >> INSET_SHIFT;
+  inset[1] = ( maxColor[1] - minColor[1] ) >> INSET_SHIFT;
+  inset[2] = ( maxColor[2] - minColor[2] ) >> INSET_SHIFT;
+  minColor[0] = ( minColor[0] + inset[0] <= 255 ) ? minColor[0] + inset[0] : 255;
+  minColor[1] = ( minColor[1] + inset[1] <= 255 ) ? minColor[1] + inset[1] : 255;
+  minColor[2] = ( minColor[2] + inset[2] <= 255 ) ? minColor[2] + inset[2] : 255;
+  maxColor[0] = ( maxColor[0] >= inset[0] ) ? maxColor[0] - inset[0] : 0;
+  maxColor[1] = ( maxColor[1] >= inset[1] ) ? maxColor[1] - inset[1] : 0;
+  maxColor[2] = ( maxColor[2] >= inset[2] ) ? maxColor[2] - inset[2] : 0;
+}
+
+
+//
+// GetMinMaxColorsAlpha for DXT5
+//
+void GetMinMaxColorsAlpha(  byte *colorBlock, byte *minColor, byte *maxColor )
+{
+  int i;
+  byte inset[4];
+  byte y,cg, co, r, g, b;
+
+  minColor[0] = minColor[1] = minColor[2] = minColor[3] = 255;
+  maxColor[0] = maxColor[1] = maxColor[2] = maxColor[3] = 0;
+
+  for ( i = 0; i < 16; i++ ) {
+    r = colorBlock[i*4+0];
+    g = colorBlock[i*4+1];
+    b = colorBlock[i*4+2];
+    y  = (g>>1) + ((r+b)>>2);
+    cg = g - ((r+b)>>1);
+    co = r - b;
+
+    colorBlock[i*4+0] = co;
+    colorBlock[i*4+1] = cg;
+    colorBlock[i*4+2] = 0;
+    colorBlock[i*4+3] = y;
+
+    if ( colorBlock[i*4+0] < minColor[0] ) { minColor[0] = colorBlock[i*4+0]; }
+    if ( colorBlock[i*4+1] < minColor[1] ) { minColor[1] = colorBlock[i*4+1]; }
+    if ( colorBlock[i*4+2] < minColor[2] ) { minColor[2] = colorBlock[i*4+2]; }
+    if ( colorBlock[i*4+3] < minColor[3] ) { minColor[3] = colorBlock[i*4+3]; }
+    if ( colorBlock[i*4+0] > maxColor[0] ) { maxColor[0] = colorBlock[i*4+0]; }
+    if ( colorBlock[i*4+1] > maxColor[1] ) { maxColor[1] = colorBlock[i*4+1]; }
+    if ( colorBlock[i*4+2] > maxColor[2] ) { maxColor[2] = colorBlock[i*4+2]; }
+    if ( colorBlock[i*4+3] > maxColor[3] ) { maxColor[3] = colorBlock[i*4+3]; }
+  }
+
+  inset[0] = ( maxColor[0] - minColor[0] ) >> INSET_SHIFT;
+  inset[1] = ( maxColor[1] - minColor[1] ) >> INSET_SHIFT;
+  inset[2] = ( maxColor[2] - minColor[2] ) >> INSET_SHIFT;
+  inset[3] = ( maxColor[3] - minColor[3] ) >> INSET_SHIFT;
+
+  minColor[0] = ( minColor[0] + inset[0] <= 255 ) ? minColor[0] + inset[0] : 255;
+  minColor[1] = ( minColor[1] + inset[1] <= 255 ) ? minColor[1] + inset[1] : 255;
+  minColor[2] = ( minColor[2] + inset[2] <= 255 ) ? minColor[2] + inset[2] : 255;
+  minColor[3] = ( minColor[3] + inset[3] <= 255 ) ? minColor[3] + inset[3] : 255;
+
+  maxColor[0] = ( maxColor[0] >= inset[0] ) ? maxColor[0] - inset[0] : 0;
+  maxColor[1] = ( maxColor[1] >= inset[1] ) ? maxColor[1] - inset[1] : 0;
+  maxColor[2] = ( maxColor[2] >= inset[2] ) ? maxColor[2] - inset[2] : 0;
+  maxColor[3] = ( maxColor[3] >= inset[3] ) ? maxColor[3] - inset[3] : 0;
+}
+
+
+void EmitColorIndices( const byte *colorBlock, const byte *minColor, const byte *maxColor, byte *&outData )
+{
+  byte colors[4][4];
+  unsigned int indices[16];
+  
+  colors[0][0] = ( maxColor[0] & C565_5_MASK ) | ( maxColor[0] >> 5 );
+  colors[0][1] = ( maxColor[1] & C565_6_MASK ) | ( maxColor[1] >> 6 );
+  colors[0][2] = ( maxColor[2] & C565_5_MASK ) | ( maxColor[2] >> 5 );
+
+  colors[1][0] = ( minColor[0] & C565_5_MASK ) | ( minColor[0] >> 5 );
+  colors[1][1] = ( minColor[1] & C565_6_MASK ) | ( minColor[1] >> 6 );
+  colors[1][2] = ( minColor[2] & C565_5_MASK ) | ( minColor[2] >> 5 );
+
+  colors[2][0] = ( 2 * colors[0][0] + 1 * colors[1][0] ) / 3;
+  colors[2][1] = ( 2 * colors[0][1] + 1 * colors[1][1] ) / 3;
+  colors[2][2] = ( 2 * colors[0][2] + 1 * colors[1][2] ) / 3;
+
+  colors[3][0] = ( 1 * colors[0][0] + 2 * colors[1][0] ) / 3;
+  colors[3][1] = ( 1 * colors[0][1] + 2 * colors[1][1] ) / 3;
+  colors[3][2] = ( 1 * colors[0][2] + 2 * colors[1][2] ) / 3;
+
+  for ( int i = 0; i < 16; i++ ) {
+    unsigned int minDistance = MAX_INT;
+    for ( int j = 0; j < 4; j++ ) {
+      unsigned int dist = ColorDistance( &colorBlock[i*4], &colors[j][0] );
+      if ( dist < minDistance ) {
+	minDistance = dist;
+	indices[i] = j;
+      }
+    }
+  }
+  dword result = 0;
+  for ( int i = 0; i < 16; i++ ) {
+    result |= ( indices[i] << (unsigned int)( i << 1 ) );
+  }
+  EmitDoubleWord( result, outData );
+}
+
+
+void EmitColorIndicesFast( const byte *colorBlock, const byte *minColor, const byte *maxColor, byte *&outData )
+{
+  word colors[4][4];
+  dword result = 0;
+
+  colors[0][0] = ( maxColor[0] & C565_5_MASK ) | ( maxColor[0] >> 5 );
+  colors[0][1] = ( maxColor[1] & C565_6_MASK ) | ( maxColor[1] >> 6 );
+  colors[0][2] = ( maxColor[2] & C565_5_MASK ) | ( maxColor[2] >> 5 );
+  colors[1][0] = ( minColor[0] & C565_5_MASK ) | ( minColor[0] >> 5 );
+  colors[1][1] = ( minColor[1] & C565_6_MASK ) | ( minColor[1] >> 6 );
+  colors[1][2] = ( minColor[2] & C565_5_MASK ) | ( minColor[2] >> 5 );
+  colors[2][0] = ( 2 * colors[0][0] + 1 * colors[1][0] ) / 3;
+  colors[2][1] = ( 2 * colors[0][1] + 1 * colors[1][1] ) / 3;
+  colors[2][2] = ( 2 * colors[0][2] + 1 * colors[1][2] ) / 3;
+  colors[3][0] = ( 1 * colors[0][0] + 2 * colors[1][0] ) / 3;
+  colors[3][1] = ( 1 * colors[0][1] + 2 * colors[1][1] ) / 3;
+  colors[3][2] = ( 1 * colors[0][2] + 2 * colors[1][2] ) / 3;
+
+  for ( int i = 15; i >= 0; i-- ) {
+    int c0 = colorBlock[i*4+0];
+    int c1 = colorBlock[i*4+1];
+    int c2 = colorBlock[i*4+2];
+    int d0 = abs( colors[0][0] - c0 ) + abs( colors[0][1] - c1 ) + abs( colors[0][2] - c2 );
+    int d1 = abs( colors[1][0] - c0 ) + abs( colors[1][1] - c1 ) + abs( colors[1][2] - c2 );
+    int d2 = abs( colors[2][0] - c0 ) + abs( colors[2][1] - c1 ) + abs( colors[2][2] - c2 );
+    int d3 = abs( colors[3][0] - c0 ) + abs( colors[3][1] - c1 ) + abs( colors[3][2] - c2 );
+
+    int b0 = d0 > d3;
+    int b1 = d1 > d2;
+    int b2 = d0 > d2;
+    int b3 = d1 > d3;
+    int b4 = d2 > d3;
+
+    int x0 = b1 & b2;
+    int x1 = b0 & b3;
+    int x2 = b0 & b4;
+    result |= ( x2 | ( ( x0 | x1 ) << 1 ) ) << ( i << 1 );
+  }
+  EmitDoubleWord( result, outData );
+}
+
+
+//
+// Emit indices for DXT5
+//
+void EmitAlphaIndices( const byte *colorBlock, const byte minAlpha, const byte maxAlpha, byte *&outData )
+{
+  byte indices[16];
+  byte alphas[8];
+
+  alphas[0] = maxAlpha;
+  alphas[1] = minAlpha;
+  alphas[2] = ( 6 * maxAlpha + 1 * minAlpha ) / 7;
+  alphas[3] = ( 5 * maxAlpha + 2 * minAlpha ) / 7;
+  alphas[4] = ( 4 * maxAlpha + 3 * minAlpha ) / 7;
+  alphas[5] = ( 3 * maxAlpha + 4 * minAlpha ) / 7;
+  alphas[6] = ( 2 * maxAlpha + 5 * minAlpha ) / 7;
+  alphas[7] = ( 1 * maxAlpha + 6 * minAlpha ) / 7;
+
+  colorBlock += 3;
+
+  for ( int i = 0; i < 16; i++ ) {
+    int minDistance = MAX_INT;
+    byte a = colorBlock[i*4];
+    for ( int j = 0; j < 8; j++ ) {
+      int dist = abs( a - alphas[j] );
+      if ( dist < minDistance ) {
+	minDistance = dist; indices[i] = j;
+      }
+    }
+  }
+
+  EmitByte( (indices[ 0] >> 0) | (indices[ 1] << 3) | (indices[ 2] << 6) , outData);
+  EmitByte( (indices[ 2] >> 2) | (indices[ 3] << 1) | (indices[ 4] << 4) | (indices[ 5] << 7) , outData);
+  EmitByte( (indices[ 5] >> 1) | (indices[ 6] << 2) | (indices[ 7] << 5) , outData);
+  EmitByte( (indices[ 8] >> 0) | (indices[ 9] << 3) | (indices[10] << 6) , outData);
+  EmitByte( (indices[10] >> 2) | (indices[11] << 1) | (indices[12] << 4) | (indices[13] << 7) , outData);
+  EmitByte( (indices[13] >> 1) | (indices[14] << 2) | (indices[15] << 5) , outData);
+}
+
+
+void EmitAlphaIndicesFast( const byte *colorBlock, const byte minAlpha, const byte maxAlpha, byte *&outData )
+{
+  //assert( maxAlpha > minAlpha );
+
+  byte indices[16];
+  byte mid = ( maxAlpha - minAlpha ) / ( 2 * 7 );
+  byte ab1 = minAlpha + mid;
+  byte ab2 = ( 6 * maxAlpha + 1 * minAlpha ) / 7 + mid;
+  byte ab3 = ( 5 * maxAlpha + 2 * minAlpha ) / 7 + mid;
+  byte ab4 = ( 4 * maxAlpha + 3 * minAlpha ) / 7 + mid;
+  byte ab5 = ( 3 * maxAlpha + 4 * minAlpha ) / 7 + mid;
+  byte ab6 = ( 2 * maxAlpha + 5 * minAlpha ) / 7 + mid;
+  byte ab7 = ( 1 * maxAlpha + 6 * minAlpha ) / 7 + mid;
+
+  colorBlock += 3;
+
+  for ( int i = 0; i < 16; i++ ) {
+
+    byte a = colorBlock[i*4];
+
+    int b1 = ( a <= ab1 );
+    int b2 = ( a <= ab2 );
+    int b3 = ( a <= ab3 );
+    int b4 = ( a <= ab4 );
+    int b5 = ( a <= ab5 );
+    int b6 = ( a <= ab6 );
+    int b7 = ( a <= ab7 );
+
+    int index = ( b1 + b2 + b3 + b4 + b5 + b6 + b7 + 1 ) & 7;
+
+    indices[i] = index ^ ( 2 > index );
+  }
+
+  EmitByte( (indices[ 0] >> 0) | (indices[ 1] << 3) | (indices[ 2] << 6) , outData);
+  EmitByte( (indices[ 2] >> 2) | (indices[ 3] << 1) | (indices[ 4] << 4) | (indices[ 5] << 7) , outData);
+  EmitByte( (indices[ 5] >> 1) | (indices[ 6] << 2) | (indices[ 7] << 5) , outData);
+  EmitByte( (indices[ 8] >> 0) | (indices[ 9] << 3) | (indices[10] << 6) , outData);
+  EmitByte( (indices[10] >> 2) | (indices[11] << 1) | (indices[12] << 4) | (indices[13] << 7) , outData);
+  EmitByte( (indices[13] >> 1) | (indices[14] << 2) | (indices[15] << 5) , outData);
+}
+
+
+double ComputeError( const byte *original, const byte *dxt, int width, int height)
+{
+  // Compute RMS error
+  double error;
+  int s, d;
+
+  error = 0.0;
+
+  // dxt: pointer to data read back from the framebuffer. RGB data upside down.
+  for (int i=0; i<height; i++)
+    {
+      for (int j=0; j<width; j++)
+	{
+	  s = original[4*(width*i+j)+0];
+	  d = dxt[3*(width*(height-i-1)+j)+0];
+	  error += (s-d) * (s-d);
+	  
+	  s = original[4*(width*i+j)+1];
+	  d = dxt[3*(width*(height-i-1)+j)+1];
+	  error += (s-d) * (s-d);
+	  
+	  s = original[4*(width*i+j)+2];
+	  d = dxt[3*(width*(height-i-1)+j)+2];
+	  error += (s-d) * (s-d);
+	}
+    }
+  error = sqrt( error / (double)( width*height ) );
+
+  return error;
+}
diff --git a/src/osgEarthDrivers/fastdxt/dxt.h b/src/osgEarthDrivers/fastdxt/dxt.h
new file mode 100644
index 0000000..03a70ab
--- /dev/null
+++ b/src/osgEarthDrivers/fastdxt/dxt.h
@@ -0,0 +1,68 @@
+/******************************************************************************
+ * Fast DXT - a realtime DXT compression tool
+ *
+ * Author : Luc Renambot
+ *
+ * Copyright (C) 2007 Electronic Visualization Laboratory,
+ * University of Illinois at Chicago
+ *
+ * This library 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.1 of the License, or
+ * (at your option) any later version.
+ *
+ * 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 GNU Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser Public License along
+ * with this library; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ *****************************************************************************/
+
+// From:
+//    Real-Time DXT Compression
+//    May 20th 2006 J.M.P. van Waveren
+//    (c) 2006, Id Software, Inc.
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <string.h>
+#include <math.h>
+
+typedef unsigned char byte;
+typedef unsigned short word;
+typedef unsigned int dword;
+
+#define C565_5_MASK 0xF8 // 0xFF minus last three bits
+#define C565_6_MASK 0xFC // 0xFF minus last two bits
+
+#define INSET_SHIFT 4 // inset the bounding box with ( range >> shift )
+
+#if !defined(MAX_INT)
+#define       MAX_INT         2147483647      /* max value for an int 32 */
+#define       MIN_INT         (-2147483647-1) /* min value for an int 32 */
+#endif
+
+
+#if defined(__GNUC__)
+#define   ALIGN16(_x)   _x __attribute((aligned(16)))
+#else
+#define   ALIGN16( x ) __declspec(align(16)) x
+#endif
+
+
+// Compress to DXT1 format
+void CompressImageDXT1( const byte *inBuf, byte *outBuf, int width, int height, int &outputBytes );
+
+// Compress to DXT5 format
+void CompressImageDXT5( const byte *inBuf, byte *outBuf, int width, int height, int &outputBytes );
+
+// Compress to DXT5 format, first convert to YCoCg color space
+void CompressImageDXT5YCoCg( const byte *inBuf, byte *outBuf, int width, int height, int &outputBytes );
+
+// Compute error between two images
+double ComputeError( const byte *original, const byte *dxt, int width, int height);
diff --git a/src/osgEarthDrivers/fastdxt/intrinsic.cpp b/src/osgEarthDrivers/fastdxt/intrinsic.cpp
new file mode 100644
index 0000000..9cb0d2b
--- /dev/null
+++ b/src/osgEarthDrivers/fastdxt/intrinsic.cpp
@@ -0,0 +1,533 @@
+/******************************************************************************
+ * Fast DXT - a realtime DXT compression tool
+ *
+ * Author : Luc Renambot
+ *
+ * Copyright (C) 2007 Electronic Visualization Laboratory,
+ * University of Illinois at Chicago
+ *
+ * This library 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.1 of the License, or
+ * (at your option) any later version.
+ *
+ * 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 GNU Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser Public License along
+ * with this library; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ *****************************************************************************/
+
+/*
+	Code convert from asm to intrinsics from:
+
+		Copyright (C) 2006 Id Software, Inc.
+		Written by J.M.P. van Waveren
+		This code 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.1 of the License, or (at your option) any later version.
+		This code 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.
+*/
+
+#include "dxt.h"
+
+#include <emmintrin.h>  // sse2
+
+
+void ExtractBlock_Intrinsics( const byte *inPtr, int width, byte *colorBlock ) 
+{
+        __m128i t0, t1, t2, t3;
+	register int w = width << 2;  // width*4
+
+        t0 = _mm_load_si128 ( (__m128i*) inPtr );
+        _mm_store_si128 ( (__m128i*) &colorBlock[0], t0 );   // copy first row, 16bytes
+
+        t1 = _mm_load_si128 ( (__m128i*) (inPtr + w) );
+        _mm_store_si128 ( (__m128i*) &colorBlock[16], t1 );   // copy second row
+
+        t2 = _mm_load_si128 ( (__m128i*) (inPtr + 2*w) );
+        _mm_store_si128 ( (__m128i*) &colorBlock[32], t2 );   // copy third row
+
+	inPtr = inPtr + w;     // add width, intead of *3
+
+        t3 = _mm_load_si128 ( (__m128i*) (inPtr + 2*w) );
+        _mm_store_si128 ( (__m128i*) &colorBlock[48], t3 );   // copy last row
+}
+
+#define R_SHUFFLE_D( x, y, z, w ) (( (w) & 3 ) << 6 | ( (z) & 3 ) << 4 | ( (y) & 3 ) << 2 | ( (x) & 3 ))
+
+ALIGN16( static byte SIMD_SSE2_byte_0[16] ) = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
+
+void GetMinMaxColors_Intrinsics( const byte *colorBlock, byte *minColor, byte *maxColor )
+{
+    __m128i t0, t1, t3, t4, t6, t7;
+
+    // get bounding box
+    // ----------------
+    
+    // load the first row
+    t0 = _mm_load_si128 ( (__m128i*) colorBlock );
+    t1 = _mm_load_si128 ( (__m128i*) colorBlock );
+
+    __m128i t16 = _mm_load_si128 ( (__m128i*) (colorBlock+16) );
+    // Minimum of Packed Unsigned Byte Integers
+    t0 = _mm_min_epu8 ( t0, t16);
+    // Maximum of Packed Unsigned Byte Integers
+    t1 = _mm_max_epu8 ( t1, t16);
+    
+    __m128i t32 = _mm_load_si128 ( (__m128i*) (colorBlock+32) );
+    t0 = _mm_min_epu8 ( t0, t32);
+    t1 = _mm_max_epu8 ( t1, t32);
+    
+    __m128i t48 = _mm_load_si128 ( (__m128i*) (colorBlock+48) );
+    t0 = _mm_min_epu8 ( t0, t48);
+    t1 = _mm_max_epu8 ( t1, t48);
+    
+    // Shuffle Packed Doublewords
+    t3 = _mm_shuffle_epi32( t0, R_SHUFFLE_D( 2, 3, 2, 3 ) );
+    t4 = _mm_shuffle_epi32( t1, R_SHUFFLE_D( 2, 3, 2, 3 ) );
+    
+    t0 = _mm_min_epu8 ( t0, t3);
+    t1 = _mm_max_epu8 ( t1, t4);
+    
+    // Shuffle Packed Low Words
+    t6 = _mm_shufflelo_epi16( t0, R_SHUFFLE_D( 2, 3, 2, 3 ) );
+    t7 = _mm_shufflelo_epi16( t1, R_SHUFFLE_D( 2, 3, 2, 3 ) );
+    
+    t0 = _mm_min_epu8 ( t0, t6);
+    t1 = _mm_max_epu8 ( t1, t7);
+    
+    // inset the bounding box
+    // ----------------------
+    
+    // Unpack Low Data
+    //__m128i t66 = _mm_set1_epi8( 0 );
+    __m128i t66 = _mm_load_si128 ( (__m128i*) SIMD_SSE2_byte_0 );
+    t0 = _mm_unpacklo_epi8(t0, t66);
+    t1 = _mm_unpacklo_epi8(t1, t66);
+    
+    // copy (movdqa)
+    //__m128i t2 = _mm_load_si128 ( &t1 );
+    __m128i t2 = t1;
+    
+    // Subtract Packed Integers
+    t2 = _mm_sub_epi16(t2, t0);
+    
+    // Shift Packed Data Right Logical 
+    t2 = _mm_srli_epi16(t2, INSET_SHIFT);
+    
+    // Add Packed Integers
+    t0 = _mm_add_epi16(t0, t2);
+    
+    t1 = _mm_sub_epi16(t1, t2);
+    
+    // Pack with Unsigned Saturation
+    t0 = _mm_packus_epi16(t0, t0);
+    t1 = _mm_packus_epi16(t1, t1);
+    
+    // store bounding box extents
+    // --------------------------
+    _mm_store_si128 ( (__m128i*) minColor, t0 );
+    _mm_store_si128 ( (__m128i*) maxColor, t1 );
+}
+
+
+ALIGN16( static word SIMD_SSE2_word_0[8] ) = { 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000 };
+ALIGN16( static word SIMD_SSE2_word_1[8] ) = { 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001 };
+ALIGN16( static word SIMD_SSE2_word_2[8] ) = { 0x0002, 0x0002, 0x0002, 0x0002, 0x0002, 0x0002, 0x0002, 0x0002 };
+ALIGN16( static word SIMD_SSE2_word_div_by_3[8] ) = { (1<<16)/3+1, (1<<16)/3+1, (1<<16)/3+1, (1<<16)/3+1, (1<<16)/3+1, (1<<16)/3+1, (1<<16)/3+1, (1<<16)/3+1 };
+ALIGN16( static byte SIMD_SSE2_byte_colorMask[16] ) = { C565_5_MASK, C565_6_MASK, C565_5_MASK, 0x00, 0x00, 0x00, 0x00, 0x00, C565_5_MASK, C565_6_MASK, C565_5_MASK, 0x00, 0x00, 0x00, 0x00, 0x00 };
+
+void EmitColorIndices_Intrinsics( const byte *colorBlock, const byte *minColor, const byte *maxColor, byte *&outData )
+{
+	ALIGN16( byte color0[16] );
+	ALIGN16( byte color1[16] );
+	ALIGN16( byte color2[16] );
+	ALIGN16( byte color3[16] );
+	ALIGN16( byte result[16] );
+	
+	// mov esi, maxColor
+	// mov edi, minColor
+
+	__m128i t0, t1, t2, t3, t4, t5, t6, t7;
+
+	t7 = _mm_setzero_si128();
+	//t7 = _mm_xor_si128(t7, t7);
+	_mm_store_si128 ( (__m128i*) &result, t7 );
+
+
+	//t0 = _mm_load_si128 ( (__m128i*)  maxColor );
+	t0 = _mm_cvtsi32_si128( *(int*)maxColor);
+
+	// Bitwise AND
+	__m128i tt = _mm_load_si128 ( (__m128i*) SIMD_SSE2_byte_colorMask );
+	t0 = _mm_and_si128(t0, tt);
+
+	t0 = _mm_unpacklo_epi8(t0, t7);
+
+	t4 = _mm_shufflelo_epi16( t0, R_SHUFFLE_D( 0, 3, 2, 3 ));
+	t5 = _mm_shufflelo_epi16( t0, R_SHUFFLE_D( 3, 1, 3, 3 ));
+
+	t4 = _mm_srli_epi16(t4, 5);
+	t5 = _mm_srli_epi16(t5, 6);
+
+	// Bitwise Logical OR
+	t0 = _mm_or_si128(t0, t4);
+	t0 = _mm_or_si128(t0, t5);   // t0 contains color0 in 565
+
+
+
+
+	//t1 = _mm_load_si128 ( (__m128i*)  minColor );
+	t1 = _mm_cvtsi32_si128( *(int*)minColor);
+
+	t1 = _mm_and_si128(t1, tt);
+
+	t1 = _mm_unpacklo_epi8(t1, t7);
+
+	t4 = _mm_shufflelo_epi16( t1, R_SHUFFLE_D( 0, 3, 2, 3 ));
+	t5 = _mm_shufflelo_epi16( t1, R_SHUFFLE_D( 3, 1, 3, 3 ));
+
+	t4 = _mm_srli_epi16(t4, 5);
+	t5 = _mm_srli_epi16(t5, 6);
+
+	t1 = _mm_or_si128(t1, t4);
+	t1 = _mm_or_si128(t1, t5);  // t1 contains color1 in 565
+
+
+
+	t2 = t0;
+
+	t2 = _mm_packus_epi16(t2, t7);
+
+	t2 = _mm_shuffle_epi32( t2, R_SHUFFLE_D( 0, 1, 0, 1 ));
+
+	_mm_store_si128 ( (__m128i*) &color0, t2 );
+
+	t6 = t0;
+	t6 = _mm_add_epi16(t6, t0);
+	t6 = _mm_add_epi16(t6, t1);
+
+	// Multiply Packed Signed Integers and Store High Result
+	__m128i tw3 = _mm_load_si128 ( (__m128i*) SIMD_SSE2_word_div_by_3 );
+	t6 = _mm_mulhi_epi16(t6, tw3);
+	t6 = _mm_packus_epi16(t6, t7);
+
+	t6 = _mm_shuffle_epi32( t6, R_SHUFFLE_D( 0, 1, 0, 1 ));
+
+	_mm_store_si128 ( (__m128i*) &color2, t6 );
+
+	t3 = t1;
+	t3 = _mm_packus_epi16(t3, t7);
+	t3 = _mm_shuffle_epi32( t3, R_SHUFFLE_D( 0, 1, 0, 1 ));
+
+	_mm_store_si128 ( (__m128i*) &color1, t3 );
+
+	t1 = _mm_add_epi16(t1, t1);
+	t0 = _mm_add_epi16(t0, t1);
+
+	t0 = _mm_mulhi_epi16(t0, tw3);
+	t0 = _mm_packus_epi16(t0, t7);
+
+	t0 = _mm_shuffle_epi32( t0, R_SHUFFLE_D( 0, 1, 0, 1 ));
+	_mm_store_si128 ( (__m128i*) &color3, t0 );
+
+	__m128i w0 = _mm_load_si128 ( (__m128i*) SIMD_SSE2_word_0);
+	__m128i w1 = _mm_load_si128 ( (__m128i*) SIMD_SSE2_word_1);
+	__m128i w2 = _mm_load_si128 ( (__m128i*) SIMD_SSE2_word_2);
+
+	    // mov eax, 32
+	    // mov esi, colorBlock
+	int x = 32;
+	//const byte *c = colorBlock;
+	while (x >= 0)
+	  {
+	    t3 = _mm_loadl_epi64( (__m128i*) (colorBlock+x+0));
+	    t3 = _mm_shuffle_epi32( t3, R_SHUFFLE_D( 0, 2, 1, 3 ));
+	    
+	    t5 = _mm_loadl_epi64( (__m128i*) (colorBlock+x+8));
+	    t5 = _mm_shuffle_epi32( t5, R_SHUFFLE_D( 0, 2, 1, 3 ));
+
+	    t0 = t3;
+	    t6 = t5;
+	    // Compute Sum of Absolute Difference
+	    __m128i c0 = _mm_load_si128 ( (__m128i*)  color0 );
+	    t0 = _mm_sad_epu8(t0, c0);
+	    t6 = _mm_sad_epu8(t6, c0);
+	    // Pack with Signed Saturation 
+	    t0 = _mm_packs_epi32 (t0, t6);
+
+	    t1 = t3;
+	    t6 = t5;
+	    __m128i c1 = _mm_load_si128 ( (__m128i*)  color1 );
+	    t1 = _mm_sad_epu8(t1, c1);
+	    t6 = _mm_sad_epu8(t6, c1);
+	    t1 = _mm_packs_epi32 (t1, t6);
+
+	    t2 = t3;
+	    t6 = t5;
+	    __m128i c2 = _mm_load_si128 ( (__m128i*)  color2 );
+	    t2 = _mm_sad_epu8(t2, c2);
+	    t6 = _mm_sad_epu8(t6, c2);
+	    t2 = _mm_packs_epi32 (t2, t6);
+
+	    __m128i c3 = _mm_load_si128 ( (__m128i*)  color3 );
+	    t3 = _mm_sad_epu8(t3, c3);
+	    t5 = _mm_sad_epu8(t5, c3);
+	    t3 = _mm_packs_epi32 (t3, t5);
+
+
+	    t4 = _mm_loadl_epi64( (__m128i*) (colorBlock+x+16));
+	    t4 = _mm_shuffle_epi32( t4, R_SHUFFLE_D( 0, 2, 1, 3 ));
+	    
+	    t5 = _mm_loadl_epi64( (__m128i*) (colorBlock+x+24));
+	    t5 = _mm_shuffle_epi32( t5, R_SHUFFLE_D( 0, 2, 1, 3 ));
+
+	    t6 = t4;
+	    t7 = t5;
+	    t6 = _mm_sad_epu8(t6, c0);
+	    t7 = _mm_sad_epu8(t7, c0);
+	    t6 = _mm_packs_epi32 (t6, t7);
+	    t0 = _mm_packs_epi32 (t0, t6);  // d0
+
+	    t6 = t4;
+	    t7 = t5;
+	    t6 = _mm_sad_epu8(t6, c1);
+	    t7 = _mm_sad_epu8(t7, c1);
+	    t6 = _mm_packs_epi32 (t6, t7);
+	    t1 = _mm_packs_epi32 (t1, t6);  // d1
+
+	    t6 = t4;
+	    t7 = t5;
+	    t6 = _mm_sad_epu8(t6, c2);
+	    t7 = _mm_sad_epu8(t7, c2);
+	    t6 = _mm_packs_epi32 (t6, t7);
+	    t2 = _mm_packs_epi32 (t2, t6);  // d2
+
+	    t4 = _mm_sad_epu8(t4, c3);
+	    t5 = _mm_sad_epu8(t5, c3);
+	    t4 = _mm_packs_epi32 (t4, t5);
+	    t3 = _mm_packs_epi32 (t3, t4);  // d3
+
+	    t7 = _mm_load_si128 ( (__m128i*) result );
+
+	    t7 = _mm_slli_epi32( t7, 16);
+
+	    t4 = t0;
+	    t5 = t1;
+	    // Compare Packed Signed Integers for Greater Than
+	    t0 = _mm_cmpgt_epi16(t0, t3); // b0
+	    t1 = _mm_cmpgt_epi16(t1, t2); // b1
+	    t4 = _mm_cmpgt_epi16(t4, t2); // b2
+	    t5 = _mm_cmpgt_epi16(t5, t3); // b3
+	    t2 = _mm_cmpgt_epi16(t2, t3); // b4
+	      
+	    t4 = _mm_and_si128(t4, t1); // x0
+	    t5 = _mm_and_si128(t5, t0); // x1
+	    t2 = _mm_and_si128(t2, t0); // x2
+
+	    t4 = _mm_or_si128(t4, t5);
+	    t2 = _mm_and_si128(t2, w1);
+	    t4 = _mm_and_si128(t4, w2);
+	    t2 = _mm_or_si128(t2, t4);
+
+	    t5 = _mm_shuffle_epi32( t2, R_SHUFFLE_D( 2, 3, 0, 1 ));
+
+	    // Unpack Low Data
+	    t2 = _mm_unpacklo_epi16 ( t2, w0);
+	    t5 = _mm_unpacklo_epi16 ( t5, w0);
+
+	    //t5 = _mm_slli_si128 ( t5, 8);
+	    t5 = _mm_slli_epi32( t5, 8);
+
+	    t7 = _mm_or_si128(t7, t5);
+	    t7 = _mm_or_si128(t7, t2);
+
+	    _mm_store_si128 ( (__m128i*) &result, t7 );
+
+	    x -=32;
+	  }
+
+	t4 = _mm_shuffle_epi32( t7, R_SHUFFLE_D( 1, 2, 3, 0 ));
+	t5 = _mm_shuffle_epi32( t7, R_SHUFFLE_D( 2, 3, 0, 1 ));
+	t6 = _mm_shuffle_epi32( t7, R_SHUFFLE_D( 3, 0, 1, 2 ));
+
+	t4 = _mm_slli_epi32 ( t4, 2);
+	t5 = _mm_slli_epi32 ( t5, 4);
+	t6 = _mm_slli_epi32 ( t6, 6);
+
+	t7 = _mm_or_si128(t7, t4);
+	t7 = _mm_or_si128(t7, t5);
+	t7 = _mm_or_si128(t7, t6);
+
+	//_mm_store_si128 ( (__m128i*) outData, t7 );
+
+	int r = _mm_cvtsi128_si32 (t7);
+	memcpy(outData, &r, 4);   // Anything better ?
+
+	outData += 4;
+}
+
+
+
+ALIGN16( static byte SIMD_SSE2_byte_1[16] ) = { 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01 };
+ALIGN16( static byte SIMD_SSE2_byte_2[16] ) = { 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02 };
+ALIGN16( static byte SIMD_SSE2_byte_7[16] ) = { 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07 };
+ALIGN16( static word SIMD_SSE2_word_div_by_7[8] ) = { (1<<16)/7+1, (1<<16)/7+1, (1<<16)/7+1, (1<<16)/7+1, (1<<16)/7+1, (1<<16)/7+1, (1<<16)/7+1, (1<<16)/7+1 };
+ALIGN16( static word SIMD_SSE2_word_div_by_14[8] ) = { (1<<16)/14+1, (1<<16)/14+1, (1<<16)/14+1, (1<<16)/14+1, (1<<16)/14+1, (1<<16)/14+1, (1<<16)/14+1, (1<<16)/14+1 };
+ALIGN16( static word SIMD_SSE2_word_scale66554400[8] ) = { 6, 6, 5, 5, 4, 4, 0, 0 };
+ALIGN16( static word SIMD_SSE2_word_scale11223300[8] ) = { 1, 1, 2, 2, 3, 3, 0, 0 };
+ALIGN16( static dword SIMD_SSE2_dword_alpha_bit_mask0[4] ) = { 7<<0, 0, 7<<0, 0 };
+ALIGN16( static dword SIMD_SSE2_dword_alpha_bit_mask1[4] ) = { 7<<3, 0, 7<<3, 0 };
+ALIGN16( static dword SIMD_SSE2_dword_alpha_bit_mask2[4] ) = { 7<<6, 0, 7<<6, 0 };
+ALIGN16( static dword SIMD_SSE2_dword_alpha_bit_mask3[4] ) = { 7<<9, 0, 7<<9, 0 };
+ALIGN16( static dword SIMD_SSE2_dword_alpha_bit_mask4[4] ) = { 7<<12, 0, 7<<12, 0 };
+ALIGN16( static dword SIMD_SSE2_dword_alpha_bit_mask5[4] ) = { 7<<15, 0, 7<<15, 0 };
+ALIGN16( static dword SIMD_SSE2_dword_alpha_bit_mask6[4] ) = { 7<<18, 0, 7<<18, 0 };
+ALIGN16( static dword SIMD_SSE2_dword_alpha_bit_mask7[4] ) = { 7<<21, 0, 7<<21, 0 };
+
+
+void EmitAlphaIndices_Intrinsics( const byte *colorBlock, const byte minAlpha, const byte maxAlpha, byte *&outData)
+{
+/*
+  __asm {
+    mov esi, colorBlock
+      movdqa xmm0, [esi+ 0]
+      movdqa xmm5, [esi+16]
+      psrld xmm0, 24
+      psrld xmm5, 24
+      packuswb xmm0, xmm5
+
+      movdqa xmm6, [esi+32]
+      movdqa xmm4, [esi+48]
+      psrld xmm6, 24
+      psrld xmm4, 24
+      packuswb xmm6, xmm4
+
+      movzx ecx, maxAlpha
+      movd xmm5, ecx
+      pshuflw xmm5, xmm5, R_SHUFFLE_D( 0, 0, 0, 0 )
+      pshufd xmm5, xmm5, R_SHUFFLE_D( 0, 0, 0, 0 )
+      movdqa xmm7, xmm5
+
+      movzx edx, minAlpha
+      movd xmm2, edx
+      pshuflw xmm2, xmm2, R_SHUFFLE_D( 0, 0, 0, 0 )
+      pshufd xmm2, xmm2, R_SHUFFLE_D( 0, 0, 0, 0 )
+      movdqa xmm3, xmm2
+
+      movdqa xmm4, xmm5
+      psubw xmm4, xmm2
+      pmulhw xmm4, SIMD_SSE2_word_div_by_14    // * ( ( 1 << 16 ) / 14 + 1 ) ) >> 16
+      movdqa xmm1, xmm2
+      paddw xmm1, xmm4
+      packuswb xmm1, xmm1                      // ab1
+
+      pmullw xmm5, SIMD_SSE2_word_scale66554400
+      pmullw xmm7, SIMD_SSE2_word_scale11223300
+      pmullw xmm2, SIMD_SSE2_word_scale11223300
+      pmullw xmm3, SIMD_SSE2_word_scale66554400
+      paddw xmm5, xmm2
+      paddw xmm7, xmm3
+      pmulhw xmm5, SIMD_SSE2_word_div_by_7 // * ( ( 1 << 16 ) / 7 + 1 ) ) >> 16
+      pmulhw xmm7, SIMD_SSE2_word_div_by_7 // * ( ( 1 << 16 ) / 7 + 1 ) ) >> 16
+      paddw xmm5, xmm4
+      paddw xmm7, xmm4
+
+      pshufd xmm2, xmm5, R_SHUFFLE_D( 0, 0, 0, 0 )
+      pshufd xmm3, xmm5, R_SHUFFLE_D( 1, 1, 1, 1 )
+      pshufd xmm4, xmm5, R_SHUFFLE_D( 2, 2, 2, 2 )
+      packuswb xmm2, xmm2 // ab2
+      packuswb xmm3, xmm3 // ab3
+      packuswb xmm4, xmm4 // ab4
+
+      packuswb xmm0, xmm6 // alpha values
+
+      pshufd xmm5, xmm7, R_SHUFFLE_D( 2, 2, 2, 2 )
+      pshufd xmm6, xmm7, R_SHUFFLE_D( 1, 1, 1, 1 )
+      pshufd xmm7, xmm7, R_SHUFFLE_D( 0, 0, 0, 0 )
+      packuswb xmm5, xmm5 // ab5
+      packuswb xmm6, xmm6 // ab6
+      packuswb xmm7, xmm7 // ab7
+
+      pminub xmm1, xmm0
+      pminub xmm2, xmm0
+      pminub xmm3, xmm0
+      pcmpeqb xmm1, xmm0
+      pcmpeqb xmm2, xmm0
+      pcmpeqb xmm3, xmm0
+      pminub xmm4, xmm0
+      pminub xmm5, xmm0
+      pminub xmm6, xmm0
+      pminub xmm7, xmm0
+      pcmpeqb xmm4, xmm0
+      pcmpeqb xmm5, xmm0
+      pcmpeqb xmm6, xmm0
+      pcmpeqb xmm7, xmm0
+      pand xmm1, SIMD_SSE2_byte_1
+      pand xmm2, SIMD_SSE2_byte_1
+      pand xmm3, SIMD_SSE2_byte_1
+      pand xmm4, SIMD_SSE2_byte_1
+      pand xmm5, SIMD_SSE2_byte_1
+      pand xmm6, SIMD_SSE2_byte_1
+      pand xmm7, SIMD_SSE2_byte_1
+      movdqa xmm0, SIMD_SSE2_byte_1
+      paddusb xmm0, xmm1
+      paddusb xmm2, xmm3
+      paddusb xmm4, xmm5
+      paddusb xmm6, xmm7
+      paddusb xmm0, xmm2
+      paddusb xmm4, xmm6
+      paddusb xmm0, xmm4
+      pand xmm0, SIMD_SSE2_byte_7
+      movdqa xmm1, SIMD_SSE2_byte_2
+      pcmpgtb xmm1, xmm0
+      pand xmm1, SIMD_SSE2_byte_1
+      pxor xmm0, xmm1
+      movdqa xmm1, xmm0
+      movdqa xmm2, xmm0
+      movdqa xmm3, xmm0
+      movdqa xmm4, xmm0
+      movdqa xmm5, xmm0
+      movdqa xmm6, xmm0
+      movdqa xmm7, xmm0
+      psrlq xmm1, 8- 3
+      psrlq xmm2, 16- 6
+      psrlq xmm3, 24- 9
+
+      psrlq xmm4, 32-12
+      psrlq xmm5, 40-15
+      psrlq xmm6, 48-18
+      psrlq xmm7, 56-21
+      pand xmm0, SIMD_SSE2_dword_alpha_bit_mask0
+      pand xmm1, SIMD_SSE2_dword_alpha_bit_mask1
+      pand xmm2, SIMD_SSE2_dword_alpha_bit_mask2
+      pand xmm3, SIMD_SSE2_dword_alpha_bit_mask3
+      pand xmm4, SIMD_SSE2_dword_alpha_bit_mask4
+      pand xmm5, SIMD_SSE2_dword_alpha_bit_mask5
+      pand xmm6, SIMD_SSE2_dword_alpha_bit_mask6
+      pand xmm7, SIMD_SSE2_dword_alpha_bit_mask7
+      por xmm0, xmm1
+      por xmm2, xmm3
+      por xmm4, xmm5
+      por xmm6, xmm7
+      por xmm0, xmm2
+      por xmm4, xmm6
+      por xmm0, xmm4
+      mov esi, outData
+      movd [esi+0], xmm0
+      pshufd xmm1, xmm0, R_SHUFFLE_D( 2, 3, 0, 1 )
+      movd [esi+3], xmm1
+      }
+  outData += 6;
+*/
+}
+
diff --git a/src/osgEarthDrivers/fastdxt/libdxt.cpp b/src/osgEarthDrivers/fastdxt/libdxt.cpp
new file mode 100644
index 0000000..7187777
--- /dev/null
+++ b/src/osgEarthDrivers/fastdxt/libdxt.cpp
@@ -0,0 +1,95 @@
+/******************************************************************************
+ * Fast DXT - a realtime DXT compression tool
+ *
+ * Author : Luc Renambot
+ *
+ * Copyright (C) 2007 Electronic Visualization Laboratory,
+ * University of Illinois at Chicago
+ *
+ * This library 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.1 of the License, or
+ * (at your option) any later version.
+ *
+ * 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 GNU Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser Public License along
+ * with this library; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ *****************************************************************************/
+
+#include "libdxt.h"
+
+#if defined(__APPLE__)
+#define memalign(x,y) malloc((y))
+#else
+#include <malloc.h>
+#endif
+
+typedef struct _work_t {
+	int width, height;
+	int nbb;
+	byte *in, *out;
+} work_t;
+
+
+
+void *slave1(void *arg)
+{
+	work_t *param = (work_t*) arg;
+	int nbbytes = 0;
+	CompressImageDXT1( param->in, param->out, param->width, param->height, nbbytes);
+	param->nbb = nbbytes;
+	return NULL;
+}
+
+void *slave5(void *arg)
+{
+	work_t *param = (work_t*) arg;
+	int nbbytes = 0;
+	CompressImageDXT5( param->in, param->out, param->width, param->height, nbbytes);
+	param->nbb = nbbytes;	
+	return NULL;
+}
+
+void *slave5ycocg(void *arg)
+{
+	work_t *param = (work_t*) arg;
+	int nbbytes = 0;
+	CompressImageDXT5YCoCg( param->in, param->out, param->width, param->height, nbbytes);
+	param->nbb = nbbytes;
+	return NULL;
+}
+
+int CompressDXT(const byte *in, byte *out, int width, int height, int format)
+{ 
+  int        nbbytes;
+
+  work_t job;
+
+  job.width = width;
+  job.height = height;
+  job.nbb = 0;
+  job.in =  (byte*)in;
+  job.out = out;
+  
+  switch (format) {
+      case FORMAT_DXT1:
+          slave1(&job);
+          break;
+      case FORMAT_DXT5:
+          slave5(&job);
+          break;
+      case FORMAT_DXT5YCOCG:
+          slave5ycocg(&job);
+          break;
+  }
+
+  // Join all the threads
+  nbbytes = job.nbb;
+  return nbbytes;
+}
\ No newline at end of file
diff --git a/src/osgEarthDrivers/fastdxt/libdxt.h b/src/osgEarthDrivers/fastdxt/libdxt.h
new file mode 100644
index 0000000..5728d8e
--- /dev/null
+++ b/src/osgEarthDrivers/fastdxt/libdxt.h
@@ -0,0 +1,35 @@
+/******************************************************************************
+ * Fast DXT - a realtime DXT compression tool
+ *
+ * Author : Luc Renambot
+ *
+ * Copyright (C) 2007 Electronic Visualization Laboratory,
+ * University of Illinois at Chicago
+ *
+ * This library 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.1 of the License, or
+ * (at your option) any later version.
+ *
+ * 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 GNU Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser Public License along
+ * with this library; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ *****************************************************************************/
+
+#include "dxt.h"
+#include "util.h"
+
+#define FORMAT_DXT1      1
+#define FORMAT_DXT5      2
+#define FORMAT_DXT5YCOCG 3
+
+
+int CompressDXT(const byte *in, byte *out, int width, int height, int format);
+
+
diff --git a/src/osgEarthDrivers/fastdxt/util.cpp b/src/osgEarthDrivers/fastdxt/util.cpp
new file mode 100644
index 0000000..9a53069
--- /dev/null
+++ b/src/osgEarthDrivers/fastdxt/util.cpp
@@ -0,0 +1,342 @@
+/******************************************************************************
+ * Fast DXT - a realtime DXT compression tool
+ *
+ * Author : Luc Renambot
+ *
+ * Copyright (C) 2007 Electronic Visualization Laboratory,
+ * University of Illinois at Chicago
+ *
+ * This library 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.1 of the License, or
+ * (at your option) any later version.
+ *
+ * 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 GNU Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser Public License along
+ * with this library; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ *****************************************************************************/
+
+#include "util.h"
+
+
+#if defined(WIN32)
+#include <io.h>
+LARGE_INTEGER perf_freq;
+LARGE_INTEGER perf_start;
+HANDLE win_err; // stderr in a console
+#elif defined(__APPLE__)
+#include <mach/mach_time.h>
+static double perf_conversion = 0.0;
+static uint64_t perf_start;
+#else
+struct timeval tv_start;
+#endif
+
+#if !defined(WIN32)
+#include <sys/stat.h>
+#include <unistd.h>
+#endif
+
+#include <fcntl.h>
+
+
+void aInitialize()
+{
+#if defined(WIN32)
+	QueryPerformanceCounter(&perf_start);
+	QueryPerformanceFrequency(&perf_freq);
+	AllocConsole();
+	win_err =  GetStdHandle(STD_ERROR_HANDLE);
+#elif defined(__APPLE__)
+	if( perf_conversion == 0.0 )
+	{
+		mach_timebase_info_data_t info;
+		kern_return_t err = mach_timebase_info( &info );
+
+		//Convert the timebase into seconds
+		if( err == 0  )
+			perf_conversion = 1e-9 * (double) info.numer / (double) info.denom;
+	}
+		// Start of time
+	perf_start = mach_absolute_time();
+
+		// Initialize the random generator
+	srand(getpid());
+#else
+		// Start of time
+	gettimeofday(&tv_start,0);
+		// Initialize the random generator
+	srand(getpid());
+#endif
+}
+
+double aTime()
+// return time since start of process in seconds
+{
+#if defined(WIN32)
+    LARGE_INTEGER perf_counter;
+#else
+    struct timeval tv;
+#endif
+
+#if defined(WIN32)
+        // Windows: get performance counter and subtract starting mark
+	QueryPerformanceCounter(&perf_counter);
+	return (double)(perf_counter.QuadPart - perf_start.QuadPart) / (double)perf_freq.QuadPart;
+#elif defined(__APPLE__)
+    uint64_t difference = mach_absolute_time() - perf_start;
+    return perf_conversion * (double) difference;
+#else
+        // UNIX: gettimeofday
+	gettimeofday(&tv,0);
+	return (double)(tv.tv_sec - tv_start.tv_sec) + (double)(tv.tv_usec - tv_start.tv_usec) / 1000000.0;
+#endif
+}
+
+
+
+void aLog(char* format,...)
+{
+	va_list vl;
+	char line[2048];
+
+	va_start(vl,format);
+	vsprintf(line,format,vl);
+	va_end(vl);
+
+#if defined(WIN32)
+	DWORD res;
+	WriteFile(win_err, line, (DWORD)strlen(line), &res, NULL);
+#endif
+	fprintf(stderr,"%s",line);
+	fflush(stderr);
+}
+
+void aError(char* format,...)
+{
+	va_list vl;
+	char line[2048];
+
+	va_start(vl,format);
+	vsprintf(line,format,vl);
+	va_end(vl);
+
+#if defined(WIN32)
+	DWORD res;
+	WriteFile(win_err, line, (DWORD)strlen(line), &res, NULL);
+#endif
+	fprintf(stderr,"%s",line);
+	fflush(stderr);
+
+    exit(1);
+}
+
+
+
+void* aAlloc(size_t const n)
+{
+	void* result;
+#if defined(WIN32)
+	result = LocalAlloc(0,n);
+#else
+	result = malloc(n);
+#endif
+	// Filling with zeros
+	memset(result, 0, n);
+
+	if(!result)
+		aError("Aura: not enough memory for %d bytes",n);
+	return result;
+}
+
+void aFree(void* const p)
+{
+#if defined(WIN32)
+	LocalFree(p);
+#else
+	if (p)
+        free(p);
+    else
+        aError("Alloc> Trying to free a NULL pointer\n");
+#endif
+}
+
+#if defined(WIN32)
+float
+drand48(void)
+{
+	return (((float) rand()) / RAND_MAX);
+}
+#endif
+
+
+void *aligned_malloc(size_t size, size_t align_size) {
+
+  char *ptr,*ptr2,*aligned_ptr;
+  int align_mask = (int)align_size - 1;
+
+  ptr=(char *)malloc(size + align_size + sizeof(int));
+  if(ptr==NULL) return(NULL);
+
+  ptr2 = ptr + sizeof(int);
+  aligned_ptr = ptr2 + (align_size - ((size_t)ptr2 & align_mask));
+
+
+  ptr2 = aligned_ptr - sizeof(int);
+  *((int *)ptr2)=(int)(aligned_ptr - ptr);
+
+  return(aligned_ptr);
+}
+
+void aligned_free(void *ptr)
+{
+	int *ptr2=(int *)ptr - 1;
+	ptr = (char*)ptr - *ptr2;
+	free(ptr);
+}
+
+
+/// File findind
+// From Nvidia toolkit
+
+using namespace std;
+
+string data_path::get_path(std::string filename)
+{
+  FILE* fp;
+  bool found = false;
+  for(unsigned int i=0; i < path.size(); i++)
+    {
+      path_name = path[i] + "/" + filename;
+      fp = ::fopen(path_name.c_str(), "r");
+
+      if(fp != 0)
+        {     
+	  fclose(fp);
+	  found = true;
+	  break;
+        }
+    }
+  
+  if (found == false)
+    {
+      path_name = filename;
+      fp = ::fopen(path_name.c_str(),"r");
+      if (fp != 0)
+        {
+	  fclose(fp);
+	  found = true;
+        }
+    }
+  
+  if (found == false)
+    return "";
+  
+  int loc = path_name.rfind('\\');
+  if (loc == -1)
+    {
+      loc = path_name.rfind('/');
+    }
+  
+  if (loc != -1)
+    file_path = path_name.substr(0, loc);
+  else
+    file_path = ".";
+  return file_path;
+}
+
+string data_path::get_file(std::string filename)
+{
+  FILE* fp;
+  
+  for(unsigned int i=0; i < path.size(); i++)
+    {
+      path_name = path[i] + "/" + filename;
+      fp = ::fopen(path_name.c_str(), "r");
+      
+      if(fp != 0)
+        {     
+	  fclose(fp);
+	  return path_name;
+        }
+    }
+  
+  path_name = filename;
+  fp = ::fopen(path_name.c_str(),"r");
+  if (fp != 0)
+    {
+      fclose(fp);
+      return path_name;
+    }
+  return "";
+}
+
+// data files, for read only
+FILE * data_path::fopen(std::string filename, const char * mode)
+{
+  
+  for(unsigned int i=0; i < path.size(); i++)
+    {
+      std::string s = path[i] + "/" + filename;
+      FILE * fp = ::fopen(s.c_str(), mode);
+      
+      if(fp != 0)
+	return fp;
+      else if (!strcmp(path[i].c_str(),""))
+	{
+	  FILE* fp = ::fopen(filename.c_str(),mode);
+	  if (fp != 0)
+	    return fp;
+	}
+    }
+  // no luck... return null
+  return 0;
+}
+
+//  fill the file stats structure 
+//  useful to get the file size and stuff
+int data_path::fstat(std::string filename, 
+#ifdef WIN32
+		     struct _stat 
+#else
+		     struct stat
+#endif
+		     * stat)
+{
+  for(unsigned int i=0; i < path.size(); i++)
+    {
+      std::string s = path[i] + "/" + filename;
+#ifdef WIN32
+      int fh = ::_open(s.c_str(), _O_RDONLY);
+#else
+      int fh = ::open(s.c_str(), O_RDONLY);
+#endif
+      if(fh >= 0)
+        {
+#ifdef WIN32
+	  int result = ::_fstat( fh, stat );
+#else
+	  int result = ::fstat (fh,stat);
+#endif
+	  if( result != 0 )
+            {
+	      fprintf( stderr, "An fstat error occurred.\n" );
+	      return 0;
+            }
+#ifdef WIN32
+	  ::_close( fh );
+#else
+	  ::close (fh);
+#endif
+	  return 1;
+    	}
+    }
+  // no luck...
+  return 0;
+}
diff --git a/src/osgEarthDrivers/fastdxt/util.h b/src/osgEarthDrivers/fastdxt/util.h
new file mode 100644
index 0000000..5b5ded1
--- /dev/null
+++ b/src/osgEarthDrivers/fastdxt/util.h
@@ -0,0 +1,108 @@
+/******************************************************************************
+ * Fast DXT - a realtime DXT compression tool
+ *
+ * Author : Luc Renambot
+ *
+ * Copyright (C) 2007 Electronic Visualization Laboratory,
+ * University of Illinois at Chicago
+ *
+ * This library 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.1 of the License, or
+ * (at your option) any later version.
+ *
+ * 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 GNU Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser Public License along
+ * with this library; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ *****************************************************************************/
+
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <vector>
+#include <string>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#if !defined(WIN32)
+#include <unistd.h>
+#include <sys/time.h>
+#else
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+#pragma warning(disable:4786)   // symbol size limitation ... STL
+#endif
+
+
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <fcntl.h>
+
+#include <stdarg.h>
+//#include <values.h>
+
+
+void   aInitialize();
+
+double aTime();
+
+void   aLog(char* format,...);
+void aError(char* format,...);
+
+void* aAlloc(size_t const n);
+void aFree(void* const p);
+
+#if defined(WIN32)
+float drand48(void);
+#endif
+
+#if defined(__APPLE__)
+#define memalign(x,y) malloc((y))
+#else
+#include <malloc.h>
+#endif
+
+
+#if defined(WIN32)
+void *aligned_malloc(size_t size, size_t align_size);
+#define memalign(x,y) aligned_malloc(y, x)
+void aligned_free(void *ptr);
+#define memfree(x) aligned_free(x)
+#else
+#define memfree(x) free(x)
+#endif
+
+
+// From NVIDIA Toolkit
+
+#ifndef DATA_PATH_H
+#define DATA_PATH_H
+
+class data_path
+{
+public:
+  std::string              file_path;
+  std::string              path_name;
+  std::vector<std::string> path;
+  
+  std::string get_path(std::string filename);
+  std::string get_file(std::string filename);
+  
+  FILE *fopen(std::string filename, const char * mode = "rb");
+    
+#ifdef WIN32
+  int fstat(std::string filename, struct _stat * stat);
+#else
+  int fstat(std::string filename, struct stat * stat);
+#endif
+};
+
+#endif
diff --git a/src/osgEarthDrivers/feature_elevation/CMakeLists.txt b/src/osgEarthDrivers/feature_elevation/CMakeLists.txt
new file mode 100644
index 0000000..286bcdd
--- /dev/null
+++ b/src/osgEarthDrivers/feature_elevation/CMakeLists.txt
@@ -0,0 +1,17 @@
+SET(TARGET_COMMON_LIBRARIES ${TARGET_COMMON_LIBRARIES} osgEarthFeatures osgEarthUtil)
+
+SET(TARGET_SRC
+    ReaderWriterFeatureElevation.cpp
+)
+SET(TARGET_H
+    FeatureElevationOptions
+)
+
+SETUP_PLUGIN(osgearth_feature_elevation)
+
+
+# to install public driver includes:
+SET(LIB_NAME feature_elevation)
+SET(LIB_PUBLIC_HEADERS FeatureElevationOptions)
+INCLUDE(ModuleInstallOsgEarthDriverIncludes OPTIONAL)
+
diff --git a/src/osgEarthDrivers/feature_elevation/FeatureElevationOptions b/src/osgEarthDrivers/feature_elevation/FeatureElevationOptions
new file mode 100644
index 0000000..619e1fd
--- /dev/null
+++ b/src/osgEarthDrivers/feature_elevation/FeatureElevationOptions
@@ -0,0 +1,85 @@
+/* -*-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.
+ *
+ * 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_FEATURE_ELEVATION_DRIVEROPTIONS
+#define OSGEARTH_DRIVER_FEATURE_ELEVATION_DRIVEROPTIONS 1
+
+#include <osgEarth/Common>
+#include <osgEarth/TileSource>
+#include <osgEarthFeatures/FeatureSource>
+
+
+namespace osgEarth { namespace Drivers
+{
+    using namespace osgEarth;
+    using namespace osgEarth::Features;
+
+    class FeatureElevationOptions : public TileSourceOptions // NO EXPORT; header only
+    {
+    public: // properties
+
+        /** feature attribute containing the elevation value */
+        optional<std::string>& attr() { return _attr; }
+        const optional<std::string>& attr() const { return _attr; }
+
+        /** Feature source from which to read the feature data */
+        optional<FeatureSourceOptions>& featureOptions() { return _featureOptions; }
+        const optional<FeatureSourceOptions>& featureOptions() const { return _featureOptions; }
+
+    public: // ctors
+
+        FeatureElevationOptions( const TileSourceOptions& options =TileSourceOptions() ) :
+            TileSourceOptions( options ),
+            _attr( "ELEVATION" )
+        {
+            setDriver( "feature_elevation" );
+            fromConfig( _conf );
+        }
+
+        virtual ~FeatureElevationOptions() { }
+
+    public:
+
+        Config getConfig() const
+        {
+            Config conf = TileSourceOptions::getConfig();
+            conf.updateIfSet( "attr", _attr );
+            conf.updateObjIfSet( "features", _featureOptions );
+
+            return conf;
+        }
+
+        void mergeConfig( const Config& conf ) {
+            TileSourceOptions::mergeConfig( conf );
+            fromConfig( conf );
+        }
+
+        void fromConfig( const Config& conf ) {
+            conf.getIfSet( "attr", _attr );
+
+            if ( conf.hasChild("features") )
+                _featureOptions->merge( conf.child("features") );
+        }
+
+        optional<FeatureSourceOptions>  _featureOptions;
+        optional<std::string>           _attr;
+    };
+
+} } // namespace osgEarth::Drivers
+
+#endif // OSGEARTH_DRIVER_FEATURE_ELEVATION_DRIVEROPTIONS
diff --git a/src/osgEarthDrivers/feature_elevation/ReaderWriterFeatureElevation.cpp b/src/osgEarthDrivers/feature_elevation/ReaderWriterFeatureElevation.cpp
new file mode 100644
index 0000000..9f0c091
--- /dev/null
+++ b/src/osgEarthDrivers/feature_elevation/ReaderWriterFeatureElevation.cpp
@@ -0,0 +1,269 @@
+/* -*-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/TileSource>
+#include <osgEarth/FileUtils>
+#include <osgEarth/Registry>
+#include <osgEarth/ImageUtils>
+#include <osgEarth/URI>
+#include <osgEarth/HeightFieldUtils>
+
+#include <osgEarthFeatures/TransformFilter>
+
+#include <osgDB/FileNameUtils>
+#include <osgDB/FileUtils>
+#include <osgDB/Registry>
+#include <osgDB/ReadFile>
+#include <osgDB/WriteFile>
+#include <osgDB/ImageOptions>
+
+#include <sstream>
+#include <stdlib.h>
+#include <memory.h>
+
+#include "FeatureElevationOptions"
+
+#define LC "[Featuer Elevation driver] "
+
+using namespace std;
+using namespace osgEarth;
+using namespace osgEarth::Drivers;
+
+
+class FeatureElevationTileSource : public TileSource
+{
+public:
+    FeatureElevationTileSource( const TileSourceOptions& options ) :
+      TileSource( options ),
+      _options(options),
+      _maxDataLevel(30)
+    {
+    }
+
+    virtual ~FeatureElevationTileSource() { }
+
+
+    Status initialize( const osgDB::Options* dbOptions )
+    {
+        Cache* cache = 0;
+
+        _dbOptions = Registry::instance()->cloneOrCreateOptions( dbOptions );
+
+        if ( _dbOptions.valid() )
+        {
+            // Set up a Custom caching bin for this TileSource
+            cache = Cache::get( _dbOptions.get() );
+            if ( cache )
+            {
+                Config optionsConf = _options.getConfig();
+
+                std::string binId = Stringify() << std::hex << hashString(optionsConf.toJSON());
+                _cacheBin = cache->addBin( binId );
+
+                if ( _cacheBin.valid() )
+                {
+                    _cacheBin->apply( _dbOptions.get() );
+                }
+            }
+        }
+
+
+        if ( !_options.featureOptions().isSet() )
+        {
+            return Status::Error( Stringify() << LC << "Illegal: feature source is required" );
+        }
+    
+        _features = FeatureSourceFactory::create( _options.featureOptions().value() );
+        if ( !_features.valid() )
+        {
+            return Status::Error( Stringify() << "Illegal: no valid feature source provided");
+        }
+
+        //if ( _features->getGeometryType() != osgEarth::Symbology::Geometry::TYPE_POLYGON )
+        //{
+        //    Status::Error( Stringify() << "Illegal: only polygon features are currently supported");
+        //    return false;
+        //}
+
+        _features->initialize( _dbOptions );
+
+        // populate feature list
+        osg::ref_ptr<FeatureCursor> cursor = _features->createFeatureCursor();
+        while ( cursor.valid() && cursor->hasMore() )
+        {
+            Feature* f = cursor->nextFeature();
+            if ( f && f->getGeometry() )
+                _featureList.push_back(f);
+        }
+
+        if (_features->getFeatureProfile())
+        {
+            if (getProfile() && !getProfile()->getSRS()->isEquivalentTo(_features->getFeatureProfile()->getSRS()))
+                OE_WARN << LC << "Specified profile does not match feature profile, ignoring specified profile." << std::endl;
+
+            _extents = _features->getFeatureProfile()->getExtent();
+
+            const Profile* profile = Profile::create(
+                _extents.getSRS(),
+                _extents.bounds().xMin(),
+                _extents.bounds().yMin(),
+                _extents.bounds().xMax(),
+                _extents.bounds().yMax());
+
+            setProfile( profile );
+        }
+        else if (getProfile())
+        {
+            _extents = getProfile()->getExtent();
+        }
+        else
+        {
+            return Status::Error( Stringify() << "Failed to establish a profile for " <<  this->getName() );
+        }
+
+        getDataExtents().push_back( DataExtent(_extents, 0, _maxDataLevel) );
+
+        return STATUS_OK;
+    }
+
+
+    osg::Image* createImage( const TileKey&        key,
+                             ProgressCallback*     progress)
+    {
+        return 0L;
+    }
+
+
+    osg::HeightField* createHeightField( const TileKey&        key,
+                                         ProgressCallback*     progress)
+    {
+        if (key.getLevelOfDetail() > _maxDataLevel)
+        {
+            //OE_NOTICE << "Reached maximum data resolution key=" << key.getLevelOfDetail() << " max=" << _maxDataLevel <<  std::endl;
+            return NULL;
+        }
+
+        int tileSize = _options.tileSize().value();
+
+        //Allocate the heightfield
+        osg::ref_ptr<osg::HeightField> hf = new osg::HeightField;
+        hf->allocate(tileSize, tileSize);
+        for (unsigned int i = 0; i < hf->getHeightList().size(); ++i) hf->getHeightList()[i] = NO_DATA_VALUE;
+
+        if (intersects(key))
+        {
+            //Get the extents of the tile
+            double xmin, ymin, xmax, ymax;
+            key.getExtent().getBounds(xmin, ymin, xmax, ymax);
+
+            // Iterate over the output heightfield and sample the data that was read into it.
+            double dx = (xmax - xmin) / (tileSize-1);
+            double dy = (ymax - ymin) / (tileSize-1);
+
+            for (int c = 0; c < tileSize; ++c)
+            {
+                double geoX = xmin + (dx * (double)c);
+                for (int r = 0; r < tileSize; ++r)
+                {
+                    double geoY = ymin + (dy * (double)r);
+
+                    float h = NO_DATA_VALUE;
+
+
+                    for (FeatureList::iterator f = _featureList.begin(); f != _featureList.end(); ++f)
+                    {
+                        osgEarth::Symbology::Polygon* p = dynamic_cast<osgEarth::Symbology::Polygon*>((*f)->getGeometry());
+
+                        if (!p)
+                        {
+                            OE_WARN << LC << "NOT A POLYGON" << std::endl;
+                        }
+                        else
+                        {
+                            GeoPoint geo(key.getProfile()->getSRS(), geoX, geoY);
+                            if (!key.getProfile()->getSRS()->isEquivalentTo(getProfile()->getSRS()))
+                                geo.transform(getProfile()->getSRS());
+                            
+                            if (p->contains2D(geo.x(), geo.y()))
+                            {
+                                h = (*f)->getDouble(_options.attr().value());
+                                break;
+                            }
+                        }
+                    }
+
+                    hf->setHeight(c, r, h);
+                }
+            }
+        }
+        return hf.release();
+    }
+
+
+    bool intersects(const TileKey& key)
+    {
+        return key.getExtent().intersects( _extents );
+    }
+
+
+private:
+
+    GeoExtent _extents;
+
+    const FeatureElevationOptions _options;
+
+    osg::ref_ptr<FeatureSource> _features;
+    FeatureList _featureList;
+
+    osg::ref_ptr< CacheBin > _cacheBin;
+    osg::ref_ptr< osgDB::Options > _dbOptions;
+
+    unsigned int _maxDataLevel;
+};
+
+
+class ReaderWriterFeatureElevationTile : public TileSourceDriver
+{
+public:
+    ReaderWriterFeatureElevationTile() {}
+
+    virtual const char* className()
+    {
+        return "Feature Elevation Tile Reader";
+    }
+
+    virtual bool acceptsExtension(const std::string& extension) const
+    {
+        return osgDB::equalCaseInsensitive( extension, "osgearth_feature_elevation" );
+    }
+
+    virtual ReadResult readObject(const std::string& file_name, const Options* opt) const
+    {
+        if ( !acceptsExtension( osgDB::getFileExtension( file_name ) ) )
+        {
+            return ReadResult::FILE_NOT_HANDLED;
+        }
+        return new FeatureElevationTileSource( getTileSourceOptions(opt) );
+    }
+};
+
+REGISTER_OSGPLUGIN(osgearth_feature_elevation, ReaderWriterFeatureElevationTile)
diff --git a/src/osgEarthDrivers/feature_ogr/FeatureCursorOGR b/src/osgEarthDrivers/feature_ogr/FeatureCursorOGR
index fd241e9..91b682d 100644
--- a/src/osgEarthDrivers/feature_ogr/FeatureCursorOGR
+++ b/src/osgEarthDrivers/feature_ogr/FeatureCursorOGR
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/feature_ogr/FeatureCursorOGR.cpp b/src/osgEarthDrivers/feature_ogr/FeatureCursorOGR.cpp
index 92e87c9..aeab6db 100644
--- a/src/osgEarthDrivers/feature_ogr/FeatureCursorOGR.cpp
+++ b/src/osgEarthDrivers/feature_ogr/FeatureCursorOGR.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -62,12 +62,12 @@ namespace
 }
 
 
-FeatureCursorOGR::FeatureCursorOGR(OGRDataSourceH           dsHandle,
-                                   OGRLayerH                layerHandle,
-                                   const FeatureSource*     source,
-                                   const FeatureProfile*    profile,
-                                   const Symbology::Query&  query,
-                                   const FeatureFilterList& filters ) :
+FeatureCursorOGR::FeatureCursorOGR(OGRDataSourceH              dsHandle,
+                                   OGRLayerH                   layerHandle,
+                                   const FeatureSource*        source,
+                                   const FeatureProfile*       profile,
+                                   const Symbology::Query&     query,
+                                   const FeatureFilterList&    filters) :
 _source           ( source ),
 _dsHandle         ( dsHandle ),
 _layerHandle      ( layerHandle ),
@@ -91,12 +91,7 @@ _filters          ( filters )
         // Or quote any layers containing spaces for PostgreSQL
         if (driverName == "ESRI Shapefile" || from.find(" ") != std::string::npos)
         {                        
-            std::string delim = "'";  //Use single quotes by default
-            if (driverName.compare("PostgreSQL") == 0)
-            {
-                //PostgreSQL uses double quotes as identifier delimeters
-                delim = "\"";
-            }            
+            std::string delim = "\"";
             from = delim + from + delim;                    
         }
 
@@ -227,7 +222,7 @@ FeatureCursorOGR::readChunk()
 
     if ( _nextHandleToQueue )
     {
-        osg::ref_ptr<Feature> f = OgrUtils::createFeature( _nextHandleToQueue, _profile->getSRS() );
+        osg::ref_ptr<Feature> f = OgrUtils::createFeature( _nextHandleToQueue, _profile.get() );
         if ( f.valid() && !_source->isBlacklisted(f->getFID()) )
         {
             if ( isGeometryValid( f->getGeometry() ) )
@@ -235,11 +230,13 @@ FeatureCursorOGR::readChunk()
                 _queue.push( f );
 
                 if ( _filters.size() > 0 )
+                {
                     preProcessList.push_back( f.release() );
+                }
             }
             else
             {
-                OE_INFO << LC << "Skipping feature with invalid geometry: " << f->getGeoJSON() << std::endl;
+                OE_DEBUG << LC << "Skipping feature with invalid geometry: " << f->getGeoJSON() << std::endl;
             }
         }
         OGR_F_Destroy( _nextHandleToQueue );
@@ -254,7 +251,7 @@ FeatureCursorOGR::readChunk()
         OGRFeatureH handle = OGR_L_GetNextFeature( _resultSetHandle );
         if ( handle )
         {
-            osg::ref_ptr<Feature> f = OgrUtils::createFeature( handle, _profile->getSRS() );
+            osg::ref_ptr<Feature> f = OgrUtils::createFeature( handle, _profile.get() );
             if ( f.valid() && !_source->isBlacklisted(f->getFID()) )
             {
                 if (isGeometryValid( f->getGeometry() ) )
@@ -262,11 +259,13 @@ FeatureCursorOGR::readChunk()
                     _queue.push( f );
 
                     if ( _filters.size() > 0 )
+                    {
                         preProcessList.push_back( f.release() );
+                    }
                 }
                 else
                 {
-                    OE_INFO << LC << "Skipping feature with invalid geometry: " << f->getGeoJSON() << std::endl;
+                    OE_DEBUG << LC << "Skipping feature with invalid geometry: " << f->getGeoJSON() << std::endl;
                 }
             }            
             OGR_F_Destroy( handle );
diff --git a/src/osgEarthDrivers/feature_ogr/FeatureSourceOGR.cpp b/src/osgEarthDrivers/feature_ogr/FeatureSourceOGR.cpp
index 15cc81a..3a5826f 100644
--- a/src/osgEarthDrivers/feature_ogr/FeatureSourceOGR.cpp
+++ b/src/osgEarthDrivers/feature_ogr/FeatureSourceOGR.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -113,6 +113,11 @@ public:
         if ( _options.url().isSet() )
         {
             _source = _options.url()->full();
+            if (osgEarth::endsWith(_source, ".zip", false) ||
+                _source.find(".zip/") != std::string::npos)
+            {
+                _source = Stringify() << "/vsizip/" << _source;
+            }
         }
         else if ( _options.connection().isSet() )
         {
@@ -204,9 +209,15 @@ public:
                                 if ( OGR_L_GetExtent( _layerHandle, &env, 1 ) == OGRERR_NONE )
                                 {
                                     GeoExtent extent( srs.get(), env.MinX, env.MinY, env.MaxX, env.MaxY );
-                                    
-                                    // got enough info to make the profile!
-                                    result = new FeatureProfile( extent );
+                                    if ( extent.isValid() )
+                                    {                                    
+                                        // got enough info to make the profile!
+                                        result = new FeatureProfile( extent );
+                                    }
+                                    else
+                                    {
+                                        OE_WARN << LC << "Extent returned from OGR was invalid.\n";
+                                    }
                                 }
                             }
                         }
@@ -286,6 +297,14 @@ public:
                 << "Feature Source: no valid source data available" << std::endl;
         }
 
+        if ( result )
+        {
+            if ( _options.geoInterp().isSet() )
+            {
+                result->geoInterp() = _options.geoInterp().get();
+            }
+        }
+
         return result;
     }
 
@@ -326,7 +345,7 @@ public:
                     layerHandle, 
                     this,
                     getFeatureProfile(),
-                    query, 
+                    query,
                     _options.filters() );
             }
             else
@@ -361,6 +380,11 @@ public:
         return _featureCount;
     }
 
+    bool supportsGetFeature() const
+    {
+        return true;
+    }
+
     virtual Feature* getFeature( FeatureID fid )
     {
         Feature* result = NULL;
@@ -371,9 +395,7 @@ public:
             OGRFeatureH handle = OGR_L_GetFeature( _layerHandle, fid);
             if (handle)
             {
-                const FeatureProfile* p = getFeatureProfile();
-                const SpatialReference* srs = p ? p->getSRS() : 0L;
-                result = OgrUtils::createFeature( handle, srs );
+                result = OgrUtils::createFeature( handle, getFeatureProfile() );
                 OGR_F_Destroy( handle );
             }
         }
diff --git a/src/osgEarthDrivers/feature_ogr/OGRFeatureOptions b/src/osgEarthDrivers/feature_ogr/OGRFeatureOptions
index 5889715..1b42972 100644
--- a/src/osgEarthDrivers/feature_ogr/OGRFeatureOptions
+++ b/src/osgEarthDrivers/feature_ogr/OGRFeatureOptions
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/feature_raster/CMakeLists.txt b/src/osgEarthDrivers/feature_raster/CMakeLists.txt
new file mode 100644
index 0000000..8b1610d
--- /dev/null
+++ b/src/osgEarthDrivers/feature_raster/CMakeLists.txt
@@ -0,0 +1,15 @@
+SET(TARGET_SRC
+    FeatureSourceRaster.cpp    
+)
+
+SET(TARGET_H   	
+    RasterFeatureOptions
+)
+
+SET(TARGET_COMMON_LIBRARIES ${TARGET_COMMON_LIBRARIES} osgEarthFeatures osgEarthSymbology osgEarthUtil)
+SETUP_PLUGIN(osgearth_feature_raster)
+
+# to install public driver includes:
+SET(LIB_NAME feature_raster)
+SET(LIB_PUBLIC_HEADERS ${TARGET_H})
+INCLUDE(ModuleInstallOsgEarthDriverIncludes OPTIONAL)
diff --git a/src/osgEarthDrivers/feature_raster/FeatureSourceRaster.cpp b/src/osgEarthDrivers/feature_raster/FeatureSourceRaster.cpp
new file mode 100644
index 0000000..e798faa
--- /dev/null
+++ b/src/osgEarthDrivers/feature_raster/FeatureSourceRaster.cpp
@@ -0,0 +1,243 @@
+/* -*-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 "RasterFeatureOptions"
+
+#include <osgDB/FileNameUtils>
+#include <osgDB/FileUtils>
+#include <osgEarth/Registry>
+#include <osgEarth/ImageUtils>
+#include <osgEarthFeatures/FeatureSource>
+
+#define LC "[Raster FeatureSource] "
+
+using namespace osgEarth;
+using namespace osgEarth::Features;
+using namespace osgEarth::Drivers;
+
+
+/**
+ * A FeatureSource that reads features from a raster layer
+ * 
+ */
+class RasterFeatureSource : public FeatureSource
+{
+public:
+    RasterFeatureSource(const RasterFeatureOptions& options ) :
+      FeatureSource( options ),
+      _options     ( options )
+    {           
+    }
+
+    virtual ~RasterFeatureSource()
+    {               
+        //nop
+    }
+
+    //override
+    void initialize( const osgDB::Options* dbOptions )
+    {
+        _dbOptions = dbOptions ? osg::clone(dbOptions) : 0L;
+    }
+
+
+    /** 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 Profile* wgs84 = Registry::instance()->getGlobalGeodeticProfile();
+        //GeoExtent extent(wgs84->getSRS(), -180, -90, 0, 90);
+        GeoExtent extent(wgs84->getSRS(), -180, -90, 180, 90);
+        FeatureProfile* profile = new FeatureProfile( extent );
+        profile->setProfile( Profile::create("wgs84", extent.xMin(), extent.yMin(), extent.xMax(), extent.yMax(), "", 1, 1) );
+        unsigned int level = *_options.level();
+        profile->setFirstLevel(level);
+        profile->setMaxLevel(level);
+        profile->setTiled(true);
+        return profile;
+    }
+
+    FeatureCursor* createFeatureCursor( const Symbology::Query& query )
+    {
+        unsigned w, h;
+        query.tileKey()->getProfile()->getNumTiles( query.tileKey()->getLevelOfDetail(), w, h );      
+        TileKey key = TileKey(query.tileKey()->getLevelOfDetail(), query.tileKey()->getTileX(), h - query.tileKey()->getTileY() -1, query.tileKey()->getProfile() );
+
+#if 0
+        // Debug
+        Polygon* poly = new Polygon();
+        poly->push_back(key.getExtent().xMin(), key.getExtent().yMin());
+        poly->push_back(key.getExtent().xMax(), key.getExtent().yMin());
+        poly->push_back(key.getExtent().xMax(), key.getExtent().yMax());
+        poly->push_back(key.getExtent().xMin(), key.getExtent().yMax());
+        FeatureList features;
+        Feature* feature = new Feature(poly, SpatialReference::create("wgs84"));
+        features.push_back( feature );
+        return new FeatureListCursor( features );
+#else
+
+        osg::ref_ptr< osgEarth::ImageLayer > layer = query.getMap()->getImageLayerByName(*_options.layer());
+        if (layer.valid())
+        {
+            GeoImage image = layer->createImage( key );
+         
+            FeatureList features;
+
+            if (image.valid())
+            {
+                double pixWidth  = key.getExtent().width() / (double)image.getImage()->s();
+                double pixHeight = key.getExtent().height() / (double)image.getImage()->t();
+                ImageUtils::PixelReader reader(image.getImage());
+
+                for (unsigned int r = 0; r < image.getImage()->t(); r++)
+                {
+                    double y = key.getExtent().yMin() + (double)r * pixHeight;
+
+                    double minX = 0;
+                    double maxX = 0;
+                    float value = 0.0;
+
+                    for (unsigned int c = 0; c < image.getImage()->s(); c++)
+                    {
+                        double x = key.getExtent().xMin() + (double)c * pixWidth;
+
+                        osg::Vec4f color = reader(c, r);
+
+                        // Starting a new row.  Initialize the values.
+                        if (c == 0)
+                        {
+                            minX = x;
+                            maxX = x + pixWidth;
+                            value = color.r(); 
+                        }
+                        // Ending a row, finish the polygon.
+                        else if (c == image.getImage()->s() -1)
+                        {
+                            // Increment the maxX to finish the row.
+                            maxX = x + pixWidth;
+                            Polygon* poly = new Polygon();
+                            poly->push_back(minX, y);
+                            poly->push_back(maxX, y);
+                            poly->push_back(maxX, y+pixHeight);
+                            poly->push_back(minX, y+pixHeight);
+                            Feature* feature = new Feature(poly, SpatialReference::create("wgs84"));
+                            feature->set(*_options.attribute(), value);
+                            features.push_back( feature );
+                            minX = x;
+                            maxX = x + pixWidth;
+                            value = color.r();
+                        }
+                        // The value is different, so complete the polygon and start a new one.
+                        else if (color.r() != value)
+                        {
+                            Polygon* poly = new Polygon();
+                            poly->push_back(minX, y);
+                            poly->push_back(maxX, y);
+                            poly->push_back(maxX, y+pixHeight);
+                            poly->push_back(minX, y+pixHeight);
+                            Feature* feature = new Feature(poly, SpatialReference::create("wgs84"));
+                            feature->set(*_options.attribute(), value);
+                            features.push_back( feature );
+                            minX = x;
+                            maxX = x + pixWidth;
+                            value = color.r();
+                        }
+                        // The value is the same as the previous value, continue the polygon by increasing the maxX.
+                        else if (color.r() == value)
+                        {
+                            maxX = x + pixWidth;
+                        }
+                    }
+
+                    
+                }
+               
+                if (!features.empty())
+                {
+                    //OE_NOTICE << LC << "Returning " << features.size() << " features" << std::endl;
+                    return new FeatureListCursor( features );
+                }
+            }
+        }
+        else
+        {
+            OE_NOTICE << LC << "Couldn't get layer " << *_options.layer() << std::endl;
+        }
+        return 0;
+#endif
+    }
+
+    virtual bool supportsGetFeature() const
+    {
+        return false;
+    }
+
+    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;
+    }
+
+
+
+private:
+    const RasterFeatureOptions       _options;    
+    FeatureSchema                   _schema;
+    osg::ref_ptr<osgDB::Options>    _dbOptions;    
+};
+
+
+class RasterFeatureSourceFactory : public FeatureSourceDriver
+{
+public:
+    RasterFeatureSourceFactory()
+    {
+        supportsExtension( "osgearth_feature_raster", "Raster feature driver for osgEarth" );
+    }
+
+    virtual const char* className()
+    {
+        return "Raster 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 RasterFeatureSource( getFeatureSourceOptions(options) ) );
+    }
+};
+
+REGISTER_OSGPLUGIN(osgearth_feature_raster, RasterFeatureSourceFactory)
+
diff --git a/src/osgEarthDrivers/feature_raster/RasterFeatureOptions b/src/osgEarthDrivers/feature_raster/RasterFeatureOptions
new file mode 100644
index 0000000..a60e64a
--- /dev/null
+++ b/src/osgEarthDrivers/feature_raster/RasterFeatureOptions
@@ -0,0 +1,89 @@
+/* -*-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_DRIVER_RASTER_FEATURE_SOURCE_OPTIONS
+#define OSGEARTH_DRIVER_RASTER_FEATURE_SOURCE_OPTIONS 1
+
+#include <osgEarth/Common>
+#include <osgEarth/URI>
+#include <osgEarthFeatures/FeatureSource>
+
+namespace osgEarth { namespace Drivers
+{
+    using namespace osgEarth;
+    using namespace osgEarth::Features;
+
+    /**
+     * Options for the Raster feature driver.
+     */
+    class RasterFeatureOptions : public FeatureSourceOptions // NO EXPORT; header only
+    {
+    public:
+        optional<std::string>& layer() { return _layer; }
+        const optional<std::string>& layer() const { return _layer; }
+
+        optional<unsigned int>& level() { return _level; }
+        const optional<unsigned int>& level() const { return _level; }
+
+        optional<std::string>& attribute() { return _attribute; }
+        const optional<std::string>& attribute() const { return _attribute; }
+
+
+    public:
+        RasterFeatureOptions( const ConfigOptions& opt =ConfigOptions() ) :
+          FeatureSourceOptions( opt ),
+          _level(0),
+          _attribute("value")
+          {
+            setDriver( "raster" );            
+            fromConfig( _conf );
+        }
+
+        virtual ~RasterFeatureOptions() { }
+
+    public:
+        Config getConfig() const {
+            Config conf = FeatureSourceOptions::getConfig();
+            conf.updateIfSet("layer", _layer);
+            conf.updateIfSet("level", _level);
+            conf.updateIfSet("attribute", _attribute);
+            return conf;
+        }
+
+    protected:
+        void mergeConfig( const Config& conf ) {
+            FeatureSourceOptions::mergeConfig( conf );
+            fromConfig( conf );
+        }
+
+    private:
+        void fromConfig( const Config& conf ) {
+            conf.getIfSet("layer", _layer);
+            conf.getIfSet("level", _level);
+            conf.getIfSet("attribute", _attribute);
+        }
+
+        optional< std::string > _layer;
+        optional< unsigned int > _level;
+        optional< std::string > _attribute;
+    };
+
+} } // namespace osgEarth::Drivers
+
+#endif // OSGEARTH_DRIVER_RASTER_FEATURE_SOURCE_OPTIONS
+
diff --git a/src/osgEarthDrivers/feature_tfs/FeatureSourceTFS.cpp b/src/osgEarthDrivers/feature_tfs/FeatureSourceTFS.cpp
index 0fc6d01..a9859e1 100644
--- a/src/osgEarthDrivers/feature_tfs/FeatureSourceTFS.cpp
+++ b/src/osgEarthDrivers/feature_tfs/FeatureSourceTFS.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -83,17 +83,23 @@ public:
 
                 std::string binId = Stringify() << std::hex << hashString(optionsConf.toJSON()) << "_tfs";
                 _cacheBin = cache->addBin( binId );
-                
-                // write a metadata record just for reference purposes.. we don't actually use it
-                Config metadata = _cacheBin->readMetadata();
-                if ( metadata.empty() )
-                {
-                    _cacheBin->writeMetadata( optionsConf );
-                }
-
                 if ( _cacheBin.valid() )
+                {                
+                    // write a metadata record just for reference purposes.. we don't actually use it
+                    Config metadata = _cacheBin->readMetadata();
+                    if ( metadata.empty() )
+                    {
+                        _cacheBin->writeMetadata( optionsConf );
+                    }
+
+                    if ( _cacheBin.valid() )
+                    {
+                        _cacheBin->apply( _dbOptions.get() );
+                    }
+                }
+                else
                 {
-                    _cacheBin->apply( _dbOptions.get() );
+                    OE_INFO << LC << "Failed to open cache bin \"" << binId << "\"\n";
                 }
             }
         }     
@@ -117,6 +123,8 @@ public:
             result->setFirstLevel( _layer.getFirstLevel());
             result->setMaxLevel( _layer.getMaxLevel());
             result->setProfile( osgEarth::Profile::create(_layer.getSRS(), _layer.getExtent().xMin(), _layer.getExtent().yMin(), _layer.getExtent().xMax(), _layer.getExtent().yMax(), 1, 1) );
+            if ( _options.geoInterp().isSet() )
+                result->geoInterp() = _options.geoInterp().get();
         }
         return result;        
     }
@@ -161,7 +169,7 @@ public:
             {
                 if ( feat_handle )
                 {
-                    osg::ref_ptr<Feature> f = OgrUtils::createFeature( feat_handle, srs );
+                    osg::ref_ptr<Feature> f = OgrUtils::createFeature( feat_handle, getFeatureProfile() );
                     if ( f.valid() && !isBlacklisted(f->getFID()) )
                     {
                         features.push_back( f.release() );
@@ -226,11 +234,22 @@ public:
     {     
         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 0
+            unsigned int numRows, numCols;
+            key.getProfile()->getNumTiles(key.getLevelOfDetail(), numCols, numRows);
+            tileY  = numRows - tileY - 1;
+#endif
+            
             std::stringstream buf;
             std::string path = osgDB::getFilePath(_options.url()->full());
-            buf << path << "/" << query.tileKey().get().getLevelOfDetail() << "/"
-                               << query.tileKey().get().getTileX() << "/"
-                               << query.tileKey().get().getTileY()
+            buf << path << "/" << level << "/"
+                               << tileX << "/"
+                               << tileY
                                << "." << _options.format().get();            
             OE_DEBUG << "TFS url " << buf.str() << std::endl;
             return buf.str();
@@ -305,13 +324,14 @@ public:
         return result;
     }
 
-    /**
-    * Gets the Feature with the given FID
-    * @returns
-    *     The Feature with the given FID or NULL if not found.
-    */
+    virtual bool supportsGetFeature() const
+    {
+        return false;
+    }
+
     virtual Feature* getFeature( FeatureID fid )
     {
+        // not supported for TFS.
         return 0;
     }
 
diff --git a/src/osgEarthDrivers/feature_tfs/TFSFeatureOptions b/src/osgEarthDrivers/feature_tfs/TFSFeatureOptions
index 8dc6fe6..7a912d3 100644
--- a/src/osgEarthDrivers/feature_tfs/TFSFeatureOptions
+++ b/src/osgEarthDrivers/feature_tfs/TFSFeatureOptions
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/feature_wfs/FeatureSourceWFS.cpp b/src/osgEarthDrivers/feature_wfs/FeatureSourceWFS.cpp
index 02eefdd..d30775f 100644
--- a/src/osgEarthDrivers/feature_wfs/FeatureSourceWFS.cpp
+++ b/src/osgEarthDrivers/feature_wfs/FeatureSourceWFS.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -177,6 +177,11 @@ public:
             }
         }
 
+        if ( _featureProfile.valid() && _options.geoInterp().isSet() )
+        {
+            _featureProfile->geoInterp() = _options.geoInterp().get();
+        }
+
         return _featureProfile.get();
     }
 
@@ -240,16 +245,13 @@ public:
         OGRLayerH layer = OGR_DS_GetLayer(ds, 0);
         if ( layer )
         {
-            FeatureProfile* fp = getFeatureProfile();
-            const SpatialReference* srs = fp ? fp->getSRS() : 0L;
-
             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, srs );
+                    osg::ref_ptr<Feature> f = OgrUtils::createFeature( feat_handle, getFeatureProfile() );
                     if ( f.valid() && !isBlacklisted(f->getFID()) )
                     {
                         features.push_back( f.release() );
@@ -322,9 +324,19 @@ public:
 
         if (query.tileKey().isSet())
         {
-            buf << "&Z=" << query.tileKey().get().getLevelOfDetail() << 
-                   "&X=" << query.tileKey().get().getTileX() <<
-                   "&Y=" << query.tileKey().get().getTileY();
+
+            unsigned int tileX = query.tileKey().get().getTileX();
+            unsigned int tileY = query.tileKey().get().getTileY();
+            unsigned int level = query.tileKey().get().getLevelOfDetail();
+#if 0
+            unsigned int numRows, numCols;
+            query.tileKey().get().getProfile()->getNumTiles(level, numCols, numRows);
+            tileY  = numRows - tileY - 1;
+#endif
+
+            buf << "&Z=" << level << 
+                   "&X=" << tileX <<
+                   "&Y=" << tileY;
         }
         else if (query.bounds().isSet())
         {            
@@ -400,13 +412,14 @@ public:
         return result;
     }
 
-    /**
-    * Gets the Feature with the given FID
-    * @returns
-    *     The Feature with the given FID or NULL if not found.
-    */
+    virtual bool supportsGetFeature() const
+    {
+        return false;
+    }
+
     virtual Feature* getFeature( FeatureID fid )
     {
+        // not supported for WFS
         return 0;
     }
 
diff --git a/src/osgEarthDrivers/feature_wfs/WFSFeatureOptions b/src/osgEarthDrivers/feature_wfs/WFSFeatureOptions
index 064dea2..d1483cb 100644
--- a/src/osgEarthDrivers/feature_wfs/WFSFeatureOptions
+++ b/src/osgEarthDrivers/feature_wfs/WFSFeatureOptions
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/gdal/GDALOptions b/src/osgEarthDrivers/gdal/GDALOptions
index 0131e93..1fd3583 100644
--- a/src/osgEarthDrivers/gdal/GDALOptions
+++ b/src/osgEarthDrivers/gdal/GDALOptions
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/gdal/ReaderWriterGDAL.cpp b/src/osgEarthDrivers/gdal/ReaderWriterGDAL.cpp
index c7957fc..ebc0fd2 100644
--- a/src/osgEarthDrivers/gdal/ReaderWriterGDAL.cpp
+++ b/src/osgEarthDrivers/gdal/ReaderWriterGDAL.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
@@ -912,6 +915,10 @@ public:
         else if ( !srcProj.empty() )
         {
             src_srs = SpatialReference::create( srcProj );
+            if ( !src_srs.valid() )
+            {
+                OE_DEBUG << LC << "Cannot create source SRS from its projection info: " << srcProj << std::endl;
+            }
         }
 
         // assert SRS is present
@@ -933,6 +940,7 @@ public:
             }
         }
 
+     
         //Get the initial geotransform
         _srcDS->GetGeoTransform(_geotransform);
 
@@ -944,36 +952,42 @@ public:
 
         const Profile* profile = NULL;
 
+        // The warp profile, if provided, takes precedence.
         if ( warpProfile )
         {
             profile = warpProfile;
+            if ( profile )
+            {
+                OE_DEBUG << LC << INDENT << "Using warp Profile: " << profile->toString() <<  std::endl;
+            }
         }
 
         // If we have an override profile, just take it.
         if ( getProfile() )
         {
             profile = getProfile();
+            if ( profile )
+            {
+                OE_DEBUG << LC << INDENT << "Using override Profile: " << profile->toString() <<  std::endl;
+            }
         }
 
+        // If neither a warp nor override profile were provided, work out the profile from the source's own SRS.
         if ( !profile && src_srs->isGeographic() )
         {
-            profile = osgEarth::Registry::instance()->getGlobalGeodeticProfile();
+            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);
+            if ( !profile )
+            {
+                return Status::Error( Stringify()
+                    << "Cannot create geographic Profile from dataset's spatial reference information: " << src_srs->getName() );
+            }
         }
 
-        //Note:  Can cause odd rendering artifacts if we have a dataset that is mercator that doesn't encompass the whole globe
-        //       if we take on the global profile.
-        /*
-        if ( !profile && src_srs->isMercator() )
-        {
-            profile = osgEarth::Registry::instance()->getGlobalMercatorProfile();
-        }*/
-
         std::string warpedSRSWKT;
 
         if ( requiresReprojection || (profile && !profile->getSRS()->isEquivalentTo( src_srs.get() )) )
         {
-            std::string destWKT = profile ? profile->getSRS()->getWKT() : src_srs->getWKT();
-
             if ( profile && profile->getSRS()->isGeographic() && (src_srs->isNorthPolar() || src_srs->isSouthPolar()) )
             {
                 _warpedDS = (GDALDataset*)GDALAutoCreateWarpedVRTforPolarStereographic(
@@ -986,6 +1000,7 @@ public:
             }
             else
             {
+                std::string destWKT = profile ? profile->getSRS()->getWKT() : src_srs->getWKT();
                 _warpedDS = (GDALDataset*)GDALAutoCreateWarpedVRT(
                     _srcDS,
                     src_srs->getWKT().c_str(),
@@ -1006,9 +1021,15 @@ public:
             warpedSRSWKT = src_srs->getWKT();
         }
 
+        if ( !_warpedDS )
+        {
+            return Status::Error( "Failed to create a warping VRT" );
+        }
+
         //Get the _geotransform
         if ( getProfile() )
         {
+            OE_DEBUG << LC << INDENT << "Get geotransform from Override Profile" <<  std::endl;
             _geotransform[0] =  getProfile()->getExtent().xMin(); //Top left x
             _geotransform[1] =  getProfile()->getExtent().width() / (double)_warpedDS->GetRasterXSize();//pixel width
             _geotransform[2] =  0;
@@ -1020,6 +1041,7 @@ public:
         }
         else
         {
+            OE_DEBUG << LC << INDENT << "Get geotransform from warped dataset" <<  std::endl;
             _warpedDS->GetGeoTransform(_geotransform);
         }
 
@@ -1067,6 +1089,12 @@ public:
                 warpedSRSWKT,
                 minX, minY, maxX, maxY);
 
+            if ( !profile )
+            {
+                return Status::Error( Stringify()
+                    << "Cannot create projected Profile from dataset's warped spatial reference WKT: " << warpedSRSWKT );
+            }
+
             OE_INFO << LC << INDENT << source << " is projected, SRS = "
                 << warpedSRSWKT << std::endl;
                 //<< _warpedDS->GetProjectionRef() << std::endl;
@@ -1114,6 +1142,7 @@ public:
 
         //Set the profile
         setProfile( profile );
+        OE_DEBUG << LC << INDENT << "Set Profile to " << (profile ? profile->toString() : "NULL") <<  std::endl;
 
         return STATUS_OK;
     }
@@ -1444,91 +1473,189 @@ public:
                 delete []green;
                 delete []blue;
                 delete []alpha;
-            }
+            }            
             else if (bandGray)
             {
-                unsigned char *gray = new unsigned char[target_width * target_height];
-                unsigned char *alpha = new unsigned char[target_width * target_height];
+                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;
+                    }
 
-                //Initialize the alpha values to 255.
-                memset(alpha, 255, target_width * target_height);
+                    // 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 );
+                    
+                    // coverage data; one channel data that is not subject to interpolated values
+                    int xbytes = target_width *  gdalSampleSize;
+                    int ybytes = target_height * gdalSampleSize;
+                    unsigned char* data = new unsigned char[xbytes * ybytes];
+                    osg::Vec4 temp;
+
+                    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, 1, 0);
+                    if ( err == CE_None )
+                    {
+                        ImageUtils::PixelWriter write(image);
 
-                image = new osg::Image;
-                image->allocateImage(tileSize, tileSize, 1, pixelFormat, GL_UNSIGNED_BYTE);
-                memset(image->data(), 0, image->getImageSizeInBytes());
+                        // 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 (!*_options.interpolateImagery() || _options.interpolation() == INTERP_NEAREST)
-                {
-                    bandGray->RasterIO(GF_Read, off_x, off_y, width, height, gray, target_width, target_height, GDT_Byte, 0, 0);
+                                if ( !isValidValue_noLock(value, bandGray) )
+                                    value = NO_DATA_VALUE;
 
-                    if (bandAlpha)
+                                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
                     {
-                        bandAlpha->RasterIO(GF_Read, off_x, off_y, width, height, alpha, target_width, target_height, GDT_Byte, 0, 0);
+                        OE_WARN << LC << "RasterIO failed.\n";
+                        // TODO - handle error condition
                     }
 
-                    for (int src_row = 0, dst_row = tile_offset_top;
-                        src_row < target_height;
-                        src_row++, dst_row++)
+                    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];
+
+                    //Initialize the alpha values to 255.
+                    memset(alpha, 255, target_width * target_height);
+
+                    image = new osg::Image;
+                    image->allocateImage(tileSize, tileSize, 1, pixelFormat, GL_UNSIGNED_BYTE);
+                    memset(image->data(), 0, image->getImageSizeInBytes());
+
+
+                    if (!*_options.interpolateImagery() || _options.interpolation() == INTERP_NEAREST)
                     {
-                        for (int src_col = 0, dst_col = tile_offset_left;
-                            src_col < target_width;
-                            ++src_col, ++dst_col)
+                        bandGray->RasterIO(GF_Read, off_x, off_y, width, height, gray, target_width, target_height, GDT_Byte, 0, 0);
+
+                        if (bandAlpha)
                         {
-                            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)))
+                            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)
                             {
-                                a = 0.0f;
+                                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;
                             }
-                            *(image->data(dst_col, dst_row) + 3) = a;
                         }
-                    }
 
-                    image->flipVertical();
-                }
-                else
-                {
+                        image->flipVertical();
+                    }
+                    else
+                    {
                         for (int r = 0; r < tileSize; ++r)
                         {
                             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;
+                            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;
+                            }
                         }
                     }
-                }
-
-                delete []gray;
-                delete []alpha;
 
+                    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;
-                image->allocateImage(tileSize, tileSize, 1, pixelFormat, GL_UNSIGNED_BYTE);
+
+                if ( _options.coverage() == true )
+                {
+                    image->allocateImage(tileSize, tileSize, 1, GL_LUMINANCE, GL_FLOAT);
+                    image->setInternalTextureFormat(GL_LUMINANCE32F_ARB);
+                    ImageUtils::markAsUnNormalized(image, true);
+                }
+                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++)
@@ -1537,19 +1664,32 @@ public:
                         src_col < target_width;
                         ++src_col, ++dst_col)
                     {
-
                         unsigned char p = palette[src_col + src_row * target_width];
-                        osg::Vec4ub color;
-                        getPalleteIndexColor( bandPalette, p, color );
-                        if (!isValidValue( p, bandPalette))
-                        {
-                            color.a() = 0.0f;
+
+                        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();
+                        }
                     }
                 }
 
@@ -1573,20 +1713,11 @@ public:
             OE_NOTICE << LC << key.str() << " does not intersect " << _options.url()->full() << std::endl;
         }
 
-        // Moved this logic up into ImageLayer::createImageWrapper.
-        ////Create a transparent image if we don't have an image
-        //if (!image.valid())
-        //{
-        //    //OE_WARN << LC << "Illegal state-- should not get here" << std::endl;
-        //    return ImageUtils::createEmptyImage();
-        //}
         return image.release();
     }
 
-    bool isValidValue(float v, GDALRasterBand* band)
+    bool isValidValue_noLock(float v, GDALRasterBand* band)
     {
-        GDAL_SCOPED_LOCK;
-
         float bandNoData = -32767.0f;
         int success;
         float value = band->GetNoDataValue(&success);
@@ -1601,16 +1732,18 @@ public:
         if (getNoDataValue() == v) return false;
 
         //Check to see if the user specified a custom min/max
-        if (v < getNoDataMinValue()) return false;
-        if (v > getNoDataMaxValue()) return false;
-
-        //Check within a sensible range
-        if (v < -32000) return false;
-        if (v > 32000) return false;
+        if (v < getMinValidValue()) return false;
+        if (v > getMaxValidValue()) return false;
 
         return true;
     }
 
+    bool isValidValue(float v, GDALRasterBand* band)
+    {
+        GDAL_SCOPED_LOCK;
+        return isValidValue_noLock( v, band );
+    }
+
 
     float getInterpolatedValue(GDALRasterBand *band, double x, double y, bool applyOffset=true)
     {
@@ -1737,7 +1870,7 @@ public:
     }
 
 
-#if 0
+#if 1
     osg::HeightField* createHeightField( const TileKey&        key,
                                          ProgressCallback*     progress)
     {
@@ -2044,6 +2177,7 @@ public:
                     {
                         h = NO_DATA_VALUE;
                     }
+
                     readHF->setHeight(c, inv_r, h );
                 }
             }
diff --git a/src/osgEarthDrivers/kml/CMakeLists.txt b/src/osgEarthDrivers/kml/CMakeLists.txt
index f356b20..cb35253 100644
--- a/src/osgEarthDrivers/kml/CMakeLists.txt
+++ b/src/osgEarthDrivers/kml/CMakeLists.txt
@@ -40,9 +40,11 @@ SET(TARGET_H
     KML_LineStyle
     KML_Model
     KML_MultiGeometry
+    KML_NetworkLink
     KML_NetworkLinkControl
     KML_Object
     KML_Overlay
+    KML_Placemark
     KML_PhotoOverlay
     KML_Point
     KML_Polygon
@@ -54,12 +56,12 @@ SET(TARGET_H
     KML_StyleMap
     KML_StyleSelector
     KMZArchive
+    rapidxml_ext.hpp
 )
 
 SET(TARGET_SRC
     ReaderWriterKML.cpp
     KMLReader.cpp
-    
     KML_Document.cpp
     KML_Feature.cpp
     KML_Folder.cpp
diff --git a/src/osgEarthDrivers/kml/KML b/src/osgEarthDrivers/kml/KML
index 92b3ef0..28783a0 100644
--- a/src/osgEarthDrivers/kml/KML
+++ b/src/osgEarthDrivers/kml/KML
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/kml/KMLOptions b/src/osgEarthDrivers/kml/KMLOptions
index 0355695..4b45a22 100644
--- a/src/osgEarthDrivers/kml/KMLOptions
+++ b/src/osgEarthDrivers/kml/KMLOptions
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/kml/KMLReader b/src/osgEarthDrivers/kml/KMLReader
index 6d11364..103169a 100644
--- a/src/osgEarthDrivers/kml/KMLReader
+++ b/src/osgEarthDrivers/kml/KMLReader
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -26,6 +26,11 @@
 #include <iostream>
 #include "KMLOptions"
 
+#include "rapidxml.hpp"
+#include "rapidxml_utils.hpp"
+
+using namespace rapidxml;
+
 namespace osgEarth_kml
 {
     using namespace osgEarth;
@@ -43,8 +48,8 @@ namespace osgEarth_kml
         /** Reads KML from a stream and returns a node */
         osg::Node* read( std::istream& in, const osgDB::Options* dbOptions ) ;
 
-        /** Reads KML from a Config object */
-        osg::Node* read( const Config& conf, const osgDB::Options* dbOptions );
+        /** Reads KML from an xml_document object */
+        osg::Node* read( xml_document<>& doc, const osgDB::Options* dbOptions );
 
     private:
         MapNode*          _mapNode;
diff --git a/src/osgEarthDrivers/kml/KMLReader.cpp b/src/osgEarthDrivers/kml/KMLReader.cpp
index c68a718..b50dc41 100644
--- a/src/osgEarthDrivers/kml/KMLReader.cpp
+++ b/src/osgEarthDrivers/kml/KMLReader.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -18,6 +18,7 @@
  */
 #include "KMLReader"
 #include "KML_Root"
+#include "KML_Geometry"
 #include <osgEarth/Registry>
 #include <osgEarth/Capabilities>
 #include <osgEarth/XmlUtils>
@@ -29,6 +30,7 @@
 using namespace osgEarth_kml;
 using namespace osgEarth;
 
+
 KMLReader::KMLReader( MapNode* mapNode, const KMLOptions* options ) :
 _mapNode( mapNode ),
 _options( options )
@@ -42,35 +44,47 @@ KMLReader::read( std::istream& in, const osgDB::Options* dbOptions )
     // pull the URI context out of the DB options:
     URIContext context(dbOptions);
 
-    // read the KML from an XML stream:
-    osg::ref_ptr<XmlDocument> xml = XmlDocument::load( in, context );
-    if ( !xml.valid() )
-        return 0L;
-
-    // convert to a config:
-    Config config = xml->getConfig();
-
-    osg::Node* node = read( config, dbOptions );
-    node->setName( context.referrer() );
-
-    return node;
+	// Load the XML
+    osg::Timer_t start = osg::Timer::instance()->tick();
+	std::stringstream buffer;
+    buffer << in.rdbuf();
+    std::string xmlStr;
+    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;
+	node->setName( context.referrer() );
+
+	return node;
 }
 
 osg::Node*
-KMLReader::read( const Config& conf, const osgDB::Options* dbOptions )
+KMLReader::read( xml_document<>& doc, const osgDB::Options* dbOptions )
 {
     osg::Group* root = new osg::Group();
     root->ref();
 
-    root->setName( conf.referrer() );
+    URIContext context(dbOptions);
+
+	root->setName( context.referrer() );
 
     KMLContext cx;
     cx._mapNode   = _mapNode;
     cx._sheet     = new StyleSheet();
     cx._options   = _options;
-    cx._srs       = SpatialReference::create( "wgs84", "egm96" );
+    //cx._srs      = SpatialReference::create( "wgs84", "egm96" );
+    // Use the geographic srs of the map so that clamping will occur against the correct vertical datum.
+    cx._srs = _mapNode->getMapSRS()->getGeographicSRS();
+    cx._referrer = context.referrer();
     cx._groupStack.push( root );
 
+
     // clone the dbOptions, and install a resource cache if there isn't one already:
     URIResultCache defaultUriCache;
     if ( !URIResultCache::from(dbOptions) )
@@ -94,19 +108,35 @@ KMLReader::read( const Config& conf, const osgDB::Options* dbOptions )
         Decluttering::setEnabled( cx._options->iconAndLabelGroup()->getOrCreateStateSet(), true );
     }
 
-    const Config* top = conf.hasChild("kml" ) ? conf.child_ptr("kml") : &conf;
+    //const Config* top = conf.hasChild("kml" ) ? conf.child_ptr("kml") : &conf;
+	xml_node<> *top = doc.first_node("kml", 0, false);
 
-    if ( top && !top->empty() )
+    if ( top)
     {
         KML_Root kmlRoot;
-        kmlRoot.scan ( *top, cx );    // first pass
-        kmlRoot.scan2( *top, cx );   // second pass
-        kmlRoot.build( *top, cx );   // third pass.
+
+        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;
+
+        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;
+
+        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;
     }
 
     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;
 
+    // Make sure the KML gets rendered after the terrain.
+    root->getOrCreateStateSet()->setRenderBinDetails(2, "RenderBin");
+
     return root;
 }
diff --git a/src/osgEarthDrivers/kml/KML_Common b/src/osgEarthDrivers/kml/KML_Common
index 15cfae8..028349f 100644
--- a/src/osgEarthDrivers/kml/KML_Common
+++ b/src/osgEarthDrivers/kml/KML_Common
@@ -1,7 +1,7 @@
 
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -21,7 +21,6 @@
 #define OSGEARTH_DRIVER_KML_COMMON 1
 
 #include <osgEarth/Common>
-#include <osgEarth/Config>
 #include <osgEarth/URI>
 #include <osgEarth/MapNode>
 #include <osgEarth/SpatialReference>
@@ -30,34 +29,42 @@
 #include <osgEarthSymbology/ResourceCache>
 #include "KMLOptions"
 
+#include "rapidxml.hpp"
+#include "rapidxml_utils.hpp"
+#include "rapidxml_ext.hpp"
+
+using namespace rapidxml;
+
 #define LC "[KML] "
 
-#define for_one( NAME, FUNC, CONF, CX ) \
+#define for_one( NAME, FUNC, NODE, CX ) \
 { \
-    Config c = conf.child( toLower( #NAME ) ); \
-    if ( !c.empty() ) { \
-        KML_##NAME instance; \
-        instance. FUNC (c, CX); \
-    } \
+		xml_node<> *n = NODE->first_node(#NAME, 0, false); \
+		if (n) {\
+	        KML_##NAME instance; \
+            instance. FUNC (n, CX); \
+		}\
 }
 
-#define for_many( NAME, FUNC, CONF, CX ) \
+
+#define for_many( NAME, FUNC, NODE, CX ) \
 { \
-   ConfigSet c = conf.children( toLower( #NAME ) ); \
-   for( ConfigSet::const_iterator i = c.begin(); i != c.end(); ++i ) { \
-        KML_##NAME instance; \
-        instance. FUNC (*i, CX); \
-   } \
+   if (NODE) { \
+       for (xml_node<>* n = NODE->first_node(#NAME, 0, false); n; n = n->next_sibling(#NAME, 0, false)) { \
+	       KML_##NAME instance; \
+		   instance. FUNC (n, CX); \
+	   } \
+   }\
 }
 
-#define for_features( FUNC, CONF, CX ) \
-    for_many( Document,      FUNC, CONF, CX ); \
-    for_many( Folder,        FUNC, CONF, CX ); \
-    for_many( PhotoOverlay,  FUNC, CONF, CX ); \
-    for_many( ScreenOverlay, FUNC, CONF, CX ); \
-    for_many( GroundOverlay, FUNC, CONF, CX ); \
-    for_many( NetworkLink,   FUNC, CONF, CX ); \
-    for_many( Placemark,     FUNC, CONF, CX );
+#define for_features( FUNC, NODE, CX ) \
+    for_many( Document,      FUNC, NODE, CX ); \
+    for_many( Folder,        FUNC, NODE, CX ); \
+    for_many( PhotoOverlay,  FUNC, NODE, CX ); \
+    for_many( ScreenOverlay, FUNC, NODE, CX ); \
+    for_many( GroundOverlay, FUNC, NODE, CX ); \
+    for_many( NetworkLink,   FUNC, NODE, CX ); \
+    for_many( Placemark,     FUNC, NODE, CX );
 
 namespace osgEarth_kml
 {
@@ -74,31 +81,42 @@ namespace osgEarth_kml
         std::stack<osg::ref_ptr<osg::Group> > _groupStack;      // resulting scene graph
         osg::ref_ptr<const SpatialReference>  _srs;             // map's spatial reference
         osg::ref_ptr<const osgDB::Options>    _dbOptions;       // I/O options (caching, etc)
+        std::string                           _referrer;        // The referrer for loading things from relative paths.
     };
 
     struct KMLUtils
     {
         // parse KML's many variants on a URL link.
-        static std::string parseLink( const Config& conf )
-        {
-            Config link = conf.child("link");
-            if ( !link.empty() )
-            {
-                if ( link.hasValue("href") )
-                    return link.value("href");
-                else if ( link.hasValue("url") )
-                    return link.value("url");
-                else
-                    return link.value();
-            }
-            else
-            {
-                link = conf.child("url");
-                if ( link.hasValue("href") )
-                    return link.value("href");
-                else
-                    return link.value();
-            }
+        static std::string parseLink( xml_node<>* node )
+		{
+			if (node)
+			{
+				xml_node<>* link = node->first_node("link", 0, false);
+				if ( link )
+				{
+					std::string url = getValue(link, "href");
+					if (url.empty())
+					{
+						url = getValue(link, "href");
+					}
+					if (url.empty())
+					{
+						url = link->value();
+					}
+					return url;
+				}
+				else
+				{
+					link = node->first_node("url", 0, false);
+					std::string url = getValue(link, "href");
+					if (url.empty())
+					{
+						url = link->value();
+					}
+					return url;
+				}
+			}
+			return "";
         }
     };
 
diff --git a/src/osgEarthDrivers/kml/KML_Container b/src/osgEarthDrivers/kml/KML_Container
index 0076475..3a83001 100644
--- a/src/osgEarthDrivers/kml/KML_Container
+++ b/src/osgEarthDrivers/kml/KML_Container
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -28,21 +28,21 @@ namespace osgEarth_kml
 
     struct KML_Container : public KML_Feature
     {
-        virtual void scan( const Config& conf, KMLContext& cx )
+        virtual void scan( xml_node<>* node, KMLContext& cx )
         {
-            KML_Feature::scan(conf, cx);
+            KML_Feature::scan(node, cx);
         }
 
-        virtual void scan2( const Config& conf, KMLContext& cx )
+        virtual void scan2( xml_node<>* node, KMLContext& cx )
         {
-            KML_Feature::scan2(conf, cx);
+            KML_Feature::scan2(node, cx);
         }
 
-        virtual void build( const Config& conf, KMLContext& cx, osg::Node* working )
+        virtual void build( xml_node<>* node, KMLContext& cx, osg::Node* working )
         {
             // assumes the top of the group stack has a new and valid Node.
             // don't call this is there was an error in the subclass build() method
-            KML_Feature::build(conf, cx, working);
+            KML_Feature::build(node, cx, working);
         }
     };
 
diff --git a/src/osgEarthDrivers/kml/KML_Document b/src/osgEarthDrivers/kml/KML_Document
index 6dc1065..7596a6d 100644
--- a/src/osgEarthDrivers/kml/KML_Document
+++ b/src/osgEarthDrivers/kml/KML_Document
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -28,11 +28,11 @@ namespace osgEarth_kml
 
     struct KML_Document : public KML_Container
     {
-        virtual void scan( const Config& conf, KMLContext& cx );
+        virtual void scan( xml_node<>* node, KMLContext& cx );
 
-        virtual void scan2( const Config& conf, KMLContext& cx );
+        virtual void scan2( xml_node<>* node, KMLContext& cx );
 
-        virtual void build( const Config& conf, KMLContext& cx );
+        virtual void build( xml_node<>* node, KMLContext& cx );
     };
 
 } // namespace osgEarth_kml
diff --git a/src/osgEarthDrivers/kml/KML_Document.cpp b/src/osgEarthDrivers/kml/KML_Document.cpp
index 9971f04..ad80a70 100644
--- a/src/osgEarthDrivers/kml/KML_Document.cpp
+++ b/src/osgEarthDrivers/kml/KML_Document.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -28,31 +28,31 @@
 using namespace osgEarth_kml;
 
 void
-KML_Document::scan( const Config& conf, KMLContext& cx )
+KML_Document::scan( xml_node<>* node, KMLContext& cx )
 {
-    KML_Container::scan(conf, cx);
-    for_many    ( Schema, scan, conf, cx );
-    for_features( scan, conf, cx );
+    KML_Container::scan(node, cx);
+    for_many    ( Schema, scan, node, cx );
+    for_features( scan, node, cx );
 }
 
 void
-KML_Document::scan2( const Config& conf, KMLContext& cx )
+KML_Document::scan2( xml_node<>* node, KMLContext& cx )
 {
-    KML_Container::scan2(conf, cx);
-    for_many    ( Schema, scan2, conf, cx );
-    for_features( scan2, conf, cx );
+    KML_Container::scan2(node, cx);
+    for_many    ( Schema, scan2, node, cx );
+    for_features( scan2, node, cx );
 }
 
 void
-KML_Document::build( const Config& conf, KMLContext& cx )
+KML_Document::build( xml_node<>* node, KMLContext& cx )
 {
     // creates an empty group and pushes it on the stack.
     osg::Group* group = new osg::Group();
     cx._groupStack.top()->addChild( group );
     cx._groupStack.push( group );
 
-    KML_Container::build(conf, cx, group);
-    for_features(build, conf, cx);
+    KML_Container::build(node, cx, group);
+    for_features(build, node, cx);
 
     cx._groupStack.pop();
 }
diff --git a/src/osgEarthDrivers/kml/KML_Feature b/src/osgEarthDrivers/kml/KML_Feature
index 9042d4d..1842c0b 100644
--- a/src/osgEarthDrivers/kml/KML_Feature
+++ b/src/osgEarthDrivers/kml/KML_Feature
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -27,11 +27,11 @@ namespace osgEarth_kml
 
     struct KML_Feature : public KML_Object
     {
-        virtual void scan( const Config& conf, KMLContext& cx );
+        virtual void scan( xml_node<>* node, KMLContext& cx );
 
-        virtual void scan2( const Config& conf, KMLContext& cx );
+        virtual void scan2( xml_node<>* node, KMLContext& cx );
 
-        virtual void build( const Config& conf, KMLContext& cx, osg::Node* working );
+        virtual void build( xml_node<>* node, KMLContext& cx, osg::Node* working );
     };
 
 } // namespace osgEarth_kml
diff --git a/src/osgEarthDrivers/kml/KML_Feature.cpp b/src/osgEarthDrivers/kml/KML_Feature.cpp
index 4c9ebfc..70c6d7d 100644
--- a/src/osgEarthDrivers/kml/KML_Feature.cpp
+++ b/src/osgEarthDrivers/kml/KML_Feature.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -27,60 +27,69 @@ using namespace osgEarth_kml;
 using namespace osgEarth;
 
 void
-KML_Feature::scan( const Config& conf, KMLContext& cx )
+KML_Feature::scan( xml_node<>* node, KMLContext& cx )
 {
-    KML_Object::scan(conf, cx);
-    for_many( Style, scan, conf, cx );
-    for_many( StyleMap, scan, conf, cx );
+    KML_Object::scan(node, cx);
+    for_many( Style, scan, node, cx );
+    for_many( StyleMap, scan, node, cx );
 }
 
 void
-KML_Feature::scan2( const Config& conf, KMLContext& cx )
+KML_Feature::scan2( xml_node<>* node, KMLContext& cx )
 {
-    KML_Object::scan2(conf, cx);
-    for_many( Style, scan2, conf, cx );
-    for_many( StyleMap, scan2, conf, cx );
+    KML_Object::scan2(node, cx);
+    for_many( Style, scan2, node, cx );
+    for_many( StyleMap, scan2, node, cx );
 }
 
 void
-KML_Feature::build( const Config& conf, KMLContext& cx, osg::Node* working )
+KML_Feature::build( xml_node<>* node, KMLContext& cx, osg::Node* working )
 {
-    KML_Object::build(conf, cx, working);
+    KML_Object::build(node, cx, working);
 
     // subclass feature is built; now add feature level data if available
     if ( working )
     {
         // parse the visibility to show/hide the item by default:
-        if ( conf.hasValue("visibility") )
-            working->setNodeMask( conf.value<int>("visibility", 1) == 1 ? ~0 : 0 );
+		std::string visibility = getValue(node, "visibility");
+        if ( !visibility.empty() )
+            working->setNodeMask( as<int>(visibility, 1) == 1 ? ~0 : 0 );
 
         // parse a "LookAt" element (stores a viewpoint)
         AnnotationData* anno = getOrCreateAnnotationData(working);
         
-        anno->setName( conf.value("name") );
-        anno->setDescription( conf.value("description") );
+        anno->setName( getValue(node, "name") );
+        anno->setDescription( getValue(node, "description") );
 
-        const Config& lookat = conf.child("lookat");
-        if ( !lookat.empty() )
+        xml_node<>* lookat = node->first_node("lookat", 0, false);
+        if ( lookat )
         {
-            Viewpoint vp(
-                lookat.value<double>("longitude", 0.0),
-                lookat.value<double>("latitude", 0.0),
-                lookat.value<double>("altitude", 0.0),
-                lookat.value<double>("heading", 0.0),
-                -lookat.value<double>("tilt", 45.0),
-                lookat.value<double>("range", 10000.0) );
+            Viewpoint vp;
+
+            vp.focalPoint() = GeoPoint(
+                cx._srs.get(),
+				as<double>(getValue(lookat, "longitude"), 0.0),
+				as<double>(getValue(lookat, "latitude"), 0.0),
+				as<double>(getValue(lookat, "altitude"), 0.0),
+                ALTMODE_ABSOLUTE );
+
+            vp.heading() =  as<double>(getValue(lookat, "heading"), 0.0);
+            vp.pitch()   = -as<double>(getValue(lookat, "tilt"), 45.0),
+            vp.range()   =  as<double>(getValue(lookat, "range"), 10000.0);
 
             anno->setViewpoint( vp );
         }
 
-        const Config& extdata = conf.child("extendeddata");
-        if ( !extdata.empty() )
+        xml_node<>* extdata = node->first_node("extendeddata", 0, false);
+        if ( extdata )
         {
-            ConfigSet innerConfs = extdata.children("data");
-            for( ConfigSet::const_iterator i = innerConfs.begin(); i != innerConfs.end(); ++i )
+            xml_node<>* data = extdata->first_node("data", 0, false);
+            if ( data )
             {
-                working->setUserValue(i->value("name"), i->value("value"));
+			    for (xml_node<>* n = data->first_node(); n; n = n->next_sibling())
+			    {
+    				working->setUserValue(getValue(n, "name"), getValue(n, "value"));
+			    }
             }
         }
     }
diff --git a/src/osgEarthDrivers/kml/KML_Folder b/src/osgEarthDrivers/kml/KML_Folder
index c96ee2d..dc1621d 100644
--- a/src/osgEarthDrivers/kml/KML_Folder
+++ b/src/osgEarthDrivers/kml/KML_Folder
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -28,11 +28,11 @@ namespace osgEarth_kml
 
     struct KML_Folder : public KML_Container
     {
-        virtual void scan( const Config& conf, KMLContext& cx );
+        virtual void scan( xml_node<>* node, KMLContext& cx );
 
-        virtual void scan2( const Config& conf, KMLContext& cx );
+        virtual void scan2( xml_node<>* node, KMLContext& cx );
 
-        virtual void build( const Config& conf, KMLContext& cx );
+        virtual void build( xml_node<>* node, KMLContext& cx );
     };
 
 } // namespace osgEarth_kml
diff --git a/src/osgEarthDrivers/kml/KML_Folder.cpp b/src/osgEarthDrivers/kml/KML_Folder.cpp
index bac34bb..e248bd9 100644
--- a/src/osgEarthDrivers/kml/KML_Folder.cpp
+++ b/src/osgEarthDrivers/kml/KML_Folder.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -28,29 +28,29 @@
 using namespace osgEarth_kml;
 
 void
-KML_Folder::scan( const Config& conf, KMLContext& cx )
+KML_Folder::scan( xml_node<>* node, KMLContext& cx )
 {
-    KML_Container::scan(conf, cx);
-    for_features( scan, conf, cx );
+    KML_Container::scan(node, cx);
+    for_features( scan, node, cx );
 }
 
 void
-KML_Folder::scan2( const Config& conf, KMLContext& cx )
+KML_Folder::scan2( xml_node<>* node, KMLContext& cx )
 {
-    KML_Container::scan2(conf, cx);
-    for_features( scan2, conf, cx );
+    KML_Container::scan2(node, cx);
+    for_features( scan2, node, cx );
 }
 
 void
-KML_Folder::build( const Config& conf, KMLContext& cx )
+KML_Folder::build( xml_node<>* node, KMLContext& cx )
 {
     // creates an empty group and pushes it on the stack.
     osg::Group* group = new osg::Group();
     cx._groupStack.top()->addChild( group );
     cx._groupStack.push( group );
 
-    KML_Container::build(conf, cx, group);
-    for_features(build, conf, cx);
+    KML_Container::build(node, cx, group);
+    for_features(build, node, cx);
 
     cx._groupStack.pop();
 }
diff --git a/src/osgEarthDrivers/kml/KML_Geometry b/src/osgEarthDrivers/kml/KML_Geometry
index 166025c..ac274bd 100644
--- a/src/osgEarthDrivers/kml/KML_Geometry
+++ b/src/osgEarthDrivers/kml/KML_Geometry
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -30,10 +30,10 @@ namespace osgEarth_kml
     struct KML_Geometry : public KML_Object
     {
         KML_Geometry() : _extrude(false), _tessellate(false) { }
-        virtual void parseCoords( const Config& conf, KMLContext& cx );
-        virtual void parseStyle( const Config& conf, KMLContext& cs, Style& style );
-        virtual void build( const Config& confParent, KMLContext& cx, Style& style );
-        void buildChild( const Config& conf, KMLContext& cx, Style& style );
+        virtual void parseCoords( xml_node<>* node, KMLContext& cx );
+        virtual void parseStyle( xml_node<>* node, KMLContext& cs, Style& style );
+        virtual void build( xml_node<>* node, KMLContext& cx, Style& style );
+        void buildChild( xml_node<>* node, KMLContext& cx, Style& style );
         osg::ref_ptr<Geometry> _geom;
         bool _extrude, _tessellate;
     private:    
diff --git a/src/osgEarthDrivers/kml/KML_Geometry.cpp b/src/osgEarthDrivers/kml/KML_Geometry.cpp
index a587116..f57a436 100644
--- a/src/osgEarthDrivers/kml/KML_Geometry.cpp
+++ b/src/osgEarthDrivers/kml/KML_Geometry.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -28,114 +28,156 @@
 using namespace osgEarth_kml;
 
 void 
-KML_Geometry::build( const Config& parentConf, KMLContext& cx, Style& style)
+KML_Geometry::build( xml_node<>* parent, KMLContext& cx, Style& style)
 {
-    const ConfigSet& children = parentConf.children();
-    for( ConfigSet::const_iterator i = children.begin(); i != children.end(); ++i )
-    {
-        buildChild( *i, cx, style );
-    }
+	for (xml_node<>* node = parent->first_node(); node; node = node->next_sibling())
+	{
+		buildChild(node, cx, style);
+	}
 }
 
 void
-KML_Geometry::buildChild( const Config& conf, KMLContext& cx, Style& style)
+KML_Geometry::buildChild( xml_node<>* node, KMLContext& cx, Style& style)
 {
-    if ( conf.key() == "point" )
+	std::string name = toLower(node->name());
+    if ( name == "point" )
     {
         KML_Point g;
-        g.parseCoords(conf, cx);
+        g.parseCoords(node, cx);
         _geom = g._geom.get();
-        g.parseStyle(conf, cx, style);
+        g.parseStyle(node, cx, style);
     }
-    else if ( conf.key() == "linestring" )
+    else if (name == "linestring" )
     {
         KML_LineString g;
-        g.parseCoords(conf, cx);
+        g.parseCoords(node, cx);
         _geom = g._geom.get();
-        g.parseStyle(conf, cx, style);
+        g.parseStyle(node, cx, style);
     }
-    else if ( conf.key() == "linearring" || conf.key() == "gx:latlonquad" )
+    else if ( name == "linearring" || name == "gx:latlonquad" )
     {
         KML_LinearRing g;
-        g.parseCoords(conf, cx);
+        g.parseCoords(node, cx);
         _geom = g._geom.get();
-        g.parseStyle(conf, cx, style);
+        g.parseStyle(node, cx, style);
     }
-    else if ( conf.key() == "polygon" )
+    else if ( name == "polygon" )
     {
         KML_Polygon g;
-        g.parseCoords(conf, cx);
+        g.parseCoords(node, cx);
         _geom = g._geom.get();
-        g.parseStyle(conf, cx, style);
+        g.parseStyle(node, cx, style);
     }
-    else if ( conf.key() == "multigeometry" )
+    else if ( name == "multigeometry" )
     {
         KML_MultiGeometry g;
-        g.parseCoords(conf, cx);
+        g.parseCoords(node, cx);
         _geom = g._geom.get();
-        g.parseStyle(conf, cx, style);
-        const ConfigSet& mgChildren = conf.children();
+        g.parseStyle(node, cx, style);
         
-        for( ConfigSet::const_iterator i = mgChildren.begin(); i != mgChildren.end(); ++i )
+        for( xml_node<>* n = node->first_node(); n; n = n->next_sibling())
         {
-            const Config& mgChild = *i;
             Style subStyle = style;
             KML_Geometry subGeom;
-            subGeom.parseStyle( mgChild, cx, subStyle );
-            subGeom.buildChild( mgChild, cx, style );
+            subGeom.parseStyle( n, cx, subStyle );
+            subGeom.buildChild( n, cx, style );
             if ( subGeom._geom.valid() )
                 dynamic_cast<MultiGeometry*>(g._geom.get())->getComponents().push_back( subGeom._geom.get() );
         }
     }
-    else if ( conf.key() == "model" )
+    else if ( name == "model" )
     {
         KML_Model g;
-        g.parseCoords(conf, cx);
+        g.parseCoords(node, cx);
         _geom = g._geom.get();
-        g.parseStyle(conf, cx, style);
+        g.parseStyle(node, cx, style);
     }
 }
 
 void
-KML_Geometry::parseCoords( const Config& conf, KMLContext& cx )
+KML_Geometry::parseCoords( xml_node<>* node, KMLContext& cx )
 {
-    const Config& coords = conf.child("coordinates");
-    StringVector tuples;
-    StringTokenizer( coords.value(), tuples, " ", "", false, true );
-    for( StringVector::const_iterator s=tuples.begin(); s != tuples.end(); ++s )
+    xml_node<>* coords = node->first_node("coordinates", 0, false);
+    if ( coords )
     {
-        StringVector parts;
-        StringTokenizer( *s, parts, ",", "", false, true );
-        if ( parts.size() >= 2 )
+        StringVector tuples;
+        StringTokenizer( coords->value(), tuples, " \n", "", false, true );
+        for( StringVector::const_iterator s=tuples.begin(); s != tuples.end(); ++s )
         {
-            osg::Vec3d point;
-            point.x() = as<double>( parts[0], 0.0 );
-            point.y() = as<double>( parts[1], 0.0 );
-            if ( parts.size() >= 3 ) {
-                point.z() = as<double>( parts[2], 0.0 );
+            StringVector parts;
+            StringTokenizer( *s, parts, ",", "", false, true );
+            if ( parts.size() >= 2 )
+            {
+                osg::Vec3d point;
+                point.x() = as<double>( parts[0], 0.0 );
+                point.y() = as<double>( parts[1], 0.0 );
+                if ( parts.size() >= 3 ) {
+                    point.z() = as<double>( parts[2], 0.0 );
+                }
+                _geom->push_back(point);
             }
-            _geom->push_back(point);
         }
     }
 }
 
 void
-KML_Geometry::parseStyle( const Config& conf, KMLContext& cx, Style& style )
+KML_Geometry::parseStyle( xml_node<>* node, KMLContext& cx, Style& style )
 {
-    _extrude = conf.value("extrude") == "1";
-    _tessellate = conf.value("tessellate") == "1";
+    _extrude = getValue(node, "extrude") == "1";
+    _tessellate = getValue(node, "tessellate") == "1";
 
-    std::string am = conf.value("altitudemode");
+    std::string am = getValue(node, "altitudemode");
     if ( am.empty() )
         am = "clampToGround"; // default.
 
-    bool isPoly = _geom->getComponentType() == Geometry::TYPE_POLYGON;
+    bool isPoly = _geom.valid() && _geom->getComponentType() == Geometry::TYPE_POLYGON;
 
     // Resolve the correct altitude symbol. CLAMP_TO_TERRAIN is the default, but the
     // technique will depend on the geometry's type and setup.
     AltitudeSymbol* alt = style.getOrCreate<AltitudeSymbol>();
     alt->clamping() = alt->CLAMP_TO_TERRAIN;
 
+
+    // Compute some info about the geometry
+
+    // Are all of the elevations zero?
+    bool zeroElev = true;
+    // Are all of the the elevations the same?
+    bool sameElev = true;
+
+    double maxElevation = -DBL_MAX;
+
+    if ( isPoly )
+    {
+        bool first = true;
+        double e = 0.0;
+        ConstGeometryIterator gi( _geom.get(), false );
+        while(gi.hasMore() )
+        {
+            const Geometry* g = gi.next();
+            for( Geometry::const_iterator ji = g->begin(); ji != g->end(); ++ji )
+            {
+                if ( !osg::equivalent(ji->z(), 0.0) )
+                    zeroElev = false;
+
+                if (first)
+                {
+                    first = false;
+                    e = ji->z();
+                }
+                else
+                {
+                    if (!osg::equivalent(e, ji->z()))
+                    {
+                        sameElev = false;
+                    }
+                }
+
+                if (ji->z() > maxElevation) maxElevation = ji->z();
+            }
+        }
+    }
+
     // clamp to ground mode:
     if ( am == "clampToGround" )
     {
@@ -163,27 +205,22 @@ KML_Geometry::parseStyle( const Config& conf, KMLContext& cx, Style& style )
     {
         alt->clamping() = alt->CLAMP_RELATIVE_TO_TERRAIN;
 
-        if ( _extrude )
-        {
-            alt->technique() = alt->TECHNIQUE_MAP;
-        }
-        else
+        if (isPoly)
         {
-            alt->technique() = alt->TECHNIQUE_SCENE;
+            // If all of the verts have the same elevation then assume that it should be clamped at the centroid and not per vertex.
+            if (sameElev)
+            {
+                alt->binding() = AltitudeSymbol::BINDING_CENTROID;
+            }
 
-            if ( isPoly )
+            if ( _extrude )
             {
-                bool zeroElev = true;
-                ConstGeometryIterator gi( _geom.get(), false );
-                while( zeroElev == true && gi.hasMore() )
-                {
-                    const Geometry* g = gi.next();
-                    for( Geometry::const_iterator ji = g->begin(); ji != g->end() && zeroElev == true; ++ji )
-                    {
-                        if ( !osg::equivalent(ji->z(), 0.0) )
-                            zeroElev = false;
-                    }
-                }
+                alt->technique() = alt->TECHNIQUE_MAP;
+            }
+            else
+            {
+                alt->technique() = alt->TECHNIQUE_SCENE;
+
                 if ( zeroElev )
                 {
                     alt->clamping()  = alt->CLAMP_TO_TERRAIN;
@@ -196,21 +233,19 @@ KML_Geometry::parseStyle( const Config& conf, KMLContext& cx, Style& style )
     // "absolute" means to treat the Z values as-is
     else if ( am == "absolute" )
     {
-        if ( _extrude )
-        {
-            alt->clamping() = alt->CLAMP_ABSOLUTE;
-            alt->technique() = alt->TECHNIQUE_MAP;
-        }
-        else
-        {
-            alt->clamping() = AltitudeSymbol::CLAMP_NONE;
-        }
+        alt->clamping() = AltitudeSymbol::CLAMP_NONE;
     }
 
     if ( _extrude )
     {
         ExtrusionSymbol* es = style.getOrCreate<ExtrusionSymbol>();
         es->flatten() = false;
+        if (*alt->clamping() == AltitudeSymbol::CLAMP_NONE)
+        {
+            // Set the height to the max elevation + the approx depth of the mariana trench so that it will extend low enough to be always go to the surface of the earth.
+            // This lets us avoid clamping absolute absolute extruded polygons completely.
+            es->height() = -(maxElevation + 11100.0);
+        }
     }
     else
     {
diff --git a/src/osgEarthDrivers/kml/KML_GroundOverlay b/src/osgEarthDrivers/kml/KML_GroundOverlay
index 3b089dd..e4cafa8 100644
--- a/src/osgEarthDrivers/kml/KML_GroundOverlay
+++ b/src/osgEarthDrivers/kml/KML_GroundOverlay
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -28,8 +28,8 @@ namespace osgEarth_kml
 
     struct KML_GroundOverlay : public KML_Overlay
     {
-        virtual void scan( const Config& conf, KMLContext& cx );
-        virtual void build( const Config& conf, KMLContext& cx );
+        virtual void scan( xml_node<>* node, KMLContext& cx );
+        virtual void build( xml_node<>* node, KMLContext& cx );
     };
 
 } // namespace osgEarth_kml
diff --git a/src/osgEarthDrivers/kml/KML_GroundOverlay.cpp b/src/osgEarthDrivers/kml/KML_GroundOverlay.cpp
index 0e6e409..85a8405 100644
--- a/src/osgEarthDrivers/kml/KML_GroundOverlay.cpp
+++ b/src/osgEarthDrivers/kml/KML_GroundOverlay.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -24,16 +24,22 @@ using namespace osgEarth_kml;
 using namespace osgEarth::Annotation;
 
 void
-KML_GroundOverlay::scan( const Config& conf, KMLContext& cx )
+KML_GroundOverlay::scan( xml_node<>* node, KMLContext& cx )
 {
-    KML_Overlay::scan( conf, cx );
+    KML_Overlay::scan( node, cx );
 }
 
 void
-KML_GroundOverlay::build( const Config& conf, KMLContext& cx )
+KML_GroundOverlay::build( xml_node<>* node, KMLContext& cx )
 {
     // the URL of the overlay image
-    std::string href = conf.child("icon").value("href");
+	xml_node<>* icon = node->first_node("icon", 0, false);
+	std::string href;
+	if (icon)
+	{
+		href = getValue(icon, "href");
+	}
+
     if ( href.empty() ) {
         OE_WARN << LC << "GroundOverlay missing required Icon element" << std::endl;
         return;
@@ -42,17 +48,18 @@ KML_GroundOverlay::build( const Config& conf, KMLContext& cx )
     ImageOverlay* im = 0L;
 
     // the extent of the overlay image
-    const Config& llb = conf.child("latlonbox");
-    if ( !llb.empty() )
+	xml_node<>* llb = node->first_node("latlonbox", 0, false);
+	xml_node<>* llq = node->first_node("gx:latlonquad", 0, false);
+    if ( llb)
     {
-        double north = llb.value<double>("north", 0.0);
-        double south = llb.value<double>("south", 0.0);
-        double east  = llb.value<double>("east", 0.0);
-        double west  = llb.value<double>("west", 0.0);
-        Angular rotation( -llb.value<double>("rotation", 0.0), Units::DEGREES );
+        double north = as<double>(getValue(llb, "north"), 0.0);
+        double south = as<double>(getValue(llb, "south"), 0.0);
+        double east  = as<double>(getValue(llb, "east"), 0.0);;
+        double west  = as<double>(getValue(llb, "west"), 0.0);;
+        Angular rotation( -as<double>(getValue(llb, "rotation"), 0.0), Units::DEGREES );
 
-        osg::ref_ptr<osg::Image> image = URI(href, conf.referrer()).readImage().getImage();
-        if ( !image.valid() )
+		osg::ref_ptr<osg::Image> image = URI(href, cx._referrer).readImage().getImage();
+		if ( !image.valid() )
         {
             OE_WARN << LC << "GroundOverlay failed to read image from " << href << std::endl;
             return;
@@ -63,16 +70,15 @@ KML_GroundOverlay::build( const Config& conf, KMLContext& cx )
         cx._groupStack.top()->addChild( im );
     }
 
-    else if ( conf.hasChild("gx:latlonquad") )
+    else if ( llq )
     {
-        const Config& llq = conf.child("gx:latlonquad");
         KML_Geometry g;
         Style style;
         g.buildChild( llq, cx, style );
         if ( g._geom.valid() && g._geom->size() >= 4 )
         {
-            osg::ref_ptr<osg::Image> image = URI(href, conf.referrer()).readImage().getImage();
-            if ( !image.valid() )
+            osg::ref_ptr<osg::Image> image = URI(href, cx._referrer).readImage().getImage();
+		    if ( !image.valid() )
             {
                 OE_WARN << LC << "GroundOverlay failed to read image from " << href << std::endl;
                 return;
@@ -96,5 +102,5 @@ KML_GroundOverlay::build( const Config& conf, KMLContext& cx )
 
 
     // superclass build always called last
-    KML_Overlay::build( conf, cx, im );
+    KML_Overlay::build( node, cx, im );
 }
diff --git a/src/osgEarthDrivers/kml/KML_IconStyle b/src/osgEarthDrivers/kml/KML_IconStyle
index 8e081d9..01e15c8 100644
--- a/src/osgEarthDrivers/kml/KML_IconStyle
+++ b/src/osgEarthDrivers/kml/KML_IconStyle
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -27,7 +27,7 @@ namespace osgEarth_kml
 
     struct KML_IconStyle : public KML_Object
     {
-        virtual void scan( const Config& conf, Style& style, KMLContext& cx );
+        virtual void scan( xml_node<>* node, Style& style, KMLContext& cx );
     };
 
 } // namespace osgEarth_kml
diff --git a/src/osgEarthDrivers/kml/KML_IconStyle.cpp b/src/osgEarthDrivers/kml/KML_IconStyle.cpp
index b8fa5cd..c197552 100644
--- a/src/osgEarthDrivers/kml/KML_IconStyle.cpp
+++ b/src/osgEarthDrivers/kml/KML_IconStyle.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -22,27 +22,36 @@
 using namespace osgEarth_kml;
 
 void
-KML_IconStyle::scan( const Config& conf, Style& style, KMLContext& cx )
+KML_IconStyle::scan( xml_node<>* node, Style& style, KMLContext& cx )
 {
-    if ( !conf.empty() )
+    if ( node )
     {
         IconSymbol* icon = style.getOrCreate<IconSymbol>();
 
         // Icon/Href or just Icon are both valid
-        std::string iconHref = conf.child("icon").value("href");
-        if ( iconHref.empty() )
-            iconHref = conf.value("icon");
+		std::string iconHref;
+		xml_node<>* iconNode = node->first_node("icon", 0, false);
+		if (iconNode)
+		{
+			iconHref = getValue(iconNode, "href");
+			if ( iconHref.empty() )
+				iconHref = getValue(node, "icon");
+		}
 
         if ( !iconHref.empty() )
-            icon->url() = StringExpression( iconHref, URIContext(conf.referrer()) );
-
+        {
+            icon->url() = StringExpression( iconHref, URIContext(cx._referrer) );
+        }
+			
         // see: https://developers.google.com/kml/documentation/kmlreference#headingdiagram
-        if ( conf.hasValue("heading") )
-            icon->heading() = NumericExpression( conf.value("heading") );
+		std::string heading = getValue(node, "heading");
+        if ( !heading.empty() )
+            icon->heading() = NumericExpression( heading );
 
         float finalScale = *cx._options->iconBaseScale();
 
-        if ( conf.hasValue("scale") )
-            icon->scale() = NumericExpression( conf.value("scale") );
+		std::string scale = getValue(node, "scale");
+        if ( !scale.empty() )
+            icon->scale() = NumericExpression( scale );
     }
 }
diff --git a/src/osgEarthDrivers/kml/KML_LabelStyle b/src/osgEarthDrivers/kml/KML_LabelStyle
index 2b9e7fd..850010d 100644
--- a/src/osgEarthDrivers/kml/KML_LabelStyle
+++ b/src/osgEarthDrivers/kml/KML_LabelStyle
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -27,7 +27,7 @@ namespace osgEarth_kml
 
     struct KML_LabelStyle : public KML_Object
     {
-        virtual void scan( const Config& conf, Style& style, KMLContext& cx );
+        virtual void scan( xml_node<>* node, Style& style, KMLContext& cx );
     };
 
 } // namespace osgEarth_kml
diff --git a/src/osgEarthDrivers/kml/KML_LabelStyle.cpp b/src/osgEarthDrivers/kml/KML_LabelStyle.cpp
index 0d7391f..c2ce119 100644
--- a/src/osgEarthDrivers/kml/KML_LabelStyle.cpp
+++ b/src/osgEarthDrivers/kml/KML_LabelStyle.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -21,6 +21,6 @@
 using namespace osgEarth_kml;
 
 void 
-KML_LabelStyle::scan( const Config& conf, Style& style, KMLContext& cx )
+KML_LabelStyle::scan( xml_node<>* node, Style& style, KMLContext& cx )
 {
 }
diff --git a/src/osgEarthDrivers/kml/KML_LineString b/src/osgEarthDrivers/kml/KML_LineString
index 1a7094c..62aef5e 100644
--- a/src/osgEarthDrivers/kml/KML_LineString
+++ b/src/osgEarthDrivers/kml/KML_LineString
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -27,8 +27,8 @@ namespace osgEarth_kml
 
     struct KML_LineString : public KML_Geometry
     {
-        virtual void parseStyle( const Config& conf, KMLContext& cs, Style& style );
-        virtual void parseCoords( const Config& conf, KMLContext& cx );
+        virtual void parseStyle( xml_node<>* node, KMLContext& cs, Style& style );
+        virtual void parseCoords( xml_node<>* node, KMLContext& cx );
     };
 
 } // namespace osgEarth_kml
diff --git a/src/osgEarthDrivers/kml/KML_LineString.cpp b/src/osgEarthDrivers/kml/KML_LineString.cpp
index e6839cc..ce61ad0 100644
--- a/src/osgEarthDrivers/kml/KML_LineString.cpp
+++ b/src/osgEarthDrivers/kml/KML_LineString.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -21,9 +21,9 @@
 using namespace osgEarth_kml;
 
 void
-KML_LineString::parseStyle( const Config& conf, KMLContext& cs, Style& style )
+KML_LineString::parseStyle( xml_node<>* node, KMLContext& cs, Style& style )
 {
-    KML_Geometry::parseStyle(conf, cs, style);
+    KML_Geometry::parseStyle(node, cs, style);
 
     // need a line symbol minimally
     LineSymbol* line = style.get<LineSymbol>();
@@ -33,15 +33,15 @@ KML_LineString::parseStyle( const Config& conf, KMLContext& cs, Style& style )
         line->stroke()->color() = osg::Vec4f(1,1,1,1);
     }
 
-    if ( conf.value("tessellate") == "1" )
+    if ( getValue(node, "tessellate") == "1" )
     {
         line->tessellation() = 20; // KML default
     }
 }
 
 void
-KML_LineString::parseCoords( const Config& conf, KMLContext& cx )
+KML_LineString::parseCoords( xml_node<>* node, KMLContext& cx )
 {
     _geom = new LineString();
-    KML_Geometry::parseCoords( conf, cx );
+    KML_Geometry::parseCoords( node, cx );
 }
diff --git a/src/osgEarthDrivers/kml/KML_LineStyle b/src/osgEarthDrivers/kml/KML_LineStyle
index ee3a474..c135ccf 100644
--- a/src/osgEarthDrivers/kml/KML_LineStyle
+++ b/src/osgEarthDrivers/kml/KML_LineStyle
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -27,7 +27,7 @@ namespace osgEarth_kml
 
     struct KML_LineStyle : public KML_Object
     {
-        virtual void scan( const Config& conf, Style& style, KMLContext& cx );
+        virtual void scan( xml_node<>* node, Style& style, KMLContext& cx );
     };
 
 } //namespace osgEarth_kml
diff --git a/src/osgEarthDrivers/kml/KML_LineStyle.cpp b/src/osgEarthDrivers/kml/KML_LineStyle.cpp
index 90b84dc..caa583e 100644
--- a/src/osgEarthDrivers/kml/KML_LineStyle.cpp
+++ b/src/osgEarthDrivers/kml/KML_LineStyle.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -21,18 +21,20 @@
 using namespace osgEarth_kml;
 
 void 
-KML_LineStyle::scan( const Config& conf, Style& style, KMLContext& cx )
+KML_LineStyle::scan( xml_node<>* node, Style& style, KMLContext& cx )
 {
-    if ( !conf.empty() )
+    if ( node )
     {
         LineSymbol* line = style.getOrCreate<LineSymbol>();
-        if ( conf.hasValue("color") )
+		std::string color = getValue(node, "color");
+        if ( !color.empty() )
         {
-            line->stroke()->color() = Color( Stringify() << "#" << conf.value("color"), Color::ABGR );
+            line->stroke()->color() = Color( Stringify() << "#" << color, Color::ABGR );
         }
-        if ( conf.hasValue("width") )
+		std::string width = getValue(node, "width");
+        if ( !width.empty() )
         {
-            line->stroke()->width() = as<float>( conf.value("width"), 1.0f );
+            line->stroke()->width() = as<float>( width, 1.0f );
         }
     }
 }
diff --git a/src/osgEarthDrivers/kml/KML_LinearRing b/src/osgEarthDrivers/kml/KML_LinearRing
index f7e302c..a14fed9 100644
--- a/src/osgEarthDrivers/kml/KML_LinearRing
+++ b/src/osgEarthDrivers/kml/KML_LinearRing
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -27,8 +27,8 @@ namespace osgEarth_kml
 
     struct KML_LinearRing : public KML_Geometry
     {
-        virtual void parseStyle( const Config& conf, KMLContext& cs, Style& style );
-        virtual void parseCoords( const Config& conf, KMLContext& cx );
+        virtual void parseStyle( xml_node<>* node, KMLContext& cs, Style& style );
+        virtual void parseCoords( xml_node<>* node, KMLContext& cx );
     };
 
 } // namespace osgEarth_kml
diff --git a/src/osgEarthDrivers/kml/KML_LinearRing.cpp b/src/osgEarthDrivers/kml/KML_LinearRing.cpp
index 9a21114..cd51d74 100644
--- a/src/osgEarthDrivers/kml/KML_LinearRing.cpp
+++ b/src/osgEarthDrivers/kml/KML_LinearRing.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -21,9 +21,9 @@
 using namespace osgEarth_kml;
 
 void
-KML_LinearRing::parseStyle( const Config& conf, KMLContext& cs, Style& style )
+KML_LinearRing::parseStyle( xml_node<>* node, KMLContext& cs, Style& style )
 {
-    KML_Geometry::parseStyle(conf, cs, style);
+    KML_Geometry::parseStyle(node, cs, style);
 
     // need a line symbol minimally
     LineSymbol* line = style.get<LineSymbol>();
@@ -33,15 +33,15 @@ KML_LinearRing::parseStyle( const Config& conf, KMLContext& cs, Style& style )
         line->stroke()->color() = osg::Vec4f(1,1,1,1);
     }
 
-    if ( conf.value("tessellate") == "1" )
+    if ( getValue(node, "tessellate") == "1" )
     {
         line->tessellation() = 20;
     }
 }
 
 void
-KML_LinearRing::parseCoords( const Config& conf, KMLContext& cx )
+KML_LinearRing::parseCoords( xml_node<>* node, KMLContext& cx )
 {
     _geom = new Ring();
-    KML_Geometry::parseCoords( conf, cx );
+    KML_Geometry::parseCoords( node, cx );
 }
diff --git a/src/osgEarthDrivers/kml/KML_Model b/src/osgEarthDrivers/kml/KML_Model
index 368ff97..098c8d7 100644
--- a/src/osgEarthDrivers/kml/KML_Model
+++ b/src/osgEarthDrivers/kml/KML_Model
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -27,8 +27,8 @@ namespace osgEarth_kml
 
     struct KML_Model : public KML_Geometry
     {
-        void parseCoords( const Config& conf, KMLContext& cx );
-        void parseStyle(const Config& conf, KMLContext& cx, Style& style);
+        void parseCoords( xml_node<>* node, KMLContext& cx );
+        void parseStyle(xml_node<>* node, KMLContext& cx, Style& style);
     };
 
 } // namespace osgEarth_kml
diff --git a/src/osgEarthDrivers/kml/KML_Model.cpp b/src/osgEarthDrivers/kml/KML_Model.cpp
index 3f624ce..fadebc9 100644
--- a/src/osgEarthDrivers/kml/KML_Model.cpp
+++ b/src/osgEarthDrivers/kml/KML_Model.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -24,76 +24,76 @@ using namespace osgEarth_kml;
 using namespace osgEarth::Symbology;
 
 void
-KML_Model::parseCoords( const Config& conf, KMLContext& cx )
+KML_Model::parseCoords( xml_node<>* node, KMLContext& cx )
 {
     PointSet* point = new PointSet();
 
-    Config location = conf.child("location");
-    if (!location.empty())
+    xml_node<>* location = node->first_node("location", 0, false);
+    if (location)
     {
-        double latitude  = location.value("latitude",  0.0);
-        double longitude = location.value("longitude", 0.0);
-        double altitude  = location.value("altitude", 0.0); 
+        double latitude  = as<double>(getValue(location, "latitude"), 0.0);
+        double longitude = as<double>(getValue(location, "longitude"), 0.0);
+        double altitude  = as<double>(getValue(location, "altitude"), 0.0);
         point->push_back( osg::Vec3d(longitude, latitude, altitude));
     }    
     _geom = point;
 }
 
 void
-KML_Model::parseStyle(const Config& conf, KMLContext& cx, Style& style)
+KML_Model::parseStyle(xml_node<>* node, KMLContext& cx, Style& style)
 {    
     ModelSymbol* model = 0L;
     
-    std::string url = KMLUtils::parseLink(conf);
+    std::string url = KMLUtils::parseLink(node);
     if ( !url.empty() )
     {
         if ( !model ) model = style.getOrCreate<ModelSymbol>();
         model->url()->setLiteral( url );
-        model->url()->setURIContext( URIContext(conf.referrer()) );
+        model->url()->setURIContext( URIContext(cx._referrer) );
+
     }
 
-    Config scale = conf.child("scale");
-    if (!scale.empty())
+    xml_node<>* scale = node->first_node("scale", 0, false);
+    if (scale)
     {
         if ( !model ) model = style.getOrCreate<ModelSymbol>();
         //TODO:  Support XYZ scale instead of single value
-        model->scale() = scale.value("x", 1.0);
+        model->scale() = as<double>(getValue(scale, "x"), 1.0);
     }
 
-    Config orientation = conf.child("orientation");
-    if (!orientation.empty())
+    xml_node<>* orientation = node->first_node("orientation", 0, false);
+    if (orientation)
     {
         if ( !model ) model = style.getOrCreate<ModelSymbol>();
         
-        double h = orientation.value("heading", 0);
+        double h = as<double>(getValue(orientation, "heading"), 0.0);
         if ( !osg::equivalent(h, 0.0) )
             model->heading() = NumericExpression( h );
 
-        double p = orientation.value("tilt", 0);
+        double p = as<double>(getValue(orientation, "tilt"), 0.0);
         if ( !osg::equivalent(p, 0.0) )
             model->pitch() = NumericExpression( p );
 
-        double r = orientation.value("roll", 0);
+        double r = as<double>(getValue(orientation, "roll"), 0.0);
         if ( !osg::equivalent(r, 0.0) )
             model->roll() = NumericExpression( r );
     }
 
     // Read and store file path aliases from a KML ResourceMap.
-    Config resource_map = conf.child("resourcemap");
-    if ( !resource_map.empty() )
+    xml_node<>* resource_map = node->first_node("resourcemap", 0, false);
+    if ( resource_map )
     {
-        const ConfigSet aliases = resource_map.children("alias");
-        for( ConfigSet::const_iterator i = aliases.begin(); i != aliases.end(); ++i )
-        {
-            std::string source = i->value("sourcehref");
-            std::string target = i->value("targethref");
+		for (xml_node<>* n = resource_map->first_node("alias", 0, false); n; n = n->next_sibling("alias", 0, false))
+		{
+			std::string source = getValue(n, "sourcehref");
+            std::string target = getValue(n, "targethref");
             if ( !source.empty() || !target.empty() )
             {
                 if ( !model ) model = style.getOrCreate<ModelSymbol>();
                 model->uriAliasMap()->insert( source, target );
             }
-        }
+		}
     }
 
-    KML_Geometry::parseStyle(conf, cx, style);
+    KML_Geometry::parseStyle(node, cx, style);
 }
diff --git a/src/osgEarthDrivers/kml/KML_MultiGeometry b/src/osgEarthDrivers/kml/KML_MultiGeometry
index 04e813e..6d33509 100644
--- a/src/osgEarthDrivers/kml/KML_MultiGeometry
+++ b/src/osgEarthDrivers/kml/KML_MultiGeometry
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -27,7 +27,7 @@ namespace osgEarth_kml
 
     struct KML_MultiGeometry : public KML_Geometry
     {
-        virtual void parseCoords( const Config& conf, KMLContext& cx );
+        virtual void parseCoords( xml_node<>* node, KMLContext& cx );
     };
 
 } // namespace osgEarth_kml
diff --git a/src/osgEarthDrivers/kml/KML_MultiGeometry.cpp b/src/osgEarthDrivers/kml/KML_MultiGeometry.cpp
index 44d4fab..5f75d82 100644
--- a/src/osgEarthDrivers/kml/KML_MultiGeometry.cpp
+++ b/src/osgEarthDrivers/kml/KML_MultiGeometry.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -21,7 +21,7 @@
 using namespace osgEarth_kml;
 
 void
-KML_MultiGeometry::parseCoords( const Config& conf, KMLContext& cx )
+KML_MultiGeometry::parseCoords( xml_node<>* node, KMLContext& cx )
 {
     _geom = new MultiGeometry();
     //KML_Geometry::parseCoords( conf, cx );
diff --git a/src/osgEarthDrivers/kml/KML_NetworkLink b/src/osgEarthDrivers/kml/KML_NetworkLink
index aa6952b..cd29745 100644
--- a/src/osgEarthDrivers/kml/KML_NetworkLink
+++ b/src/osgEarthDrivers/kml/KML_NetworkLink
@@ -26,7 +26,7 @@ using namespace osgEarth;
 
 struct KML_NetworkLink : public KML_Object
 {
-    virtual void build( const Config& conf, KMLContext& cx );
+    virtual void build( xml_node<>* node, KMLContext& cx );
 };
 
 #endif // OSGEARTH_DRIVER_KML_KML_NETWORKLINK
diff --git a/src/osgEarthDrivers/kml/KML_NetworkLink.cpp b/src/osgEarthDrivers/kml/KML_NetworkLink.cpp
index f266ca1..5aaadf1 100644
--- a/src/osgEarthDrivers/kml/KML_NetworkLink.cpp
+++ b/src/osgEarthDrivers/kml/KML_NetworkLink.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -29,32 +29,34 @@
 using namespace osgEarth_kml;
 
 void
-KML_NetworkLink::build( const Config& conf, KMLContext& cx )
+KML_NetworkLink::build( xml_node<>* node, KMLContext& cx )
 {
-    std::string name = conf.value("name");
+	if (!node) return;
+
+    std::string name = getValue(node, "name");
 
     // parse the link:
-    std::string href = KMLUtils::parseLink(conf);
+    std::string href = KMLUtils::parseLink(node);
 
     // "open" determines whether to load it immediately
-    bool open = conf.value<bool>("open", false);
+    bool open = as<bool>(getValue(node, "open"), false);
 
     // if it's region-bound, parse it as a paged LOD:
-    const Config& regionConf = conf.child("region");
-    if ( !regionConf.empty() )
+    xml_node<>* region = node->first_node("region", 0, false);
+    if ( region )
     {
-        const Config& llaBoxConf = regionConf.child("latlonaltbox");
-        if ( llaBoxConf.empty() )
+        xml_node<>* llaBox = region->first_node("latlonaltbox", 0, false);
+        if ( !llaBox )
             return;
 
         const SpatialReference* geoSRS = cx._mapNode->getMapSRS()->getGeographicSRS();
 
         GeoExtent llaExtent(
             geoSRS,
-            llaBoxConf.value<double>("west",  0.0),
-            llaBoxConf.value<double>("south", 0.0),
-            llaBoxConf.value<double>("east",  0.0),
-            llaBoxConf.value<double>("north", 0.0) );
+            as<double>(getValue(llaBox, "west"), 0.0),
+			as<double>(getValue(llaBox, "south"), 0.0),
+			as<double>(getValue(llaBox, "east"), 0.0),
+			as<double>(getValue(llaBox, "north"), 0.0));
 
         // find the ECEF LOD center point:
         double x, y;
@@ -70,14 +72,14 @@ KML_NetworkLink::build( const Config& conf, KMLContext& cx )
 
         // parse the LOD ranges:
         float minRange = 0, maxRange = 1e6;
-        const Config& lodConf = regionConf.child("lod");
-        if ( !lodConf.empty() ) 
+        xml_node<>* lod = region->first_node("lod", 0, false);
+        if ( lod ) 
         {
             // swapped
-            minRange = lodConf.value<float>( "minlodpixels", 0.0f );
+            minRange = as<float>(getValue(lod, "minlodpixels"), 0.0f);
             if ( minRange < 0.0f )
                 minRange = 0.0f;
-            maxRange = lodConf.value<float>( "maxlodpixels", FLT_MAX );
+			maxRange = as<float>(getValue(lod, "maxlodpixels"), FLT_MAX);
             if ( maxRange < 0.0f )
                 maxRange = FLT_MAX;
         }
diff --git a/src/osgEarthDrivers/kml/KML_NetworkLinkControl b/src/osgEarthDrivers/kml/KML_NetworkLinkControl
index 8463a18..ac2d6ba 100644
--- a/src/osgEarthDrivers/kml/KML_NetworkLinkControl
+++ b/src/osgEarthDrivers/kml/KML_NetworkLinkControl
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -27,8 +27,8 @@ namespace osgEarth_kml
 
     struct KML_NetworkLinkControl : public KML_Object
     {
-        virtual void scan( const Config& conf, KMLContext& cx );
-        virtual void build( const Config& conf, KMLContext& cx );
+        virtual void scan( xml_node<>* node, KMLContext& cx );
+        virtual void build( xml_node<>* node, KMLContext& cx );
     };
 
 } // namespace osgEarth_kml
diff --git a/src/osgEarthDrivers/kml/KML_NetworkLinkControl.cpp b/src/osgEarthDrivers/kml/KML_NetworkLinkControl.cpp
index 855550f..6e04eae 100644
--- a/src/osgEarthDrivers/kml/KML_NetworkLinkControl.cpp
+++ b/src/osgEarthDrivers/kml/KML_NetworkLinkControl.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -21,13 +21,13 @@
 using namespace osgEarth_kml;
 
 void
-KML_NetworkLinkControl::scan( const Config& conf, KMLContext& cx )
+KML_NetworkLinkControl::scan( xml_node<>* node, KMLContext& cx )
 {
-    KML_Object::scan( conf, cx );
+    KML_Object::scan( node, cx );
 }
 
 void
-KML_NetworkLinkControl::build( const Config& conf, KMLContext& cx )
+KML_NetworkLinkControl::build( xml_node<>* node, KMLContext& cx )
 {
-    KML_Object::build( conf, cx, 0L );
+    KML_Object::build( node, cx, 0L );
 }
diff --git a/src/osgEarthDrivers/kml/KML_Object b/src/osgEarthDrivers/kml/KML_Object
index 719dd91..7272d16 100644
--- a/src/osgEarthDrivers/kml/KML_Object
+++ b/src/osgEarthDrivers/kml/KML_Object
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -29,11 +29,11 @@ namespace osgEarth_kml
 
     struct KML_Object
     {
-        virtual void scan( const Config& conf, KMLContext& cx ) { }
+        virtual void scan( xml_node<>* node, KMLContext& cx ) { }
         
-        virtual void scan2( const Config& conf, KMLContext& cx ) { }
+        virtual void scan2( xml_node<>* node, KMLContext& cx ) { }
 
-        virtual void build( const Config& conf, KMLContext& cx, osg::Node* working );
+        virtual void build( xml_node<>* node, KMLContext& cx, osg::Node* working );
 
         virtual ~KML_Object() { }
 
diff --git a/src/osgEarthDrivers/kml/KML_Object.cpp b/src/osgEarthDrivers/kml/KML_Object.cpp
index dc8f7b1..b3ea4cf 100644
--- a/src/osgEarthDrivers/kml/KML_Object.cpp
+++ b/src/osgEarthDrivers/kml/KML_Object.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -21,7 +21,7 @@
 using namespace osgEarth_kml;
 
 void
-KML_Object::build( const Config& conf, KMLContext& cx, osg::Node* working )
+KML_Object::build( xml_node<>* node, KMLContext& cx, osg::Node* working )
 {
     //todo - read ID
 }
diff --git a/src/osgEarthDrivers/kml/KML_Overlay b/src/osgEarthDrivers/kml/KML_Overlay
index 53f753e..6a00ce4 100644
--- a/src/osgEarthDrivers/kml/KML_Overlay
+++ b/src/osgEarthDrivers/kml/KML_Overlay
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -28,8 +28,8 @@ namespace osgEarth_kml
 
     struct KML_Overlay : public KML_Feature
     {
-        virtual void scan( const Config& conf, KMLContext& cx );
-        virtual void build( const Config& conf, KMLContext& cx, osg::Node* working );
+        virtual void scan( xml_node<>* node, KMLContext& cx );
+        virtual void build( xml_node<>* node, KMLContext& cx, osg::Node* working );
     };
 
 } // namespace osgEarth_kml
diff --git a/src/osgEarthDrivers/kml/KML_Overlay.cpp b/src/osgEarthDrivers/kml/KML_Overlay.cpp
index 92ec346..50158f8 100644
--- a/src/osgEarthDrivers/kml/KML_Overlay.cpp
+++ b/src/osgEarthDrivers/kml/KML_Overlay.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -21,13 +21,13 @@
 using namespace osgEarth_kml;
 
 void
-KML_Overlay::scan( const Config& conf, KMLContext& cx )
+KML_Overlay::scan( xml_node<>* node, KMLContext& cx )
 {
-    KML_Feature::scan( conf, cx );
+    KML_Feature::scan( node, cx );
 }
 
 void
-KML_Overlay::build( const Config& conf, KMLContext& cx, osg::Node* working )
+KML_Overlay::build( xml_node<>* node, KMLContext& cx, osg::Node* working )
 {
-    KML_Feature::build( conf, cx, working );
+    KML_Feature::build( node, cx, working );
 }
diff --git a/src/osgEarthDrivers/kml/KML_PhotoOverlay b/src/osgEarthDrivers/kml/KML_PhotoOverlay
index 8e0d842..791888f 100644
--- a/src/osgEarthDrivers/kml/KML_PhotoOverlay
+++ b/src/osgEarthDrivers/kml/KML_PhotoOverlay
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -28,8 +28,8 @@ namespace osgEarth_kml
 
     struct KML_PhotoOverlay : public KML_Overlay
     {
-        virtual void scan( const Config& conf, KMLContext& cx );
-        virtual void build( const Config& conf, KMLContext& cx );
+        virtual void scan( xml_node<>* node, KMLContext& cx );
+        virtual void build( xml_node<>* node, KMLContext& cx );
     };
 
 } // namespace osgEarth_kml
diff --git a/src/osgEarthDrivers/kml/KML_PhotoOverlay.cpp b/src/osgEarthDrivers/kml/KML_PhotoOverlay.cpp
index 5e6f8c2..734e3a3 100644
--- a/src/osgEarthDrivers/kml/KML_PhotoOverlay.cpp
+++ b/src/osgEarthDrivers/kml/KML_PhotoOverlay.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -21,13 +21,13 @@
 using namespace osgEarth_kml;
 
 void
-KML_PhotoOverlay::scan( const Config& conf, KMLContext& cx )
+KML_PhotoOverlay::scan( xml_node<>* node, KMLContext& cx )
 {
-    KML_Overlay::scan( conf, cx );
+    KML_Overlay::scan( node, cx );
 }
 
 void
-KML_PhotoOverlay::build( const Config& conf, KMLContext& cx )
+KML_PhotoOverlay::build( xml_node<>* node, KMLContext& cx )
 {
-    KML_Overlay::build( conf, cx, 0L );
+    KML_Overlay::build( node, cx, 0L );
 }
diff --git a/src/osgEarthDrivers/kml/KML_Placemark b/src/osgEarthDrivers/kml/KML_Placemark
index fb85875..6804ba1 100644
--- a/src/osgEarthDrivers/kml/KML_Placemark
+++ b/src/osgEarthDrivers/kml/KML_Placemark
@@ -26,7 +26,7 @@ using namespace osgEarth;
 
 struct KML_Placemark : public KML_Feature
 {
-    virtual void build( const Config& conf, KMLContext& cx );
+    virtual void build( xml_node<>* node, KMLContext& cx );
 };
 
 #endif // OSGEARTH_DRIVER_KML_KML_PLACEMARK
diff --git a/src/osgEarthDrivers/kml/KML_Placemark.cpp b/src/osgEarthDrivers/kml/KML_Placemark.cpp
index e3f212e..90b3d38 100644
--- a/src/osgEarthDrivers/kml/KML_Placemark.cpp
+++ b/src/osgEarthDrivers/kml/KML_Placemark.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -35,29 +35,33 @@ using namespace osgEarth::Features;
 using namespace osgEarth::Annotation;
 
 void 
-KML_Placemark::build( const Config& conf, KMLContext& cx )
+KML_Placemark::build( xml_node<>* node, KMLContext& cx )
 {
 	Style masterStyle;
 
-	if (conf.hasValue("styleurl"))
+	std::string styleUrl = getValue(node, "styleurl");
+
+	if (!styleUrl.empty())
 	{	// process a "stylesheet" style
-		const Style* ref_style = cx._sheet->getStyle( conf.value("styleurl"), false );
+		const Style* ref_style = cx._sheet->getStyle( styleUrl, false );
 		if (ref_style)
 		{
 			masterStyle = masterStyle.combineWith(*ref_style);
 		}
 	}
-	if ( conf.hasChild("style") )
+
+	xml_node<>* style = node->first_node("style", 0, false);
+	if ( style )
 	{	// process an "inline" style
 		KML_Style kmlStyle;
-		kmlStyle.scan(conf.child("style"), cx);
+		kmlStyle.scan(style, cx);
 		masterStyle = masterStyle.combineWith(cx._activeStyle);
 	}
 
     // parse the geometry. the placemark must have geometry to be valid. The 
     // geometry parse may optionally specify an altitude mode as well.
     KML_Geometry geometry;
-    geometry.build(conf, cx, masterStyle);
+    geometry.build(node, cx, masterStyle);
 
     Geometry* allGeom = geometry._geom.get();
     if ( allGeom )
@@ -91,22 +95,15 @@ KML_Placemark::build( const Config& conf, KMLContext& cx )
                 IconSymbol*     icon  = style.get<IconSymbol>();
                 TextSymbol*     text  = style.get<TextSymbol>();
 
-                if ( !text && cx._options->defaultTextSymbol().valid() )
-                    text = cx._options->defaultTextSymbol().get();
-
                 // the annotation name:
-                std::string name = conf.hasValue("name") ? conf.value("name") : "";
-                if ( text && !name.empty() )
-                {
-                    text->content()->setLiteral( name );
-                }
+                std::string name = getValue(node, "name");
 
                 AnnotationNode* featureNode = 0L;
                 AnnotationNode* iconNode    = 0L;
                 AnnotationNode* modelNode   = 0L;
 
                 // one coordinate? It's a place marker or a label.
-                if ( model || icon || text || geom->getTotalPointCount() == 1 )
+                if ( (model || icon || text) && geom->getTotalPointCount() == 1 )
                 {
                     // load up the default icon if there we don't have one.
                     if ( !model && !icon )
@@ -139,9 +136,19 @@ KML_Placemark::build( const Config& conf, KMLContext& cx )
                     }
 
                     // is there a label?
-                    else if ( !text && !name.empty() )
+                    else if ( !name.empty() )
                     {
-                        text = style.getOrCreate<TextSymbol>();
+                        if ( !text && cx._options->defaultTextSymbol().valid() )
+                        {
+                            text = cx._options->defaultTextSymbol().get();
+                            style.addSymbol( text );
+
+                        }
+                        else
+                        {
+                            text = style.getOrCreate<TextSymbol>();
+                            text->encoding() = TextSymbol::ENCODING_UTF8;
+                        }
                         text->content()->setLiteral( name );
                     }
 
@@ -195,11 +202,11 @@ KML_Placemark::build( const Config& conf, KMLContext& cx )
                     }
 
                     if ( iconNode )
-                        KML_Feature::build( conf, cx, iconNode );
+                        KML_Feature::build( node, cx, iconNode );
                     if ( modelNode )
-                        KML_Feature::build( conf, cx, modelNode );
+                        KML_Feature::build( node, cx, modelNode );
                     if ( featureNode )
-                        KML_Feature::build( conf, cx, featureNode );
+                        KML_Feature::build( node, cx, featureNode );
                 }
 
                 else
@@ -218,17 +225,17 @@ KML_Placemark::build( const Config& conf, KMLContext& cx )
                                 Decluttering::setEnabled( iconNode->getOrCreateStateSet(), true );
                             }
                         }
-                        KML_Feature::build( conf, cx, iconNode );
+                        KML_Feature::build( node, cx, iconNode );
                     }
                     if ( modelNode )
                     {
                         cx._groupStack.top()->addChild( modelNode );
-                        KML_Feature::build( conf, cx, modelNode );
+                        KML_Feature::build( node, cx, modelNode );
                     }
                     if ( featureNode )
                     {
                         cx._groupStack.top()->addChild( featureNode );
-                        KML_Feature::build( conf, cx, featureNode );
+                        KML_Feature::build( node, cx, featureNode );
                     }
                 }
             }
diff --git a/src/osgEarthDrivers/kml/KML_Point b/src/osgEarthDrivers/kml/KML_Point
index 97f5be9..3b4879f 100644
--- a/src/osgEarthDrivers/kml/KML_Point
+++ b/src/osgEarthDrivers/kml/KML_Point
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -27,7 +27,7 @@ namespace osgEarth_kml
 
     struct KML_Point : public KML_Geometry
     {
-        virtual void parseCoords( const Config& conf, KMLContext& cx );
+        virtual void parseCoords( xml_node<>* node, KMLContext& cx );
     };
 
 } // namespace osgEarth_kml
diff --git a/src/osgEarthDrivers/kml/KML_Point.cpp b/src/osgEarthDrivers/kml/KML_Point.cpp
index 9c1555b..5199208 100644
--- a/src/osgEarthDrivers/kml/KML_Point.cpp
+++ b/src/osgEarthDrivers/kml/KML_Point.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -21,8 +21,8 @@
 using namespace osgEarth_kml;
 
 void
-KML_Point::parseCoords( const Config& conf, KMLContext& cx )
+KML_Point::parseCoords( xml_node<>* node, KMLContext& cx )
 {
     _geom = new PointSet();
-    KML_Geometry::parseCoords( conf, cx );
+    KML_Geometry::parseCoords( node, cx );
 }
diff --git a/src/osgEarthDrivers/kml/KML_PolyStyle b/src/osgEarthDrivers/kml/KML_PolyStyle
index 8eb299e..d6e68c8 100644
--- a/src/osgEarthDrivers/kml/KML_PolyStyle
+++ b/src/osgEarthDrivers/kml/KML_PolyStyle
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -27,7 +27,7 @@ namespace osgEarth_kml
 
     struct KML_PolyStyle : public KML_Object
     {
-        virtual void scan( const Config& conf, Style& style, KMLContext& cx );
+        virtual void scan( xml_node<>* node, Style& style, KMLContext& cx );
     };
 
 } // namespace osgEarth_kml
diff --git a/src/osgEarthDrivers/kml/KML_PolyStyle.cpp b/src/osgEarthDrivers/kml/KML_PolyStyle.cpp
index 8ed3d43..2d7b8de 100644
--- a/src/osgEarthDrivers/kml/KML_PolyStyle.cpp
+++ b/src/osgEarthDrivers/kml/KML_PolyStyle.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -21,37 +21,39 @@
 using namespace osgEarth_kml;
 
 void
-KML_PolyStyle::scan( const Config& conf, Style& style, KMLContext& cx )
+KML_PolyStyle::scan( xml_node<>* node, Style& style, KMLContext& cx )
 {
-	if (!conf.empty())
+	if (node)
 	{
 		Color color(Color::White);
-		bool colorSpecified = conf.hasValue("color");
-		if (colorSpecified)
+		std::string colorVal = getValue(node, "color");
+		if (!colorVal.empty())
 		{
-			color = Color(Stringify() << "#" << conf.value("color"), Color::ABGR);
+			color = Color(Stringify() << "#" << colorVal, Color::ABGR);
 		}
 
 		bool fill = true;	// By default it is true
-		if (conf.hasValue("fill"))
+		std::string fillVal = getValue(node, "fill");
+		if (!fillVal.empty())
 		{
-			fill = (as<int>(conf.value("fill"), 1) == 1);
+			fill = (as<int>(fillVal, 1) == 1);
 			if (!fill)
 			{
 				color.a() = 0;
 			}
 		}
 
-		if (colorSpecified || !style.has<PolygonSymbol>())
+		if (!colorVal.empty() || !style.has<PolygonSymbol>())
 		{
 			PolygonSymbol* poly = style.getOrCreate<PolygonSymbol>();
 			poly->fill()->color() = color;
 		}
 
 		bool outline = true;	// By default it is true
-		if (conf.hasValue("outline"))
+		std::string outlineVal = getValue(node, "outline");
+		if (!outlineVal.empty())
 		{
-			outline = (as<int>(conf.value("outline"), 0) == 1);
+			outline = (as<int>(outlineVal, 0) == 1);
 		}
 		if (!outline)
 		{
diff --git a/src/osgEarthDrivers/kml/KML_Polygon b/src/osgEarthDrivers/kml/KML_Polygon
index a1ab6b2..acd7c4a 100644
--- a/src/osgEarthDrivers/kml/KML_Polygon
+++ b/src/osgEarthDrivers/kml/KML_Polygon
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -27,8 +27,8 @@ namespace osgEarth_kml
 
     struct KML_Polygon : public KML_Geometry
     {
-        virtual void parseStyle( const Config& conf, KMLContext& cx, Style& style);
-        virtual void parseCoords( const Config& conf, KMLContext& cx );
+        virtual void parseStyle( xml_node<>* node, KMLContext& cx, Style& style);
+        virtual void parseCoords( xml_node<>* node, KMLContext& cx );
     };
 
 } // namespace osgEarth_kml
diff --git a/src/osgEarthDrivers/kml/KML_Polygon.cpp b/src/osgEarthDrivers/kml/KML_Polygon.cpp
index 9cabed8..e095773 100644
--- a/src/osgEarthDrivers/kml/KML_Polygon.cpp
+++ b/src/osgEarthDrivers/kml/KML_Polygon.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -23,9 +23,9 @@
 using namespace osgEarth_kml;
 
 void
-KML_Polygon::parseStyle(const Config& conf, KMLContext& cx, Style& style)
+KML_Polygon::parseStyle(xml_node<>* node, KMLContext& cx, Style& style)
 {
-    KML_Geometry::parseStyle(conf, cx, style);
+    KML_Geometry::parseStyle(node, cx, style);
 
     // need at minimum a poly symbol.
     if ( !style.has<PolygonSymbol>() )
@@ -35,18 +35,18 @@ KML_Polygon::parseStyle(const Config& conf, KMLContext& cx, Style& style)
 }
 
 void
-KML_Polygon::parseCoords( const Config& conf, KMLContext& cx )
+KML_Polygon::parseCoords( xml_node<>* node, KMLContext& cx )
 {
     Polygon* poly = new Polygon();
 
-    Config outerConf = conf.child("outerboundaryis");
-    if ( !outerConf.empty() )
+    xml_node<>* outer = node->first_node("outerboundaryis", 0, false);
+    if ( outer )
     {
-        Config outerRingConf = outerConf.child("linearring");
-        if ( !outerRingConf.empty() )
+        xml_node<>* outerRing = outer->first_node("linearring", 0, false);
+        if ( outerRing )
         {
             KML_LinearRing outer;
-            outer.parseCoords( outerRingConf, cx );
+            outer.parseCoords( outerRing, cx );
             if ( outer._geom.valid() )
             {
                 dynamic_cast<Ring*>(outer._geom.get())->rewind( Ring::ORIENTATION_CCW );
@@ -55,22 +55,21 @@ KML_Polygon::parseCoords( const Config& conf, KMLContext& cx )
             }
         }
 
-        ConfigSet innerConfs = conf.children("innerboundaryis");
-        for( ConfigSet::const_iterator i = innerConfs.begin(); i != innerConfs.end(); ++i )
-        {
-            Config innerRingConf = i->child("linearring");
-            if ( !innerRingConf.empty() )
-            {
-                KML_LinearRing inner;
-                inner.parseCoords( innerRingConf, cx );
-                if ( inner._geom.valid() )
-                {
-                    Geometry* innerGeom = inner._geom.get();
-                    dynamic_cast<Ring*>(innerGeom)->rewind( Ring::ORIENTATION_CW );
-                    poly->getHoles().push_back( dynamic_cast<Ring*>(innerGeom) );
-                }
-            }
-        }
+		for (xml_node<>* n = node->first_node("innerboundaryis", 0, false); n; n = n->next_sibling("innerboundaryis", 0, false))
+		{
+			xml_node<>* innerRing = n->first_node("linearring", 0, false);
+			if ( innerRing )
+			{
+				KML_LinearRing inner;
+				inner.parseCoords( innerRing, cx );
+				if ( inner._geom.valid() )
+				{
+					Geometry* innerGeom = inner._geom.get();
+					dynamic_cast<Ring*>(innerGeom)->rewind( Ring::ORIENTATION_CW );
+					poly->getHoles().push_back( dynamic_cast<Ring*>(innerGeom) );
+				}
+			}
+		}
     }
 
     _geom = poly;
diff --git a/src/osgEarthDrivers/kml/KML_Root b/src/osgEarthDrivers/kml/KML_Root
index 15b1062..5721ad6 100644
--- a/src/osgEarthDrivers/kml/KML_Root
+++ b/src/osgEarthDrivers/kml/KML_Root
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -21,13 +21,14 @@
 
 #include "KML_Object"
 
+
 namespace osgEarth_kml
 {
     struct KML_Root
     {
-        virtual void scan( const Config& conf, KMLContext& cx );
-        virtual void scan2( const Config& conf, KMLContext& cx );
-        virtual void build( const Config& conf, KMLContext& cx );
+        virtual void scan( xml_node<>* node, KMLContext& cx );
+        virtual void scan2( xml_node<>* node, KMLContext& cx );
+        virtual void build( xml_node<>* node, KMLContext& cx );
 
         virtual ~KML_Root() { }
     };
diff --git a/src/osgEarthDrivers/kml/KML_Root.cpp b/src/osgEarthDrivers/kml/KML_Root.cpp
index 3c74085..cfcaeef 100644
--- a/src/osgEarthDrivers/kml/KML_Root.cpp
+++ b/src/osgEarthDrivers/kml/KML_Root.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -29,22 +29,22 @@
 using namespace osgEarth_kml;
 
 void 
-KML_Root::scan( const Config& conf, KMLContext& cx )
+KML_Root::scan( xml_node<>* node, KMLContext& cx )
 {
-    for_features( scan, conf, cx );
-    for_one( NetworkLinkControl, scan, conf, cx );
+    for_features( scan, node, cx );
+    for_one( NetworkLinkControl, scan, node, cx );
 }
 
 void
-KML_Root::scan2( const Config& conf, KMLContext& cx )
+KML_Root::scan2( xml_node<>* node, KMLContext& cx )
 {
-    for_features( scan2, conf, cx );
-    for_one( NetworkLinkControl, scan2, conf, cx );
+    for_features( scan2, node, cx );
+    for_one( NetworkLinkControl, scan2, node, cx );
 }
 
 void
-KML_Root::build( const Config& conf, KMLContext& cx )
+KML_Root::build( xml_node<>* node, KMLContext& cx )
 {
-    for_features( build, conf, cx );
-    for_one( NetworkLink, build, conf, cx );
+    for_features( build, node, cx );
+    for_one( NetworkLink, build, node, cx );
 }
diff --git a/src/osgEarthDrivers/kml/KML_Schema b/src/osgEarthDrivers/kml/KML_Schema
index 00f7fb0..8eb3fa6 100644
--- a/src/osgEarthDrivers/kml/KML_Schema
+++ b/src/osgEarthDrivers/kml/KML_Schema
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/kml/KML_Schema.cpp b/src/osgEarthDrivers/kml/KML_Schema.cpp
index 3f292c1..f5b9d18 100644
--- a/src/osgEarthDrivers/kml/KML_Schema.cpp
+++ b/src/osgEarthDrivers/kml/KML_Schema.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/kml/KML_ScreenOverlay b/src/osgEarthDrivers/kml/KML_ScreenOverlay
index de4a44a..2eccf41 100644
--- a/src/osgEarthDrivers/kml/KML_ScreenOverlay
+++ b/src/osgEarthDrivers/kml/KML_ScreenOverlay
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -28,8 +28,8 @@ namespace osgEarth_kml
 
     struct KML_ScreenOverlay : public KML_Overlay
     {
-        virtual void scan( const Config& conf, KMLContext& cx );
-        virtual void build( const Config& conf, KMLContext& cx );
+        virtual void scan( xml_node<>* node, KMLContext& cx );
+        virtual void build( xml_node<>* node, KMLContext& cx );
     };
 
 } // namespace osgEarth_kml
diff --git a/src/osgEarthDrivers/kml/KML_ScreenOverlay.cpp b/src/osgEarthDrivers/kml/KML_ScreenOverlay.cpp
index d81135f..012f2db 100644
--- a/src/osgEarthDrivers/kml/KML_ScreenOverlay.cpp
+++ b/src/osgEarthDrivers/kml/KML_ScreenOverlay.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -21,13 +21,13 @@
 using namespace osgEarth_kml;
 
 void
-KML_ScreenOverlay::scan( const Config& conf, KMLContext& cx )
+KML_ScreenOverlay::scan( xml_node<>* node, KMLContext& cx )
 {
-    KML_Overlay::scan( conf, cx );
+    KML_Overlay::scan( node, cx );
 }
 
 void
-KML_ScreenOverlay::build( const Config& conf, KMLContext& cx )
+KML_ScreenOverlay::build( xml_node<>* node, KMLContext& cx )
 {
     //todo
     //KML_Overlay::build( conf, cx, 0L );
diff --git a/src/osgEarthDrivers/kml/KML_Style b/src/osgEarthDrivers/kml/KML_Style
index 5f53951..8c46dfb 100644
--- a/src/osgEarthDrivers/kml/KML_Style
+++ b/src/osgEarthDrivers/kml/KML_Style
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -28,7 +28,7 @@ namespace osgEarth_kml
 
     struct KML_Style : public KML_StyleSelector
     {
-        virtual void scan( const Config& conf, KMLContext& cx );
+        virtual void scan( xml_node<>* node, KMLContext& cx );
     };
 
 } // namespace osgEarth_kml
diff --git a/src/osgEarthDrivers/kml/KML_Style.cpp b/src/osgEarthDrivers/kml/KML_Style.cpp
index 0584a1b..bbe9472 100644
--- a/src/osgEarthDrivers/kml/KML_Style.cpp
+++ b/src/osgEarthDrivers/kml/KML_Style.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -25,21 +25,21 @@
 using namespace osgEarth_kml;
 
 void
-KML_Style::scan( const Config& conf, KMLContext& cx )
+KML_Style::scan( xml_node<>* node, KMLContext& cx )
 {
-    Style style( conf.value("id") );
+    Style style( getValue(node, "id") );
 
     KML_IconStyle icon;
-    icon.scan( conf.child("iconstyle"), style, cx );
+    icon.scan( node->first_node("iconstyle", 0, false), style, cx );
 
     KML_LabelStyle label;
-    label.scan( conf.child("labelstyle"), style, cx );
+    label.scan( node->first_node("labelstyle", 0, false), style, cx );
 
     KML_LineStyle line;
-    line.scan( conf.child("linestyle"), style, cx );
+    line.scan( node->first_node("linestyle", 0, false), style, cx );
 
     KML_PolyStyle poly;
-    poly.scan( conf.child("polystyle"), style, cx );
+    poly.scan( node->first_node("polystyle", 0, false), style, cx );
 
     cx._sheet->addStyle( style );
 
diff --git a/src/osgEarthDrivers/kml/KML_StyleMap b/src/osgEarthDrivers/kml/KML_StyleMap
index 201254e..a03103b 100644
--- a/src/osgEarthDrivers/kml/KML_StyleMap
+++ b/src/osgEarthDrivers/kml/KML_StyleMap
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -28,7 +28,7 @@ namespace osgEarth_kml
 
     struct KML_StyleMap : public KML_StyleSelector
     {
-        virtual void scan2( const Config& conf, KMLContext& cx );
+        virtual void scan2( xml_node<>* node, KMLContext& cx );
     };
 
 } // namespace osgEarth_kml
diff --git a/src/osgEarthDrivers/kml/KML_StyleMap.cpp b/src/osgEarthDrivers/kml/KML_StyleMap.cpp
index 84cf032..9a1847f 100644
--- a/src/osgEarthDrivers/kml/KML_StyleMap.cpp
+++ b/src/osgEarthDrivers/kml/KML_StyleMap.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -21,19 +21,19 @@
 using namespace osgEarth_kml;
 
 void
-KML_StyleMap::scan2( const Config& conf, KMLContext& cx )
+KML_StyleMap::scan2( xml_node<>* node, KMLContext& cx )
 {
-    const Config& pair = conf.child("pair");
-    if ( !pair.empty() )
+    xml_node<>* pair = node->first_node("pair", 0, false);
+    if ( pair )
     {
-        const std::string& url = pair.value("styleurl" );
+        const std::string& url = getValue(pair, "styleurl");
         if ( !url.empty() )
         {
             const Style* style = cx._sheet->getStyle( url );
             if ( style )
             {
                 Style aliasStyle = *style;
-                aliasStyle.setName( conf.value("id") );
+                aliasStyle.setName( getValue(node, "id") );
                 cx._sheet->addStyle( aliasStyle );
             }
         }
diff --git a/src/osgEarthDrivers/kml/KML_StyleSelector b/src/osgEarthDrivers/kml/KML_StyleSelector
index 5f3ede5..8523bda 100644
--- a/src/osgEarthDrivers/kml/KML_StyleSelector
+++ b/src/osgEarthDrivers/kml/KML_StyleSelector
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/kml/KMZArchive b/src/osgEarthDrivers/kml/KMZArchive
index f14de5a..bd4bb6d 100644
--- a/src/osgEarthDrivers/kml/KMZArchive
+++ b/src/osgEarthDrivers/kml/KMZArchive
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/kml/KMZArchive.cpp b/src/osgEarthDrivers/kml/KMZArchive.cpp
index 23c4a51..a9d90cd 100644
--- a/src/osgEarthDrivers/kml/KMZArchive.cpp
+++ b/src/osgEarthDrivers/kml/KMZArchive.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/kml/ReaderWriterKML.cpp b/src/osgEarthDrivers/kml/ReaderWriterKML.cpp
index 3b96823..c277326 100644
--- a/src/osgEarthDrivers/kml/ReaderWriterKML.cpp
+++ b/src/osgEarthDrivers/kml/ReaderWriterKML.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -22,6 +22,7 @@
 #include <osgDB/Registry>
 #include <osgDB/FileUtils>
 #include <osgDB/Archive>
+#include <osgEarth/Containers>
 #include <osgEarth/Registry>
 #include <osgEarth/ThreadingUtils>
 
@@ -113,7 +114,7 @@ struct ReaderWriterKML : public osgDB::ReaderWriter
 
 private:
 
-    Threading::PerThread< osg::ref_ptr<KMZArchive> > _archive;
+    PerThread< osg::ref_ptr<KMZArchive> > _archive;
 
 #endif // SUPPORT_KMZ
 };
diff --git a/src/osgEarthDrivers/kml/rapidxml.hpp b/src/osgEarthDrivers/kml/rapidxml.hpp
new file mode 100644
index 0000000..ae91e08
--- /dev/null
+++ b/src/osgEarthDrivers/kml/rapidxml.hpp
@@ -0,0 +1,2596 @@
+#ifndef RAPIDXML_HPP_INCLUDED
+#define RAPIDXML_HPP_INCLUDED
+
+// Copyright (C) 2006, 2009 Marcin Kalicinski
+// Version 1.13
+// Revision $DateTime: 2009/05/13 01:46:17 $
+//! \file rapidxml.hpp This file contains rapidxml parser and DOM implementation
+
+// If standard library is disabled, user must provide implementations of required functions and typedefs
+#if !defined(RAPIDXML_NO_STDLIB)
+    #include <cstdlib>      // For std::size_t
+    #include <cassert>      // For assert
+    #include <new>          // For placement new
+#endif
+
+// On MSVC, disable "conditional expression is constant" warning (level 4). 
+// This warning is almost impossible to avoid with certain types of templated code
+#ifdef _MSC_VER
+    #pragma warning(push)
+    #pragma warning(disable:4127)   // Conditional expression is constant
+#endif
+
+///////////////////////////////////////////////////////////////////////////
+// RAPIDXML_PARSE_ERROR
+    
+#if defined(RAPIDXML_NO_EXCEPTIONS)
+
+#define RAPIDXML_PARSE_ERROR(what, where) { parse_error_handler(what, where); assert(0); }
+
+namespace rapidxml
+{
+    //! When exceptions are disabled by defining RAPIDXML_NO_EXCEPTIONS, 
+    //! this function is called to notify user about the error.
+    //! It must be defined by the user.
+    //! <br><br>
+    //! This function cannot return. If it does, the results are undefined.
+    //! <br><br>
+    //! A very simple definition might look like that:
+    //! <pre>
+    //! void %rapidxml::%parse_error_handler(const char *what, void *where)
+    //! {
+    //!     std::cout << "Parse error: " << what << "\n";
+    //!     std::abort();
+    //! }
+    //! </pre>
+    //! \param what Human readable description of the error.
+    //! \param where Pointer to character data where error was detected.
+    void parse_error_handler(const char *what, void *where);
+}
+
+#else
+    
+#include <exception>    // For std::exception
+
+#define RAPIDXML_PARSE_ERROR(what, where) throw parse_error(what, where)
+
+namespace rapidxml
+{
+
+    //! Parse error exception. 
+    //! This exception is thrown by the parser when an error occurs. 
+    //! Use what() function to get human-readable error message. 
+    //! Use where() function to get a pointer to position within source text where error was detected.
+    //! <br><br>
+    //! If throwing exceptions by the parser is undesirable, 
+    //! it can be disabled by defining RAPIDXML_NO_EXCEPTIONS macro before rapidxml.hpp is included.
+    //! This will cause the parser to call rapidxml::parse_error_handler() function instead of throwing an exception.
+    //! This function must be defined by the user.
+    //! <br><br>
+    //! This class derives from <code>std::exception</code> class.
+    class parse_error: public std::exception
+    {
+    
+    public:
+    
+        //! Constructs parse error
+        parse_error(const char *what, void *where)
+            : m_what(what)
+            , m_where(where)
+        {
+        }
+
+        //! Gets human readable description of error.
+        //! \return Pointer to null terminated description of the error.
+        virtual const char *what() const throw()
+        {
+            return m_what;
+        }
+
+        //! Gets pointer to character data where error happened.
+        //! Ch should be the same as char type of xml_document that produced the error.
+        //! \return Pointer to location within the parsed string where error occured.
+        template<class Ch>
+        Ch *where() const
+        {
+            return reinterpret_cast<Ch *>(m_where);
+        }
+
+    private:  
+
+        const char *m_what;
+        void *m_where;
+
+    };
+}
+
+#endif
+
+///////////////////////////////////////////////////////////////////////////
+// Pool sizes
+
+#ifndef RAPIDXML_STATIC_POOL_SIZE
+    // Size of static memory block of memory_pool.
+    // Define RAPIDXML_STATIC_POOL_SIZE before including rapidxml.hpp if you want to override the default value.
+    // No dynamic memory allocations are performed by memory_pool until static memory is exhausted.
+    #define RAPIDXML_STATIC_POOL_SIZE (64 * 1024)
+#endif
+
+#ifndef RAPIDXML_DYNAMIC_POOL_SIZE
+    // Size of dynamic memory block of memory_pool.
+    // Define RAPIDXML_DYNAMIC_POOL_SIZE before including rapidxml.hpp if you want to override the default value.
+    // After the static block is exhausted, dynamic blocks with approximately this size are allocated by memory_pool.
+    #define RAPIDXML_DYNAMIC_POOL_SIZE (64 * 1024)
+#endif
+
+#ifndef RAPIDXML_ALIGNMENT
+    // Memory allocation alignment.
+    // Define RAPIDXML_ALIGNMENT before including rapidxml.hpp if you want to override the default value, which is the size of pointer.
+    // All memory allocations for nodes, attributes and strings will be aligned to this value.
+    // This must be a power of 2 and at least 1, otherwise memory_pool will not work.
+    #define RAPIDXML_ALIGNMENT sizeof(void *)
+#endif
+
+namespace rapidxml
+{
+    // Forward declarations
+    template<class Ch> class xml_node;
+    template<class Ch> class xml_attribute;
+    template<class Ch> class xml_document;
+    
+    //! Enumeration listing all node types produced by the parser.
+    //! Use xml_node::type() function to query node type.
+    enum node_type
+    {
+        node_document,      //!< A document node. Name and value are empty.
+        node_element,       //!< An element node. Name contains element name. Value contains text of first data node.
+        node_data,          //!< A data node. Name is empty. Value contains data text.
+        node_cdata,         //!< A CDATA node. Name is empty. Value contains data text.
+        node_comment,       //!< A comment node. Name is empty. Value contains comment text.
+        node_declaration,   //!< A declaration node. Name and value are empty. Declaration parameters (version, encoding and standalone) are in node attributes.
+        node_doctype,       //!< A DOCTYPE node. Name is empty. Value contains DOCTYPE text.
+        node_pi             //!< A PI node. Name contains target. Value contains instructions.
+    };
+
+    ///////////////////////////////////////////////////////////////////////
+    // Parsing flags
+
+    //! Parse flag instructing the parser to not create data nodes. 
+    //! Text of first data node will still be placed in value of parent element, unless rapidxml::parse_no_element_values flag is also specified.
+    //! Can be combined with other flags by use of | operator.
+    //! <br><br>
+    //! See xml_document::parse() function.
+    const int parse_no_data_nodes = 0x1;            
+
+    //! Parse flag instructing the parser to not use text of first data node as a value of parent element.
+    //! Can be combined with other flags by use of | operator.
+    //! Note that child data nodes of element node take precendence over its value when printing. 
+    //! That is, if element has one or more child data nodes <em>and</em> a value, the value will be ignored.
+    //! Use rapidxml::parse_no_data_nodes flag to prevent creation of data nodes if you want to manipulate data using values of elements.
+    //! <br><br>
+    //! See xml_document::parse() function.
+    const int parse_no_element_values = 0x2;
+    
+    //! Parse flag instructing the parser to not place zero terminators after strings in the source text.
+    //! By default zero terminators are placed, modifying source text.
+    //! Can be combined with other flags by use of | operator.
+    //! <br><br>
+    //! See xml_document::parse() function.
+    const int parse_no_string_terminators = 0x4;
+    
+    //! Parse flag instructing the parser to not translate entities in the source text.
+    //! By default entities are translated, modifying source text.
+    //! Can be combined with other flags by use of | operator.
+    //! <br><br>
+    //! See xml_document::parse() function.
+    const int parse_no_entity_translation = 0x8;
+    
+    //! Parse flag instructing the parser to disable UTF-8 handling and assume plain 8 bit characters.
+    //! By default, UTF-8 handling is enabled.
+    //! Can be combined with other flags by use of | operator.
+    //! <br><br>
+    //! See xml_document::parse() function.
+    const int parse_no_utf8 = 0x10;
+    
+    //! Parse flag instructing the parser to create XML declaration node.
+    //! By default, declaration node is not created.
+    //! Can be combined with other flags by use of | operator.
+    //! <br><br>
+    //! See xml_document::parse() function.
+    const int parse_declaration_node = 0x20;
+    
+    //! Parse flag instructing the parser to create comments nodes.
+    //! By default, comment nodes are not created.
+    //! Can be combined with other flags by use of | operator.
+    //! <br><br>
+    //! See xml_document::parse() function.
+    const int parse_comment_nodes = 0x40;
+    
+    //! Parse flag instructing the parser to create DOCTYPE node.
+    //! By default, doctype node is not created.
+    //! Although W3C specification allows at most one DOCTYPE node, RapidXml will silently accept documents with more than one.
+    //! Can be combined with other flags by use of | operator.
+    //! <br><br>
+    //! See xml_document::parse() function.
+    const int parse_doctype_node = 0x80;
+    
+    //! Parse flag instructing the parser to create PI nodes.
+    //! By default, PI nodes are not created.
+    //! Can be combined with other flags by use of | operator.
+    //! <br><br>
+    //! See xml_document::parse() function.
+    const int parse_pi_nodes = 0x100;
+    
+    //! Parse flag instructing the parser to validate closing tag names. 
+    //! If not set, name inside closing tag is irrelevant to the parser.
+    //! By default, closing tags are not validated.
+    //! Can be combined with other flags by use of | operator.
+    //! <br><br>
+    //! See xml_document::parse() function.
+    const int parse_validate_closing_tags = 0x200;
+    
+    //! Parse flag instructing the parser to trim all leading and trailing whitespace of data nodes.
+    //! By default, whitespace is not trimmed. 
+    //! This flag does not cause the parser to modify source text.
+    //! Can be combined with other flags by use of | operator.
+    //! <br><br>
+    //! See xml_document::parse() function.
+    const int parse_trim_whitespace = 0x400;
+
+    //! Parse flag instructing the parser to condense all whitespace runs of data nodes to a single space character.
+    //! Trimming of leading and trailing whitespace of data is controlled by rapidxml::parse_trim_whitespace flag.
+    //! By default, whitespace is not normalized. 
+    //! If this flag is specified, source text will be modified.
+    //! Can be combined with other flags by use of | operator.
+    //! <br><br>
+    //! See xml_document::parse() function.
+    const int parse_normalize_whitespace = 0x800;
+
+    // Compound flags
+    
+    //! Parse flags which represent default behaviour of the parser. 
+    //! This is always equal to 0, so that all other flags can be simply ored together.
+    //! Normally there is no need to inconveniently disable flags by anding with their negated (~) values.
+    //! This also means that meaning of each flag is a <i>negation</i> of the default setting. 
+    //! For example, if flag name is rapidxml::parse_no_utf8, it means that utf-8 is <i>enabled</i> by default,
+    //! and using the flag will disable it.
+    //! <br><br>
+    //! See xml_document::parse() function.
+    const int parse_default = 0;
+    
+    //! A combination of parse flags that forbids any modifications of the source text. 
+    //! This also results in faster parsing. However, note that the following will occur:
+    //! <ul>
+    //! <li>names and values of nodes will not be zero terminated, you have to use xml_base::name_size() and xml_base::value_size() functions to determine where name and value ends</li>
+    //! <li>entities will not be translated</li>
+    //! <li>whitespace will not be normalized</li>
+    //! </ul>
+    //! See xml_document::parse() function.
+    const int parse_non_destructive = parse_no_string_terminators | parse_no_entity_translation;
+    
+    //! A combination of parse flags resulting in fastest possible parsing, without sacrificing important data.
+    //! <br><br>
+    //! See xml_document::parse() function.
+    const int parse_fastest = parse_non_destructive | parse_no_data_nodes;
+    
+    //! A combination of parse flags resulting in largest amount of data being extracted. 
+    //! This usually results in slowest parsing.
+    //! <br><br>
+    //! See xml_document::parse() function.
+    const int parse_full = parse_declaration_node | parse_comment_nodes | parse_doctype_node | parse_pi_nodes | parse_validate_closing_tags;
+
+    ///////////////////////////////////////////////////////////////////////
+    // Internals
+
+    //! \cond internal
+    namespace internal
+    {
+
+        // Struct that contains lookup tables for the parser
+        // It must be a template to allow correct linking (because it has static data members, which are defined in a header file).
+        template<int Dummy>
+        struct lookup_tables
+        {
+            static const unsigned char lookup_whitespace[256];              // Whitespace table
+            static const unsigned char lookup_node_name[256];               // Node name table
+            static const unsigned char lookup_text[256];                    // Text table
+            static const unsigned char lookup_text_pure_no_ws[256];         // Text table
+            static const unsigned char lookup_text_pure_with_ws[256];       // Text table
+            static const unsigned char lookup_attribute_name[256];          // Attribute name table
+            static const unsigned char lookup_attribute_data_1[256];        // Attribute data table with single quote
+            static const unsigned char lookup_attribute_data_1_pure[256];   // Attribute data table with single quote
+            static const unsigned char lookup_attribute_data_2[256];        // Attribute data table with double quotes
+            static const unsigned char lookup_attribute_data_2_pure[256];   // Attribute data table with double quotes
+            static const unsigned char lookup_digits[256];                  // Digits
+            static const unsigned char lookup_upcase[256];                  // To uppercase conversion table for ASCII characters
+        };
+
+        // Find length of the string
+        template<class Ch>
+        inline std::size_t measure(const Ch *p)
+        {
+            const Ch *tmp = p;
+            while (*tmp) 
+                ++tmp;
+            return tmp - p;
+        }
+
+        // Compare strings for equality
+        template<class Ch>
+        inline bool compare(const Ch *p1, std::size_t size1, const Ch *p2, std::size_t size2, bool case_sensitive)
+        {
+            if (size1 != size2)
+                return false;
+            if (case_sensitive)
+            {
+                for (const Ch *end = p1 + size1; p1 < end; ++p1, ++p2)
+                    if (*p1 != *p2)
+                        return false;
+            }
+            else
+            {
+                for (const Ch *end = p1 + size1; p1 < end; ++p1, ++p2)
+                    if (lookup_tables<0>::lookup_upcase[static_cast<unsigned char>(*p1)] != lookup_tables<0>::lookup_upcase[static_cast<unsigned char>(*p2)])
+                        return false;
+            }
+            return true;
+        }
+    }
+    //! \endcond
+
+    ///////////////////////////////////////////////////////////////////////
+    // Memory pool
+    
+    //! This class is used by the parser to create new nodes and attributes, without overheads of dynamic memory allocation.
+    //! In most cases, you will not need to use this class directly. 
+    //! However, if you need to create nodes manually or modify names/values of nodes, 
+    //! you are encouraged to use memory_pool of relevant xml_document to allocate the memory. 
+    //! Not only is this faster than allocating them by using <code>new</code> operator, 
+    //! but also their lifetime will be tied to the lifetime of document, 
+    //! possibly simplyfing memory management. 
+    //! <br><br>
+    //! Call allocate_node() or allocate_attribute() functions to obtain new nodes or attributes from the pool. 
+    //! You can also call allocate_string() function to allocate strings.
+    //! Such strings can then be used as names or values of nodes without worrying about their lifetime.
+    //! Note that there is no <code>free()</code> function -- all allocations are freed at once when clear() function is called, 
+    //! or when the pool is destroyed.
+    //! <br><br>
+    //! It is also possible to create a standalone memory_pool, and use it 
+    //! to allocate nodes, whose lifetime will not be tied to any document.
+    //! <br><br>
+    //! Pool maintains <code>RAPIDXML_STATIC_POOL_SIZE</code> bytes of statically allocated memory. 
+    //! Until static memory is exhausted, no dynamic memory allocations are done.
+    //! When static memory is exhausted, pool allocates additional blocks of memory of size <code>RAPIDXML_DYNAMIC_POOL_SIZE</code> each,
+    //! by using global <code>new[]</code> and <code>delete[]</code> operators. 
+    //! This behaviour can be changed by setting custom allocation routines. 
+    //! Use set_allocator() function to set them.
+    //! <br><br>
+    //! Allocations for nodes, attributes and strings are aligned at <code>RAPIDXML_ALIGNMENT</code> bytes.
+    //! This value defaults to the size of pointer on target architecture.
+    //! <br><br>
+    //! To obtain absolutely top performance from the parser,
+    //! it is important that all nodes are allocated from a single, contiguous block of memory.
+    //! Otherwise, cache misses when jumping between two (or more) disjoint blocks of memory can slow down parsing quite considerably.
+    //! If required, you can tweak <code>RAPIDXML_STATIC_POOL_SIZE</code>, <code>RAPIDXML_DYNAMIC_POOL_SIZE</code> and <code>RAPIDXML_ALIGNMENT</code> 
+    //! to obtain best wasted memory to performance compromise.
+    //! To do it, define their values before rapidxml.hpp file is included.
+    //! \param Ch Character type of created nodes. 
+    template<class Ch = char>
+    class memory_pool
+    {
+        
+    public:
+
+        //! \cond internal
+        typedef void *(alloc_func)(std::size_t);       // Type of user-defined function used to allocate memory
+        typedef void (free_func)(void *);              // Type of user-defined function used to free memory
+        //! \endcond
+        
+        //! Constructs empty pool with default allocator functions.
+        memory_pool()
+            : m_alloc_func(0)
+            , m_free_func(0)
+        {
+            init();
+        }
+
+        //! Destroys pool and frees all the memory. 
+        //! This causes memory occupied by nodes allocated by the pool to be freed.
+        //! Nodes allocated from the pool are no longer valid.
+        ~memory_pool()
+        {
+            clear();
+        }
+
+        //! Allocates a new node from the pool, and optionally assigns name and value to it. 
+        //! If the allocation request cannot be accomodated, this function will throw <code>std::bad_alloc</code>.
+        //! If exceptions are disabled by defining RAPIDXML_NO_EXCEPTIONS, this function
+        //! will call rapidxml::parse_error_handler() function.
+        //! \param type Type of node to create.
+        //! \param name Name to assign to the node, or 0 to assign no name.
+        //! \param value Value to assign to the node, or 0 to assign no value.
+        //! \param name_size Size of name to assign, or 0 to automatically calculate size from name string.
+        //! \param value_size Size of value to assign, or 0 to automatically calculate size from value string.
+        //! \return Pointer to allocated node. This pointer will never be NULL.
+        xml_node<Ch> *allocate_node(node_type type, 
+                                    const Ch *name = 0, const Ch *value = 0, 
+                                    std::size_t name_size = 0, std::size_t value_size = 0)
+        {
+            void *memory = allocate_aligned(sizeof(xml_node<Ch>));
+            xml_node<Ch> *node = new(memory) xml_node<Ch>(type);
+            if (name)
+            {
+                if (name_size > 0)
+                    node->name(name, name_size);
+                else
+                    node->name(name);
+            }
+            if (value)
+            {
+                if (value_size > 0)
+                    node->value(value, value_size);
+                else
+                    node->value(value);
+            }
+            return node;
+        }
+
+        //! Allocates a new attribute from the pool, and optionally assigns name and value to it.
+        //! If the allocation request cannot be accomodated, this function will throw <code>std::bad_alloc</code>.
+        //! If exceptions are disabled by defining RAPIDXML_NO_EXCEPTIONS, this function
+        //! will call rapidxml::parse_error_handler() function.
+        //! \param name Name to assign to the attribute, or 0 to assign no name.
+        //! \param value Value to assign to the attribute, or 0 to assign no value.
+        //! \param name_size Size of name to assign, or 0 to automatically calculate size from name string.
+        //! \param value_size Size of value to assign, or 0 to automatically calculate size from value string.
+        //! \return Pointer to allocated attribute. This pointer will never be NULL.
+        xml_attribute<Ch> *allocate_attribute(const Ch *name = 0, const Ch *value = 0, 
+                                              std::size_t name_size = 0, std::size_t value_size = 0)
+        {
+            void *memory = allocate_aligned(sizeof(xml_attribute<Ch>));
+            xml_attribute<Ch> *attribute = new(memory) xml_attribute<Ch>;
+            if (name)
+            {
+                if (name_size > 0)
+                    attribute->name(name, name_size);
+                else
+                    attribute->name(name);
+            }
+            if (value)
+            {
+                if (value_size > 0)
+                    attribute->value(value, value_size);
+                else
+                    attribute->value(value);
+            }
+            return attribute;
+        }
+
+        //! Allocates a char array of given size from the pool, and optionally copies a given string to it.
+        //! If the allocation request cannot be accomodated, this function will throw <code>std::bad_alloc</code>.
+        //! If exceptions are disabled by defining RAPIDXML_NO_EXCEPTIONS, this function
+        //! will call rapidxml::parse_error_handler() function.
+        //! \param source String to initialize the allocated memory with, or 0 to not initialize it.
+        //! \param size Number of characters to allocate, or zero to calculate it automatically from source string length; if size is 0, source string must be specified and null terminated.
+        //! \return Pointer to allocated char array. This pointer will never be NULL.
+        Ch *allocate_string(const Ch *source = 0, std::size_t size = 0)
+        {
+            assert(source || size);     // Either source or size (or both) must be specified
+            if (size == 0)
+                size = internal::measure(source) + 1;
+            Ch *result = static_cast<Ch *>(allocate_aligned(size * sizeof(Ch)));
+            if (source)
+                for (std::size_t i = 0; i < size; ++i)
+                    result[i] = source[i];
+            return result;
+        }
+
+        //! Clones an xml_node and its hierarchy of child nodes and attributes.
+        //! Nodes and attributes are allocated from this memory pool.
+        //! Names and values are not cloned, they are shared between the clone and the source.
+        //! Result node can be optionally specified as a second parameter, 
+        //! in which case its contents will be replaced with cloned source node.
+        //! This is useful when you want to clone entire document.
+        //! \param source Node to clone.
+        //! \param result Node to put results in, or 0 to automatically allocate result node
+        //! \return Pointer to cloned node. This pointer will never be NULL.
+        xml_node<Ch> *clone_node(const xml_node<Ch> *source, xml_node<Ch> *result = 0)
+        {
+            // Prepare result node
+            if (result)
+            {
+                result->remove_all_attributes();
+                result->remove_all_nodes();
+                result->type(source->type());
+            }
+            else
+                result = allocate_node(source->type());
+
+            // Clone name and value
+            result->name(source->name(), source->name_size());
+            result->value(source->value(), source->value_size());
+
+            // Clone child nodes and attributes
+            for (xml_node<Ch> *child = source->first_node(); child; child = child->next_sibling())
+                result->append_node(clone_node(child));
+            for (xml_attribute<Ch> *attr = source->first_attribute(); attr; attr = attr->next_attribute())
+                result->append_attribute(allocate_attribute(attr->name(), attr->value(), attr->name_size(), attr->value_size()));
+
+            return result;
+        }
+
+        //! Clears the pool. 
+        //! This causes memory occupied by nodes allocated by the pool to be freed.
+        //! Any nodes or strings allocated from the pool will no longer be valid.
+        void clear()
+        {
+            while (m_begin != m_static_memory)
+            {
+                char *previous_begin = reinterpret_cast<header *>(align(m_begin))->previous_begin;
+                if (m_free_func)
+                    m_free_func(m_begin);
+                else
+                    delete[] m_begin;
+                m_begin = previous_begin;
+            }
+            init();
+        }
+
+        //! Sets or resets the user-defined memory allocation functions for the pool.
+        //! This can only be called when no memory is allocated from the pool yet, otherwise results are undefined.
+        //! Allocation function must not return invalid pointer on failure. It should either throw,
+        //! stop the program, or use <code>longjmp()</code> function to pass control to other place of program. 
+        //! If it returns invalid pointer, results are undefined.
+        //! <br><br>
+        //! User defined allocation functions must have the following forms:
+        //! <br><code>
+        //! <br>void *allocate(std::size_t size);
+        //! <br>void free(void *pointer);
+        //! </code><br>
+        //! \param af Allocation function, or 0 to restore default function
+        //! \param ff Free function, or 0 to restore default function
+        void set_allocator(alloc_func *af, free_func *ff)
+        {
+            assert(m_begin == m_static_memory && m_ptr == align(m_begin));    // Verify that no memory is allocated yet
+            m_alloc_func = af;
+            m_free_func = ff;
+        }
+
+    private:
+
+        struct header
+        {
+            char *previous_begin;
+        };
+
+        void init()
+        {
+            m_begin = m_static_memory;
+            m_ptr = align(m_begin);
+            m_end = m_static_memory + sizeof(m_static_memory);
+        }
+        
+        char *align(char *ptr)
+        {
+            std::size_t alignment = ((RAPIDXML_ALIGNMENT - (std::size_t(ptr) & (RAPIDXML_ALIGNMENT - 1))) & (RAPIDXML_ALIGNMENT - 1));
+            return ptr + alignment;
+        }
+        
+        char *allocate_raw(std::size_t size)
+        {
+            // Allocate
+            void *memory;   
+            if (m_alloc_func)   // Allocate memory using either user-specified allocation function or global operator new[]
+            {
+                memory = m_alloc_func(size);
+                assert(memory); // Allocator is not allowed to return 0, on failure it must either throw, stop the program or use longjmp
+            }
+            else
+            {
+                memory = new char[size];
+#ifdef RAPIDXML_NO_EXCEPTIONS
+                if (!memory)            // If exceptions are disabled, verify memory allocation, because new will not be able to throw bad_alloc
+                    RAPIDXML_PARSE_ERROR("out of memory", 0);
+#endif
+            }
+            return static_cast<char *>(memory);
+        }
+        
+        void *allocate_aligned(std::size_t size)
+        {
+            // Calculate aligned pointer
+            char *result = align(m_ptr);
+
+            // If not enough memory left in current pool, allocate a new pool
+            if (result + size > m_end)
+            {
+                // Calculate required pool size (may be bigger than RAPIDXML_DYNAMIC_POOL_SIZE)
+                std::size_t pool_size = RAPIDXML_DYNAMIC_POOL_SIZE;
+                if (pool_size < size)
+                    pool_size = size;
+                
+                // Allocate
+                std::size_t alloc_size = sizeof(header) + (2 * RAPIDXML_ALIGNMENT - 2) + pool_size;     // 2 alignments required in worst case: one for header, one for actual allocation
+                char *raw_memory = allocate_raw(alloc_size);
+                    
+                // Setup new pool in allocated memory
+                char *pool = align(raw_memory);
+                header *new_header = reinterpret_cast<header *>(pool);
+                new_header->previous_begin = m_begin;
+                m_begin = raw_memory;
+                m_ptr = pool + sizeof(header);
+                m_end = raw_memory + alloc_size;
+
+                // Calculate aligned pointer again using new pool
+                result = align(m_ptr);
+            }
+
+            // Update pool and return aligned pointer
+            m_ptr = result + size;
+            return result;
+        }
+
+        char *m_begin;                                      // Start of raw memory making up current pool
+        char *m_ptr;                                        // First free byte in current pool
+        char *m_end;                                        // One past last available byte in current pool
+        char m_static_memory[RAPIDXML_STATIC_POOL_SIZE];    // Static raw memory
+        alloc_func *m_alloc_func;                           // Allocator function, or 0 if default is to be used
+        free_func *m_free_func;                             // Free function, or 0 if default is to be used
+    };
+
+    ///////////////////////////////////////////////////////////////////////////
+    // XML base
+
+    //! Base class for xml_node and xml_attribute implementing common functions: 
+    //! name(), name_size(), value(), value_size() and parent().
+    //! \param Ch Character type to use
+    template<class Ch = char>
+    class xml_base
+    {
+
+    public:
+        
+        ///////////////////////////////////////////////////////////////////////////
+        // Construction & destruction
+    
+        // Construct a base with empty name, value and parent
+        xml_base()
+            : m_name(0)
+            , m_value(0)
+            , m_parent(0)
+        {
+        }
+
+        ///////////////////////////////////////////////////////////////////////////
+        // Node data access
+    
+        //! Gets name of the node. 
+        //! Interpretation of name depends on type of node.
+        //! Note that name will not be zero-terminated if rapidxml::parse_no_string_terminators option was selected during parse.
+        //! <br><br>
+        //! Use name_size() function to determine length of the name.
+        //! \return Name of node, or empty string if node has no name.
+        Ch *name() const
+        {
+            return m_name ? m_name : nullstr();
+        }
+
+        //! Gets size of node name, not including terminator character.
+        //! This function works correctly irrespective of whether name is or is not zero terminated.
+        //! \return Size of node name, in characters.
+        std::size_t name_size() const
+        {
+            return m_name ? m_name_size : 0;
+        }
+
+        //! Gets value of node. 
+        //! Interpretation of value depends on type of node.
+        //! Note that value will not be zero-terminated if rapidxml::parse_no_string_terminators option was selected during parse.
+        //! <br><br>
+        //! Use value_size() function to determine length of the value.
+        //! \return Value of node, or empty string if node has no value.
+        Ch *value() const
+        {
+            return m_value ? m_value : nullstr();
+        }
+
+        //! Gets size of node value, not including terminator character.
+        //! This function works correctly irrespective of whether value is or is not zero terminated.
+        //! \return Size of node value, in characters.
+        std::size_t value_size() const
+        {
+            return m_value ? m_value_size : 0;
+        }
+
+        ///////////////////////////////////////////////////////////////////////////
+        // Node modification
+    
+        //! Sets name of node to a non zero-terminated string.
+        //! See \ref ownership_of_strings.
+        //! <br><br>
+        //! Note that node does not own its name or value, it only stores a pointer to it. 
+        //! It will not delete or otherwise free the pointer on destruction.
+        //! It is reponsibility of the user to properly manage lifetime of the string.
+        //! The easiest way to achieve it is to use memory_pool of the document to allocate the string -
+        //! on destruction of the document the string will be automatically freed.
+        //! <br><br>
+        //! Size of name must be specified separately, because name does not have to be zero terminated.
+        //! Use name(const Ch *) function to have the length automatically calculated (string must be zero terminated).
+        //! \param name Name of node to set. Does not have to be zero terminated.
+        //! \param size Size of name, in characters. This does not include zero terminator, if one is present.
+        void name(const Ch *name, std::size_t size)
+        {
+            m_name = const_cast<Ch *>(name);
+            m_name_size = size;
+        }
+
+        //! Sets name of node to a zero-terminated string.
+        //! See also \ref ownership_of_strings and xml_node::name(const Ch *, std::size_t).
+        //! \param name Name of node to set. Must be zero terminated.
+        void name(const Ch *name)
+        {
+            this->name(name, internal::measure(name));
+        }
+
+        //! Sets value of node to a non zero-terminated string.
+        //! See \ref ownership_of_strings.
+        //! <br><br>
+        //! Note that node does not own its name or value, it only stores a pointer to it. 
+        //! It will not delete or otherwise free the pointer on destruction.
+        //! It is reponsibility of the user to properly manage lifetime of the string.
+        //! The easiest way to achieve it is to use memory_pool of the document to allocate the string -
+        //! on destruction of the document the string will be automatically freed.
+        //! <br><br>
+        //! Size of value must be specified separately, because it does not have to be zero terminated.
+        //! Use value(const Ch *) function to have the length automatically calculated (string must be zero terminated).
+        //! <br><br>
+        //! If an element has a child node of type node_data, it will take precedence over element value when printing.
+        //! If you want to manipulate data of elements using values, use parser flag rapidxml::parse_no_data_nodes to prevent creation of data nodes by the parser.
+        //! \param value value of node to set. Does not have to be zero terminated.
+        //! \param size Size of value, in characters. This does not include zero terminator, if one is present.
+        void value(const Ch *value, std::size_t size)
+        {
+            m_value = const_cast<Ch *>(value);
+            m_value_size = size;
+        }
+
+        //! Sets value of node to a zero-terminated string.
+        //! See also \ref ownership_of_strings and xml_node::value(const Ch *, std::size_t).
+        //! \param value Vame of node to set. Must be zero terminated.
+        void value(const Ch *value)
+        {
+            this->value(value, internal::measure(value));
+        }
+
+        ///////////////////////////////////////////////////////////////////////////
+        // Related nodes access
+    
+        //! Gets node parent.
+        //! \return Pointer to parent node, or 0 if there is no parent.
+        xml_node<Ch> *parent() const
+        {
+            return m_parent;
+        }
+
+    protected:
+
+        // Return empty string
+        static Ch *nullstr()
+        {
+            static Ch zero = Ch('\0');
+            return &zero;
+        }
+
+        Ch *m_name;                         // Name of node, or 0 if no name
+        Ch *m_value;                        // Value of node, or 0 if no value
+        std::size_t m_name_size;            // Length of node name, or undefined of no name
+        std::size_t m_value_size;           // Length of node value, or undefined if no value
+        xml_node<Ch> *m_parent;             // Pointer to parent node, or 0 if none
+
+    };
+
+    //! Class representing attribute node of XML document. 
+    //! Each attribute has name and value strings, which are available through name() and value() functions (inherited from xml_base).
+    //! Note that after parse, both name and value of attribute will point to interior of source text used for parsing. 
+    //! Thus, this text must persist in memory for the lifetime of attribute.
+    //! \param Ch Character type to use.
+    template<class Ch = char>
+    class xml_attribute: public xml_base<Ch>
+    {
+
+        friend class xml_node<Ch>;
+    
+    public:
+
+        ///////////////////////////////////////////////////////////////////////////
+        // Construction & destruction
+    
+        //! Constructs an empty attribute with the specified type. 
+        //! Consider using memory_pool of appropriate xml_document if allocating attributes manually.
+        xml_attribute()
+        {
+        }
+
+        ///////////////////////////////////////////////////////////////////////////
+        // Related nodes access
+    
+        //! Gets document of which attribute is a child.
+        //! \return Pointer to document that contains this attribute, or 0 if there is no parent document.
+        xml_document<Ch> *document() const
+        {
+            if (xml_node<Ch> *node = this->parent())
+            {
+                while (node->parent())
+                    node = node->parent();
+                return node->type() == node_document ? static_cast<xml_document<Ch> *>(node) : 0;
+            }
+            else
+                return 0;
+        }
+
+        //! Gets previous attribute, optionally matching attribute name. 
+        //! \param name Name of attribute to find, or 0 to return previous attribute regardless of its name; this string doesn't have to be zero-terminated if name_size is non-zero
+        //! \param name_size Size of name, in characters, or 0 to have size calculated automatically from string
+        //! \param case_sensitive Should name comparison be case-sensitive; non case-sensitive comparison works properly only for ASCII characters
+        //! \return Pointer to found attribute, or 0 if not found.
+        xml_attribute<Ch> *previous_attribute(const Ch *name = 0, std::size_t name_size = 0, bool case_sensitive = true) const
+        {
+            if (name)
+            {
+                if (name_size == 0)
+                    name_size = internal::measure(name);
+                for (xml_attribute<Ch> *attribute = m_prev_attribute; attribute; attribute = attribute->m_prev_attribute)
+                    if (internal::compare(attribute->name(), attribute->name_size(), name, name_size, case_sensitive))
+                        return attribute;
+                return 0;
+            }
+            else
+                return this->m_parent ? m_prev_attribute : 0;
+        }
+
+        //! Gets next attribute, optionally matching attribute name. 
+        //! \param name Name of attribute to find, or 0 to return next attribute regardless of its name; this string doesn't have to be zero-terminated if name_size is non-zero
+        //! \param name_size Size of name, in characters, or 0 to have size calculated automatically from string
+        //! \param case_sensitive Should name comparison be case-sensitive; non case-sensitive comparison works properly only for ASCII characters
+        //! \return Pointer to found attribute, or 0 if not found.
+        xml_attribute<Ch> *next_attribute(const Ch *name = 0, std::size_t name_size = 0, bool case_sensitive = true) const
+        {
+            if (name)
+            {
+                if (name_size == 0)
+                    name_size = internal::measure(name);
+                for (xml_attribute<Ch> *attribute = m_next_attribute; attribute; attribute = attribute->m_next_attribute)
+                    if (internal::compare(attribute->name(), attribute->name_size(), name, name_size, case_sensitive))
+                        return attribute;
+                return 0;
+            }
+            else
+                return this->m_parent ? m_next_attribute : 0;
+        }
+
+    private:
+
+        xml_attribute<Ch> *m_prev_attribute;        // Pointer to previous sibling of attribute, or 0 if none; only valid if parent is non-zero
+        xml_attribute<Ch> *m_next_attribute;        // Pointer to next sibling of attribute, or 0 if none; only valid if parent is non-zero
+    
+    };
+
+    ///////////////////////////////////////////////////////////////////////////
+    // XML node
+
+    //! Class representing a node of XML document. 
+    //! Each node may have associated name and value strings, which are available through name() and value() functions. 
+    //! Interpretation of name and value depends on type of the node.
+    //! Type of node can be determined by using type() function.
+    //! <br><br>
+    //! Note that after parse, both name and value of node, if any, will point interior of source text used for parsing. 
+    //! Thus, this text must persist in the memory for the lifetime of node.
+    //! \param Ch Character type to use.
+    template<class Ch = char>
+    class xml_node: public xml_base<Ch>
+    {
+
+    public:
+
+        ///////////////////////////////////////////////////////////////////////////
+        // Construction & destruction
+    
+        //! Constructs an empty node with the specified type. 
+        //! Consider using memory_pool of appropriate document to allocate nodes manually.
+        //! \param type Type of node to construct.
+        xml_node(node_type type)
+            : m_type(type)
+            , m_first_node(0)
+            , m_first_attribute(0)
+        {
+        }
+
+        ///////////////////////////////////////////////////////////////////////////
+        // Node data access
+    
+        //! Gets type of node.
+        //! \return Type of node.
+        node_type type() const
+        {
+            return m_type;
+        }
+
+        ///////////////////////////////////////////////////////////////////////////
+        // Related nodes access
+    
+        //! Gets document of which node is a child.
+        //! \return Pointer to document that contains this node, or 0 if there is no parent document.
+        xml_document<Ch> *document() const
+        {
+            xml_node<Ch> *node = const_cast<xml_node<Ch> *>(this);
+            while (node->parent())
+                node = node->parent();
+            return node->type() == node_document ? static_cast<xml_document<Ch> *>(node) : 0;
+        }
+
+        //! Gets first child node, optionally matching node name.
+        //! \param name Name of child to find, or 0 to return first child regardless of its name; this string doesn't have to be zero-terminated if name_size is non-zero
+        //! \param name_size Size of name, in characters, or 0 to have size calculated automatically from string
+        //! \param case_sensitive Should name comparison be case-sensitive; non case-sensitive comparison works properly only for ASCII characters
+        //! \return Pointer to found child, or 0 if not found.
+        xml_node<Ch> *first_node(const Ch *name = 0, std::size_t name_size = 0, bool case_sensitive = true) const
+        {
+            if (name)
+            {
+                if (name_size == 0)
+                    name_size = internal::measure(name);
+                for (xml_node<Ch> *child = m_first_node; child; child = child->next_sibling())
+                    if (internal::compare(child->name(), child->name_size(), name, name_size, case_sensitive))
+                        return child;
+                return 0;
+            }
+            else
+                return m_first_node;
+        }
+
+        //! Gets last child node, optionally matching node name. 
+        //! Behaviour is undefined if node has no children.
+        //! Use first_node() to test if node has children.
+        //! \param name Name of child to find, or 0 to return last child regardless of its name; this string doesn't have to be zero-terminated if name_size is non-zero
+        //! \param name_size Size of name, in characters, or 0 to have size calculated automatically from string
+        //! \param case_sensitive Should name comparison be case-sensitive; non case-sensitive comparison works properly only for ASCII characters
+        //! \return Pointer to found child, or 0 if not found.
+        xml_node<Ch> *last_node(const Ch *name = 0, std::size_t name_size = 0, bool case_sensitive = true) const
+        {
+            assert(m_first_node);  // Cannot query for last child if node has no children
+            if (name)
+            {
+                if (name_size == 0)
+                    name_size = internal::measure(name);
+                for (xml_node<Ch> *child = m_last_node; child; child = child->previous_sibling())
+                    if (internal::compare(child->name(), child->name_size(), name, name_size, case_sensitive))
+                        return child;
+                return 0;
+            }
+            else
+                return m_last_node;
+        }
+
+        //! Gets previous sibling node, optionally matching node name. 
+        //! Behaviour is undefined if node has no parent.
+        //! Use parent() to test if node has a parent.
+        //! \param name Name of sibling to find, or 0 to return previous sibling regardless of its name; this string doesn't have to be zero-terminated if name_size is non-zero
+        //! \param name_size Size of name, in characters, or 0 to have size calculated automatically from string
+        //! \param case_sensitive Should name comparison be case-sensitive; non case-sensitive comparison works properly only for ASCII characters
+        //! \return Pointer to found sibling, or 0 if not found.
+        xml_node<Ch> *previous_sibling(const Ch *name = 0, std::size_t name_size = 0, bool case_sensitive = true) const
+        {
+            assert(this->m_parent);     // Cannot query for siblings if node has no parent
+            if (name)
+            {
+                if (name_size == 0)
+                    name_size = internal::measure(name);
+                for (xml_node<Ch> *sibling = m_prev_sibling; sibling; sibling = sibling->m_prev_sibling)
+                    if (internal::compare(sibling->name(), sibling->name_size(), name, name_size, case_sensitive))
+                        return sibling;
+                return 0;
+            }
+            else
+                return m_prev_sibling;
+        }
+
+        //! Gets next sibling node, optionally matching node name. 
+        //! Behaviour is undefined if node has no parent.
+        //! Use parent() to test if node has a parent.
+        //! \param name Name of sibling to find, or 0 to return next sibling regardless of its name; this string doesn't have to be zero-terminated if name_size is non-zero
+        //! \param name_size Size of name, in characters, or 0 to have size calculated automatically from string
+        //! \param case_sensitive Should name comparison be case-sensitive; non case-sensitive comparison works properly only for ASCII characters
+        //! \return Pointer to found sibling, or 0 if not found.
+        xml_node<Ch> *next_sibling(const Ch *name = 0, std::size_t name_size = 0, bool case_sensitive = true) const
+        {
+            assert(this->m_parent);     // Cannot query for siblings if node has no parent
+            if (name)
+            {
+                if (name_size == 0)
+                    name_size = internal::measure(name);
+                for (xml_node<Ch> *sibling = m_next_sibling; sibling; sibling = sibling->m_next_sibling)
+                    if (internal::compare(sibling->name(), sibling->name_size(), name, name_size, case_sensitive))
+                        return sibling;
+                return 0;
+            }
+            else
+                return m_next_sibling;
+        }
+
+        //! Gets first attribute of node, optionally matching attribute name.
+        //! \param name Name of attribute to find, or 0 to return first attribute regardless of its name; this string doesn't have to be zero-terminated if name_size is non-zero
+        //! \param name_size Size of name, in characters, or 0 to have size calculated automatically from string
+        //! \param case_sensitive Should name comparison be case-sensitive; non case-sensitive comparison works properly only for ASCII characters
+        //! \return Pointer to found attribute, or 0 if not found.
+        xml_attribute<Ch> *first_attribute(const Ch *name = 0, std::size_t name_size = 0, bool case_sensitive = true) const
+        {
+            if (name)
+            {
+                if (name_size == 0)
+                    name_size = internal::measure(name);
+                for (xml_attribute<Ch> *attribute = m_first_attribute; attribute; attribute = attribute->m_next_attribute)
+                    if (internal::compare(attribute->name(), attribute->name_size(), name, name_size, case_sensitive))
+                        return attribute;
+                return 0;
+            }
+            else
+                return m_first_attribute;
+        }
+
+        //! Gets last attribute of node, optionally matching attribute name.
+        //! \param name Name of attribute to find, or 0 to return last attribute regardless of its name; this string doesn't have to be zero-terminated if name_size is non-zero
+        //! \param name_size Size of name, in characters, or 0 to have size calculated automatically from string
+        //! \param case_sensitive Should name comparison be case-sensitive; non case-sensitive comparison works properly only for ASCII characters
+        //! \return Pointer to found attribute, or 0 if not found.
+        xml_attribute<Ch> *last_attribute(const Ch *name = 0, std::size_t name_size = 0, bool case_sensitive = true) const
+        {
+            if (name)
+            {
+                if (name_size == 0)
+                    name_size = internal::measure(name);
+                for (xml_attribute<Ch> *attribute = m_last_attribute; attribute; attribute = attribute->m_prev_attribute)
+                    if (internal::compare(attribute->name(), attribute->name_size(), name, name_size, case_sensitive))
+                        return attribute;
+                return 0;
+            }
+            else
+                return m_first_attribute ? m_last_attribute : 0;
+        }
+
+        ///////////////////////////////////////////////////////////////////////////
+        // Node modification
+    
+        //! Sets type of node.
+        //! \param type Type of node to set.
+        void type(node_type type)
+        {
+            m_type = type;
+        }
+
+        ///////////////////////////////////////////////////////////////////////////
+        // Node manipulation
+
+        //! Prepends a new child node.
+        //! The prepended child becomes the first child, and all existing children are moved one position back.
+        //! \param child Node to prepend.
+        void prepend_node(xml_node<Ch> *child)
+        {
+            assert(child && !child->parent() && child->type() != node_document);
+            if (first_node())
+            {
+                child->m_next_sibling = m_first_node;
+                m_first_node->m_prev_sibling = child;
+            }
+            else
+            {
+                child->m_next_sibling = 0;
+                m_last_node = child;
+            }
+            m_first_node = child;
+            child->m_parent = this;
+            child->m_prev_sibling = 0;
+        }
+
+        //! Appends a new child node. 
+        //! The appended child becomes the last child.
+        //! \param child Node to append.
+        void append_node(xml_node<Ch> *child)
+        {
+            assert(child && !child->parent() && child->type() != node_document);
+            if (first_node())
+            {
+                child->m_prev_sibling = m_last_node;
+                m_last_node->m_next_sibling = child;
+            }
+            else
+            {
+                child->m_prev_sibling = 0;
+                m_first_node = child;
+            }
+            m_last_node = child;
+            child->m_parent = this;
+            child->m_next_sibling = 0;
+        }
+
+        //! Inserts a new child node at specified place inside the node. 
+        //! All children after and including the specified node are moved one position back.
+        //! \param where Place where to insert the child, or 0 to insert at the back.
+        //! \param child Node to insert.
+        void insert_node(xml_node<Ch> *where, xml_node<Ch> *child)
+        {
+            assert(!where || where->parent() == this);
+            assert(child && !child->parent() && child->type() != node_document);
+            if (where == m_first_node)
+                prepend_node(child);
+            else if (where == 0)
+                append_node(child);
+            else
+            {
+                child->m_prev_sibling = where->m_prev_sibling;
+                child->m_next_sibling = where;
+                where->m_prev_sibling->m_next_sibling = child;
+                where->m_prev_sibling = child;
+                child->m_parent = this;
+            }
+        }
+
+        //! Removes first child node. 
+        //! If node has no children, behaviour is undefined.
+        //! Use first_node() to test if node has children.
+        void remove_first_node()
+        {
+            assert(first_node());
+            xml_node<Ch> *child = m_first_node;
+            m_first_node = child->m_next_sibling;
+            if (child->m_next_sibling)
+                child->m_next_sibling->m_prev_sibling = 0;
+            else
+                m_last_node = 0;
+            child->m_parent = 0;
+        }
+
+        //! Removes last child of the node. 
+        //! If node has no children, behaviour is undefined.
+        //! Use first_node() to test if node has children.
+        void remove_last_node()
+        {
+            assert(first_node());
+            xml_node<Ch> *child = m_last_node;
+            if (child->m_prev_sibling)
+            {
+                m_last_node = child->m_prev_sibling;
+                child->m_prev_sibling->m_next_sibling = 0;
+            }
+            else
+                m_first_node = 0;
+            child->m_parent = 0;
+        }
+
+        //! Removes specified child from the node
+        // \param where Pointer to child to be removed.
+        void remove_node(xml_node<Ch> *where)
+        {
+            assert(where && where->parent() == this);
+            assert(first_node());
+            if (where == m_first_node)
+                remove_first_node();
+            else if (where == m_last_node)
+                remove_last_node();
+            else
+            {
+                where->m_prev_sibling->m_next_sibling = where->m_next_sibling;
+                where->m_next_sibling->m_prev_sibling = where->m_prev_sibling;
+                where->m_parent = 0;
+            }
+        }
+
+        //! Removes all child nodes (but not attributes).
+        void remove_all_nodes()
+        {
+            for (xml_node<Ch> *node = first_node(); node; node = node->m_next_sibling)
+                node->m_parent = 0;
+            m_first_node = 0;
+        }
+
+        //! Prepends a new attribute to the node.
+        //! \param attribute Attribute to prepend.
+        void prepend_attribute(xml_attribute<Ch> *attribute)
+        {
+            assert(attribute && !attribute->parent());
+            if (first_attribute())
+            {
+                attribute->m_next_attribute = m_first_attribute;
+                m_first_attribute->m_prev_attribute = attribute;
+            }
+            else
+            {
+                attribute->m_next_attribute = 0;
+                m_last_attribute = attribute;
+            }
+            m_first_attribute = attribute;
+            attribute->m_parent = this;
+            attribute->m_prev_attribute = 0;
+        }
+
+        //! Appends a new attribute to the node.
+        //! \param attribute Attribute to append.
+        void append_attribute(xml_attribute<Ch> *attribute)
+        {
+            assert(attribute && !attribute->parent());
+            if (first_attribute())
+            {
+                attribute->m_prev_attribute = m_last_attribute;
+                m_last_attribute->m_next_attribute = attribute;
+            }
+            else
+            {
+                attribute->m_prev_attribute = 0;
+                m_first_attribute = attribute;
+            }
+            m_last_attribute = attribute;
+            attribute->m_parent = this;
+            attribute->m_next_attribute = 0;
+        }
+
+        //! Inserts a new attribute at specified place inside the node. 
+        //! All attributes after and including the specified attribute are moved one position back.
+        //! \param where Place where to insert the attribute, or 0 to insert at the back.
+        //! \param attribute Attribute to insert.
+        void insert_attribute(xml_attribute<Ch> *where, xml_attribute<Ch> *attribute)
+        {
+            assert(!where || where->parent() == this);
+            assert(attribute && !attribute->parent());
+            if (where == m_first_attribute)
+                prepend_attribute(attribute);
+            else if (where == 0)
+                append_attribute(attribute);
+            else
+            {
+                attribute->m_prev_attribute = where->m_prev_attribute;
+                attribute->m_next_attribute = where;
+                where->m_prev_attribute->m_next_attribute = attribute;
+                where->m_prev_attribute = attribute;
+                attribute->m_parent = this;
+            }
+        }
+
+        //! Removes first attribute of the node. 
+        //! If node has no attributes, behaviour is undefined.
+        //! Use first_attribute() to test if node has attributes.
+        void remove_first_attribute()
+        {
+            assert(first_attribute());
+            xml_attribute<Ch> *attribute = m_first_attribute;
+            if (attribute->m_next_attribute)
+            {
+                attribute->m_next_attribute->m_prev_attribute = 0;
+            }
+            else
+                m_last_attribute = 0;
+            attribute->m_parent = 0;
+            m_first_attribute = attribute->m_next_attribute;
+        }
+
+        //! Removes last attribute of the node. 
+        //! If node has no attributes, behaviour is undefined.
+        //! Use first_attribute() to test if node has attributes.
+        void remove_last_attribute()
+        {
+            assert(first_attribute());
+            xml_attribute<Ch> *attribute = m_last_attribute;
+            if (attribute->m_prev_attribute)
+            {
+                attribute->m_prev_attribute->m_next_attribute = 0;
+                m_last_attribute = attribute->m_prev_attribute;
+            }
+            else
+                m_first_attribute = 0;
+            attribute->m_parent = 0;
+        }
+
+        //! Removes specified attribute from node.
+        //! \param where Pointer to attribute to be removed.
+        void remove_attribute(xml_attribute<Ch> *where)
+        {
+            assert(first_attribute() && where->parent() == this);
+            if (where == m_first_attribute)
+                remove_first_attribute();
+            else if (where == m_last_attribute)
+                remove_last_attribute();
+            else
+            {
+                where->m_prev_attribute->m_next_attribute = where->m_next_attribute;
+                where->m_next_attribute->m_prev_attribute = where->m_prev_attribute;
+                where->m_parent = 0;
+            }
+        }
+
+        //! Removes all attributes of node.
+        void remove_all_attributes()
+        {
+            for (xml_attribute<Ch> *attribute = first_attribute(); attribute; attribute = attribute->m_next_attribute)
+                attribute->m_parent = 0;
+            m_first_attribute = 0;
+        }
+        
+    private:
+
+        ///////////////////////////////////////////////////////////////////////////
+        // Restrictions
+
+        // No copying
+        xml_node(const xml_node &);
+        void operator =(const xml_node &);
+    
+        ///////////////////////////////////////////////////////////////////////////
+        // Data members
+    
+        // Note that some of the pointers below have UNDEFINED values if certain other pointers are 0.
+        // This is required for maximum performance, as it allows the parser to omit initialization of 
+        // unneded/redundant values.
+        //
+        // The rules are as follows:
+        // 1. first_node and first_attribute contain valid pointers, or 0 if node has no children/attributes respectively
+        // 2. last_node and last_attribute are valid only if node has at least one child/attribute respectively, otherwise they contain garbage
+        // 3. prev_sibling and next_sibling are valid only if node has a parent, otherwise they contain garbage
+
+        node_type m_type;                       // Type of node; always valid
+        xml_node<Ch> *m_first_node;             // Pointer to first child node, or 0 if none; always valid
+        xml_node<Ch> *m_last_node;              // Pointer to last child node, or 0 if none; this value is only valid if m_first_node is non-zero
+        xml_attribute<Ch> *m_first_attribute;   // Pointer to first attribute of node, or 0 if none; always valid
+        xml_attribute<Ch> *m_last_attribute;    // Pointer to last attribute of node, or 0 if none; this value is only valid if m_first_attribute is non-zero
+        xml_node<Ch> *m_prev_sibling;           // Pointer to previous sibling of node, or 0 if none; this value is only valid if m_parent is non-zero
+        xml_node<Ch> *m_next_sibling;           // Pointer to next sibling of node, or 0 if none; this value is only valid if m_parent is non-zero
+
+    };
+
+    ///////////////////////////////////////////////////////////////////////////
+    // XML document
+    
+    //! This class represents root of the DOM hierarchy. 
+    //! It is also an xml_node and a memory_pool through public inheritance.
+    //! Use parse() function to build a DOM tree from a zero-terminated XML text string.
+    //! parse() function allocates memory for nodes and attributes by using functions of xml_document, 
+    //! which are inherited from memory_pool.
+    //! To access root node of the document, use the document itself, as if it was an xml_node.
+    //! \param Ch Character type to use.
+    template<class Ch = char>
+    class xml_document: public xml_node<Ch>, public memory_pool<Ch>
+    {
+    
+    public:
+
+        //! Constructs empty XML document
+        xml_document()
+            : xml_node<Ch>(node_document)
+        {
+        }
+
+        //! Parses zero-terminated XML string according to given flags.
+        //! Passed string will be modified by the parser, unless rapidxml::parse_non_destructive flag is used.
+        //! The string must persist for the lifetime of the document.
+        //! In case of error, rapidxml::parse_error exception will be thrown.
+        //! <br><br>
+        //! If you want to parse contents of a file, you must first load the file into the memory, and pass pointer to its beginning.
+        //! Make sure that data is zero-terminated.
+        //! <br><br>
+        //! Document can be parsed into multiple times. 
+        //! Each new call to parse removes previous nodes and attributes (if any), but does not clear memory pool.
+        //! \param text XML data to parse; pointer is non-const to denote fact that this data may be modified by the parser.
+        template<int Flags>
+        void parse(Ch *text)
+        {
+            assert(text);
+            
+            // Remove current contents
+            this->remove_all_nodes();
+            this->remove_all_attributes();
+            
+            // Parse BOM, if any
+            parse_bom<Flags>(text);
+            
+            // Parse children
+            while (1)
+            {
+                // Skip whitespace before node
+                skip<whitespace_pred, Flags>(text);
+                if (*text == 0)
+                    break;
+
+                // Parse and append new child
+                if (*text == Ch('<'))
+                {
+                    ++text;     // Skip '<'
+                    if (xml_node<Ch> *node = parse_node<Flags>(text))
+                        this->append_node(node);
+                }
+                else
+                    RAPIDXML_PARSE_ERROR("expected <", text);
+            }
+
+        }
+
+        //! Clears the document by deleting all nodes and clearing the memory pool.
+        //! All nodes owned by document pool are destroyed.
+        void clear()
+        {
+            this->remove_all_nodes();
+            this->remove_all_attributes();
+            memory_pool<Ch>::clear();
+        }
+        
+    private:
+
+        ///////////////////////////////////////////////////////////////////////
+        // Internal character utility functions
+        
+        // Detect whitespace character
+        struct whitespace_pred
+        {
+            static unsigned char test(Ch ch)
+            {
+                return internal::lookup_tables<0>::lookup_whitespace[static_cast<unsigned char>(ch)];
+            }
+        };
+
+        // Detect node name character
+        struct node_name_pred
+        {
+            static unsigned char test(Ch ch)
+            {
+                return internal::lookup_tables<0>::lookup_node_name[static_cast<unsigned char>(ch)];
+            }
+        };
+
+        // Detect attribute name character
+        struct attribute_name_pred
+        {
+            static unsigned char test(Ch ch)
+            {
+                return internal::lookup_tables<0>::lookup_attribute_name[static_cast<unsigned char>(ch)];
+            }
+        };
+
+        // Detect text character (PCDATA)
+        struct text_pred
+        {
+            static unsigned char test(Ch ch)
+            {
+                return internal::lookup_tables<0>::lookup_text[static_cast<unsigned char>(ch)];
+            }
+        };
+
+        // Detect text character (PCDATA) that does not require processing
+        struct text_pure_no_ws_pred
+        {
+            static unsigned char test(Ch ch)
+            {
+                return internal::lookup_tables<0>::lookup_text_pure_no_ws[static_cast<unsigned char>(ch)];
+            }
+        };
+
+        // Detect text character (PCDATA) that does not require processing
+        struct text_pure_with_ws_pred
+        {
+            static unsigned char test(Ch ch)
+            {
+                return internal::lookup_tables<0>::lookup_text_pure_with_ws[static_cast<unsigned char>(ch)];
+            }
+        };
+
+        // Detect attribute value character
+        template<Ch Quote>
+        struct attribute_value_pred
+        {
+            static unsigned char test(Ch ch)
+            {
+                if (Quote == Ch('\''))
+                    return internal::lookup_tables<0>::lookup_attribute_data_1[static_cast<unsigned char>(ch)];
+                if (Quote == Ch('\"'))
+                    return internal::lookup_tables<0>::lookup_attribute_data_2[static_cast<unsigned char>(ch)];
+                return 0;       // Should never be executed, to avoid warnings on Comeau
+            }
+        };
+
+        // Detect attribute value character
+        template<Ch Quote>
+        struct attribute_value_pure_pred
+        {
+            static unsigned char test(Ch ch)
+            {
+                if (Quote == Ch('\''))
+                    return internal::lookup_tables<0>::lookup_attribute_data_1_pure[static_cast<unsigned char>(ch)];
+                if (Quote == Ch('\"'))
+                    return internal::lookup_tables<0>::lookup_attribute_data_2_pure[static_cast<unsigned char>(ch)];
+                return 0;       // Should never be executed, to avoid warnings on Comeau
+            }
+        };
+
+        // Insert coded character, using UTF8 or 8-bit ASCII
+        template<int Flags>
+        static void insert_coded_character(Ch *&text, unsigned long code)
+        {
+            if (Flags & parse_no_utf8)
+            {
+                // Insert 8-bit ASCII character
+                // Todo: possibly verify that code is less than 256 and use replacement char otherwise?
+                text[0] = static_cast<unsigned char>(code);
+                text += 1;
+            }
+            else
+            {
+                // Insert UTF8 sequence
+                if (code < 0x80)    // 1 byte sequence
+                {
+	                text[0] = static_cast<unsigned char>(code);
+                    text += 1;
+                }
+                else if (code < 0x800)  // 2 byte sequence
+                {
+	                text[1] = static_cast<unsigned char>((code | 0x80) & 0xBF); code >>= 6;
+	                text[0] = static_cast<unsigned char>(code | 0xC0);
+                    text += 2;
+                }
+	            else if (code < 0x10000)    // 3 byte sequence
+                {
+	                text[2] = static_cast<unsigned char>((code | 0x80) & 0xBF); code >>= 6;
+	                text[1] = static_cast<unsigned char>((code | 0x80) & 0xBF); code >>= 6;
+	                text[0] = static_cast<unsigned char>(code | 0xE0);
+                    text += 3;
+                }
+	            else if (code < 0x110000)   // 4 byte sequence
+                {
+	                text[3] = static_cast<unsigned char>((code | 0x80) & 0xBF); code >>= 6;
+	                text[2] = static_cast<unsigned char>((code | 0x80) & 0xBF); code >>= 6;
+	                text[1] = static_cast<unsigned char>((code | 0x80) & 0xBF); code >>= 6;
+	                text[0] = static_cast<unsigned char>(code | 0xF0);
+                    text += 4;
+                }
+                else    // Invalid, only codes up to 0x10FFFF are allowed in Unicode
+                {
+                    RAPIDXML_PARSE_ERROR("invalid numeric character entity", text);
+                }
+            }
+        }
+
+        // Skip characters until predicate evaluates to true
+        template<class StopPred, int Flags>
+        static void skip(Ch *&text)
+        {
+            Ch *tmp = text;
+            while (StopPred::test(*tmp))
+                ++tmp;
+            text = tmp;
+        }
+
+        // Skip characters until predicate evaluates to true while doing the following:
+        // - replacing XML character entity references with proper characters (' & " < > &#...;)
+        // - condensing whitespace sequences to single space character
+        template<class StopPred, class StopPredPure, int Flags>
+        static Ch *skip_and_expand_character_refs(Ch *&text)
+        {
+            // If entity translation, whitespace condense and whitespace trimming is disabled, use plain skip
+            if (Flags & parse_no_entity_translation && 
+                !(Flags & parse_normalize_whitespace) &&
+                !(Flags & parse_trim_whitespace))
+            {
+                skip<StopPred, Flags>(text);
+                return text;
+            }
+            
+            // Use simple skip until first modification is detected
+            skip<StopPredPure, Flags>(text);
+
+            // Use translation skip
+            Ch *src = text;
+            Ch *dest = src;
+            while (StopPred::test(*src))
+            {
+                // If entity translation is enabled    
+                if (!(Flags & parse_no_entity_translation))
+                {
+                    // Test if replacement is needed
+                    if (src[0] == Ch('&'))
+                    {
+                        switch (src[1])
+                        {
+
+                        // & '
+                        case Ch('a'): 
+                            if (src[2] == Ch('m') && src[3] == Ch('p') && src[4] == Ch(';'))
+                            {
+                                *dest = Ch('&');
+                                ++dest;
+                                src += 5;
+                                continue;
+                            }
+                            if (src[2] == Ch('p') && src[3] == Ch('o') && src[4] == Ch('s') && src[5] == Ch(';'))
+                            {
+                                *dest = Ch('\'');
+                                ++dest;
+                                src += 6;
+                                continue;
+                            }
+                            break;
+
+                        // "
+                        case Ch('q'): 
+                            if (src[2] == Ch('u') && src[3] == Ch('o') && src[4] == Ch('t') && src[5] == Ch(';'))
+                            {
+                                *dest = Ch('"');
+                                ++dest;
+                                src += 6;
+                                continue;
+                            }
+                            break;
+
+                        // >
+                        case Ch('g'): 
+                            if (src[2] == Ch('t') && src[3] == Ch(';'))
+                            {
+                                *dest = Ch('>');
+                                ++dest;
+                                src += 4;
+                                continue;
+                            }
+                            break;
+
+                        // <
+                        case Ch('l'): 
+                            if (src[2] == Ch('t') && src[3] == Ch(';'))
+                            {
+                                *dest = Ch('<');
+                                ++dest;
+                                src += 4;
+                                continue;
+                            }
+                            break;
+
+                        // &#...; - assumes ASCII
+                        case Ch('#'): 
+                            if (src[2] == Ch('x'))
+                            {
+                                unsigned long code = 0;
+                                src += 3;   // Skip &#x
+                                while (1)
+                                {
+                                    unsigned char digit = internal::lookup_tables<0>::lookup_digits[static_cast<unsigned char>(*src)];
+                                    if (digit == 0xFF)
+                                        break;
+                                    code = code * 16 + digit;
+                                    ++src;
+                                }
+                                insert_coded_character<Flags>(dest, code);    // Put character in output
+                            }
+                            else
+                            {
+                                unsigned long code = 0;
+                                src += 2;   // Skip &#
+                                while (1)
+                                {
+                                    unsigned char digit = internal::lookup_tables<0>::lookup_digits[static_cast<unsigned char>(*src)];
+                                    if (digit == 0xFF)
+                                        break;
+                                    code = code * 10 + digit;
+                                    ++src;
+                                }
+                                insert_coded_character<Flags>(dest, code);    // Put character in output
+                            }
+                            if (*src == Ch(';'))
+                                ++src;
+                            else
+                                RAPIDXML_PARSE_ERROR("expected ;", src);
+                            continue;
+
+                        // Something else
+                        default:
+                            // Ignore, just copy '&' verbatim
+                            break;
+
+                        }
+                    }
+                }
+                
+                // If whitespace condensing is enabled
+                if (Flags & parse_normalize_whitespace)
+                {
+                    // Test if condensing is needed                 
+                    if (whitespace_pred::test(*src))
+                    {
+                        *dest = Ch(' '); ++dest;    // Put single space in dest
+                        ++src;                      // Skip first whitespace char
+                        // Skip remaining whitespace chars
+                        while (whitespace_pred::test(*src))
+                            ++src;
+                        continue;
+                    }
+                }
+
+                // No replacement, only copy character
+                *dest++ = *src++;
+
+            }
+
+            // Return new end
+            text = src;
+            return dest;
+
+        }
+
+        ///////////////////////////////////////////////////////////////////////
+        // Internal parsing functions
+        
+        // Parse BOM, if any
+        template<int Flags>
+        void parse_bom(Ch *&text)
+        {
+            // UTF-8?
+            if (static_cast<unsigned char>(text[0]) == 0xEF && 
+                static_cast<unsigned char>(text[1]) == 0xBB && 
+                static_cast<unsigned char>(text[2]) == 0xBF)
+            {
+                text += 3;      // Skup utf-8 bom
+            }
+        }
+
+        // Parse XML declaration (<?xml...)
+        template<int Flags>
+        xml_node<Ch> *parse_xml_declaration(Ch *&text)
+        {
+            // If parsing of declaration is disabled
+            if (!(Flags & parse_declaration_node))
+            {
+                // Skip until end of declaration
+                while (text[0] != Ch('?') || text[1] != Ch('>'))
+                {
+                    if (!text[0])
+                        RAPIDXML_PARSE_ERROR("unexpected end of data", text);
+                    ++text;
+                }
+                text += 2;    // Skip '?>'
+                return 0;
+            }
+
+            // Create declaration
+            xml_node<Ch> *declaration = this->allocate_node(node_declaration);
+
+            // Skip whitespace before attributes or ?>
+            skip<whitespace_pred, Flags>(text);
+
+            // Parse declaration attributes
+            parse_node_attributes<Flags>(text, declaration);
+            
+            // Skip ?>
+            if (text[0] != Ch('?') || text[1] != Ch('>'))
+                RAPIDXML_PARSE_ERROR("expected ?>", text);
+            text += 2;
+            
+            return declaration;
+        }
+
+        // Parse XML comment (<!--...)
+        template<int Flags>
+        xml_node<Ch> *parse_comment(Ch *&text)
+        {
+            // If parsing of comments is disabled
+            if (!(Flags & parse_comment_nodes))
+            {
+                // Skip until end of comment
+                while (text[0] != Ch('-') || text[1] != Ch('-') || text[2] != Ch('>'))
+                {
+                    if (!text[0])
+                        RAPIDXML_PARSE_ERROR("unexpected end of data", text);
+                    ++text;
+                }
+                text += 3;     // Skip '-->'
+                return 0;      // Do not produce comment node
+            }
+
+            // Remember value start
+            Ch *value = text;
+
+            // Skip until end of comment
+            while (text[0] != Ch('-') || text[1] != Ch('-') || text[2] != Ch('>'))
+            {
+                if (!text[0])
+                    RAPIDXML_PARSE_ERROR("unexpected end of data", text);
+                ++text;
+            }
+
+            // Create comment node
+            xml_node<Ch> *comment = this->allocate_node(node_comment);
+            comment->value(value, text - value);
+            
+            // Place zero terminator after comment value
+            if (!(Flags & parse_no_string_terminators))
+                *text = Ch('\0');
+            
+            text += 3;     // Skip '-->'
+            return comment;
+        }
+
+        // Parse DOCTYPE
+        template<int Flags>
+        xml_node<Ch> *parse_doctype(Ch *&text)
+        {
+            // Remember value start
+            Ch *value = text;
+
+            // Skip to >
+            while (*text != Ch('>'))
+            {
+                // Determine character type
+                switch (*text)
+                {
+                
+                // If '[' encountered, scan for matching ending ']' using naive algorithm with depth
+                // This works for all W3C test files except for 2 most wicked
+                case Ch('['):
+                {
+                    ++text;     // Skip '['
+                    int depth = 1;
+                    while (depth > 0)
+                    {
+                        switch (*text)
+                        {
+                            case Ch('['): ++depth; break;
+                            case Ch(']'): --depth; break;
+                            case 0: RAPIDXML_PARSE_ERROR("unexpected end of data", text);
+                        }
+                        ++text;
+                    }
+                    break;
+                }
+                
+                // Error on end of text
+                case Ch('\0'):
+                    RAPIDXML_PARSE_ERROR("unexpected end of data", text);
+                
+                // Other character, skip it
+                default:
+                    ++text;
+
+                }
+            }
+            
+            // If DOCTYPE nodes enabled
+            if (Flags & parse_doctype_node)
+            {
+                // Create a new doctype node
+                xml_node<Ch> *doctype = this->allocate_node(node_doctype);
+                doctype->value(value, text - value);
+                
+                // Place zero terminator after value
+                if (!(Flags & parse_no_string_terminators))
+                    *text = Ch('\0');
+
+                text += 1;      // skip '>'
+                return doctype;
+            }
+            else
+            {
+                text += 1;      // skip '>'
+                return 0;
+            }
+
+        }
+
+        // Parse PI
+        template<int Flags>
+        xml_node<Ch> *parse_pi(Ch *&text)
+        {
+            // If creation of PI nodes is enabled
+            if (Flags & parse_pi_nodes)
+            {
+                // Create pi node
+                xml_node<Ch> *pi = this->allocate_node(node_pi);
+
+                // Extract PI target name
+                Ch *name = text;
+                skip<node_name_pred, Flags>(text);
+                if (text == name)
+                    RAPIDXML_PARSE_ERROR("expected PI target", text);
+                pi->name(name, text - name);
+                
+                // Skip whitespace between pi target and pi
+                skip<whitespace_pred, Flags>(text);
+
+                // Remember start of pi
+                Ch *value = text;
+                
+                // Skip to '?>'
+                while (text[0] != Ch('?') || text[1] != Ch('>'))
+                {
+                    if (*text == Ch('\0'))
+                        RAPIDXML_PARSE_ERROR("unexpected end of data", text);
+                    ++text;
+                }
+
+                // Set pi value (verbatim, no entity expansion or whitespace normalization)
+                pi->value(value, text - value);     
+                
+                // Place zero terminator after name and value
+                if (!(Flags & parse_no_string_terminators))
+                {
+                    pi->name()[pi->name_size()] = Ch('\0');
+                    pi->value()[pi->value_size()] = Ch('\0');
+                }
+                
+                text += 2;                          // Skip '?>'
+                return pi;
+            }
+            else
+            {
+                // Skip to '?>'
+                while (text[0] != Ch('?') || text[1] != Ch('>'))
+                {
+                    if (*text == Ch('\0'))
+                        RAPIDXML_PARSE_ERROR("unexpected end of data", text);
+                    ++text;
+                }
+                text += 2;    // Skip '?>'
+                return 0;
+            }
+        }
+
+        // Parse and append data
+        // Return character that ends data.
+        // This is necessary because this character might have been overwritten by a terminating 0
+        template<int Flags>
+        Ch parse_and_append_data(xml_node<Ch> *node, Ch *&text, Ch *contents_start)
+        {
+            // Backup to contents start if whitespace trimming is disabled
+            if (!(Flags & parse_trim_whitespace))
+                text = contents_start;     
+            
+            // Skip until end of data
+            Ch *value = text, *end;
+            if (Flags & parse_normalize_whitespace)
+                end = skip_and_expand_character_refs<text_pred, text_pure_with_ws_pred, Flags>(text);   
+            else
+                end = skip_and_expand_character_refs<text_pred, text_pure_no_ws_pred, Flags>(text);
+
+            // Trim trailing whitespace if flag is set; leading was already trimmed by whitespace skip after >
+            if (Flags & parse_trim_whitespace)
+            {
+                if (Flags & parse_normalize_whitespace)
+                {
+                    // Whitespace is already condensed to single space characters by skipping function, so just trim 1 char off the end
+                    if (*(end - 1) == Ch(' '))
+                        --end;
+                }
+                else
+                {
+                    // Backup until non-whitespace character is found
+                    while (whitespace_pred::test(*(end - 1)))
+                        --end;
+                }
+            }
+            
+            // If characters are still left between end and value (this test is only necessary if normalization is enabled)
+            // Create new data node
+            if (!(Flags & parse_no_data_nodes))
+            {
+                xml_node<Ch> *data = this->allocate_node(node_data);
+                data->value(value, end - value);
+                node->append_node(data);
+            }
+
+            // Add data to parent node if no data exists yet
+            if (!(Flags & parse_no_element_values)) 
+                if (*node->value() == Ch('\0'))
+                    node->value(value, end - value);
+
+            // Place zero terminator after value
+            if (!(Flags & parse_no_string_terminators))
+            {
+                Ch ch = *text;
+                *end = Ch('\0');
+                return ch;      // Return character that ends data; this is required because zero terminator overwritten it
+            }
+
+            // Return character that ends data
+            return *text;
+        }
+
+        // Parse CDATA
+        template<int Flags>
+        xml_node<Ch> *parse_cdata(Ch *&text)
+        {
+            // If CDATA is disabled
+            if (Flags & parse_no_data_nodes)
+            {
+                // Skip until end of cdata
+                while (text[0] != Ch(']') || text[1] != Ch(']') || text[2] != Ch('>'))
+                {
+                    if (!text[0])
+                        RAPIDXML_PARSE_ERROR("unexpected end of data", text);
+                    ++text;
+                }
+                text += 3;      // Skip ]]>
+                return 0;       // Do not produce CDATA node
+            }
+
+            // Skip until end of cdata
+            Ch *value = text;
+            while (text[0] != Ch(']') || text[1] != Ch(']') || text[2] != Ch('>'))
+            {
+                if (!text[0])
+                    RAPIDXML_PARSE_ERROR("unexpected end of data", text);
+                ++text;
+            }
+
+            // Create new cdata node
+            xml_node<Ch> *cdata = this->allocate_node(node_cdata);
+            cdata->value(value, text - value);
+
+            // Place zero terminator after value
+            if (!(Flags & parse_no_string_terminators))
+                *text = Ch('\0');
+
+            text += 3;      // Skip ]]>
+            return cdata;
+        }
+        
+        // Parse element node
+        template<int Flags>
+        xml_node<Ch> *parse_element(Ch *&text)
+        {
+            // Create element node
+            xml_node<Ch> *element = this->allocate_node(node_element);
+
+            // Extract element name
+            Ch *name = text;
+            skip<node_name_pred, Flags>(text);
+            if (text == name)
+                RAPIDXML_PARSE_ERROR("expected element name", text);
+            element->name(name, text - name);
+            
+            // Skip whitespace between element name and attributes or >
+            skip<whitespace_pred, Flags>(text);
+
+            // Parse attributes, if any
+            parse_node_attributes<Flags>(text, element);
+
+            // Determine ending type
+            if (*text == Ch('>'))
+            {
+                ++text;
+                parse_node_contents<Flags>(text, element);
+            }
+            else if (*text == Ch('/'))
+            {
+                ++text;
+                if (*text != Ch('>'))
+                    RAPIDXML_PARSE_ERROR("expected >", text);
+                ++text;
+            }
+            else
+                RAPIDXML_PARSE_ERROR("expected >", text);
+
+            // Place zero terminator after name
+            if (!(Flags & parse_no_string_terminators))
+                element->name()[element->name_size()] = Ch('\0');
+
+            // Return parsed element
+            return element;
+        }
+
+        // Determine node type, and parse it
+        template<int Flags>
+        xml_node<Ch> *parse_node(Ch *&text)
+        {
+            // Parse proper node type
+            switch (text[0])
+            {
+
+            // <...
+            default: 
+                // Parse and append element node
+                return parse_element<Flags>(text);
+
+            // <?...
+            case Ch('?'): 
+                ++text;     // Skip ?
+                if ((text[0] == Ch('x') || text[0] == Ch('X')) &&
+                    (text[1] == Ch('m') || text[1] == Ch('M')) && 
+                    (text[2] == Ch('l') || text[2] == Ch('L')) &&
+                    whitespace_pred::test(text[3]))
+                {
+                    // '<?xml ' - xml declaration
+                    text += 4;      // Skip 'xml '
+                    return parse_xml_declaration<Flags>(text);
+                }
+                else
+                {
+                    // Parse PI
+                    return parse_pi<Flags>(text);
+                }
+            
+            // <!...
+            case Ch('!'): 
+
+                // Parse proper subset of <! node
+                switch (text[1])    
+                {
+                
+                // <!-
+                case Ch('-'):
+                    if (text[2] == Ch('-'))
+                    {
+                        // '<!--' - xml comment
+                        text += 3;     // Skip '!--'
+                        return parse_comment<Flags>(text);
+                    }
+                    break;
+
+                // <![
+                case Ch('['):
+                    if (text[2] == Ch('C') && text[3] == Ch('D') && text[4] == Ch('A') && 
+                        text[5] == Ch('T') && text[6] == Ch('A') && text[7] == Ch('['))
+                    {
+                        // '<![CDATA[' - cdata
+                        text += 8;     // Skip '![CDATA['
+                        return parse_cdata<Flags>(text);
+                    }
+                    break;
+
+                // <!D
+                case Ch('D'):
+                    if (text[2] == Ch('O') && text[3] == Ch('C') && text[4] == Ch('T') && 
+                        text[5] == Ch('Y') && text[6] == Ch('P') && text[7] == Ch('E') && 
+                        whitespace_pred::test(text[8]))
+                    {
+                        // '<!DOCTYPE ' - doctype
+                        text += 9;      // skip '!DOCTYPE '
+                        return parse_doctype<Flags>(text);
+                    }
+
+                }   // switch
+
+                // Attempt to skip other, unrecognized node types starting with <!
+                ++text;     // Skip !
+                while (*text != Ch('>'))
+                {
+                    if (*text == 0)
+                        RAPIDXML_PARSE_ERROR("unexpected end of data", text);
+                    ++text;
+                }
+                ++text;     // Skip '>'
+                return 0;   // No node recognized
+
+            }
+        }
+
+        // Parse contents of the node - children, data etc.
+        template<int Flags>
+        void parse_node_contents(Ch *&text, xml_node<Ch> *node)
+        {
+            // For all children and text
+            while (1)
+            {
+                // Skip whitespace between > and node contents
+                Ch *contents_start = text;      // Store start of node contents before whitespace is skipped
+                skip<whitespace_pred, Flags>(text);
+                Ch next_char = *text;
+
+            // After data nodes, instead of continuing the loop, control jumps here.
+            // This is because zero termination inside parse_and_append_data() function
+            // would wreak havoc with the above code.
+            // Also, skipping whitespace after data nodes is unnecessary.
+            after_data_node:    
+                
+                // Determine what comes next: node closing, child node, data node, or 0?
+                switch (next_char)
+                {
+                
+                // Node closing or child node
+                case Ch('<'):
+                    if (text[1] == Ch('/'))
+                    {
+                        // Node closing
+                        text += 2;      // Skip '</'
+                        if (Flags & parse_validate_closing_tags)
+                        {
+                            // Skip and validate closing tag name
+                            Ch *closing_name = text;
+                            skip<node_name_pred, Flags>(text);
+                            if (!internal::compare(node->name(), node->name_size(), closing_name, text - closing_name, true))
+                                RAPIDXML_PARSE_ERROR("invalid closing tag name", text);
+                        }
+                        else
+                        {
+                            // No validation, just skip name
+                            skip<node_name_pred, Flags>(text);
+                        }
+                        // Skip remaining whitespace after node name
+                        skip<whitespace_pred, Flags>(text);
+                        if (*text != Ch('>'))
+                            RAPIDXML_PARSE_ERROR("expected >", text);
+                        ++text;     // Skip '>'
+                        return;     // Node closed, finished parsing contents
+                    }
+                    else
+                    {
+                        // Child node
+                        ++text;     // Skip '<'
+                        if (xml_node<Ch> *child = parse_node<Flags>(text))
+                            node->append_node(child);
+                    }
+                    break;
+
+                // End of data - error
+                case Ch('\0'):
+                    RAPIDXML_PARSE_ERROR("unexpected end of data", text);
+
+                // Data node
+                default:
+                    next_char = parse_and_append_data<Flags>(node, text, contents_start);
+                    goto after_data_node;   // Bypass regular processing after data nodes
+
+                }
+            }
+        }
+        
+        // Parse XML attributes of the node
+        template<int Flags>
+        void parse_node_attributes(Ch *&text, xml_node<Ch> *node)
+        {
+            // For all attributes 
+            while (attribute_name_pred::test(*text))
+            {
+                // Extract attribute name
+                Ch *name = text;
+                ++text;     // Skip first character of attribute name
+                skip<attribute_name_pred, Flags>(text);
+                if (text == name)
+                    RAPIDXML_PARSE_ERROR("expected attribute name", name);
+
+                // Create new attribute
+                xml_attribute<Ch> *attribute = this->allocate_attribute();
+                attribute->name(name, text - name);
+                node->append_attribute(attribute);
+
+                // Skip whitespace after attribute name
+                skip<whitespace_pred, Flags>(text);
+
+                // Skip =
+                if (*text != Ch('='))
+                    RAPIDXML_PARSE_ERROR("expected =", text);
+                ++text;
+
+                // Add terminating zero after name
+                if (!(Flags & parse_no_string_terminators))
+                    attribute->name()[attribute->name_size()] = 0;
+
+                // Skip whitespace after =
+                skip<whitespace_pred, Flags>(text);
+
+                // Skip quote and remember if it was ' or "
+                Ch quote = *text;
+                if (quote != Ch('\'') && quote != Ch('"'))
+                    RAPIDXML_PARSE_ERROR("expected ' or \"", text);
+                ++text;
+
+                // Extract attribute value and expand char refs in it
+                Ch *value = text, *end;
+                const int AttFlags = Flags & ~parse_normalize_whitespace;   // No whitespace normalization in attributes
+                if (quote == Ch('\''))
+                    end = skip_and_expand_character_refs<attribute_value_pred<Ch('\'')>, attribute_value_pure_pred<Ch('\'')>, AttFlags>(text);
+                else
+                    end = skip_and_expand_character_refs<attribute_value_pred<Ch('"')>, attribute_value_pure_pred<Ch('"')>, AttFlags>(text);
+                
+                // Set attribute value
+                attribute->value(value, end - value);
+                
+                // Make sure that end quote is present
+                if (*text != quote)
+                    RAPIDXML_PARSE_ERROR("expected ' or \"", text);
+                ++text;     // Skip quote
+
+                // Add terminating zero after value
+                if (!(Flags & parse_no_string_terminators))
+                    attribute->value()[attribute->value_size()] = 0;
+
+                // Skip whitespace after attribute value
+                skip<whitespace_pred, Flags>(text);
+            }
+        }
+
+    };
+
+    //! \cond internal
+    namespace internal
+    {
+
+        // Whitespace (space \n \r \t)
+        template<int Dummy>
+        const unsigned char lookup_tables<Dummy>::lookup_whitespace[256] = 
+        {
+          // 0   1   2   3   4   5   6   7   8   9   A   B   C   D   E   F
+             0,  0,  0,  0,  0,  0,  0,  0,  0,  1,  1,  0,  0,  1,  0,  0,  // 0
+             0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  // 1
+             1,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  // 2
+             0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  // 3
+             0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  // 4
+             0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  // 5
+             0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  // 6
+             0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  // 7
+             0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  // 8
+             0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  // 9
+             0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  // A
+             0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  // B
+             0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  // C
+             0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  // D
+             0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  // E
+             0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0   // F
+        };
+
+        // Node name (anything but space \n \r \t / > ? \0)
+        template<int Dummy>
+        const unsigned char lookup_tables<Dummy>::lookup_node_name[256] = 
+        {
+          // 0   1   2   3   4   5   6   7   8   9   A   B   C   D   E   F
+             0,  1,  1,  1,  1,  1,  1,  1,  1,  0,  0,  1,  1,  0,  1,  1,  // 0
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 1
+             0,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  0,  // 2
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  0,  0,  // 3
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 4
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 5
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 6
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 7
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 8
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 9
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // A
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // B
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // C
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // D
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // E
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1   // F
+        };
+
+        // Text (i.e. PCDATA) (anything but < \0)
+        template<int Dummy>
+        const unsigned char lookup_tables<Dummy>::lookup_text[256] = 
+        {
+          // 0   1   2   3   4   5   6   7   8   9   A   B   C   D   E   F
+             0,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 0
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 1
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 2
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  0,  1,  1,  1,  // 3
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 4
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 5
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 6
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 7
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 8
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 9
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // A
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // B
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // C
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // D
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // E
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1   // F
+        };
+
+        // Text (i.e. PCDATA) that does not require processing when ws normalization is disabled 
+        // (anything but < \0 &)
+        template<int Dummy>
+        const unsigned char lookup_tables<Dummy>::lookup_text_pure_no_ws[256] = 
+        {
+          // 0   1   2   3   4   5   6   7   8   9   A   B   C   D   E   F
+             0,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 0
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 1
+             1,  1,  1,  1,  1,  1,  0,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 2
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  0,  1,  1,  1,  // 3
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 4
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 5
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 6
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 7
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 8
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 9
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // A
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // B
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // C
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // D
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // E
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1   // F
+        };
+
+        // Text (i.e. PCDATA) that does not require processing when ws normalizationis is enabled
+        // (anything but < \0 & space \n \r \t)
+        template<int Dummy>
+        const unsigned char lookup_tables<Dummy>::lookup_text_pure_with_ws[256] = 
+        {
+          // 0   1   2   3   4   5   6   7   8   9   A   B   C   D   E   F
+             0,  1,  1,  1,  1,  1,  1,  1,  1,  0,  0,  1,  1,  0,  1,  1,  // 0
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 1
+             0,  1,  1,  1,  1,  1,  0,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 2
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  0,  1,  1,  1,  // 3
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 4
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 5
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 6
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 7
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 8
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 9
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // A
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // B
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // C
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // D
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // E
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1   // F
+        };
+
+        // Attribute name (anything but space \n \r \t / < > = ? ! \0)
+        template<int Dummy>
+        const unsigned char lookup_tables<Dummy>::lookup_attribute_name[256] = 
+        {
+          // 0   1   2   3   4   5   6   7   8   9   A   B   C   D   E   F
+             0,  1,  1,  1,  1,  1,  1,  1,  1,  0,  0,  1,  1,  0,  1,  1,  // 0
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 1
+             0,  0,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  0,  // 2
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  0,  0,  0,  0,  // 3
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 4
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 5
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 6
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 7
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 8
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 9
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // A
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // B
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // C
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // D
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // E
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1   // F
+        };
+
+        // Attribute data with single quote (anything but ' \0)
+        template<int Dummy>
+        const unsigned char lookup_tables<Dummy>::lookup_attribute_data_1[256] = 
+        {
+          // 0   1   2   3   4   5   6   7   8   9   A   B   C   D   E   F
+             0,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 0
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 1
+             1,  1,  1,  1,  1,  1,  1,  0,  1,  1,  1,  1,  1,  1,  1,  1,  // 2
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 3
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 4
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 5
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 6
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 7
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 8
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 9
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // A
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // B
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // C
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // D
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // E
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1   // F
+        };
+
+        // Attribute data with single quote that does not require processing (anything but ' \0 &)
+        template<int Dummy>
+        const unsigned char lookup_tables<Dummy>::lookup_attribute_data_1_pure[256] = 
+        {
+          // 0   1   2   3   4   5   6   7   8   9   A   B   C   D   E   F
+             0,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 0
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 1
+             1,  1,  1,  1,  1,  1,  0,  0,  1,  1,  1,  1,  1,  1,  1,  1,  // 2
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 3
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 4
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 5
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 6
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 7
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 8
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 9
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // A
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // B
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // C
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // D
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // E
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1   // F
+        };
+
+        // Attribute data with double quote (anything but " \0)
+        template<int Dummy>
+        const unsigned char lookup_tables<Dummy>::lookup_attribute_data_2[256] = 
+        {
+          // 0   1   2   3   4   5   6   7   8   9   A   B   C   D   E   F
+             0,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 0
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 1
+             1,  1,  0,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 2
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 3
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 4
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 5
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 6
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 7
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 8
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 9
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // A
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // B
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // C
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // D
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // E
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1   // F
+        };
+
+        // Attribute data with double quote that does not require processing (anything but " \0 &)
+        template<int Dummy>
+        const unsigned char lookup_tables<Dummy>::lookup_attribute_data_2_pure[256] = 
+        {
+          // 0   1   2   3   4   5   6   7   8   9   A   B   C   D   E   F
+             0,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 0
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 1
+             1,  1,  0,  1,  1,  1,  0,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 2
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 3
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 4
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 5
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 6
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 7
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 8
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 9
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // A
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // B
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // C
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // D
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // E
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1   // F
+        };
+
+        // Digits (dec and hex, 255 denotes end of numeric character reference)
+        template<int Dummy>
+        const unsigned char lookup_tables<Dummy>::lookup_digits[256] = 
+        {
+          // 0   1   2   3   4   5   6   7   8   9   A   B   C   D   E   F
+           255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,  // 0
+           255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,  // 1
+           255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,  // 2
+             0,  1,  2,  3,  4,  5,  6,  7,  8,  9,255,255,255,255,255,255,  // 3
+           255, 10, 11, 12, 13, 14, 15,255,255,255,255,255,255,255,255,255,  // 4
+           255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,  // 5
+           255, 10, 11, 12, 13, 14, 15,255,255,255,255,255,255,255,255,255,  // 6
+           255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,  // 7
+           255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,  // 8
+           255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,  // 9
+           255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,  // A
+           255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,  // B
+           255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,  // C
+           255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,  // D
+           255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,  // E
+           255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255   // F
+        };
+    
+        // Upper case conversion
+        template<int Dummy>
+        const unsigned char lookup_tables<Dummy>::lookup_upcase[256] = 
+        {
+          // 0,  1,  2,  3,  4,  5,  6,  7,  8,  9,  A   B   C   D   E   F
+           0,  1,  2,  3,  4,  5,  6,  7,  8,  9,  10, 11, 12, 13, 14, 15,   // 0
+           16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,   // 1
+           32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47,   // 2
+           48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63,   // 3
+           64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79,   // 4
+           80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95,   // 5
+           96, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79,   // 6
+           80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 123,124,125,126,127,  // 7
+           128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,  // 8
+           144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,  // 9
+           160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,  // A
+           176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,  // B
+           192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,  // C
+           208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,  // D
+           224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,  // E
+           240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255   // F
+        };
+    }
+    //! \endcond
+
+}
+
+// Undefine internal macros
+#undef RAPIDXML_PARSE_ERROR
+
+// On MSVC, restore warnings state
+#ifdef _MSC_VER
+    #pragma warning(pop)
+#endif
+
+#endif
diff --git a/src/osgEarthDrivers/kml/rapidxml_ext.hpp b/src/osgEarthDrivers/kml/rapidxml_ext.hpp
new file mode 100644
index 0000000..2e6250b
--- /dev/null
+++ b/src/osgEarthDrivers/kml/rapidxml_ext.hpp
@@ -0,0 +1,61 @@
+#ifndef RAPIDXML_EXT_HPP_INCLUDED 
+#define RAPIDXML_EXT_HPP_INCLUDED 1
+
+#include "rapidxml.hpp"
+#include <string>
+
+#include <osgEarth/StringUtils>
+
+using namespace rapidxml;
+
+/**
+ * Gets the value of an attribute on an xml_node.  Key is case insensitive.
+ */
+inline std::string getAttr(xml_node<>* node, const std::string& key)
+{
+	for (xml_attribute<> *attr = node->first_attribute(); attr; attr = attr->next_attribute())
+	{
+		if (osgEarth::ciEquals(attr->name(), key))
+		{
+			return attr->value();
+		}
+	}
+	return "";
+}
+
+/**
+ * Gets the value of a child element.  Key is case insensitive.
+ */
+inline std::string getChildValue(xml_node<>* node, const std::string& key)
+{
+	if (node)
+	{
+		xml_node<>* child = node->first_node(key.c_str(), 0, false);
+		if (child)
+		{
+			return child->value();
+		}
+	}
+	return "";
+}
+
+/*
+ * Gets a value from an xml_node by first looking at the attribtes and then looking at the child elements
+ */
+inline std::string getValue(xml_node<>* node, const std::string &key)
+{
+    std::string value = "";
+	if (node)
+	{
+		// First look through the attributes
+		value = getAttr(node, key);
+		if (value.empty())
+		{
+			value = getChildValue(node, key);
+        }
+	}
+	return value;
+}
+
+
+#endif
diff --git a/src/osgEarthDrivers/kml/rapidxml_iterators.hpp b/src/osgEarthDrivers/kml/rapidxml_iterators.hpp
new file mode 100644
index 0000000..52ebc29
--- /dev/null
+++ b/src/osgEarthDrivers/kml/rapidxml_iterators.hpp
@@ -0,0 +1,174 @@
+#ifndef RAPIDXML_ITERATORS_HPP_INCLUDED
+#define RAPIDXML_ITERATORS_HPP_INCLUDED
+
+// Copyright (C) 2006, 2009 Marcin Kalicinski
+// Version 1.13
+// Revision $DateTime: 2009/05/13 01:46:17 $
+//! \file rapidxml_iterators.hpp This file contains rapidxml iterators
+
+#include "rapidxml.hpp"
+
+namespace rapidxml
+{
+
+    //! Iterator of child nodes of xml_node
+    template<class Ch>
+    class node_iterator
+    {
+    
+    public:
+
+        typedef typename xml_node<Ch> value_type;
+        typedef typename xml_node<Ch> &reference;
+        typedef typename xml_node<Ch> *pointer;
+        typedef std::ptrdiff_t difference_type;
+        typedef std::bidirectional_iterator_tag iterator_category;
+        
+        node_iterator()
+            : m_node(0)
+        {
+        }
+
+        node_iterator(xml_node<Ch> *node)
+            : m_node(node->first_node())
+        {
+        }
+        
+        reference operator *() const
+        {
+            assert(m_node);
+            return *m_node;
+        }
+
+        pointer operator->() const
+        {
+            assert(m_node);
+            return m_node;
+        }
+
+        node_iterator& operator++()
+        {
+            assert(m_node);
+            m_node = m_node->next_sibling();
+            return *this;
+        }
+
+        node_iterator operator++(int)
+        {
+            node_iterator tmp = *this;
+            ++this;
+            return tmp;
+        }
+
+        node_iterator& operator--()
+        {
+            assert(m_node && m_node->previous_sibling());
+            m_node = m_node->previous_sibling();
+            return *this;
+        }
+
+        node_iterator operator--(int)
+        {
+            node_iterator tmp = *this;
+            ++this;
+            return tmp;
+        }
+
+        bool operator ==(const node_iterator<Ch> &rhs)
+        {
+            return m_node == rhs.m_node;
+        }
+
+        bool operator !=(const node_iterator<Ch> &rhs)
+        {
+            return m_node != rhs.m_node;
+        }
+
+    private:
+
+        xml_node<Ch> *m_node;
+
+    };
+
+    //! Iterator of child attributes of xml_node
+    template<class Ch>
+    class attribute_iterator
+    {
+    
+    public:
+
+        typedef typename xml_attribute<Ch> value_type;
+        typedef typename xml_attribute<Ch> &reference;
+        typedef typename xml_attribute<Ch> *pointer;
+        typedef std::ptrdiff_t difference_type;
+        typedef std::bidirectional_iterator_tag iterator_category;
+        
+        attribute_iterator()
+            : m_attribute(0)
+        {
+        }
+
+        attribute_iterator(xml_node<Ch> *node)
+            : m_attribute(node->first_attribute())
+        {
+        }
+        
+        reference operator *() const
+        {
+            assert(m_attribute);
+            return *m_attribute;
+        }
+
+        pointer operator->() const
+        {
+            assert(m_attribute);
+            return m_attribute;
+        }
+
+        attribute_iterator& operator++()
+        {
+            assert(m_attribute);
+            m_attribute = m_attribute->next_attribute();
+            return *this;
+        }
+
+        attribute_iterator operator++(int)
+        {
+            attribute_iterator tmp = *this;
+            ++this;
+            return tmp;
+        }
+
+        attribute_iterator& operator--()
+        {
+            assert(m_attribute && m_attribute->previous_attribute());
+            m_attribute = m_attribute->previous_attribute();
+            return *this;
+        }
+
+        attribute_iterator operator--(int)
+        {
+            attribute_iterator tmp = *this;
+            ++this;
+            return tmp;
+        }
+
+        bool operator ==(const attribute_iterator<Ch> &rhs)
+        {
+            return m_attribute == rhs.m_attribute;
+        }
+
+        bool operator !=(const attribute_iterator<Ch> &rhs)
+        {
+            return m_attribute != rhs.m_attribute;
+        }
+
+    private:
+
+        xml_attribute<Ch> *m_attribute;
+
+    };
+
+}
+
+#endif
diff --git a/src/osgEarthDrivers/kml/rapidxml_print.hpp b/src/osgEarthDrivers/kml/rapidxml_print.hpp
new file mode 100644
index 0000000..0ae2b14
--- /dev/null
+++ b/src/osgEarthDrivers/kml/rapidxml_print.hpp
@@ -0,0 +1,421 @@
+#ifndef RAPIDXML_PRINT_HPP_INCLUDED
+#define RAPIDXML_PRINT_HPP_INCLUDED
+
+// Copyright (C) 2006, 2009 Marcin Kalicinski
+// Version 1.13
+// Revision $DateTime: 2009/05/13 01:46:17 $
+//! \file rapidxml_print.hpp This file contains rapidxml printer implementation
+
+#include "rapidxml.hpp"
+
+// Only include streams if not disabled
+#ifndef RAPIDXML_NO_STREAMS
+    #include <ostream>
+    #include <iterator>
+#endif
+
+namespace rapidxml
+{
+
+    ///////////////////////////////////////////////////////////////////////
+    // Printing flags
+
+    const int print_no_indenting = 0x1;   //!< Printer flag instructing the printer to suppress indenting of XML. See print() function.
+
+    ///////////////////////////////////////////////////////////////////////
+    // Internal
+
+    //! \cond internal
+    namespace internal
+    {
+        
+        ///////////////////////////////////////////////////////////////////////////
+        // Internal character operations
+    
+        // Copy characters from given range to given output iterator
+        template<class OutIt, class Ch>
+        inline OutIt copy_chars(const Ch *begin, const Ch *end, OutIt out)
+        {
+            while (begin != end)
+                *out++ = *begin++;
+            return out;
+        }
+        
+        // Copy characters from given range to given output iterator and expand
+        // characters into references (< > ' " &)
+        template<class OutIt, class Ch>
+        inline OutIt copy_and_expand_chars(const Ch *begin, const Ch *end, Ch noexpand, OutIt out)
+        {
+            while (begin != end)
+            {
+                if (*begin == noexpand)
+                {
+                    *out++ = *begin;    // No expansion, copy character
+                }
+                else
+                {
+                    switch (*begin)
+                    {
+                    case Ch('<'):
+                        *out++ = Ch('&'); *out++ = Ch('l'); *out++ = Ch('t'); *out++ = Ch(';');
+                        break;
+                    case Ch('>'): 
+                        *out++ = Ch('&'); *out++ = Ch('g'); *out++ = Ch('t'); *out++ = Ch(';');
+                        break;
+                    case Ch('\''): 
+                        *out++ = Ch('&'); *out++ = Ch('a'); *out++ = Ch('p'); *out++ = Ch('o'); *out++ = Ch('s'); *out++ = Ch(';');
+                        break;
+                    case Ch('"'): 
+                        *out++ = Ch('&'); *out++ = Ch('q'); *out++ = Ch('u'); *out++ = Ch('o'); *out++ = Ch('t'); *out++ = Ch(';');
+                        break;
+                    case Ch('&'): 
+                        *out++ = Ch('&'); *out++ = Ch('a'); *out++ = Ch('m'); *out++ = Ch('p'); *out++ = Ch(';'); 
+                        break;
+                    default:
+                        *out++ = *begin;    // No expansion, copy character
+                    }
+                }
+                ++begin;    // Step to next character
+            }
+            return out;
+        }
+
+        // Fill given output iterator with repetitions of the same character
+        template<class OutIt, class Ch>
+        inline OutIt fill_chars(OutIt out, int n, Ch ch)
+        {
+            for (int i = 0; i < n; ++i)
+                *out++ = ch;
+            return out;
+        }
+
+        // Find character
+        template<class Ch, Ch ch>
+        inline bool find_char(const Ch *begin, const Ch *end)
+        {
+            while (begin != end)
+                if (*begin++ == ch)
+                    return true;
+            return false;
+        }
+
+        ///////////////////////////////////////////////////////////////////////////
+        // Internal printing operations
+    
+        // Print node
+        template<class OutIt, class Ch>
+        inline OutIt print_node(OutIt out, const xml_node<Ch> *node, int flags, int indent)
+        {
+            // Print proper node type
+            switch (node->type())
+            {
+
+            // Document
+            case node_document:
+                out = print_children(out, node, flags, indent);
+                break;
+
+            // Element
+            case node_element:
+                out = print_element_node(out, node, flags, indent);
+                break;
+            
+            // Data
+            case node_data:
+                out = print_data_node(out, node, flags, indent);
+                break;
+            
+            // CDATA
+            case node_cdata:
+                out = print_cdata_node(out, node, flags, indent);
+                break;
+
+            // Declaration
+            case node_declaration:
+                out = print_declaration_node(out, node, flags, indent);
+                break;
+
+            // Comment
+            case node_comment:
+                out = print_comment_node(out, node, flags, indent);
+                break;
+            
+            // Doctype
+            case node_doctype:
+                out = print_doctype_node(out, node, flags, indent);
+                break;
+
+            // Pi
+            case node_pi:
+                out = print_pi_node(out, node, flags, indent);
+                break;
+
+                // Unknown
+            default:
+                assert(0);
+                break;
+            }
+            
+            // If indenting not disabled, add line break after node
+            if (!(flags & print_no_indenting))
+                *out = Ch('\n'), ++out;
+
+            // Return modified iterator
+            return out;
+        }
+        
+        // Print children of the node                               
+        template<class OutIt, class Ch>
+        inline OutIt print_children(OutIt out, const xml_node<Ch> *node, int flags, int indent)
+        {
+            for (xml_node<Ch> *child = node->first_node(); child; child = child->next_sibling())
+                out = print_node(out, child, flags, indent);
+            return out;
+        }
+
+        // Print attributes of the node
+        template<class OutIt, class Ch>
+        inline OutIt print_attributes(OutIt out, const xml_node<Ch> *node, int flags)
+        {
+            for (xml_attribute<Ch> *attribute = node->first_attribute(); attribute; attribute = attribute->next_attribute())
+            {
+                if (attribute->name() && attribute->value())
+                {
+                    // Print attribute name
+                    *out = Ch(' '), ++out;
+                    out = copy_chars(attribute->name(), attribute->name() + attribute->name_size(), out);
+                    *out = Ch('='), ++out;
+                    // Print attribute value using appropriate quote type
+                    if (find_char<Ch, Ch('"')>(attribute->value(), attribute->value() + attribute->value_size()))
+                    {
+                        *out = Ch('\''), ++out;
+                        out = copy_and_expand_chars(attribute->value(), attribute->value() + attribute->value_size(), Ch('"'), out);
+                        *out = Ch('\''), ++out;
+                    }
+                    else
+                    {
+                        *out = Ch('"'), ++out;
+                        out = copy_and_expand_chars(attribute->value(), attribute->value() + attribute->value_size(), Ch('\''), out);
+                        *out = Ch('"'), ++out;
+                    }
+                }
+            }
+            return out;
+        }
+
+        // Print data node
+        template<class OutIt, class Ch>
+        inline OutIt print_data_node(OutIt out, const xml_node<Ch> *node, int flags, int indent)
+        {
+            assert(node->type() == node_data);
+            if (!(flags & print_no_indenting))
+                out = fill_chars(out, indent, Ch('\t'));
+            out = copy_and_expand_chars(node->value(), node->value() + node->value_size(), Ch(0), out);
+            return out;
+        }
+
+        // Print data node
+        template<class OutIt, class Ch>
+        inline OutIt print_cdata_node(OutIt out, const xml_node<Ch> *node, int flags, int indent)
+        {
+            assert(node->type() == node_cdata);
+            if (!(flags & print_no_indenting))
+                out = fill_chars(out, indent, Ch('\t'));
+            *out = Ch('<'); ++out;
+            *out = Ch('!'); ++out;
+            *out = Ch('['); ++out;
+            *out = Ch('C'); ++out;
+            *out = Ch('D'); ++out;
+            *out = Ch('A'); ++out;
+            *out = Ch('T'); ++out;
+            *out = Ch('A'); ++out;
+            *out = Ch('['); ++out;
+            out = copy_chars(node->value(), node->value() + node->value_size(), out);
+            *out = Ch(']'); ++out;
+            *out = Ch(']'); ++out;
+            *out = Ch('>'); ++out;
+            return out;
+        }
+
+        // Print element node
+        template<class OutIt, class Ch>
+        inline OutIt print_element_node(OutIt out, const xml_node<Ch> *node, int flags, int indent)
+        {
+            assert(node->type() == node_element);
+
+            // Print element name and attributes, if any
+            if (!(flags & print_no_indenting))
+                out = fill_chars(out, indent, Ch('\t'));
+            *out = Ch('<'), ++out;
+            out = copy_chars(node->name(), node->name() + node->name_size(), out);
+            out = print_attributes(out, node, flags);
+            
+            // If node is childless
+            if (node->value_size() == 0 && !node->first_node())
+            {
+                // Print childless node tag ending
+                *out = Ch('/'), ++out;
+                *out = Ch('>'), ++out;
+            }
+            else
+            {
+                // Print normal node tag ending
+                *out = Ch('>'), ++out;
+
+                // Test if node contains a single data node only (and no other nodes)
+                xml_node<Ch> *child = node->first_node();
+                if (!child)
+                {
+                    // If node has no children, only print its value without indenting
+                    out = copy_and_expand_chars(node->value(), node->value() + node->value_size(), Ch(0), out);
+                }
+                else if (child->next_sibling() == 0 && child->type() == node_data)
+                {
+                    // If node has a sole data child, only print its value without indenting
+                    out = copy_and_expand_chars(child->value(), child->value() + child->value_size(), Ch(0), out);
+                }
+                else
+                {
+                    // Print all children with full indenting
+                    if (!(flags & print_no_indenting))
+                        *out = Ch('\n'), ++out;
+                    out = print_children(out, node, flags, indent + 1);
+                    if (!(flags & print_no_indenting))
+                        out = fill_chars(out, indent, Ch('\t'));
+                }
+
+                // Print node end
+                *out = Ch('<'), ++out;
+                *out = Ch('/'), ++out;
+                out = copy_chars(node->name(), node->name() + node->name_size(), out);
+                *out = Ch('>'), ++out;
+            }
+            return out;
+        }
+
+        // Print declaration node
+        template<class OutIt, class Ch>
+        inline OutIt print_declaration_node(OutIt out, const xml_node<Ch> *node, int flags, int indent)
+        {
+            // Print declaration start
+            if (!(flags & print_no_indenting))
+                out = fill_chars(out, indent, Ch('\t'));
+            *out = Ch('<'), ++out;
+            *out = Ch('?'), ++out;
+            *out = Ch('x'), ++out;
+            *out = Ch('m'), ++out;
+            *out = Ch('l'), ++out;
+
+            // Print attributes
+            out = print_attributes(out, node, flags);
+            
+            // Print declaration end
+            *out = Ch('?'), ++out;
+            *out = Ch('>'), ++out;
+            
+            return out;
+        }
+
+        // Print comment node
+        template<class OutIt, class Ch>
+        inline OutIt print_comment_node(OutIt out, const xml_node<Ch> *node, int flags, int indent)
+        {
+            assert(node->type() == node_comment);
+            if (!(flags & print_no_indenting))
+                out = fill_chars(out, indent, Ch('\t'));
+            *out = Ch('<'), ++out;
+            *out = Ch('!'), ++out;
+            *out = Ch('-'), ++out;
+            *out = Ch('-'), ++out;
+            out = copy_chars(node->value(), node->value() + node->value_size(), out);
+            *out = Ch('-'), ++out;
+            *out = Ch('-'), ++out;
+            *out = Ch('>'), ++out;
+            return out;
+        }
+
+        // Print doctype node
+        template<class OutIt, class Ch>
+        inline OutIt print_doctype_node(OutIt out, const xml_node<Ch> *node, int flags, int indent)
+        {
+            assert(node->type() == node_doctype);
+            if (!(flags & print_no_indenting))
+                out = fill_chars(out, indent, Ch('\t'));
+            *out = Ch('<'), ++out;
+            *out = Ch('!'), ++out;
+            *out = Ch('D'), ++out;
+            *out = Ch('O'), ++out;
+            *out = Ch('C'), ++out;
+            *out = Ch('T'), ++out;
+            *out = Ch('Y'), ++out;
+            *out = Ch('P'), ++out;
+            *out = Ch('E'), ++out;
+            *out = Ch(' '), ++out;
+            out = copy_chars(node->value(), node->value() + node->value_size(), out);
+            *out = Ch('>'), ++out;
+            return out;
+        }
+
+        // Print pi node
+        template<class OutIt, class Ch>
+        inline OutIt print_pi_node(OutIt out, const xml_node<Ch> *node, int flags, int indent)
+        {
+            assert(node->type() == node_pi);
+            if (!(flags & print_no_indenting))
+                out = fill_chars(out, indent, Ch('\t'));
+            *out = Ch('<'), ++out;
+            *out = Ch('?'), ++out;
+            out = copy_chars(node->name(), node->name() + node->name_size(), out);
+            *out = Ch(' '), ++out;
+            out = copy_chars(node->value(), node->value() + node->value_size(), out);
+            *out = Ch('?'), ++out;
+            *out = Ch('>'), ++out;
+            return out;
+        }
+
+    }
+    //! \endcond
+
+    ///////////////////////////////////////////////////////////////////////////
+    // Printing
+
+    //! Prints XML to given output iterator.
+    //! \param out Output iterator to print to.
+    //! \param node Node to be printed. Pass xml_document to print entire document.
+    //! \param flags Flags controlling how XML is printed.
+    //! \return Output iterator pointing to position immediately after last character of printed text.
+    template<class OutIt, class Ch> 
+    inline OutIt print(OutIt out, const xml_node<Ch> &node, int flags = 0)
+    {
+        return internal::print_node(out, &node, flags, 0);
+    }
+
+#ifndef RAPIDXML_NO_STREAMS
+
+    //! Prints XML to given output stream.
+    //! \param out Output stream to print to.
+    //! \param node Node to be printed. Pass xml_document to print entire document.
+    //! \param flags Flags controlling how XML is printed.
+    //! \return Output stream.
+    template<class Ch> 
+    inline std::basic_ostream<Ch> &print(std::basic_ostream<Ch> &out, const xml_node<Ch> &node, int flags = 0)
+    {
+        print(std::ostream_iterator<Ch>(out), node, flags);
+        return out;
+    }
+
+    //! Prints formatted XML to given output stream. Uses default printing flags. Use print() function to customize printing process.
+    //! \param out Output stream to print to.
+    //! \param node Node to be printed.
+    //! \return Output stream.
+    template<class Ch> 
+    inline std::basic_ostream<Ch> &operator <<(std::basic_ostream<Ch> &out, const xml_node<Ch> &node)
+    {
+        return print(out, node);
+    }
+
+#endif
+
+}
+
+#endif
diff --git a/src/osgEarthDrivers/kml/rapidxml_utils.hpp b/src/osgEarthDrivers/kml/rapidxml_utils.hpp
new file mode 100644
index 0000000..37c2953
--- /dev/null
+++ b/src/osgEarthDrivers/kml/rapidxml_utils.hpp
@@ -0,0 +1,122 @@
+#ifndef RAPIDXML_UTILS_HPP_INCLUDED
+#define RAPIDXML_UTILS_HPP_INCLUDED
+
+// Copyright (C) 2006, 2009 Marcin Kalicinski
+// Version 1.13
+// Revision $DateTime: 2009/05/13 01:46:17 $
+//! \file rapidxml_utils.hpp This file contains high-level rapidxml utilities that can be useful
+//! in certain simple scenarios. They should probably not be used if maximizing performance is the main objective.
+
+#include "rapidxml.hpp"
+#include <vector>
+#include <string>
+#include <fstream>
+#include <stdexcept>
+
+namespace rapidxml
+{
+
+    //! Represents data loaded from a file
+    template<class Ch = char>
+    class file
+    {
+        
+    public:
+        
+        //! Loads file into the memory. Data will be automatically destroyed by the destructor.
+        //! \param filename Filename to load.
+        file(const char *filename)
+        {
+            using namespace std;
+
+            // Open stream
+            basic_ifstream<Ch> stream(filename, ios::binary);
+            if (!stream)
+                throw runtime_error(string("cannot open file ") + filename);
+            stream.unsetf(ios::skipws);
+            
+            // Determine stream size
+            stream.seekg(0, ios::end);
+            size_t size = stream.tellg();
+            stream.seekg(0);   
+            
+            // Load data and add terminating 0
+            m_data.resize(size + 1);
+            stream.read(&m_data.front(), static_cast<streamsize>(size));
+            m_data[size] = 0;
+        }
+
+        //! Loads file into the memory. Data will be automatically destroyed by the destructor
+        //! \param stream Stream to load from
+        file(std::basic_istream<Ch> &stream)
+        {
+            using namespace std;
+
+            // Load data and add terminating 0
+            stream.unsetf(ios::skipws);
+            m_data.assign(istreambuf_iterator<Ch>(stream), istreambuf_iterator<Ch>());
+            if (stream.fail() || stream.bad())
+                throw runtime_error("error reading stream");
+            m_data.push_back(0);
+        }
+        
+        //! Gets file data.
+        //! \return Pointer to data of file.
+        Ch *data()
+        {
+            return &m_data.front();
+        }
+
+        //! Gets file data.
+        //! \return Pointer to data of file.
+        const Ch *data() const
+        {
+            return &m_data.front();
+        }
+
+        //! Gets file data size.
+        //! \return Size of file data, in characters.
+        std::size_t size() const
+        {
+            return m_data.size();
+        }
+
+    private:
+
+        std::vector<Ch> m_data;   // File data
+
+    };
+
+    //! Counts children of node. Time complexity is O(n).
+    //! \return Number of children of node
+    template<class Ch>
+    inline std::size_t count_children(xml_node<Ch> *node)
+    {
+        xml_node<Ch> *child = node->first_node();
+        std::size_t count = 0;
+        while (child)
+        {
+            ++count;
+            child = child->next_sibling();
+        }
+        return count;
+    }
+
+    //! Counts attributes of node. Time complexity is O(n).
+    //! \return Number of attributes of node
+    template<class Ch>
+    inline std::size_t count_attributes(xml_node<Ch> *node)
+    {
+        xml_attribute<Ch> *attr = node->first_attribute();
+        std::size_t count = 0;
+        while (attr)
+        {
+            ++count;
+            attr = attr->next_attribute();
+        }
+        return count;
+    }
+
+}
+
+#endif
diff --git a/src/osgEarthDrivers/label_annotation/AnnotationLabelSource.cpp b/src/osgEarthDrivers/label_annotation/AnnotationLabelSource.cpp
index 4f8ecc8..116506d 100644
--- a/src/osgEarthDrivers/label_annotation/AnnotationLabelSource.cpp
+++ b/src/osgEarthDrivers/label_annotation/AnnotationLabelSource.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -46,7 +46,7 @@ public:
     osg::Node* createNode(
         const FeatureList&   input,
         const Style&         style,
-        const FilterContext& context )
+        FilterContext&       context )
     {
         if ( style.get<TextSymbol>() == 0L && style.get<IconSymbol>() == 0L )
             return 0L;
@@ -67,7 +67,7 @@ public:
 
         for( FeatureList::const_iterator i = input.begin(); i != input.end(); ++i )
         {
-            const Feature* feature = i->get();
+            Feature* feature = i->get();
             if ( !feature )
                 continue;
             
@@ -126,38 +126,40 @@ public:
             {
                 if ( context.featureIndex() )
                 {
-                    context.featureIndex()->tagNode(node, const_cast<Feature*>(feature));
+                    context.featureIndex()->tagNode(node, feature);
                 }
 
                 group->addChild( node );
             }
         }
 
-        VirtualProgram* vp = VirtualProgram::getOrCreate(group->getOrCreateStateSet());
-        vp->setInheritShaders( false );
+        // Note to self: need to change this to support picking later. -gw
+        //VirtualProgram* vp = VirtualProgram::getOrCreate(group->getOrCreateStateSet());
+        //vp->setInheritShaders( false );
 
         return group;
     }
 
 
-    osg::Node* makePlaceNode(const FilterContext& context,
-                             const Feature*       feature, 
-                             const Style&         style, 
-                             NumericExpression&   priorityExpr )
+    osg::Node* makePlaceNode(FilterContext&     context,
+                             Feature*           feature, 
+                             const Style&       style, 
+                             NumericExpression& priorityExpr )
     {
         osg::Vec3d center = feature->getGeometry()->getBounds().center();
         GeoPoint point(feature->getSRS(), center.x(), center.y());
 
-        PlaceNode* placeNode = new PlaceNode(0L, point, style, context.getDBOptions());
+        //LabelNode* node = new LabelNode(0L, point, style);
+        PlaceNode* node = new PlaceNode(0L, point, style, context.getDBOptions());
 
         if ( !priorityExpr.empty() )
         {
             AnnotationData* data = new AnnotationData();
             data->setPriority( feature->eval(priorityExpr, &context) );
-            placeNode->setAnnotationData( data );
+            node->setAnnotationData( data );
         }
 
-        return placeNode;
+        return node;
     }
 
 };
diff --git a/src/osgEarthDrivers/label_overlay/OverlayLabelSource.cpp b/src/osgEarthDrivers/label_overlay/OverlayLabelSource.cpp
deleted file mode 100644
index b279b64..0000000
--- a/src/osgEarthDrivers/label_overlay/OverlayLabelSource.cpp
+++ /dev/null
@@ -1,216 +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 <osgEarthFeatures/LabelSource>
-#include <osgEarthSymbology/Expression>
-#include <osgEarthUtil/Controls>
-#include <osgEarth/CullingUtils>
-#include <osgEarth/ECEF>
-#include <osg/ClusterCullingCallback>
-#include <osg/MatrixTransform>
-#include <osgDB/FileNameUtils>
-#include <set>
-
-using namespace osgEarth;
-using namespace osgEarth::Features;
-using namespace osgEarth::Symbology;
-using namespace osgEarth::Util;
-
-
-/******* Deprecated ---- please use AnnotationLabelSource instead *************/
-
-
-class OverlayLabelSource : public LabelSource
-{
-public:
-    OverlayLabelSource( const LabelSourceOptions& options )
-        : LabelSource( options )
-    {
-        //nop
-    }
-
-    /**
-     * Creates a simple label. The caller is responsible for placing it in the scene.
-     */
-    osg::Node* createNode(
-        const std::string& text,
-        const Style&       style )
-    {
-        const TextSymbol* symbol = style.get<TextSymbol>();
-
-        Controls::LabelControl* label = new Controls::LabelControl( text );
-        if ( symbol )
-        {
-            if ( symbol->fill().isSet() )
-                label->setForeColor( symbol->fill()->color() );
-            if ( symbol->halo().isSet() )
-                label->setHaloColor( symbol->halo()->color() );
-            //if ( symbol->size().isSet() )
-            //    label->setFontSize( *symbol->size() );
-            if ( symbol->font().isSet() )
-            {
-                osgText::Font* font = osgText::readFontFile(*symbol->font() );
-                // mitigates mipmapping issues that cause rendering artifacts for some fonts/placement
-                if ( font )
-                    font->setGlyphImageMargin( 2 );
-                label->setFont( font );
-            }
-            if ( symbol->encoding().isSet() )
-            {
-                osgText::String::Encoding enc;
-                switch(symbol->encoding().value())
-                {
-                case TextSymbol::ENCODING_ASCII: enc = osgText::String::ENCODING_ASCII; break;
-                case TextSymbol::ENCODING_UTF8: enc = osgText::String::ENCODING_UTF8; break;
-                case TextSymbol::ENCODING_UTF16: enc = osgText::String::ENCODING_UTF16; break;
-                case TextSymbol::ENCODING_UTF32: enc = osgText::String::ENCODING_UTF32; break;
-                default: enc = osgText::String::ENCODING_UNDEFINED; break;
-                }
-                label->setEncoding( enc );
-            }
-        }
-        Controls::ControlNode* node = new Controls::ControlNode( label );
-        return node;
-    }
-
-    /**
-     * Creates a complete set of positioned label nodes from a feature list.
-     */
-    osg::Node* createNode(
-        const FeatureList&   input,
-        const Style&         style,
-        const FilterContext& context )
-    {
-        const TextSymbol* text = style.get<TextSymbol>();
-
-        osg::Group* group = 0L;
-        std::set<std::string> used; // to prevent dupes
-        bool skipDupes = (text->removeDuplicateLabels() == true);
-
-        StringExpression  contentExpr ( *text->content() );
-        NumericExpression priorityExpr( *text->priority() );
-
-        //bool makeECEF = false;
-        const SpatialReference* ecef = 0L;
-        if ( context.isGeoreferenced() )
-        {
-            //makeECEF = context.getSession()->getMapInfo().isGeocentric();
-            ecef = context.getSession()->getMapSRS()->getECEF();
-        }
-
-        for( FeatureList::const_iterator i = input.begin(); i != input.end(); ++i )
-        {
-            const Feature* feature = i->get();
-            if ( !feature )
-                continue;
-
-            const Geometry* geom = feature->getGeometry();
-            if ( !geom )
-                continue;
-
-            osg::Vec3d centroid  = geom->getBounds().center();
-
-            if ( ecef )
-            {
-                context.profile()->getSRS()->transform( centroid, ecef, centroid );
-                //context.profile()->getSRS()->transformToECEF( centroid, centroid );
-            }
-
-            const std::string& value = feature->eval( contentExpr, &context );
-
-            if ( !value.empty() && (!skipDupes || used.find(value) == used.end()) )
-            {
-                if ( !group )
-                {
-                    group = new osg::Group();
-                }
-
-                double priority = feature->eval( priorityExpr, &context );
-
-                Controls::LabelControl* label = new Controls::LabelControl( value );
-                if ( text->fill().isSet() )
-                    label->setForeColor( text->fill()->color() );
-                if ( text->halo().isSet() )
-                    label->setHaloColor( text->halo()->color() );
-                //if ( text->size().isSet() )
-                //    label->setFontSize( *text->size() );
-                if ( text->font().isSet() )
-                {
-                    // mitigates mipmapping issues that cause rendering artifacts for some fonts/placement
-                    osgText::Font* font = osgText::readFontFile(*text->font() );
-                    // mitigates mipmapping issues that cause rendering artifacts for some fonts/placement
-                    if ( font )
-                        font->setGlyphImageMargin( 2 );
-                    label->setFont( font );
-                }
-
-                Controls::ControlNode* node = new Controls::ControlNode( label, priority );
-
-                osg::MatrixTransform* xform = new osg::MatrixTransform( osg::Matrixd::translate(centroid) );
-                xform->addChild( node );
-
-                // for a geocentric map, do a simple dot product cull.
-                if ( ecef )
-                {
-                    xform->setCullCallback( new CullNodeByHorizon(
-                        centroid, 
-                        ecef->getEllipsoid() ) );
-                        //context.getSession()->getMapSRS()->getEllipsoid() ) ); //getMapInfo().getProfile()->getSRS()->getEllipsoid()) );
-                    group->addChild( xform );
-                }
-                else
-                {
-                    group->addChild( xform );
-                }
-
-                if ( skipDupes )
-                {
-                    used.insert( value );
-                }
-            }
-        }
-
-        return group;
-    }
-};
-
-//------------------------------------------------------------------------
-
-class OverlayLabelSourceDriver : public LabelSourceDriver
-{
-public:
-    OverlayLabelSourceDriver()
-    {
-        supportsExtension( "osgearth_label_overlay", "osgEarth overlay label plugin" );
-    }
-
-    virtual const char* className()
-    {
-        return "osgEarth Overlay Label Plugin";
-    }
-
-    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 OverlayLabelSource( getLabelSourceOptions(options) );
-    }
-};
-
-REGISTER_OSGPLUGIN(osgearth_label_overlay, OverlayLabelSourceDriver)
diff --git a/src/osgEarthDrivers/mask_feature/FeatureMaskOptions b/src/osgEarthDrivers/mask_feature/FeatureMaskOptions
index 5346487..24a17b6 100644
--- a/src/osgEarthDrivers/mask_feature/FeatureMaskOptions
+++ b/src/osgEarthDrivers/mask_feature/FeatureMaskOptions
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/mask_feature/FeatureMaskSource.cpp b/src/osgEarthDrivers/mask_feature/FeatureMaskSource.cpp
index 722e387..1664c84 100644
--- a/src/osgEarthDrivers/mask_feature/FeatureMaskSource.cpp
+++ b/src/osgEarthDrivers/mask_feature/FeatureMaskSource.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/mbtiles/CMakeLists.txt b/src/osgEarthDrivers/mbtiles/CMakeLists.txt
index 3fb8748..5d4033d 100644
--- a/src/osgEarthDrivers/mbtiles/CMakeLists.txt
+++ b/src/osgEarthDrivers/mbtiles/CMakeLists.txt
@@ -1,3 +1,5 @@
+IF(SQLITE3_FOUND)
+
 INCLUDE_DIRECTORIES( ${SQLITE3_INCLUDE_DIR} )
 
 #SET(TARGET_COMMON_LIBRARIES
@@ -25,3 +27,5 @@ SETUP_PLUGIN(osgearth_mbtiles)
 SET(LIB_NAME mbtiles)
 SET(LIB_PUBLIC_HEADERS MBTilesOptions)
 INCLUDE(ModuleInstallOsgEarthDriverIncludes OPTIONAL)
+
+ENDIF(SQLITE3_FOUND)
\ No newline at end of file
diff --git a/src/osgEarthDrivers/mbtiles/MBTilesOptions b/src/osgEarthDrivers/mbtiles/MBTilesOptions
index 867ca9a..d5721e1 100644
--- a/src/osgEarthDrivers/mbtiles/MBTilesOptions
+++ b/src/osgEarthDrivers/mbtiles/MBTilesOptions
@@ -54,8 +54,9 @@ namespace osgEarth { namespace Drivers
          * scan the table to determine the min/max.  This can take time when first loading the file so if you know the levels of your file 
          * up front you can set this to false and just use the min_level max_level settings of the tile source.
          */
-        optional<bool>& computeLevels() { return _computeLevels;}
-        const optional<bool>& computeLevels() const { return _computeLevels;}
+        optional<bool>& computeLevels() { return _computeLevels; }
+        const optional<bool>& computeLevels() const { return _computeLevels; }
+
     public:
         MBTilesTileSourceOptions(const TileSourceOptions& opt =TileSourceOptions()) :
             TileSourceOptions( opt ),
diff --git a/src/osgEarthDrivers/mbtiles/MBTilesPlugin.cpp b/src/osgEarthDrivers/mbtiles/MBTilesPlugin.cpp
index f3c7329..a941bae 100644
--- a/src/osgEarthDrivers/mbtiles/MBTilesPlugin.cpp
+++ b/src/osgEarthDrivers/mbtiles/MBTilesPlugin.cpp
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/osgEarthDrivers/mbtiles/MBTilesTileSource b/src/osgEarthDrivers/mbtiles/MBTilesTileSource
index d2891f9..fc0535a 100644
--- a/src/osgEarthDrivers/mbtiles/MBTilesTileSource
+++ b/src/osgEarthDrivers/mbtiles/MBTilesTileSource
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/osgEarthDrivers/mbtiles/MBTilesTileSource.cpp b/src/osgEarthDrivers/mbtiles/MBTilesTileSource.cpp
index a01057e..8b17959 100644
--- a/src/osgEarthDrivers/mbtiles/MBTilesTileSource.cpp
+++ b/src/osgEarthDrivers/mbtiles/MBTilesTileSource.cpp
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
@@ -146,7 +149,16 @@ MBTilesTileSource::initialize(const osgDB::Options* dbOptions)
         std::string metaDataFormat;
         getMetaData( "format", metaDataFormat );
         if ( !metaDataFormat.empty() )
-            _tileFormat = metaDataFormat;
+            _tileFormat = metaDataFormat;        
+
+        // Try to get it from the options.
+        if (_tileFormat.empty())
+        {
+            if ( _options.format().isSet() )
+            {
+                _tileFormat = _options.format().value();
+            }
+        }
 
         // By this point, we require a valid tile format.
         if ( _tileFormat.empty() )
@@ -236,9 +248,9 @@ MBTilesTileSource::initialize(const osgDB::Options* dbOptions)
     // make an empty image.
     int size = 256;
     _emptyImage = new osg::Image();
-    _emptyImage->allocateImage(size, size, 1, _forceRGB? GL_RGB : GL_RGBA, GL_UNSIGNED_BYTE);
+    _emptyImage->allocateImage(size, size, 1, GL_RGBA, GL_UNSIGNED_BYTE);
     unsigned char *data = _emptyImage->data(0,0);
-    memset(data, 0, (_forceRGB?3:4) * size * size);
+    memset(data, 0, 4 * size * size);
 
     return STATUS_OK;
 }    
diff --git a/src/osgEarthDrivers/model_feature_geom/FeatureGeomModelOptions b/src/osgEarthDrivers/model_feature_geom/FeatureGeomModelOptions
index 42b59c5..27053a9 100644
--- a/src/osgEarthDrivers/model_feature_geom/FeatureGeomModelOptions
+++ b/src/osgEarthDrivers/model_feature_geom/FeatureGeomModelOptions
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/model_feature_geom/FeatureGeomModelSource.cpp b/src/osgEarthDrivers/model_feature_geom/FeatureGeomModelSource.cpp
index abb133d..cd26ee7 100644
--- a/src/osgEarthDrivers/model_feature_geom/FeatureGeomModelSource.cpp
+++ b/src/osgEarthDrivers/model_feature_geom/FeatureGeomModelSource.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/model_feature_stencil/FeatureStencilModelOptions b/src/osgEarthDrivers/model_feature_stencil/FeatureStencilModelOptions
index e6e6ca2..2211aa3 100644
--- a/src/osgEarthDrivers/model_feature_stencil/FeatureStencilModelOptions
+++ b/src/osgEarthDrivers/model_feature_stencil/FeatureStencilModelOptions
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/model_feature_stencil/FeatureStencilModelSource.cpp b/src/osgEarthDrivers/model_feature_stencil/FeatureStencilModelSource.cpp
index a60250f..a836259 100644
--- a/src/osgEarthDrivers/model_feature_stencil/FeatureStencilModelSource.cpp
+++ b/src/osgEarthDrivers/model_feature_stencil/FeatureStencilModelSource.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/model_simple/SimpleModelOptions b/src/osgEarthDrivers/model_simple/SimpleModelOptions
index 388bd91..38317b5 100644
--- a/src/osgEarthDrivers/model_simple/SimpleModelOptions
+++ b/src/osgEarthDrivers/model_simple/SimpleModelOptions
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/model_simple/SimpleModelSource.cpp b/src/osgEarthDrivers/model_simple/SimpleModelSource.cpp
index 109d117..ac8c4e5 100644
--- a/src/osgEarthDrivers/model_simple/SimpleModelSource.cpp
+++ b/src/osgEarthDrivers/model_simple/SimpleModelSource.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -78,7 +78,9 @@ namespace
             : osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN)
             , m_scale(scale)
             , m_offset(offset)
-        {}
+        {
+            setNodeMaskOverride( ~0 );
+        }
 
         virtual void apply(osg::PagedLOD& node)
         {
@@ -97,6 +99,37 @@ namespace
         float m_scale;
         float m_offset;
     };
+
+    /**
+     * Visitor that sets the DBOptions on deferred-loading nodes.
+     */
+    class SetDBOptionsVisitor : public osg::NodeVisitor
+    {
+    private:
+        osg::ref_ptr<osgDB::Options> _dbOptions;
+
+    public:
+        SetDBOptionsVisitor(const osgDB::Options* dbOptions)
+        {
+            setTraversalMode( TRAVERSE_ALL_CHILDREN );
+            setNodeMaskOverride( ~0 );
+            _dbOptions = Registry::cloneOrCreateOptions( dbOptions );                
+        }
+
+    public: // osg::NodeVisitor
+
+        void apply(osg::PagedLOD& node)
+        {
+            node.setDatabaseOptions( _dbOptions.get() );
+            traverse(node);
+        }
+
+        void apply(osg::ProxyNode& node)
+        {
+            node.setDatabaseOptions( _dbOptions.get() );
+            traverse(node);
+        }
+    };
 }
 
 //--------------------------------------------------------------------------
@@ -110,13 +143,21 @@ public:
     //override
     void initialize( const osgDB::Options* dbOptions )
     {
+        _dbOptions = dbOptions;
         ModelSource::initialize( dbOptions );
     }
 
     // override
-    osg::Node* createNodeImplementation(const Map* map, const osgDB::Options* dbOptions, ProgressCallback* progress )
+    osg::Node* createNodeImplementation(const Map* map, ProgressCallback* progress)
     {
         osg::ref_ptr<osg::Node> result;
+
+        // Set up the DB Options for possible paged or proxy loading.
+        osg::ref_ptr<osgDB::Options> localDBOptions = 
+            Registry::instance()->cloneOrCreateOptions( _dbOptions.get() );
+
+        localDBOptions->getDatabasePathList().push_back( osgDB::getFilePath(_options.url()->full()) );
+
         
         // Only support paging if they've enabled it and provided a min/max range
         bool usePagedLOD = *_options.paged() &&
@@ -131,13 +172,7 @@ public:
             // Only load the model if it's not paged or we don't have a location set.
             if (!usePagedLOD || !_options.location().isSet())
             {
-                // required if the model includes local refs, like PagedLOD or ProxyNode:
-                osg::ref_ptr<osgDB::Options> localOptions = 
-                    Registry::instance()->cloneOrCreateOptions( dbOptions );
-
-                localOptions->getDatabasePathList().push_back( osgDB::getFilePath(_options.url()->full()) );
-
-                result = _options.url()->getNode( localOptions.get(), progress );                
+                result = _options.url()->getNode( localDBOptions.get(), progress );                
             }
         }
 
@@ -195,11 +230,11 @@ public:
                     osg::Vec3d center = result->getBound().center();
                     OE_DEBUG << "Radius=" << result->getBound().radius() << " center=" << center.x() << "," << center.y() << "," << center.z() << std::endl;                    
                     plod->setCenter(result->getBound().center());
-                    plod->setRadius(result->getBound().radius());                    
+                    plod->setRadius(std::max(result->getBound().radius(), maxRange));
                 }
                 lod = plod;
             }   
-            lod->setRange(0, minRange, maxRange);                
+            lod->setRange(0, minRange, maxRange);
             mt->addChild(lod);                
         }
         else
@@ -245,6 +280,14 @@ public:
                     new osg::Program(),
                     osg::StateAttribute::OFF | osg::StateAttribute::OVERRIDE );
             }
+
+            // apply the DB options if there are any, so that deferred nodes like PagedLOD et al
+            // will inherit the loading options.
+            if ( _dbOptions.valid() )
+            {
+                SetDBOptionsVisitor setDBO( localDBOptions.get() );
+                result->accept( setDBO );
+            }
         }
 
 
@@ -253,7 +296,8 @@ public:
 
 protected:
 
-    const SimpleModelOptions _options;
+    const SimpleModelOptions           _options;
+    osg::ref_ptr<const osgDB::Options> _dbOptions;
 };
 
 
diff --git a/src/osgEarthDrivers/noise/NoiseDriver.cpp b/src/osgEarthDrivers/noise/NoiseDriver.cpp
deleted file mode 100644
index ad79a4e..0000000
--- a/src/osgEarthDrivers/noise/NoiseDriver.cpp
+++ /dev/null
@@ -1,321 +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 <osgEarth/TileSource>
-#include <osgEarth/Registry>
-#include <osgEarth/URI>
-#include <osgEarth/ImageUtils>
-
-#include <osgEarthUtil/SimplexNoise>
-
-#include <osg/Notify>
-#include <osgDB/FileNameUtils>
-#include <osgDB/FileUtils>
-#include <osgDB/Registry>
-#include <osgDB/ReadFile>
-#include <osgDB/WriteFile>
-#include <sstream>
-#include <cmath>
-
-#include "NoiseOptions"
-
-using namespace osgEarth;
-using namespace osgEarth::Util;
-
-namespace osgEarth { namespace Drivers { namespace Noise
-{
-    class NoiseSource : public TileSource
-    {
-    public:
-        NoiseSource( const TileSourceOptions& options ) : TileSource( options ), _options(options)
-        {
-            _noise.setFrequency  ( *_options.frequency() );
-            _noise.setPersistence( *_options.persistence() );
-            _noise.setLacunarity ( *_options.lacunarity() );
-            _noise.setOctaves    ( *_options.octaves() );
-            _noise.setRange      ( -1.0, 1.0 );
-        }
-
-        // Yahoo! uses spherical mercator, but the top LOD is a 2x2 tile set.
-        Status initialize(const osgDB::Options* dbOptions)
-        {            
-            _dbOptions = Registry::instance()->cloneOrCreateOptions( dbOptions );            
-            setProfile( osgEarth::Registry::instance()->getGlobalGeodeticProfile() );
-
-            // resolve frequency if the user set resolution
-            if (_options.resolution().isSet() && !_options.resolution().isSetTo(0.0))
-            {
-                _noise.setFrequency( 1.0 / *_options.resolution() );
-            }
-
-            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;
-        }
-
-        inline double sample(double x, double y, double z)
-        {
-            return _noise.getValue(x, y, z);
-        }
-
-        inline double sample(const osg::Vec3d& v)
-        {
-            return _noise.getValue(v.x(), v.y(), v.z());
-        }
-
-        inline double turbulence(const osg::Vec3d& v, double f)
-        {
-            double t = -0.5;
-            for( ; f<getPixelsPerTile()/2; f *= 2 ) 
-                t += std::abs(_noise.getValue(v.x(), v.y(), v.z())/f);
-            return t;
-        }
-
-        inline double stripes(double x, double f)
-        {
-            double t = 0.5 + 0.5 * asin(f * 2*osg::PI * x);
-            return t * t - 0.5;
-        }
-
-
-        osg::Image* createImage(const TileKey&    key,
-                                ProgressCallback* progress)
-        {
-            if ( _options.normalMap() == true )
-            {
-                return createNormalMap(key, progress);
-            }
-            else
-            {
-                const SpatialReference* srs = key.getProfile()->getSRS();
-
-                double projNormX, projNormY;
-                if ( srs->isProjected() )
-                {
-                    projNormX = 1.0/key.getProfile()->getExtent().width();
-                    projNormY = 1.0/key.getProfile()->getExtent().height();
-                }
-
-                osg::Image* image = new osg::Image();
-                image->allocateImage( getPixelsPerTile(), getPixelsPerTile(), 1, GL_RGB, 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 x = key.getExtent().xMin() + (double)s * dx;
-                        double y = key.getExtent().yMin() + (double)t * dy;
-                        
-                        osg::Vec3d world(x, y, 0.0);
-
-                        if ( srs->isGeographic() )
-                        {
-                            srs->transform(world, srs->getECEF(), world);
-                            world.normalize();
-                        }
-                        else
-                        {
-                            world.x() *= projNormX;
-                            world.y() *= projNormY;
-                            world.z()  = 0.0;
-                        }
-
-                        double n = sample(world); //world.x(), world.y(), world.z());
-                        //double n = 0.1 * stripes(world.x() + 2.0*turbulence(noise, world, 1.0), 1.6);
-                        //double n = -.10 * turbulence(noise, world, 0.2);
-
-                        // scale and bias from[-1..1] to [0..1] for coloring.
-                        n = osg::clampBetween( (n+1.0)*0.5, 0.0, 1.0 );
-
-                        write(osg::Vec4f(n,n,n,1), s, t);
-                    }
-                }
-
-                return image;
-            }
-        }
-
-
-        osg::HeightField* createHeightField(const TileKey&        key,
-                                            ProgressCallback*     progress )
-        {
-            const SpatialReference* srs = key.getProfile()->getSRS();
-
-            osg::HeightField* hf = new osg::HeightField();
-            hf->allocate( getPixelsPerTile(), getPixelsPerTile() );
-
-            double dx = key.getExtent().width() / (double)(hf->getNumColumns()-1);
-            double dy = key.getExtent().height() / (double)(hf->getNumRows()-1);
-
-            double bias  = _options.bias().get();
-            double scale = _options.scale().get();
-        
-            //Initialize the heightfield
-            for (unsigned int c = 0; c < hf->getNumColumns(); c++) 
-            {
-                for (unsigned int r = 0; r < hf->getNumRows(); r++)
-                {                
-                    double lon = key.getExtent().xMin() + (double)c * dx;
-                    double lat = key.getExtent().yMin() + (double)r * dy;
-
-                    osg::Vec3d world(lon, lat, 0.0);
-                    if ( srs->isGeographic() )
-                    {
-                        srs->transform(world, srs->getECEF(), world);
-                        world.normalize();
-                    }
-
-                    double n = sample( world );
-
-                    // Scale the noise value.
-                    double h = osg::clampBetween(
-                        (float)(bias + scale * n),
-                        *_options.minElevation(),
-                        *_options.maxElevation() );
-
-                    hf->setHeight( c, r, h );
-
-                    // NOTE! The elevation engine treats extreme values (>32000, etc)
-                    // as "no data" so be careful with your scale.
-                }
-            }     
-
-            return hf;
-        }
-
-
-        osg::Image* createNormalMap(const TileKey& key, ProgressCallback* progress)
-        {
-            // set up the image and prepare to write to it.
-            osg::Image* image = new osg::Image();
-            image->allocateImage( getPixelsPerTile(), getPixelsPerTile(), 1, GL_RGB, GL_UNSIGNED_BYTE );
-            ImageUtils::PixelWriter write(image);
-
-            const GeoExtent&        ex     = key.getExtent();
-            const SpatialReference* srs    = ex.getSRS();
-            bool                    isGeo  = srs->isGeographic();
-            const SpatialReference* ecef   = srs->getECEF();
-
-            double dx = ex.width()  / (double)(image->s()-1);
-            double dy = ex.height() / (double)(image->t()-1);
-
-            double scale  = _options.scale().get();
-            double bias   = _options.bias().get();
-
-            // figure out the spacing between pixels in the same units as the height value:
-            double udx = dx;
-            double udy = dy;
-            if ( isGeo )
-            {
-                udx = srs->transformUnits(dx, ecef, ex.south()+0.5*dy);
-                udy = srs->transformUnits(dy, ecef, ex.south()+0.5*dy);
-            }
-
-            double z = 0.0;
-            std::vector<osg::Vec3d> v(4);
-            double samples[4];
-
-            for(int s=0; s<image->s(); ++s)
-            {
-                for(int t=0; t<image->t(); ++t)
-                {
-                    double x = ex.xMin() + (double)s * dx;
-                    double y = ex.yMin() + (double)t * dy;
-
-                    if ( isGeo )
-                    {
-                        v[0].set(x-dx, y, z);
-                        v[1].set(x+dx, y, z);
-                        v[2].set(x, y+dy, z);
-                        v[3].set(x, y-dy, z);
-                        srs->transform(v, ecef);
-                        for(int i=0; i<4; ++i )
-                            samples[i] = bias + scale * sample(v[i]);
-                    }
-                    else
-                    {
-                        samples[0] = bias + scale * sample(x-dx, y, z);
-                        samples[1] = bias + scale * sample(x+dx, y, z);
-                        samples[2] = bias + scale * sample(x, y+dy, z);
-                        samples[3] = bias + scale * sample(x, y-dy, z);
-                    }
-
-                    osg::Vec3d west (-udx,    0, samples[0]);
-                    osg::Vec3d east ( udx,    0, samples[1]);
-                    osg::Vec3d north(   0,  udy, samples[2]);
-                    osg::Vec3d south(   0, -udy, samples[3]);
-
-                    // calculate the normal at the center point.
-                    osg::Vec3 normal = (east-west) ^ (north-south);
-                    normal.normalize();
-
-                    // encode as: xyz[-1..1]=>r[0..255]. (In reality Z will always fall 
-                    // between [0..1] but a uniform encoding makes the shader code simpler.)
-                    normal.x() = normal.x()*0.5f + 0.5f;
-                    normal.y() = normal.y()*0.5f + 0.5f;
-                    normal.z() = normal.z()*0.5f + 0.5f;
-                    normal.normalize();
-
-                    write(osg::Vec4f(normal,1), s, t);
-                }
-            }
-
-            return image;
-        }
-
-
-    private:
-        NoiseOptions                 _options;
-        osg::ref_ptr<osgDB::Options> _dbOptions;
-        SimplexNoise                 _noise;
-    };
-
-
-    class NoiseDriver : public TileSourceDriver
-    {
-        public:
-            NoiseDriver()
-            {
-                supportsExtension( "osgearth_noise", "Procedural noise generator" );
-            }
-
-            virtual const char* className()
-            {
-                return "Procedural noise generator";
-            }
-
-            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 NoiseSource( getTileSourceOptions(options) );
-            }
-    };
-
-    REGISTER_OSGPLUGIN(osgearth_noise, NoiseDriver);
-
-} } } // namespace osgEarth::Drivers::Noise
diff --git a/src/osgEarthDrivers/ocean_simple/ElevationProxyImageLayer b/src/osgEarthDrivers/ocean_simple/ElevationProxyImageLayer
index f275549..ea917a6 100644
--- a/src/osgEarthDrivers/ocean_simple/ElevationProxyImageLayer
+++ b/src/osgEarthDrivers/ocean_simple/ElevationProxyImageLayer
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -47,7 +47,7 @@ namespace osgEarth { namespace Drivers { namespace SimpleOcean
 
     public: // ImageLayer
 
-        virtual void initTileSource();
+        virtual TileSource* createTileSource();
 
         virtual bool isKeyInRange( const TileKey& key ) const;
 
diff --git a/src/osgEarthDrivers/ocean_simple/ElevationProxyImageLayer.cpp b/src/osgEarthDrivers/ocean_simple/ElevationProxyImageLayer.cpp
index 441f887..fb1891c 100644
--- a/src/osgEarthDrivers/ocean_simple/ElevationProxyImageLayer.cpp
+++ b/src/osgEarthDrivers/ocean_simple/ElevationProxyImageLayer.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -32,11 +32,10 @@ _mapf     ( sourceMap )
     _runtimeOptions.cachePolicy() = CachePolicy::NO_CACHE;
 }
 
-void
-ElevationProxyImageLayer::initTileSource()
+TileSource*
+ElevationProxyImageLayer::createTileSource()
 {
-    _tileSourceInitAttempted = true;
-    _tileSourceInitFailed    = false;
+    return 0L;
 }
 
 bool
@@ -65,7 +64,7 @@ ElevationProxyImageLayer::createImage(const TileKey& key, ProgressCallback* prog
 
     osg::ref_ptr<osg::HeightField> hf;
 
-    if ( _mapf.populateHeightField(hf, key, true) )
+    if ( _mapf.populateHeightField(hf, key, true, 0L) )
     {
         // encode the heightfield as a 16-bit normalized LUNIMANCE image
         osg::Image* image = new osg::Image();
diff --git a/src/osgEarthDrivers/ocean_simple/SimpleOceanDriver.cpp b/src/osgEarthDrivers/ocean_simple/SimpleOceanDriver.cpp
index fd60dde..2364300 100644
--- a/src/osgEarthDrivers/ocean_simple/SimpleOceanDriver.cpp
+++ b/src/osgEarthDrivers/ocean_simple/SimpleOceanDriver.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/ocean_simple/SimpleOceanNode b/src/osgEarthDrivers/ocean_simple/SimpleOceanNode
index 4e90c84..d37497f 100644
--- a/src/osgEarthDrivers/ocean_simple/SimpleOceanNode
+++ b/src/osgEarthDrivers/ocean_simple/SimpleOceanNode
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/ocean_simple/SimpleOceanNode.cpp b/src/osgEarthDrivers/ocean_simple/SimpleOceanNode.cpp
index 24e9de6..c1ab3de 100644
--- a/src/osgEarthDrivers/ocean_simple/SimpleOceanNode.cpp
+++ b/src/osgEarthDrivers/ocean_simple/SimpleOceanNode.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -128,6 +128,8 @@ SimpleOceanNode::rebuild()
 
         mpoptions.enableBlending() = true;        // gotsta blend with the main node
 
+        mpoptions.color() = _options.baseColor().get();
+
         mno.setTerrainOptions( mpoptions );
 
         // make the ocean's map node:
@@ -179,7 +181,7 @@ SimpleOceanNode::rebuild()
         std::string fragSource = _options.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 );
+        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 );
diff --git a/src/osgEarthDrivers/ocean_simple/SimpleOceanOptions b/src/osgEarthDrivers/ocean_simple/SimpleOceanOptions
index 5aa112f..2822b0e 100644
--- a/src/osgEarthDrivers/ocean_simple/SimpleOceanOptions
+++ b/src/osgEarthDrivers/ocean_simple/SimpleOceanOptions
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -86,7 +86,7 @@ namespace osgEarth { namespace Drivers { namespace SimpleOcean
               _highFeatherOffset( -10.0f ),
               _maxRange         ( 1000000.0f ),
               _fadeRange        ( 125000.0f ),
-              _maxLOD           ( 11 ),
+              _maxLOD           ( 20 ),
               _baseColor        ( osg::Vec4(0.2, 0.3, 0.5, 0.8) )
         {
             mergeConfig( _conf );
diff --git a/src/osgEarthDrivers/ocean_simple/SimpleOceanShaders b/src/osgEarthDrivers/ocean_simple/SimpleOceanShaders
index 43cb296..f00fc62 100644
--- a/src/osgEarthDrivers/ocean_simple/SimpleOceanShaders
+++ b/src/osgEarthDrivers/ocean_simple/SimpleOceanShaders
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/ocean_triton/CMakeLists.txt b/src/osgEarthDrivers/ocean_triton/CMakeLists.txt
index a9a519f..c4a3ac6 100644
--- a/src/osgEarthDrivers/ocean_triton/CMakeLists.txt
+++ b/src/osgEarthDrivers/ocean_triton/CMakeLists.txt
@@ -1,3 +1,4 @@
+IF(TRITON_FOUND)
 
 SET(TARGET_SRC TritonDriver.cpp
                TritonNode.cpp
@@ -28,3 +29,5 @@ SET(LIB_NAME ocean_triton)
 SET(LIB_PUBLIC_HEADERS TritonOptions)
 
 INCLUDE(ModuleInstallOsgEarthDriverIncludes OPTIONAL)
+
+ENDIF(TRITON_FOUND)
\ No newline at end of file
diff --git a/src/osgEarthDrivers/ocean_triton/TritonContext b/src/osgEarthDrivers/ocean_triton/TritonContext
index b0afeb7..2a44977 100644
--- a/src/osgEarthDrivers/ocean_triton/TritonContext
+++ b/src/osgEarthDrivers/ocean_triton/TritonContext
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -16,6 +16,9 @@
  * 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_CONTEXT_H
+#define OSGEARTH_TRITON_CONTEXT_H
+
 #include "TritonOptions"
 #include <osg/Referenced>
 #include <osg/Light>
@@ -31,10 +34,8 @@ namespace Triton {
     class Ocean;
 }
 
-namespace osgEarth { namespace Drivers { namespace Triton
+namespace osgEarth { namespace Triton
 {
-    using namespace osgEarth;
-
     /**
      * Contains all the Triton SDK handles.
      */
@@ -44,14 +45,14 @@ namespace osgEarth { namespace Drivers { namespace Triton
         TritonContext(const TritonOptions& options);
 
         /** Sets the spatial reference system of the map */
-        void setSRS(const SpatialReference* srs);
+        void setSRS(const osgEarth::SpatialReference* srs);
 
     public: // accessors
 
         bool ready() const { return _initAttempted && !_initFailed; }
 
         /** Spatial reference of the map */
-        const SpatialReference* getSRS() const { return _srs.get(); }
+        const osgEarth::SpatialReference* getSRS() const { return _srs.get(); }
 
         void initialize(osg::RenderInfo& renderInfo);
 
@@ -72,11 +73,13 @@ namespace osgEarth { namespace Drivers { namespace Triton
         bool             _initFailed;
         Threading::Mutex _initMutex;
 
-        osg::ref_ptr<const SpatialReference> _srs;
+        osg::ref_ptr<const osgEarth::SpatialReference> _srs;
 
         ::Triton::ResourceLoader* _resourceLoader;
         ::Triton::Environment*    _environment;
         ::Triton::Ocean*          _ocean;
     };
 
-} } } // namespace osgEarth::Drivers::Triton
+} } // namespace osgEarth::Triton
+
+#endif // OSGEARTH_TRITON_CONTEXT_H
\ No newline at end of file
diff --git a/src/osgEarthDrivers/ocean_triton/TritonContext.cpp b/src/osgEarthDrivers/ocean_triton/TritonContext.cpp
index 490a046..58e79e3 100644
--- a/src/osgEarthDrivers/ocean_triton/TritonContext.cpp
+++ b/src/osgEarthDrivers/ocean_triton/TritonContext.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -16,15 +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/>
  */
-#include "TritonContext"
 #include <Triton.h>
+#include "TritonContext"
 #include <osg/GLExtensions>
 #include <osgEarth/SpatialReference>
 
 #define LC "[TritonContext] "
 
-using namespace osgEarth;
-using namespace osgEarth::Drivers::Triton;
+using namespace osgEarth::Triton;
 
 
 TritonContext::TritonContext(const TritonOptions& options) :
@@ -39,7 +38,7 @@ _ocean                ( 0L )
 }
 
 void
-TritonContext::setSRS(const SpatialReference* srs)
+TritonContext::setSRS(const osgEarth::SpatialReference* srs)
 {
     _srs = srs;
 }
@@ -124,7 +123,8 @@ TritonContext::update(double simTime)
 {
     if ( _ocean )
     {
-        _ocean->UpdateSimulation( simTime );
+        // fmod requires b/c CUDA is limited to single-precision values
+        _ocean->UpdateSimulation( fmod(simTime, 86400.0) );
     }
 }
 
diff --git a/src/osgEarthDrivers/ocean_triton/TritonDrawable b/src/osgEarthDrivers/ocean_triton/TritonDrawable
index 4831ee7..313cc88 100644
--- a/src/osgEarthDrivers/ocean_triton/TritonDrawable
+++ b/src/osgEarthDrivers/ocean_triton/TritonDrawable
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -16,6 +16,9 @@
  * 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_DRAWABLE_H
+#define OSGEARTH_TRITON_DRAWABLE_H
+
 #include <osg/Drawable>
 #include <osg/RenderInfo>
 #include <osg/TextureCubeMap>
@@ -23,17 +26,15 @@
 #include <osg/Texture2D>
 
 #include <osgEarth/MapNode>
+#include <osgEarth/Terrain>
+#include <osgEarth/NativeProgramAdapter>
 
-const unsigned int OCEAN_MASK         = 0x4; // 0100
-
-class OceanTerrainChangedCallback;
+const unsigned int TRITON_OCEAN_MASK = 0x4; // 0100
 
-namespace osgEarth { namespace Drivers { namespace Triton
+namespace osgEarth { namespace Triton
 {
     class TritonContext;
 
-    using namespace osgEarth;
-
     /**
      * Custom drawable for rendering the Triton ocean effects
      */
@@ -69,12 +70,16 @@ namespace osgEarth { namespace Drivers { namespace Triton
         osg::BoundingBox                  _bbox;
         osg::ref_ptr<osg::Texture2D> _heightMap;
         osg::ref_ptr<osg::Camera> _heightCamera;
-        osg::observer_ptr<OceanTerrainChangedCallback> _terrainChangedCallback;
+        osg::observer_ptr<osgEarth::TerrainCallback> _terrainChangedCallback;
         
         osg::ref_ptr< osg::Texture2D >       _planarReflectionMap;
         osg::ref_ptr< osg::RefMatrix >       _planarReflectionProjection;
+
+        mutable osg::buffered_object<osgEarth::NativeProgramAdapterCollection> _adapters;
         
         TritonDrawable(const TritonDrawable& copy, const osg::CopyOp& op=osg::CopyOp::SHALLOW_COPY) { }
     };
 
-} } } // namespace osgEarth::Drivers::Triton
+} } // namespace osgEarth::Triton
+
+#endif // OSGEARTH_TRITON_DRAWABLE_H
diff --git a/src/osgEarthDrivers/ocean_triton/TritonDrawable.cpp b/src/osgEarthDrivers/ocean_triton/TritonDrawable.cpp
index 2207636..f63df95 100644
--- a/src/osgEarthDrivers/ocean_triton/TritonDrawable.cpp
+++ b/src/osgEarthDrivers/ocean_triton/TritonDrawable.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -16,339 +16,349 @@
  * 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 <osg/MatrixTransform>
 #include <osgEarth/SpatialReference>
 #include <osgEarth/VirtualProgram>
-#include <Triton.h>
 
+#undef  LC
 #define LC "[TritonDrawable] "
 
 //#define DEBUG_HEIGHTMAP
 
-using namespace osgEarth;
-using namespace osgEarth::Drivers::Triton;
+//#define USE_HEIGHT_MAP
 
-#ifdef DEBUG_HEIGHTMAP
-osg::Node*
-makeFrustumFromCamera( osg::Camera* camera )
+using namespace osgEarth::Triton;
+
+namespace
 {
-    osg::Matrixd proj;
-    osg::Matrixd mv;
-    if (camera)
-    {
-        proj = camera->getProjectionMatrix();
-        mv = camera->getViewMatrix();
-    }
-    else
+#ifdef DEBUG_HEIGHTMAP
+    osg::Node*
+    makeFrustumFromCamera( osg::Camera* camera )
     {
-        proj.makePerspective( 30., 1., 1., 10. );
-        // leave mv as identity
-    }
+        osg::Matrixd proj;
+        osg::Matrixd mv;
+        if (camera)
+        {
+            proj = camera->getProjectionMatrix();
+            mv = camera->getViewMatrix();
+        }
+        else
+        {
+            proj.makePerspective( 30., 1., 1., 10. );
+            // leave mv as identity
+        }
 
-    double near=0.0, far=0.0;
-    double left=0.0, right=0.0, bottom=0.0, top=0.0;
-    double fovy=0.0, aspectRatio=0.0;
-    double nLeft=0.0, nRight=0.0, nBottom=0.0, nTop=0.0;
-    double fLeft=0.0, fRight=0.0, fBottom=0.0, fTop=0.0;
+        double near=0.0, far=0.0;
+        double left=0.0, right=0.0, bottom=0.0, top=0.0;
+        double fovy=0.0, aspectRatio=0.0;
+        double nLeft=0.0, nRight=0.0, nBottom=0.0, nTop=0.0;
+        double fLeft=0.0, fRight=0.0, fBottom=0.0, fTop=0.0;
 
-    osg::Geode* geode = new osg::Geode;
+        osg::Geode* geode = new osg::Geode;
 
-    if( proj.getPerspective(fovy, aspectRatio, near, far) )
-    {
-        near = proj(3,2) / (proj(2,2)-1.0);
-        far = proj(3,2) / (1.0+proj(2,2));
-        
-        nLeft = near * (proj(2,0)-1.0) / proj(0,0);
-        nRight = near * (1.0+proj(2,0)) / proj(0,0);
-        nTop = near * (1.0+proj(2,1)) / proj(1,1);
-        nBottom = near * (proj(2,1)-1.0) / proj(1,1);
-
-        fLeft = far * (proj(2,0)-1.0) / proj(0,0);
-        fRight = far * (1.0+proj(2,0)) / proj(0,0);
-        fTop = far * (1.0+proj(2,1)) / proj(1,1);
-        fBottom = far * (proj(2,1)-1.0) / proj(1,1);
-
-        osg::Vec3Array* v = new osg::Vec3Array;
-        v->resize( 9 );
-        (*v)[0].set( 0., 0., 0. );
-
-        (*v)[1].set( nLeft, nBottom, -near );
-        (*v)[2].set( nRight, nBottom, -near );
-        (*v)[3].set( nRight, nTop, -near );
-        (*v)[4].set( nLeft, nTop, -near );
-        (*v)[5].set( fLeft, fBottom, -far );
-        (*v)[6].set( fRight, fBottom, -far );
-        (*v)[7].set( fRight, fTop, -far );
-        (*v)[8].set( fLeft, fTop, -far );
-
-        osg::Geometry* geom = new osg::Geometry;
-        geom->setVertexArray( v );
-
-        GLushort idxLines[8] = {
-            0, 5, 0, 6, 0, 7, 0, 8 };
-        GLushort idxLoops0[4] = {
-            1, 2, 3, 4 };
-        GLushort idxLoops1[4] = {
-            5, 6, 7, 8 };
-        geom->addPrimitiveSet( new osg::DrawElementsUShort( osg::PrimitiveSet::LINES, 8, idxLines ) );
-        geom->addPrimitiveSet( new osg::DrawElementsUShort( osg::PrimitiveSet::LINE_LOOP, 4, idxLoops0 ) );
-        geom->addPrimitiveSet( new osg::DrawElementsUShort( osg::PrimitiveSet::LINE_LOOP, 4, idxLoops1 ) );
-
-        geode->addDrawable( geom );
-    }
-    else if(proj.getOrtho( left, right, bottom, top, near, far) )
-    {
-        nLeft = left;//near * (proj(2,0)-1.0) / proj(0,0);
-        nRight = right;//near * (1.0+proj(2,0)) / proj(0,0);
-        nTop = top;//near * (1.0+proj(2,1)) / proj(1,1);
-        nBottom = bottom;//near * (proj(2,1)-1.0) / proj(1,1);
-
-        fLeft = left;//far * (proj(2,0)-1.0) / proj(0,0);
-        fRight = right;//far * (1.0+proj(2,0)) / proj(0,0);
-        fTop = top;//far * (1.0+proj(2,1)) / proj(1,1);
-        fBottom = bottom;//far * (proj(2,1)-1.0) / proj(1,1);
-
-        osg::Vec3Array* v = new osg::Vec3Array;
-        v->resize( 9 );
-        (*v)[0].set( 0., 0., 0. );
-
-        (*v)[1].set( nLeft, nBottom, -near );
-        (*v)[2].set( nRight, nBottom, -near );
-        (*v)[3].set( nRight, nTop, -near );
-        (*v)[4].set( nLeft, nTop, -near );
-        (*v)[5].set( fLeft, fBottom, -far );
-        (*v)[6].set( fRight, fBottom, -far );
-        (*v)[7].set( fRight, fTop, -far );
-        (*v)[8].set( fLeft, fTop, -far );
-
-        osg::Geometry* geom = new osg::Geometry;
-        geom->setVertexArray( v );
-
-        GLushort idxLines[8] = {
-            1, 5, 2, 6, 3, 7, 4, 8 };
-        GLushort idxLoops0[4] = {
-            1, 2, 3, 4 };
-        GLushort idxLoops1[4] = {
-            5, 6, 7, 8 };
-        geom->addPrimitiveSet( new osg::DrawElementsUShort( osg::PrimitiveSet::LINES, 8, idxLines ) );
-        geom->addPrimitiveSet( new osg::DrawElementsUShort( osg::PrimitiveSet::LINE_LOOP, 4, idxLoops0 ) );
-        geom->addPrimitiveSet( new osg::DrawElementsUShort( osg::PrimitiveSet::LINE_LOOP, 4, idxLoops1 ) );
-
-        geode->addDrawable( geom );
-    }
+        if( proj.getPerspective(fovy, aspectRatio, near, far) )
+        {
+            near = proj(3,2) / (proj(2,2)-1.0);
+            far = proj(3,2) / (1.0+proj(2,2));
+
+            nLeft = near * (proj(2,0)-1.0) / proj(0,0);
+            nRight = near * (1.0+proj(2,0)) / proj(0,0);
+            nTop = near * (1.0+proj(2,1)) / proj(1,1);
+            nBottom = near * (proj(2,1)-1.0) / proj(1,1);
+
+            fLeft = far * (proj(2,0)-1.0) / proj(0,0);
+            fRight = far * (1.0+proj(2,0)) / proj(0,0);
+            fTop = far * (1.0+proj(2,1)) / proj(1,1);
+            fBottom = far * (proj(2,1)-1.0) / proj(1,1);
+
+            osg::Vec3Array* v = new osg::Vec3Array;
+            v->resize( 9 );
+            (*v)[0].set( 0., 0., 0. );
+
+            (*v)[1].set( nLeft, nBottom, -near );
+            (*v)[2].set( nRight, nBottom, -near );
+            (*v)[3].set( nRight, nTop, -near );
+            (*v)[4].set( nLeft, nTop, -near );
+            (*v)[5].set( fLeft, fBottom, -far );
+            (*v)[6].set( fRight, fBottom, -far );
+            (*v)[7].set( fRight, fTop, -far );
+            (*v)[8].set( fLeft, fTop, -far );
+
+            osg::Geometry* geom = new osg::Geometry;
+            geom->setVertexArray( v );
+
+            GLushort idxLines[8] = {
+                0, 5, 0, 6, 0, 7, 0, 8 };
+            GLushort idxLoops0[4] = {
+                1, 2, 3, 4 };
+            GLushort idxLoops1[4] = {
+                5, 6, 7, 8 };
+            geom->addPrimitiveSet( new osg::DrawElementsUShort( osg::PrimitiveSet::LINES, 8, idxLines ) );
+            geom->addPrimitiveSet( new osg::DrawElementsUShort( osg::PrimitiveSet::LINE_LOOP, 4, idxLoops0 ) );
+            geom->addPrimitiveSet( new osg::DrawElementsUShort( osg::PrimitiveSet::LINE_LOOP, 4, idxLoops1 ) );
+
+            geode->addDrawable( geom );
+        }
+        else if(proj.getOrtho( left, right, bottom, top, near, far) )
+        {
+            nLeft = left;//near * (proj(2,0)-1.0) / proj(0,0);
+            nRight = right;//near * (1.0+proj(2,0)) / proj(0,0);
+            nTop = top;//near * (1.0+proj(2,1)) / proj(1,1);
+            nBottom = bottom;//near * (proj(2,1)-1.0) / proj(1,1);
+
+            fLeft = left;//far * (proj(2,0)-1.0) / proj(0,0);
+            fRight = right;//far * (1.0+proj(2,0)) / proj(0,0);
+            fTop = top;//far * (1.0+proj(2,1)) / proj(1,1);
+            fBottom = bottom;//far * (proj(2,1)-1.0) / proj(1,1);
+
+            osg::Vec3Array* v = new osg::Vec3Array;
+            v->resize( 9 );
+            (*v)[0].set( 0., 0., 0. );
+
+            (*v)[1].set( nLeft, nBottom, -near );
+            (*v)[2].set( nRight, nBottom, -near );
+            (*v)[3].set( nRight, nTop, -near );
+            (*v)[4].set( nLeft, nTop, -near );
+            (*v)[5].set( fLeft, fBottom, -far );
+            (*v)[6].set( fRight, fBottom, -far );
+            (*v)[7].set( fRight, fTop, -far );
+            (*v)[8].set( fLeft, fTop, -far );
+
+            osg::Geometry* geom = new osg::Geometry;
+            geom->setVertexArray( v );
+
+            GLushort idxLines[8] = {
+                1, 5, 2, 6, 3, 7, 4, 8 };
+            GLushort idxLoops0[4] = {
+                1, 2, 3, 4 };
+            GLushort idxLoops1[4] = {
+                5, 6, 7, 8 };
+            geom->addPrimitiveSet( new osg::DrawElementsUShort( osg::PrimitiveSet::LINES, 8, idxLines ) );
+            geom->addPrimitiveSet( new osg::DrawElementsUShort( osg::PrimitiveSet::LINE_LOOP, 4, idxLoops0 ) );
+            geom->addPrimitiveSet( new osg::DrawElementsUShort( osg::PrimitiveSet::LINE_LOOP, 4, idxLoops1 ) );
+
+            geode->addDrawable( geom );
+        }
 
-    geode->getOrCreateStateSet()->setMode( GL_LIGHTING, osg::StateAttribute::OFF );
+        geode->getOrCreateStateSet()->setMode( GL_LIGHTING, osg::StateAttribute::OFF );
 
 
 
-    osg::MatrixTransform* mt = new osg::MatrixTransform;
-    osg::Matrixd mvi = osg::Matrixd::inverse( mv );
-    mt->setMatrix( mvi );
-    mt->addChild( geode );
+        osg::MatrixTransform* mt = new osg::MatrixTransform;
+        osg::Matrixd mvi = osg::Matrixd::inverse( mv );
+        mt->setMatrix( mvi );
+        mt->addChild( geode );
 
-    return mt;
-}
+        return mt;
+    }
 
-osg::Camera *
-CreateTextureQuadOverlay( osg::Texture * texture, float x, float y, float w, float h )
-{
-    osg::Camera * camera = new osg::Camera;
-
-    // set up the background color and clear mask.
-    camera->setClearMask( GL_DEPTH_BUFFER_BIT );
-
-    // set viewport
-    camera->setProjectionMatrixAsOrtho2D( 0, 1, 0, 1 );
-    camera->setViewMatrix( osg::Matrix::identity() );
-    camera->setReferenceFrame( osg::Transform::ABSOLUTE_RF );
-
-    osg::Geode * geode = new osg::Geode();
-    camera->addChild( geode );
-/*
-    osg::Geometry * background = osg::createTexturedQuadGeometry( osg::Vec3( x,y,0 ),osg::Vec3( w,0,0 ), osg::Vec3( 0,h,0 ) );
-    geode->addDrawable( background );
-
-    osg::Vec4Array* colors = new osg::Vec4Array;
-    colors->push_back( osg::Vec4( 1,1,0,0.3 ) );
-    background->setColorArray( colors );
-    background->setColorBinding( osg::Geometry::BIND_OVERALL );
-    background->getOrCreateStateSet()->setMode( GL_BLEND, osg::StateAttribute::ON );
-    background->getOrCreateStateSet()->setMode( GL_DEPTH_TEST, osg::StateAttribute::OFF );
-*/
-    osg::Geometry * quad = osg::createTexturedQuadGeometry( osg::Vec3( x,y,0 ),osg::Vec3( w,0,0 ), osg::Vec3( 0,h,0 ) );
-    geode->addDrawable( quad );
-    quad->getOrCreateStateSet()->setTextureAttributeAndModes( 0, texture, osg::StateAttribute::ON );
-    quad->getOrCreateStateSet()->setMode( GL_BLEND, osg::StateAttribute::ON );
-    quad->getOrCreateStateSet()->setMode( GL_LIGHTING, osg::StateAttribute::OFF );
-    quad->getOrCreateStateSet()->setMode( GL_DEPTH_TEST, osg::StateAttribute::OFF );
-
-
-    // set the camera to render after the main camera.
-    camera->setRenderOrder(osg::Camera::POST_RENDER);
-
-    return camera;
-}
+    osg::Camera *
+    CreateTextureQuadOverlay( osg::Texture * texture, float x, float y, float w, float h )
+    {
+        osg::Camera * camera = new osg::Camera;
+
+        // set up the background color and clear mask.
+        camera->setClearMask( GL_DEPTH_BUFFER_BIT );
+
+        // set viewport
+        camera->setProjectionMatrixAsOrtho2D( 0, 1, 0, 1 );
+        camera->setViewMatrix( osg::Matrix::identity() );
+        camera->setReferenceFrame( osg::Transform::ABSOLUTE_RF );
+
+        osg::Geode * geode = new osg::Geode();
+        camera->addChild( geode );
+    /*
+        osg::Geometry * background = osg::createTexturedQuadGeometry( osg::Vec3( x,y,0 ),osg::Vec3( w,0,0 ), osg::Vec3( 0,h,0 ) );
+        geode->addDrawable( background );
+
+        osg::Vec4Array* colors = new osg::Vec4Array;
+        colors->push_back( osg::Vec4( 1,1,0,0.3 ) );
+        background->setColorArray( colors );
+        background->setColorBinding( osg::Geometry::BIND_OVERALL );
+        background->getOrCreateStateSet()->setMode( GL_BLEND, osg::StateAttribute::ON );
+        background->getOrCreateStateSet()->setMode( GL_DEPTH_TEST, osg::StateAttribute::OFF );
+    */
+        osg::Geometry * quad = osg::createTexturedQuadGeometry( osg::Vec3( x,y,0 ),osg::Vec3( w,0,0 ), osg::Vec3( 0,h,0 ) );
+        geode->addDrawable( quad );
+        quad->getOrCreateStateSet()->setTextureAttributeAndModes( 0, texture, osg::StateAttribute::ON );
+        quad->getOrCreateStateSet()->setMode( GL_BLEND, osg::StateAttribute::ON );
+        quad->getOrCreateStateSet()->setMode( GL_LIGHTING, osg::StateAttribute::OFF );
+        quad->getOrCreateStateSet()->setMode( GL_DEPTH_TEST, osg::StateAttribute::OFF );
+
+
+        // set the camera to render after the main camera.
+        camera->setRenderOrder(osg::Camera::POST_RENDER);
+
+        return camera;
+    }
 #endif /* DEBUG_HEIGHTMAP */
 
-struct PassHeightMapToTritonCallback : public osg::Camera::DrawCallback 
-{
-    PassHeightMapToTritonCallback(TritonContext* triton) : _TRITON(triton), _enable(false), _id(0) { _ptrEnable = const_cast<bool*> (&_enable); };
-    virtual void operator()( osg::RenderInfo& renderInfo ) const
+
+    struct PassHeightMapToTritonCallback : public osg::Camera::DrawCallback
     {
-        if( _enable )
+        PassHeightMapToTritonCallback(TritonContext* triton) : _TRITON(triton), _enable(false), _id(0) { _ptrEnable = const_cast<bool*> (&_enable); };
+        virtual void operator()( osg::RenderInfo& renderInfo ) const
         {
-            *_ptrEnable = false; // cannot directly change _enable because of const
+            if( _enable )
+            {
+                *_ptrEnable = false; // cannot directly change _enable because of const
 
-            //osg::notify( osg::ALWAYS ) << "passing heightmap to Triton" << std::endl;
-            if(_TRITON->ready())
-                _TRITON->getEnvironment()->SetHeightMap((Triton::TextureHandle)_id,_heightMapMatrix);
+                //osg::notify( osg::ALWAYS ) << "passing heightmap to Triton" << std::endl;
+                if(_TRITON->ready())
+                    _TRITON->getEnvironment()->SetHeightMap((::Triton::TextureHandle)_id,_heightMapMatrix);
+            }
         }
-    }
 
-    unsigned int _frameNumber;
-    bool _enable;
-    bool* _ptrEnable;
-    int _id;
-    Triton::Matrix4 _heightMapMatrix;
-private:
-    osg::observer_ptr<TritonContext>  _TRITON;
-};
+        unsigned int _frameNumber;
+        bool _enable;
+        bool* _ptrEnable;
+        int _id;
+        ::Triton::Matrix4 _heightMapMatrix;
+
+    private:
+        osg::observer_ptr<TritonContext>  _TRITON;
+    };
 
-class OceanTerrainChangedCallback : public osgEarth::TerrainCallback
-{
-public:
-    OceanTerrainChangedCallback(TritonContext* triton, osgEarth::MapNode* mapNode, osg::Camera* heightCam, osg::Texture2D* heightMap )
-      : _TRITON(triton),
-        _mapNode(mapNode),
-        _heightCam(heightCam),
-        _heightMap(heightMap)
-    {
-    }
 
-    virtual void onTileAdded(const osgEarth::TileKey& tileKey, osg::Node* terrain, osgEarth::TerrainCallbackContext& context)
+    class OceanTerrainChangedCallback : public osgEarth::TerrainCallback
     {
-        if ( !_TRITON->ready() )
-            return;
-
-        osg::Vec3d eye, center, up;
-        _viewMatrix.getLookAt(eye, center, up);
-        double fovyDEG=0.0, aspectRatio=0.0, zNear=0.0, zFar=0.0;
-        _projectionMatrix.getPerspective(fovyDEG, aspectRatio,zNear,zFar);
-
-        // aspect_ratio = tan( HFOV/2 ) / tan( VFOV/2 )
-        // tan( HFOV/2 ) = tan( VFOV/2 ) * aspect_ratio
-        // HFOV/2 = atan( tan( VFOV/2 ) * aspect_ratio )
-        // HFOV = 2.0 * atan( tan( VFOV/2 ) * aspect_ratio )
-        double fovxDEG = osg::RadiansToDegrees( 2.0 * atan( tan(osg::DegreesToRadians(fovyDEG))/2.0 * aspectRatio ));
-
-        double eyeLat=0.0, eyeLon=0.0, eyeHeight=0.0;
-        _mapNode->getMap()->getSRS()->getEllipsoid()->convertXYZToLatLongHeight(eye.x(), eye.y(), eye.z(), eyeLat, eyeLon, eyeHeight);
-        double clampedEyeX=0.0, clampedEyeY=0.0,clampedEyeZ=0.0;
-        _mapNode->getMap()->getSRS()->getEllipsoid()->convertLatLongHeightToXYZ(eyeLat, eyeLon, 0.0, clampedEyeX, clampedEyeY, clampedEyeZ);
-        osg::Vec3 mslEye(clampedEyeX,clampedEyeY,clampedEyeZ);
-        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
-        double eyeLen = eye.length();
-        double radE = mslEye.length();
-        double hmax = radE + 8848.0;
-        double hmin = radE - 12262.0;
-        double hasl = osg::maximum(0.1, eyeLen - radE);
-        double radius = eyeLen - hasl;
-        double horizonDistance = osg::minimum(radE, sqrt( 2.0*radius*hasl + hasl*hasl ));
-
-        osg::Vec3d heightCamEye(eye);
-
-        double near = osg::maximum(1.0, heightCamEye.length() - hmax);
-        double far = osg::maximum(10.0, heightCamEye.length() - hmin + radE);
-        //osg::notify( osg::ALWAYS ) << "near = " << near << "; far = " << far << std::endl;
-
-        _heightCam->setProjectionMatrix(osg::Matrix::ortho(-horizonDistance,horizonDistance,-horizonDistance,horizonDistance,near,far) );
-        _heightCam->setViewMatrixAsLookAt( heightCamEye, osg::Vec3d(0.0,0.0,0.0), osg::Vec3d(0.0,0.0,1.0));
-
-        const osg::Matrixd bias(0.5, 0.0, 0.0, 0.0,
-                                0.0, 0.5, 0.0, 0.0,
-                                0.0, 0.0, 0.5, 0.0,
-                                0.5, 0.5, 0.5, 1.0);
-
-        osg::Matrix hMM = _heightCam->getViewMatrix() * _heightCam->getProjectionMatrix() * bias;
-        Triton::Matrix4 heightMapMatrix(hMM(0,0),hMM(0,1),hMM(0,2),hMM(0,3),
-                                        hMM(1,0),hMM(1,1),hMM(1,2),hMM(1,3),
-                                        hMM(2,0),hMM(2,1),hMM(2,2),hMM(2,3),
-                                        hMM(3,0),hMM(3,1),hMM(3,2),hMM(3,3));
-
-        //unsigned int contextID = _viewer->getCamera()->getGraphicsContext()->getState()->getContextID();
-        _texObj = _heightMap->getTextureObject(_contextID);
-        //osg::notify( osg::ALWAYS ) << "_contextID " << _contextID << std::endl;
-
-        if(_texObj)
+    public:
+        OceanTerrainChangedCallback(TritonContext* triton, osgEarth::MapNode* mapNode, osg::Camera* heightCam, osg::Texture2D* heightMap )
+          : _TRITON(triton),
+            _mapNode(mapNode),
+            _heightCam(heightCam),
+            _heightMap(heightMap)
+        {
+        }
+
+        virtual void onTileAdded(const osgEarth::TileKey& tileKey, osg::Node* terrain, osgEarth::TerrainCallbackContext& context)
         {
-            PassHeightMapToTritonCallback* cb = dynamic_cast<PassHeightMapToTritonCallback*>(_heightCam->getFinalDrawCallback());
-            if( cb )
+            if ( !_TRITON->ready() )
+                return;
+
+            osg::Vec3d eye, center, up;
+            _viewMatrix.getLookAt(eye, center, up);
+            double fovyDEG=0.0, aspectRatio=0.0, zNear=0.0, zFar=0.0;
+            _projectionMatrix.getPerspective(fovyDEG, aspectRatio,zNear,zFar);
+
+            // aspect_ratio = tan( HFOV/2 ) / tan( VFOV/2 )
+            // tan( HFOV/2 ) = tan( VFOV/2 ) * aspect_ratio
+            // HFOV/2 = atan( tan( VFOV/2 ) * aspect_ratio )
+            // HFOV = 2.0 * atan( tan( VFOV/2 ) * aspect_ratio )
+            double fovxDEG = osg::RadiansToDegrees( 2.0 * atan( tan(osg::DegreesToRadians(fovyDEG))/2.0 * aspectRatio ));
+
+            double eyeLat=0.0, eyeLon=0.0, eyeHeight=0.0;
+            _mapNode->getMap()->getSRS()->getEllipsoid()->convertXYZToLatLongHeight(eye.x(), eye.y(), eye.z(), eyeLat, eyeLon, eyeHeight);
+            double clampedEyeX=0.0, clampedEyeY=0.0,clampedEyeZ=0.0;
+            _mapNode->getMap()->getSRS()->getEllipsoid()->convertLatLongHeightToXYZ(eyeLat, eyeLon, 0.0, clampedEyeX, clampedEyeY, clampedEyeZ);
+            osg::Vec3 mslEye(clampedEyeX,clampedEyeY,clampedEyeZ);
+            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
+            double eyeLen = eye.length();
+            double radE = mslEye.length();
+            double hmax = radE + 8848.0;
+            double hmin = radE - 12262.0;
+            double hasl = osg::maximum(0.1, eyeLen - radE);
+            double radius = eyeLen - hasl;
+            double horizonDistance = osg::minimum(radE, sqrt( 2.0*radius*hasl + hasl*hasl ));
+
+            osg::Vec3d heightCamEye(eye);
+
+            double near = osg::maximum(1.0, heightCamEye.length() - hmax);
+            double far = osg::maximum(10.0, heightCamEye.length() - hmin + radE);
+            //osg::notify( osg::ALWAYS ) << "near = " << near << "; far = " << far << std::endl;
+
+            _heightCam->setProjectionMatrix(osg::Matrix::ortho(-horizonDistance,horizonDistance,-horizonDistance,horizonDistance,near,far) );
+            _heightCam->setViewMatrixAsLookAt( heightCamEye, osg::Vec3d(0.0,0.0,0.0), osg::Vec3d(0.0,0.0,1.0));
+
+            const osg::Matrixd bias(0.5, 0.0, 0.0, 0.0,
+                                    0.0, 0.5, 0.0, 0.0,
+                                    0.0, 0.0, 0.5, 0.0,
+                                    0.5, 0.5, 0.5, 1.0);
+
+            osg::Matrix hMM = _heightCam->getViewMatrix() * _heightCam->getProjectionMatrix() * bias;
+            ::Triton::Matrix4 heightMapMatrix(hMM(0,0),hMM(0,1),hMM(0,2),hMM(0,3),
+                                              hMM(1,0),hMM(1,1),hMM(1,2),hMM(1,3),
+                                              hMM(2,0),hMM(2,1),hMM(2,2),hMM(2,3),
+                                              hMM(3,0),hMM(3,1),hMM(3,2),hMM(3,3));
+
+            //unsigned int contextID = _viewer->getCamera()->getGraphicsContext()->getState()->getContextID();
+            _texObj = _heightMap->getTextureObject(_contextID);
+            //osg::notify( osg::ALWAYS ) << "_contextID " << _contextID << std::endl;
+
+            if(_texObj)
             {
-                cb->_enable = true;
-                cb->_id = _texObj->id();
-                cb->_heightMapMatrix = heightMapMatrix;
+                PassHeightMapToTritonCallback* cb = dynamic_cast<PassHeightMapToTritonCallback*>(_heightCam->getFinalDrawCallback());
+                if( cb )
+                {
+                    cb->_enable = true;
+                    cb->_id = _texObj->id();
+                    cb->_heightMapMatrix = heightMapMatrix;
+                }
             }
-        }
-#ifdef DEBUG_HEIGHTMAP
-        _mapNode->getParent(0)->removeChild(0 ,1);
-        _mapNode->getParent(0)->insertChild(0, makeFrustumFromCamera(_heightCam));
-#endif /* DEBUG_HEIGHTMAP */
+    #ifdef DEBUG_HEIGHTMAP
+            _mapNode->getParent(0)->removeChild(0 ,1);
+            _mapNode->getParent(0)->insertChild(0, makeFrustumFromCamera(_heightCam));
+    #endif /* DEBUG_HEIGHTMAP */
 
-    }
+        }
 
-    void setViewMatrix(const osg::Matrix& viewMatrix) { _viewMatrix = viewMatrix; };
-    void setProjectionMatrix(const osg::Matrix& projectionMatrix) { _projectionMatrix = projectionMatrix; };
-    void setContextID(unsigned int contextID) {_contextID = contextID;}
+        void setViewMatrix(const osg::Matrix& viewMatrix) { _viewMatrix = viewMatrix; };
+        void setProjectionMatrix(const osg::Matrix& projectionMatrix) { _projectionMatrix = projectionMatrix; };
+        void setContextID(unsigned int contextID) {_contextID = contextID;}
 
-private:
-    osg::observer_ptr<TritonContext> _TRITON;
-    osg::observer_ptr<osgEarth::MapNode> _mapNode;
-    osg::Camera* _heightCam;
-    osg::Texture2D* _heightMap;
-    osg::Texture::TextureObject* _texObj;
-    osg::Matrix _viewMatrix, _projectionMatrix;
-    unsigned int _contextID;
-};
+    private:
+        osg::observer_ptr<TritonContext> _TRITON;
+        osg::observer_ptr<osgEarth::MapNode> _mapNode;
+        osg::Camera* _heightCam;
+        osg::Texture2D* _heightMap;
+        osg::Texture::TextureObject* _texObj;
+        osg::Matrix _viewMatrix, _projectionMatrix;
+        unsigned int _contextID;
+    };
 
 
-const char* vertexShader =
-    "#version " GLSL_VERSION_STR "\n"
-    GLSL_DEFAULT_PRECISION_FLOAT "\n"
+    const char* vertexShader =
+        "#version " GLSL_VERSION_STR "\n"
+        GLSL_DEFAULT_PRECISION_FLOAT "\n"
 
-    "attribute vec4  oe_terrain_attr; \n" // osgearth_elevData //oe_terrain_attr
-    "varying float height;\n"
+        "attribute vec4 oe_terrain_attr; \n"
+        "varying float oe_triton_height;\n"
 
-    "void setupContour(inout vec4 VertexModel) \n"
-    "{ \n"
-    "    height = oe_terrain_attr[3]; \n"
-    "} \n";
+        "void setupContour(inout vec4 VertexModel) \n"
+        "{ \n"
+        "    oe_triton_height = oe_terrain_attr[3]; \n"
+        "} \n";
 
-// The fragment shader simply takes the texture index that we generated
-// in the vertex shader and does a texture lookup. In this case we're
-// just wholesale replacing the color, so if the map had any existing
-// imagery, this will overwrite it.
+    // The fragment shader simply takes the texture index that we generated
+    // in the vertex shader and does a texture lookup. In this case we're
+    // just wholesale replacing the color, so if the map had any existing
+    // imagery, this will overwrite it.
 
-const char* fragmentShader =
-    "#version " GLSL_VERSION_STR "\n"
-    GLSL_DEFAULT_PRECISION_FLOAT "\n"
+    const char* fragmentShader =
+        "#version " GLSL_VERSION_STR "\n"
+        GLSL_DEFAULT_PRECISION_FLOAT "\n"
 
-    "varying float height;\n"
+        "varying float oe_triton_height;\n"
 
-    "void colorContour( inout vec4 color ) \n"
-    "{ \n"
+        "void colorContour( inout vec4 color ) \n"
+        "{ \n"
 #ifdef DEBUG_HEIGHTMAP
-      // Map to black = -500m, white = +500m
-      "   float nHeight = clamp(height / 1000.0 + 0.5, 0.0, 1.0);\n"
+          // Map to black = -500m, white = +500m
+          "   float nHeight = clamp(oe_triton_height / 1000.0 + 0.5, 0.0, 1.0);\n"
 #else
-      "   float nHeight = height;\n"
+          "   float nHeight = oe_triton_height;\n"
 #endif
-    "    color = vec4( nHeight, 0.0, 0.0, 1.0 ); \n"
-    "} \n";
+        "    color = vec4( nHeight, 0.0, 0.0, 1.0 ); \n"
+        "} \n";
+}
+
 
 TritonDrawable::TritonDrawable(osgEarth::MapNode* mapNode, TritonContext* TRITON) :
 _TRITON(TRITON),
@@ -362,40 +372,55 @@ _mapNode(mapNode)
     setDataVariance( osg::Object::DYNAMIC );
 
     this->getOrCreateStateSet()->setRenderingHint( osg::StateSet::TRANSPARENT_BIN );
-    //this->getOrCreateStateSet()->setRenderBinDetails( 97, "RenderBin" );
+    this->getOrCreateStateSet()->setRenderBinDetails( 97, "RenderBin" );
 }
 
 void
 TritonDrawable::drawImplementation(osg::RenderInfo& renderInfo) const
 {
-    osg::State& state = *renderInfo.getState();
-    
-    state.disableAllVertexArrays();
+    osg::State* state = renderInfo.getState();
+
+    state->disableAllVertexArrays();
 
     _TRITON->initialize( renderInfo );
     if ( !_TRITON->ready() )
         return;
 
-    if(!_terrainChangedCallback.valid())
-        const_cast< TritonDrawable *>( this )->setupHeightMap(_mapNode.get());;
+#ifdef USE_HEIGHT_MAP
+    if( !_terrainChangedCallback.valid())
+        const_cast< TritonDrawable *>( this )->setupHeightMap(_mapNode.get());
+#endif
 
     ::Triton::Environment* environment = _TRITON->getEnvironment();
 
+    osgEarth::NativeProgramAdapterCollection& adapters = _adapters[ state->getContextID() ];
+    if ( adapters.empty() )
+    {
+        adapters.push_back( new osgEarth::NativeProgramAdapter(state, (GLint)_TRITON->getOcean()->GetShaderObject(::Triton::GOD_RAYS)) );
+        adapters.push_back( new osgEarth::NativeProgramAdapter(state, (GLint)_TRITON->getOcean()->GetShaderObject(::Triton::SPRAY_PARTICLES)) );
+        adapters.push_back( new osgEarth::NativeProgramAdapter(state, (GLint)_TRITON->getOcean()->GetShaderObject(::Triton::WAKE_SPRAY_PARTICLES)) );
+        adapters.push_back( new osgEarth::NativeProgramAdapter(state, (GLint)_TRITON->getOcean()->GetShaderObject(::Triton::WATER_DECALS)) );
+        adapters.push_back( new osgEarth::NativeProgramAdapter(state, (GLint)_TRITON->getOcean()->GetShaderObject(::Triton::WATER_SURFACE)) );
+        adapters.push_back( new osgEarth::NativeProgramAdapter(state, (GLint)_TRITON->getOcean()->GetShaderObject(::Triton::WATER_SURFACE_PATCH)) );
+    }
+    adapters.apply( state );
+
     // Pass the final view and projection matrices into Triton.
     if ( environment )
     {
-        environment->SetCameraMatrix( state.getModelViewMatrix().ptr() );
-        environment->SetProjectionMatrix( state.getProjectionMatrix().ptr() );
+        environment->SetCameraMatrix( state->getModelViewMatrix().ptr() );
+        environment->SetProjectionMatrix( state->getProjectionMatrix().ptr() );
     }
 
     if(_terrainChangedCallback.valid())
     {
-        _terrainChangedCallback->setViewMatrix( renderInfo.getView()->getCamera()->getViewMatrix() );
-        _terrainChangedCallback->setProjectionMatrix(renderInfo.getView()->getCamera()->getProjectionMatrix() );
-        _terrainChangedCallback->setContextID(renderInfo.getView()->getCamera()->getGraphicsContext()->getState()->getContextID() );
+        OceanTerrainChangedCallback* c = static_cast<OceanTerrainChangedCallback*>(_terrainChangedCallback.get());
+        c->setViewMatrix( renderInfo.getView()->getCamera()->getViewMatrix() );
+        c->setProjectionMatrix(renderInfo.getView()->getCamera()->getProjectionMatrix() );
+        c->setContextID(renderInfo.getView()->getCamera()->getGraphicsContext()->getState()->getContextID() );
     }
 
-    state.dirtyAllVertexArrays();
+    state->dirtyAllVertexArrays();
 
     // Now light and draw the ocean:
     if ( environment )
@@ -453,9 +478,8 @@ TritonDrawable::drawImplementation(osg::RenderInfo& renderInfo) const
         if ( _cubeMap.valid() )
         {
             environment->SetEnvironmentMap(
-                (::Triton::TextureHandle)_cubeMap->getTextureObject( state.getContextID() )->id(), transformFromYUpToZUpCubeMapCoords );
+                (::Triton::TextureHandle)_cubeMap->getTextureObject( state->getContextID() )->id(), transformFromYUpToZUpCubeMapCoords );
 
-#if 1
             if( _planarReflectionMap.valid() && _planarReflectionProjection.valid() )
             {
                 osg::Matrix & p = *_planarReflectionProjection;
@@ -465,10 +489,9 @@ TritonDrawable::drawImplementation(osg::RenderInfo& renderInfo) const
                                                     p(2,0), p(2,1), p(2,2) );
 
                 environment->SetPlanarReflectionMap( (::Triton::TextureHandle)
-                                                      _planarReflectionMap->getTextureObject( state.getContextID() )->id(),
+                                                      _planarReflectionMap->getTextureObject( state->getContextID() )->id(),
                                                       planarProjection, 0.125  );
             }
-#endif
         }
 
         // Draw the ocean for the current time sample
@@ -478,7 +501,7 @@ TritonDrawable::drawImplementation(osg::RenderInfo& renderInfo) const
         }
     }
 
-    state.dirtyAllVertexArrays();
+    state->dirtyAllVertexArrays();
 }
 
 void TritonDrawable::setupHeightMap(osgEarth::MapNode* mapNode)
@@ -502,21 +525,21 @@ void TritonDrawable::setupHeightMap(osgEarth::MapNode* mapNode)
     _heightCamera->setRenderOrder(osg::Camera::PRE_RENDER);
     _heightCamera->setRenderTargetImplementation( osg::Camera::FRAME_BUFFER_OBJECT );
     _heightCamera->attach(osg::Camera::COLOR_BUFFER, _heightMap);
-    _heightCamera->setCullMask( ~OCEAN_MASK );
+    _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 
+    // Install the shaders. We also bind osgEarth's elevation data attribute, which the
     // terrain engine automatically generates at the specified location.
     osgEarth::VirtualProgram* heightProgram = new osgEarth::VirtualProgram();
     heightProgram->setFunction( "setupContour", vertexShader,   osgEarth::ShaderComp::LOCATION_VERTEX_MODEL);
     heightProgram->setFunction( "colorContour", fragmentShader, osgEarth::ShaderComp::LOCATION_FRAGMENT_COLORING);//, -1.0 );
+    heightProgram->addBindAttribLocation( "oe_terrain_attr", osg::Drawable::ATTRIBUTE_6 );
 
     osg::StateSet *stateSet = _heightCamera->getOrCreateStateSet();
     stateSet->setAttributeAndModes(heightProgram, osg::StateAttribute::ON);// | osg::StateAttribute::OVERRIDE);
     stateSet->setMode(GL_LIGHTING, osg::StateAttribute::OFF);// | osg::StateAttribute::OVERRIDE);
 
-//    heightProgram->addBindAttribLocation( "oe_terrain_attr", osg::Drawable::ATTRIBUTE_6 );
 
     if( mapNode && _heightCamera )
     {
diff --git a/src/osgEarthDrivers/ocean_triton/TritonDriver.cpp b/src/osgEarthDrivers/ocean_triton/TritonDriver.cpp
index 7dd718b..6c3eaf2 100644
--- a/src/osgEarthDrivers/ocean_triton/TritonDriver.cpp
+++ b/src/osgEarthDrivers/ocean_triton/TritonDriver.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -30,14 +30,11 @@
 #undef  LC
 #define LC "[TritonDriver] "
 
-using namespace osgEarth;
-using namespace osgEarth::Util;
-
 //---------------------------------------------------------------------------
 
-namespace osgEarth { namespace Drivers { namespace Triton
+namespace osgEarth { namespace Triton
 {
-    class TritonDriver : public OceanDriver
+    class TritonDriver : public osgEarth::Util::OceanDriver
     {
     public:
         TritonDriver()
@@ -83,7 +80,7 @@ namespace osgEarth { namespace Drivers { namespace Triton
                 }
             }
 
-            MapNode* mapNode = getMapNode(options);
+            osgEarth::MapNode* mapNode = getMapNode(options);
             return new TritonNode( mapNode, tritonOptions );
         }
 
@@ -93,4 +90,4 @@ namespace osgEarth { namespace Drivers { namespace Triton
 
     REGISTER_OSGPLUGIN(osgearth_ocean_triton, TritonDriver)
 
-} } } // namespace osgEarth::Drivers::Triton
+} } // namespace osgEarth::Triton
diff --git a/src/osgEarthDrivers/ocean_triton/TritonNode b/src/osgEarthDrivers/ocean_triton/TritonNode
index 5a4c249..3c662c3 100644
--- a/src/osgEarthDrivers/ocean_triton/TritonNode
+++ b/src/osgEarthDrivers/ocean_triton/TritonNode
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -16,29 +16,26 @@
  * 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_TRITON_TRITON_NODE
-#define OSGEARTH_DRIVER_TRITON_TRITON_NODE 1
+#ifndef OSGEARTH_TRITON_NODE
+#define OSGEARTH_TRITON_NODE 1
 
 #include "TritonOptions"
 #include <osgEarthUtil/Ocean>
 #include <osgEarth/MapNode>
 #include <osg/Drawable>
 
-namespace osgEarth { namespace Drivers { namespace Triton
+namespace osgEarth { namespace Triton
 {
     class TritonContext;
 
-    using namespace osgEarth;
-    using namespace osgEarth::Util;
-
     /**
      * Node that roots the Triton adapter.
      */
-    class TritonNode : public OceanNode
+    class TritonNode : public osgEarth::Util::OceanNode
     {
     public:
         TritonNode(
-            MapNode*           mapNode,
+            osgEarth::MapNode*   mapNode,
             const TritonOptions& options );
 
     protected: // OceanNode
@@ -59,6 +56,6 @@ namespace osgEarth { namespace Drivers { namespace Triton
         osg::Drawable*              _drawable;
     };
 
-} } } // namespace osgEarth::Drivers::Triton
+} } // namespace osgEarth::Triton
 
-#endif // OSGEARTH_DRIVER_TRITON_TRITON_NODE
+#endif // OSGEARTH_TRITON_NODE
diff --git a/src/osgEarthDrivers/ocean_triton/TritonNode.cpp b/src/osgEarthDrivers/ocean_triton/TritonNode.cpp
index c7df5dd..99d38a7 100644
--- a/src/osgEarthDrivers/ocean_triton/TritonNode.cpp
+++ b/src/osgEarthDrivers/ocean_triton/TritonNode.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -17,24 +17,22 @@
  * 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 <Triton.h>
 
 #define LC "[TritonNode] "
 
-using namespace osgEarth;
-using namespace osgEarth::Util;
-using namespace osgEarth::Drivers::Triton;
+using namespace osgEarth::Triton;
 
-TritonNode::TritonNode(MapNode*           mapNode,
+TritonNode::TritonNode(osgEarth::MapNode*   mapNode,
                        const TritonOptions& options) :
 OceanNode( options ),
 _options ( options )
 {
-    const Map* map = mapNode->getMap();
+    const osgEarth::Map* map = mapNode->getMap();
     if ( map )
         setSRS( map->getSRS() );
 
@@ -47,7 +45,7 @@ _options ( options )
     _drawable = tritonDrawable;
     osg::Geode* geode = new osg::Geode();
     geode->addDrawable( _drawable );
-    geode->setNodeMask( OCEAN_MASK );
+    geode->setNodeMask( TRITON_OCEAN_MASK );
 
     this->addChild( geode );
 
@@ -82,5 +80,5 @@ TritonNode::traverse(osg::NodeVisitor& nv)
     {
         _TRITON->update(nv.getFrameStamp()->getSimulationTime());
     }
-    OceanNode::traverse(nv);
+    osgEarth::Util::OceanNode::traverse(nv);
 }
diff --git a/src/osgEarthDrivers/ocean_triton/TritonOptions b/src/osgEarthDrivers/ocean_triton/TritonOptions
index 7ac3342..68d0531 100644
--- a/src/osgEarthDrivers/ocean_triton/TritonOptions
+++ b/src/osgEarthDrivers/ocean_triton/TritonOptions
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -16,26 +16,21 @@
  * 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_TRITON_TRITON_OPTIONS
-#define OSGEARTH_DRIVER_TRITON_TRITON_OPTIONS 1
+#ifndef OSGEARTH_TRITON_OPTIONS
+#define OSGEARTH_TRITON_OPTIONS 1
 
 #include <osgEarthUtil/Ocean>
 
-namespace osgEarth { namespace Drivers { namespace Triton
+namespace osgEarth { namespace Triton
 {
-    using namespace osgEarth;
-    using namespace osgEarth::Util;
-
     /**
      * Options for controlling the ocean surface node.
      */
-    class /*header-only*/ TritonOptions : public OceanOptions
+    class /*header-only*/ TritonOptions : public osgEarth::Util::OceanOptions
     {
     public:
-
-    public:
-        TritonOptions(const OceanOptions& conf =OceanOptions()) :
-          OceanOptions( conf )
+        TritonOptions(const osgEarth::Util::OceanOptions& conf = osgEarth::Util::OceanOptions()) :
+            osgEarth::Util::OceanOptions( conf )
         {
             setDriver( "triton" );
             fromConfig( _conf );
@@ -44,20 +39,20 @@ namespace osgEarth { namespace Drivers { namespace Triton
         virtual ~TritonOptions() { }
 
         /* User name for license activation */
-        optional<std::string>& user() { return _user; }
-        const optional<std::string>& user() const { return _user; }
+        osgEarth::optional<std::string>& user() { return _user; }
+        const osgEarth::optional<std::string>& user() const { return _user; }
 
         /* License code string */
-        optional<std::string>& licenseCode() { return _licenseCode; }
-        const optional<std::string>& licenseCode() const { return _licenseCode; }
+        osgEarth::optional<std::string>& licenseCode() { return _licenseCode; }
+        const osgEarth::optional<std::string>& licenseCode() const { return _licenseCode; }
 
         /* SilverLining resource path */
-        optional<std::string>& resourcePath() { return _resourcePath; }
-        const optional<std::string>& resourcePath() const { return _resourcePath; }
+        osgEarth::optional<std::string>& resourcePath() { return _resourcePath; }
+        const osgEarth::optional<std::string>& resourcePath() const { return _resourcePath; }
 
     public:
-        Config getConfig() const {
-            Config conf = OceanOptions::newConfig();
+        osgEarth::Config getConfig() const {
+            osgEarth::Config conf = osgEarth::Util::OceanOptions::newConfig();
             conf.addIfSet("user", _user);
             conf.addIfSet("license_code", _licenseCode);
             conf.addIfSet("resource_path", _resourcePath);
@@ -65,24 +60,24 @@ namespace osgEarth { namespace Drivers { namespace Triton
         }
 
     protected:
-        void mergeConfig( const Config& conf ) {
-            OceanOptions::mergeConfig( conf );
+        void mergeConfig( const osgEarth::Config& conf ) {
+            osgEarth::Util::OceanOptions::mergeConfig( conf );
             fromConfig( conf );
         }
 
     private:
-        void fromConfig( const Config& conf ) {
+        void fromConfig( const osgEarth::Config& conf ) {
             conf.getIfSet("user", _user);
             conf.getIfSet("license_code", _licenseCode);
             conf.getIfSet("resource_path", _resourcePath);
         }
 
     private:
-        optional<std::string> _user;
-        optional<std::string> _licenseCode;
-        optional<std::string> _resourcePath;
+        osgEarth::optional<std::string> _user;
+        osgEarth::optional<std::string> _licenseCode;
+        osgEarth::optional<std::string> _resourcePath;
     };
 
-} } } // namespace osgEarth::Drivers::Triton
+} } // namespace osgEarth::Triton
 
-#endif // OSGEARTH_DRIVER_TRITON_TRITON_OPTIONS
+#endif // OSGEARTH_TRITON_OPTIONS
diff --git a/src/osgEarthDrivers/osg/OSGOptions b/src/osgEarthDrivers/osg/OSGOptions
index c1ef980..9dd2846 100644
--- a/src/osgEarthDrivers/osg/OSGOptions
+++ b/src/osgEarthDrivers/osg/OSGOptions
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/osg/OSGTileSource.cpp b/src/osgEarthDrivers/osg/OSGTileSource.cpp
index 704f329..3f176ab 100644
--- a/src/osgEarthDrivers/osg/OSGTileSource.cpp
+++ b/src/osgEarthDrivers/osg/OSGTileSource.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/quadkey/CMakeLists.txt b/src/osgEarthDrivers/quadkey/CMakeLists.txt
new file mode 100644
index 0000000..ed17f70
--- /dev/null
+++ b/src/osgEarthDrivers/quadkey/CMakeLists.txt
@@ -0,0 +1,15 @@
+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
new file mode 100644
index 0000000..fdf3879
--- /dev/null
+++ b/src/osgEarthDrivers/quadkey/QuadKeyOptions
@@ -0,0 +1,82 @@
+/* -*-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.
+ *
+ * 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
new file mode 100644
index 0000000..8f8d910
--- /dev/null
+++ b/src/osgEarthDrivers/quadkey/ReaderWriterQuadKey.cpp
@@ -0,0 +1,199 @@
+/* -*-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/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)
+    {
+        //nop
+    }
+
+
+    Status initialize(const osgDB::Options* dbOptions)
+    {
+        _dbOptions = Registry::instance()->cloneOrCreateOptions(dbOptions);        
+
+        URI uri = _options.url().value();
+        if ( uri.empty() )
+        {
+            return Status::Error( "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()
+    {
+        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/ReaderWriterRefresh.cpp b/src/osgEarthDrivers/refresh/ReaderWriterRefresh.cpp
index 7db40ae..f7ae050 100644
--- a/src/osgEarthDrivers/refresh/ReaderWriterRefresh.cpp
+++ b/src/osgEarthDrivers/refresh/ReaderWriterRefresh.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/osgEarthDrivers/refresh/RefreshOptions b/src/osgEarthDrivers/refresh/RefreshOptions
index 7f97cbb..ec33e90 100644
--- a/src/osgEarthDrivers/refresh/RefreshOptions
+++ b/src/osgEarthDrivers/refresh/RefreshOptions
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/script_engine_duktape/CMakeLists.txt b/src/osgEarthDrivers/script_engine_duktape/CMakeLists.txt
index eaeaf43..72d853d 100644
--- a/src/osgEarthDrivers/script_engine_duktape/CMakeLists.txt
+++ b/src/osgEarthDrivers/script_engine_duktape/CMakeLists.txt
@@ -1,3 +1,4 @@
+IF(NOT WITH_EXTERNAL_DUKTAPE OR DUKTAPE_FOUND)
 
 IF(WITH_EXTERNAL_DUKTAPE)
 
@@ -40,3 +41,5 @@ SET(LIB_NAME scriptengine_javascript)
 SET(LIB_PUBLIC_HEADERS ${TARGET_H} )
 INCLUDE(ModuleInstallOsgEarthDriverIncludes OPTIONAL)
 
+ENDIF(NOT WITH_EXTERNAL_DUKTAPE OR DUKTAPE_FOUND)
+
diff --git a/src/osgEarthDrivers/script_engine_duktape/DuktapeEngine b/src/osgEarthDrivers/script_engine_duktape/DuktapeEngine
index 0c8b113..e663089 100644
--- a/src/osgEarthDrivers/script_engine_duktape/DuktapeEngine
+++ b/src/osgEarthDrivers/script_engine_duktape/DuktapeEngine
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -23,7 +23,7 @@
 #include <osgEarthFeatures/ScriptEngine>
 #include <osgEarthFeatures/Script>
 #include <osgEarthFeatures/Feature>
-#include <osgEarth/ThreadingUtils>
+#include <osgEarth/Containers>
 #include "duktape.h"
 
 namespace osgEarth { namespace Drivers { namespace Duktape
@@ -62,7 +62,7 @@ namespace osgEarth { namespace Drivers { namespace Duktape
             duk_context* _ctx;
         };
 
-        Threading::PerThread<Context> _contexts;
+        PerThread<Context> _contexts;
 
         const ScriptEngineOptions _options;
     };
diff --git a/src/osgEarthDrivers/script_engine_duktape/DuktapeEngine.cpp b/src/osgEarthDrivers/script_engine_duktape/DuktapeEngine.cpp
index 265669e..804747c 100644
--- a/src/osgEarthDrivers/script_engine_duktape/DuktapeEngine.cpp
+++ b/src/osgEarthDrivers/script_engine_duktape/DuktapeEngine.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/script_engine_duktape/JSGeometry b/src/osgEarthDrivers/script_engine_duktape/JSGeometry
index fbf52db..95f5fdc 100644
--- a/src/osgEarthDrivers/script_engine_duktape/JSGeometry
+++ b/src/osgEarthDrivers/script_engine_duktape/JSGeometry
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/script_engine_duktape/Plugin.cpp b/src/osgEarthDrivers/script_engine_duktape/Plugin.cpp
index 417f3d8..49217be 100644
--- a/src/osgEarthDrivers/script_engine_duktape/Plugin.cpp
+++ b/src/osgEarthDrivers/script_engine_duktape/Plugin.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/script_engine_duktape/duktape.h b/src/osgEarthDrivers/script_engine_duktape/duktape.h
index 717319e..5151736 100644
--- a/src/osgEarthDrivers/script_engine_duktape/duktape.h
+++ b/src/osgEarthDrivers/script_engine_duktape/duktape.h
@@ -200,7 +200,8 @@ static __inline__ unsigned long long duk_rdtsc(void) {
 #if defined(i386) || defined(__i386) || defined(__i386__) || \
     defined(__i486__) || defined(__i586__) || defined(__i686__) || \
     defined(__IA32__) || defined(_M_IX86) || defined(__X86__) || \
-    defined(_X86_) || defined(__THW_INTEL__) || defined(__I86__)
+    defined(_X86_) || defined(__THW_INTEL__) || defined(__I86__) || \
+    defined(__ANDROID__)
 #define DUK_F_X86
 #endif
 
diff --git a/src/osgEarthDrivers/script_engine_javascriptcore/CMakeLists.txt b/src/osgEarthDrivers/script_engine_javascriptcore/CMakeLists.txt
index a7b8cd0..534b5c4 100644
--- a/src/osgEarthDrivers/script_engine_javascriptcore/CMakeLists.txt
+++ b/src/osgEarthDrivers/script_engine_javascriptcore/CMakeLists.txt
@@ -1,3 +1,4 @@
+IF(JAVASCRIPTCORE_FOUND)
 
 INCLUDE_DIRECTORIES( ${JAVASCRIPTCORE_INCLUDE_DIR} )
 
@@ -21,3 +22,5 @@ SET(LIB_NAME scriptengine_javascriptcore)
 SET(LIB_PUBLIC_HEADERS ${TARGET_H} )
 INCLUDE(ModuleInstallOsgEarthDriverIncludes OPTIONAL)
 
+ENDIF(JAVASCRIPTCORE_FOUND)
+
diff --git a/src/osgEarthDrivers/script_engine_v8/CMakeLists.txt b/src/osgEarthDrivers/script_engine_v8/CMakeLists.txt
index 1fa22a0..72d611c 100644
--- a/src/osgEarthDrivers/script_engine_v8/CMakeLists.txt
+++ b/src/osgEarthDrivers/script_engine_v8/CMakeLists.txt
@@ -1,3 +1,4 @@
+IF(V8_FOUND AND USE_V8)
 
 INCLUDE_DIRECTORIES( ${V8_INCLUDE_DIR} )
 
@@ -24,3 +25,5 @@ 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/sky_gl/GLSkyDriver.cpp b/src/osgEarthDrivers/sky_gl/GLSkyDriver.cpp
index 6915bbc..6459641 100644
--- a/src/osgEarthDrivers/sky_gl/GLSkyDriver.cpp
+++ b/src/osgEarthDrivers/sky_gl/GLSkyDriver.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/sky_gl/GLSkyNode b/src/osgEarthDrivers/sky_gl/GLSkyNode
index da00fba..5a8c128 100644
--- a/src/osgEarthDrivers/sky_gl/GLSkyNode
+++ b/src/osgEarthDrivers/sky_gl/GLSkyNode
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -56,6 +56,7 @@ namespace osgEarth { namespace Drivers { namespace GLSky
         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 10618a1..c54e99e 100644
--- a/src/osgEarthDrivers/sky_gl/GLSkyNode.cpp
+++ b/src/osgEarthDrivers/sky_gl/GLSkyNode.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
@@ -134,6 +137,13 @@ 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;
diff --git a/src/osgEarthDrivers/sky_gl/GLSkyOptions b/src/osgEarthDrivers/sky_gl/GLSkyOptions
index cab3f35..67a9f6b 100644
--- a/src/osgEarthDrivers/sky_gl/GLSkyOptions
+++ b/src/osgEarthDrivers/sky_gl/GLSkyOptions
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/sky_gl/GLSkyShaders b/src/osgEarthDrivers/sky_gl/GLSkyShaders
index ca2316b..92ceade 100644
--- a/src/osgEarthDrivers/sky_gl/GLSkyShaders
+++ b/src/osgEarthDrivers/sky_gl/GLSkyShaders
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -31,13 +31,14 @@ namespace osgEarth { namespace Drivers { namespace GLSky
         "uniform bool oe_mode_GL_LIGHTING; \n"
         "varying vec4 oe_lighting_adjustment; \n"
         "varying vec4 oe_lighting_zero_vec; \n"
-        "varying vec3 oe_Normal; \n"
+
+        "vec3 oe_global_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 = oe_Normal; \n"
+        "    vec3 N = oe_global_Normal; \n"
         "    float NdotL = dot( N, normalize(gl_LightSource[0].position.xyz) ); \n"
         "    NdotL = max( 0.0, NdotL ); \n"
 
@@ -95,7 +96,8 @@ namespace osgEarth { namespace Drivers { namespace GLSky
 
         "uniform bool oe_mode_GL_LIGHTING; \n"
         "varying vec3 oe_glsky_vertexView3; \n"
-        "varying vec3 oe_Normal; \n"
+        
+        "vec3 oe_global_Normal; \n"
 
         "void oe_sky_fragment_main(inout vec4 color) \n"
         "{ \n"        
@@ -103,7 +105,7 @@ namespace osgEarth { namespace Drivers { namespace GLSky
 
         "    vec3 L = normalize(gl_LightSource[0].position.xyz); \n"
         "    vec3 V = normalize(oe_glsky_vertexView3); \n"
-        "    vec3 N = normalize(oe_Normal); \n"
+        "    vec3 N = normalize(oe_global_Normal); \n"
         "    vec3 R = normalize(-reflect(L,N)); \n"
 
         "    float NdotL = max(dot(N,L), 0.0); \n"
diff --git a/src/osgEarthDrivers/sky_silverlining/CMakeLists.txt b/src/osgEarthDrivers/sky_silverlining/CMakeLists.txt
index 0e25d0d..a8f27ee 100644
--- a/src/osgEarthDrivers/sky_silverlining/CMakeLists.txt
+++ b/src/osgEarthDrivers/sky_silverlining/CMakeLists.txt
@@ -1,3 +1,5 @@
+IF(SILVERLINING_FOUND)
+
 SET(TARGET_SRC 
     SilverLiningDriver.cpp
     SilverLiningNode.cpp
@@ -30,3 +32,5 @@ SET(LIB_NAME sky_silverlining)
 SET(LIB_PUBLIC_HEADERS SilverLiningOptions)
 
 INCLUDE(ModuleInstallOsgEarthDriverIncludes OPTIONAL)
+
+ENDIF(SILVERLINING_FOUND)
diff --git a/src/osgEarthDrivers/sky_silverlining/SilverLiningCloudsDrawable b/src/osgEarthDrivers/sky_silverlining/SilverLiningCloudsDrawable
index bf1fd9e..2ac1db3 100644
--- a/src/osgEarthDrivers/sky_silverlining/SilverLiningCloudsDrawable
+++ b/src/osgEarthDrivers/sky_silverlining/SilverLiningCloudsDrawable
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -19,13 +19,14 @@
 #include <osg/Drawable>
 #include <osg/RenderInfo>
 #include <osg/Version>
+#include <osgEarth/NativeProgramAdapter>
+#include <vector>
+#include <map>
 
-namespace osgEarth { namespace Drivers { namespace SilverLining
+namespace osgEarth { namespace SilverLining
 {
     class SilverLiningContext;
 
-    using namespace osgEarth;
-
     /**
      * Custom drawable for rendering the SilverLining clouds
      */
@@ -54,9 +55,10 @@ namespace osgEarth { namespace Drivers { namespace SilverLining
         virtual ~CloudsDrawable() { }
 
         osg::observer_ptr<SilverLiningContext> _SL;
-		bool _draw;
+
+        mutable osg::buffered_object<osgEarth::NativeProgramAdapterCollection> _adapters;
         
         CloudsDrawable(const CloudsDrawable& copy, const osg::CopyOp& op=osg::CopyOp::SHALLOW_COPY) { }
     };
 
-} } } // namespace osgEarth::Drivers::SilverLining
+} } // namespace osgEarth::SilverLining
diff --git a/src/osgEarthDrivers/sky_silverlining/SilverLiningCloudsDrawable.cpp b/src/osgEarthDrivers/sky_silverlining/SilverLiningCloudsDrawable.cpp
index 128342c..2556fc9 100644
--- a/src/osgEarthDrivers/sky_silverlining/SilverLiningCloudsDrawable.cpp
+++ b/src/osgEarthDrivers/sky_silverlining/SilverLiningCloudsDrawable.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -16,15 +16,16 @@
  * 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 <SilverLining.h>
 #include "SilverLiningCloudsDrawable"
 #include "SilverLiningContext"
 #include <osgEarth/SpatialReference>
-#include <SilverLining.h>
 
+#undef  LC
 #define LC "[SilverLining:SkyDrawable] "
 
-using namespace osgEarth;
-using namespace osgEarth::Drivers::SilverLining;
+using namespace osgEarth::SilverLining;
+
 
 CloudsDrawable::CloudsDrawable(SilverLiningContext* SL) :
 _SL( SL )
@@ -33,14 +34,31 @@ _SL( SL )
     setSupportsDisplayList( false );
     
     // not MT-safe (camera updates, etc)
-    this->setDataVariance(osg::Object::DYNAMIC);    
+    this->setDataVariance(osg::Object::DYNAMIC);
 }
 
 void
 CloudsDrawable::drawImplementation(osg::RenderInfo& renderInfo) const
 {
-    if(_SL->ready())
+    if( _SL->ready() )
     {
+        const osg::State* state = renderInfo.getState();
+
+        osgEarth::NativeProgramAdapterCollection& adapters = _adapters[ state->getContextID() ]; // thread safe.
+        if ( adapters.empty() )
+        {
+            adapters.push_back( new osgEarth::NativeProgramAdapter(state, _SL->getAtmosphere()->GetSkyShader()) );
+            adapters.push_back( new osgEarth::NativeProgramAdapter(state, _SL->getAtmosphere()->GetBillboardShader()) );
+            adapters.push_back( new osgEarth::NativeProgramAdapter(state, _SL->getAtmosphere()->GetStarShader()) );
+            adapters.push_back( new osgEarth::NativeProgramAdapter(state, _SL->getAtmosphere()->GetPrecipitationShader()) );
+
+            SL_VECTOR(unsigned) handles = _SL->getAtmosphere()->GetActivePlanarCloudShaders();
+            for(int i=0; i<handles.size(); ++i)          
+                adapters.push_back( new osgEarth::NativeProgramAdapter(state, handles[i]) );
+        }
+
+        adapters.apply( state );
+
         renderInfo.getState()->disableAllVertexArrays();
         _SL->getAtmosphere()->DrawObjects( true, true, true );
         renderInfo.getState()->dirtyAllVertexArrays();
diff --git a/src/osgEarthDrivers/sky_silverlining/SilverLiningContext b/src/osgEarthDrivers/sky_silverlining/SilverLiningContext
index fcc9603..2fffbfd 100644
--- a/src/osgEarthDrivers/sky_silverlining/SilverLiningContext
+++ b/src/osgEarthDrivers/sky_silverlining/SilverLiningContext
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -30,10 +30,8 @@ namespace osgEarth {
     class SpatialReference;
 }
 
-namespace osgEarth { namespace Drivers { namespace SilverLining
+namespace osgEarth { namespace SilverLining
 {
-    using namespace osgEarth;
-
     /**
      * Contains all the SilverLining SDK pointers.
      */
@@ -48,6 +46,9 @@ namespace osgEarth { namespace Drivers { namespace SilverLining
         /** Sets the spatial reference system of the map */
         void setSRS(const SpatialReference* srs);
 
+        /** Sets the minimum ambient lighting value */
+        void setMinimumAmbient(const osg::Vec4f& value);
+
     public: // accessors
 
         bool ready() const { return _initAttempted && !_initFailed; }
@@ -88,19 +89,20 @@ namespace osgEarth { namespace Drivers { namespace SilverLining
 
         double _skyBoxSize;
 
-        osg::observer_ptr<osg::Light>        _light;
-        osg::ref_ptr<const SpatialReference> _srs;
+        osg::observer_ptr<osg::Light>                  _light;
+        osg::ref_ptr<const osgEarth::SpatialReference> _srs;
 
-        bool             _initAttempted;
-        bool             _initFailed;
-        Threading::Mutex _initMutex;
+        bool                       _initAttempted;
+        bool                       _initFailed;
+        osgEarth::Threading::Mutex _initMutex;
 
         double _maxAmbientLightingAlt;
 
         osg::observer_ptr<osg::Camera> _camera;
         osg::Vec3d                     _cameraPos; // eye point
+        osg::Vec4f                     _minAmbient;
 
         SilverLiningOptions _options;
     };
 
-} } } // namespace osgEarth::Drivers::SilverLining
+} } // namespace osgEarth::SilverLining
diff --git a/src/osgEarthDrivers/sky_silverlining/SilverLiningContext.cpp b/src/osgEarthDrivers/sky_silverlining/SilverLiningContext.cpp
index 8fc0afd..33860f6 100644
--- a/src/osgEarthDrivers/sky_silverlining/SilverLiningContext.cpp
+++ b/src/osgEarthDrivers/sky_silverlining/SilverLiningContext.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -16,15 +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/>
  */
-#include "SilverLiningContext"
 #include <SilverLining.h> // SilverLinking SDK
+#include "SilverLiningContext"
 #include <osg/Light>
 #include <osgEarth/SpatialReference>
 
 #define LC "[SilverLiningContext] "
 
-using namespace osgEarth;
-using namespace osgEarth::Drivers::SilverLining;
+using namespace osgEarth::SilverLining;
 
 
 SilverLiningContext::SilverLiningContext(const SilverLiningOptions& options) :
@@ -33,7 +32,8 @@ _initAttempted        ( false ),
 _initFailed           ( false ),
 _maxAmbientLightingAlt( -1.0 ),
 _atmosphere           ( 0L ),
-_clouds               ( 0L )
+_clouds               ( 0L ),
+_minAmbient           ( 0,0,0,0 )
 {
     // Create a SL atmosphere (the main SL object).
     // TODO: plug in the username + license key.
@@ -42,6 +42,14 @@ _clouds               ( 0L )
         options.licenseCode()->c_str() );
 }
 
+SilverLiningContext::~SilverLiningContext()
+{
+    if ( _atmosphere )
+        delete _atmosphere;
+
+    OE_INFO << LC << "Destroyed\n";
+}
+
 void
 SilverLiningContext::setLight(osg::Light* light)
 {
@@ -49,12 +57,18 @@ SilverLiningContext::setLight(osg::Light* light)
 }
 
 void
-SilverLiningContext::setSRS(const SpatialReference* srs)
+SilverLiningContext::setSRS(const osgEarth::SpatialReference* srs)
 {
     _srs = srs;
 }
 
 void
+SilverLiningContext::setMinimumAmbient(const osg::Vec4f& value)
+{
+    _minAmbient = value;
+}
+
+void
 SilverLiningContext::initialize(osg::RenderInfo& renderInfo)
 {
     if ( !_initAttempted && !_initFailed )
@@ -96,6 +110,7 @@ SilverLiningContext::initialize(osg::RenderInfo& renderInfo)
 
                 if ( _options.drawClouds() == true )
                 {
+                    OE_INFO << LC << "Initializing clouds\n";
                     setupClouds();
                 }
             }
@@ -163,7 +178,13 @@ SilverLiningContext::updateLight()
     osg::Vec3 direction(x, y, z);
     direction.normalize();
 
-    _light->setAmbient( osg::Vec4(ra, ga, ba, 1.0f) );
+    osg::Vec4 ambient(
+        osg::clampAbove(ra, _minAmbient.r()),
+        osg::clampAbove(ba, _minAmbient.g()),
+        osg::clampAbove(ga, _minAmbient.b()),
+        1.0);
+
+    _light->setAmbient( ambient );
     _light->setDiffuse( osg::Vec4(rd, gd, bd, 1.0f) );
     _light->setPosition( osg::Vec4(direction, 0.0f) ); //w=0 means "at infinity"
 }
@@ -199,11 +220,12 @@ SilverLiningContext::updateLocation()
 
         ::SilverLining::Location loc;
         loc.SetAltitude ( latLonAlt.z() );
-        loc.SetLongitude( osg::DegreesToRadians(latLonAlt.x()) );
-        loc.SetLatitude ( osg::DegreesToRadians(latLonAlt.y()) );
+        loc.SetLongitude( latLonAlt.x() ); //osg::DegreesToRadians(latLonAlt.x()) );
+        loc.SetLatitude ( latLonAlt.y() ); //osg::DegreesToRadians(latLonAlt.y()) );
 
         _atmosphere->GetConditions()->SetLocation( loc );
 
+#if 0
         if ( _clouds )
         {
 #if 1 //TODO: figure out why we need to call this a couple times before
@@ -215,11 +237,6 @@ SilverLiningContext::updateLocation()
             }
         }
 #endif
+#endif
     }
 }
-
-SilverLiningContext::~SilverLiningContext()
-{
-    // clean up all the SL handles.
-    delete _atmosphere;
-}
diff --git a/src/osgEarthDrivers/sky_silverlining/SilverLiningDriver.cpp b/src/osgEarthDrivers/sky_silverlining/SilverLiningDriver.cpp
index c97730b..1005bb5 100644
--- a/src/osgEarthDrivers/sky_silverlining/SilverLiningDriver.cpp
+++ b/src/osgEarthDrivers/sky_silverlining/SilverLiningDriver.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -24,11 +24,9 @@
 
 #define LC "[SilverLiningDriver] "
 
-using namespace osgEarth::Util;
-
-namespace osgEarth { namespace Drivers { namespace SilverLining
+namespace osgEarth { namespace SilverLining
 {
-    class SilverLiningDriver : public SkyDriver
+    class SilverLiningDriver : public osgEarth::Util::SkyDriver
     {
     public:
         SilverLiningDriver()
@@ -70,7 +68,7 @@ namespace osgEarth { namespace Drivers { namespace SilverLining
                 }
             }
 
-            MapNode* mapnode = getMapNode(options);
+            osgEarth::MapNode* mapnode = getMapNode(options);
             const Map* map = mapnode ? mapnode->getMap() : 0L;
             return new SilverLiningNode( map, slOptions );
         }
@@ -81,4 +79,4 @@ namespace osgEarth { namespace Drivers { namespace SilverLining
 
     REGISTER_OSGPLUGIN(osgearth_sky_silverlining, SilverLiningDriver)
 
-} } } // namespace osgEarth::Drivers::SilverLining
+} } // namespace osgEarth::Drivers::SilverLining
diff --git a/src/osgEarthDrivers/sky_silverlining/SilverLiningNode b/src/osgEarthDrivers/sky_silverlining/SilverLiningNode
index e149a69..3f697e4 100644
--- a/src/osgEarthDrivers/sky_silverlining/SilverLiningNode
+++ b/src/osgEarthDrivers/sky_silverlining/SilverLiningNode
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -22,22 +22,20 @@
 #include <osgEarth/Map>
 #include <osgEarth/PhongLightingEffect>
 #include <osg/Light>
+#include <osg/LightSource>
 
-namespace osgEarth { namespace Drivers { namespace SilverLining
+namespace osgEarth { namespace SilverLining
 {
     class SilverLiningContext;
 
-    using namespace osgEarth;
-    using namespace osgEarth::Util;
-
     /**
      * Node that roots the silverlining adapter.
      */
-    class SilverLiningNode : public SkyNode
+    class SilverLiningNode : public osgEarth::Util::SkyNode
     {
     public:
         SilverLiningNode(
-            const Map*                 map,
+            const osgEarth::Map*       map,
             const SilverLiningOptions& options );
 
     public: // SkyNode
@@ -46,7 +44,9 @@ namespace osgEarth { namespace Drivers { namespace SilverLining
 
         void attach(osg::View* view, int lightNum);
 
+        // callbacks from base class.
         void onSetDateTime();
+        void onSetMinimumAmbient();
 
     public: // osg::Node
 
@@ -56,13 +56,14 @@ namespace osgEarth { namespace Drivers { namespace SilverLining
         virtual ~SilverLiningNode();
 
         osg::ref_ptr<SilverLiningContext> _SL;
-		osg::Geode* _geode;
+		osg::ref_ptr<osg::Geode> _geode;
+        osg::ref_ptr<osg::LightSource> _lightSource;
         osg::Drawable* _skyDrawable;
 		osg::ref_ptr<osg::Drawable> _cloudsDrawable;
         osg::ref_ptr<osg::Light> _light;
 		double _lastAltitude;
 		const SilverLiningOptions _options;
-        osg::ref_ptr<PhongLightingEffect> _lighting;
+        osg::ref_ptr<osgEarth::PhongLightingEffect> _lighting;
     };
 
-} } } // namespace osgEarth::Drivers::SilverLining
+} } // namespace osgEarth::SilverLining
diff --git a/src/osgEarthDrivers/sky_silverlining/SilverLiningNode.cpp b/src/osgEarthDrivers/sky_silverlining/SilverLiningNode.cpp
index 82dad8f..383f076 100644
--- a/src/osgEarthDrivers/sky_silverlining/SilverLiningNode.cpp
+++ b/src/osgEarthDrivers/sky_silverlining/SilverLiningNode.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -16,6 +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 <SilverLining.h>
 
 #include "SilverLiningNode"
 #include "SilverLiningContext"
@@ -25,15 +26,13 @@
 #include <osg/Light>
 #include <osg/LightSource>
 #include <osgEarth/CullingUtils>
-#include <SilverLining.h>
 
+#undef  LC
 #define LC "[SilverLiningNode] "
 
-using namespace osgEarth;
-using namespace osgEarth::Util;
-using namespace osgEarth::Drivers::SilverLining;
+using namespace osgEarth::SilverLining;
 
-SilverLiningNode::SilverLiningNode(const Map*                 map,
+SilverLiningNode::SilverLiningNode(const osgEarth::Map*       map,
                                    const SilverLiningOptions& options) :
 _options     (options),
 _lastAltitude(DBL_MAX)
@@ -46,10 +45,9 @@ _lastAltitude(DBL_MAX)
     _light->setPosition( osg::Vec4(1, 0, 0, 0) ); // w=0 means infinity
     _light->setDirection( osg::Vec3(-1,0,0) );
 
-    osg::LightSource* source = new osg::LightSource();
-    source->setLight( _light.get() );
-    source->setReferenceFrame(osg::LightSource::RELATIVE_RF);
-    this->addChild( source );
+    _lightSource = new osg::LightSource();
+    _lightSource->setLight( _light.get() );
+    _lightSource->setReferenceFrame(osg::LightSource::RELATIVE_RF);
 
     // The main silver lining data:
     _SL = new SilverLiningContext( options );
@@ -59,16 +57,15 @@ _lastAltitude(DBL_MAX)
     // Geode to hold each of the SL drawables:
     _geode = new osg::Geode();
     _geode->setCullingActive( false );
-    this->addChild( _geode );
 
-    // Draws the sky:
+    // Draws the sky before everything else
     _skyDrawable = new SkyDrawable( _SL.get() );
-    //_skyDrawable->getOrCreateStateSet()->setRenderBinDetails( 98, "RenderBin" );
+    _skyDrawable->getOrCreateStateSet()->setRenderBinDetails( -99, "RenderBin" );
     _geode->addDrawable( _skyDrawable );
 
-    // Clouds
+    // Clouds draw after everything else
     _cloudsDrawable = new CloudsDrawable( _SL.get() );
-    //_cloudsDrawable->getOrCreateStateSet()->setRenderBinDetails( 99, "RenderBin" );
+    _cloudsDrawable->getOrCreateStateSet()->setRenderBinDetails( 99, "DepthSortedBin" );
     _geode->addDrawable( _cloudsDrawable.get() );
 
     // scene lighting
@@ -79,7 +76,7 @@ _lastAltitude(DBL_MAX)
 
     // ensure it's depth sorted and draws after the terrain
     //stateset->setRenderingHint(osg::StateSet::TRANSPARENT_BIN);
-    //getOrCreateStateSet()->setRenderBinDetails( 100, "RenderBin" );
+    //getOrCreateStateSet()->setRenderBinDetails( 100, "DepthSortedBin" );
 
     // SL requires an update pass.
     ADJUST_UPDATE_TRAV_COUNT(this, +1);
@@ -112,6 +109,11 @@ SilverLiningNode::onSetDateTime()
     _SL->getAtmosphere()->GetConditions()->SetTime( utcTime );
 }
 
+void
+SilverLiningNode::onSetMinimumAmbient()
+{
+    _SL->setMinimumAmbient( getMinimumAmbient() );
+}
 
 void
 SilverLiningNode::traverse(osg::NodeVisitor& nv)
@@ -121,18 +123,15 @@ SilverLiningNode::traverse(osg::NodeVisitor& nv)
         if ( nv.getVisitorType() == nv.UPDATE_VISITOR )
         {
 			int frameNumber = nv.getFrameStamp()->getFrameNumber();
-            _SL->updateLocation();
-            _SL->updateLight();
-            _SL->getAtmosphere()->UpdateSkyAndClouds();
             _skyDrawable->dirtyBound();
 
             if( _cloudsDrawable )
             {
-                if (_lastAltitude <= *_options.cloudsMaxAltitude() )
+                if ( _lastAltitude <= *_options.cloudsMaxAltitude() )
                 {
                     if ( _cloudsDrawable->getNumParents() == 0 )
                         _geode->addDrawable( _cloudsDrawable.get() );
-                    
+
                     _cloudsDrawable->dirtyBound();
                 }
                 else
@@ -142,8 +141,10 @@ SilverLiningNode::traverse(osg::NodeVisitor& nv)
                 }
             }
         }
+
         else if ( nv.getVisitorType() == nv.CULL_VISITOR )
         {
+
             // TODO: make this multi-camera safe
             _SL->setCameraPosition( nv.getEyePoint() );
             osgUtil::CullVisitor* cv = Culling::asCullVisitor(nv);
@@ -154,11 +155,22 @@ SilverLiningNode::traverse(osg::NodeVisitor& nv)
 				cv->getEyePoint().length() - _SL->getSRS()->getEllipsoid()->getRadiusEquator() :
 				cv->getEyePoint().z();
 
-			if (_lastAltitude <= *_options.cloudsMaxAltitude() )
-			{
-				_SL->getAtmosphere()->CullObjects();
-			}
+            _SL->updateLocation();
+            _SL->updateLight();
+            _SL->getAtmosphere()->UpdateSkyAndClouds();
+			_SL->getAtmosphere()->CullObjects();
         }
     }
+
     osgEarth::Util::SkyNode::traverse( nv );
+
+    if ( _geode.valid() )
+    {
+        _geode->accept(nv);
+    }
+
+    if ( _lightSource.valid() )
+    {
+        _lightSource->accept(nv);
+    }
 }
diff --git a/src/osgEarthDrivers/sky_silverlining/SilverLiningOptions b/src/osgEarthDrivers/sky_silverlining/SilverLiningOptions
index 5d47cab..c469296 100644
--- a/src/osgEarthDrivers/sky_silverlining/SilverLiningOptions
+++ b/src/osgEarthDrivers/sky_silverlining/SilverLiningOptions
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -21,19 +21,16 @@
 
 #include <osgEarthUtil/Sky>
 
-namespace osgEarth { namespace Drivers { namespace SilverLining
+namespace osgEarth { namespace SilverLining
 {
-    using namespace osgEarth;
-    using namespace osgEarth::Util;
-
     /**
      * Options for creating a SilverLining environment node
      */
-    class SilverLiningOptions : public SkyOptions
+    class SilverLiningOptions : public osgEarth::Util::SkyOptions
     {
     public:
-        SilverLiningOptions(const SkyOptions& options =SkyOptions()) :
-          SkyOptions(options),
+        SilverLiningOptions(const osgEarth::Util::SkyOptions& options =osgEarth::Util::SkyOptions()) :
+          osgEarth::Util::SkyOptions(options),
           _drawClouds(false),
           _cloudsMaxAltitude(20000)
         {
@@ -65,8 +62,8 @@ namespace osgEarth { namespace Drivers { namespace SilverLining
 		const optional<double>& cloudsMaxAltitude() const { return _cloudsMaxAltitude; }
 
     public:
-        Config getConfig() const {
-            Config conf = SkyOptions::getConfig();
+        osgEarth::Config getConfig() const {
+            osgEarth::Config conf = osgEarth::Util::SkyOptions::getConfig();
             conf.addIfSet("user", _user);
             conf.addIfSet("license_code", _licenseCode);
             conf.addIfSet("resource_path", _resourcePath);
@@ -76,13 +73,13 @@ namespace osgEarth { namespace Drivers { namespace SilverLining
         }
 
     protected:
-        void mergeConfig( const Config& conf ) {
-            SkyOptions::mergeConfig( conf );
+        void mergeConfig( const osgEarth::Config& conf ) {
+            osgEarth::Util::SkyOptions::mergeConfig( conf );
             fromConfig(conf);
         }
 
     private:
-        void fromConfig( const Config& conf ) {
+        void fromConfig( const osgEarth::Config& conf ) {
             conf.getIfSet("user", _user);
             conf.getIfSet("license_code", _licenseCode);
             conf.getIfSet("resource_path", _resourcePath);
@@ -90,15 +87,15 @@ namespace osgEarth { namespace Drivers { namespace SilverLining
 			conf.getIfSet("clouds_max_altitude", _cloudsMaxAltitude);
         }
 
-        optional<std::string> _user;
-        optional<std::string> _licenseCode;
-        optional<std::string> _resourcePath;
-        optional<bool>        _drawClouds;
-		optional<double>      _cloudsMaxAltitude;
-		int                   _lastCullFrameNumber;
+        osgEarth::optional<std::string> _user;
+        osgEarth::optional<std::string> _licenseCode;
+        osgEarth::optional<std::string> _resourcePath;
+        osgEarth::optional<bool>        _drawClouds;
+		osgEarth::optional<double>      _cloudsMaxAltitude;
+		int                             _lastCullFrameNumber;
     };
 
-} } } // namespace osgEarth::Drivers::SilverLiningPlugin
+} } // namespace osgEarth::SilverLiningPlugin
 
 #endif // OSGEARTH_DRIVER_SILVERLINING_OPTIONS
 
diff --git a/src/osgEarthDrivers/sky_silverlining/SilverLiningSkyDrawable b/src/osgEarthDrivers/sky_silverlining/SilverLiningSkyDrawable
index 1af61fc..5b68270 100644
--- a/src/osgEarthDrivers/sky_silverlining/SilverLiningSkyDrawable
+++ b/src/osgEarthDrivers/sky_silverlining/SilverLiningSkyDrawable
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -20,12 +20,10 @@
 #include <osg/RenderInfo>
 #include <osg/Version>
 
-namespace osgEarth { namespace Drivers { namespace SilverLining
+namespace osgEarth { namespace SilverLining
 {
     class SilverLiningContext;
 
-    using namespace osgEarth;
-
     /**
      * Custom drawable for rendering the SilverLining effects
      */
@@ -55,4 +53,4 @@ namespace osgEarth { namespace Drivers { namespace SilverLining
         SkyDrawable(const SkyDrawable& copy, const osg::CopyOp& op=osg::CopyOp::SHALLOW_COPY) { }
     };
 
-} } } // namespace osgEarth::Drivers::SilverLining
+} } // namespace osgEarth::SilverLining
diff --git a/src/osgEarthDrivers/sky_silverlining/SilverLiningSkyDrawable.cpp b/src/osgEarthDrivers/sky_silverlining/SilverLiningSkyDrawable.cpp
index 64e3081..201f618 100644
--- a/src/osgEarthDrivers/sky_silverlining/SilverLiningSkyDrawable.cpp
+++ b/src/osgEarthDrivers/sky_silverlining/SilverLiningSkyDrawable.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -16,15 +16,15 @@
  * 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 <SilverLining.h>
 #include "SilverLiningSkyDrawable"
 #include "SilverLiningContext"
 #include <osgEarth/SpatialReference>
-#include <SilverLining.h>
 
 #define LC "[SilverLining:SkyDrawable] "
 
-using namespace osgEarth;
-using namespace osgEarth::Drivers::SilverLining;
+using namespace osgEarth::SilverLining;
+
 
 SkyDrawable::SkyDrawable(SilverLiningContext* SL) :
 _SL( SL )
diff --git a/src/osgEarthDrivers/sky_simple/CMakeLists.txt b/src/osgEarthDrivers/sky_simple/CMakeLists.txt
index 7bae2db..4e7dd63 100644
--- a/src/osgEarthDrivers/sky_simple/CMakeLists.txt
+++ b/src/osgEarthDrivers/sky_simple/CMakeLists.txt
@@ -1,22 +1,48 @@
-SET(TARGET_SRC 
+#
+# SimpleSky plugin
+#
+
+set(TARGET_GLSL
+    SimpleSky.Atmosphere.frag.glsl
+    SimpleSky.Atmosphere.vert.glsl
+    SimpleSky.Ground.ONeil.frag.glsl
+    SimpleSky.Ground.ONeil.vert.glsl
+    SimpleSky.Moon.frag.glsl
+    SimpleSky.Moon.vert.glsl
+    SimpleSky.Stars.frag.glsl
+    SimpleSky.Stars.vert.glsl
+    SimpleSky.Stars.GLES.frag.glsl
+    SimpleSky.Stars.GLES.vert.glsl
+    SimpleSky.Sun.frag.glsl
+    SimpleSky.Sun.vert.glsl )
+
+set(TARGET_IN
+    SimpleSkyShaders.cpp.in)
+
+set(SHADERS_CPP "${CMAKE_CURRENT_BINARY_DIR}/AutoGenShaders.cpp")
+
+configure_shaders(
+    SimpleSkyShaders.cpp.in
+    ${SHADERS_CPP}
+    ${TARGET_GLSL} )
+    
+set(TARGET_SRC 
     SimpleSkyDriver.cpp
     SimpleSkyNode.cpp
-)
-SET(TARGET_H
+    ${SHADERS_CPP} )
+
+set(TARGET_H
     SimpleSkyOptions
 	SimpleSkyNode
-    SimpleSkyShaders
-)
+    SimpleSkyShaders )
 
-SET(TARGET_COMMON_LIBRARIES ${TARGET_COMMON_LIBRARIES}
-    osgEarthUtil
-)
+set(TARGET_COMMON_LIBRARIES ${TARGET_COMMON_LIBRARIES}
+    osgEarthUtil )
 
-SETUP_PLUGIN(osgearth_sky_simple)
+setup_plugin(osgearth_sky_simple)
 
 
 # to install public driver includes:
-SET(LIB_NAME sky_simple)
-SET(LIB_PUBLIC_HEADERS SimpleSkyOptions)
-
-INCLUDE(ModuleInstallOsgEarthDriverIncludes OPTIONAL)
+set(LIB_NAME sky_simple)
+set(LIB_PUBLIC_HEADERS SimpleSkyOptions)
+include(ModuleInstallOsgEarthDriverIncludes OPTIONAL)
diff --git a/src/osgEarthDrivers/sky_simple/SimpleSky.Atmosphere.frag.glsl b/src/osgEarthDrivers/sky_simple/SimpleSky.Atmosphere.frag.glsl
new file mode 100644
index 0000000..66a75d4
--- /dev/null
+++ b/src/osgEarthDrivers/sky_simple/SimpleSky.Atmosphere.frag.glsl
@@ -0,0 +1,34 @@
+#version $GLSL_VERSION_STR
+$GLSL_DEFAULT_PRECISION_FLOAT
+
+#pragma vp_entryPoint "atmos_fragment_main"
+#pragma vp_location   "fragment_coloring"
+#pragma vp_order      "0.8"
+
+uniform vec3 atmos_v3LightDir; 
+
+uniform float atmos_g; 				
+uniform float atmos_g2; 
+uniform float atmos_fWeather; 
+
+varying vec3 atmos_v3Direction; 	
+varying vec3 atmos_mieColor; 
+varying vec3 atmos_rayleighColor; 
+
+const float fExposure = 4.0; 
+
+float atmos_fastpow(in float x, in float y) 
+{ 
+    return x/(x+y-y*x); 
+} 
+
+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 skyColor = 1.0 - exp(f4Color * -fExposure); 
+    color.rgb = skyColor.rgb*atmos_fWeather; 
+    color.a = (skyColor.r+skyColor.g+skyColor.b) * 2.0; 
+}
diff --git a/src/osgEarthDrivers/sky_simple/SimpleSky.Atmosphere.vert.glsl b/src/osgEarthDrivers/sky_simple/SimpleSky.Atmosphere.vert.glsl
new file mode 100644
index 0000000..57f59d4
--- /dev/null
+++ b/src/osgEarthDrivers/sky_simple/SimpleSky.Atmosphere.vert.glsl
@@ -0,0 +1,155 @@
+#version $GLSL_VERSION_STR
+$GLSL_DEFAULT_PRECISION_FLOAT
+
+#pragma vp_entryPoint "atmos_vertex_main"
+#pragma vp_location   "vertex_view"
+#pragma vp_order      "0.5"
+
+// Atmospheric Scattering and Sun Shaders
+// Adapted from code that is Copyright (c) 2004 Sean ONeil
+
+uniform mat4 osg_ViewMatrixInverse;   // camera position in [3].xyz
+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 		
+uniform float atmos_fInnerRadius;     // Inner planetary radius 
+uniform float atmos_fInnerRadius2;    // fInnerRadius^2 
+uniform float atmos_fKrESun;          // Kr * ESun 	
+uniform float atmos_fKmESun;          // Km * ESun 		
+uniform float atmos_fKr4PI;           // Kr * 4 * PI 	
+uniform float atmos_fKm4PI;           // Km * 4 * PI 		
+uniform float atmos_fScale;           // 1 / (fOuterRadius - fInnerRadius) 	
+uniform float atmos_fScaleDepth;      // The scale depth 
+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; 
+
+vec3 vVec; 
+float atmos_fCameraHeight;    // The camera's current height 		
+float atmos_fCameraHeight2;   // fCameraHeight^2 
+
+float atmos_fastpow(in float x, in float y) 
+{ 
+    return x/(x+y-y*x); 
+} 
+
+float atmos_scale(float fCos) 	
+{ 
+    float x = 1.0 - fCos; 
+    return atmos_fScaleDepth * exp(-0.00287 + x*(0.459 + x*(3.83 + x*(-6.80 + x*5.25)))); 
+} 
+
+void atmos_SkyFromSpace(void) 
+{ 
+    // Get the ray from the camera to the vertex and its length (which is the far point of the ray passing through the atmosphere) 
+    vec3 v3Pos = gl_Vertex.xyz; 
+    vec3 v3Ray = v3Pos - vVec; 
+    float fFar = length(v3Ray); 
+    v3Ray /= fFar; 
+
+    // Calculate the closest intersection of the ray with the outer atmosphere 
+    // (which is the near point of the ray passing through the atmosphere) 
+    float B = 2.0 * dot(vVec, v3Ray); 
+    float C = atmos_fCameraHeight2 - atmos_fOuterRadius2; 
+    float fDet = max(0.0, B*B - 4.0 * C); 	
+    float fNear = 0.5 * (-B - sqrt(fDet)); 		
+
+    // Calculate the ray's starting position, then calculate its scattering offset 
+    vec3 v3Start = vVec + v3Ray * fNear; 			
+    fFar -= fNear; 	
+    float fStartAngle = dot(v3Ray, v3Start) / atmos_fOuterRadius; 			
+    float fStartDepth = exp(-1.0 / atmos_fScaleDepth); 
+    float fStartOffset = fStartDepth*atmos_scale(fStartAngle); 		
+
+    // Initialize the atmos_ing loop variables 	
+    float fSampleLength = fFar / atmos_fSamples; 		
+    float fScaledLength = fSampleLength * atmos_fScale; 					
+    vec3 v3SampleRay = v3Ray * fSampleLength; 	
+    vec3 v3SamplePoint = v3Start + v3SampleRay * 0.5; 	
+
+    // Now loop through the sample rays 
+    vec3 v3FrontColor = vec3(0.0, 0.0, 0.0); 
+    vec3 v3Attenuate;   
+    for(int i=0; i<atmos_nSamples; i++) 		
+    { 
+        float fHeight = length(v3SamplePoint); 			
+        float fDepth = exp(atmos_fScaleOverScaleDepth * (atmos_fInnerRadius - fHeight)); 
+        float fLightAngle = dot(atmos_v3LightDir, v3SamplePoint) / fHeight; 		
+        float fCameraAngle = dot(v3Ray, v3SamplePoint) / fHeight; 			
+        float fscatter = (fStartOffset + fDepth*(atmos_scale(fLightAngle) - atmos_scale(fCameraAngle))); 	
+        v3Attenuate = exp(-fscatter * (atmos_v3InvWavelength * atmos_fKr4PI + atmos_fKm4PI)); 	
+        v3FrontColor += v3Attenuate * (fDepth * fScaledLength); 					
+        v3SamplePoint += v3SampleRay; 		
+    } 		
+
+    // Finally, scale the Mie and Rayleigh colors and set up the varying 			
+    // variables for the pixel shader 	
+    atmos_mieColor      = v3FrontColor * atmos_fKmESun; 				
+    atmos_rayleighColor = v3FrontColor * (atmos_v3InvWavelength * atmos_fKrESun); 						
+    atmos_v3Direction = vVec  - v3Pos; 			
+} 		
+
+void atmos_SkyFromAtmosphere(void) 		
+{ 
+    // Get the ray from the camera to the vertex, and its length (which is the far 
+    // point of the ray passing through the atmosphere) 
+    vec3 v3Pos = gl_Vertex.xyz; 	
+    vec3 v3Ray = v3Pos - vVec; 			
+    float fFar = length(v3Ray); 					
+    v3Ray /= fFar; 				
+
+    // Calculate the ray's starting position, then calculate its atmos_ing offset 
+    vec3 v3Start = vVec; 
+    float fHeight = length(v3Start); 		
+    float fDepth = exp(atmos_fScaleOverScaleDepth * (atmos_fInnerRadius - atmos_fCameraHeight)); 
+    float fStartAngle = dot(v3Ray, v3Start) / fHeight; 	
+    float fStartOffset = fDepth*atmos_scale(fStartAngle); 
+
+    // Initialize the atmos_ing loop variables 		
+    float fSampleLength = fFar / atmos_fSamples; 			
+    float fScaledLength = fSampleLength * atmos_fScale; 				
+    vec3 v3SampleRay = v3Ray * fSampleLength; 		
+    vec3 v3SamplePoint = v3Start + v3SampleRay * 0.5; 
+
+    // Now loop through the sample rays 		
+    vec3 v3FrontColor = vec3(0.0, 0.0, 0.0); 		
+    vec3 v3Attenuate;   
+    for(int i=0; i<atmos_nSamples; i++) 			
+    { 	
+        float fHeight = length(v3SamplePoint); 	
+        float fDepth = exp(atmos_fScaleOverScaleDepth * (atmos_fInnerRadius - fHeight)); 
+        float fLightAngle = dot(atmos_v3LightDir, v3SamplePoint) / fHeight; 
+        float fCameraAngle = dot(v3Ray, v3SamplePoint) / fHeight; 	
+        float fscatter = (fStartOffset + fDepth*(atmos_scale(fLightAngle) - atmos_scale(fCameraAngle))); 	
+        v3Attenuate = exp(-fscatter * (atmos_v3InvWavelength * atmos_fKr4PI + atmos_fKm4PI)); 	
+        v3FrontColor += v3Attenuate * (fDepth * fScaledLength); 		
+        v3SamplePoint += v3SampleRay; 		
+    } 
+
+    // Finally, scale the Mie and Rayleigh colors and set up the varying 
+    // variables for the pixel shader 					
+    atmos_mieColor      = v3FrontColor * atmos_fKmESun; 			
+    atmos_rayleighColor = v3FrontColor * (atmos_v3InvWavelength * atmos_fKrESun); 				
+    atmos_v3Direction = vVec - v3Pos; 				
+} 
+
+void atmos_vertex_main(inout vec4 VertexVIEW) 
+{ 
+    // Get camera position and height 
+    vVec = osg_ViewMatrixInverse[3].xyz; 
+    atmos_fCameraHeight = length(vVec); 
+    atmos_fCameraHeight2 = atmos_fCameraHeight*atmos_fCameraHeight; 
+    if(atmos_fCameraHeight >= atmos_fOuterRadius)
+    { 
+        atmos_SkyFromSpace(); 
+    } 
+    else
+    { 
+        atmos_SkyFromAtmosphere(); 
+    } 
+}
diff --git a/src/osgEarthDrivers/sky_simple/SimpleSky.Ground.ONeil.frag.glsl b/src/osgEarthDrivers/sky_simple/SimpleSky.Ground.ONeil.frag.glsl
new file mode 100644
index 0000000..b7fba37
--- /dev/null
+++ b/src/osgEarthDrivers/sky_simple/SimpleSky.Ground.ONeil.frag.glsl
@@ -0,0 +1,67 @@
+#version $GLSL_VERSION_STR
+$GLSL_DEFAULT_PRECISION_FLOAT
+
+#pragma vp_entryPoint "atmos_fragment_main"
+#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; 
+        
+vec3 oe_global_Normal;          // surface normal (from osgEarth)
+
+void atmos_fragment_main(inout vec4 color) 
+{ 
+    if ( oe_mode_GL_LIGHTING == false )
+    {
+        return; 
+    }
+
+    vec3 ambient = gl_LightSource[0].ambient.rgb;
+    float minAmbient = ambient.r;
+
+    vec3 N = normalize(oe_global_Normal); 
+    vec3 L = normalize(atmos_lightDir); //normalize(gl_LightSource[0].position.xyz); 
+    vec3 U = normalize(atmos_up); 
+
+    const float maxAmbient = 0.5;
+    float daytime = max(0.0, dot(U,L));
+    float brightness = clamp(daytime, minAmbient, maxAmbient);
+
+    float NdotL = max(dot(N,L), 0.0);
+
+    const float lowAlt  = 1.0;
+    const float highAlt = 4.0;
+    float altitudeInfluence = 1.0 - clamp( (atmos_space-lowAlt)/(highAlt-lowAlt), 0.0, 1.0);
+    float useNormals = altitudeInfluence * (1.0-brightness);
+
+    // try to brighten up surfaces the sun is shining on
+    float overExposure = 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);
+
+    if (NdotL > 0.0 ) { 
+        vec3 V = normalize(atmos_vert); 
+        vec3 H = normalize(L-V); 
+        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); 
+    } 
+
+    // clamp the attentuation to the minimum ambient lighting:
+    vec3 attenuation = max(atmos_atten, ambient); 
+
+    // ramp exposure from ground (full) to space (50%).
+    float exposure = atmos_exposure*clamp(1.0-atmos_space, 0.5, 1.0); 
+
+    vec3 atmosColor = 1.0 - exp(-exposure * (atmos_color + sceneColor.rgb * attenuation)); 
+    color.rgb = gl_FrontMaterial.emission.rgb + atmosColor; 
+}
diff --git a/src/osgEarthDrivers/sky_simple/SimpleSky.Ground.ONeil.vert.glsl b/src/osgEarthDrivers/sky_simple/SimpleSky.Ground.ONeil.vert.glsl
new file mode 100644
index 0000000..08ef66c
--- /dev/null
+++ b/src/osgEarthDrivers/sky_simple/SimpleSky.Ground.ONeil.vert.glsl
@@ -0,0 +1,166 @@
+#version $GLSL_VERSION_STR
+$GLSL_DEFAULT_PRECISION_FLOAT
+
+#pragma vp_entryPoint "atmos_vertex_main"
+#pragma vp_location   "vertex_view"
+#pragma vp_order      "0.5"
+
+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_v3InvWavelength;   // 1 / pow(wavelength,4) for the rgb channels 
+uniform float atmos_fOuterRadius;     // Outer atmosphere radius 
+uniform float atmos_fOuterRadius2;    // fOuterRadius^2 		
+uniform float atmos_fInnerRadius;     // Inner planetary radius 
+uniform float atmos_fInnerRadius2;    // fInnerRadius^2 
+uniform float atmos_fKrESun;          // Kr * ESun 	
+uniform float atmos_fKmESun;          // Km * ESun 		
+uniform float atmos_fKr4PI;           // Kr * 4 * PI 	
+uniform float atmos_fKm4PI;           // Km * 4 * PI 		
+uniform float atmos_fScale;           // 1 / (fOuterRadius - fInnerRadius) 	
+uniform float atmos_fScaleDepth;      // The scale depth 
+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
+        
+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; 
+
+vec3 oe_global_Normal;             // surface normal (from osgEarth)
+
+float atmos_scale(float fCos) 	
+{ 
+    float x = 1.0 - fCos; 
+    return atmos_fScaleDepth * exp(-0.00287 + x*(0.459 + x*(3.83 + x*(-6.80 + x*5.25)))); 
+} 
+
+void atmos_GroundFromSpace(in vec4 vertexVIEW) 
+{ 
+    // Get the ray from the camera to the vertex and its length (which is the far point of the ray passing through the atmosphere) 
+    vec3 v3Pos = vertexVIEW.xyz; 
+    vec3 v3Ray = v3Pos; 
+    float fFar = length(v3Ray); 
+    v3Ray /= fFar; 
+                
+    vec4 ec4 = osg_ViewMatrix * vec4(0,0,0,1); 
+    vec3 earthCenter = ec4.xyz/ec4.w; 
+    vec3 normal = normalize(v3Pos-earthCenter); 
+    atmos_up = normal; 
+
+    // Calculate the closest intersection of the ray with the outer atmosphere 
+    // (which is the near point of the ray passing through the atmosphere) 
+    float B = 2.0 * dot(-earthCenter, v3Ray); 
+    float C = atmos_fCameraHeight2 - atmos_fOuterRadius2; 
+    float fDet = max(0.0, B*B - 4.0*C); 	
+    float fNear = 0.5 * (-B - sqrt(fDet)); 		
+
+    // Calculate the ray's starting position, then calculate its scattering offset 
+    vec3 v3Start = v3Ray * fNear; 			
+    fFar -= fNear; 
+    float fDepth = exp((atmos_fInnerRadius - atmos_fOuterRadius) / atmos_fScaleDepth);
+    float fCameraAngle = dot(-v3Ray, normal); 
+    float fLightAngle = dot(atmos_lightDir, normal); 
+    float fCameraScale = atmos_scale(fCameraAngle); 
+    float fLightScale = atmos_scale(fLightAngle); 
+    float fCameraOffset = fDepth*fCameraScale; 
+    float fTemp = fLightScale * fCameraScale; 		
+
+    // Initialize the scattering loop variables 
+    float fSampleLength = fFar / atmos_fSamples; 		
+    float fScaledLength = fSampleLength * atmos_fScale; 					
+    vec3 v3SampleRay = v3Ray * fSampleLength; 	
+    vec3 v3SamplePoint = v3Start + v3SampleRay * 0.5; 	
+
+    // Now loop through the sample rays 
+    vec3 v3FrontColor = vec3(0.0, 0.0, 0.0); 
+    vec3 v3Attenuate = vec3(1,0,0); 
+
+    for(int i=0; i<atmos_nSamples; ++i) 
+    {         
+        float fHeight = length(v3SamplePoint-earthCenter); 			
+        float fDepth = exp(atmos_fScaleOverScaleDepth * (atmos_fInnerRadius - fHeight)); 
+        float fScatter = fDepth*fTemp - fCameraOffset; 
+        v3Attenuate = exp(-fScatter * (atmos_v3InvWavelength * atmos_fKr4PI + atmos_fKm4PI)); 	
+        v3FrontColor += v3Attenuate * (fDepth * fScaledLength); 					
+        v3SamplePoint += v3SampleRay; 		
+    } 	
+
+    atmos_color = v3FrontColor * (atmos_v3InvWavelength * atmos_fKrESun + atmos_fKmESun); 
+    atmos_atten = v3Attenuate; 
+} 		
+
+void atmos_GroundFromAtmosphere(in vec4 vertexVIEW) 		
+{ 
+    // Get the ray from the camera to the vertex and its length (which is the far point of the ray passing through the atmosphere) 
+    vec3 v3Pos = vertexVIEW.xyz / vertexVIEW.w; 
+    vec3 v3Ray = v3Pos; 
+    float fFar = length(v3Ray); 
+    v3Ray /= fFar; 
+        
+    vec4 ec4 = osg_ViewMatrix * vec4(0,0,0,1); 
+    vec3 earthCenter = ec4.xyz/ec4.w; 
+    vec3 normal = normalize(v3Pos-earthCenter); 
+    atmos_up = normal; 
+
+    // Calculate the ray's starting position, then calculate its scattering offset 
+    float fDepth = exp((atmos_fInnerRadius - atmos_fCameraHeight) / atmos_fScaleDepth);
+    float fCameraAngle = max(0.0, dot(-v3Ray, normal)); 
+    float fLightAngle = dot(atmos_lightDir, normal); 
+    float fCameraScale = atmos_scale(fCameraAngle); 
+    float fLightScale = atmos_scale(fLightAngle); 
+    float fCameraOffset = fDepth*fCameraScale; 
+    float fTemp = fLightScale * fCameraScale; 
+
+    // Initialize the scattering loop variables 	
+    float fSampleLength = fFar / atmos_fSamples; 		
+    float fScaledLength = fSampleLength * atmos_fScale; 					
+    vec3 v3SampleRay = v3Ray * fSampleLength; 	
+    vec3 v3SamplePoint = v3SampleRay * 0.5; 	
+
+    // Now loop through the sample rays 
+    vec3 v3FrontColor = vec3(0.0, 0.0, 0.0); 
+    vec3 v3Attenuate;   
+    for(int i=0; i<atmos_nSamples; i++) 		
+    { 
+        float fHeight = length(v3SamplePoint-earthCenter); 			
+        float fDepth = exp(atmos_fScaleOverScaleDepth * (atmos_fInnerRadius - fHeight)); 
+        float fScatter = fDepth*fTemp - fCameraOffset; 
+        v3Attenuate = exp(-fScatter * (atmos_v3InvWavelength * atmos_fKr4PI + atmos_fKm4PI)); 	
+        v3FrontColor += v3Attenuate * (fDepth * fScaledLength); 					
+        v3SamplePoint += v3SampleRay; 		
+    } 		
+
+    atmos_color = v3FrontColor * (atmos_v3InvWavelength * atmos_fKrESun + atmos_fKmESun); 			
+    atmos_atten = v3Attenuate; 
+} 
+
+void atmos_vertex_main(inout vec4 vertexVIEW) 
+{ 
+    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_vert = vertexVIEW.xyz; 
+
+    atmos_space = max(0.0, (atmos_fCameraHeight-atmos_fInnerRadius)/(atmos_fOuterRadius-atmos_fInnerRadius));
+
+    if(atmos_fCameraHeight >= atmos_fOuterRadius) 
+    { 
+        atmos_GroundFromSpace(vertexVIEW); 
+    } 
+    else 
+    { 
+        atmos_GroundFromAtmosphere(vertexVIEW); 
+    } 
+}
diff --git a/src/osgEarthDrivers/sky_simple/SimpleSky.Moon.frag.glsl b/src/osgEarthDrivers/sky_simple/SimpleSky.Moon.frag.glsl
new file mode 100644
index 0000000..2cf02c3
--- /dev/null
+++ b/src/osgEarthDrivers/sky_simple/SimpleSky.Moon.frag.glsl
@@ -0,0 +1,10 @@
+#version $GLSL_VERSION_STR
+$GLSL_DEFAULT_PRECISION_FLOAT
+
+varying vec4 moon_TexCoord;
+uniform sampler2D moonTex;
+
+void main( void ) 
+{ 
+   gl_FragColor = texture2D(moonTex, moon_TexCoord.st);
+}
diff --git a/src/osgEarthDrivers/sky_simple/SimpleSky.Moon.vert.glsl b/src/osgEarthDrivers/sky_simple/SimpleSky.Moon.vert.glsl
new file mode 100644
index 0000000..420b269
--- /dev/null
+++ b/src/osgEarthDrivers/sky_simple/SimpleSky.Moon.vert.glsl
@@ -0,0 +1,11 @@
+#version $GLSL_VERSION_STR
+$GLSL_DEFAULT_PRECISION_FLOAT
+
+uniform mat4 osg_ModelViewProjectionMatrix;
+varying vec4 moon_TexCoord;
+
+void main() 
+{ 
+    moon_TexCoord = gl_MultiTexCoord0; 
+    gl_Position = osg_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
new file mode 100644
index 0000000..a393abf
--- /dev/null
+++ b/src/osgEarthDrivers/sky_simple/SimpleSky.Stars.GLES.frag.glsl
@@ -0,0 +1,9 @@
+#version $GLSL_VERSION_STR 
+$GLSL_DEFAULT_PRECISION_FLOAT 
+
+varying float visibility; 
+varying vec4 osg_FrontColor; 
+void main( void ) 
+{ 
+    gl_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
new file mode 100644
index 0000000..730b7c2
--- /dev/null
+++ b/src/osgEarthDrivers/sky_simple/SimpleSky.Stars.GLES.vert.glsl
@@ -0,0 +1,28 @@
+#version $GLSL_VERSION_STR 
+$GLSL_DEFAULT_PRECISION_FLOAT 
+
+uniform vec3 atmos_v3LightDir; 
+uniform mat4 osg_ViewMatrixInverse; 
+varying float visibility; 
+varying vec4 osg_FrontColor; 
+
+float 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); 
+} 
+
+void main() 
+{ 
+    osg_FrontColor = gl_Color; 
+    gl_PointSize = gl_Color.r * 2.0; 
+    gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; 
+    vec3 eye = osg_ViewMatrixInverse[3].xyz; 
+    float hae = length(eye) - 6378137.0; 
+    // highness: visibility increases with altitude
+    float highness = remap( hae, 25000.0, 150000.0, 0.0, 1.0 ); 
+    eye = normalize(eye); 
+    // darkness: visibility increase as the sun goes around the other side of the earth
+    float darkness = 1.0-remap(dot(eye,atmos_v3LightDir), -0.25, 0.0, 0.0, 1.0); 
+    visibility = clamp(highness + darkness, 0.0, 1.0); 
+}
diff --git a/src/osgEarthDrivers/sky_simple/SimpleSky.Stars.frag.glsl b/src/osgEarthDrivers/sky_simple/SimpleSky.Stars.frag.glsl
new file mode 100644
index 0000000..183d4d7
--- /dev/null
+++ b/src/osgEarthDrivers/sky_simple/SimpleSky.Stars.frag.glsl
@@ -0,0 +1,12 @@
+#version 120
+
+varying float visibility; 
+varying vec4 osg_FrontColor; 
+
+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; 
+}
diff --git a/src/osgEarthDrivers/sky_simple/SimpleSky.Stars.vert.glsl b/src/osgEarthDrivers/sky_simple/SimpleSky.Stars.vert.glsl
new file mode 100644
index 0000000..74a8ba0
--- /dev/null
+++ b/src/osgEarthDrivers/sky_simple/SimpleSky.Stars.vert.glsl
@@ -0,0 +1,27 @@
+#version 120
+
+uniform vec3 atmos_v3LightDir; 
+uniform mat4 osg_ViewMatrixInverse; 
+varying float visibility; 
+varying vec4 osg_FrontColor; 
+
+float 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); 
+} 
+
+void main() 
+{ 
+    osg_FrontColor = gl_Color; 
+    gl_PointSize = gl_Color.r * 14.0; 
+    gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; 
+    vec3 eye = osg_ViewMatrixInverse[3].xyz; 
+    float hae = length(eye) - 6378137.0; 
+    // highness: visibility increases with altitude
+    float highness = remap( hae, 25000.0, 150000.0, 0.0, 1.0 ); 
+    eye = normalize(eye); 
+    // darkness: visibility increase as the sun goes around the other side of the earth
+    float darkness = 1.0-remap(dot(eye,atmos_v3LightDir), -0.25, 0.0, 0.0, 1.0); 
+    visibility = clamp(highness + darkness, 0.0, 1.0); 
+}
diff --git a/src/osgEarthDrivers/sky_simple/SimpleSky.Sun.frag.glsl b/src/osgEarthDrivers/sky_simple/SimpleSky.Sun.frag.glsl
new file mode 100644
index 0000000..0ae7273
--- /dev/null
+++ b/src/osgEarthDrivers/sky_simple/SimpleSky.Sun.frag.glsl
@@ -0,0 +1,18 @@
+#version $GLSL_VERSION_STR 
+$GLSL_DEFAULT_PRECISION_FLOAT 
+
+uniform float atmos_sunAlpha; 
+varying vec3 atmos_v3Direction; 
+
+float atmos_fastpow(in float x, in float y) 
+{ 
+    return x/(x+y-y*x); 
+} 
+
+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; 
+}
diff --git a/src/osgEarthDrivers/sky_simple/SimpleSky.Sun.vert.glsl b/src/osgEarthDrivers/sky_simple/SimpleSky.Sun.vert.glsl
new file mode 100644
index 0000000..62f0f22
--- /dev/null
+++ b/src/osgEarthDrivers/sky_simple/SimpleSky.Sun.vert.glsl
@@ -0,0 +1,12 @@
+#version $GLSL_VERSION_STR
+$GLSL_DEFAULT_PRECISION_FLOAT
+
+varying vec3 atmos_v3Direction; 
+
+void main() 
+{ 
+    vec3 v3Pos = gl_Vertex.xyz; 
+    gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; 
+    atmos_v3Direction = vec3(0.0,0.0,1.0) - v3Pos; 
+    atmos_v3Direction = atmos_v3Direction/length(atmos_v3Direction); 
+}
diff --git a/src/osgEarthDrivers/sky_simple/SimpleSkyDriver.cpp b/src/osgEarthDrivers/sky_simple/SimpleSkyDriver.cpp
index 5da5a97..d363128 100644
--- a/src/osgEarthDrivers/sky_simple/SimpleSkyDriver.cpp
+++ b/src/osgEarthDrivers/sky_simple/SimpleSkyDriver.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/sky_simple/SimpleSkyNode b/src/osgEarthDrivers/sky_simple/SimpleSkyNode
index 909fe02..88a59fa 100644
--- a/src/osgEarthDrivers/sky_simple/SimpleSkyNode
+++ b/src/osgEarthDrivers/sky_simple/SimpleSkyNode
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -57,6 +57,7 @@ namespace osgEarth { namespace Drivers { namespace SimpleSky
         void onSetSunVisible();
         void onSetMoonVisible();
         void onSetStarsVisible();
+        void onSetMinimumAmbient();
 
     public: // osg::Node
 
diff --git a/src/osgEarthDrivers/sky_simple/SimpleSkyNode.cpp b/src/osgEarthDrivers/sky_simple/SimpleSkyNode.cpp
index 4ba5b93..13fb103 100644
--- a/src/osgEarthDrivers/sky_simple/SimpleSkyNode.cpp
+++ b/src/osgEarthDrivers/sky_simple/SimpleSkyNode.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
@@ -32,10 +35,13 @@
 #include <osgEarth/CullingUtils>
 #include <osgEarth/ShaderFactory>
 #include <osgEarth/ShaderGenerator>
+#include <osgEarth/Shaders>
 
 #include <osg/MatrixTransform>
 #include <osg/ShapeDrawable>
 #include <osg/PointSprite>
+#include <osg/PolygonMode>
+#include <osg/Texture2D>
 #include <osg/BlendFunc>
 #include <osg/FrontFace>
 #include <osg/CullFace>
@@ -272,7 +278,7 @@ SimpleSkyNode::computeBound() const
 }
 
 void 
-    SimpleSkyNode::traverse( osg::NodeVisitor& nv ) 
+SimpleSkyNode::traverse( osg::NodeVisitor& nv ) 
 { 
     if ( nv.getVisitorType() == nv.CULL_VISITOR && _cullContainer.valid() ) 
     { 
@@ -338,6 +344,12 @@ SimpleSkyNode::onSetDateTime()
 }
 
 void
+SimpleSkyNode::onSetMinimumAmbient()
+{
+    _light->setAmbient( getMinimumAmbient() );
+}
+
+void
 SimpleSkyNode::attach( osg::View* view, int lightNum )
 {
     if ( !view || !_light.valid() )
@@ -407,17 +419,11 @@ SimpleSkyNode::makeSceneLighting()
     VirtualProgram* vp = VirtualProgram::getOrCreate( stateset );
     vp->setName( "SimpleSky Scene Lighting" );
 
-    if ( _options.atmosphericLighting() == true )
+    if (_options.atmosphericLighting() == true && !Registry::capabilities().isGLES() )
     {
-        vp->setFunction(
-            "atmos_vertex_main",
-            Ground_Scattering_Vertex,
-            ShaderComp::LOCATION_VERTEX_VIEW);
-
-        vp->setFunction(
-            "atmos_fragment_main", 
-            Ground_Scattering_Fragment,
-            ShaderComp::LOCATION_FRAGMENT_LIGHTING);
+        Shaders pkg;
+        pkg.load( vp, pkg.Ground_ONeil_Vert );
+        pkg.load( vp, pkg.Ground_ONeil_Frag );
     }
 
     else
@@ -471,6 +477,14 @@ SimpleSkyNode::makeAtmosphere(const osg::EllipsoidModel* em)
     // create some skeleton geometry to shade:
     osg::Geometry* drawable = s_makeEllipsoidGeometry( em, _outerRadius, false );
 
+    // disable wireframe/point rendering on the atmosphere, since it is distracting.
+    if ( _options.allowWireframe() == false )
+    {
+        drawable->getOrCreateStateSet()->setAttributeAndModes(
+            new osg::PolygonMode(osg::PolygonMode::FRONT_AND_BACK, osg::PolygonMode::FILL),
+            osg::StateAttribute::PROTECTED);
+    }
+
     osg::Geode* geode = new osg::Geode();
     geode->addDrawable( drawable );
     
@@ -489,15 +503,9 @@ SimpleSkyNode::makeAtmosphere(const osg::EllipsoidModel* em)
         vp->setName( "SimpleSky Atmosphere" );
         vp->setInheritShaders( false );
 
-        vp->setFunction(
-            "atmos_vertex_main",
-            Atmosphere_Vertex,
-            ShaderComp::LOCATION_VERTEX_VIEW);
-
-        vp->setFunction(
-            "atmos_fragment_main",
-            Atmosphere_Fragment,
-            ShaderComp::LOCATION_FRAGMENT_LIGHTING);
+        Shaders pkg;
+        pkg.load( vp, pkg.Atmosphere_Vert );
+        pkg.load( vp, pkg.Atmosphere_Frag );
     }
 
     // A nested camera isolates the projection matrix calculations so the node won't 
@@ -535,11 +543,19 @@ SimpleSkyNode::makeSun()
     set->setAttributeAndModes( new osg::Depth(osg::Depth::ALWAYS, 0, 1, false), osg::StateAttribute::ON );
 
     // create shaders
+    Shaders pkg;
     osg::Program* program = new osg::Program();
-    osg::Shader* vs = new osg::Shader( osg::Shader::VERTEX, Sun_Vertex );
+
+    osg::Shader* vs = new osg::Shader(
+        osg::Shader::VERTEX,
+        ShaderLoader::load(pkg.Sun_Vert, pkg) );
     program->addShader( vs );
-    osg::Shader* fs = new osg::Shader( osg::Shader::FRAGMENT, Sun_Fragment );
+
+    osg::Shader* fs = new osg::Shader(
+        osg::Shader::FRAGMENT,
+        ShaderLoader::load(pkg.Sun_Frag, pkg) );
     program->addShader( fs );
+
     set->setAttributeAndModes( program, osg::StateAttribute::ON );
 
     // A nested camera isolates the projection matrix calculations so the node won't 
@@ -593,16 +609,21 @@ SimpleSkyNode::makeMoon()
     set->setRenderBinDetails( BIN_MOON, "RenderBin" );
     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
 
     set->addUniform(new osg::Uniform("moonTex", 0));
 
     // create shaders
+    Shaders pkg;
     osg::Program* program = new osg::Program();
-    osg::Shader* vs = new osg::Shader( osg::Shader::VERTEX, Moon_Vertex );
+    osg::Shader* vs = new osg::Shader(
+        osg::Shader::VERTEX,
+        ShaderLoader::load(pkg.Moon_Vert, pkg) );
     program->addShader( vs );
-    osg::Shader* fs = new osg::Shader( osg::Shader::FRAGMENT, Moon_Fragment );
+    osg::Shader* fs = new osg::Shader(
+        osg::Shader::FRAGMENT,
+        ShaderLoader::load(pkg.Moon_Frag, pkg) );
     program->addShader( fs );
     set->setAttributeAndModes( program, osg::StateAttribute::ON | osg::StateAttribute::PROTECTED );
 #endif
@@ -676,7 +697,7 @@ SimpleSkyNode::makeStars()
 
     _stars = buildStarGeometry(stars);
 
-    // make the moon's transform:
+    // make the stars' transform:
     _starsXform = new osg::MatrixTransform();
     _starsXform->addChild( _stars.get() );
 
@@ -723,22 +744,23 @@ SimpleSkyNode::buildStarGeometry(const std::vector<StarData>& stars)
     sset->setTextureAttributeAndModes( 0, new osg::PointSprite(), osg::StateAttribute::ON );
     sset->setMode( GL_VERTEX_PROGRAM_POINT_SIZE, osg::StateAttribute::ON );
 
+    Shaders pkg;
     std::string starVertSource, starFragSource;
-    if ( Registry::capabilities().getGLSLVersion() < 1.2f )
+    if ( Registry::capabilities().isGLES() )
     {
-        starVertSource = Stars_Vertex_110;
-        starFragSource = Stars_Fragment_110;
+        starVertSource = ShaderLoader::load(pkg.Stars_GLES_Vert, pkg);
+        starFragSource = ShaderLoader::load(pkg.Stars_GLES_Frag, pkg);
     }
     else
     {
-        starVertSource = Stars_Vertex_120;
-        starFragSource = Stars_Fragment_120;
+        starVertSource = ShaderLoader::load(pkg.Stars_Vert, pkg);
+        starFragSource = ShaderLoader::load(pkg.Stars_Frag, pkg);
     }
 
     osg::Program* program = new osg::Program;
     program->addShader( new osg::Shader(osg::Shader::VERTEX, starVertSource) );
     program->addShader( new osg::Shader(osg::Shader::FRAGMENT, starFragSource) );
-    sset->setAttributeAndModes( program, osg::StateAttribute::ON );
+    sset->setAttributeAndModes( program, osg::StateAttribute::ON | osg::StateAttribute::PROTECTED );
 
     sset->setRenderBinDetails( BIN_STARS, "RenderBin");
     sset->setAttributeAndModes( new osg::Depth(osg::Depth::ALWAYS, 0, 1, false), osg::StateAttribute::ON );
@@ -751,6 +773,7 @@ SimpleSkyNode::buildStarGeometry(const std::vector<StarData>& stars)
     osg::Camera* cam = new osg::Camera();
     cam->getOrCreateStateSet()->setRenderBinDetails( BIN_STARS, "RenderBin" );
     cam->setRenderOrder( osg::Camera::NESTED_RENDER );
+    cam->setNearFarRatio(1e-9);
     cam->setComputeNearFarMode( osg::CullSettings::COMPUTE_NEAR_FAR_USING_BOUNDING_VOLUMES );
     cam->addChild( starGeode );
 
diff --git a/src/osgEarthDrivers/sky_simple/SimpleSkyOptions b/src/osgEarthDrivers/sky_simple/SimpleSkyOptions
index 5308e48..8152540 100644
--- a/src/osgEarthDrivers/sky_simple/SimpleSkyOptions
+++ b/src/osgEarthDrivers/sky_simple/SimpleSkyOptions
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -35,7 +35,8 @@ namespace osgEarth { namespace Drivers { namespace SimpleSky
         SimpleSkyOptions(const ConfigOptions& options =ConfigOptions()) :
           SkyOptions(options),
           _atmosphericLighting(true),
-          _exposure           (3.0f)
+          _exposure           (3.0f),
+          _allowWireframe     (false)
         {
             setDriver( "simple" );
             fromConfig( _conf );
@@ -57,12 +58,17 @@ namespace osgEarth { namespace Drivers { namespace SimpleSky
         optional<std::string>& starFile() { return _starFile; }
         const optional<std::string>& starFile() const { return _starFile; }
 
+        /** Whether to permit wireframe/point polygonmode rendering. Default is false. */
+        optional<bool>& allowWireframe() { return _allowWireframe; }
+        const optional<bool>& allowWireframe() const { return _allowWireframe; }
+
     public:
         Config getConfig() const {
             Config conf = SkyOptions::getConfig();
             conf.addIfSet("atmospheric_lighting", _atmosphericLighting);
             conf.addIfSet("exposure", _exposure);
             conf.addIfSet("star_file", _starFile);
+            conf.addIfSet("allow_wireframe", _allowWireframe);
             return conf;
         }
 
@@ -77,11 +83,13 @@ namespace osgEarth { namespace Drivers { namespace SimpleSky
             conf.getIfSet("atmospheric_lighting", _atmosphericLighting);
             conf.getIfSet("exposure", _exposure);
             conf.getIfSet("star_file", _starFile);
+            conf.getIfSet("allow_wireframe", _allowWireframe);
         }
 
         optional<bool>        _atmosphericLighting;
         optional<float>       _exposure;
         optional<std::string> _starFile;
+        optional<bool>        _allowWireframe;
     };
 
 } } } // namespace osgEarth::Drivers::SimpleSky
diff --git a/src/osgEarthDrivers/sky_simple/SimpleSkyShaders b/src/osgEarthDrivers/sky_simple/SimpleSkyShaders
index 474b238..a6405fc 100644
--- a/src/osgEarthDrivers/sky_simple/SimpleSkyShaders
+++ b/src/osgEarthDrivers/sky_simple/SimpleSkyShaders
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -19,665 +19,26 @@
 #ifndef OSGEARTH_DRIVER_SIMPLE_SKY_SHADERS
 #define OSGEARTH_DRIVER_SIMPLE_SKY_SHADERS 1
 
-#include <osgEarth/VirtualProgram>
+#include <osgEarth/ShaderLoader>
 
 namespace osgEarth { namespace Drivers { namespace SimpleSky
 {
-    // Atmospheric Scattering and Sun Shaders
-    // Adapted from code that is Copyright (c) 2004 Sean O'Neil
-
-    static char* Atmosphere_Vertex =
-        "#version " GLSL_VERSION_STR "\n"
-        GLSL_DEFAULT_PRECISION_FLOAT "\n"
-
-        "uniform mat4 osg_ViewMatrixInverse;   // camera position in [3].xyz\n"
-        "uniform vec3 atmos_v3LightDir;        // The direction vector to the light source \n"
-        "uniform vec3 atmos_v3InvWavelength;   // 1 / pow(wavelength,4) for the rgb channels \n"
-        "uniform float atmos_fOuterRadius;     // Outer atmosphere radius \n"
-        "uniform float atmos_fOuterRadius2;    // fOuterRadius^2 \n"		
-        "uniform float atmos_fInnerRadius;     // Inner planetary radius \n"
-        "uniform float atmos_fInnerRadius2;    // fInnerRadius^2 \n"
-        "uniform float atmos_fKrESun;          // Kr * ESun \n"	
-        "uniform float atmos_fKmESun;          // Km * ESun \n"		
-        "uniform float atmos_fKr4PI;           // Kr * 4 * PI \n"	
-        "uniform float atmos_fKm4PI;           // Km * 4 * PI \n"		
-        "uniform float atmos_fScale;           // 1 / (fOuterRadius - fInnerRadius) \n"	
-        "uniform float atmos_fScaleDepth;      // The scale depth \n"
-        "uniform float atmos_fScaleOverScaleDepth;     // fScale / fScaleDepth \n"	
-        "uniform int atmos_nSamples; \n"	
-        "uniform float atmos_fSamples; \n"				
-
-        "varying vec3 atmos_v3Direction; \n"
-        "varying vec3 atmos_mieColor; \n"
-        "varying vec3 atmos_rayleighColor; \n"
-
-        "vec3 vVec; \n"
-        "float atmos_fCameraHeight;    // The camera's current height \n"		
-        "float atmos_fCameraHeight2;   // fCameraHeight^2 \n"
-
-        "float atmos_fastpow(in float x, in float y) \n"
-        "{ \n"
-        "    return x/(x+y-y*x); \n"
-        "} \n"
-
-        "float atmos_scale(float fCos) \n"	
-        "{ \n"
-        "    float x = 1.0 - fCos; \n"
-        "    return atmos_fScaleDepth * exp(-0.00287 + x*(0.459 + x*(3.83 + x*(-6.80 + x*5.25)))); \n"
-        "} \n"
-
-        "void atmos_SkyFromSpace(void) \n"
-        "{ \n"
-        "    // Get the ray from the camera to the vertex and its length (which is the far point of the ray passing through the atmosphere) \n"
-        "    vec3 v3Pos = gl_Vertex.xyz; \n"
-        "    vec3 v3Ray = v3Pos - vVec; \n"
-        "    float fFar = length(v3Ray); \n"
-        "    v3Ray /= fFar; \n"
-
-        "    // Calculate the closest intersection of the ray with the outer atmosphere \n"
-        "    // (which is the near point of the ray passing through the atmosphere) \n"
-        "    float B = 2.0 * dot(vVec, v3Ray); \n"
-        "    float C = atmos_fCameraHeight2 - atmos_fOuterRadius2; \n"
-        "    float fDet = max(0.0, B*B - 4.0 * C); \n"	
-        "    float fNear = 0.5 * (-B - sqrt(fDet)); \n"		
-
-        "    // Calculate the ray's starting position, then calculate its scattering offset \n"
-        "    vec3 v3Start = vVec + v3Ray * fNear; \n"			
-        "    fFar -= fNear; \n"	
-        "    float fStartAngle = dot(v3Ray, v3Start) / atmos_fOuterRadius; \n"			
-        "    float fStartDepth = exp(-1.0 / atmos_fScaleDepth); \n"
-        "    float fStartOffset = fStartDepth*atmos_scale(fStartAngle); \n"		
-
-        "    // Initialize the atmos_ing loop variables \n"	
-        "    float fSampleLength = fFar / atmos_fSamples; \n"		
-        "    float fScaledLength = fSampleLength * atmos_fScale; \n"					
-        "    vec3 v3SampleRay = v3Ray * fSampleLength; \n"	
-        "    vec3 v3SamplePoint = v3Start + v3SampleRay * 0.5; \n"	
-
-        "    // Now loop through the sample rays \n"
-        "    vec3 v3FrontColor = vec3(0.0, 0.0, 0.0); \n"
-        "    vec3 v3Attenuate; \n"  
-        "    for(int i=0; i<atmos_nSamples; i++) \n"		
-        "    { \n"
-        "        float fHeight = length(v3SamplePoint); \n"			
-        "        float fDepth = exp(atmos_fScaleOverScaleDepth * (atmos_fInnerRadius - fHeight)); \n"
-        "        float fLightAngle = dot(atmos_v3LightDir, v3SamplePoint) / fHeight; \n"		
-        "        float fCameraAngle = dot(v3Ray, v3SamplePoint) / fHeight; \n"			
-        "        float fscatter = (fStartOffset + fDepth*(atmos_scale(fLightAngle) - atmos_scale(fCameraAngle))); \n"	
-        "        v3Attenuate = exp(-fscatter * (atmos_v3InvWavelength * atmos_fKr4PI + atmos_fKm4PI)); \n"	
-        "        v3FrontColor += v3Attenuate * (fDepth * fScaledLength); \n"					
-        "        v3SamplePoint += v3SampleRay; \n"		
-        "    } \n"		
-
-        "    // Finally, scale the Mie and Rayleigh colors and set up the varying \n"			
-        "    // variables for the pixel shader \n"	
-        "    atmos_mieColor      = v3FrontColor * atmos_fKmESun; \n"				
-        "    atmos_rayleighColor = v3FrontColor * (atmos_v3InvWavelength * atmos_fKrESun); \n"						
-        "    atmos_v3Direction = vVec  - v3Pos; \n"			
-        "} \n"		
-
-        "void atmos_SkyFromAtmosphere(void) \n"		
-        "{ \n"
-        "  // Get the ray from the camera to the vertex, and its length (which is the far \n"
-        "  // point of the ray passing through the atmosphere) \n"		
-        "  vec3 v3Pos = gl_Vertex.xyz; \n"	
-        "  vec3 v3Ray = v3Pos - vVec; \n"			
-        "  float fFar = length(v3Ray); \n"					
-        "  v3Ray /= fFar; \n"				
-
-        "  // Calculate the ray's starting position, then calculate its atmos_ing offset \n"
-        "  vec3 v3Start = vVec; \n"
-        "  float fHeight = length(v3Start); \n"		
-        "  float fDepth = exp(atmos_fScaleOverScaleDepth * (atmos_fInnerRadius - atmos_fCameraHeight)); \n"
-        "  float fStartAngle = dot(v3Ray, v3Start) / fHeight; \n"	
-        "  float fStartOffset = fDepth*atmos_scale(fStartAngle); \n"
-
-        "  // Initialize the atmos_ing loop variables \n"		
-        "  float fSampleLength = fFar / atmos_fSamples; \n"			
-        "  float fScaledLength = fSampleLength * atmos_fScale; \n"				
-        "  vec3 v3SampleRay = v3Ray * fSampleLength; \n"		
-        "  vec3 v3SamplePoint = v3Start + v3SampleRay * 0.5; \n"
-
-        "  // Now loop through the sample rays \n"		
-        "  vec3 v3FrontColor = vec3(0.0, 0.0, 0.0); \n"		
-        "  vec3 v3Attenuate; \n"  
-        "  for(int i=0; i<atmos_nSamples; i++) \n"			
-        "  { \n"	
-        "    float fHeight = length(v3SamplePoint); \n"	
-        "    float fDepth = exp(atmos_fScaleOverScaleDepth * (atmos_fInnerRadius - fHeight)); \n"
-        "    float fLightAngle = dot(atmos_v3LightDir, v3SamplePoint) / fHeight; \n"
-        "    float fCameraAngle = dot(v3Ray, v3SamplePoint) / fHeight; \n"	
-        "    float fscatter = (fStartOffset + fDepth*(atmos_scale(fLightAngle) - atmos_scale(fCameraAngle))); \n"	
-        "    v3Attenuate = exp(-fscatter * (atmos_v3InvWavelength * atmos_fKr4PI + atmos_fKm4PI)); \n"	
-        "    v3FrontColor += v3Attenuate * (fDepth * fScaledLength); \n"		
-        "    v3SamplePoint += v3SampleRay; \n"		
-        "  } \n"
-
-        "  // Finally, scale the Mie and Rayleigh colors and set up the varying \n"
-        "  // variables for the pixel shader \n"					
-        "  atmos_mieColor      = v3FrontColor * atmos_fKmESun; \n"			
-        "  atmos_rayleighColor = v3FrontColor * (atmos_v3InvWavelength * atmos_fKrESun); \n"				
-        "  atmos_v3Direction = vVec - v3Pos; \n"				
-        "} \n"
-
-        "void atmos_vertex_main(inout vec4 VertexVIEW) \n"
-        "{ \n"
-        "    // Get camera position and height \n"
-        "    vVec = osg_ViewMatrixInverse[3].xyz; \n"
-        "    atmos_fCameraHeight = length(vVec); \n"
-        "    atmos_fCameraHeight2 = atmos_fCameraHeight*atmos_fCameraHeight; \n"
-        "    if(atmos_fCameraHeight >= atmos_fOuterRadius) \n"
-        "    { \n"
-        "        atmos_SkyFromSpace(); \n"
-        "    } \n"
-        "    else { \n"
-        "        atmos_SkyFromAtmosphere(); \n"
-        "    } \n"
-        "} \n";
-
-    static char* Atmosphere_Fragment = 
-        "uniform vec3 atmos_v3LightDir; \n"
-
-        "uniform float atmos_g; \n"				
-        "uniform float atmos_g2; \n"
-        "uniform float atmos_fWeather; \n"
-
-        "varying vec3 atmos_v3Direction; \n"	
-        "varying vec3 atmos_mieColor; \n"
-        "varying vec3 atmos_rayleighColor; \n"
-
-        "const float fExposure = 4.0; \n"
-
-        "float atmos_fastpow(in float x, in float y) \n"
-        "{ \n"
-        "    return x/(x+y-y*x); \n"
-        "} \n"
-
-        "void atmos_fragment_main(inout vec4 color) \n"
-        "{ \n"				
-        "    float fCos = dot(atmos_v3LightDir, atmos_v3Direction) / length(atmos_v3Direction); \n"
-        "    float fRayleighPhase = 1.0; \n" // 0.75 * (1.0 + fCos*fCos); \n"
-        "    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); \n"
-        "    vec3 f4Color = fRayleighPhase * atmos_rayleighColor + fMiePhase * atmos_mieColor; \n"
-        "    vec3 skyColor = 1.0 - exp(f4Color * -fExposure); \n"
-        "    color.rgb = skyColor.rgb*atmos_fWeather; \n"
-        "    color.a = (skyColor.r+skyColor.g+skyColor.b) * 2.0; \n"
-        "} \n";
-
-
-    //-- GROUND LIGHTING WITH ATMOSPHERIC SCATTERING --------------------------
-    
-    static const char* Ground_Scattering_Vertex =
-        "uniform bool oe_mode_GL_LIGHTING; \n"
-
-        "uniform mat4 osg_ViewMatrixInverse;   // world camera position in [3].xyz \n"
-        "uniform mat4 osg_ViewMatrix;          // GL view matrix \n"
-        "uniform vec3 atmos_v3LightDir;        // The direction vector to the light source \n"
-        "uniform vec3 atmos_v3InvWavelength;   // 1 / pow(wavelength,4) for the rgb channels \n"
-        "uniform float atmos_fOuterRadius;     // Outer atmosphere radius \n"
-        "uniform float atmos_fOuterRadius2;    // fOuterRadius^2 \n"		
-        "uniform float atmos_fInnerRadius;     // Inner planetary radius \n"
-        "uniform float atmos_fInnerRadius2;    // fInnerRadius^2 \n"
-        "uniform float atmos_fKrESun;          // Kr * ESun \n"	
-        "uniform float atmos_fKmESun;          // Km * ESun \n"		
-        "uniform float atmos_fKr4PI;           // Kr * 4 * PI \n"	
-        "uniform float atmos_fKm4PI;           // Km * 4 * PI \n"		
-        "uniform float atmos_fScale;           // 1 / (fOuterRadius - fInnerRadius) \n"	
-        "uniform float atmos_fScaleDepth;      // The scale depth \n"
-        "uniform float atmos_fScaleOverScaleDepth;     // fScale / fScaleDepth \n"	
-        "uniform int atmos_nSamples; \n"	
-        "uniform float atmos_fSamples; \n"
-
-        "varying vec3 atmos_color; \n"         // primary sky light color
-        "varying vec3 atmos_atten; \n"         // sky light attenuation factor
-        "varying vec3 oe_Normal; \n"           // surface normal (from osgEarth)
-        "varying vec3 atmos_lightDir; \n"      // light direction in view space
-        
-        "float atmos_fCameraHeight;            // The camera's current height \n"		
-        "float atmos_fCameraHeight2;           // fCameraHeight^2 \n"
-
-        "varying vec3 atmos_up; \n"            // earth up vector at vertex location (not the normal)
-        "varying float atmos_space; \n"        // [0..1]: camera: 0=inner radius (ground); 1.0=outer radius
-        "varying vec3 atmos_vert; \n"
-
-        "float atmos_scale(float fCos) \n"	
-        "{ \n"
-        "    float x = 1.0 - fCos; \n"
-        "    return atmos_fScaleDepth * exp(-0.00287 + x*(0.459 + x*(3.83 + x*(-6.80 + x*5.25)))); \n"
-        "} \n"
-
-        "void atmos_GroundFromSpace(in vec4 vertexVIEW) \n"
-        "{ \n"
-        "    // Get the ray from the camera to the vertex and its length (which is the far point of the ray passing through the atmosphere) \n"
-        "    vec3 v3Pos = vertexVIEW.xyz; \n"
-        "    vec3 v3Ray = v3Pos; \n"
-        "    float fFar = length(v3Ray); \n"
-        "    v3Ray /= fFar; \n"
-                
-        "    vec4 ec4 = osg_ViewMatrix * vec4(0,0,0,1); \n"
-        "    vec3 earthCenter = ec4.xyz/ec4.w; \n"
-        "    vec3 normal = normalize(v3Pos-earthCenter); \n"
-        "    atmos_up = normal; \n"
-
-        "    // Calculate the closest intersection of the ray with the outer atmosphere \n"
-        "    // (which is the near point of the ray passing through the atmosphere) \n"
-        "    float B = 2.0 * dot(-earthCenter, v3Ray); \n"
-        "    float C = atmos_fCameraHeight2 - atmos_fOuterRadius2; \n"
-        "    float fDet = max(0.0, B*B - 4.0*C); \n"	
-        "    float fNear = 0.5 * (-B - sqrt(fDet)); \n"		
-
-        "    // Calculate the ray's starting position, then calculate its scattering offset \n"
-        "    vec3 v3Start = v3Ray * fNear; \n"			
-        "    fFar -= fNear; \n"
-        "    float fDepth = exp((atmos_fInnerRadius - atmos_fOuterRadius) / atmos_fScaleDepth);\n"
-        "    float fCameraAngle = dot(-v3Ray, normal); \n"
-        "    float fLightAngle = dot(atmos_lightDir, normal); \n"
-        "    float fCameraScale = atmos_scale(fCameraAngle); \n"
-        "    float fLightScale = atmos_scale(fLightAngle); \n"
-        "    float fCameraOffset = fDepth*fCameraScale; \n"
-        "    float fTemp = fLightScale * fCameraScale; \n"		
-
-        "    // Initialize the scattering loop variables \n"
-        "    float fSampleLength = fFar / atmos_fSamples; \n"		
-        "    float fScaledLength = fSampleLength * atmos_fScale; \n"					
-        "    vec3 v3SampleRay = v3Ray * fSampleLength; \n"	
-        "    vec3 v3SamplePoint = v3Start + v3SampleRay * 0.5; \n"	
-
-        "    // Now loop through the sample rays \n"
-        "    vec3 v3FrontColor = vec3(0.0, 0.0, 0.0); \n"
-        "    vec3 v3Attenuate = vec3(1,0,0); \n"
-
-        "    for(int i=0; i<atmos_nSamples; ++i) \n"
-        "    { \n"        
-        "        float fHeight = length(v3SamplePoint-earthCenter); \n"			
-        "        float fDepth = exp(atmos_fScaleOverScaleDepth * (atmos_fInnerRadius - fHeight)); \n"
-        "        float fScatter = fDepth*fTemp - fCameraOffset; \n"
-        "        v3Attenuate = exp(-fScatter * (atmos_v3InvWavelength * atmos_fKr4PI + atmos_fKm4PI)); \n"	
-        "        v3FrontColor += v3Attenuate * (fDepth * fScaledLength); \n"					
-        "        v3SamplePoint += v3SampleRay; \n"		
-        "    } \n"	
-
-        "    atmos_color = v3FrontColor * (atmos_v3InvWavelength * atmos_fKrESun + atmos_fKmESun); \n"
-        "    atmos_atten = v3Attenuate; \n"
-        "} \n"		
-
-        "void atmos_GroundFromAtmosphere(in vec4 vertexVIEW) \n"		
-        "{ \n"
-        "    // Get the ray from the camera to the vertex and its length (which is the far point of the ray passing through the atmosphere) \n"
-        "    vec3 v3Pos = vertexVIEW.xyz / vertexVIEW.w; \n"
-        "    vec3 v3Ray = v3Pos; \n"
-        "    float fFar = length(v3Ray); \n"
-        "    v3Ray /= fFar; \n"
-        
-        "    vec4 ec4 = osg_ViewMatrix * vec4(0,0,0,1); \n"
-        "    vec3 earthCenter = ec4.xyz/ec4.w; \n"
-        "    vec3 normal = normalize(v3Pos-earthCenter); \n"
-        "    atmos_up = normal; \n"
-
-        "    // Calculate the ray's starting position, then calculate its scattering offset \n"
-        "    float fDepth = exp((atmos_fInnerRadius - atmos_fCameraHeight) / atmos_fScaleDepth);\n"
-        "    float fCameraAngle = max(0.0, dot(-v3Ray, normal)); \n"
-        "    float fLightAngle = dot(atmos_lightDir, normal); \n"
-        "    float fCameraScale = atmos_scale(fCameraAngle); \n"
-        "    float fLightScale = atmos_scale(fLightAngle); \n"
-        "    float fCameraOffset = fDepth*fCameraScale; \n"
-        "    float fTemp = fLightScale * fCameraScale; \n"
-
-        "    // Initialize the scattering loop variables \n"	
-        "    float fSampleLength = fFar / atmos_fSamples; \n"		
-        "    float fScaledLength = fSampleLength * atmos_fScale; \n"					
-        "    vec3 v3SampleRay = v3Ray * fSampleLength; \n"	
-        "    vec3 v3SamplePoint = v3SampleRay * 0.5; \n"	
-
-        "    // Now loop through the sample rays \n"
-        "    vec3 v3FrontColor = vec3(0.0, 0.0, 0.0); \n"
-        "    vec3 v3Attenuate; \n"  
-        "    for(int i=0; i<atmos_nSamples; i++) \n"		
-        "    { \n"
-        "        float fHeight = length(v3SamplePoint-earthCenter); \n"			
-        "        float fDepth = exp(atmos_fScaleOverScaleDepth * (atmos_fInnerRadius - fHeight)); \n"
-        "        float fScatter = fDepth*fTemp - fCameraOffset; \n"
-        "        v3Attenuate = exp(-fScatter * (atmos_v3InvWavelength * atmos_fKr4PI + atmos_fKm4PI)); \n"	
-        "        v3FrontColor += v3Attenuate * (fDepth * fScaledLength); \n"					
-        "        v3SamplePoint += v3SampleRay; \n"		
-        "    } \n"		
-
-        "    atmos_color = v3FrontColor * (atmos_v3InvWavelength * atmos_fKrESun + atmos_fKmESun); \n"			
-        "    atmos_atten = v3Attenuate; \n"
-        "} \n"
-
-        "void atmos_vertex_main(inout vec4 vertexVIEW) \n"
-        "{ \n"
-        "    if ( oe_mode_GL_LIGHTING == false ) return; \n"
-
-        "    atmos_fCameraHeight = length(osg_ViewMatrixInverse[3].xyz); \n"
-        "    atmos_fCameraHeight2 = atmos_fCameraHeight*atmos_fCameraHeight; \n"
-        "    atmos_lightDir = normalize(gl_LightSource[0].position.xyz); \n" // view space
-        "    atmos_vert = vertexVIEW.xyz; \n"
-
-        "    atmos_space = (atmos_fCameraHeight-atmos_fInnerRadius)/(atmos_fOuterRadius-atmos_fInnerRadius); \n"
-        "    atmos_space = clamp(atmos_space, 0.0, 1.0); \n"
-
-        "    if(atmos_fCameraHeight >= atmos_fOuterRadius) \n"
-        "    { \n"
-        "        atmos_GroundFromSpace(vertexVIEW); \n"
-        "    } \n"
-        "    else \n"
-        "    { \n"
-        "        atmos_GroundFromAtmosphere(vertexVIEW); \n"
-        "    } \n"
-        "} \n";
-
-    static const char* Ground_Scattering_Fragment =
-        "uniform bool oe_mode_GL_LIGHTING; \n"
-        "uniform float atmos_exposure; \n"  // scene exposure (ground level)
-        "varying vec3 atmos_lightDir; \n"   // light direction (view coords)
-        "varying vec3 oe_Normal; \n"        // vertex normal (view coords)
-        "varying vec3 atmos_color; \n"      // atmospheric lighting color
-        "varying vec3 atmos_atten; \n"      // atmospheric lighting attentuation factor
-        "varying vec3 atmos_up; \n"         // earth up vector at fragment (in view coords)
-        "varying float atmos_space; \n"     // camera altitude (0=ground, 1=atmos outer radius)
-        "varying vec3 atmos_vert; \n"
-
-        "void atmos_fragment_main(inout vec4 color) \n"
-        "{ \n"
-        "    if ( oe_mode_GL_LIGHTING == false ) return; \n"
-
-        "    vec3 ambient = gl_LightSource[0].ambient.rgb;\n"
-
-        "    vec3 N = normalize(oe_Normal); \n"
-        "    vec3 L = normalize(gl_LightSource[0].position.xyz); \n"
-        "    vec3 U = normalize(atmos_up); \n"
-        "    float NdotL = max(dot(N,L), 0.0); \n"
-        "    float NdotLnormalized = (dot(N,L)+1.0)*0.5;\n"
-        "    NdotL = mix(NdotL, NdotLnormalized, ambient.r); \n"
-
-        // Calculate how much normal-based shading should be applied.
-        // Between "low" and "high" (which are NdotL's) we phase out the
-        // normal-based shading (since the atmospheric scattering will
-        // take over at that point). This provides a nice dawn or dusk
-        // "shining" effect on the sides of buildings or terrain.
-        // Adjust the "low": Higher value will increase the effect of
-        // normal-based shading at shallower sun angles (i.e. it will
-        // become more prominent. Perhaps it makes sense in the future to
-        // make this a "scattering effect" uniform or something -gw)
-        "    const float low = 0.5; \n" //0.6; \n"
-        "    const float high = 0.9; \n"
-        "    float UdotL = clamp(dot(U,L), low, high); \n"
-        "    float shadeFactor = 1.0 - (UdotL-low)/(high-low); \n"
-
-        // start applying normal-based shading when we're at twice the 
-        // altitude of the atmosphere's outer radius:
-        "    float normFactor = max(0.0, 1.0-(2.0*atmos_space)); \n"
-
-        // try to brighten up surfaces the sun is shining on
-        "    float overExposure = 1.0; \n" //1.0+(max(NdotL-0.5, 0.0)*0.25);\n"
-
-        // calculate the base scene color. Skip ambience since we'll be
-        // factoring that in later.
-        "    vec4 sceneColor = mix(color*overExposure, color*NdotL, normFactor*shadeFactor); \n"
-
-        "    if (NdotL > 0.0 ) { \n"
-        "        vec3 V = normalize(atmos_vert); \n"
-        "        vec3 H = normalize(L-V); \n"
-        "        float HdotN = max(dot(H,N), 0.0); \n"
-        "        float shine = clamp(gl_FrontMaterial.shininess, 1.0, 128.0); \n"
-        "        sceneColor += gl_FrontLightProduct[0].specular * pow(HdotN, shine); \n"
-        "    } \n"
-
-        // clamp the attentuation to the minimum ambient lighting:
-        "    vec3 attenuation = max(atmos_atten, ambient); \n"
-
-        // ramp exposure from ground (full) to space (50%).
-        "    float exposure = atmos_exposure*clamp(1.0-atmos_space, 0.5, 1.0); \n"
-
-        "    vec3 atmosColor = 1.0 - exp(-exposure * (atmos_color + sceneColor.rgb * attenuation)); \n"
-        "    color.rgb = gl_FrontMaterial.emission.rgb + atmosColor; \n"
-        "} \n";
-
-    //-- BASIC PHONG GROUND LIGHTING ------------------------------------------
-
-#ifdef OSG_GLES2_AVAILABLE
-    static const char* Ground_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 oe_Normal; \n"
-
-        "void atmos_vertex_main(inout vec4 VertexVIEW) \n"
-        "{ \n"
-        "    oe_lighting_adjustment = vec4(1.0); \n"
-        "    if (oe_mode_GL_LIGHTING) \n"
-        "    { \n"
-        "        vec3 N = oe_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"
-        "} \n";
-
-    static const char* Ground_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 atmos_fragment_main(inout vec4 color) \n"
-        "{ \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).
-        "    if ( oe_mode_GL_LIGHTING ) \n"
-        "    { \n"
-        "        float alpha = color.a; \n"
-        "        color = color * oe_lighting_adjustment + oe_lighting_zero_vec; \n"
-        "        color.a = alpha; \n"
-        "    } \n"
-        "} \n";
-
-#else
-    static const char* Ground_Phong_Vertex =
-        "#version " GLSL_VERSION_STR "\n"
-        GLSL_DEFAULT_PRECISION_FLOAT "\n"
-        
-        "uniform bool oe_mode_GL_LIGHTING; \n"
-        "varying vec3 atmos_vertexView3; \n"
-
-        "void atmos_vertex_main(inout vec4 VertexVIEW) \n"
-        "{ \n"
-        "    if ( oe_mode_GL_LIGHTING == false ) return; \n"
-
-        "    atmos_vertexView3 = VertexVIEW.xyz / VertexVIEW.w; \n"
-        "} \n";
-
-    static const char* Ground_Phong_Fragment =
-        "#version " GLSL_VERSION_STR "\n"
-        GLSL_DEFAULT_PRECISION_FLOAT "\n"
-
-        "uniform bool oe_mode_GL_LIGHTING; \n"
-        "varying vec3 atmos_vertexView3; \n"
-        "varying vec3 oe_Normal; \n"
-
-        "void atmos_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(atmos_vertexView3); \n"
-        "    vec3 N = normalize(oe_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
-
-
-    //-- SUN ----------------------------------------------------
-
-    static const char* Sun_Vertex =
-        "varying vec3 atmos_v3Direction; \n"
-        "void main() \n"
-        "{ \n"
-        "    vec3 v3Pos = gl_Vertex.xyz; \n"
-        "    gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; \n"
-        "    atmos_v3Direction = vec3(0.0,0.0,1.0) - v3Pos; \n"
-        "    atmos_v3Direction = atmos_v3Direction/length(atmos_v3Direction); \n"
-        "} \n";
-
-    static const char* Sun_Fragment =
-        "#version " GLSL_VERSION_STR "\n"
-        GLSL_DEFAULT_PRECISION_FLOAT "\n"
-
-        "uniform float atmos_sunAlpha; \n"
-        "varying vec3 atmos_v3Direction; \n"
-
-        "float atmos_fastpow(in float x, in float y) \n"
-        "{ \n"
-        "    return x/(x+y-y*x); \n"
-        "} \n"
-
-        "void main( void ) \n"
-        "{ \n"
-        "   float fCos = -atmos_v3Direction[2]; \n"         
-        "   float fMiePhase = 0.050387596899224826 * (1.0 + fCos*fCos) / atmos_fastpow(1.9024999999999999 - -1.8999999999999999*fCos, 1.5); \n"
-        "   gl_FragColor.rgb = fMiePhase*vec3(.3,.3,.2); \n"
-        "   gl_FragColor.a = atmos_sunAlpha*gl_FragColor.r; \n"
-        "} \n";
-
-    // -- MOON -------------------------------------------------
-
-    static const char* Moon_Vertex = 
-        "uniform mat4 osg_ModelViewProjectionMatrix;"
-        "varying vec4 moon_TexCoord;\n"
-        "void main() \n"
-        "{ \n"
-        "    moon_TexCoord = gl_MultiTexCoord0; \n"
-        "    gl_Position = osg_ModelViewProjectionMatrix * gl_Vertex; \n"
-        "} \n";
-
-    static const char* Moon_Fragment =
-        "varying vec4 moon_TexCoord;\n"
-        "uniform sampler2D moonTex;\n"
-        "void main( void ) \n"
-        "{ \n"
-        "   gl_FragColor = texture2D(moonTex, moon_TexCoord.st);\n"
-        "} \n";
-
-    // -- STARS ------------------------------------------------
-
-    static const char* Stars_Vertex_110 =
-        "#version " GLSL_VERSION_STR "\n"
-        GLSL_DEFAULT_PRECISION_FLOAT "\n"
-
-        "uniform vec3 atmos_v3LightDir; \n"
-        "uniform mat4 osg_ViewMatrixInverse; \n"
-        "varying float visibility; \n"
-        "varying vec4 osg_FrontColor; \n"
-
-        "float remap( float val, float vmin, float vmax, float r0, float r1 ) \n"
-        "{ \n"
-        "    float vr = (clamp(val, vmin, vmax)-vmin)/(vmax-vmin); \n"
-        "    return r0 + vr * (r1-r0); \n"
-        "} \n"
-
-        "void main() \n"
-        "{ \n"
-        "    osg_FrontColor = gl_Color; \n"
-        "    gl_PointSize = gl_Color.r * 2.0; \n"
-        "    gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; \n"
-        "    vec3 eye = osg_ViewMatrixInverse[3].xyz; \n"
-        "    float hae = length(eye) - 6378137.0; \n"
-        // "highness": visibility increases with altitude
-        "    float highness = remap( hae, 25000.0, 150000.0, 0.0, 1.0 ); \n"
-        "    eye = normalize(eye); \n"
-        // "darkness": visibility increase as the sun goes around the other side of the earth
-        "    float darkness = 1.0-remap(dot(eye,atmos_v3LightDir), -0.25, 0.0, 0.0, 1.0); \n"
-        "    visibility = clamp(highness + darkness, 0.0, 1.0); \n"
-        "} \n";
-
-    static const char* Stars_Fragment_110 =
-        "#version " GLSL_VERSION_STR "\n"
-        GLSL_DEFAULT_PRECISION_FLOAT "\n"
-
-        "varying float visibility; \n"
-        "varying vec4 osg_FrontColor; \n"
-        "void main( void ) \n"
-        "{ \n"
-        "    gl_FragColor = osg_FrontColor * visibility; \n"
-        "} \n";
-
-
-    static const char* Stars_Vertex_120 =
-        "#version 120\n"
-
-        "uniform vec3 atmos_v3LightDir; \n"
-        "uniform mat4 osg_ViewMatrixInverse; \n"
-        "varying float visibility; \n"
-        "varying vec4 osg_FrontColor; \n"
-
-        "float remap( float val, float vmin, float vmax, float r0, float r1 ) \n"
-        "{ \n"
-        "    float vr = (clamp(val, vmin, vmax)-vmin)/(vmax-vmin); \n"
-        "    return r0 + vr * (r1-r0); \n"
-        "} \n"
-
-        "void main() \n"
-        "{ \n"
-        "    osg_FrontColor = gl_Color; \n"
-        "    gl_PointSize = gl_Color.r * 14.0; \n"
-        "    gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; \n"
-        "    vec3 eye = osg_ViewMatrixInverse[3].xyz; \n"
-        "    float hae = length(eye) - 6378137.0; \n"
-        // "highness": visibility increases with altitude
-        "    float highness = remap( hae, 25000.0, 150000.0, 0.0, 1.0 ); \n"
-        "    eye = normalize(eye); \n"
-        // "darkness": visibility increase as the sun goes around the other side of the earth
-        "    float darkness = 1.0-remap(dot(eye,atmos_v3LightDir), -0.25, 0.0, 0.0, 1.0); \n"
-        "    visibility = clamp(highness + darkness, 0.0, 1.0); \n"
-        "} \n";
-
-    static const char* Stars_Fragment_120 =
-        "#version 120\n"
-        "varying float visibility; \n"
-        "varying vec4 osg_FrontColor; \n"
-        "void main( void ) \n"
-        "{ \n"
-        "    float b1 = 1.0-(2.0*abs(gl_PointCoord.s-0.5)); \n"
-        "    float b2 = 1.0-(2.0*abs(gl_PointCoord.t-0.5)); \n"
-        "    float i = b1*b1 * b2*b2; \n"
-        "    gl_FragColor = osg_FrontColor * i * visibility; \n"
-        "} \n";
+    struct Shaders : public osgEarth::ShaderPackage
+	{
+        Shaders();
+		std::string Atmosphere_Vert;
+		std::string Atmosphere_Frag;
+        std::string Ground_ONeil_Vert;
+        std::string Ground_ONeil_Frag;
+        std::string Moon_Vert;
+        std::string Moon_Frag;
+        std::string Stars_Vert;
+        std::string Stars_Frag;
+        std::string Stars_GLES_Vert;
+        std::string Stars_GLES_Frag;
+        std::string Sun_Vert;
+        std::string Sun_Frag;
+	};
 
 } } } // namespace osgEarth::Drivers::SimpleSky
 
diff --git a/src/osgEarthDrivers/sky_simple/SimpleSkyShaders.cpp.in b/src/osgEarthDrivers/sky_simple/SimpleSkyShaders.cpp.in
new file mode 100644
index 0000000..c234184
--- /dev/null
+++ b/src/osgEarthDrivers/sky_simple/SimpleSkyShaders.cpp.in
@@ -0,0 +1,45 @@
+// ***DO NOT EDIT THIS FILE - IT IS AUTOMATICALLY GENERATED BY CMAKE***
+
+#include <osgEarthDrivers/sky_simple/SimpleSkyShaders>
+
+
+using namespace osgEarth::Drivers::SimpleSky;
+
+Shaders::Shaders()
+{
+    Atmosphere_Vert = "SimpleSky.Atmosphere.vert.glsl";
+    _sources[Atmosphere_Vert] = OE_MULTILINE(@SimpleSky.Atmosphere.vert.glsl@);
+
+    Atmosphere_Frag = "SimpleSky.Atmosphere.frag.glsl";
+    _sources[Atmosphere_Frag] = OE_MULTILINE(@SimpleSky.Atmosphere.frag.glsl@);
+
+    Ground_ONeil_Vert = "SimpleSky.Ground.ONeil.vert.glsl";
+    _sources[Ground_ONeil_Vert] = OE_MULTILINE(@SimpleSky.Ground.ONeil.vert.glsl@);
+
+    Ground_ONeil_Frag = "SimpleSky.Ground.ONeil.frag.glsl";
+    _sources[Ground_ONeil_Frag] = OE_MULTILINE(@SimpleSky.Ground.ONeil.frag.glsl@);
+
+    Moon_Vert = "SimpleSky.Moon.vert.glsl";
+    _sources[Moon_Vert] = OE_MULTILINE(@SimpleSky.Moon.vert.glsl@);
+
+    Moon_Frag = "SimpleSky.Moon.frag.glsl";
+    _sources[Moon_Frag] = OE_MULTILINE(@SimpleSky.Moon.frag.glsl@);
+
+    Stars_Vert = "SimpleSky.Stars.vert.glsl";
+    _sources[Stars_Vert] = OE_MULTILINE(@SimpleSky.Stars.vert.glsl@);
+
+    Stars_Frag = "SimpleSky.Stars.frag.glsl";
+    _sources[Stars_Frag] = OE_MULTILINE(@SimpleSky.Stars.frag.glsl@);
+    
+    Stars_GLES_Vert = "SimpleSky.Stars.GLES.vert.glsl";
+    _sources[Stars_GLES_Vert] = OE_MULTILINE(@SimpleSky.Stars.GLES.vert.glsl@);
+
+    Stars_GLES_Frag = "SimpleSky.Stars.GLES.frag.glsl";
+    _sources[Stars_GLES_Frag] = OE_MULTILINE(@SimpleSky.Stars.GLES.frag.glsl@);
+
+    Sun_Vert = "SimpleSky.Sun.vert.glsl";
+    _sources[Sun_Vert] = OE_MULTILINE(@SimpleSky.Sun.vert.glsl@);
+
+    Sun_Frag = "SimpleSky.Sun.frag.glsl";
+    _sources[Sun_Frag] = OE_MULTILINE(@SimpleSky.Sun.frag.glsl@);
+}
diff --git a/src/osgEarthDrivers/splat_mask/SplatMaskDriver.cpp b/src/osgEarthDrivers/splat_mask/SplatMaskDriver.cpp
index 04497e6..69c7392 100644
--- a/src/osgEarthDrivers/splat_mask/SplatMaskDriver.cpp
+++ b/src/osgEarthDrivers/splat_mask/SplatMaskDriver.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/splat_mask/SplatMaskOptions b/src/osgEarthDrivers/splat_mask/SplatMaskOptions
index 4cca8ea..95e06d8 100644
--- a/src/osgEarthDrivers/splat_mask/SplatMaskOptions
+++ b/src/osgEarthDrivers/splat_mask/SplatMaskOptions
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/template_matclass/TemplateMatClassDriver.cpp b/src/osgEarthDrivers/template_matclass/TemplateMatClassDriver.cpp
index 120fb20..f60f51b 100644
--- a/src/osgEarthDrivers/template_matclass/TemplateMatClassDriver.cpp
+++ b/src/osgEarthDrivers/template_matclass/TemplateMatClassDriver.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/template_matclass/TemplateMatClassOptions b/src/osgEarthDrivers/template_matclass/TemplateMatClassOptions
index 5001ac8..070593b 100644
--- a/src/osgEarthDrivers/template_matclass/TemplateMatClassOptions
+++ b/src/osgEarthDrivers/template_matclass/TemplateMatClassOptions
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/tilecache/ReaderWriterTileCache.cpp b/src/osgEarthDrivers/tilecache/ReaderWriterTileCache.cpp
index 3fc7c42..9289596 100644
--- a/src/osgEarthDrivers/tilecache/ReaderWriterTileCache.cpp
+++ b/src/osgEarthDrivers/tilecache/ReaderWriterTileCache.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/tilecache/TileCacheOptions b/src/osgEarthDrivers/tilecache/TileCacheOptions
index fe220e7..f276eab 100644
--- a/src/osgEarthDrivers/tilecache/TileCacheOptions
+++ b/src/osgEarthDrivers/tilecache/TileCacheOptions
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/tileindex/ReaderWriterTileIndex.cpp b/src/osgEarthDrivers/tileindex/ReaderWriterTileIndex.cpp
index 085e51b..e6c2b63 100644
--- a/src/osgEarthDrivers/tileindex/ReaderWriterTileIndex.cpp
+++ b/src/osgEarthDrivers/tileindex/ReaderWriterTileIndex.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/osgEarthDrivers/tileindex/TileIndexOptions b/src/osgEarthDrivers/tileindex/TileIndexOptions
index df5d7b0..f1d8deb 100644
--- a/src/osgEarthDrivers/tileindex/TileIndexOptions
+++ b/src/osgEarthDrivers/tileindex/TileIndexOptions
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/tileservice/ReaderWriterTileService.cpp b/src/osgEarthDrivers/tileservice/ReaderWriterTileService.cpp
index c050b1d..ab029b2 100644
--- a/src/osgEarthDrivers/tileservice/ReaderWriterTileService.cpp
+++ b/src/osgEarthDrivers/tileservice/ReaderWriterTileService.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/tileservice/TileServiceOptions b/src/osgEarthDrivers/tileservice/TileServiceOptions
index 0809615..5cffb96 100644
--- a/src/osgEarthDrivers/tileservice/TileServiceOptions
+++ b/src/osgEarthDrivers/tileservice/TileServiceOptions
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/tms/TMSOptions b/src/osgEarthDrivers/tms/TMSOptions
index f4e2a4f..9de1f7b 100644
--- a/src/osgEarthDrivers/tms/TMSOptions
+++ b/src/osgEarthDrivers/tms/TMSOptions
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/tms/TMSPlugin.cpp b/src/osgEarthDrivers/tms/TMSPlugin.cpp
index a3d0b1e..8e29479 100644
--- a/src/osgEarthDrivers/tms/TMSPlugin.cpp
+++ b/src/osgEarthDrivers/tms/TMSPlugin.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/osgEarthDrivers/tms/TMSTileSource b/src/osgEarthDrivers/tms/TMSTileSource
index d5b3333..6f514ad 100644
--- a/src/osgEarthDrivers/tms/TMSTileSource
+++ b/src/osgEarthDrivers/tms/TMSTileSource
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/osgEarthDrivers/tms/TMSTileSource.cpp b/src/osgEarthDrivers/tms/TMSTileSource.cpp
index 167dc43..e4bb374 100644
--- a/src/osgEarthDrivers/tms/TMSTileSource.cpp
+++ b/src/osgEarthDrivers/tms/TMSTileSource.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/osgEarthDrivers/vdatum_egm2008/EGM2008.cpp b/src/osgEarthDrivers/vdatum_egm2008/EGM2008.cpp
index e17b917..7cc085b 100644
--- a/src/osgEarthDrivers/vdatum_egm2008/EGM2008.cpp
+++ b/src/osgEarthDrivers/vdatum_egm2008/EGM2008.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/vdatum_egm2008/EGM2008Grid.h b/src/osgEarthDrivers/vdatum_egm2008/EGM2008Grid.h
index 8847ed3..5068cd8 100644
--- a/src/osgEarthDrivers/vdatum_egm2008/EGM2008Grid.h
+++ b/src/osgEarthDrivers/vdatum_egm2008/EGM2008Grid.h
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/vdatum_egm84/EGM84.cpp b/src/osgEarthDrivers/vdatum_egm84/EGM84.cpp
index d4fc584..4184902 100644
--- a/src/osgEarthDrivers/vdatum_egm84/EGM84.cpp
+++ b/src/osgEarthDrivers/vdatum_egm84/EGM84.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -62,7 +62,7 @@ namespace
                     unsigned outc = unsigned( (inputLon-origin.x())/colStep );
                     unsigned outr = unsigned( (inputLat-origin.y())/rowStep );
 
-                    Linear h( (double)s_egm84grid[r*cols+c], Units::CENTIMETERS );
+                    Distance h( (double)s_egm84grid[r*cols+c], Units::CENTIMETERS );
                     hf->setHeight( outc, outr, float(h.as(Units::METERS)) );
                 }
             }
diff --git a/src/osgEarthDrivers/vdatum_egm84/EGM84Grid.h b/src/osgEarthDrivers/vdatum_egm84/EGM84Grid.h
index 0cb18a2..fda23fa 100644
--- a/src/osgEarthDrivers/vdatum_egm84/EGM84Grid.h
+++ b/src/osgEarthDrivers/vdatum_egm84/EGM84Grid.h
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/vdatum_egm96/EGM96.cpp b/src/osgEarthDrivers/vdatum_egm96/EGM96.cpp
index 3859152..4838895 100644
--- a/src/osgEarthDrivers/vdatum_egm96/EGM96.cpp
+++ b/src/osgEarthDrivers/vdatum_egm96/EGM96.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/vdatum_egm96/EGM96Grid.h b/src/osgEarthDrivers/vdatum_egm96/EGM96Grid.h
index ffffac9..e7090d2 100644
--- a/src/osgEarthDrivers/vdatum_egm96/EGM96Grid.h
+++ b/src/osgEarthDrivers/vdatum_egm96/EGM96Grid.h
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/vpb/ReaderWriterVPB.cpp b/src/osgEarthDrivers/vpb/ReaderWriterVPB.cpp
index d1df239..e16b280 100644
--- a/src/osgEarthDrivers/vpb/ReaderWriterVPB.cpp
+++ b/src/osgEarthDrivers/vpb/ReaderWriterVPB.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/vpb/VPBOptions b/src/osgEarthDrivers/vpb/VPBOptions
index 7c34f3c..9f2b3d1 100644
--- a/src/osgEarthDrivers/vpb/VPBOptions
+++ b/src/osgEarthDrivers/vpb/VPBOptions
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/wcs/ReaderWriterWCS.cpp b/src/osgEarthDrivers/wcs/ReaderWriterWCS.cpp
index 70486d8..ff425b6 100644
--- a/src/osgEarthDrivers/wcs/ReaderWriterWCS.cpp
+++ b/src/osgEarthDrivers/wcs/ReaderWriterWCS.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/wcs/WCS11Source.cpp b/src/osgEarthDrivers/wcs/WCS11Source.cpp
index 2de0c87..68d621e 100644
--- a/src/osgEarthDrivers/wcs/WCS11Source.cpp
+++ b/src/osgEarthDrivers/wcs/WCS11Source.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/wcs/WCS11Source.h b/src/osgEarthDrivers/wcs/WCS11Source.h
index 2f7ed10..2db2654 100644
--- a/src/osgEarthDrivers/wcs/WCS11Source.h
+++ b/src/osgEarthDrivers/wcs/WCS11Source.h
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/wcs/WCSOptions b/src/osgEarthDrivers/wcs/WCSOptions
index e21c3cb..031bd39 100644
--- a/src/osgEarthDrivers/wcs/WCSOptions
+++ b/src/osgEarthDrivers/wcs/WCSOptions
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/wms/ReaderWriterWMS.cpp b/src/osgEarthDrivers/wms/ReaderWriterWMS.cpp
index 8094550..95326e5 100644
--- a/src/osgEarthDrivers/wms/ReaderWriterWMS.cpp
+++ b/src/osgEarthDrivers/wms/ReaderWriterWMS.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -23,6 +23,7 @@
 #include <osgEarth/TimeControl>
 #include <osgEarth/XmlUtils>
 #include <osgEarth/ImageUtils>
+#include <osgEarth/Containers>
 #include <osgEarthUtil/WMS>
 #include <osgDB/FileNameUtils>
 #include <osgDB/FileUtils>
@@ -120,13 +121,13 @@ public:
         }
         else
         {
-            OE_INFO << "[osgEarth::WMS] Got capabilities from " << capUrl.full() << std::endl;
+            OE_INFO << LC << "Got capabilities from " << capUrl.full() << std::endl;
         }
 
         if ( _formatToUse.empty() && capabilities.valid() )
         {
             _formatToUse = capabilities->suggestExtension();
-            OE_INFO << "[osgEarth::WMS] No format specified, capabilities suggested extension " << _formatToUse << std::endl;
+            OE_INFO << LC << "No format specified, capabilities suggested extension " << _formatToUse << std::endl;
         }
 
         if ( _formatToUse.empty() )
@@ -234,11 +235,11 @@ public:
             tsUrl = URI(_options.url()->full() + sep + std::string("request=GetTileService") );
         }
 
-        OE_INFO << "[osgEarth::WMS] Testing for JPL/TileService at " << tsUrl.full() << std::endl;
+        OE_INFO << LC << "Testing for JPL/TileService at " << tsUrl.full() << std::endl;
         _tileService = TileServiceReader::read(tsUrl.full(), dbOptions);
         if (_tileService.valid())
         {
-            OE_INFO << "[osgEarth::WMS] Found JPL/TileService spec" << std::endl;
+            OE_INFO << LC << "Found JPL/TileService spec" << std::endl;
             TileService::TilePatternList patterns;
             _tileService->getMatchingPatterns(
                 _options.layers().value(),
@@ -257,7 +258,7 @@ public:
         }
         else
         {
-            OE_INFO << "[osgEarth::WMS] No JPL/TileService spec found; assuming standard WMS" << std::endl;
+            OE_INFO << LC << "No JPL/TileService spec found; assuming standard WMS" << std::endl;
         }
 
         // Use the override profile if one is passed in.
@@ -268,7 +269,7 @@ public:
 
         if ( getProfile() )
         {
-            OE_NOTICE << "[osgEarth::WMS] Profile=" << getProfile()->toString() << std::endl;
+            OE_INFO << LC << "Profile=" << getProfile()->toString() << std::endl;
 
             // set up the cache options properly for a TileSource.
             _dbOptions = Registry::instance()->cloneOrCreateOptions( dbOptions );            
@@ -308,11 +309,19 @@ public:
 
         // Try to get the image first
         out_response = URI( uri ).readImage( _dbOptions.get(), progress);
+
+        if ( out_response.succeeded() )
+        {
+            image = out_response.getImage();
+        }
         
-        
+#if 0
         if ( !out_response.succeeded() )
         {
-            // If it failed, try to read it again as a string to get the exception.
+            // If it failed, see whether there's any info in the response.
+            OE_WARN << LC << "Failed, response:\n" << out_response.metadata().toJSON(true);
+            
+            // BAD: caches string data in an image cache. -gw
             out_response = URI( uri ).readString( _dbOptions.get(), progress );      
 
             // get the mime type:
@@ -329,23 +338,25 @@ public:
                     Config ex = se.child("serviceexceptionreport").child("serviceexception");
                     if ( !ex.empty() )
                     {
-                        OE_NOTICE << "WMS Service Exception: " << ex.toJSON(true) << std::endl;
+                        OE_INFO << LC << "WMS Service Exception: " << ex.toJSON(true) << std::endl;
                     }
                     else
                     {
-                        OE_NOTICE << "WMS Response: " << se.toJSON(true) << std::endl;
+                        OE_INFO << LC << "WMS Response: " << se.toJSON(true) << std::endl;
                     }
                 }
                 else
                 {
-                    OE_NOTICE << "WMS: unknown error." << std::endl;
+                    OE_INFO << LC << "WMS: unknown error." << std::endl;
                 }
-            }            
+            }     
         }
         else
         {
             image = out_response.getImage();
         }
+#endif
+
         return image.release();
     }
 
@@ -559,7 +570,7 @@ private:
     bool                             _isPlaying;
     std::vector<SequenceFrameInfo>   _seqFrameInfoVec;
 
-    mutable Threading::ThreadSafeObserverSet<osg::ImageSequence> _sequenceCache;
+    mutable ThreadSafeObserverSet<osg::ImageSequence> _sequenceCache;
 };
 
 
diff --git a/src/osgEarthDrivers/wms/TileService b/src/osgEarthDrivers/wms/TileService
index 6391f96..921e2c2 100644
--- a/src/osgEarthDrivers/wms/TileService
+++ b/src/osgEarthDrivers/wms/TileService
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/wms/TileService.cpp b/src/osgEarthDrivers/wms/TileService.cpp
index ba8b20d..bdc7023 100644
--- a/src/osgEarthDrivers/wms/TileService.cpp
+++ b/src/osgEarthDrivers/wms/TileService.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/wms/WMSOptions b/src/osgEarthDrivers/wms/WMSOptions
index 0764bf3..eedbd6e 100644
--- a/src/osgEarthDrivers/wms/WMSOptions
+++ b/src/osgEarthDrivers/wms/WMSOptions
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/xyz/ReaderWriterXYZ.cpp b/src/osgEarthDrivers/xyz/ReaderWriterXYZ.cpp
index 1372d0f..a60ffa9 100644
--- a/src/osgEarthDrivers/xyz/ReaderWriterXYZ.cpp
+++ b/src/osgEarthDrivers/xyz/ReaderWriterXYZ.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/osgEarthDrivers/xyz/XYZOptions b/src/osgEarthDrivers/xyz/XYZOptions
index 4d35e4b..f3d5460 100644
--- a/src/osgEarthDrivers/xyz/XYZOptions
+++ b/src/osgEarthDrivers/xyz/XYZOptions
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/yahoo/ReaderWriterYahoo.cpp b/src/osgEarthDrivers/yahoo/ReaderWriterYahoo.cpp
index a0c4254..ad9d3f6 100644
--- a/src/osgEarthDrivers/yahoo/ReaderWriterYahoo.cpp
+++ b/src/osgEarthDrivers/yahoo/ReaderWriterYahoo.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/yahoo/YahooOptions b/src/osgEarthDrivers/yahoo/YahooOptions
index f480c3d..77a2c16 100644
--- a/src/osgEarthDrivers/yahoo/YahooOptions
+++ b/src/osgEarthDrivers/yahoo/YahooOptions
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/cache_sqlite3/CMakeLists.txt b/src/osgEarthDriversDisabled/cache_sqlite3/cache_sqlite3/CMakeLists.txt
similarity index 100%
rename from src/osgEarthDrivers/cache_sqlite3/CMakeLists.txt
rename to src/osgEarthDriversDisabled/cache_sqlite3/cache_sqlite3/CMakeLists.txt
diff --git a/src/osgEarthDrivers/cache_sqlite3/Sqlite3Cache.cpp b/src/osgEarthDriversDisabled/cache_sqlite3/cache_sqlite3/Sqlite3Cache.cpp
similarity index 100%
rename from src/osgEarthDrivers/cache_sqlite3/Sqlite3Cache.cpp
rename to src/osgEarthDriversDisabled/cache_sqlite3/cache_sqlite3/Sqlite3Cache.cpp
diff --git a/src/osgEarthDrivers/cache_sqlite3/Sqlite3CacheOptions b/src/osgEarthDriversDisabled/cache_sqlite3/cache_sqlite3/Sqlite3CacheOptions
similarity index 100%
rename from src/osgEarthDrivers/cache_sqlite3/Sqlite3CacheOptions
rename to src/osgEarthDriversDisabled/cache_sqlite3/cache_sqlite3/Sqlite3CacheOptions
diff --git a/src/osgEarthDrivers/engine_droam/AMRGeometry b/src/osgEarthDriversDisabled/engine_droam/AMRGeometry
similarity index 100%
rename from src/osgEarthDrivers/engine_droam/AMRGeometry
rename to src/osgEarthDriversDisabled/engine_droam/AMRGeometry
diff --git a/src/osgEarthDrivers/engine_droam/AMRGeometry.cpp b/src/osgEarthDriversDisabled/engine_droam/AMRGeometry.cpp
similarity index 100%
rename from src/osgEarthDrivers/engine_droam/AMRGeometry.cpp
rename to src/osgEarthDriversDisabled/engine_droam/AMRGeometry.cpp
diff --git a/src/osgEarthDrivers/engine_droam/AMRShaders.h b/src/osgEarthDriversDisabled/engine_droam/AMRShaders.h
similarity index 100%
rename from src/osgEarthDrivers/engine_droam/AMRShaders.h
rename to src/osgEarthDriversDisabled/engine_droam/AMRShaders.h
diff --git a/src/osgEarthDrivers/engine_droam/CMakeLists.txt b/src/osgEarthDriversDisabled/engine_droam/CMakeLists.txt
similarity index 100%
rename from src/osgEarthDrivers/engine_droam/CMakeLists.txt
rename to src/osgEarthDriversDisabled/engine_droam/CMakeLists.txt
diff --git a/src/osgEarthDrivers/engine_droam/Common b/src/osgEarthDriversDisabled/engine_droam/Common
similarity index 100%
rename from src/osgEarthDrivers/engine_droam/Common
rename to src/osgEarthDriversDisabled/engine_droam/Common
diff --git a/src/osgEarthDrivers/engine_droam/CubeManifold b/src/osgEarthDriversDisabled/engine_droam/CubeManifold
similarity index 100%
rename from src/osgEarthDrivers/engine_droam/CubeManifold
rename to src/osgEarthDriversDisabled/engine_droam/CubeManifold
diff --git a/src/osgEarthDrivers/engine_droam/CubeManifold.cpp b/src/osgEarthDriversDisabled/engine_droam/CubeManifold.cpp
similarity index 100%
rename from src/osgEarthDrivers/engine_droam/CubeManifold.cpp
rename to src/osgEarthDriversDisabled/engine_droam/CubeManifold.cpp
diff --git a/src/osgEarthDrivers/engine_droam/DRoamNode b/src/osgEarthDriversDisabled/engine_droam/DRoamNode
similarity index 100%
rename from src/osgEarthDrivers/engine_droam/DRoamNode
rename to src/osgEarthDriversDisabled/engine_droam/DRoamNode
diff --git a/src/osgEarthDrivers/engine_droam/DRoamNode.cpp b/src/osgEarthDriversDisabled/engine_droam/DRoamNode.cpp
similarity index 100%
rename from src/osgEarthDrivers/engine_droam/DRoamNode.cpp
rename to src/osgEarthDriversDisabled/engine_droam/DRoamNode.cpp
diff --git a/src/osgEarthDrivers/engine_droam/Diamond b/src/osgEarthDriversDisabled/engine_droam/Diamond
similarity index 100%
rename from src/osgEarthDrivers/engine_droam/Diamond
rename to src/osgEarthDriversDisabled/engine_droam/Diamond
diff --git a/src/osgEarthDrivers/engine_droam/Diamond.cpp b/src/osgEarthDriversDisabled/engine_droam/Diamond.cpp
similarity index 100%
rename from src/osgEarthDrivers/engine_droam/Diamond.cpp
rename to src/osgEarthDriversDisabled/engine_droam/Diamond.cpp
diff --git a/src/osgEarthDrivers/engine_droam/GeodeticManifold b/src/osgEarthDriversDisabled/engine_droam/GeodeticManifold
similarity index 100%
rename from src/osgEarthDrivers/engine_droam/GeodeticManifold
rename to src/osgEarthDriversDisabled/engine_droam/GeodeticManifold
diff --git a/src/osgEarthDrivers/engine_droam/GeodeticManifold.cpp b/src/osgEarthDriversDisabled/engine_droam/GeodeticManifold.cpp
similarity index 100%
rename from src/osgEarthDrivers/engine_droam/GeodeticManifold.cpp
rename to src/osgEarthDriversDisabled/engine_droam/GeodeticManifold.cpp
diff --git a/src/osgEarthDrivers/engine_droam/Manifold b/src/osgEarthDriversDisabled/engine_droam/Manifold
similarity index 100%
rename from src/osgEarthDrivers/engine_droam/Manifold
rename to src/osgEarthDriversDisabled/engine_droam/Manifold
diff --git a/src/osgEarthDrivers/engine_droam/Manifold.cpp b/src/osgEarthDriversDisabled/engine_droam/Manifold.cpp
similarity index 100%
rename from src/osgEarthDrivers/engine_droam/Manifold.cpp
rename to src/osgEarthDriversDisabled/engine_droam/Manifold.cpp
diff --git a/src/osgEarthDrivers/engine_droam/MeshManager b/src/osgEarthDriversDisabled/engine_droam/MeshManager
similarity index 100%
rename from src/osgEarthDrivers/engine_droam/MeshManager
rename to src/osgEarthDriversDisabled/engine_droam/MeshManager
diff --git a/src/osgEarthDrivers/engine_droam/MeshManager.cpp b/src/osgEarthDriversDisabled/engine_droam/MeshManager.cpp
similarity index 100%
rename from src/osgEarthDrivers/engine_droam/MeshManager.cpp
rename to src/osgEarthDriversDisabled/engine_droam/MeshManager.cpp
diff --git a/src/osgEarthDrivers/engine_droam/Plugin.cpp b/src/osgEarthDriversDisabled/engine_droam/Plugin.cpp
similarity index 100%
rename from src/osgEarthDrivers/engine_droam/Plugin.cpp
rename to src/osgEarthDriversDisabled/engine_droam/Plugin.cpp
diff --git a/src/osgEarthDrivers/engine_osgterrain/CMakeLists.txt b/src/osgEarthDriversDisabled/engine_osgterrain/CMakeLists.txt
similarity index 100%
rename from src/osgEarthDrivers/engine_osgterrain/CMakeLists.txt
rename to src/osgEarthDriversDisabled/engine_osgterrain/CMakeLists.txt
diff --git a/src/osgEarthDrivers/engine_osgterrain/Common b/src/osgEarthDriversDisabled/engine_osgterrain/Common
similarity index 100%
rename from src/osgEarthDrivers/engine_osgterrain/Common
rename to src/osgEarthDriversDisabled/engine_osgterrain/Common
diff --git a/src/osgEarthDrivers/engine_osgterrain/CustomTerrain b/src/osgEarthDriversDisabled/engine_osgterrain/CustomTerrain
similarity index 100%
rename from src/osgEarthDrivers/engine_osgterrain/CustomTerrain
rename to src/osgEarthDriversDisabled/engine_osgterrain/CustomTerrain
diff --git a/src/osgEarthDrivers/engine_osgterrain/CustomTerrain.cpp b/src/osgEarthDriversDisabled/engine_osgterrain/CustomTerrain.cpp
similarity index 100%
rename from src/osgEarthDrivers/engine_osgterrain/CustomTerrain.cpp
rename to src/osgEarthDriversDisabled/engine_osgterrain/CustomTerrain.cpp
diff --git a/src/osgEarthDrivers/engine_osgterrain/CustomTerrainTechnique b/src/osgEarthDriversDisabled/engine_osgterrain/CustomTerrainTechnique
similarity index 100%
rename from src/osgEarthDrivers/engine_osgterrain/CustomTerrainTechnique
rename to src/osgEarthDriversDisabled/engine_osgterrain/CustomTerrainTechnique
diff --git a/src/osgEarthDrivers/engine_osgterrain/CustomTile b/src/osgEarthDriversDisabled/engine_osgterrain/CustomTile
similarity index 100%
rename from src/osgEarthDrivers/engine_osgterrain/CustomTile
rename to src/osgEarthDriversDisabled/engine_osgterrain/CustomTile
diff --git a/src/osgEarthDrivers/engine_osgterrain/CustomTile.cpp b/src/osgEarthDriversDisabled/engine_osgterrain/CustomTile.cpp
similarity index 100%
rename from src/osgEarthDrivers/engine_osgterrain/CustomTile.cpp
rename to src/osgEarthDriversDisabled/engine_osgterrain/CustomTile.cpp
diff --git a/src/osgEarthDrivers/engine_osgterrain/DynamicLODScaleCallback b/src/osgEarthDriversDisabled/engine_osgterrain/DynamicLODScaleCallback
similarity index 100%
rename from src/osgEarthDrivers/engine_osgterrain/DynamicLODScaleCallback
rename to src/osgEarthDriversDisabled/engine_osgterrain/DynamicLODScaleCallback
diff --git a/src/osgEarthDrivers/engine_osgterrain/FileLocationCallback b/src/osgEarthDriversDisabled/engine_osgterrain/FileLocationCallback
similarity index 100%
rename from src/osgEarthDrivers/engine_osgterrain/FileLocationCallback
rename to src/osgEarthDriversDisabled/engine_osgterrain/FileLocationCallback
diff --git a/src/osgEarthDrivers/engine_osgterrain/KeyNodeFactory b/src/osgEarthDriversDisabled/engine_osgterrain/KeyNodeFactory
similarity index 100%
rename from src/osgEarthDrivers/engine_osgterrain/KeyNodeFactory
rename to src/osgEarthDriversDisabled/engine_osgterrain/KeyNodeFactory
diff --git a/src/osgEarthDrivers/engine_osgterrain/KeyNodeFactory.cpp b/src/osgEarthDriversDisabled/engine_osgterrain/KeyNodeFactory.cpp
similarity index 100%
rename from src/osgEarthDrivers/engine_osgterrain/KeyNodeFactory.cpp
rename to src/osgEarthDriversDisabled/engine_osgterrain/KeyNodeFactory.cpp
diff --git a/src/osgEarthDrivers/engine_osgterrain/LODFactorCallback b/src/osgEarthDriversDisabled/engine_osgterrain/LODFactorCallback
similarity index 100%
rename from src/osgEarthDrivers/engine_osgterrain/LODFactorCallback
rename to src/osgEarthDriversDisabled/engine_osgterrain/LODFactorCallback
diff --git a/src/osgEarthDrivers/engine_osgterrain/LODFactorCallback.cpp b/src/osgEarthDriversDisabled/engine_osgterrain/LODFactorCallback.cpp
similarity index 100%
rename from src/osgEarthDrivers/engine_osgterrain/LODFactorCallback.cpp
rename to src/osgEarthDriversDisabled/engine_osgterrain/LODFactorCallback.cpp
diff --git a/src/osgEarthDrivers/engine_osgterrain/MultiPassTerrainTechnique b/src/osgEarthDriversDisabled/engine_osgterrain/MultiPassTerrainTechnique
similarity index 100%
rename from src/osgEarthDrivers/engine_osgterrain/MultiPassTerrainTechnique
rename to src/osgEarthDriversDisabled/engine_osgterrain/MultiPassTerrainTechnique
diff --git a/src/osgEarthDrivers/engine_osgterrain/MultiPassTerrainTechnique.cpp b/src/osgEarthDriversDisabled/engine_osgterrain/MultiPassTerrainTechnique.cpp
similarity index 100%
rename from src/osgEarthDrivers/engine_osgterrain/MultiPassTerrainTechnique.cpp
rename to src/osgEarthDriversDisabled/engine_osgterrain/MultiPassTerrainTechnique.cpp
diff --git a/src/osgEarthDrivers/engine_osgterrain/OSGTerrainEngineNode b/src/osgEarthDriversDisabled/engine_osgterrain/OSGTerrainEngineNode
similarity index 100%
rename from src/osgEarthDrivers/engine_osgterrain/OSGTerrainEngineNode
rename to src/osgEarthDriversDisabled/engine_osgterrain/OSGTerrainEngineNode
diff --git a/src/osgEarthDrivers/engine_osgterrain/OSGTerrainEngineNode.cpp b/src/osgEarthDriversDisabled/engine_osgterrain/OSGTerrainEngineNode.cpp
similarity index 100%
rename from src/osgEarthDrivers/engine_osgterrain/OSGTerrainEngineNode.cpp
rename to src/osgEarthDriversDisabled/engine_osgterrain/OSGTerrainEngineNode.cpp
diff --git a/src/osgEarthDrivers/engine_osgterrain/OSGTerrainOptions b/src/osgEarthDriversDisabled/engine_osgterrain/OSGTerrainOptions
similarity index 100%
rename from src/osgEarthDrivers/engine_osgterrain/OSGTerrainOptions
rename to src/osgEarthDriversDisabled/engine_osgterrain/OSGTerrainOptions
diff --git a/src/osgEarthDrivers/engine_osgterrain/OSGTileFactory b/src/osgEarthDriversDisabled/engine_osgterrain/OSGTileFactory
similarity index 100%
rename from src/osgEarthDrivers/engine_osgterrain/OSGTileFactory
rename to src/osgEarthDriversDisabled/engine_osgterrain/OSGTileFactory
diff --git a/src/osgEarthDrivers/engine_osgterrain/OSGTileFactory.cpp b/src/osgEarthDriversDisabled/engine_osgterrain/OSGTileFactory.cpp
similarity index 100%
rename from src/osgEarthDrivers/engine_osgterrain/OSGTileFactory.cpp
rename to src/osgEarthDriversDisabled/engine_osgterrain/OSGTileFactory.cpp
diff --git a/src/osgEarthDrivers/engine_osgterrain/ParallelKeyNodeFactory b/src/osgEarthDriversDisabled/engine_osgterrain/ParallelKeyNodeFactory
similarity index 100%
rename from src/osgEarthDrivers/engine_osgterrain/ParallelKeyNodeFactory
rename to src/osgEarthDriversDisabled/engine_osgterrain/ParallelKeyNodeFactory
diff --git a/src/osgEarthDrivers/engine_osgterrain/ParallelKeyNodeFactory.cpp b/src/osgEarthDriversDisabled/engine_osgterrain/ParallelKeyNodeFactory.cpp
similarity index 100%
rename from src/osgEarthDrivers/engine_osgterrain/ParallelKeyNodeFactory.cpp
rename to src/osgEarthDriversDisabled/engine_osgterrain/ParallelKeyNodeFactory.cpp
diff --git a/src/osgEarthDrivers/engine_osgterrain/Plugin.cpp b/src/osgEarthDriversDisabled/engine_osgterrain/Plugin.cpp
similarity index 100%
rename from src/osgEarthDrivers/engine_osgterrain/Plugin.cpp
rename to src/osgEarthDriversDisabled/engine_osgterrain/Plugin.cpp
diff --git a/src/osgEarthDrivers/engine_osgterrain/SerialKeyNodeFactory b/src/osgEarthDriversDisabled/engine_osgterrain/SerialKeyNodeFactory
similarity index 100%
rename from src/osgEarthDrivers/engine_osgterrain/SerialKeyNodeFactory
rename to src/osgEarthDriversDisabled/engine_osgterrain/SerialKeyNodeFactory
diff --git a/src/osgEarthDrivers/engine_osgterrain/SerialKeyNodeFactory.cpp b/src/osgEarthDriversDisabled/engine_osgterrain/SerialKeyNodeFactory.cpp
similarity index 100%
rename from src/osgEarthDrivers/engine_osgterrain/SerialKeyNodeFactory.cpp
rename to src/osgEarthDriversDisabled/engine_osgterrain/SerialKeyNodeFactory.cpp
diff --git a/src/osgEarthDrivers/engine_osgterrain/SinglePassTerrainTechnique b/src/osgEarthDriversDisabled/engine_osgterrain/SinglePassTerrainTechnique
similarity index 100%
rename from src/osgEarthDrivers/engine_osgterrain/SinglePassTerrainTechnique
rename to src/osgEarthDriversDisabled/engine_osgterrain/SinglePassTerrainTechnique
diff --git a/src/osgEarthDrivers/engine_osgterrain/SinglePassTerrainTechnique.cpp b/src/osgEarthDriversDisabled/engine_osgterrain/SinglePassTerrainTechnique.cpp
similarity index 100%
rename from src/osgEarthDrivers/engine_osgterrain/SinglePassTerrainTechnique.cpp
rename to src/osgEarthDriversDisabled/engine_osgterrain/SinglePassTerrainTechnique.cpp
diff --git a/src/osgEarthDrivers/engine_osgterrain/StreamingTerrainNode b/src/osgEarthDriversDisabled/engine_osgterrain/StreamingTerrainNode
similarity index 100%
rename from src/osgEarthDrivers/engine_osgterrain/StreamingTerrainNode
rename to src/osgEarthDriversDisabled/engine_osgterrain/StreamingTerrainNode
diff --git a/src/osgEarthDrivers/engine_osgterrain/StreamingTerrainNode.cpp b/src/osgEarthDriversDisabled/engine_osgterrain/StreamingTerrainNode.cpp
similarity index 100%
rename from src/osgEarthDrivers/engine_osgterrain/StreamingTerrainNode.cpp
rename to src/osgEarthDriversDisabled/engine_osgterrain/StreamingTerrainNode.cpp
diff --git a/src/osgEarthDrivers/engine_osgterrain/StreamingTile b/src/osgEarthDriversDisabled/engine_osgterrain/StreamingTile
similarity index 100%
rename from src/osgEarthDrivers/engine_osgterrain/StreamingTile
rename to src/osgEarthDriversDisabled/engine_osgterrain/StreamingTile
diff --git a/src/osgEarthDrivers/engine_osgterrain/StreamingTile.cpp b/src/osgEarthDriversDisabled/engine_osgterrain/StreamingTile.cpp
similarity index 100%
rename from src/osgEarthDrivers/engine_osgterrain/StreamingTile.cpp
rename to src/osgEarthDriversDisabled/engine_osgterrain/StreamingTile.cpp
diff --git a/src/osgEarthDrivers/engine_osgterrain/Terrain b/src/osgEarthDriversDisabled/engine_osgterrain/Terrain
similarity index 100%
rename from src/osgEarthDrivers/engine_osgterrain/Terrain
rename to src/osgEarthDriversDisabled/engine_osgterrain/Terrain
diff --git a/src/osgEarthDrivers/engine_osgterrain/Terrain.cpp b/src/osgEarthDriversDisabled/engine_osgterrain/Terrain.cpp
similarity index 100%
rename from src/osgEarthDrivers/engine_osgterrain/Terrain.cpp
rename to src/osgEarthDriversDisabled/engine_osgterrain/Terrain.cpp
diff --git a/src/osgEarthDrivers/engine_osgterrain/TerrainNode b/src/osgEarthDriversDisabled/engine_osgterrain/TerrainNode
similarity index 100%
rename from src/osgEarthDrivers/engine_osgterrain/TerrainNode
rename to src/osgEarthDriversDisabled/engine_osgterrain/TerrainNode
diff --git a/src/osgEarthDrivers/engine_osgterrain/TerrainNode.cpp b/src/osgEarthDriversDisabled/engine_osgterrain/TerrainNode.cpp
similarity index 100%
rename from src/osgEarthDrivers/engine_osgterrain/TerrainNode.cpp
rename to src/osgEarthDriversDisabled/engine_osgterrain/TerrainNode.cpp
diff --git a/src/osgEarthDrivers/engine_osgterrain/Tile b/src/osgEarthDriversDisabled/engine_osgterrain/Tile
similarity index 100%
rename from src/osgEarthDrivers/engine_osgterrain/Tile
rename to src/osgEarthDriversDisabled/engine_osgterrain/Tile
diff --git a/src/osgEarthDrivers/engine_osgterrain/Tile.cpp b/src/osgEarthDriversDisabled/engine_osgterrain/Tile.cpp
similarity index 100%
rename from src/osgEarthDrivers/engine_osgterrain/Tile.cpp
rename to src/osgEarthDriversDisabled/engine_osgterrain/Tile.cpp
diff --git a/src/osgEarthDrivers/engine_osgterrain/TileBuilder b/src/osgEarthDriversDisabled/engine_osgterrain/TileBuilder
similarity index 100%
rename from src/osgEarthDrivers/engine_osgterrain/TileBuilder
rename to src/osgEarthDriversDisabled/engine_osgterrain/TileBuilder
diff --git a/src/osgEarthDrivers/engine_osgterrain/TileBuilder.cpp b/src/osgEarthDriversDisabled/engine_osgterrain/TileBuilder.cpp
similarity index 100%
rename from src/osgEarthDrivers/engine_osgterrain/TileBuilder.cpp
rename to src/osgEarthDriversDisabled/engine_osgterrain/TileBuilder.cpp
diff --git a/src/osgEarthDrivers/engine_osgterrain/TransparentLayer b/src/osgEarthDriversDisabled/engine_osgterrain/TransparentLayer
similarity index 100%
rename from src/osgEarthDrivers/engine_osgterrain/TransparentLayer
rename to src/osgEarthDriversDisabled/engine_osgterrain/TransparentLayer
diff --git a/src/osgEarthDrivers/engine_quadtree/CMakeLists.txt b/src/osgEarthDriversDisabled/engine_quadtree/CMakeLists.txt
similarity index 100%
rename from src/osgEarthDrivers/engine_quadtree/CMakeLists.txt
rename to src/osgEarthDriversDisabled/engine_quadtree/CMakeLists.txt
diff --git a/src/osgEarthDrivers/engine_quadtree/Common b/src/osgEarthDriversDisabled/engine_quadtree/Common
similarity index 100%
rename from src/osgEarthDrivers/engine_quadtree/Common
rename to src/osgEarthDriversDisabled/engine_quadtree/Common
diff --git a/src/osgEarthDrivers/engine_quadtree/CustomPagedLOD b/src/osgEarthDriversDisabled/engine_quadtree/CustomPagedLOD
similarity index 100%
rename from src/osgEarthDrivers/engine_quadtree/CustomPagedLOD
rename to src/osgEarthDriversDisabled/engine_quadtree/CustomPagedLOD
diff --git a/src/osgEarthDrivers/engine_quadtree/CustomPagedLOD.cpp b/src/osgEarthDriversDisabled/engine_quadtree/CustomPagedLOD.cpp
similarity index 100%
rename from src/osgEarthDrivers/engine_quadtree/CustomPagedLOD.cpp
rename to src/osgEarthDriversDisabled/engine_quadtree/CustomPagedLOD.cpp
diff --git a/src/osgEarthDrivers/engine_quadtree/DynamicLODScaleCallback b/src/osgEarthDriversDisabled/engine_quadtree/DynamicLODScaleCallback
similarity index 100%
rename from src/osgEarthDrivers/engine_quadtree/DynamicLODScaleCallback
rename to src/osgEarthDriversDisabled/engine_quadtree/DynamicLODScaleCallback
diff --git a/src/osgEarthDrivers/engine_quadtree/FileLocationCallback b/src/osgEarthDriversDisabled/engine_quadtree/FileLocationCallback
similarity index 100%
rename from src/osgEarthDrivers/engine_quadtree/FileLocationCallback
rename to src/osgEarthDriversDisabled/engine_quadtree/FileLocationCallback
diff --git a/src/osgEarthDrivers/engine_quadtree/KeyNodeFactory b/src/osgEarthDriversDisabled/engine_quadtree/KeyNodeFactory
similarity index 100%
rename from src/osgEarthDrivers/engine_quadtree/KeyNodeFactory
rename to src/osgEarthDriversDisabled/engine_quadtree/KeyNodeFactory
diff --git a/src/osgEarthDrivers/engine_quadtree/KeyNodeFactory.cpp b/src/osgEarthDriversDisabled/engine_quadtree/KeyNodeFactory.cpp
similarity index 100%
rename from src/osgEarthDrivers/engine_quadtree/KeyNodeFactory.cpp
rename to src/osgEarthDriversDisabled/engine_quadtree/KeyNodeFactory.cpp
diff --git a/src/osgEarthDrivers/engine_quadtree/LODFactorCallback b/src/osgEarthDriversDisabled/engine_quadtree/LODFactorCallback
similarity index 100%
rename from src/osgEarthDrivers/engine_quadtree/LODFactorCallback
rename to src/osgEarthDriversDisabled/engine_quadtree/LODFactorCallback
diff --git a/src/osgEarthDrivers/engine_quadtree/LODFactorCallback.cpp b/src/osgEarthDriversDisabled/engine_quadtree/LODFactorCallback.cpp
similarity index 100%
rename from src/osgEarthDrivers/engine_quadtree/LODFactorCallback.cpp
rename to src/osgEarthDriversDisabled/engine_quadtree/LODFactorCallback.cpp
diff --git a/src/osgEarthDrivers/engine_quadtree/QuadTreeTerrainEngineDriver.cpp b/src/osgEarthDriversDisabled/engine_quadtree/QuadTreeTerrainEngineDriver.cpp
similarity index 100%
rename from src/osgEarthDrivers/engine_quadtree/QuadTreeTerrainEngineDriver.cpp
rename to src/osgEarthDriversDisabled/engine_quadtree/QuadTreeTerrainEngineDriver.cpp
diff --git a/src/osgEarthDrivers/engine_quadtree/QuadTreeTerrainEngineNode b/src/osgEarthDriversDisabled/engine_quadtree/QuadTreeTerrainEngineNode
similarity index 100%
rename from src/osgEarthDrivers/engine_quadtree/QuadTreeTerrainEngineNode
rename to src/osgEarthDriversDisabled/engine_quadtree/QuadTreeTerrainEngineNode
diff --git a/src/osgEarthDrivers/engine_quadtree/QuadTreeTerrainEngineNode.cpp b/src/osgEarthDriversDisabled/engine_quadtree/QuadTreeTerrainEngineNode.cpp
similarity index 100%
rename from src/osgEarthDrivers/engine_quadtree/QuadTreeTerrainEngineNode.cpp
rename to src/osgEarthDriversDisabled/engine_quadtree/QuadTreeTerrainEngineNode.cpp
diff --git a/src/osgEarthDrivers/engine_quadtree/QuadTreeTerrainEngineOptions b/src/osgEarthDriversDisabled/engine_quadtree/QuadTreeTerrainEngineOptions
similarity index 100%
rename from src/osgEarthDrivers/engine_quadtree/QuadTreeTerrainEngineOptions
rename to src/osgEarthDriversDisabled/engine_quadtree/QuadTreeTerrainEngineOptions
diff --git a/src/osgEarthDrivers/engine_quadtree/QuickReleaseGLObjects b/src/osgEarthDriversDisabled/engine_quadtree/QuickReleaseGLObjects
similarity index 100%
rename from src/osgEarthDrivers/engine_quadtree/QuickReleaseGLObjects
rename to src/osgEarthDriversDisabled/engine_quadtree/QuickReleaseGLObjects
diff --git a/src/osgEarthDrivers/engine_quadtree/SerialKeyNodeFactory b/src/osgEarthDriversDisabled/engine_quadtree/SerialKeyNodeFactory
similarity index 100%
rename from src/osgEarthDrivers/engine_quadtree/SerialKeyNodeFactory
rename to src/osgEarthDriversDisabled/engine_quadtree/SerialKeyNodeFactory
diff --git a/src/osgEarthDrivers/engine_quadtree/SerialKeyNodeFactory.cpp b/src/osgEarthDriversDisabled/engine_quadtree/SerialKeyNodeFactory.cpp
similarity index 100%
rename from src/osgEarthDrivers/engine_quadtree/SerialKeyNodeFactory.cpp
rename to src/osgEarthDriversDisabled/engine_quadtree/SerialKeyNodeFactory.cpp
diff --git a/src/osgEarthDrivers/engine_quadtree/TerrainNode b/src/osgEarthDriversDisabled/engine_quadtree/TerrainNode
similarity index 100%
rename from src/osgEarthDrivers/engine_quadtree/TerrainNode
rename to src/osgEarthDriversDisabled/engine_quadtree/TerrainNode
diff --git a/src/osgEarthDrivers/engine_quadtree/TerrainNode.cpp b/src/osgEarthDriversDisabled/engine_quadtree/TerrainNode.cpp
similarity index 100%
rename from src/osgEarthDrivers/engine_quadtree/TerrainNode.cpp
rename to src/osgEarthDriversDisabled/engine_quadtree/TerrainNode.cpp
diff --git a/src/osgEarthDrivers/engine_quadtree/TileModel b/src/osgEarthDriversDisabled/engine_quadtree/TileModel
similarity index 100%
rename from src/osgEarthDrivers/engine_quadtree/TileModel
rename to src/osgEarthDriversDisabled/engine_quadtree/TileModel
diff --git a/src/osgEarthDrivers/engine_quadtree/TileModelCompiler b/src/osgEarthDriversDisabled/engine_quadtree/TileModelCompiler
similarity index 100%
rename from src/osgEarthDrivers/engine_quadtree/TileModelCompiler
rename to src/osgEarthDriversDisabled/engine_quadtree/TileModelCompiler
diff --git a/src/osgEarthDrivers/engine_quadtree/TileModelCompiler.cpp b/src/osgEarthDriversDisabled/engine_quadtree/TileModelCompiler.cpp
similarity index 100%
rename from src/osgEarthDrivers/engine_quadtree/TileModelCompiler.cpp
rename to src/osgEarthDriversDisabled/engine_quadtree/TileModelCompiler.cpp
diff --git a/src/osgEarthDrivers/engine_quadtree/TileModelFactory b/src/osgEarthDriversDisabled/engine_quadtree/TileModelFactory
similarity index 100%
rename from src/osgEarthDrivers/engine_quadtree/TileModelFactory
rename to src/osgEarthDriversDisabled/engine_quadtree/TileModelFactory
diff --git a/src/osgEarthDrivers/engine_quadtree/TileModelFactory.cpp b/src/osgEarthDriversDisabled/engine_quadtree/TileModelFactory.cpp
similarity index 100%
rename from src/osgEarthDrivers/engine_quadtree/TileModelFactory.cpp
rename to src/osgEarthDriversDisabled/engine_quadtree/TileModelFactory.cpp
diff --git a/src/osgEarthDrivers/engine_quadtree/TileNode b/src/osgEarthDriversDisabled/engine_quadtree/TileNode
similarity index 100%
rename from src/osgEarthDrivers/engine_quadtree/TileNode
rename to src/osgEarthDriversDisabled/engine_quadtree/TileNode
diff --git a/src/osgEarthDrivers/engine_quadtree/TileNode.cpp b/src/osgEarthDriversDisabled/engine_quadtree/TileNode.cpp
similarity index 100%
rename from src/osgEarthDrivers/engine_quadtree/TileNode.cpp
rename to src/osgEarthDriversDisabled/engine_quadtree/TileNode.cpp
diff --git a/src/osgEarthDrivers/engine_quadtree/TileNodeRegistry b/src/osgEarthDriversDisabled/engine_quadtree/TileNodeRegistry
similarity index 100%
rename from src/osgEarthDrivers/engine_quadtree/TileNodeRegistry
rename to src/osgEarthDriversDisabled/engine_quadtree/TileNodeRegistry
diff --git a/src/osgEarthDrivers/engine_quadtree/TileNodeRegistry.cpp b/src/osgEarthDriversDisabled/engine_quadtree/TileNodeRegistry.cpp
similarity index 100%
rename from src/osgEarthDrivers/engine_quadtree/TileNodeRegistry.cpp
rename to src/osgEarthDriversDisabled/engine_quadtree/TileNodeRegistry.cpp
diff --git a/src/osgEarthDrivers/engine_seamless/AutoBuffer b/src/osgEarthDriversDisabled/engine_seamless/AutoBuffer
similarity index 100%
rename from src/osgEarthDrivers/engine_seamless/AutoBuffer
rename to src/osgEarthDriversDisabled/engine_seamless/AutoBuffer
diff --git a/src/osgEarthDrivers/engine_seamless/CMakeLists.txt b/src/osgEarthDriversDisabled/engine_seamless/CMakeLists.txt
similarity index 100%
rename from src/osgEarthDrivers/engine_seamless/CMakeLists.txt
rename to src/osgEarthDriversDisabled/engine_seamless/CMakeLists.txt
diff --git a/src/osgEarthDrivers/engine_seamless/Euler b/src/osgEarthDriversDisabled/engine_seamless/Euler
similarity index 100%
rename from src/osgEarthDrivers/engine_seamless/Euler
rename to src/osgEarthDriversDisabled/engine_seamless/Euler
diff --git a/src/osgEarthDrivers/engine_seamless/Euler.cpp b/src/osgEarthDriversDisabled/engine_seamless/Euler.cpp
similarity index 100%
rename from src/osgEarthDrivers/engine_seamless/Euler.cpp
rename to src/osgEarthDriversDisabled/engine_seamless/Euler.cpp
diff --git a/src/osgEarthDrivers/engine_seamless/GeoPatch b/src/osgEarthDriversDisabled/engine_seamless/GeoPatch
similarity index 100%
rename from src/osgEarthDrivers/engine_seamless/GeoPatch
rename to src/osgEarthDriversDisabled/engine_seamless/GeoPatch
diff --git a/src/osgEarthDrivers/engine_seamless/GeoPatch.cpp b/src/osgEarthDriversDisabled/engine_seamless/GeoPatch.cpp
similarity index 100%
rename from src/osgEarthDrivers/engine_seamless/GeoPatch.cpp
rename to src/osgEarthDriversDisabled/engine_seamless/GeoPatch.cpp
diff --git a/src/osgEarthDrivers/engine_seamless/Geographic b/src/osgEarthDriversDisabled/engine_seamless/Geographic
similarity index 100%
rename from src/osgEarthDrivers/engine_seamless/Geographic
rename to src/osgEarthDriversDisabled/engine_seamless/Geographic
diff --git a/src/osgEarthDrivers/engine_seamless/Geographic.cpp b/src/osgEarthDriversDisabled/engine_seamless/Geographic.cpp
similarity index 100%
rename from src/osgEarthDrivers/engine_seamless/Geographic.cpp
rename to src/osgEarthDriversDisabled/engine_seamless/Geographic.cpp
diff --git a/src/osgEarthDrivers/engine_seamless/MultiArray b/src/osgEarthDriversDisabled/engine_seamless/MultiArray
similarity index 100%
rename from src/osgEarthDrivers/engine_seamless/MultiArray
rename to src/osgEarthDriversDisabled/engine_seamless/MultiArray
diff --git a/src/osgEarthDrivers/engine_seamless/Patch b/src/osgEarthDriversDisabled/engine_seamless/Patch
similarity index 100%
rename from src/osgEarthDrivers/engine_seamless/Patch
rename to src/osgEarthDriversDisabled/engine_seamless/Patch
diff --git a/src/osgEarthDrivers/engine_seamless/Patch.cpp b/src/osgEarthDriversDisabled/engine_seamless/Patch.cpp
similarity index 100%
rename from src/osgEarthDrivers/engine_seamless/Patch.cpp
rename to src/osgEarthDriversDisabled/engine_seamless/Patch.cpp
diff --git a/src/osgEarthDrivers/engine_seamless/PatchGroup b/src/osgEarthDriversDisabled/engine_seamless/PatchGroup
similarity index 100%
rename from src/osgEarthDrivers/engine_seamless/PatchGroup
rename to src/osgEarthDriversDisabled/engine_seamless/PatchGroup
diff --git a/src/osgEarthDrivers/engine_seamless/PatchGroup.cpp b/src/osgEarthDriversDisabled/engine_seamless/PatchGroup.cpp
similarity index 100%
rename from src/osgEarthDrivers/engine_seamless/PatchGroup.cpp
rename to src/osgEarthDriversDisabled/engine_seamless/PatchGroup.cpp
diff --git a/src/osgEarthDrivers/engine_seamless/PatchInfo b/src/osgEarthDriversDisabled/engine_seamless/PatchInfo
similarity index 100%
rename from src/osgEarthDrivers/engine_seamless/PatchInfo
rename to src/osgEarthDriversDisabled/engine_seamless/PatchInfo
diff --git a/src/osgEarthDrivers/engine_seamless/PatchSet b/src/osgEarthDriversDisabled/engine_seamless/PatchSet
similarity index 100%
rename from src/osgEarthDrivers/engine_seamless/PatchSet
rename to src/osgEarthDriversDisabled/engine_seamless/PatchSet
diff --git a/src/osgEarthDrivers/engine_seamless/PatchSet.cpp b/src/osgEarthDriversDisabled/engine_seamless/PatchSet.cpp
similarity index 100%
rename from src/osgEarthDrivers/engine_seamless/PatchSet.cpp
rename to src/osgEarthDriversDisabled/engine_seamless/PatchSet.cpp
diff --git a/src/osgEarthDrivers/engine_seamless/Projected b/src/osgEarthDriversDisabled/engine_seamless/Projected
similarity index 100%
rename from src/osgEarthDrivers/engine_seamless/Projected
rename to src/osgEarthDriversDisabled/engine_seamless/Projected
diff --git a/src/osgEarthDrivers/engine_seamless/Projected.cpp b/src/osgEarthDriversDisabled/engine_seamless/Projected.cpp
similarity index 100%
rename from src/osgEarthDrivers/engine_seamless/Projected.cpp
rename to src/osgEarthDriversDisabled/engine_seamless/Projected.cpp
diff --git a/src/osgEarthDrivers/engine_seamless/QSC b/src/osgEarthDriversDisabled/engine_seamless/QSC
similarity index 100%
rename from src/osgEarthDrivers/engine_seamless/QSC
rename to src/osgEarthDriversDisabled/engine_seamless/QSC
diff --git a/src/osgEarthDrivers/engine_seamless/QSC.cpp b/src/osgEarthDriversDisabled/engine_seamless/QSC.cpp
similarity index 100%
rename from src/osgEarthDrivers/engine_seamless/QSC.cpp
rename to src/osgEarthDriversDisabled/engine_seamless/QSC.cpp
diff --git a/src/osgEarthDrivers/engine_seamless/SeamlessEngineNode b/src/osgEarthDriversDisabled/engine_seamless/SeamlessEngineNode
similarity index 100%
rename from src/osgEarthDrivers/engine_seamless/SeamlessEngineNode
rename to src/osgEarthDriversDisabled/engine_seamless/SeamlessEngineNode
diff --git a/src/osgEarthDrivers/engine_seamless/SeamlessEngineNode.cpp b/src/osgEarthDriversDisabled/engine_seamless/SeamlessEngineNode.cpp
similarity index 100%
rename from src/osgEarthDrivers/engine_seamless/SeamlessEngineNode.cpp
rename to src/osgEarthDriversDisabled/engine_seamless/SeamlessEngineNode.cpp
diff --git a/src/osgEarthDrivers/engine_seamless/SeamlessOptions b/src/osgEarthDriversDisabled/engine_seamless/SeamlessOptions
similarity index 100%
rename from src/osgEarthDrivers/engine_seamless/SeamlessOptions
rename to src/osgEarthDriversDisabled/engine_seamless/SeamlessOptions
diff --git a/src/osgEarthDrivers/engine_seamless/SeamlessPlugin.cpp b/src/osgEarthDriversDisabled/engine_seamless/SeamlessPlugin.cpp
similarity index 100%
rename from src/osgEarthDrivers/engine_seamless/SeamlessPlugin.cpp
rename to src/osgEarthDriversDisabled/engine_seamless/SeamlessPlugin.cpp
diff --git a/src/osgEarthDrivers/engine_seamless/doc/README b/src/osgEarthDriversDisabled/engine_seamless/doc/README
similarity index 100%
rename from src/osgEarthDrivers/engine_seamless/doc/README
rename to src/osgEarthDriversDisabled/engine_seamless/doc/README
diff --git a/src/osgEarthDrivers/engine_seamless/doc/euler.kml b/src/osgEarthDriversDisabled/engine_seamless/doc/euler.kml
similarity index 100%
rename from src/osgEarthDrivers/engine_seamless/doc/euler.kml
rename to src/osgEarthDriversDisabled/engine_seamless/doc/euler.kml
diff --git a/src/osgEarthDrivers/engine_seamless/doc/notes.org b/src/osgEarthDriversDisabled/engine_seamless/doc/notes.org
similarity index 100%
rename from src/osgEarthDrivers/engine_seamless/doc/notes.org
rename to src/osgEarthDriversDisabled/engine_seamless/doc/notes.org
diff --git a/src/osgEarthDriversDisabled/feature_mapnikvectortiles/CMakeLists.txt b/src/osgEarthDriversDisabled/feature_mapnikvectortiles/CMakeLists.txt
new file mode 100644
index 0000000..7f0214e
--- /dev/null
+++ b/src/osgEarthDriversDisabled/feature_mapnikvectortiles/CMakeLists.txt
@@ -0,0 +1,29 @@
+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
new file mode 100644
index 0000000..22b8e1e
--- /dev/null
+++ b/src/osgEarthDriversDisabled/feature_mapnikvectortiles/FeatureSourceMVT.cpp
@@ -0,0 +1,446 @@
+/* -*-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/MVTFeatureOptions b/src/osgEarthDriversDisabled/feature_mapnikvectortiles/MVTFeatureOptions
new file mode 100644
index 0000000..357086c
--- /dev/null
+++ b/src/osgEarthDriversDisabled/feature_mapnikvectortiles/MVTFeatureOptions
@@ -0,0 +1,76 @@
+/* -*-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_DRIVER_MVT_FEATURE_SOURCE_OPTIONS
+#define OSGEARTH_DRIVER_MVT_FEATURE_SOURCE_OPTIONS 1
+
+#include <osgEarth/Common>
+#include <osgEarth/URI>
+#include <osgEarthFeatures/FeatureSource>
+
+namespace osgEarth { namespace Drivers
+{
+    using namespace osgEarth;
+    using namespace osgEarth::Features;
+
+    /**
+     * Options for the TFS feature driver.
+     */
+    class MVTFeatureOptions : 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; }
+
+    public:
+        MVTFeatureOptions( const ConfigOptions& opt =ConfigOptions() ) :
+          FeatureSourceOptions( opt )
+          {
+            setDriver( "mapnikvectortiles" );            
+            fromConfig( _conf );
+        }
+
+        virtual ~MVTFeatureOptions() { }
+
+    public:
+        Config getConfig() const {
+            Config conf = FeatureSourceOptions::getConfig();
+            conf.updateIfSet( "url", _url ); 
+            return conf;
+        }
+
+    protected:
+        void mergeConfig( const Config& conf ) {
+            FeatureSourceOptions::mergeConfig( conf );
+            fromConfig( conf );
+        }
+
+    private:
+        void fromConfig( const Config& conf ) {
+            conf.getIfSet( "url", _url );
+        }
+
+        optional<URI>         _url;        
+        optional<std::string> _format;
+    };
+
+} } // namespace osgEarth::Drivers
+
+#endif // OSGEARTH_DRIVER_MVT_FEATURE_SOURCE_OPTIONS
+
diff --git a/src/osgEarthDriversDisabled/feature_mapnikvectortiles/vector_tile.proto b/src/osgEarthDriversDisabled/feature_mapnikvectortiles/vector_tile.proto
new file mode 100644
index 0000000..e25ac3b
--- /dev/null
+++ b/src/osgEarthDriversDisabled/feature_mapnikvectortiles/vector_tile.proto
@@ -0,0 +1,92 @@
+// 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/osgEarthDrivers/label_overlay/CMakeLists.txt b/src/osgEarthDriversDisabled/label_overlay/CMakeLists.txt
similarity index 100%
rename from src/osgEarthDrivers/label_overlay/CMakeLists.txt
rename to src/osgEarthDriversDisabled/label_overlay/CMakeLists.txt
diff --git a/src/osgEarthDrivers/label_overlay/OverlayLabelSource b/src/osgEarthDriversDisabled/label_overlay/OverlayLabelSource
similarity index 100%
rename from src/osgEarthDrivers/label_overlay/OverlayLabelSource
rename to src/osgEarthDriversDisabled/label_overlay/OverlayLabelSource
diff --git a/src/osgEarthDriversDisabled/label_overlay/OverlayLabelSource.cpp b/src/osgEarthDriversDisabled/label_overlay/OverlayLabelSource.cpp
new file mode 100644
index 0000000..8ea478f
--- /dev/null
+++ b/src/osgEarthDriversDisabled/label_overlay/OverlayLabelSource.cpp
@@ -0,0 +1,213 @@
+/* -*-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 <osgEarthFeatures/LabelSource>
+#include <osgEarthSymbology/Expression>
+#include <osgEarthUtil/Controls>
+#include <osgEarth/Horizon>
+#include <osgEarth/ECEF>
+#include <osg/ClusterCullingCallback>
+#include <osg/MatrixTransform>
+#include <osgDB/FileNameUtils>
+#include <set>
+
+using namespace osgEarth;
+using namespace osgEarth::Features;
+using namespace osgEarth::Symbology;
+using namespace osgEarth::Util;
+
+
+/******* Deprecated ---- please use AnnotationLabelSource instead *************/
+
+
+class OverlayLabelSource : public LabelSource
+{
+public:
+    OverlayLabelSource( const LabelSourceOptions& options )
+        : LabelSource( options )
+    {
+        //nop
+    }
+
+    /**
+     * Creates a simple label. The caller is responsible for placing it in the scene.
+     */
+    osg::Node* createNode(
+        const std::string& text,
+        const Style&       style )
+    {
+        const TextSymbol* symbol = style.get<TextSymbol>();
+
+        Controls::LabelControl* label = new Controls::LabelControl( text );
+        if ( symbol )
+        {
+            if ( symbol->fill().isSet() )
+                label->setForeColor( symbol->fill()->color() );
+            if ( symbol->halo().isSet() )
+                label->setHaloColor( symbol->halo()->color() );
+            //if ( symbol->size().isSet() )
+            //    label->setFontSize( *symbol->size() );
+            if ( symbol->font().isSet() )
+            {
+                osgText::Font* font = osgText::readFontFile(*symbol->font() );
+                // mitigates mipmapping issues that cause rendering artifacts for some fonts/placement
+                if ( font )
+                    font->setGlyphImageMargin( 2 );
+                label->setFont( font );
+            }
+            if ( symbol->encoding().isSet() )
+            {
+                osgText::String::Encoding enc;
+                switch(symbol->encoding().value())
+                {
+                case TextSymbol::ENCODING_ASCII: enc = osgText::String::ENCODING_ASCII; break;
+                case TextSymbol::ENCODING_UTF8: enc = osgText::String::ENCODING_UTF8; break;
+                case TextSymbol::ENCODING_UTF16: enc = osgText::String::ENCODING_UTF16; break;
+                case TextSymbol::ENCODING_UTF32: enc = osgText::String::ENCODING_UTF32; break;
+                default: enc = osgText::String::ENCODING_UNDEFINED; break;
+                }
+                label->setEncoding( enc );
+            }
+        }
+        Controls::ControlNode* node = new Controls::ControlNode( label );
+        return node;
+    }
+
+    /**
+     * Creates a complete set of positioned label nodes from a feature list.
+     */
+    osg::Node* createNode(
+        const FeatureList&   input,
+        const Style&         style,
+        const FilterContext& context )
+    {
+        const TextSymbol* text = style.get<TextSymbol>();
+
+        osg::Group* group = 0L;
+        std::set<std::string> used; // to prevent dupes
+        bool skipDupes = (text->removeDuplicateLabels() == true);
+
+        StringExpression  contentExpr ( *text->content() );
+        NumericExpression priorityExpr( *text->priority() );
+
+        //bool makeECEF = false;
+        const SpatialReference* ecef = 0L;
+        if ( context.isGeoreferenced() )
+        {
+            //makeECEF = context.getSession()->getMapInfo().isGeocentric();
+            ecef = context.getSession()->getMapSRS()->getECEF();
+        }
+
+        for( FeatureList::const_iterator i = input.begin(); i != input.end(); ++i )
+        {
+            const Feature* feature = i->get();
+            if ( !feature )
+                continue;
+
+            const Geometry* geom = feature->getGeometry();
+            if ( !geom )
+                continue;
+
+            osg::Vec3d centroid  = geom->getBounds().center();
+
+            if ( ecef )
+            {
+                context.profile()->getSRS()->transform( centroid, ecef, centroid );
+                //context.profile()->getSRS()->transformToECEF( centroid, centroid );
+            }
+
+            const std::string& value = feature->eval( contentExpr, &context );
+
+            if ( !value.empty() && (!skipDupes || used.find(value) == used.end()) )
+            {
+                if ( !group )
+                {
+                    group = new osg::Group();
+                }
+
+                double priority = feature->eval( priorityExpr, &context );
+
+                Controls::LabelControl* label = new Controls::LabelControl( value );
+                if ( text->fill().isSet() )
+                    label->setForeColor( text->fill()->color() );
+                if ( text->halo().isSet() )
+                    label->setHaloColor( text->halo()->color() );
+                //if ( text->size().isSet() )
+                //    label->setFontSize( *text->size() );
+                if ( text->font().isSet() )
+                {
+                    // mitigates mipmapping issues that cause rendering artifacts for some fonts/placement
+                    osgText::Font* font = osgText::readFontFile(*text->font() );
+                    // mitigates mipmapping issues that cause rendering artifacts for some fonts/placement
+                    if ( font )
+                        font->setGlyphImageMargin( 2 );
+                    label->setFont( font );
+                }
+
+                Controls::ControlNode* node = new Controls::ControlNode( label, priority );
+
+                osg::MatrixTransform* xform = new osg::MatrixTransform( osg::Matrixd::translate(centroid) );
+                xform->addChild( node );
+
+                // for a geocentric map, do a simple dot product cull.
+                if ( ecef )
+                {
+                    xform->setCullCallback( new HorizonCullCallback(*ecef->getEllipsoid()) );
+                    group->addChild( xform );
+                }
+                else
+                {
+                    group->addChild( xform );
+                }
+
+                if ( skipDupes )
+                {
+                    used.insert( value );
+                }
+            }
+        }
+
+        return group;
+    }
+};
+
+//------------------------------------------------------------------------
+
+class OverlayLabelSourceDriver : public LabelSourceDriver
+{
+public:
+    OverlayLabelSourceDriver()
+    {
+        supportsExtension( "osgearth_label_overlay", "osgEarth overlay label plugin" );
+    }
+
+    virtual const char* className()
+    {
+        return "osgEarth Overlay Label Plugin";
+    }
+
+    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 OverlayLabelSource( getLabelSourceOptions(options) );
+    }
+};
+
+REGISTER_OSGPLUGIN(osgearth_label_overlay, OverlayLabelSourceDriver)
diff --git a/src/osgEarthDrivers/model_feature_label/CMakeLists.txt b/src/osgEarthDriversDisabled/model_feature_label/CMakeLists.txt
similarity index 100%
rename from src/osgEarthDrivers/model_feature_label/CMakeLists.txt
rename to src/osgEarthDriversDisabled/model_feature_label/CMakeLists.txt
diff --git a/src/osgEarthDrivers/model_feature_label/FeatureLabelModelOptions b/src/osgEarthDriversDisabled/model_feature_label/FeatureLabelModelOptions
similarity index 100%
rename from src/osgEarthDrivers/model_feature_label/FeatureLabelModelOptions
rename to src/osgEarthDriversDisabled/model_feature_label/FeatureLabelModelOptions
diff --git a/src/osgEarthDrivers/model_feature_label/FeatureLabelModelSource.cpp b/src/osgEarthDriversDisabled/model_feature_label/FeatureLabelModelSource.cpp
similarity index 100%
rename from src/osgEarthDrivers/model_feature_label/FeatureLabelModelSource.cpp
rename to src/osgEarthDriversDisabled/model_feature_label/FeatureLabelModelSource.cpp
diff --git a/src/osgEarthDrivers/noise/CMakeLists.txt b/src/osgEarthDriversDisabled/noise/CMakeLists.txt
similarity index 100%
rename from src/osgEarthDrivers/noise/CMakeLists.txt
rename to src/osgEarthDriversDisabled/noise/CMakeLists.txt
diff --git a/src/osgEarthDriversDisabled/noise/NoiseDriver.cpp b/src/osgEarthDriversDisabled/noise/NoiseDriver.cpp
new file mode 100644
index 0000000..5e59d9c
--- /dev/null
+++ b/src/osgEarthDriversDisabled/noise/NoiseDriver.cpp
@@ -0,0 +1,322 @@
+/* -*-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 <osgEarth/TileSource>
+#include <osgEarth/Registry>
+#include <osgEarth/URI>
+#include <osgEarth/ImageUtils>
+
+#include <osgEarthUtil/SimplexNoise>
+
+#include <osg/Notify>
+#include <osgDB/FileNameUtils>
+#include <osgDB/FileUtils>
+#include <osgDB/Registry>
+#include <osgDB/ReadFile>
+#include <osgDB/WriteFile>
+#include <sstream>
+#include <cmath>
+
+#include "NoiseOptions"
+
+using namespace osgEarth;
+using namespace osgEarth::Util;
+
+namespace osgEarth { namespace Drivers { namespace Noise
+{
+    class NoiseSource : public TileSource
+    {
+    public:
+        NoiseSource( const TileSourceOptions& options ) : TileSource( options ), _options(options)
+        {
+            _noise.setFrequency  ( *_options.frequency() );
+            _noise.setPersistence( *_options.persistence() );
+            _noise.setLacunarity ( *_options.lacunarity() );
+            _noise.setOctaves    ( *_options.octaves() );
+            _noise.setRange      ( -1.0, 1.0 );
+        }
+
+        // Yahoo! uses spherical mercator, but the top LOD is a 2x2 tile set.
+        Status initialize(const osgDB::Options* dbOptions)
+        {            
+            _dbOptions = Registry::instance()->cloneOrCreateOptions( dbOptions );            
+            setProfile( osgEarth::Registry::instance()->getGlobalGeodeticProfile() );
+
+            // resolve frequency if the user set resolution
+            if (_options.resolution().isSet() && !_options.resolution().isSetTo(0.0))
+            {
+                _noise.setFrequency( 1.0 / *_options.resolution() );
+            }
+
+            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;
+        }
+
+        inline double sample(double x, double y, double z)
+        {
+            return _noise.getValue(x, y, z);
+        }
+
+        inline double sample(const osg::Vec3d& v)
+        {
+            return _noise.getValue(v.x(), v.y(), v.z());
+        }
+
+        inline double turbulence(const osg::Vec3d& v, double f)
+        {
+            double t = -0.5;
+            for( ; f<getPixelsPerTile()/2; f *= 2 ) 
+                t += std::abs(_noise.getValue(v.x(), v.y(), v.z())/f);
+            return t;
+        }
+
+        inline double stripes(double x, double f)
+        {
+            double t = 0.5 + 0.5 * asin(f * 2*osg::PI * x);
+            return t * t - 0.5;
+        }
+
+
+        osg::Image* createImage(const TileKey&    key,
+                                ProgressCallback* progress)
+        {
+            if ( _options.normalMap() == true )
+            {
+                return createNormalMap(key, progress);
+            }
+            else
+            {
+                const SpatialReference* srs = key.getProfile()->getSRS();
+
+                double projNormX, projNormY;
+                if ( srs->isProjected() )
+                {
+                    projNormX = 1.0/key.getProfile()->getExtent().width();
+                    projNormY = 1.0/key.getProfile()->getExtent().height();
+                }
+
+                osg::Image* image = new osg::Image();
+                image->allocateImage( getPixelsPerTile(), getPixelsPerTile(), 1, GL_RGB, 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 x = key.getExtent().xMin() + (double)s * dx;
+                        double y = key.getExtent().yMin() + (double)t * dy;
+                        
+                        osg::Vec3d world(x, y, 0.0);
+
+                        if ( srs->isGeographic() )
+                        {
+                            srs->transform(world, srs->getECEF(), world);
+                            world.normalize();
+                        }
+                        else
+                        {
+                            world.x() *= projNormX;
+                            world.y() *= projNormY;
+                            world.z()  = 0.0;
+                        }
+
+                        double n = sample(world); //world.x(), world.y(), world.z());
+                        //double n = 0.1 * stripes(world.x() + 2.0*turbulence(noise, world, 1.0), 1.6);
+                        //double n = -.10 * turbulence(noise, world, 0.2);
+                        //n = turbulence(world, 1);
+
+                        // scale and bias from[-1..1] to [0..1] for coloring.
+                        n = osg::clampBetween( n+0.5, 0.0, 1.0 );                        
+
+                        write(osg::Vec4f(n,n,n,1), s, t);
+                    }
+                }
+
+                return image;
+            }
+        }
+
+
+        osg::HeightField* createHeightField(const TileKey&        key,
+                                            ProgressCallback*     progress )
+        {
+            const SpatialReference* srs = key.getProfile()->getSRS();
+
+            osg::HeightField* hf = new osg::HeightField();
+            hf->allocate( getPixelsPerTile(), getPixelsPerTile() );
+
+            double dx = key.getExtent().width() / (double)(hf->getNumColumns()-1);
+            double dy = key.getExtent().height() / (double)(hf->getNumRows()-1);
+
+            double bias  = _options.bias().get();
+            double scale = _options.scale().get();
+        
+            //Initialize the heightfield
+            for (unsigned int c = 0; c < hf->getNumColumns(); c++) 
+            {
+                for (unsigned int r = 0; r < hf->getNumRows(); r++)
+                {                
+                    double lon = key.getExtent().xMin() + (double)c * dx;
+                    double lat = key.getExtent().yMin() + (double)r * dy;
+
+                    osg::Vec3d world(lon, lat, 0.0);
+                    if ( srs->isGeographic() )
+                    {
+                        srs->transform(world, srs->getECEF(), world);
+                        world.normalize();
+                    }
+
+                    double n = sample( world );
+
+                    // Scale the noise value.
+                    double h = osg::clampBetween(
+                        (float)(bias + scale * n),
+                        *_options.minElevation(),
+                        *_options.maxElevation() );
+
+                    hf->setHeight( c, r, h );
+
+                    // NOTE! The elevation engine treats extreme values (>32000, etc)
+                    // as "no data" so be careful with your scale.
+                }
+            }     
+
+            return hf;
+        }
+
+
+        osg::Image* createNormalMap(const TileKey& key, ProgressCallback* progress)
+        {
+            // set up the image and prepare to write to it.
+            osg::Image* image = new osg::Image();
+            image->allocateImage( getPixelsPerTile(), getPixelsPerTile(), 1, GL_RGB, GL_UNSIGNED_BYTE );
+            ImageUtils::PixelWriter write(image);
+
+            const GeoExtent&        ex     = key.getExtent();
+            const SpatialReference* srs    = ex.getSRS();
+            bool                    isGeo  = srs->isGeographic();
+            const SpatialReference* ecef   = srs->getECEF();
+
+            double dx = ex.width()  / (double)(image->s()-1);
+            double dy = ex.height() / (double)(image->t()-1);
+
+            double scale  = _options.scale().get();
+            double bias   = _options.bias().get();
+
+            // figure out the spacing between pixels in the same units as the height value:
+            double udx = dx;
+            double udy = dy;
+            if ( isGeo )
+            {
+                udx = srs->transformUnits(dx, ecef, ex.south()+0.5*dy);
+                udy = srs->transformUnits(dy, ecef, ex.south()+0.5*dy);
+            }
+
+            double z = 0.0;
+            std::vector<osg::Vec3d> v(4);
+            double samples[4];
+
+            for(int s=0; s<image->s(); ++s)
+            {
+                for(int t=0; t<image->t(); ++t)
+                {
+                    double x = ex.xMin() + (double)s * dx;
+                    double y = ex.yMin() + (double)t * dy;
+
+                    if ( isGeo )
+                    {
+                        v[0].set(x-dx, y, z);
+                        v[1].set(x+dx, y, z);
+                        v[2].set(x, y+dy, z);
+                        v[3].set(x, y-dy, z);
+                        srs->transform(v, ecef);
+                        for(int i=0; i<4; ++i )
+                            samples[i] = bias + scale * sample(v[i]);
+                    }
+                    else
+                    {
+                        samples[0] = bias + scale * sample(x-dx, y, z);
+                        samples[1] = bias + scale * sample(x+dx, y, z);
+                        samples[2] = bias + scale * sample(x, y+dy, z);
+                        samples[3] = bias + scale * sample(x, y-dy, z);
+                    }
+
+                    osg::Vec3d west (-udx,    0, samples[0]);
+                    osg::Vec3d east ( udx,    0, samples[1]);
+                    osg::Vec3d north(   0,  udy, samples[2]);
+                    osg::Vec3d south(   0, -udy, samples[3]);
+
+                    // calculate the normal at the center point.
+                    osg::Vec3 normal = (east-west) ^ (north-south);
+                    normal.normalize();
+
+                    // encode as: xyz[-1..1]=>r[0..255]. (In reality Z will always fall 
+                    // between [0..1] but a uniform encoding makes the shader code simpler.)
+                    normal.x() = normal.x()*0.5f + 0.5f;
+                    normal.y() = normal.y()*0.5f + 0.5f;
+                    normal.z() = normal.z()*0.5f + 0.5f;
+                    normal.normalize();
+
+                    write(osg::Vec4f(normal,1), s, t);
+                }
+            }
+
+            return image;
+        }
+
+
+    private:
+        NoiseOptions                 _options;
+        osg::ref_ptr<osgDB::Options> _dbOptions;
+        SimplexNoise                 _noise;
+    };
+
+
+    class NoiseDriver : public TileSourceDriver
+    {
+        public:
+            NoiseDriver()
+            {
+                supportsExtension( "osgearth_noise", "Procedural noise generator" );
+            }
+
+            virtual const char* className()
+            {
+                return "Procedural noise generator";
+            }
+
+            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 NoiseSource( getTileSourceOptions(options) );
+            }
+    };
+
+    REGISTER_OSGPLUGIN(osgearth_noise, NoiseDriver);
+
+} } } // namespace osgEarth::Drivers::Noise
diff --git a/src/osgEarthDrivers/noise/NoiseOptions b/src/osgEarthDriversDisabled/noise/NoiseOptions
similarity index 100%
rename from src/osgEarthDrivers/noise/NoiseOptions
rename to src/osgEarthDriversDisabled/noise/NoiseOptions
diff --git a/src/osgEarthExtensions/CMakeLists.txt b/src/osgEarthExtensions/CMakeLists.txt
new file mode 100644
index 0000000..7210852
--- /dev/null
+++ b/src/osgEarthExtensions/CMakeLists.txt
@@ -0,0 +1,29 @@
+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/osgEarthExtensions/billboard/BillboardExtension b/src/osgEarthExtensions/billboard/BillboardExtension
new file mode 100644
index 0000000..570b586
--- /dev/null
+++ b/src/osgEarthExtensions/billboard/BillboardExtension
@@ -0,0 +1,71 @@
+/* -*-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.
+ *
+ * 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_BILLBOARD_BILLBOARD_EXTENSION
+#define OSGEARTH_BILLBOARD_BILLBOARD_EXTENSION 1
+
+#include "BillboardOptions"
+#include <osgEarth/Extension>
+#include <osgEarth/MapNode>
+#include <osgEarthFeatures/FeatureSource>
+
+namespace osgEarth { namespace Billboard
+{
+    using namespace osgEarth;
+    using namespace osgEarth::Features;
+
+    /**
+     * Extension for loading the splatting effect on demand.
+     */
+    class BillboardExtension : public Extension, public ExtensionInterface<MapNode>
+    {
+    public:
+        META_Object(osgearth_ext_billboard, BillboardExtension);
+
+        // CTORs
+        BillboardExtension();
+        BillboardExtension(const BillboardOptions& options);
+
+        // DTOR
+        virtual ~BillboardExtension();
+
+
+    public: // Extension
+
+        void setDBOptions(const osgDB::Options* dbOptions);
+
+
+    public: // ExtensionInterface<MapNode>
+
+        bool connect(MapNode* mapNode);
+
+        bool disconnect(MapNode* mapNode);
+
+
+    protected: // Object
+        BillboardExtension(const BillboardExtension& rhs, const osg::CopyOp& op) { }
+
+    private:
+        const BillboardOptions                 _options;
+        osg::ref_ptr<const osgDB::Options>     _dbOptions;
+        osg::ref_ptr<FeatureSource>            _features;
+    };
+
+} } // namespace osgEarth::Billboard
+
+#endif // OSGEARTH_BILLBOARD_BILLBOARD_EXTENSION
diff --git a/src/osgEarthExtensions/billboard/BillboardExtension.cpp b/src/osgEarthExtensions/billboard/BillboardExtension.cpp
new file mode 100644
index 0000000..8fe3b52
--- /dev/null
+++ b/src/osgEarthExtensions/billboard/BillboardExtension.cpp
@@ -0,0 +1,288 @@
+/* -*-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.
+ *
+ * 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 "BillboardExtension"
+#include "BillboardShaders"
+
+#include <osg/Point>
+#include <osg/Texture2D>
+
+#include <osgEarth/ElevationQuery>
+#include <osgEarth/MapNode>
+#include <osgEarth/Registry>
+#include <osgEarth/ShaderGenerator>
+#include <osgEarth/VirtualProgram>
+#include <osgEarth/Random>
+#include <osgEarthFeatures/TransformFilter>
+#include <osgEarthFeatures/ScatterFilter>
+
+using namespace osgEarth;
+using namespace osgEarth::Billboard;
+using namespace osgEarth::Features;
+
+#define LC "[BillboardExtension] "
+
+
+BillboardExtension::BillboardExtension()
+{
+    //nop
+}
+
+BillboardExtension::BillboardExtension(const BillboardOptions& options) :
+_options( options )
+{
+    //nop
+}
+
+BillboardExtension::~BillboardExtension()
+{
+    //nop
+}
+
+void
+BillboardExtension::setDBOptions(const osgDB::Options* dbOptions)
+{
+    _dbOptions = dbOptions;
+}
+
+bool
+BillboardExtension::connect(MapNode* mapNode)
+{
+    if ( !mapNode )
+    {
+        OE_WARN << LC << "Illegal: MapNode cannot be null." << std::endl;
+        return false;
+    }
+
+    OE_INFO << LC << "Connecting to MapNode.\n";
+
+    if ( !_options.imageURI().isSet() )
+    {
+        OE_WARN << LC << "Illegal: image URI is required" << std::endl;
+        return false;
+    }
+
+    if ( !_options.featureOptions().isSet() )
+    {
+        OE_WARN << LC << "Illegal: feature source is required" << std::endl;
+        return false;
+    }
+    
+    _features = FeatureSourceFactory::create( _options.featureOptions().value() );
+    if ( !_features.valid() )
+    {
+        OE_WARN << LC << "Illegal: no valid feature source provided" << std::endl;
+        return false;
+    }
+
+    //if ( _features->getGeometryType() != osgEarth::Symbology::Geometry::TYPE_POINTSET )
+    //{
+    //    OE_WARN << LC << "Illegal: only points currently supported" << std::endl;
+    //    return false;
+    //}
+
+    _features->initialize( _dbOptions );
+
+    osg::Vec3dArray* verts;
+    if ( _features->getFeatureProfile() )
+    {
+        verts = new osg::Vec3dArray();
+        
+        OE_NOTICE << "Reading features...\n";
+        osg::ref_ptr<FeatureCursor> cursor = _features->createFeatureCursor();
+        while ( cursor.valid() && cursor->hasMore() )
+        {
+            Feature* f = cursor->nextFeature();
+            if ( f && f->getGeometry() )
+            {
+                if ( f->getGeometry()->getComponentType() == Geometry::TYPE_POLYGON )
+                {
+                    FilterContext cx;
+                    cx.setProfile( new FeatureProfile(_features->getFeatureProfile()->getExtent()) );
+
+                    ScatterFilter scatter;
+                    scatter.setDensity( _options.density().get() );
+                    scatter.setRandom( true );
+                    FeatureList featureList;
+                    featureList.push_back(f);
+                    scatter.push( featureList, cx );
+                }
+
+                // Init a filter to tranform feature in desired SRS 
+                if (!mapNode->getMapSRS()->isEquivalentTo(_features->getFeatureProfile()->getSRS()))
+                {
+                    FilterContext cx;
+                    cx.setProfile( new FeatureProfile(_features->getFeatureProfile()->getExtent()) );
+
+                    TransformFilter xform( mapNode->getMapSRS() );
+                    FeatureList featureList;
+                    featureList.push_back(f);
+                    cx = xform.push(featureList, cx);
+                }
+
+                GeometryIterator iter(f->getGeometry());
+                while(iter.hasMore()) {
+                    const Geometry* geom = iter.next();
+                    osg::ref_ptr<osg::Vec3dArray> fVerts = geom->toVec3dArray();
+                    verts->insert(verts->end(), fVerts->begin(), fVerts->end());
+                }
+            }
+        }
+    }
+    else
+    {
+        OE_WARN << LC << "Illegal: feature source has no SRS" << std::endl;
+        return false;
+    }
+
+
+    if ( verts && verts->size() > 0 )
+    {
+        OE_NOTICE << LC << "Read " << verts->size() << " points.\n";
+
+        //localize all the verts
+        GeoPoint centroid;
+        _features->getFeatureProfile()->getExtent().getCentroid(centroid);
+        centroid = centroid.transform(mapNode->getMapSRS());
+
+        OE_NOTICE << "Centroid = " << centroid.x() << ", " << centroid.y() << "\n";
+
+        osg::Matrixd l2w, w2l;
+        centroid.createLocalToWorld(l2w);
+        w2l.invert(l2w);
+
+        osg::MatrixTransform* mt = new osg::MatrixTransform;
+        mt->setMatrix(l2w);
+
+        OE_NOTICE << "Clamping elevations...\n";
+        osgEarth::ElevationQuery eq(mapNode->getMap());
+        eq.setFallBackOnNoData( true );
+        eq.getElevations(verts->asVector(), mapNode->getMapSRS(), true, 0.005);
+        
+        OE_NOTICE << "Building geometry...\n";
+        osg::Vec3Array* normals = new osg::Vec3Array(verts->size());
+
+        osg::Vec4Array* colors = new osg::Vec4Array(verts->size());
+        Random rng;
+
+        for (int i=0; i < verts->size(); i++)
+        {
+            GeoPoint vert(mapNode->getMapSRS(), (*verts)[i], osgEarth::ALTMODE_ABSOLUTE);
+
+            osg::Vec3d world;
+            vert.toWorld(world);
+            (*verts)[i] = world * w2l;
+
+            osg::Vec3 normal = world;
+            normal.normalize();
+            (*normals)[i] = osg::Matrix::transform3x3(normal, w2l);
+
+            double n = rng.next();
+            (*colors)[i].set( n, n, n, 1 );
+        }
+
+        //create geom and primitive sets
+        osg::Geometry* geometry = new osg::Geometry();
+        geometry->setVertexArray( verts );
+        geometry->setNormalArray( normals );
+        geometry->setNormalBinding( osg::Geometry::BIND_PER_VERTEX );
+        geometry->setColorArray(colors);
+        geometry->setColorBinding( osg::Geometry::BIND_PER_VERTEX );
+
+        geometry->addPrimitiveSet( new osg::DrawArrays( GL_POINTS, 0, verts->size() ) );
+
+        //create image and texture to render to
+        osg::Texture2D* tex = new osg::Texture2D(_options.imageURI()->getImage(_dbOptions));
+        tex->setResizeNonPowerOfTwoHint(false);
+        tex->setFilter( osg::Texture::MIN_FILTER, osg::Texture::LINEAR_MIPMAP_LINEAR );
+        tex->setFilter( osg::Texture::MAG_FILTER, osg::Texture::LINEAR );
+        tex->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE);
+        tex->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE);
+
+        geometry->setName("BillboardPoints");
+
+        osg::Geode* geode = new osg::Geode;
+        geode->addDrawable(geometry);
+
+        //osg::ref_ptr<StateSetCache> cache = new StateSetCache();
+        //Registry::shaderGenerator().run(geode, cache.get());
+
+        //set the texture related uniforms
+        osg::StateSet* geode_ss = geode->getOrCreateStateSet();
+        geode_ss->setTextureAttributeAndModes( 2, tex, 1 );
+        geode_ss->getOrCreateUniform("billboard_tex", osg::Uniform::SAMPLER_2D)->set( 2 );
+
+        float bbWidth = (float)tex->getImage()->s() / 2.0f;
+        float bbHeight = (float)tex->getImage()->t();
+        float aspect = (float)tex->getImage()->s() / (float)tex->getImage()->t();
+        if (_options.height().isSet())
+        {
+            bbHeight = _options.height().get();
+            if (!_options.width().isSet())
+            {
+                bbWidth = bbHeight * aspect / 2.0f;
+            }
+        }
+        if (_options.width().isSet())
+        {
+            bbWidth = _options.width().get() / 2.0f;
+            if (!_options.height().isSet())
+            {
+                bbHeight = _options.width().get() / aspect;
+            }
+        }
+
+        geode_ss->getOrCreateUniform("billboard_width", osg::Uniform::FLOAT)->set( bbWidth );
+        geode_ss->getOrCreateUniform("billboard_height", osg::Uniform::FLOAT)->set( bbHeight );
+        geode_ss->setMode(GL_BLEND, osg::StateAttribute::ON);
+
+        //for now just using an osg::Program
+        //TODO: need to add GeometryShader support to the shader comp setup
+        osg::Program* pgm = new osg::Program;
+        pgm->setName("billboard_program");
+        pgm->addShader( new osg::Shader( osg::Shader::VERTEX, billboardVertShader ) );
+        pgm->addShader( new osg::Shader( osg::Shader::GEOMETRY, billboardGeomShader ) );
+        pgm->addShader( new osg::Shader( osg::Shader::FRAGMENT, billboardFragmentShader ) );
+        pgm->setParameter( GL_GEOMETRY_VERTICES_OUT_EXT, 4 );
+        pgm->setParameter( GL_GEOMETRY_INPUT_TYPE_EXT, GL_POINTS );
+        pgm->setParameter( GL_GEOMETRY_OUTPUT_TYPE_EXT, GL_TRIANGLE_STRIP );
+        geode_ss->setAttribute(pgm);
+
+        geode_ss->setMode( GL_CULL_FACE, osg::StateAttribute::OFF );
+        geode->setCullingActive(false);
+
+        mt->addChild(geode);
+        mapNode->getModelLayerGroup()->addChild(mt);
+
+        return true;
+    }
+
+    return false;
+}
+
+bool
+BillboardExtension::disconnect(MapNode* mapNode)
+{
+    if ( mapNode )
+    {
+        //TODO
+    }
+
+    return true;
+}
+
diff --git a/src/osgEarthExtensions/billboard/BillboardOptions b/src/osgEarthExtensions/billboard/BillboardOptions
new file mode 100644
index 0000000..f1af67c
--- /dev/null
+++ b/src/osgEarthExtensions/billboard/BillboardOptions
@@ -0,0 +1,111 @@
+/* -*-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.
+ *
+ * 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_BILLBOARD_OPTIONS
+#define OSGEARTH_DRIVER_BILLBOARD_OPTIONS 1
+
+#include <osgEarth/Common>
+#include <osgEarth/URI>
+#include <osgEarthFeatures/FeatureSource>
+
+namespace osgEarth { namespace Billboard
+{
+    using namespace osgEarth;
+    using namespace osgEarth::Features;
+
+    /**
+     * Options governing the classification splatting engine.
+     */
+    class /*header-only*/ BillboardOptions : public DriverConfigOptions
+    {
+    public:
+        /** URI of the billboard image file */
+        optional<URI>& imageURI() { return _imageURI; }
+        const optional<URI>& imageURI() const { return _imageURI; }
+        
+        /** scale  */
+        optional<float>& scale() { return _scale; }
+        const optional<float>& scale() const { return _scale; }
+
+        /** width and height */
+        optional<float>& width() { return _width; }
+        const optional<float>& width() const { return _width; }
+
+        optional<float>& height() { return _height; }
+        const optional<float>& height() const { return _height; }
+
+        optional<float>& density() { return _density; }
+        const optional<float>& density() const { return _density; }
+
+        /** Feature source from which to read the feature data */
+        optional<FeatureSourceOptions>& featureOptions() { return _featureOptions; }
+        const optional<FeatureSourceOptions>& featureOptions() const { return _featureOptions; }
+
+
+    public:
+        BillboardOptions( const ConfigOptions& opt =ConfigOptions() ) : DriverConfigOptions( opt )
+        {
+            setDriver( "billboard" );
+            _scale.init(1.0f);
+            _density.init(100.0f);
+            fromConfig( _conf );
+        }
+
+        virtual ~BillboardOptions() { }
+
+    public:
+        Config getConfig() const {
+            Config conf = DriverConfigOptions::getConfig();
+            conf.updateIfSet("image",  _imageURI);
+            conf.updateIfSet("scale",  _scale);
+            conf.updateIfSet("width",  _width);
+            conf.updateIfSet("height",  _height);
+            conf.updateIfSet("density", _density);
+            conf.updateObjIfSet( "features", _featureOptions );
+            return conf;
+        }
+
+    protected:
+        void mergeConfig( const Config& conf ) {
+            DriverConfigOptions::mergeConfig( conf );
+            fromConfig( conf );
+        }
+
+    private:
+        void fromConfig( const Config& conf ) {
+            conf.getIfSet("image",  _imageURI);
+            conf.getIfSet("scale",  _scale);
+            conf.getIfSet("width",  _width);
+            conf.getIfSet("height",  _height);
+            conf.getIfSet("density", _density);
+
+            if ( conf.hasChild("features") )
+                _featureOptions->merge( conf.child("features") );
+        }
+
+        optional<URI>                   _imageURI;
+        optional<float>                 _scale;
+        optional<float>                 _width;
+        optional<float>                 _height;
+        optional<float>                 _density;
+        optional<FeatureSourceOptions>  _featureOptions;
+    };
+
+} } // namespace osgEarth::Billboard
+
+#endif // OSGEARTH_DRIVER_BILLBOARD_OPTIONS
diff --git a/src/osgEarthExtensions/billboard/BillboardPlugin.cpp b/src/osgEarthExtensions/billboard/BillboardPlugin.cpp
new file mode 100644
index 0000000..abcd69c
--- /dev/null
+++ b/src/osgEarthExtensions/billboard/BillboardPlugin.cpp
@@ -0,0 +1,56 @@
+/* -*-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.
+ *
+ * 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 "BillboardOptions"
+#include "BillboardExtension"
+
+#include <osgDB/ReaderWriter>
+#include <osgDB/Registry>
+#include <osgDB/FileNameUtils>
+
+namespace osgEarth { namespace Billboard
+{
+    /**
+     * Plugin entry point
+     */
+    class BillboardPlugin : public osgDB::ReaderWriter
+    {
+    public: // Plugin stuff
+
+        BillboardPlugin() {
+            supportsExtension( "osgearth_billboard", "osgEarth Billboard Extension Plugin" );
+        }
+        
+        const char* className() {
+            return "osgEarth Billboard Extension Plugin";
+        }
+
+        virtual ~BillboardPlugin() { }
+
+        ReadResult readObject(const std::string& filename, const osgDB::Options* dbOptions) const
+        {
+          if ( !acceptsExtension(osgDB::getLowerCaseFileExtension(filename)) )
+                return ReadResult::FILE_NOT_HANDLED;
+
+          return ReadResult( new BillboardExtension(Extension::getConfigOptions(dbOptions)) );
+        }
+    };
+
+    REGISTER_OSGPLUGIN(osgearth_billboard, BillboardPlugin)
+
+} } // namespace osgEarth::Billboard
diff --git a/src/osgEarthExtensions/billboard/BillboardShaders b/src/osgEarthExtensions/billboard/BillboardShaders
new file mode 100644
index 0000000..8324de9
--- /dev/null
+++ b/src/osgEarthExtensions/billboard/BillboardShaders
@@ -0,0 +1,106 @@
+/* -*-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_BILLBOARD_SHADERS
+#define OSGEARTH_BILLBOARD_SHADERS 1
+
+#include <osgEarth/VirtualProgram>
+
+using namespace osgEarth;
+
+namespace
+{
+
+    const char* billboardVertShader =
+        "#version " GLSL_VERSION_STR "\n"
+        //"#extension GL_EXT_geometry_shader4 : enable\n"
+        GLSL_DEFAULT_PRECISION_FLOAT "\n"
+        "varying out vec3 normal;\n"
+        "varying out vec4 color;\n"
+        "void main(void)\n"
+        "{\n"
+        "    normal = gl_Normal;\n"
+        "    color = gl_Color;\n"
+        "    gl_Position = gl_Vertex;\n"
+        "}\n";
+
+
+    /**
+     * 
+     */
+    const char* billboardGeomShader =
+        "#version " GLSL_VERSION_STR "\n"
+        "#extension GL_EXT_geometry_shader4 : enable\n"
+        GLSL_DEFAULT_PRECISION_FLOAT "\n"
+        "varying out vec2 tex_coord;\n"
+        "varying out float brightness;\n"
+        "varying in vec3 normal[];\n"
+        "varying in vec4 color[];\n"
+        "uniform float billboard_width; \n"
+        "uniform float billboard_height; \n"
+        "uniform sampler2D billboard_tex; \n"
+        "void main(void)\n"
+        "{\n"
+        "    vec4 v = gl_ModelViewMatrix * gl_PositionIn[0];\n"
+        "    vec4 v2 = gl_ModelViewMatrix * (gl_PositionIn[0] + vec4(normal[0]*billboard_height, 0.0));\n"
+        "    \n"
+        // TODO: this width calculation isn't great but works for now
+        "    vec4 center_v = gl_ModelViewMatrix * vec4(0.,0.,0.,1.);\n"
+        "    vec4 right_v = gl_ModelViewMatrix * vec4(billboard_width,0.,0.,1.);\n"
+        "    float width = distance(right_v, center_v);\n"
+        "    \n"
+        "    brightness = color[0].r;\n"
+        "    gl_Position = gl_ProjectionMatrix * (v + vec4(width, 0., 0., 0.)); \n"
+        "    tex_coord = vec2(1.0, 0.0); \n"
+        "    EmitVertex(); \n"
+        "    gl_Position = gl_ProjectionMatrix * (v + vec4(-width, 0., 0., 0.)); \n"
+        "    tex_coord = vec2(0.0, 0.0); \n"
+        "    brightness = color[0].r;\n"
+        "    EmitVertex(); \n"
+        
+        "    brightness = color[0].r*2.0;\n"
+        "    gl_Position = gl_ProjectionMatrix * (v2 + vec4(width, 0., 0., 0.)); \n"
+        "    tex_coord = vec2(1.0, 1.0); \n"
+        "    EmitVertex(); \n"
+        "    gl_Position = gl_ProjectionMatrix * (v2 + vec4(-width, 0., 0., 0.)); \n"
+        "    tex_coord = vec2(0.0, 1.0); \n"
+        "    EmitVertex(); \n"
+        "    EndPrimitive(); \n"
+        "}\n";
+
+
+    const char* billboardFragmentShader =
+        "#version " GLSL_VERSION_STR "\n"
+        //"#extension GL_EXT_geometry_shader4 : enable\n"
+        GLSL_DEFAULT_PRECISION_FLOAT "\n"
+        "uniform sampler2D billboard_tex; \n"
+        "varying vec2 tex_coord;\n"
+        "varying float brightness;\n"
+        "void main(void) {\n"
+        "    float contrast = clamp(1.0-brightness, 0.85, 1.0);\n"
+        "    vec4 color = texture2D(billboard_tex, tex_coord);\n"
+        "    color.rgb = clamp(((color.rgb-0.5)*contrast + 0.5) * (1.0+brightness), 0.0, 1.0);\n"
+        "    if ( color.a < 0.5 ) discard; \n"
+        "    gl_FragColor = color; \n"
+        "}\n";
+}
+
+#endif // OSGEARTH_BILLBOARD_SHADERS
\ No newline at end of file
diff --git a/src/osgEarthExtensions/billboard/CMakeLists.txt b/src/osgEarthExtensions/billboard/CMakeLists.txt
new file mode 100644
index 0000000..eb36568
--- /dev/null
+++ b/src/osgEarthExtensions/billboard/CMakeLists.txt
@@ -0,0 +1,27 @@
+#
+# image billboarding plugin
+#
+
+SET(TARGET_SRC
+        BillboardPlugin.cpp
+	BillboardExtension.cpp)
+	
+SET(LIB_PUBLIC_HEADERS
+	BillboardExtension
+	BillboardOptions)
+	
+SET(TARGET_H
+	${LIB_PUBLIC_HEADERS}
+	BillboardShaders)
+
+SET(TARGET_COMMON_LIBRARIES ${TARGET_COMMON_LIBRARIES}
+    osgEarthUtil
+    osgEarthFeatures)
+	
+SETUP_EXTENSION(osgearth_billboard)
+
+# to install public driver includes:
+SET(LIB_NAME billboard)
+
+INCLUDE(ModuleInstallOsgEarthExtensionIncludes OPTIONAL)
+
diff --git a/src/osgEarthExtensions/bumpmap/BumpMap.frag.common.glsl b/src/osgEarthExtensions/bumpmap/BumpMap.frag.common.glsl
new file mode 100644
index 0000000..63a34ac
--- /dev/null
+++ b/src/osgEarthExtensions/bumpmap/BumpMap.frag.common.glsl
@@ -0,0 +1,26 @@
+#pragma vp_define "OE_USE_NORMAL_MAP"
+
+#ifdef OE_USE_NORMAL_MAP
+
+// normal map version:
+uniform sampler2D oe_nmap_normalTex;
+in vec4 oe_nmap_normalCoords;
+
+float oe_bumpmap_getSlope()
+{
+    vec4 encodedNormal = texture2D(oe_nmap_normalTex, oe_nmap_normalCoords.st);
+    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_bumpmap_slope;
+
+float oe_bumpmap_getSlope()
+{
+    return oe_bumpmap_slope;
+}
+
+#endif // OE_USE_NORMAL_MAP
diff --git a/src/osgEarthExtensions/bumpmap/BumpMap.frag.progressive.glsl b/src/osgEarthExtensions/bumpmap/BumpMap.frag.progressive.glsl
new file mode 100644
index 0000000..1c5f58d
--- /dev/null
+++ b/src/osgEarthExtensions/bumpmap/BumpMap.frag.progressive.glsl
@@ -0,0 +1,56 @@
+#version 110
+
+#pragma vp_entryPoint "oe_bumpmap_fragment"
+#pragma vp_location   "fragment_coloring"
+#pragma vp_order      "0.3"
+
+#pragma include "BumpMap.frag.common.glsl"
+
+uniform sampler2D oe_bumpmap_tex;
+uniform float oe_bumpmap_intensity;
+uniform int oe_bumpmap_octaves;
+uniform float oe_bumpmap_maxRange;
+
+// stage global
+vec3 oe_global_Normal;
+
+// from BumpMap.model.vert.glsl
+in vec2 oe_bumpmap_coords;
+
+// from BumpMap.view.vert.glsl
+in float oe_bumpmap_range;
+
+// Entry point for progressive blended bump maps
+void oe_bumpmap_fragment(inout vec4 color)
+{
+	// sample the bump map
+    const float amplitudeDecay = 1.0; // no decay.
+    float maxLOD = float(oe_bumpmap_octaves)+1.0;
+
+    // starter vector:
+    vec3 bump = vec3(0.0);    
+    float scale = 1.0;
+    float amplitude = 1.0;
+    float limit = oe_bumpmap_range;
+    float range = oe_bumpmap_maxRange;
+    float lastRange = oe_bumpmap_maxRange;
+    for(float lod = 1.0; lod < maxLOD; lod += 1.0, scale *= 2.0, amplitude *= amplitudeDecay)
+    {
+        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;
+        if ( range <= limit )
+            break;
+        lastRange = range;
+        range = oe_bumpmap_maxRange/exp(lod);
+    }
+
+    // finally, transform into view space and normalize the vector.
+    bump = normalize(gl_NormalMatrix*bump);
+
+    float slope = oe_bumpmap_getSlope();
+
+	// permute the normal with the bump.
+	oe_global_Normal = normalize(oe_global_Normal + bump*oe_bumpmap_intensity*slope);
+}
diff --git a/src/osgEarthExtensions/bumpmap/BumpMap.frag.simple.glsl b/src/osgEarthExtensions/bumpmap/BumpMap.frag.simple.glsl
new file mode 100644
index 0000000..77dc404
--- /dev/null
+++ b/src/osgEarthExtensions/bumpmap/BumpMap.frag.simple.glsl
@@ -0,0 +1,23 @@
+#version 110
+
+#pragma vp_entryPoint "oe_bumpmap_fragment"
+#pragma vp_location   "fragment_coloring"
+#pragma vp_order      "0.3"
+
+#pragma include "BumpMap.frag.common.glsl"
+
+vec3 oe_global_Normal;
+
+uniform sampler2D oe_bumpmap_tex;
+uniform float oe_bumpmap_intensity;
+in vec2 oe_bumpmap_coords;
+
+void oe_bumpmap_fragment(inout vec4 color)
+{
+	// sample the bump map
+    vec3 bump = gl_NormalMatrix * normalize(texture2D(oe_bumpmap_tex, oe_bumpmap_coords).xyz*2.0-1.0);
+
+	// permute the normal with the bump.
+    float slope = oe_bumpmap_getSlope();
+	oe_global_Normal = normalize(oe_global_Normal + bump*oe_bumpmap_intensity*slope);
+}
diff --git a/src/osgEarthExtensions/bumpmap/BumpMap.vert.model.glsl b/src/osgEarthExtensions/bumpmap/BumpMap.vert.model.glsl
new file mode 100644
index 0000000..d60e751
--- /dev/null
+++ b/src/osgEarthExtensions/bumpmap/BumpMap.vert.model.glsl
@@ -0,0 +1,60 @@
+#version 110
+
+#pragma vp_entryPoint "oe_bumpmap_vertexModel"
+#pragma vp_location   "vertex_model"
+#pragma vp_order      "0.5"
+#pragma vp_define     "OE_USE_NORMAL_MAP"
+
+uniform vec4 oe_tile_key;
+uniform float oe_bumpmap_scale;
+
+varying vec4 oe_layer_tilec;
+varying vec3 oe_Normal;
+
+varying vec2 oe_bumpmap_coords;
+varying float oe_bumpmap_range;
+
+#ifdef OE_USE_NORMAL_MAP
+uniform mat4 oe_nmap_normalTexMatrix;
+varying vec4 oe_bumpmap_normalCoords;
+#else
+varying float oe_bumpmap_slope;
+#endif
+
+vec2 oe_bumpmap_scaleCoords(in vec2 coords, in float targetLOD)
+{
+    float dL = oe_tile_key.z - targetLOD;
+    float factor = exp2(dL);
+    float invFactor = 1.0/factor;
+    vec2 scale = vec2(invFactor);
+    vec2 result = coords * scale;
+
+    // For upsampling we need to calculate an offset as well
+    float upSampleToggle = factor >= 1.0 ? 1.0 : 0.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 += upSampleToggle * offset;
+    }
+
+    return result;
+}
+
+void oe_bumpmap_vertexModel(inout vec4 VertexMODEL)
+{            
+    // quantize the scale factor
+    float iscale = float(int(oe_bumpmap_scale));
+
+    // scale sampling coordinates to a target LOD.
+    const float targetLOD = 13.0;
+    oe_bumpmap_coords = oe_bumpmap_scaleCoords(oe_layer_tilec.st, targetLOD) * iscale;
+
+#ifdef OE_USE_NORMAL_MAP
+    oe_bumpmap_normalCoords = oe_nmap_normalTexMatrix * oe_layer_tilec;
+#else
+    // calcluate slope and augment it.
+    oe_bumpmap_slope = clamp(2.5*(1.0-oe_Normal.z), 0.0, 1.0);
+#endif
+}
diff --git a/src/osgEarthExtensions/bumpmap/BumpMap.vert.view.glsl b/src/osgEarthExtensions/bumpmap/BumpMap.vert.view.glsl
new file mode 100644
index 0000000..9e37147
--- /dev/null
+++ b/src/osgEarthExtensions/bumpmap/BumpMap.vert.view.glsl
@@ -0,0 +1,12 @@
+#version 110
+
+#pragma vp_entryPoint "oe_bumpmap_vertexView"
+#pragma vp_location   "vertex_view"
+#pragma vp_order      "0.5"
+
+varying float oe_bumpmap_range;
+
+void oe_bumpmap_vertexView(inout vec4 vertexView)
+{
+    oe_bumpmap_range = -vertexView.z;
+}
diff --git a/src/osgEarthExtensions/bumpmap/BumpMapExtension b/src/osgEarthExtensions/bumpmap/BumpMapExtension
new file mode 100644
index 0000000..813022d
--- /dev/null
+++ b/src/osgEarthExtensions/bumpmap/BumpMapExtension
@@ -0,0 +1,73 @@
+/* -*-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.
+ *
+ * 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_BUMPMAP_EXTENSION
+#define OSGEARTH_BUMPMAP_EXTENSION 1
+
+#include "BumpMapOptions"
+#include "BumpMapTerrainEffect"
+#include <osgEarth/Extension>
+#include <osgEarth/MapNode>
+#include <osgEarthUtil/Controls>
+
+namespace osgEarth { namespace BumpMap
+{
+    using namespace osgEarth;
+    using namespace osgEarth::Util::Controls;
+
+    /**
+     * Extension for loading the normal mapping effect on demand.
+     */
+    class BumpMapExtension : public Extension,
+                             public ExtensionInterface<MapNode>
+    {
+    public:
+        META_Object(osgearth_ext_BumpMap, BumpMapExtension);
+
+        // CTORs
+        BumpMapExtension();
+        BumpMapExtension(const BumpMapOptions& options);
+
+        // DTOR
+        virtual ~BumpMapExtension();
+
+
+    public: // Extension
+
+        void setDBOptions(const osgDB::Options* dbOptions);
+
+
+    public: // ExtensionInterface<MapNode>
+
+        bool connect(MapNode* mapNode);
+
+        bool disconnect(MapNode* mapNode);
+
+
+    protected: // Object
+        BumpMapExtension(const BumpMapExtension& rhs, const osg::CopyOp& op) { }
+
+    private:
+        const BumpMapOptions                 _options;
+        osg::ref_ptr<const osgDB::Options>   _dbOptions;
+        osg::ref_ptr<BumpMapTerrainEffect>   _effect;
+    };
+
+} } // namespace osgEarth::BumpMap
+
+#endif // OSGEARTH_BUMPMAP_EXTENSION
diff --git a/src/osgEarthExtensions/bumpmap/BumpMapExtension.cpp b/src/osgEarthExtensions/bumpmap/BumpMapExtension.cpp
new file mode 100644
index 0000000..30e9e07
--- /dev/null
+++ b/src/osgEarthExtensions/bumpmap/BumpMapExtension.cpp
@@ -0,0 +1,98 @@
+/* -*-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.
+ *
+ * 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 "BumpMapExtension"
+#include "BumpMapTerrainEffect"
+
+#include <osgEarth/MapNode>
+
+using namespace osgEarth;
+using namespace osgEarth::BumpMap;
+
+#define LC "[BumpMapExtension] "
+
+
+BumpMapExtension::BumpMapExtension()
+{
+    //nop
+}
+
+BumpMapExtension::BumpMapExtension(const BumpMapOptions& options) :
+_options( options )
+{
+    //nop
+}
+
+BumpMapExtension::~BumpMapExtension()
+{
+    //nop
+}
+
+void
+BumpMapExtension::setDBOptions(const osgDB::Options* dbOptions)
+{
+    _dbOptions = dbOptions;
+}
+
+bool
+BumpMapExtension::connect(MapNode* mapNode)
+{
+    if ( !mapNode )
+    {
+        OE_WARN << LC << "Illegal: MapNode cannot be null." << std::endl;
+        return false;
+    }
+    
+    osg::ref_ptr<osg::Image> image = _options.imageURI()->getImage( _dbOptions.get() );
+    if ( !image.valid() )
+    {
+        OE_WARN << LC << "Failed; unable to load normal map image from "
+            << _options.imageURI()->full() << "\n";
+        return false;
+    }
+
+    _effect = new BumpMapTerrainEffect( _dbOptions.get() );
+    _effect->setBumpMapImage( image.get() );
+
+    if (_options.intensity().isSet())
+        _effect->getIntensityUniform()->set( _options.intensity().get() );
+
+    if (_options.scale().isSet())
+        _effect->getScaleUniform()->set( _options.scale().get() );
+
+    if ( _options.octaves().isSet() )
+        _effect->setOctaves( _options.octaves().get() );
+
+    mapNode->getTerrainEngine()->addEffect( _effect.get() );
+    
+    OE_INFO << LC << "Installed.\n";
+
+    return true;
+}
+
+bool
+BumpMapExtension::disconnect(MapNode* mapNode)
+{
+    if ( mapNode )
+    {
+        mapNode->getTerrainEngine()->removeEffect( _effect.get() );
+    }
+    _effect = 0L;
+    return true;
+}
+
diff --git a/src/osgEarthExtensions/bumpmap/BumpMapOptions b/src/osgEarthExtensions/bumpmap/BumpMapOptions
new file mode 100644
index 0000000..c38cd0f
--- /dev/null
+++ b/src/osgEarthExtensions/bumpmap/BumpMapOptions
@@ -0,0 +1,110 @@
+/* -*-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.
+ *
+ * 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_BUMPMAP_OPTIONS
+#define OSGEARTH_DRIVER_BUMPMAP_OPTIONS 1
+
+#include <osgEarth/Common>
+#include <osgEarth/URI>
+
+namespace osgEarth { namespace BumpMap
+{
+    using namespace osgEarth;
+
+    /**
+     * Options governing bump mapping of the terrain.
+     * A Bump Map is a repeating normal texture that modifies the
+     * existing normal vector per fragment.
+     */
+    class BumpMapOptions : public DriverConfigOptions // NO EXPORT; header only
+    {
+    public:
+        // bump map texture to load.
+        optional<URI>& imageURI() { return _imageURI; }
+        const optional<URI>& imageURI() const { return _imageURI; }
+
+        // Intensity of normal map effect.
+        optional<float>& intensity() { return _intensity; }
+        const optional<float>& intensity() const { return _intensity; }
+
+        // Scale factor for the normal map texture.
+        optional<float>& scale() { return _scale; }
+        const optional<float>& scale() const { return _scale; }
+
+        /** Number of times to proressively scale and multisample the bump map
+            based on camera range. Default is 1. */
+        optional<int>& octaves() { return _octaves; }
+        const optional<int>& octaves() const { return _octaves; }
+
+        /** Camera range at which to render one octave (outer range). */
+        optional<float>& maxRange() { return _maxRange; }
+        const optional<float>& maxRange() const { return _maxRange; }
+
+    public:
+        BumpMapOptions( const ConfigOptions& opt =ConfigOptions() ) : DriverConfigOptions( opt )
+        {
+            setDriver( "bumpmap" );
+
+            // Defaults.
+            _intensity.init( 1.0f );
+            _scale.init    ( 1.0f );
+            _octaves.init  ( 1 );
+            _maxRange.init (25000.0f);
+
+            fromConfig( _conf );
+        }
+
+        virtual ~BumpMapOptions() { }
+
+    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);
+            return conf;
+        }
+
+    protected:
+        void mergeConfig( const Config& conf ) {
+            DriverConfigOptions::mergeConfig( conf );
+            fromConfig( conf );
+        }
+
+    private:
+        void fromConfig( const Config& conf ) {
+            conf.getIfSet("image",     _imageURI);
+            conf.getIfSet("intensity", _intensity);
+            conf.getIfSet("scale",     _scale);
+            conf.getIfSet("octaves",   _octaves);
+            conf.getIfSet("max_range", _maxRange);
+        }
+
+        optional<URI>   _imageURI;
+        optional<float> _intensity;
+        optional<float> _scale;
+        optional<int>   _octaves;
+        optional<float> _maxRange;
+    };
+
+} } // namespace osgEarth::BumpMap
+
+#endif // OSGEARTH_DRIVER_BUMPMAP_OPTIONS
+
diff --git a/src/osgEarthExtensions/bumpmap/BumpMapPlugin.cpp b/src/osgEarthExtensions/bumpmap/BumpMapPlugin.cpp
new file mode 100644
index 0000000..a0ebbd2
--- /dev/null
+++ b/src/osgEarthExtensions/bumpmap/BumpMapPlugin.cpp
@@ -0,0 +1,56 @@
+/* -*-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.
+ *
+ * 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 "BumpMapOptions"
+#include "BumpMapExtension"
+
+#include <osgDB/ReaderWriter>
+#include <osgDB/Registry>
+#include <osgDB/FileNameUtils>
+
+namespace osgEarth { namespace BumpMap
+{
+    /**
+     * Plugin entry point
+     */
+    class BumpMapPlugin : public osgDB::ReaderWriter
+    {
+    public: // Plugin stuff
+
+        BumpMapPlugin() {
+            supportsExtension( "osgearth_bumpmap", "osgEarth Bump Map Extension Plugin" );
+        }
+        
+        const char* className() {
+            return "osgEarth Bump Map Extension Plugin";
+        }
+
+        virtual ~BumpMapPlugin() { }
+
+        ReadResult readObject(const std::string& filename, const osgDB::Options* dbOptions) const
+        {
+          if ( !acceptsExtension(osgDB::getLowerCaseFileExtension(filename)) )
+                return ReadResult::FILE_NOT_HANDLED;
+
+          return ReadResult( new BumpMapExtension(Extension::getConfigOptions(dbOptions)) );
+        }
+    };
+
+    REGISTER_OSGPLUGIN(osgearth_bumpmap, BumpMapPlugin)
+
+} } // namespace osgEarth::BumpMap
diff --git a/src/osgEarthExtensions/bumpmap/BumpMapShaders b/src/osgEarthExtensions/bumpmap/BumpMapShaders
new file mode 100644
index 0000000..d835e7e
--- /dev/null
+++ b/src/osgEarthExtensions/bumpmap/BumpMapShaders
@@ -0,0 +1,39 @@
+// ***DO NOT EDIT THIS FILE - IT IS AUTOMATICALLY GENERATED BY CMAKE***
+/* -*-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_BUMPMAP_SHADERS
+#define OSGEARTH_BUMPMAP_SHADERS 1
+
+#include <osgEarth/ShaderLoader>
+
+namespace osgEarth { namespace BumpMap
+{
+    struct Shaders : public osgEarth::ShaderPackage
+	{
+        Shaders();
+        std::string VertexModel, VertexView;
+        std::string FragmentSimple, FragmentProgressive, FragmentCommon;
+	};
+	
+} } // namespace osgEarth/BumpMap
+
+#endif // OSGEARTH_BUMPMAP_SHADERS
\ No newline at end of file
diff --git a/src/osgEarthExtensions/bumpmap/BumpMapShaders.cpp.in b/src/osgEarthExtensions/bumpmap/BumpMapShaders.cpp.in
new file mode 100644
index 0000000..e4cf5a7
--- /dev/null
+++ b/src/osgEarthExtensions/bumpmap/BumpMapShaders.cpp.in
@@ -0,0 +1,24 @@
+/** CMake Template File - compiled into AutoGenShaders.cpp */
+#include <osgEarthExtensions/bumpmap/BumpMapShaders>
+
+#define MULTILINE(...) #__VA_ARGS__
+
+using namespace osgEarth::BumpMap;
+
+Shaders::Shaders()
+{
+    VertexModel = "BumpMap.vert.model.glsl";
+    _sources[VertexModel] = MULTILINE(@BumpMap.vert.model.glsl@);
+
+    VertexView = "BumpMap.vert.view.glsl";
+    _sources[VertexView] = MULTILINE(@BumpMap.vert.view.glsl@);
+
+    FragmentSimple = "BumpMap.frag.simple.glsl";
+    _sources[FragmentSimple] = MULTILINE(@BumpMap.frag.simple.glsl@);
+
+    FragmentProgressive = "BumpMap.frag.progressive.glsl";
+    _sources[FragmentProgressive] = MULTILINE(@BumpMap.frag.progressive.glsl@);
+
+    FragmentCommon = "BumpMap.frag.common.glsl";
+    _sources[FragmentCommon] = MULTILINE(@BumpMap.frag.common.glsl@);
+};
diff --git a/src/osgEarthExtensions/bumpmap/BumpMapTerrainEffect b/src/osgEarthExtensions/bumpmap/BumpMapTerrainEffect
new file mode 100644
index 0000000..b426c30
--- /dev/null
+++ b/src/osgEarthExtensions/bumpmap/BumpMapTerrainEffect
@@ -0,0 +1,82 @@
+/* -*-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_BumpMap_TERRAIN_EFFECT_H
+#define OSGEARTH_BumpMap_TERRAIN_EFFECT_H
+
+#include <osgEarth/TerrainEffect>
+#include <osgEarth/ImageLayer>
+#include <osg/Image>
+#include <osg/Uniform>
+#include <osg/Texture2D>
+
+using namespace osgEarth;
+
+namespace osgEarth { namespace BumpMap
+{
+    /**
+     * Effect that applies bump mapping to the terrain.
+     */
+    class BumpMapTerrainEffect : public TerrainEffect
+    {
+    public:
+        /** construct a new terrain effect. */
+        BumpMapTerrainEffect(const osgDB::Options* dbOptions);
+
+        /** Sets the image containing the normal offsets. */
+        void setBumpMapImage(osg::Image* image);
+
+        /** Sets the number of progressive octaves. */
+        void setOctaves(int value) { _octaves = value; }
+
+        /** Sets the range of the first octave. */
+        void setMaxRange(float value) { _maxRange = value; }
+
+        /** UNiform that controls intensity */
+        osg::Uniform* getIntensityUniform() const { return _intensityUniform.get(); }
+
+        /** Uniform that controls scale factor */
+        osg::Uniform* getScaleUniform() const { return _scaleUniform.get(); }
+
+
+    public: // TerrainEffect interface
+
+        void onInstall(TerrainEngineNode* engine);
+
+        void onUninstall(TerrainEngineNode* engine);
+
+
+    protected:
+        virtual ~BumpMapTerrainEffect() { }
+
+        bool  _ok;
+        int   _bumpMapUnit;
+        int   _octaves;
+        float _maxRange;
+        osg::ref_ptr<osg::Texture2D> _bumpMapTex;
+        osg::ref_ptr<osg::Uniform>   _bumpMapTexUniform;
+        osg::ref_ptr<osg::Uniform>   _scaleUniform;
+        osg::ref_ptr<osg::Uniform>   _intensityUniform;
+    };
+
+} } // namespace osgEarth::BumpMap
+
+#endif // OSGEARTH_BumpMap_TERRAIN_EFFECT_H
diff --git a/src/osgEarthExtensions/bumpmap/BumpMapTerrainEffect.cpp b/src/osgEarthExtensions/bumpmap/BumpMapTerrainEffect.cpp
new file mode 100644
index 0000000..9a2f737
--- /dev/null
+++ b/src/osgEarthExtensions/bumpmap/BumpMapTerrainEffect.cpp
@@ -0,0 +1,140 @@
+/* -*-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 "BumpMapTerrainEffect"
+#include "BumpMapOptions"
+
+#include <osgEarth/Registry>
+#include <osgEarth/Capabilities>
+#include <osgEarth/VirtualProgram>
+#include <osgEarth/TerrainEngineNode>
+#include <osgEarth/TerrainTileNode>
+
+#include "BumpMapShaders"
+
+#define LC "[BumpMap] "
+
+#define BUMP_SAMPLER   "oe_bumpmap_tex"
+
+using namespace osgEarth;
+using namespace osgEarth::BumpMap;
+
+
+BumpMapTerrainEffect::BumpMapTerrainEffect(const osgDB::Options* dbOptions)
+{
+    BumpMapOptions defaults;
+    _octaves = defaults.octaves().get();
+    _maxRange = defaults.maxRange().get();
+
+    _scaleUniform     = new osg::Uniform("oe_bumpmap_scale", defaults.scale().get());
+    _intensityUniform = new osg::Uniform("oe_bumpmap_intensity", defaults.intensity().get());
+}
+
+void
+BumpMapTerrainEffect::setBumpMapImage(osg::Image* image)
+{
+    if ( image )
+    {
+        _bumpMapTex = new osg::Texture2D(image);
+        _bumpMapTex->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT);
+        _bumpMapTex->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT);
+        _bumpMapTex->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR);
+        _bumpMapTex->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR);
+        _bumpMapTex->setMaxAnisotropy(1.0f);
+        _bumpMapTex->setUnRefImageDataAfterApply(true);
+        _bumpMapTex->setResizeNonPowerOfTwoHint(false);
+    }
+    else
+    {
+        OE_WARN << LC << "Failed to load the bump map texture\n";
+    }
+}
+
+void
+BumpMapTerrainEffect::onInstall(TerrainEngineNode* engine)
+{
+    if ( engine && _bumpMapTex.valid() )
+    {
+        osg::StateSet* stateset = engine->getTerrainStateSet();
+
+        // install the NormalMap texture array:
+        if ( engine->getResources()->reserveTextureImageUnit(_bumpMapUnit, "BumpMap") )
+        {
+            // NormalMap sampler
+            _bumpMapTexUniform = stateset->getOrCreateUniform(BUMP_SAMPLER, osg::Uniform::SAMPLER_2D);
+            _bumpMapTexUniform->set( _bumpMapUnit );
+            stateset->setTextureAttribute( _bumpMapUnit, _bumpMapTex.get(), osg::StateAttribute::ON );
+
+            // configure shaders
+            VirtualProgram* vp = VirtualProgram::getOrCreate(stateset);
+
+            Shaders package;            
+            package.define( "OE_USE_NORMAL_MAP", engine->normalTexturesRequired() );
+
+            package.load( vp, package.VertexModel );
+            package.load( vp, package.VertexView );
+            package.load( vp, _octaves <= 1? package.FragmentSimple : package.FragmentProgressive );
+
+            if ( _octaves > 1 )
+                stateset->addUniform(new osg::Uniform("oe_bumpmap_octaves", _octaves));
+
+            stateset->addUniform(new osg::Uniform("oe_bumpmap_maxRange", _maxRange));
+
+            stateset->addUniform( _scaleUniform.get() );
+            stateset->addUniform( _intensityUniform.get() );
+        }
+    }
+}
+
+
+void
+BumpMapTerrainEffect::onUninstall(TerrainEngineNode* engine)
+{
+    osg::StateSet* stateset = engine->getStateSet();
+    if ( stateset )
+    {
+        if ( _bumpMapTex.valid() )
+        {
+            stateset->removeUniform("oe_bumpmap_maxRange");
+            stateset->removeUniform("oe_bumpmap_octaves");
+            stateset->removeUniform( _scaleUniform.get() );
+            stateset->removeUniform( _intensityUniform.get() );
+            stateset->removeUniform( _bumpMapTexUniform.get() );
+            stateset->removeTextureAttribute( _bumpMapUnit, osg::StateAttribute::TEXTURE );
+        }
+
+        VirtualProgram* vp = VirtualProgram::get(stateset);
+        if ( vp )
+        {
+            Shaders pkg;
+            pkg.unload( vp, pkg.VertexModel );
+            pkg.unload( vp, pkg.VertexView );
+            pkg.unload( vp, pkg.FragmentSimple );
+            pkg.unload( vp, pkg.FragmentProgressive );
+        }
+    }
+    
+    if ( _bumpMapUnit >= 0 )
+    {
+        engine->getResources()->releaseTextureImageUnit( _bumpMapUnit );
+        _bumpMapUnit = -1;
+    }
+}
diff --git a/src/osgEarthExtensions/bumpmap/CMakeLists.txt b/src/osgEarthExtensions/bumpmap/CMakeLists.txt
new file mode 100644
index 0000000..7bd9ce6
--- /dev/null
+++ b/src/osgEarthExtensions/bumpmap/CMakeLists.txt
@@ -0,0 +1,46 @@
+#
+# bump mapping plugin
+#
+
+set(TARGET_GLSL
+    BumpMap.vert.model.glsl
+	BumpMap.vert.view.glsl
+    BumpMap.frag.simple.glsl
+    BumpMap.frag.progressive.glsl
+    BumpMap.frag.common.glsl)
+
+set(SHADERS_CPP "${CMAKE_CURRENT_BINARY_DIR}/AutoGenShaders.cpp")
+
+set(TARGET_IN
+    BumpMapShaders.cpp.in)
+
+configure_shaders(
+    BumpMapShaders.cpp.in
+    ${SHADERS_CPP}
+    ${TARGET_GLSL} )
+
+set(TARGET_SRC
+	BumpMapPlugin.cpp
+	BumpMapExtension.cpp
+	BumpMapTerrainEffect.cpp
+	${SHADERS_CPP} )
+	
+set(LIB_PUBLIC_HEADERS
+	BumpMapExtension
+	BumpMapOptions
+	BumpMapTerrainEffect)
+	
+set(TARGET_H
+	${LIB_PUBLIC_HEADERS}
+	BumpMapShaders )
+
+
+set(TARGET_COMMON_LIBRARIES ${TARGET_COMMON_LIBRARIES}
+    osgEarthUtil)
+	
+setup_extension(osgearth_bumpmap)
+
+# to install public driver includes:
+set(LIB_NAME bumpmap)
+
+#include(ModuleInstallOsgEarthExtensionIncludes OPTIONAL)
diff --git a/src/osgEarthExtensions/mapinspector/CMakeLists.txt b/src/osgEarthExtensions/mapinspector/CMakeLists.txt
new file mode 100644
index 0000000..6fad79a
--- /dev/null
+++ b/src/osgEarthExtensions/mapinspector/CMakeLists.txt
@@ -0,0 +1,27 @@
+#
+# MapInspector Extension
+#
+
+SET(TARGET_SRC
+	MapInspectorPlugin.cpp
+	MapInspectorExtension.cpp
+	MapInspectorUI.cpp)
+	
+SET(LIB_PUBLIC_HEADERS
+	MapInspectorExtension
+	MapInspectorUI)
+	
+SET(TARGET_H
+	${LIB_PUBLIC_HEADERS} )
+
+SET(TARGET_COMMON_LIBRARIES ${TARGET_COMMON_LIBRARIES}
+    osgEarthUtil
+	osgEarthFeatures
+	osgEarthSymbology)
+	
+SETUP_EXTENSION(osgearth_mapinspector)
+
+SET(LIB_NAME mapinspector)
+
+INCLUDE(ModuleInstallOsgEarthExtensionIncludes OPTIONAL)
+
diff --git a/src/osgEarthExtensions/mapinspector/MapInspectorExtension b/src/osgEarthExtensions/mapinspector/MapInspectorExtension
new file mode 100644
index 0000000..b2e4a37
--- /dev/null
+++ b/src/osgEarthExtensions/mapinspector/MapInspectorExtension
@@ -0,0 +1,84 @@
+/* -*-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.
+ *
+ * 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_MAPINSPECTOR_EXTENSION
+#define OSGEARTH_MAPINSPECTOR_EXTENSION 1
+
+#include <osgEarth/Extension>
+#include <osgEarth/MapNode>
+#include <osgEarth/MapCallback>
+#include <osgEarthUtil/Controls>
+
+namespace osgEarth { namespace MapInspector
+{
+    using namespace osgEarth;
+    using namespace osgEarth::Util::Controls;
+
+    /**
+     * Displays visual metadata about the visible layers in a map.
+     */
+    class MapInspectorExtension : public Extension,
+                                  public ExtensionInterface<MapNode>,
+                                  public ExtensionInterface<Control>,
+                                  public MapCallback
+    {
+    public:
+        META_Object(osgearth_ext_MapInspector, MapInspectorExtension);
+
+        // CTORs
+        MapInspectorExtension();
+        MapInspectorExtension(const ConfigOptions& options);
+
+        // DTOR
+        virtual ~MapInspectorExtension();
+
+
+    public: // ExtensionInterface<MapNode>
+
+        bool connect(MapNode* mapNode);
+
+        bool disconnect(MapNode* mapNode);
+
+
+    public: // ExtensionInterface<Control>
+
+        bool connect(Control* control);
+
+        bool disconnect(Control* control);
+
+        
+    public: // MapCallback
+
+        void onMapModelChanged(const MapModelChange& change);
+
+
+    protected: // Object
+
+        MapInspectorExtension(const MapInspectorExtension& rhs, const osg::CopyOp& op) { }
+
+
+    private:
+        osg::observer_ptr<MapNode> _mapNode;
+        osg::ref_ptr<Control>      _ui;
+
+        void ctor();
+    };
+
+} } // namespace osgEarth::MapInspector
+
+#endif // OSGEARTH_MAPINSPECTOR_EXTENSION
diff --git a/src/osgEarthExtensions/mapinspector/MapInspectorExtension.cpp b/src/osgEarthExtensions/mapinspector/MapInspectorExtension.cpp
new file mode 100644
index 0000000..a581b0e
--- /dev/null
+++ b/src/osgEarthExtensions/mapinspector/MapInspectorExtension.cpp
@@ -0,0 +1,112 @@
+/* -*-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.
+ *
+ * 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 "MapInspectorExtension"
+#include "MapInspectorUI"
+
+#include <osgEarthFeatures/FeatureSource>
+#include <osgEarthDrivers/feature_ogr/OGRFeatureOptions>
+#include <osgEarthAnnotation/FeatureNode>
+#include <osgEarthSymbology/Style>
+
+using namespace osgEarth;
+using namespace osgEarth::Util;
+using namespace osgEarth::Util::Controls;
+using namespace osgEarth::MapInspector;
+
+#define LC "[MapInspector] "
+
+MapInspectorExtension::MapInspectorExtension()
+{
+    ctor();
+}
+
+MapInspectorExtension::MapInspectorExtension(const ConfigOptions& options)
+{
+    ctor();
+}
+
+MapInspectorExtension::~MapInspectorExtension()
+{
+    // nop
+}
+
+void
+MapInspectorExtension::ctor()
+{
+    OE_INFO << LC << "loaded\n";
+    _ui = new MapInspectorUI();
+}
+
+
+void 
+MapInspectorExtension::onMapModelChanged(const MapModelChange& change)
+{
+    osg::ref_ptr<MapNode> mapNode;
+    _mapNode.lock(mapNode);
+    static_cast<MapInspectorUI*>(_ui.get())->reinit(mapNode.get());
+}
+
+bool
+MapInspectorExtension::connect(MapNode* mapNode)
+{
+    OE_INFO << LC << "connected\n";
+    if ( mapNode )
+    {
+        _mapNode = mapNode;
+        _mapNode->getMap()->addMapCallback(this);
+        static_cast<MapInspectorUI*>(_ui.get())->reinit(mapNode);
+    }
+    
+    return true;
+}
+
+bool
+MapInspectorExtension::disconnect(MapNode* mapNode)
+{
+    OE_INFO << LC << "disconnected\n";
+
+    if ( mapNode )
+        mapNode->getMap()->removeMapCallback(this);
+
+    static_cast<MapInspectorUI*>(_ui.get())->reinit(0L);
+    return true;
+}
+
+bool
+MapInspectorExtension::connect(Control* control)
+{
+    Container* container = dynamic_cast<Container*>(control);
+    if ( container && _ui.valid() )
+    {
+        container->addControl( _ui.get() );
+    }
+
+    return true;
+}
+
+bool
+MapInspectorExtension::disconnect(Control* control)
+{
+    Container* container = dynamic_cast<Container*>(control);
+    if ( container && _ui.valid() )
+    {
+        container->removeChild( _ui.get() );
+    }
+    return true;
+}
diff --git a/src/osgEarthExtensions/mapinspector/MapInspectorPlugin.cpp b/src/osgEarthExtensions/mapinspector/MapInspectorPlugin.cpp
new file mode 100644
index 0000000..94ae2bd
--- /dev/null
+++ b/src/osgEarthExtensions/mapinspector/MapInspectorPlugin.cpp
@@ -0,0 +1,55 @@
+/* -*-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.
+ *
+ * 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 "MapInspectorExtension"
+
+#include <osgDB/ReaderWriter>
+#include <osgDB/Registry>
+#include <osgDB/FileNameUtils>
+
+namespace osgEarth { namespace MapInspector
+{
+    /**
+     * Plugin entry point
+     */
+    class MapInspectorPlugin : public osgDB::ReaderWriter
+    {
+    public: // Plugin stuff
+
+        MapInspectorPlugin() {
+            supportsExtension( "osgearth_mapinspector", "osgEarth MapInspector Extension" );
+        }
+        
+        const char* className() {
+            return "osgEarth MapInspector Extension";
+        }
+
+        virtual ~MapInspectorPlugin() { }
+
+        ReadResult readObject(const std::string& filename, const osgDB::Options* dbOptions) const
+        {
+          if ( !acceptsExtension(osgDB::getLowerCaseFileExtension(filename)) )
+                return ReadResult::FILE_NOT_HANDLED;
+
+          return ReadResult( new MapInspectorExtension(Extension::getConfigOptions(dbOptions)) );
+        }
+    };
+
+    REGISTER_OSGPLUGIN(osgearth_mapinspector, MapInspectorPlugin)
+
+} } // namespace osgEarth::MapInspector
diff --git a/src/osgEarthExtensions/mapinspector/MapInspectorUI b/src/osgEarthExtensions/mapinspector/MapInspectorUI
new file mode 100644
index 0000000..e825c40
--- /dev/null
+++ b/src/osgEarthExtensions/mapinspector/MapInspectorUI
@@ -0,0 +1,49 @@
+/* -*-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.
+ *
+ * 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_MAPINSPECTOR_UI
+#define OSGEARTH_MAPINSPECTOR_UI 1
+
+#include <osgEarth/MapCallback>
+#include <osgEarth/MapNode>
+#include <osgEarthUtil/Controls>
+#include <osg/View>
+
+namespace osgEarth { namespace MapInspector
+{
+    using namespace osgEarth;
+
+    class MapInspectorUI : public osgEarth::Util::Controls::Grid
+    {
+    public:
+        /** create UI */
+        MapInspectorUI();
+
+        /** rebuild everything */
+        void reinit(MapNode* mapNode);
+
+    private:
+        osg::ref_ptr<osg::Group>    _annos;
+
+        void addTerrainLayer(class TerrainLayer* layer, MapNode* mapNode);
+        void addModelLayer  (class ModelLayer*   layer, MapNode* mapNode);
+    };
+
+} } // namespace
+
+#endif // OSGEARTH_MAPINSPECTOR_UI
\ No newline at end of file
diff --git a/src/osgEarthExtensions/mapinspector/MapInspectorUI.cpp b/src/osgEarthExtensions/mapinspector/MapInspectorUI.cpp
new file mode 100644
index 0000000..e9ee281
--- /dev/null
+++ b/src/osgEarthExtensions/mapinspector/MapInspectorUI.cpp
@@ -0,0 +1,162 @@
+/* -*-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.
+ *
+ * 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 "MapInspectorUI"
+#include <osgEarth/GeoData>
+#include <osgEarthAnnotation/FeatureNode>
+#include <osgEarthAnnotation/LabelNode>
+#include <osgEarthFeatures/Feature>
+#include <osgEarthSymbology/Style>
+#include <osgEarthSymbology/Geometry>
+
+using namespace osgEarth;
+using namespace osgEarth::Annotation;
+using namespace osgEarth::Features;
+using namespace osgEarth::Symbology;
+using namespace osgEarth::MapInspector;
+
+namespace ui = osgEarth::Util::Controls;
+
+#define LC "[MapInspectorUI] "
+
+MapInspectorUI::MapInspectorUI()
+{
+    //nop
+}
+
+void
+MapInspectorUI::reinit(MapNode* mapNode)
+{
+    if ( !_annos.valid() )
+        _annos = new osg::Group();
+
+    _annos->removeChildren(0, _annos->getNumChildren());
+
+    this->clearControls();
+
+    if ( mapNode )
+    {
+        // install annotation group as neccesary
+        if (_annos->getNumParents() == 0 || _annos->getParent(0) != mapNode)
+        {
+            if ( _annos->getNumParents() > 0 )
+            {
+                _annos->getParent(0)->removeChild(_annos.get());
+            }
+            mapNode->addChild( _annos.get() );
+        }
+        
+        Map* map = mapNode->getMap();
+
+        for(int i=0; i<map->getNumElevationLayers(); ++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 );
+        }
+
+        for(int i=0; i<map->getNumModelLayers(); ++i)
+        {
+            ModelLayer* layer = map->getModelLayerAt(i);
+            addModelLayer( layer, mapNode );
+        }
+    }
+    else
+    {
+        OE_INFO << LC << "MapNode is null\n";
+    }
+}
+
+void
+MapInspectorUI::addTerrainLayer(TerrainLayer* layer,
+                                MapNode*      mapNode)
+{
+    if ( layer->getTileSource() == 0L )
+        return;
+
+    const Color colors[6] = {
+        Color::White,
+        Color::Yellow,
+        Color::Cyan,
+        Color::Lime,
+        Color::Red,
+        Color::Magenta
+    };
+    Color color = colors[(int)layer->getUID()%6];
+
+    osg::ref_ptr<MultiGeometry> collection = new MultiGeometry();
+
+    const DataExtentList& exlist = layer->getTileSource()->getDataExtents();
+    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 );
+    }
+
+    // poly:
+    {
+        Style style;
+        style.getOrCreate<LineSymbol>()->stroke()->color() = color;
+        style.getOrCreate<LineSymbol>()->stroke()->width() = 2;
+        style.getOrCreate<AltitudeSymbol>()->clamping() = AltitudeSymbol::CLAMP_TO_TERRAIN;
+        style.getOrCreate<AltitudeSymbol>()->technique() = AltitudeSymbol::TECHNIQUE_DRAPE;
+        style.getOrCreate<RenderSymbol>()->lighting() = false;
+
+        Feature* feature = new Feature(collection.get(), layer->getProfile()->getSRS(), style);
+        FeatureNode* node = new FeatureNode( mapNode, feature );
+        _annos->addChild( node );
+    }
+
+    // label:
+    std::string text = 
+        !layer->getName().empty()? layer->getName() :
+        Stringify() << "Layer " << layer->getUID();
+
+    {
+        Style style;
+        TextSymbol* ts = style.getOrCreate<TextSymbol>();
+        ts->halo()->color().set(0,0,0,1);
+        ts->declutter() = true;
+        ts->alignment() = TextSymbol::ALIGN_CENTER_CENTER;
+        
+        osg::Vec2d center = collection->getBounds().center2d();
+        GeoPoint position(layer->getProfile()->getSRS(), center.x(), center.y(), 0.0, ALTMODE_ABSOLUTE);
+
+        LabelNode* label = new LabelNode(mapNode, position, text, style);
+        _annos->addChild( label );
+    }
+
+    unsigned r = this->getNumRows();
+    setControl(0, r, new ui::LabelControl(text, color));
+}
+
+void
+MapInspectorUI::addModelLayer(ModelLayer* layer,
+                              MapNode*    mapNode)
+{
+}
diff --git a/src/osgEarthExtensions/noise/CMakeLists.txt b/src/osgEarthExtensions/noise/CMakeLists.txt
new file mode 100644
index 0000000..1fb5617
--- /dev/null
+++ b/src/osgEarthExtensions/noise/CMakeLists.txt
@@ -0,0 +1,25 @@
+#
+# Noise generator plugin
+#
+
+set(TARGET_SRC
+	NoisePlugin.cpp
+	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_extension(osgearth_noise)
+
+# to install public driver includes:
+set(LIB_NAME noise)
diff --git a/src/osgEarthExtensions/noise/NoiseExtension b/src/osgEarthExtensions/noise/NoiseExtension
new file mode 100644
index 0000000..1a2fcd1
--- /dev/null
+++ b/src/osgEarthExtensions/noise/NoiseExtension
@@ -0,0 +1,73 @@
+/* -*-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.
+ *
+ * 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:
+        META_Object(osgearth_ext_normalmap, NoiseExtension);
+
+        // CTORs
+        NoiseExtension();
+        NoiseExtension(const NoiseOptions& options);
+
+        // DTOR
+        virtual ~NoiseExtension();
+
+
+    public: // Extension
+
+        void setDBOptions(const osgDB::Options* dbOptions);
+
+
+    public: // ExtensionInterface<MapNode>
+
+        bool connect(MapNode* mapNode);
+
+        bool disconnect(MapNode* mapNode);
+
+
+    protected: // Object
+        NoiseExtension(const NoiseExtension& rhs, const osg::CopyOp& op) { }
+
+    private:
+        const NoiseOptions                 _options;
+        osg::ref_ptr<const osgDB::Options>     _dbOptions;
+        osg::ref_ptr<NoiseTerrainEffect>   _effect;
+    };
+
+} } // namespace osgEarth::Noise
+
+#endif // OSGEARTH_NOISE_EXTENSION
diff --git a/src/osgEarthExtensions/noise/NoiseExtension.cpp b/src/osgEarthExtensions/noise/NoiseExtension.cpp
new file mode 100644
index 0000000..b7bcd44
--- /dev/null
+++ b/src/osgEarthExtensions/noise/NoiseExtension.cpp
@@ -0,0 +1,78 @@
+/* -*-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.
+ *
+ * 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"
+
+using namespace osgEarth;
+using namespace osgEarth::Noise;
+
+#define LC "[NoiseExtension] "
+
+
+NoiseExtension::NoiseExtension()
+{
+    //nop
+}
+
+NoiseExtension::NoiseExtension(const NoiseOptions& options) :
+_options( 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( _dbOptions.get() );
+
+    mapNode->getTerrainEngine()->addEffect( _effect.get() );
+    
+    OE_INFO << LC << "Installed!\n";
+
+    return true;
+}
+
+bool
+NoiseExtension::disconnect(MapNode* mapNode)
+{
+    if ( mapNode )
+    {
+        mapNode->getTerrainEngine()->removeEffect( _effect.get() );
+    }
+    _effect = 0L;
+    return true;
+}
+
diff --git a/src/osgEarthExtensions/noise/NoiseOptions b/src/osgEarthExtensions/noise/NoiseOptions
new file mode 100644
index 0000000..d7e316c
--- /dev/null
+++ b/src/osgEarthExtensions/noise/NoiseOptions
@@ -0,0 +1,68 @@
+/* -*-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.
+ *
+ * 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:
+        // No properties.
+
+    public:
+        NoiseOptions( const ConfigOptions& opt =ConfigOptions() ) : DriverConfigOptions( opt )
+        {
+            setDriver( "noise" );
+            fromConfig( _conf );
+        }
+
+        virtual ~NoiseOptions() { }
+
+    public:
+        Config getConfig() const {
+            Config conf = DriverConfigOptions::getConfig();
+            // future props.
+            return conf;
+        }
+
+    protected:
+        void mergeConfig( const Config& conf ) {
+            DriverConfigOptions::mergeConfig( conf );
+            fromConfig( conf );
+        }
+
+    private:
+        void fromConfig( const Config& conf ) {
+            //future props
+        }
+    };
+
+} } // namespace osgEarth::Noise
+
+#endif // OSGEARTH_DRIVER_NOISE_OPTIONS
+
diff --git a/src/osgEarthExtensions/noise/NoisePlugin.cpp b/src/osgEarthExtensions/noise/NoisePlugin.cpp
new file mode 100644
index 0000000..31ebfe3
--- /dev/null
+++ b/src/osgEarthExtensions/noise/NoisePlugin.cpp
@@ -0,0 +1,56 @@
+/* -*-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.
+ *
+ * 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 "NoiseOptions"
+#include "NoiseExtension"
+
+#include <osgDB/ReaderWriter>
+#include <osgDB/Registry>
+#include <osgDB/FileNameUtils>
+
+namespace osgEarth { namespace Noise
+{
+    /**
+     * Plugin entry point
+     */
+    class NoisePlugin : public osgDB::ReaderWriter
+    {
+    public: // Plugin stuff
+
+        NoisePlugin() {
+            supportsExtension( "osgearth_noise", "osgEarth Noise Generator Extension" );
+        }
+        
+        const char* className() {
+            return "osgEarth Noise Generator Extension";
+        }
+
+        virtual ~NoisePlugin() { }
+
+        ReadResult readObject(const std::string& filename, const osgDB::Options* dbOptions) const
+        {
+          if ( !acceptsExtension(osgDB::getLowerCaseFileExtension(filename)) )
+                return ReadResult::FILE_NOT_HANDLED;
+
+          return ReadResult( new NoiseExtension(Extension::getConfigOptions(dbOptions)) );
+        }
+    };
+
+    REGISTER_OSGPLUGIN(osgearth_noise, NoisePlugin)
+
+} } // namespace osgEarth::Noise
diff --git a/src/osgEarthExtensions/noise/NoiseTerrainEffect b/src/osgEarthExtensions/noise/NoiseTerrainEffect
new file mode 100644
index 0000000..26b73b3
--- /dev/null
+++ b/src/osgEarthExtensions/noise/NoiseTerrainEffect
@@ -0,0 +1,62 @@
+/* -*-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 <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 osgDB::Options* dbOptions);
+
+
+    public: // TerrainEffect interface
+
+        void onInstall(TerrainEngineNode* engine);
+
+        void onUninstall(TerrainEngineNode* engine);
+
+
+    protected:
+        virtual ~NoiseTerrainEffect() { }
+
+        osg::Texture* createNoiseTexture() const;
+
+        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/osgEarthExtensions/noise/NoiseTerrainEffect.cpp b/src/osgEarthExtensions/noise/NoiseTerrainEffect.cpp
new file mode 100644
index 0000000..a1b91e2
--- /dev/null
+++ b/src/osgEarthExtensions/noise/NoiseTerrainEffect.cpp
@@ -0,0 +1,166 @@
+/* -*-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 osgDB::Options* dbOptions) :
+_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 = 1024;
+    const int slices = 1;
+
+    GLenum type = slices > 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<slices; ++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 1
+    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/osgEarthExtensions/normalmap/CMakeLists.txt b/src/osgEarthExtensions/normalmap/CMakeLists.txt
new file mode 100644
index 0000000..528f4a1
--- /dev/null
+++ b/src/osgEarthExtensions/normalmap/CMakeLists.txt
@@ -0,0 +1,43 @@
+#
+# normal mapping plugin
+#
+
+set(TARGET_GLSL
+    NormalMap.vert.glsl
+    NormalMap.frag.glsl)
+
+set(TARGET_IN
+    NormalMapShaders.cpp.in)
+
+set(SHADERS_CPP "${CMAKE_CURRENT_BINARY_DIR}/AutoGenShaders.cpp")
+
+configure_shaders(
+    NormalMapShaders.cpp.in
+    ${SHADERS_CPP}
+    ${TARGET_GLSL} )
+
+set(TARGET_SRC
+	NormalMapPlugin.cpp
+	NormalMapExtension.cpp
+	NormalMapTerrainEffect.cpp
+	${SHADERS_CPP} )
+	
+set(LIB_PUBLIC_HEADERS
+	NormalMapExtension
+	NormalMapOptions
+	NormalMapTerrainEffect)
+	
+set(TARGET_H
+	${LIB_PUBLIC_HEADERS}
+	NormalMapShaders )
+
+
+set(TARGET_COMMON_LIBRARIES ${TARGET_COMMON_LIBRARIES}
+    osgEarthUtil)
+	
+setup_extension(osgearth_normalmap)
+
+# to install public driver includes:
+set(LIB_NAME normalmap)
+
+#include(ModuleInstallOsgEarthExtensionIncludes OPTIONAL)
diff --git a/src/osgEarthExtensions/normalmap/NormalMap.frag.glsl b/src/osgEarthExtensions/normalmap/NormalMap.frag.glsl
new file mode 100644
index 0000000..1124ebc
--- /dev/null
+++ b/src/osgEarthExtensions/normalmap/NormalMap.frag.glsl
@@ -0,0 +1,27 @@
+#version 110
+#pragma vp_entryPoint "oe_nmap_fragment"
+#pragma vp_location   "fragment_coloring"
+#pragma vp_order      "0.2"
+
+// stage global:
+vec3 oe_global_Normal;
+
+uniform vec4 oe_tile_key;
+uniform sampler2D oe_nmap_normalTex;
+varying vec4 oe_nmap_normalCoords;
+varying mat3 oe_nmap_TBN;
+
+void oe_nmap_fragment(inout vec4 color)
+{
+    vec4 encodedNormal = texture2D(oe_nmap_normalTex, oe_nmap_normalCoords.st);
+    vec3 normalTangent = normalize(encodedNormal.xyz*2.0-1.0);
+    oe_global_Normal = normalize(oe_nmap_TBN * normalTangent);
+
+    // visualize curvature:
+    //color.rgb = vec3(0,0,0);
+    //if(decoded.a >= 0.4) color.r = 1.0;
+    //if(decoded.a <= -0.4) color.b = 1.0;
+
+    // visualize normals:
+    //color.rgb = encodedNormal.xyz;
+}
diff --git a/src/osgEarthExtensions/normalmap/NormalMap.vert.glsl b/src/osgEarthExtensions/normalmap/NormalMap.vert.glsl
new file mode 100644
index 0000000..26dde1c
--- /dev/null
+++ b/src/osgEarthExtensions/normalmap/NormalMap.vert.glsl
@@ -0,0 +1,25 @@
+#version 110
+#pragma vp_entryPoint "oe_nmap_vertex"
+#pragma vp_location   "vertex_model"
+#pragma vp_order      "0.5"
+
+varying vec3 oe_Normal;
+varying vec4 oe_layer_tilec;
+
+uniform mat4 oe_nmap_normalTexMatrix;
+varying vec4 oe_nmap_normalCoords;
+varying mat3 oe_nmap_TBN;
+
+void oe_nmap_vertex(inout vec4 VertexMODEL)
+{
+    // calculate the sampling coordinates for the normal texture
+    oe_nmap_normalCoords = oe_nmap_normalTexMatrix * oe_layer_tilec;
+
+    // form the matrix that will transform a normal vector from
+    // tangent space to model space in the fragment shader.
+    // We expect the vertex normal to be a simple UP vector.
+    vec3 B = vec3(0,1,0);
+    vec3 N = oe_Normal;
+    vec3 T = normalize(cross(B,N));
+    oe_nmap_TBN = gl_NormalMatrix * mat3(T, B, N);
+}
\ No newline at end of file
diff --git a/src/osgEarthExtensions/normalmap/NormalMapExtension b/src/osgEarthExtensions/normalmap/NormalMapExtension
new file mode 100644
index 0000000..241486b
--- /dev/null
+++ b/src/osgEarthExtensions/normalmap/NormalMapExtension
@@ -0,0 +1,73 @@
+/* -*-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.
+ *
+ * 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_NORMALMAP_EXTENSION
+#define OSGEARTH_NORMALMAP_EXTENSION 1
+
+#include "NormalMapOptions"
+#include "NormalMapTerrainEffect"
+#include <osgEarth/Extension>
+#include <osgEarth/MapNode>
+#include <osgEarthUtil/Controls>
+
+namespace osgEarth { namespace NormalMap
+{
+    using namespace osgEarth;
+    using namespace osgEarth::Util::Controls;
+
+    /**
+     * Extension for loading the normal mapping effect on demand.
+     */
+    class NormalMapExtension : public Extension,
+                               public ExtensionInterface<MapNode>
+    {
+    public:
+        META_Object(osgearth_ext_normalmap, NormalMapExtension);
+
+        // CTORs
+        NormalMapExtension();
+        NormalMapExtension(const NormalMapOptions& options);
+
+        // DTOR
+        virtual ~NormalMapExtension();
+
+
+    public: // Extension
+
+        void setDBOptions(const osgDB::Options* dbOptions);
+
+
+    public: // ExtensionInterface<MapNode>
+
+        bool connect(MapNode* mapNode);
+
+        bool disconnect(MapNode* mapNode);
+
+
+    protected: // Object
+        NormalMapExtension(const NormalMapExtension& rhs, const osg::CopyOp& op) { }
+
+    private:
+        const NormalMapOptions                 _options;
+        osg::ref_ptr<const osgDB::Options>     _dbOptions;
+        osg::ref_ptr<NormalMapTerrainEffect>   _effect;
+    };
+
+} } // namespace osgEarth::NormalMap
+
+#endif // OSGEARTH_NORMALMAP_EXTENSION
diff --git a/src/osgEarthExtensions/normalmap/NormalMapExtension.cpp b/src/osgEarthExtensions/normalmap/NormalMapExtension.cpp
new file mode 100644
index 0000000..b1f565b
--- /dev/null
+++ b/src/osgEarthExtensions/normalmap/NormalMapExtension.cpp
@@ -0,0 +1,80 @@
+/* -*-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.
+ *
+ * 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 "NormalMapExtension"
+#include "NormalMapTerrainEffect"
+
+#include <osgEarth/MapNode>
+
+using namespace osgEarth;
+using namespace osgEarth::NormalMap;
+
+#define LC "[NormalMapExtension] "
+
+
+NormalMapExtension::NormalMapExtension()
+{
+    //nop
+}
+
+NormalMapExtension::NormalMapExtension(const NormalMapOptions& options) :
+_options( options )
+{
+    //nop
+}
+
+NormalMapExtension::~NormalMapExtension()
+{
+    //nop
+}
+
+void
+NormalMapExtension::setDBOptions(const osgDB::Options* dbOptions)
+{
+    _dbOptions = dbOptions;
+}
+
+bool
+NormalMapExtension::connect(MapNode* mapNode)
+{
+    if ( !mapNode )
+    {
+        OE_WARN << LC << "Illegal: MapNode cannot be null." << std::endl;
+        return false;
+    }
+
+    _effect = new NormalMapTerrainEffect( _dbOptions.get() );
+
+    mapNode->getTerrainEngine()->addEffect( _effect.get() );
+    
+    OE_INFO << LC << "Installed!\n";
+
+    return true;
+}
+
+bool
+NormalMapExtension::disconnect(MapNode* mapNode)
+{
+    if ( mapNode )
+    {
+        mapNode->getTerrainEngine()->removeEffect( _effect.get() );
+    }
+    _effect = 0L;
+    return true;
+}
+
diff --git a/src/osgEarthExtensions/normalmap/NormalMapOptions b/src/osgEarthExtensions/normalmap/NormalMapOptions
new file mode 100644
index 0000000..248f1c4
--- /dev/null
+++ b/src/osgEarthExtensions/normalmap/NormalMapOptions
@@ -0,0 +1,72 @@
+/* -*-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.
+ *
+ * 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_NORMALMAP_OPTIONS
+#define OSGEARTH_DRIVER_NORMALMAP_OPTIONS 1
+
+#include <osgEarth/Common>
+#include <osgEarth/URI>
+
+namespace osgEarth { namespace NormalMap
+{
+    using namespace osgEarth;
+
+    /**
+     * Options governing normal mapping of the terrain.
+     */
+    class NormalMapOptions : public DriverConfigOptions // NO EXPORT; header only
+    {
+    public:
+        // No properties.
+
+    public:
+        NormalMapOptions( const ConfigOptions& opt =ConfigOptions() ) : DriverConfigOptions( opt )
+        {
+            setDriver( "normalmap" );
+            fromConfig( _conf );
+        }
+
+        virtual ~NormalMapOptions() { }
+
+    public:
+        Config getConfig() const {
+            Config conf = DriverConfigOptions::getConfig();
+            // future props.
+            return conf;
+        }
+
+    protected:
+        void mergeConfig( const Config& conf ) {
+            DriverConfigOptions::mergeConfig( conf );
+            fromConfig( conf );
+        }
+
+    private:
+        void fromConfig( const Config& conf ) {
+            //future props
+        }
+
+        optional<URI>   _imageURI;
+        optional<float> _intensity;
+        optional<float> _scale;
+    };
+
+} } // namespace osgEarth::NormalMap
+
+#endif // OSGEARTH_DRIVER_NORMALMAP_OPTIONS
+
diff --git a/src/osgEarthExtensions/normalmap/NormalMapPlugin.cpp b/src/osgEarthExtensions/normalmap/NormalMapPlugin.cpp
new file mode 100644
index 0000000..18de306
--- /dev/null
+++ b/src/osgEarthExtensions/normalmap/NormalMapPlugin.cpp
@@ -0,0 +1,56 @@
+/* -*-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.
+ *
+ * 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 "NormalMapOptions"
+#include "NormalMapExtension"
+
+#include <osgDB/ReaderWriter>
+#include <osgDB/Registry>
+#include <osgDB/FileNameUtils>
+
+namespace osgEarth { namespace NormalMap
+{
+    /**
+     * Plugin entry point
+     */
+    class NormalMapPlugin : public osgDB::ReaderWriter
+    {
+    public: // Plugin stuff
+
+        NormalMapPlugin() {
+            supportsExtension( "osgearth_normalmap", "osgEarth Normal Map Extension Plugin" );
+        }
+        
+        const char* className() {
+            return "osgEarth Normal Map Extension Plugin";
+        }
+
+        virtual ~NormalMapPlugin() { }
+
+        ReadResult readObject(const std::string& filename, const osgDB::Options* dbOptions) const
+        {
+          if ( !acceptsExtension(osgDB::getLowerCaseFileExtension(filename)) )
+                return ReadResult::FILE_NOT_HANDLED;
+
+          return ReadResult( new NormalMapExtension(Extension::getConfigOptions(dbOptions)) );
+        }
+    };
+
+    REGISTER_OSGPLUGIN(osgearth_normalmap, NormalMapPlugin)
+
+} } // namespace osgEarth::NormalMap
diff --git a/src/osgEarthExtensions/normalmap/NormalMapShaders b/src/osgEarthExtensions/normalmap/NormalMapShaders
new file mode 100644
index 0000000..95d1616
--- /dev/null
+++ b/src/osgEarthExtensions/normalmap/NormalMapShaders
@@ -0,0 +1,33 @@
+// ***DO NOT EDIT THIS FILE - IT IS AUTOMATICALLY GENERATED BY CMAKE***
+/* -*-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 <osgEarth/ShaderLoader>
+
+namespace osgEarth { namespace NormalMap
+{
+    struct Shaders : public osgEarth::ShaderPackage
+	{
+        Shaders();
+        std::string Vertex, Fragment;
+	};
+	
+} } // namespace osgEarth/NormalMap
diff --git a/src/osgEarthExtensions/normalmap/NormalMapShaders.cpp.in b/src/osgEarthExtensions/normalmap/NormalMapShaders.cpp.in
new file mode 100644
index 0000000..e7549b2
--- /dev/null
+++ b/src/osgEarthExtensions/normalmap/NormalMapShaders.cpp.in
@@ -0,0 +1,14 @@
+// ***DO NOT EDIT THIS FILE - IT IS AUTOMATICALLY GENERATED BY CMAKE***
+
+#include <osgEarthExtensions/normalmap/NormalMapShaders>
+
+using namespace osgEarth::NormalMap;
+
+Shaders::Shaders()
+{
+    Vertex = "NormalMap.vert.glsl";
+    _sources[Vertex] = OE_MULTILINE(@NormalMap.vert.glsl@);
+
+    Fragment = "NormalMap.frag.glsl";
+    _sources[Fragment] = OE_MULTILINE(@NormalMap.frag.glsl@);
+}
diff --git a/src/osgEarthExtensions/normalmap/NormalMapTerrainEffect b/src/osgEarthExtensions/normalmap/NormalMapTerrainEffect
new file mode 100644
index 0000000..34f6770
--- /dev/null
+++ b/src/osgEarthExtensions/normalmap/NormalMapTerrainEffect
@@ -0,0 +1,64 @@
+/* -*-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_NORMALMAP_TERRAIN_EFFECT_H
+#define OSGEARTH_NORMALMAP_TERRAIN_EFFECT_H
+
+#include <osgEarth/TerrainEffect>
+#include <osgEarth/ImageLayer>
+#include <osg/Image>
+#include <osg/Uniform>
+#include <osg/Texture2D>
+
+using namespace osgEarth;
+
+namespace osgEarth { namespace NormalMap
+{
+    /**
+     * Effect that applies normal mapping to the terrain.
+     */
+    class NormalMapTerrainEffect : public TerrainEffect
+    {
+    public:
+        /** construct a new terrain effect. */
+        NormalMapTerrainEffect(
+            const osgDB::Options* dbOptions);
+
+
+    public: // TerrainEffect interface
+
+        void onInstall(TerrainEngineNode* engine);
+
+        void onUninstall(TerrainEngineNode* engine);
+
+
+    protected:
+        virtual ~NormalMapTerrainEffect() { }
+
+        bool _ok;
+        int  _normalMapUnit;
+        osg::ref_ptr<osg::Texture2D> _normalMapTex;
+        osg::ref_ptr<osg::Uniform>   _normalMapTexUniform;
+    };
+
+} } // namespace osgEarth::NormalMap
+
+#endif // OSGEARTH_NORMALMAP_TERRAIN_EFFECT_H
diff --git a/src/osgEarthExtensions/normalmap/NormalMapTerrainEffect.cpp b/src/osgEarthExtensions/normalmap/NormalMapTerrainEffect.cpp
new file mode 100644
index 0000000..4d444b7
--- /dev/null
+++ b/src/osgEarthExtensions/normalmap/NormalMapTerrainEffect.cpp
@@ -0,0 +1,136 @@
+/* -*-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 "NormalMapTerrainEffect"
+
+#include <osgEarth/Registry>
+#include <osgEarth/Capabilities>
+#include <osgEarth/VirtualProgram>
+#include <osgEarth/TerrainEngineNode>
+#include <osgEarth/TerrainTileNode>
+#include <osgEarth/ShaderLoader>
+
+#include "NormalMapShaders"
+
+#define LC "[NormalMap] "
+
+#define NORMAL_SAMPLER "oe_nmap_normalTex"
+#define NORMAL_MATRIX  "oe_nmap_normalTexMatrix"
+
+using namespace osgEarth;
+using namespace osgEarth::NormalMap;
+
+namespace
+{
+    class NormalTexInstaller : public TerrainTileNodeCallback
+    {
+    public:
+        NormalTexInstaller(NormalMapTerrainEffect* effect, int unit)
+            : _effect(effect), _unit(unit) { }
+        
+    public: // TileNodeCallback
+        void operator()(const TileKey& key, osg::Node* node)
+        {
+            TerrainTileNode* tile = osgEarth::findTopMostNodeOfType<TerrainTileNode>(node);
+            if ( !tile )
+                return;
+            
+            osg::StateSet* ss = node->getOrCreateStateSet();
+            osg::Texture* tex = tile->getNormalTexture();
+            if ( tex )
+            {
+                ss->setTextureAttribute(_unit, tex);
+            }
+
+            osg::RefMatrixf* mat = tile->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(NORMAL_MATRIX, fmat) );
+        }
+
+    private:
+        osg::observer_ptr<NormalMapTerrainEffect> _effect;
+        int _unit;
+    };
+}
+
+
+NormalMapTerrainEffect::NormalMapTerrainEffect(const osgDB::Options* dbOptions) :
+_normalMapUnit( -1 )
+{
+    //nop
+}
+
+void
+NormalMapTerrainEffect::onInstall(TerrainEngineNode* engine)
+{
+    if ( engine )
+    {
+        engine->requireNormalTextures();
+
+        engine->getResources()->reserveTextureImageUnit(_normalMapUnit, "NormalMap");
+        engine->addTileNodeCallback( new NormalTexInstaller(this, _normalMapUnit) );
+        
+        // shader components
+        osg::StateSet* stateset = engine->getTerrainStateSet();
+        VirtualProgram* vp = VirtualProgram::getOrCreate(stateset);
+
+        // configure shaders
+        Shaders package;
+        package.load( vp, package.Vertex );
+        package.load( vp, package.Fragment );
+        
+        stateset->addUniform( new osg::Uniform(NORMAL_SAMPLER, _normalMapUnit) );
+    }
+}
+
+
+void
+NormalMapTerrainEffect::onUninstall(TerrainEngineNode* engine)
+{
+    osg::StateSet* stateset = engine->getStateSet();
+    if ( stateset )
+    {
+        VirtualProgram* vp = VirtualProgram::get(stateset);
+        if ( vp )
+        {
+            Shaders package;
+            package.unload( vp, package.Vertex );
+            package.unload( vp, package.Fragment );
+        }
+        stateset->removeUniform( NORMAL_SAMPLER );
+    }
+    
+    if ( _normalMapUnit >= 0 )
+    {
+        engine->getResources()->releaseTextureImageUnit( _normalMapUnit );
+        _normalMapUnit = -1;
+    }
+}
diff --git a/src/osgEarthExtensions/splat/Biome b/src/osgEarthExtensions/splat/Biome
new file mode 100644
index 0000000..684ad7a
--- /dev/null
+++ b/src/osgEarthExtensions/splat/Biome
@@ -0,0 +1,83 @@
+/* -*-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.
+ *
+ * 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_BIOME_H
+#define OSGEARTH_SPLAT_BIOME_H 1
+
+#include "SplatExport"
+#include "SplatCatalog"
+#include <osgEarth/GeoData>
+#include <vector>
+
+namespace osgEarth { namespace Splat
+{
+    /**
+     * Defines a set of geospatial extents under which to apply a particular
+     * procedural catalog.
+     */
+    class Biome
+    {
+    public: // types        
+        struct Region
+        {
+            GeoExtent extent;
+            double    zmin, zmin2;
+            double    zmax, zmax2;
+            double    meanRadius2;
+            osg::Polytope tope;
+        };
+
+    public:
+        /** Construct an empty biome. */
+        Biome();
+
+        /** Deserialize a biome. */
+        Biome(const Config& conf);
+
+        /** Get the list of bounding boxes enclosing this biome. */
+        std::vector<Region>& getRegions() { return _regions; }
+        const std::vector<Region>& getRegions() const { return _regions; }
+
+        /** Name of this biome. */
+        optional<std::string>& name() { return _name; }
+        const optional<std::string>& name() const { return _name; }
+
+        /** URI of the catalog mapped to this biome */
+        optional<URI>& catalogURI() { return _catalogURI; }
+        const optional<URI>& catalogURI() const { return _catalogURI; }
+
+        /** The catalog to apply for this biome. */
+        void setCatalog(SplatCatalog* catalog) { _catalog = catalog; }
+        SplatCatalog* getCatalog() const { return _catalog.get(); }
+
+        /** Serialize this biome's primitive members. */
+        Config getConfig() const;
+
+    protected:
+
+        optional<std::string>      _name;
+        optional<URI>              _catalogURI;
+        std::vector<Region>        _regions;
+        osg::ref_ptr<SplatCatalog> _catalog;
+    };
+
+    typedef std::vector<Biome> BiomeVector;
+
+} } // namespace osgEarth::Splat
+
+#endif // OSGEARTH_SPLAT_BIOME_H
diff --git a/src/osgEarthExtensions/splat/Biome.cpp b/src/osgEarthExtensions/splat/Biome.cpp
new file mode 100644
index 0000000..e977ff4
--- /dev/null
+++ b/src/osgEarthExtensions/splat/Biome.cpp
@@ -0,0 +1,73 @@
+/* -*-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.
+ *
+ * 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 "Biome"
+
+using namespace osgEarth;
+using namespace osgEarth::Splat;
+
+#define LC "[Biome] "
+
+#define SPLAT_BIOME_CURRENT_VERSION 1
+
+
+//............................................................................
+
+Biome::Biome()
+{
+    //nop
+}
+
+Biome::Biome(const Config& conf)
+{
+    conf.getIfSet( "name", _name );
+    conf.getIfSet( "catalog", _catalogURI );
+    
+    // only supports lat long for now
+    const SpatialReference* srs = SpatialReference::create("wgs84");
+    
+    const Config& extentsConf = conf.child("regions");
+    for(ConfigSet::const_iterator i = extentsConf.children().begin();
+        i != extentsConf.children().end();
+        ++i)
+    {
+        double xmin = i->value("xmin", -DBL_MAX);
+        double xmax = i->value("xmax",  DBL_MAX);
+        double ymin = i->value("ymin", -DBL_MAX);
+        double ymax = i->value("ymax",  DBL_MAX);
+        double zmin = i->value("zmin", -DBL_MAX);
+        double zmax = i->value("zmax",  DBL_MAX);
+        
+        _regions.push_back( Region() );
+        _regions.back().extent = GeoExtent(srs, xmin, ymin, xmax, ymax);
+        _regions.back().zmin  = zmin;
+        _regions.back().zmax  = zmax;
+    }
+}
+
+Config
+Biome::getConfig() const
+{
+    Config conf("biome");
+    conf.addIfSet( "name",    _name );
+    conf.addIfSet( "catalog", _catalogURI );
+    // TODO: add regions
+    OE_WARN << LC << "INTERNAL: Biome::getConfig() not fully implemented.\n";
+    return conf;
+}
+
diff --git a/src/osgEarthExtensions/splat/BiomeSelector b/src/osgEarthExtensions/splat/BiomeSelector
new file mode 100644
index 0000000..c4c4838
--- /dev/null
+++ b/src/osgEarthExtensions/splat/BiomeSelector
@@ -0,0 +1,53 @@
+/* -*-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.
+ *
+ * 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_BIOME_SELECTOR_CALLBACK
+#define OSGEARTH_SPLAT_BIOME_SELECTOR_CALLBACK 1
+
+#include "Biome"
+#include <osg/NodeCallback>
+#include <osg/Polytope>
+
+namespace osgEarth { namespace Splat
+{
+    /**
+     * Cull callback that applies the approprate stateset based on what
+     * it determines to be the active Biome.
+     */
+    class BiomeSelector : public osg::NodeCallback
+    {
+    public:
+        BiomeSelector(
+            const BiomeVector&           biomes,
+            const SplatTextureDefVector& textureDefs,
+            osg::StateSet*               basicStateSet,
+            int                          textureImageUnit);
+
+    public: // osg::NodeCallback
+
+        void operator()(osg::Node* node, osg::NodeVisitor* nv);
+
+    protected:
+        BiomeVector                                _biomes;
+        std::vector< osg::ref_ptr<osg::StateSet> > _stateSets;
+        std::vector< osg::Polytope >               _topes;
+    };
+
+} } // namespace osgEarth::Splat
+
+#endif // OSGEARTH_SPLAT_BIOME_SELECTOR_CALLBACK
diff --git a/src/osgEarthExtensions/splat/BiomeSelector.cpp b/src/osgEarthExtensions/splat/BiomeSelector.cpp
new file mode 100644
index 0000000..56e5194
--- /dev/null
+++ b/src/osgEarthExtensions/splat/BiomeSelector.cpp
@@ -0,0 +1,143 @@
+/* -*-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.
+ *
+ * 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 "BiomeSelector"
+
+#include <osgEarth/CullingUtils>
+#include <osgEarth/VirtualProgram>
+#include <osgEarth/GeoData>
+
+using namespace osgEarth;
+using namespace osgEarth::Splat;
+
+#define LC "[BiomeSelector] "
+
+BiomeSelector::BiomeSelector(const BiomeVector&           biomes,
+                             const SplatTextureDefVector& textureDefs,
+                             osg::StateSet*               stateSet,
+                             int                          textureImageUnit) :
+_biomes( biomes )
+{
+    for(unsigned i=0; i<_biomes.size(); ++i)
+    {
+        Biome& biome = _biomes[i];
+
+        // pre-calculate optimized values for each biome region.
+        std::vector<Biome::Region>& regions = biome.getRegions();
+        for(unsigned r=0; r<regions.size(); ++r)
+        {
+            Biome::Region& region = regions[r];
+            region.extent.createPolytope( region.tope );
+            region.zmin2 = region.zmin > -DBL_MAX ? region.zmin*region.zmin : region.zmin;
+            region.zmax2 = region.zmax < DBL_MAX ? region.zmax*region.zmax : region.zmax;
+
+            // this only needs to be very approximate.
+            region.meanRadius2 = region.extent.getSRS()->isGeographic() ?
+                region.extent.getSRS()->getEllipsoid()->getRadiusEquator() : 0.0;
+            region.meanRadius2 *= region.meanRadius2;
+        }
+
+        // next, set up a stateset with the approprate texture and program
+        // for this biome.
+        const SplatTextureDef& textureDef = textureDefs[i];
+
+        // shallow-copy the stateset in prepration for customization:
+        osg::StateSet* biomeSS = 
+            i == 0 ? stateSet :
+            osg::clone(stateSet, osg::CopyOp::SHALLOW_COPY);
+
+        // install his biome's texture set:
+        biomeSS->setTextureAttribute(textureImageUnit, textureDef._texture.get());
+
+        // install this biome's sampling function. Use cloneOrCreate since each
+        // stateset needs a different shader set in its VP.
+        VirtualProgram* vp = VirtualProgram::cloneOrCreate( biomeSS );
+        osg::Shader* shader = new osg::Shader(osg::Shader::FRAGMENT, textureDef._samplingFunction);
+        vp->setShader( "oe_splat_getRenderInfo", shader );
+
+        // put it on the list for fast access in the cull traversal.
+        _stateSets.push_back( biomeSS );
+    }
+}
+
+void
+BiomeSelector::operator()(osg::Node* node, osg::NodeVisitor* nv)
+{
+    osgUtil::CullVisitor* cv = Culling::asCullVisitor(nv);
+
+    osg::Vec3d vp = cv->getViewPoint();
+    double z2 = vp.length2();
+    
+    osg::StateSet* stateSet = 0L;
+
+    for(unsigned b=0; b<_biomes.size() && stateSet == 0L; ++b)
+    {
+        const Biome& biome = _biomes[b];        
+        bool match = false;
+            
+        if ( biome.getRegions().size() == 0 )
+        {
+            // empty biome is a match.
+            //OE_INFO << "matched " << b << "b/c biome has no regions\n";
+            match = true;
+        }
+        else
+        {
+            // check each region of the biome:
+            for(unsigned r=0; r<biome.getRegions().size() && !match; ++r)
+            {
+                const Biome::Region& region = biome.getRegions()[r];
+
+                // empty extent/tope is a match:
+                if ( region.tope.empty() )
+                {
+                    //OE_INFO << "matched " << b << "b/c tope is empty\n";
+                    match = true;
+                }
+
+                // otherwise, check for intersection:
+                else if ( region.tope.contains(vp) )
+                {
+                    double hat2 = z2 - region.meanRadius2;
+                    if ( hat2 >= region.zmin2 && hat2 <= region.zmax2 )
+                    {
+                        //OE_INFO << "matched " << b << "b/c eyepoint intersects\n";
+                        match = true;
+                    }
+                }
+            }
+        }
+
+        if ( match )
+        {
+            stateSet = _stateSets[b].get();
+        }
+    }
+
+    if ( stateSet )
+    {
+        cv->pushStateSet( stateSet );
+    }
+
+    traverse(node, nv);
+
+    if ( stateSet )
+    {
+        cv->popStateSet();
+    }
+}
diff --git a/src/osgEarthExtensions/splat/CMakeLists.txt b/src/osgEarthExtensions/splat/CMakeLists.txt
new file mode 100644
index 0000000..bc77317
--- /dev/null
+++ b/src/osgEarthExtensions/splat/CMakeLists.txt
@@ -0,0 +1,60 @@
+#
+# texture splatting plugin
+#
+
+set(TARGET_GLSL
+    Splat.types.glsl
+    Splat.Noise.glsl
+    Splat.vert.model.glsl
+    Splat.vert.view.glsl
+    Splat.frag.glsl
+    Splat.frag.common.glsl
+    Splat.frag.getRenderInfo.glsl
+    Splat.util.glsl )
+
+set(SHADERS_CPP "${CMAKE_CURRENT_BINARY_DIR}/AutoGenShaders.cpp")
+
+configure_shaders(
+    SplatShaders.cpp.in
+    ${SHADERS_CPP}
+    ${TARGET_GLSL} )
+
+set(TARGET_IN    
+    SplatShaders.cpp.in)
+
+set(TARGET_SRC
+    Biome.cpp
+	BiomeSelector.cpp
+    LandUseTileSource.cpp
+	SplatPlugin.cpp
+	SplatExtension.cpp
+	SplatCatalog.cpp
+	SplatTerrainEffect.cpp
+	SplatCoverageLegend.cpp
+    ${SHADERS_CPP})
+	
+set(LIB_PUBLIC_HEADERS
+    Biome
+	BiomeSelector
+    LandUseTileSource
+	SplatCoverageLegend
+	SplatCatalog
+	SplatExtension
+	SplatOptions
+	SplatTerrainEffect)
+	
+SET(TARGET_H
+	${LIB_PUBLIC_HEADERS}
+	SplatExport
+	SplatShaders)
+
+SET(TARGET_COMMON_LIBRARIES ${TARGET_COMMON_LIBRARIES}
+    osgEarthUtil)
+	
+setup_extension(osgearth_splat)
+
+# to install public driver includes:
+set(LIB_NAME splat)
+
+include(ModuleInstallOsgEarthExtensionIncludes OPTIONAL)
+
diff --git a/src/osgEarthExtensions/splat/LandUseTileSource b/src/osgEarthExtensions/splat/LandUseTileSource
new file mode 100644
index 0000000..972e43c
--- /dev/null
+++ b/src/osgEarthExtensions/splat/LandUseTileSource
@@ -0,0 +1,180 @@
+/* -*-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.
+ *
+ * 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_LAND_USE_TILE_SOURCE
+#define OSGEARTH_SPLAT_LAND_USE_TILE_SOURCE 1
+
+#include <osgEarth/TileSource>
+#include <osgEarth/ImageLayer>
+#include <osgEarthUtil/SimplexNoise>
+#include <osgDB/FileNameUtils>
+
+namespace osgEarth { namespace Splat
+{
+    /**
+     * Configuration options for the land use tile source
+     */
+    class LandUseOptions : public osgEarth::TileSourceOptions    
+    {
+    public:
+        LandUseOptions(const osgEarth::TileSourceOptions& options)
+            : osgEarth::TileSourceOptions(options)
+        {
+            setDriver("landuse");
+            baseLOD().init( 32u );
+            warpFactor().init( 0.01f );
+            fromConfig( _conf );
+        }
+
+    public:
+        
+        /**
+         * Image layer from which to read source coverage data.
+         */
+        optional<ImageLayerOptions>& imageLayerOptions() { return _imageLayerOptions; }
+        const optional<ImageLayerOptions>& imageLayerOptions() const { return _imageLayerOptions; }
+
+        std::vector<ImageLayerOptions>& imageLayerOptionsVector() { return _imageLayerOptionsVec; }
+        const std::vector<ImageLayerOptions>& imageLayerOptionsVector() const { return _imageLayerOptionsVec; }
+
+        /**
+         * 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>& baseLOD() { return _baseLOD; }
+        const optional<unsigned>& baseLOD() const { return _baseLOD; }
+
+        /**
+         * Bit size of the encoded data. The default is 32 (for a 32-bit signed
+         * floating point value) but you can set it to 16 if you know your data
+         * values are all within the range of a signed 16-bit float.
+         */
+        optional<unsigned>& bits() { return _bits; }
+        const optional<unsigned>& bits() const { return _bits; }
+
+    public:
+        Config getConfig() const
+        {
+            Config conf;
+            conf.addIfSet("warp",      _warp);
+            conf.addIfSet("base_lod",  _baseLOD);
+            conf.addIfSet("bits",      _bits);
+            conf.addObjIfSet( "image", _imageLayerOptions );
+            
+            for(std::vector<ImageLayerOptions>::const_iterator i = _imageLayerOptionsVec.begin();
+                i != _imageLayerOptionsVec.end(); 
+                ++i)
+            {
+                conf.add( "image", i->getConfig() );
+            }
+            return conf;
+        }
+
+    protected:
+        void mergeConfig( const Config& conf ) {
+            TileSourceOptions::mergeConfig( conf );
+            fromConfig( conf );
+        }
+
+    private:
+        void fromConfig( const Config& conf )
+        {
+            conf.getIfSet("warp", _warp);
+            conf.getIfSet("base_lod", _baseLOD);
+            conf.getIfSet("bits",      _bits);
+            conf.getObjIfSet( "image", _imageLayerOptions );
+            
+            ConfigSet layerConfs = conf.children("image");
+            for(ConfigSet::const_iterator i = layerConfs.begin(); i != layerConfs.end(); ++i)
+            {
+                _imageLayerOptionsVec.push_back( ImageLayerOptions(*i) );
+            }
+        }
+        
+    private:
+        optional<float>             _warp;
+        optional<unsigned>          _baseLOD;
+        optional<unsigned>          _bits;
+        optional<ImageLayerOptions> _imageLayerOptions;
+        std::vector<ImageLayerOptions> _imageLayerOptionsVec;
+    };
+
+    /**
+     * Tile source that will read from ANOTHER tile source and perform
+     * various pre-processing syntheses operations like warping and detailing.
+     */
+    class LandUseTileSource : public osgEarth::TileSource
+    {
+    public:
+        LandUseTileSource(const LandUseOptions& options);
+
+    public: // TileSource
+
+        // Initialize the tile source.
+        Status initialize(const osgDB::Options* options);
+
+        // Create an image.
+        osg::Image* createImage(const osgEarth::TileKey& key, osgEarth::ProgressCallback* progress);
+
+    protected:
+        virtual ~LandUseTileSource() { }
+
+        osg::ref_ptr<osgDB::Options> _dbOptions;
+        LandUseOptions               _options;        
+        osg::ref_ptr<ImageLayer>     _imageLayer;
+        ImageLayerVector             _imageLayers;
+        std::vector<float>           _warps;
+        osgEarth::Util::SimplexNoise _noiseGen;
+    };
+
+    /**
+     * Driver plugin used to load a land use tile source.
+     */
+    class LandUseDriver : public osgEarth::TileSourceDriver
+    {
+    public:
+        LandUseDriver()
+        {
+            supportsExtension( "osgearth_landuse", "Land Use Driver" );
+        }
+
+        virtual const char* className()
+        {
+            return "Land Use 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 LandUseTileSource( getTileSourceOptions(options) );
+        }
+    };
+
+    REGISTER_OSGPLUGIN(osgearth_landuse, LandUseDriver);
+
+} } // namespace osgEarth::Splat
+
+#endif // OSGEARTH_SPLAT_LAND_USE_TILE_SOURCE
diff --git a/src/osgEarthExtensions/splat/LandUseTileSource.cpp b/src/osgEarthExtensions/splat/LandUseTileSource.cpp
new file mode 100644
index 0000000..94ad4f6
--- /dev/null
+++ b/src/osgEarthExtensions/splat/LandUseTileSource.cpp
@@ -0,0 +1,283 @@
+/* -*-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.
+ *
+ * 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 "LandUseTileSource"
+#include <osgEarth/ImageLayer>
+#include <osgEarth/MapFrame>
+#include <osgEarth/Registry>
+#include <osgEarth/ImageUtils>
+#include <osgEarthUtil/SimplexNoise>
+
+using namespace osgEarth;
+using namespace osgEarth::Splat;
+
+#define LC "[LandUseTileSource] "
+
+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(
+            osg::clampBetween( covIn.x() + n1*warp, 0.0f, 1.0f ),
+            osg::clampBetween( covIn.y() + n1*warp, 0.0f, 1.0f ) );
+    }
+
+    float getNoise(osgEarth::Util::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);
+        //out = n;
+        return n;
+    }
+}
+
+
+LandUseTileSource::LandUseTileSource(const LandUseOptions& options) :
+TileSource( options ),
+_options  ( options )
+{
+    //nop
+}
+
+TileSource::Status
+LandUseTileSource::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() )
+    {
+        ImageLayerOptions ilo = _options.imageLayerOptions().get();
+        ilo.cachePolicy() = CachePolicy::NO_CACHE;
+        _imageLayer = new ImageLayer( ilo );
+        _imageLayer->setTargetProfileHint( profile );
+    }
+
+    // load all the image layers:
+    _imageLayers.assign( _options.imageLayerOptionsVector().size(), 0L );
+    _warps.assign( _options.imageLayerOptionsVector().size(), 0.0f );
+
+    for(unsigned i=0; i<_options.imageLayerOptionsVector().size(); ++i)
+    {
+        ImageLayerOptions ilo = _options.imageLayerOptionsVector()[i];
+        ilo.cachePolicy() = CachePolicy::NO_CACHE;
+        ImageLayer* layer = new ImageLayer( ilo );
+        layer->setTargetProfileHint( profile );
+        _imageLayers[i] = layer;
+
+        Config conf = ilo.getConfig();
+        _warps[i] = conf.value("warp", _options.warpFactor().get());
+    }
+
+    // set up the IO options so that we do not cache input data.
+    CachePolicy::NO_CACHE.apply( _dbOptions.get() );
+
+    // set up the noise generator.
+    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 };
+    
+    // Configure the noise function:
+    _noiseGen.setNormalize  ( true );
+    _noiseGen.setRange      ( 0.0, 1.0 );
+    _noiseGen.setFrequency  ( F[0] );
+    _noiseGen.setPersistence( P[0] );
+    _noiseGen.setLacunarity ( L[0] );
+    _noiseGen.setOctaves    ( 8 );
+
+    return STATUS_OK;
+}
+
+namespace
+{
+    struct ILayer 
+    {
+        GeoImage  image;
+        float     scale;
+        osg::Vec2 bias;
+        bool      valid;
+        float     warp;
+        ImageUtils::PixelReader* read;
+
+        ILayer() : valid(true), read(0L) { }
+
+        ~ILayer() { if (read) delete read; }
+
+        void load(const TileKey& key, ImageLayer* sourceLayer, float sourceWarp, ProgressCallback* progress)
+        {
+            if ( sourceLayer->getEnabled() && sourceLayer->getVisible() && sourceLayer->isKeyInRange(key) )
+            {
+                for(TileKey k = key; k.valid() && !image.valid(); k = k.createParentKey())
+                {
+                    image = sourceLayer->createImage(k, progress);
+                } 
+            }
+
+            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());
+
+                warp = sourceWarp;
+            }
+        }
+    };
+}
+
+osg::Image*
+LandUseTileSource::createImage(const TileKey&    key,
+                               ProgressCallback* progress)
+{
+    if ( _imageLayers.empty() )
+        return 0L;
+
+    std::vector<ILayer> layers(_imageLayers.size());
+
+    // Allocate the new coverage image; it will contain unnormalized values.
+    osg::Image* out = new osg::Image();
+    ImageUtils::markAsUnNormalized(out, true);
+
+    // Allocate a suitable format:
+    GLenum dataType;
+    GLint  internalFormat;
+    
+    if ( _options.bits().isSetTo(16u) )
+    {
+        // 16-bit float:
+        dataType       = GL_FLOAT;
+        internalFormat = GL_LUMINANCE16F_ARB;
+    }
+    else //if ( _options.bits().isSetTo(32u) )
+    {
+        // 32-bit float:
+        dataType       = GL_FLOAT;
+        internalFormat = GL_LUMINANCE32F_ARB;
+    }
+    
+    int tilesize = getPixelsPerTile();
+
+    out->allocateImage(tilesize, tilesize, 1, GL_LUMINANCE, dataType);
+    out->setInternalTextureFormat(internalFormat);
+
+    float noiseLOD = _options.baseLOD().get();
+    float warp     = _options.warpFactor().get();
+
+    osg::Vec2 cov;    // coverage coordinates
+    float     noise;  // noise value
+    osg::Vec2 noiseCoords;
+
+    ImageUtils::PixelWriter write( out );
+
+    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);
+
+    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)
+            {
+                ILayer& layer = layers[L];
+                if ( !layer.valid )
+                    continue;
+
+                if ( !layer.image.valid() )
+                    layer.load(key, _imageLayers[L], _warps[L], 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 )
+                {
+                    // Noise is like a repeating overlay at the noiseLOD. So sample it using
+                    // straight U/V tile coordinates.
+                    noiseCoords = getSplatCoords( key, noiseLOD, osg::Vec2(u,v) );
+                    noise = getNoise( _noiseGen, noiseCoords );
+
+                    cov = warpCoverageCoords(cov, noise, layer.warp);
+
+                    osg::Vec4 texel = (*layer.read)(cov.x(), cov.y());
+                    if ( texel.r() != NO_DATA_VALUE )
+                    {
+                        write.f(texel, u, v);
+                        wrotePixel = true;
+                    }
+                }
+            }
+
+            if ( !wrotePixel )
+            {
+                write.f(nodata, u, v);
+            }
+        }
+    }
+
+    return out;
+}
diff --git a/src/osgEarthExtensions/splat/ModelSplatter b/src/osgEarthExtensions/splat/ModelSplatter
new file mode 100644
index 0000000..8874e85
--- /dev/null
+++ b/src/osgEarthExtensions/splat/ModelSplatter
@@ -0,0 +1,70 @@
+/* -*-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_SPLAT_MODEL_SPLATTER
+#define OSGEARTH_SPLAT_MODEL_SPLATTER 1
+
+#include "SplatExport"
+#include "SplatOptions"
+#include <osgEarth/TileKey>
+#include <osgEarth/TerrainEngineNode>
+
+namespace osgEarth { namespace Splat
+{
+    using namespace osgEarth;
+
+    /**
+     * Extension for loading the splatting effect on demand.
+     */
+    class ModelSplatter : public TerrainEngine::NodeCallback
+    {
+    public:
+        ModelSplatter();
+
+        /**
+         * Sets the model to splat
+         */
+        void setModel(osg::Node* node);
+
+        void setNumInstances(unsigned num);
+
+        void setMinLOD(unsigned lod);
+
+
+    public: // TileNodeCallback
+
+        // Attaches the model splatter to a new tile node
+        void operator()(const TileKey& key, osg::Node* tileNode);
+
+    protected:
+        virtual ~ModelSplatter();
+
+    private:
+        osg::ref_ptr<osg::Node> _model;
+        Threading::Mutex        _modelMutex;
+        unsigned                _count;
+        unsigned                _minLOD;
+        bool                    _dirty;
+
+        void establish();
+        osg::Node* makeChild(int delta);
+    };
+
+} } // namespace osgEarth::Splat
+
+#endif // OSGEARTH_SPLAT_MODEL_SPLATTER
diff --git a/src/osgEarthExtensions/splat/ModelSplatter.cpp b/src/osgEarthExtensions/splat/ModelSplatter.cpp
new file mode 100644
index 0000000..64201cf
--- /dev/null
+++ b/src/osgEarthExtensions/splat/ModelSplatter.cpp
@@ -0,0 +1,206 @@
+/* -*-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 "ModelSplatter"
+
+#include <osgEarth/Registry>
+#include <osgEarth/ShaderFactory>
+#include <osgEarth/ShaderGenerator>
+#include <osgEarth/VirtualProgram>
+#include <osgEarth/DrawInstanced>
+#include <osgEarth/TerrainTileNode>
+#include <osgEarth/NodeUtils>
+
+#include <osgUtil/Optimizer>
+
+using namespace osgEarth;
+using namespace osgEarth::Splat;
+
+#define LC "[ModelSplatter] "
+
+namespace
+{
+    const char* vs_model = 
+        "#version 120 \n" 
+        "#extension GL_EXT_gpu_shader4 : enable \n" 
+        "#extension GL_ARB_draw_instanced: enable \n" 
+
+        "uniform sampler2D oe_tile_elevationTex; \n"
+        "uniform mat4 oe_tile_elevationTexMatrix; \n"
+        "uniform vec2 oe_trees_span; \n"
+
+        "uniform vec4 oe_tile_key; \n"
+
+        "varying float oe_modelsplat_dist; \n"
+        
+        "float oe_modelsplat_rand(vec2 co) \n"
+        "{\n"
+        "   return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453);\n"
+        "}\n"
+
+        "void oe_modelsplat_vert_model(inout vec4 VertexMODEL) \n" 
+        "{ \n"
+        "    vec2 span = oe_trees_span; \n"
+        "    float fInstanceID = float(gl_InstanceID); \n"
+        "    float rx = oe_modelsplat_rand( vec2(fInstanceID, oe_tile_key.z)); \n"
+        "    float ry = oe_modelsplat_rand( vec2(rx, -fInstanceID)); \n"
+        "    vec2 rxy = vec2(rx, ry); \n"
+        "    vec2 offset = -0.5*span + span*rxy; \n"
+        "    VertexMODEL.xy += offset; \n"
+
+        // matrix mult probably unnecessary 
+        "    vec4 rc = oe_tile_elevationTexMatrix * vec4(rx, ry, 0.0, 1.0); \n"
+     
+        // scale and bias the tex coords for heightfield sampling:
+        // "17" is the tile size. Obviously this needs to be parameterized.
+        "    rc *= 16.0/17.0; \n"
+        "    rc += 0.5/17.0; \n"
+
+        "    float h = texture2D(oe_tile_elevationTex, rc.st).r; \n"
+        "    VertexMODEL.z += h; \n"
+        "} \n";
+
+    const char* fs =
+        "#version 110\n"
+        "void oe_modelsplat_frag(inout vec4 color) { \n"
+        "    if (color.a < 0.2) discard; \n"
+        "} \n";
+}
+
+//..........................................................................
+
+
+ModelSplatter::ModelSplatter() :
+_dirty( true ),
+_count( 128 ),
+_minLOD( 14 )
+{
+    //nop
+}
+
+ModelSplatter::~ModelSplatter()
+{
+    //nop
+}
+
+void 
+ModelSplatter::setModel(osg::Node* node)
+{
+    _model = node;
+    _dirty = true;
+}
+
+void
+ModelSplatter::setNumInstances(unsigned num)
+{
+    _count = num;
+    _dirty = true;
+}
+
+void 
+ModelSplatter::setMinLOD(unsigned lod)
+{
+    _minLOD = lod;
+    _dirty = true;
+}
+
+void
+ModelSplatter::establish()
+{
+    if ( _dirty && _model.valid() )
+    {
+        Threading::ScopedMutexLock lock(_modelMutex);
+        if ( _dirty && _model.valid() )
+        {
+            _dirty = false;
+            
+            // have to set unref-after-apply to false:
+            osgUtil::Optimizer::TextureVisitor texvis(
+                true, false,
+                false, false,
+                false, false);
+            _model->accept( texvis );
+
+            // have to enimilate the scale matrix:
+            osgUtil::Optimizer::FlattenStaticTransformsVisitor flatten;
+            _model->accept( flatten );
+
+            osg::BoundingBox bbox;
+            DrawInstanced::ConvertToDrawInstanced cdi( _count, bbox, true );
+            _model->accept( cdi );
+
+            osg::ref_ptr<StateSetCache> cache = new StateSetCache();
+            Registry::shaderGenerator().run(_model, cache.get());
+
+            VirtualProgram* vp = VirtualProgram::getOrCreate( _model->getOrCreateStateSet() );
+
+            vp->setFunction("oe_modelsplat_vert_model", vs_model, ShaderComp::LOCATION_VERTEX_MODEL);
+        }
+    }
+}
+
+void
+ModelSplatter::operator()(const TileKey& key, osg::Node* node)
+{
+#if 0
+    TerrainTileNode* tile = osgEarth::findTopMostNodeOfType<TerrainTileNode>(node);
+    if ( !tile )
+        return;
+
+    if ( key.getLOD() >= _minLOD && _model.valid() )
+    {
+        // make sure the correct model is loaded
+        establish();
+
+        //// elevation texture and matrix are required
+        //osg::Texture* elevationTex = tile->getModel()->elevationModel().get();
+        //if ( !elevationTex )
+        //{
+        //    //OE_WARN << LC << "No elevation texture for key " << key.str() << "\n";
+        //    return;
+        //}
+
+        //osg::RefMatrixf* elevationTexMat = tile->getElevationTextureMatrix();
+        //if ( !elevationTexMat )
+        //{
+        //    //OE_WARN << LC << "No elevation texture matrix for key " << key.str() << "\n";
+        //    return;
+        //}
+        
+        osg::Group* payload = tile->getOrCreatePayloadGroup();
+        payload->addChild( _model.get() );
+        //tile->addChild( _model.get() );
+
+        osg::StateSet* ss = payload->getOrCreateStateSet();
+
+        // first, a rotation vector to make trees point up.
+        GeoPoint p;
+        key.getExtent().getCentroid(p);
+
+        // calculate the scatter area:
+        float h = key.getExtent().height() * 111320.0f;
+        float w = key.getExtent().width() * 111320.0f * cos(fabs(osg::DegreesToRadians(p.y())));
+        ss->addUniform( new osg::Uniform("oe_trees_span", osg::Vec2f(w,h)) );
+        
+        //// hack..
+        //ss->setTextureAttributeAndModes(2, tile->getElevationTexture(), 1);
+        //ss->addUniform(new osg::Uniform("oe_terrain_tex", 2));
+        //ss->addUniform(new osg::Uniform("oe_terrain_tex_matrix", osg::Matrixf(*elevationTexMat)) );        
+    }
+#endif
+}
diff --git a/src/osgEarthExtensions/splat/Splat.Noise.glsl b/src/osgEarthExtensions/splat/Splat.Noise.glsl
new file mode 100644
index 0000000..39c599b
--- /dev/null
+++ b/src/osgEarthExtensions/splat/Splat.Noise.glsl
@@ -0,0 +1,157 @@
+#version 110
+//
+// Description : Array and textureless GLSL 2D/3D/4D simplex 
+//               noise functions.
+//      Author : Ian McEwan, Ashima Arts.
+//  Maintainer : ijm
+//     Lastmod : 20110822 (ijm)
+//     License : Copyright (C) 2011 Ashima Arts. All rights reserved.
+//               Distributed under the MIT License. See LICENSE file.
+//               https://github.com/ashima/webgl-noise
+// 
+
+vec4 oe_noise_mod289(vec4 x) {
+  return x - floor(x * (1.0 / 289.0)) * 289.0; }
+
+float oe_noise_mod289(float x) {
+  return x - floor(x * (1.0 / 289.0)) * 289.0; }
+
+vec4 oe_noise_permute(vec4 x) {
+     return oe_noise_mod289(((x*34.0)+1.0)*x);
+}
+
+float oe_noise_permute(float x) {
+     return oe_noise_mod289(((x*34.0)+1.0)*x);
+}
+
+vec4 oe_noise_taylorInvSqrt(vec4 r)
+{
+  return 1.79284291400159 - 0.85373472095314 * r;
+}
+
+float oe_noise_taylorInvSqrt(float r)
+{
+  return 1.79284291400159 - 0.85373472095314 * r;
+}
+
+vec4 oe_noise_grad4(float j, vec4 ip)
+  {
+  const vec4 ones = vec4(1.0, 1.0, 1.0, -1.0);
+  vec4 p,s;
+
+  p.xyz = floor( fract (vec3(j) * ip.xyz) * 7.0) * ip.z - 1.0;
+  p.w = 1.5 - dot(abs(p.xyz), ones.xyz);
+  s = vec4(lessThan(p, vec4(0.0)));
+  p.xyz = p.xyz + (s.xyz*2.0 - 1.0) * s.www; 
+
+  return p;
+  }
+						
+// (sqrt(5) - 1)/4 = F4, used once below
+#define oe_noise_F4 0.309016994374947451
+
+float oe_noise_snoise(vec4 v)
+  {
+  const vec4  C = vec4( 0.138196601125011,  // (5 - sqrt(5))/20  G4
+                        0.276393202250021,  // 2 * G4
+                        0.414589803375032,  // 3 * G4
+                       -0.447213595499958); // -1 + 4 * G4
+
+// First corner
+  vec4 i  = floor(v + dot(v, vec4(oe_noise_F4)) );
+  vec4 x0 = v -   i + dot(i, C.xxxx);
+
+// Other corners
+
+// Rank sorting originally contributed by Bill Licea-Kane, AMD (formerly ATI)
+  vec4 i0;
+  vec3 isX = step( x0.yzw, x0.xxx );
+  vec3 isYZ = step( x0.zww, x0.yyz );
+//  i0.x = dot( isX, vec3( 1.0 ) );
+  i0.x = isX.x + isX.y + isX.z;
+  i0.yzw = 1.0 - isX;
+//  i0.y += dot( isYZ.xy, vec2( 1.0 ) );
+  i0.y += isYZ.x + isYZ.y;
+  i0.zw += 1.0 - isYZ.xy;
+  i0.z += isYZ.z;
+  i0.w += 1.0 - isYZ.z;
+
+  // i0 now contains the unique values 0,1,2,3 in each channel
+  vec4 i3 = clamp( i0, 0.0, 1.0 );
+  vec4 i2 = clamp( i0-1.0, 0.0, 1.0 );
+  vec4 i1 = clamp( i0-2.0, 0.0, 1.0 );
+
+  //  x0 = x0 - 0.0 + 0.0 * C.xxxx
+  //  x1 = x0 - i1  + 1.0 * C.xxxx
+  //  x2 = x0 - i2  + 2.0 * C.xxxx
+  //  x3 = x0 - i3  + 3.0 * C.xxxx
+  //  x4 = x0 - 1.0 + 4.0 * C.xxxx
+  vec4 x1 = x0 - i1 + C.xxxx;
+  vec4 x2 = x0 - i2 + C.yyyy;
+  vec4 x3 = x0 - i3 + C.zzzz;
+  vec4 x4 = x0 + C.wwww;
+
+// Permutations
+  i = oe_noise_mod289(i); 
+  float j0 = oe_noise_permute( oe_noise_permute( oe_noise_permute( oe_noise_permute(i.w) + i.z) + i.y) + i.x);
+  vec4 j1 = oe_noise_permute( oe_noise_permute( oe_noise_permute( oe_noise_permute (
+             i.w + vec4(i1.w, i2.w, i3.w, 1.0 ))
+           + i.z + vec4(i1.z, i2.z, i3.z, 1.0 ))
+           + i.y + vec4(i1.y, i2.y, i3.y, 1.0 ))
+           + i.x + vec4(i1.x, i2.x, i3.x, 1.0 ));
+
+// Gradients: 7x7x6 points over a cube, mapped onto a 4-cross polytope
+// 7*7*6 = 294, which is close to the ring size 17*17 = 289.
+  vec4 ip = vec4(1.0/294.0, 1.0/49.0, 1.0/7.0, 0.0) ;
+
+  vec4 p0 = oe_noise_grad4(j0,   ip);
+  vec4 p1 = oe_noise_grad4(j1.x, ip);
+  vec4 p2 = oe_noise_grad4(j1.y, ip);
+  vec4 p3 = oe_noise_grad4(j1.z, ip);
+  vec4 p4 = oe_noise_grad4(j1.w, ip);
+
+// Normalise gradients
+  vec4 norm = oe_noise_taylorInvSqrt(vec4(dot(p0,p0), dot(p1,p1), dot(p2, p2), dot(p3,p3)));
+  p0 *= norm.x;
+  p1 *= norm.y;
+  p2 *= norm.z;
+  p3 *= norm.w;
+  p4 *= oe_noise_taylorInvSqrt(dot(p4,p4));
+
+// Mix contributions from the five corners
+  vec3 m0 = max(0.6 - vec3(dot(x0,x0), dot(x1,x1), dot(x2,x2)), 0.0);
+  vec2 m1 = max(0.6 - vec2(dot(x3,x3), dot(x4,x4)            ), 0.0);
+  m0 = m0 * m0;
+  m1 = m1 * m1;
+  return 49.0 * ( dot(m0*m0, vec3( dot( p0, x0 ), dot( p1, x1 ), dot( p2, x2 )))
+               + dot(m1*m1, vec2( dot( p3, x3 ), dot( p4, x4 ) ) ) ) ;
+}
+
+// Generates a tiled fractal simplex noise value and clamps the values to [0..1].
+float oe_noise_fractal4D(in vec2 seed, in float frequency, in float persistence, in float lacunarity, in int octaves)
+{
+    const float TwoPI = 6.283185;
+	float f = frequency;
+	float amp = 1.0;
+	float maxAmp = 0.0;
+	float n = 0.0;
+    
+    vec4 seed4D;
+    seed4D.xy = cos(seed*TwoPI)/TwoPI;
+    seed4D.zw = sin(seed*TwoPI)/TwoPI;
+
+	for(int i=0; i<octaves; ++i)
+	{
+		n += oe_noise_snoise(seed4D*f) * amp;
+		maxAmp += amp;
+		amp *= persistence;
+		f *= lacunarity;
+	}
+	//return n / maxAmp;
+    const float low = 0.0;
+    const float high = 1.0;
+
+    n /= maxAmp;
+    n = n * (high-low)/2.0 + (high+low)/2.0;
+    return clamp(n, 0.0, 1.0);
+}
diff --git a/src/osgEarthExtensions/splat/Splat.frag.common.glsl b/src/osgEarthExtensions/splat/Splat.frag.common.glsl
new file mode 100644
index 0000000..a0e0488
--- /dev/null
+++ b/src/osgEarthExtensions/splat/Splat.frag.common.glsl
@@ -0,0 +1,29 @@
+// begin: Splat.frag.common.glsl
+
+#pragma vp_define "OE_USE_NORMAL_MAP"
+#ifdef OE_USE_NORMAL_MAP
+
+// normal map version:
+uniform sampler2D oe_nmap_normalTex;
+in vec4 oe_nmap_normalCoords;
+
+float oe_splat_getSlope()
+{
+    vec4 encodedNormal = texture2D(oe_nmap_normalTex, oe_nmap_normalCoords.st);
+    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/osgEarthExtensions/splat/Splat.frag.getRenderInfo.glsl b/src/osgEarthExtensions/splat/Splat.frag.getRenderInfo.glsl
new file mode 100644
index 0000000..3ee6f7b
--- /dev/null
+++ b/src/osgEarthExtensions/splat/Splat.frag.getRenderInfo.glsl
@@ -0,0 +1,18 @@
+#version 330
+
+#pragma include "Splat.types.glsl"
+
+// Samples the coverage data and returns main and detail indices.
+oe_SplatRenderInfo oe_splat_getRenderInfo(in float value, in oe_SplatEnv env)
+{
+    float primary = -1.0;   // primary texture index
+    float detail = -1.0;    // detail texture index
+    float brightness = 1.0; // default noise function brightness factor
+    float contrast = 1.0;   // default noise function contrast factor
+    float threshold = 0.0;  // default noise function threshold
+    float slope = 0.0;      // default minimum slope
+
+    $CODE_INJECTION_POINT
+
+    return oe_SplatRenderInfo(primary, detail, brightness, contrast, threshold, slope);
+}
diff --git a/src/osgEarthExtensions/splat/Splat.frag.glsl b/src/osgEarthExtensions/splat/Splat.frag.glsl
new file mode 100644
index 0000000..63e867f
--- /dev/null
+++ b/src/osgEarthExtensions/splat/Splat.frag.glsl
@@ -0,0 +1,275 @@
+#version 330
+
+#pragma vp_entryPoint "oe_splat_complex"
+#pragma vp_location   "fragment_coloring"
+#pragma vp_order      "0.4"                 // before terrain image layers
+
+// 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"
+
+// ref: Splat.getRenderInfo.frag.glsl
+oe_SplatRenderInfo oe_splat_getRenderInfo(in float value, in oe_SplatEnv env);
+
+// from: Splat.util.glsl
+void oe_splat_getLodBlend(in float range, in float baseLOD, out float lod0, out float lod1, out float blend);
+vec2 oe_splat_getSplatCoords(in vec2 coords, in float lod);
+
+// from the terrain engine:
+in vec4 oe_layer_tilec;
+uniform vec4 oe_tile_key;
+
+// from the vertex shader:
+in vec2 oe_splat_covtc;
+in float oe_splat_range;
+
+// from SplatTerrainEffect:
+uniform float oe_splat_warp;
+uniform float oe_splat_blur;
+uniform sampler2D oe_splat_coverageTex;
+uniform sampler2DArray oe_splatTex;
+uniform float oe_splat_scaleOffset;
+
+uniform float oe_splat_detailRange;
+uniform float oe_splat_noiseScale;
+uniform float oe_splat_useBilinear; // 1=true, -1=false
+
+#ifdef SPLAT_EDIT
+uniform float oe_splat_brightness;
+uniform float oe_splat_contrast;
+uniform float oe_splat_threshold;
+uniform float oe_splat_minSlope;
+#endif
+
+// Warps the coverage sampling coordinates to mitigate blockiness.
+vec2 oe_splat_warpCoverageCoords(in vec2 splat_tc, in oe_SplatEnv env)
+{
+    vec2 seed = oe_splat_covtc;
+    float n1 = 2.0*env.noise.y-1.0;
+    vec2 tc = oe_splat_covtc + n1*oe_splat_warp;
+    return clamp(tc, 0.0, 1.0);
+}
+
+vec4 oe_splat_getTexel(in float index, in vec2 tc)
+{
+    return texture(oe_splatTex, vec3(tc, index));
+}
+
+// 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 = ri.detailIndex >= 0.0 ? 1.0 : 0.0;
+
+#ifdef SPLAT_EDIT
+    float brightness = oe_splat_brightness;
+    float contrast = oe_splat_contrast;
+    float threshold = oe_splat_threshold;
+    float minSlope = oe_splat_minSlope;
+#else
+    float brightness = ri.brightness;
+    float contrast = ri.contrast;
+    float threshold = ri.threshold;
+    float minSlope = ri.minSlope;
+#endif
+
+    // start with the noise value
+    float n = env.noise.x;
+	
+    // apply slope limiter, then reclamp and threshold:
+    float s;
+    if ( env.slope >= minSlope )
+        s = 1.0;
+    else if ( env.slope < 0.1*minSlope )
+        s = 0.0;
+    else
+        s = (env.slope-0.1*minSlope)/(minSlope-0.1*minSlope);
+
+    brightness *= s;
+
+    // apply brightness and contrast, then reclamp
+    n = clamp(((n-0.5)*contrast + 0.5) * brightness, 0.0, 1.0);
+    
+    // apply final threshold:
+	n = n < threshold ? 0.0 : n;
+
+    // sample the texel and return it.
+    vec4 result = oe_splat_getTexel( max(ri.detailIndex,0), tc);
+    return vec4(result.rgb, hasDetail*n);
+}
+
+// Generates a texel using nearest-neighbor coverage sampling.
+vec4 oe_splat_nearest(in vec2 splat_tc, in oe_SplatEnv env)
+{
+    vec2 tc = oe_splat_covtc; //oe_splat_warpCoverageCoords(splat_tc, env);
+    float coverageValue = texture2D(oe_splat_coverageTex, tc).r;
+    oe_SplatRenderInfo ri = oe_splat_getRenderInfo(coverageValue, 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 );
+}
+
+// Generates a texel using bilinear filtering on the coverage data.
+vec4 oe_splat_bilinear(in vec2 splat_tc, in oe_SplatEnv env)
+{
+    vec4 texel = vec4(0,0,0,1);
+
+    //TODO: coverage warping is slow due to the noise function. Consider removing/reworking.
+    vec2 tc = oe_splat_covtc; //oe_splat_warpCoverageCoords(splat_tc, env);
+
+    float a = oe_splat_blur;
+    float pixelWidth = a/256.0; // 256 = hard-coded cov tex size //TODO 
+    float halfPixelWidth = 0.5*pixelWidth;
+    float pixelWidth2 = pixelWidth*pixelWidth;
+
+    // Find the four quantized coverage coordinates that form a box around the actual
+    // coverage coordinates, where each quantized coord is at the center of a coverage texel.
+    vec2 rem = mod(tc, pixelWidth);
+    vec2 sw;
+    sw.x = tc.x - rem.x + (rem.x >= halfPixelWidth ? halfPixelWidth : -halfPixelWidth);
+    sw.y = tc.y - rem.y + (rem.y >= halfPixelWidth ? halfPixelWidth : -halfPixelWidth);
+    vec2 ne = sw + pixelWidth;
+    vec2 nw = vec2(sw.x, ne.y);
+    vec2 se = vec2(ne.x, sw.y);
+
+    // Calculate the weighting for each corner.
+    vec2 dsw = tc-sw;
+    vec2 dse = tc-se;
+    vec2 dne = tc-ne;
+    vec2 dnw = tc-nw;
+
+    float sw_weight = max(pixelWidth2-dot(dsw,dsw),0.0);
+    float se_weight = max(pixelWidth2-dot(dse,dse),0.0);
+    float ne_weight = max(pixelWidth2-dot(dne,dne),0.0);
+    float nw_weight = max(pixelWidth2-dot(dnw,dnw),0.0);
+
+    // normalize the weights so they total 1.0
+    float invTotalWeight = 1.0/(sw_weight+se_weight+ne_weight+nw_weight);
+    sw_weight *= invTotalWeight;
+    se_weight *= invTotalWeight;
+    ne_weight *= invTotalWeight;
+    nw_weight *= invTotalWeight;
+
+    // Sample coverage values using quantized corner coords:
+    float value_sw = texture2D(oe_splat_coverageTex, clamp(sw, 0.0, 1.0)).r;
+    float value_se = texture2D(oe_splat_coverageTex, clamp(se, 0.0, 1.0)).r;
+    float value_ne = texture2D(oe_splat_coverageTex, clamp(ne, 0.0, 1.0)).r;
+    float value_nw = texture2D(oe_splat_coverageTex, clamp(nw, 0.0, 1.0)).r;
+
+    // Build the render info data for each corner:
+    oe_SplatRenderInfo ri_sw = oe_splat_getRenderInfo(value_sw, env);
+    oe_SplatRenderInfo ri_se = oe_splat_getRenderInfo(value_se, env);
+    oe_SplatRenderInfo ri_ne = oe_splat_getRenderInfo(value_ne, env);
+    oe_SplatRenderInfo ri_nw = oe_splat_getRenderInfo(value_nw, env);
+
+    // 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;
+
+    // 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;
+    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);   
+
+    // 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);
+
+    return texel;
+}
+
+#ifdef SPLAT_GPU_NOISE
+
+uniform float oe_splat_freq;
+uniform float oe_splat_pers;
+uniform float oe_splat_lac;
+uniform float oe_splat_octaves;
+
+// see: Splat.Noise.glsl
+float oe_noise_fractal4D(in vec2 seed, in float frequency, in float persistence, in float lacunarity, in int octaves);
+
+vec4 oe_splat_getNoise(in vec2 tc)
+{
+    return vec4(oe_noise_fractal4D(tc, oe_splat_freq, oe_splat_pers, oe_splat_lac, int(oe_splat_octaves)));
+}
+
+#else // !SPLAT_GPU_NOISE
+
+uniform sampler2D oe_splat_noiseTex;
+vec4 oe_splat_getNoise(in vec2 tc)
+{
+    return texture(oe_splat_noiseTex, tc.st);
+}
+
+#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);
+    vec2 noiseCoords = oe_splat_getSplatCoords(oe_layer_tilec.st, noiseLOD);
+
+    oe_SplatEnv env;
+    env.range = oe_splat_range;
+    env.slope = oe_splat_getSlope();
+    env.noise = oe_splat_getNoise(noiseCoords);
+    env.elevation = 0.0;
+
+    color = oe_splat_bilinear(oe_layer_tilec.st, env);
+}
+
+// Main entry point for fragment shader.
+void oe_splat_complex(inout vec4 color)
+{
+    // Noise coords.
+    float noiseLOD = floor(oe_splat_noiseScale);
+    vec2 noiseCoords = oe_splat_getSplatCoords(oe_layer_tilec.st, noiseLOD); //TODO: move to VS for slight speedup
+
+    oe_SplatEnv env;
+    env.range = oe_splat_range;
+    env.slope = oe_splat_getSlope();
+    env.noise = oe_splat_getNoise(noiseCoords);
+    env.elevation = 0.0;
+
+    // quantize the scale offset so we take the hit in the FS
+    float scaleOffset = oe_splat_scaleOffset >= 0.0 ? ceil(oe_splat_scaleOffset) : floor(oe_splat_scaleOffset);
+        
+    // Calculate the 2 LODs we need to blend. We have to do this in the FS because 
+    // it's quite possible for a single triangle to span more than 2 LODs.
+    float lod0;
+    float lod1;
+    float lodBlend = -1.0;
+    oe_splat_getLodBlend(oe_splat_range, scaleOffset, lod0, lod1, lodBlend);
+
+    // Sample the two LODs:
+    vec2 tc0 = oe_splat_getSplatCoords(oe_layer_tilec.st, lod0);
+    vec4 texel0 = oe_splat_bilinear(tc0, env);
+    
+    vec2 tc1 = oe_splat_getSplatCoords(oe_layer_tilec.st, lod1);
+    vec4 texel1 = oe_splat_bilinear(tc1, env);
+    
+    // Blend:
+    vec4 texel = mix(texel0, texel1, lodBlend);
+
+    color = mix(color, texel, texel.a);
+
+    // uncomment to visualize slope.
+    //color.rgba = vec4(env.slope,0,0,1);
+}
diff --git a/src/osgEarthExtensions/splat/Splat.types.glsl b/src/osgEarthExtensions/splat/Splat.types.glsl
new file mode 100644
index 0000000..299a978
--- /dev/null
+++ b/src/osgEarthExtensions/splat/Splat.types.glsl
@@ -0,0 +1,21 @@
+// begin: Splat.types.glsl
+
+// Environment structure passed around locally.
+struct oe_SplatEnv {
+    float range;
+    float elevation;
+    float slope;
+    vec4 noise;
+};
+
+// Rendering parameters for splat texture and noise-based detail texture.
+struct oe_SplatRenderInfo {
+    float primaryIndex;
+    float detailIndex;
+    float brightness;
+    float contrast;
+    float threshold;
+    float minSlope;
+};
+
+// end: Splat.types.glsl
\ No newline at end of file
diff --git a/src/osgEarthExtensions/splat/Splat.util.glsl b/src/osgEarthExtensions/splat/Splat.util.glsl
new file mode 100644
index 0000000..4bd0d3a
--- /dev/null
+++ b/src/osgEarthExtensions/splat/Splat.util.glsl
@@ -0,0 +1,64 @@
+#version 120
+#pragma vp_location "fragment_coloring"
+
+uniform vec4 oe_tile_key;  // osgEarth TileKey
+
+
+// Mapping of view ranges to splat texture levels of detail.
+#define RANGE_COUNT 11
+const float oe_SplatRanges[RANGE_COUNT] = float[](  50.0, 125.0, 250.0, 500.0, 1000.0, 4000.0, 30000.0, 150000.0, 300000.0, 1000000.0, 5000000.0 );
+const float oe_SplatLevels[RANGE_COUNT] = float[](  20.0,  19.0,  18.0,  17.0,   16.0,   14.0,    12.0,     10.0,      8.0,       6.0,       4.0 );
+
+/**
+ * Given a camera distance, return the two LODs it falls between and
+ * the blend factor [0..1] between then.
+ * in  range   = camera distace to fragment
+ * in  baseLOD = LOD at which texture scale is 1.0
+ * out LOD0    = near LOD
+ * out LOD1    = far LOD
+ * out blend   = Blend factor between LOD0 and LOD1 [0..1]
+ */
+void
+oe_splat_getLodBlend(in float range, in float baseLOD, out float out_LOD0, out float out_LOD1, out float out_blend)
+{
+    float clampedRange = clamp(range, oe_SplatRanges[0], oe_SplatRanges[RANGE_COUNT-1]);
+
+    out_blend = -1.0;
+    for(int i=0; i<RANGE_COUNT-1 && out_blend < 0; ++i)
+    {
+        if ( clampedRange >= oe_SplatRanges[i] && clampedRange <= oe_SplatRanges[i+1] )
+        {
+            out_LOD0 = oe_SplatLevels[i]   + baseLOD;
+            out_LOD1 = oe_SplatLevels[i+1] + baseLOD;
+            out_blend = clamp((clampedRange-oe_SplatRanges[i])/(oe_SplatRanges[i+1]-oe_SplatRanges[i]), 0.0, 1.0);
+        }
+    }
+}
+
+/**
+ * Scales the incoming tile splat coordinates to match the requested
+ * LOD level. We offset the level from the current tile key's LOD (.z)
+ * because otherwise you run into single-precision jitter at high LODs.
+ */
+vec2 
+oe_splat_getSplatCoords(in vec2 tc, float lod)
+{
+    float dL = oe_tile_key.z - lod;
+    float factor = exp2(dL);
+    float invFactor = 1.0/factor;
+    vec2 scale = vec2(invFactor); 
+    vec2 result = tc * scale;
+
+    // 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;
+}
+
diff --git a/src/osgEarthExtensions/splat/Splat.vert.model.glsl b/src/osgEarthExtensions/splat/Splat.vert.model.glsl
new file mode 100644
index 0000000..065f8ac
--- /dev/null
+++ b/src/osgEarthExtensions/splat/Splat.vert.model.glsl
@@ -0,0 +1,14 @@
+#version 330
+#pragma vp_entryPoint "oe_splat_vertex_model"
+#pragma vp_location   "vertex_model"
+#pragma vp_order      "0.5"
+
+out vec3 vp_Normal;
+out float oe_splat_slope;
+
+void oe_splat_vertex_model(inout vec4 VertexMODEL)
+{
+    // calculate slope from the Z component of the current normal
+    // since the terrain is in LTP space.
+    oe_splat_slope = 1.0-vp_Normal.z;
+}
diff --git a/src/osgEarthExtensions/splat/Splat.vert.view.glsl b/src/osgEarthExtensions/splat/Splat.vert.view.glsl
new file mode 100644
index 0000000..db81a15
--- /dev/null
+++ b/src/osgEarthExtensions/splat/Splat.vert.view.glsl
@@ -0,0 +1,24 @@
+#version 330
+
+#pragma vp_entryPoint "oe_splat_vertex_view"
+#pragma vp_location   "vertex_view"
+#pragma vp_order      "0.5"
+
+#pragma include "Splat.types.glsl"
+
+out vec4 oe_layer_tilec;
+out float oe_splat_range;
+out vec2 oe_splat_covtc;
+
+uniform mat4 $COVERAGE_TEXMAT_UNIFORM;   // assigned at runtime
+
+
+void oe_splat_vertex_view(inout vec4 VertexVIEW)
+{
+    // range from camera to vertex
+    oe_splat_range = -VertexVIEW.z;
+
+    // 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_TEXMAT_UNIFORM * oe_layer_tilec).st;
+}
diff --git a/src/osgEarthExtensions/splat/SplatCatalog b/src/osgEarthExtensions/splat/SplatCatalog
new file mode 100644
index 0000000..0b65677
--- /dev/null
+++ b/src/osgEarthExtensions/splat/SplatCatalog
@@ -0,0 +1,178 @@
+/* -*-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.
+ *
+ * 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_SPLAT_CATALOG
+#define OSGEARTH_SPLAT_SPLAT_CATALOG 1
+
+#include "SplatExport"
+#include <osg/Referenced>
+#include <osg/Texture2DArray>
+#include <osgEarth/Containers>
+#include <osgEarth/URI>
+
+namespace osgDB {
+    class Options;
+}
+
+namespace osgEarth { namespace Splat
+{
+    /**
+     * Defines the parameters for noise-generated detail splatting
+     * for a given class/range.
+     */
+    struct SplatDetailData
+    {
+        optional<URI>   _imageURI;
+        optional<float> _brightness;
+        optional<float> _contrast;
+        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;
+        std::string                       _samplingFunction;
+    };
+    
+    typedef std::vector<SplatTextureDef> SplatTextureDefVector;
+
+    /**
+     * 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; }
+
+        /**
+         * Whether the catalog is empty
+         */
+        bool empty() const { return _classes.empty(); }
+
+        /**
+         * 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);
+
+    public: // properties
+
+        /** Name of this catalog */
+        optional<std::string>& name() { return _name; }
+        const optional<std::string>& name() const { return _name; }
+
+
+    public: // serialization
+
+        // populate this object from a Config
+        void fromConfig(const Config& conf);
+
+        // serialize this object to a Config.
+        Config getConfig() const;
+        
+
+    public: // static utility
+
+        /** Reads a splat catalog from a URI. */
+        static SplatCatalog* read(const URI& uri, const osgDB::Options* options);
+
+
+    protected:
+
+        virtual ~SplatCatalog() { }
+
+        optional<int>         _version;
+        optional<std::string> _name;
+        optional<std::string> _description;
+        SplatClassMap         _classes;
+        SplatTextureDef       _textureDef;
+    };
+
+} } // namespace osgEarth::Splat
+
+#endif // OSGEARTH_SPLAT_SPLAT_CATALOG
diff --git a/src/osgEarthExtensions/splat/SplatCatalog.cpp b/src/osgEarthExtensions/splat/SplatCatalog.cpp
new file mode 100644
index 0000000..55b82ec
--- /dev/null
+++ b/src/osgEarthExtensions/splat/SplatCatalog.cpp
@@ -0,0 +1,389 @@
+/* -*-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.
+ *
+ * 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 <osgEarth/XmlUtils>
+#include <osg/Texture2DArray>
+
+using namespace osgEarth;
+using namespace osgEarth::Splat;
+
+#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("brightness", _brightness);
+    conf.getIfSet("contrast",   _contrast);
+    conf.getIfSet("threshold",  _threshold);
+    conf.getIfSet("slope",      _slope);
+}
+
+Config
+SplatDetailData::getConfig() const
+{
+    Config conf;
+    conf.addIfSet("image",      _imageURI);
+    conf.addIfSet("brightness", _brightness);
+    conf.addIfSet("contrast",   _contrast);
+    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.value("name");
+
+    if ( conf.hasChild("range") )
+    {
+        // 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));
+            }
+        }
+    }
+    else
+    {
+        // just one.
+        _ranges.push_back( SplatRangeData(conf) );
+    }
+}
+
+Config
+SplatClass::getConfig() const
+{
+    Config conf( _name );
+    for(SplatRangeDataVector::const_iterator i = _ranges.begin(); i != _ranges.end(); ++i)
+    {
+        conf.add( "range", 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)
+        {
+            SplatClass sclass(*i);
+            if ( !sclass._name.empty() )
+            {
+                _classes[sclass._name] = sclass;
+            }
+        }
+    }
+}
+
+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( "class", 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() );
+        }
+
+        OE_INFO << LC << "Catalog \"" << this->name().get()
+            << "\" texture size = "<< imagesInOrder.size()
+            << std::endl;
+    }
+
+    return out._texture.valid();
+}
+
+SplatCatalog*
+SplatCatalog::read(const URI&            uri,
+                   const osgDB::Options* options)
+{
+    osg::ref_ptr<SplatCatalog> catalog;
+
+    osg::ref_ptr<XmlDocument> doc = XmlDocument::load( uri, options );
+    if ( doc.valid() )
+    {
+        catalog = new SplatCatalog();
+        catalog->fromConfig( doc->getConfig().child("catalog") );
+        if ( catalog->empty() )
+        {
+            OE_WARN << LC << "Catalog is empty! (" << uri.full() << ")\n";
+            catalog = 0L;
+        }
+        else
+        {
+            OE_INFO << LC << "Catalog \"" << catalog->name().get() << "\""
+                << " contains " << catalog->getClasses().size()
+                << " classes.\n";
+        }
+    }
+    else
+    {
+        OE_WARN << LC << "Failed to read catalog from " << uri.full() << "\n";
+    }
+
+    return catalog.release();
+}
\ No newline at end of file
diff --git a/src/osgEarthExtensions/splat/SplatCoverageLegend b/src/osgEarthExtensions/splat/SplatCoverageLegend
new file mode 100644
index 0000000..1f5f0ab
--- /dev/null
+++ b/src/osgEarthExtensions/splat/SplatCoverageLegend
@@ -0,0 +1,108 @@
+/* -*-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.
+ *
+ * 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_COVERAGE_LEGEND
+#define OSGEARTH_SPLAT_COVERAGE_LEGEND 1
+
+#include "SplatExport"
+#include <osgEarth/Config>
+#include <osg/Referenced>
+#include <string>
+
+namespace osgEarth { namespace Splat
+{
+    /**
+     * 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;
+
+    /**
+     * Collection of coverage value predicates.
+     */
+
+    /**
+     * Legend that maps coverage values (or value predicates) to splat
+     * catalog classes.
+     */
+    class SplatCoverageLegend : public osg::Referenced
+    {
+    public:
+        SplatCoverageLegend();
+
+    public:
+        typedef std::vector< osg::ref_ptr<CoverageValuePredicate> > Predicates;
+
+        /**
+         * The collection of value->class mapping predicates
+         */
+        const Predicates& getPredicates() const { return _predicates; }
+
+        /**
+         * Whether this legend is empty.
+         */
+        bool empty() const { return _predicates.empty(); }
+
+    public: // serialization
+
+        // populate this object from a Config
+        void fromConfig(const Config& conf);
+
+        // serialize this object to a Config.
+        Config getConfig() const;
+
+
+    protected:
+
+        virtual ~SplatCoverageLegend() { }
+
+        optional<std::string> _name;
+        optional<std::string> _source;
+
+        Predicates _predicates;
+    };
+
+} } // namespace osgEarth::Splat
+
+#endif // OSGEARTH_SPLAT_COVERAGE_LEGEND
diff --git a/src/osgEarthExtensions/splat/SplatCoverageLegend.cpp b/src/osgEarthExtensions/splat/SplatCoverageLegend.cpp
new file mode 100644
index 0000000..bb0bda1
--- /dev/null
+++ b/src/osgEarthExtensions/splat/SplatCoverageLegend.cpp
@@ -0,0 +1,78 @@
+/* -*-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.
+ *
+ * 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 "SplatCoverageLegend"
+#include <osgEarth/Config>
+
+using namespace osgEarth;
+using namespace osgEarth::Splat;
+
+#define LC "[SplatCoverageLegend] "
+
+//............................................................................
+
+SplatCoverageLegend::SplatCoverageLegend()
+{
+    //nop
+}
+
+void
+SplatCoverageLegend::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
+SplatCoverageLegend::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( "mapping", pred );
+    }
+    conf.add( "mappings", preds );
+
+    return conf;
+}
+
diff --git a/src/osgEarthExtensions/splat/SplatExport b/src/osgEarthExtensions/splat/SplatExport
new file mode 100644
index 0000000..1001fa0
--- /dev/null
+++ b/src/osgEarthExtensions/splat/SplatExport
@@ -0,0 +1,63 @@
+/* -*-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.
+ *
+ * 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_SPLAT_EXPORT_H
+#define OSGEARTH_SPLAT_EXPORT_H 1
+
+// define USE_DEPRECATED_API is used to include in API which is being phased 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( OSGEARTH_SPLAT_LIBRARY_STATIC )
+    #    define OSGEARTH_SPLAT_EXPORT
+    #  elif defined( OSGEARTH_SPLAT_LIBRARY )
+    #    define OSGEARTH_SPLAT_EXPORT   __declspec(dllexport)
+    #  else
+    #    define OSGEARTH_SPLAT_EXPORT   __declspec(dllimport)
+    #  endif
+#else
+    #  define OSGEARTH_SPLAT_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
+
+#endif
+
diff --git a/src/osgEarthExtensions/splat/SplatExtension b/src/osgEarthExtensions/splat/SplatExtension
new file mode 100644
index 0000000..868505a
--- /dev/null
+++ b/src/osgEarthExtensions/splat/SplatExtension
@@ -0,0 +1,83 @@
+/* -*-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.
+ *
+ * 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_SPLAT_EXTENSION
+#define OSGEARTH_SPLAT_SPLAT_EXTENSION 1
+
+#include "SplatExport"
+#include "SplatOptions"
+#include "SplatTerrainEffect"
+#include <osgEarth/Extension>
+#include <osgEarth/MapNode>
+#include <osgEarthUtil/Controls>
+
+namespace osgEarth { namespace Splat
+{
+    using namespace osgEarth;
+    using namespace osgEarth::Util::Controls;
+
+    /**
+     * Extension for loading the splatting effect on demand.
+     */
+    class SplatExtension : public Extension,
+                           public ExtensionInterface<MapNode>,
+                           public ExtensionInterface<Control>
+    {
+    public:
+        META_Object(osgearth_ext_splat, SplatExtension);
+
+        // CTORs
+        SplatExtension();
+        SplatExtension(const SplatOptions& options);
+
+        // DTOR
+        virtual ~SplatExtension();
+
+
+    public: // Extension
+
+        void setDBOptions(const osgDB::Options* dbOptions);
+
+
+    public: // ExtensionInterface<MapNode>
+
+        bool connect(MapNode* mapNode);
+
+        bool disconnect(MapNode* mapNode);
+
+
+    public: // ExtensionInterface<Control>
+
+        bool connect(Control* control);
+
+        bool disconnect(Control* control);
+
+
+    protected: // Object
+        SplatExtension(const SplatExtension& rhs, const osg::CopyOp& op) { }
+
+    private:
+        const SplatOptions                 _options;
+        osg::ref_ptr<const osgDB::Options> _dbOptions;
+        osg::ref_ptr<SplatTerrainEffect>   _effect;
+        osg::ref_ptr<ImageLayer>           _coverageLayer;
+    };
+
+} } // namespace osgEarth::Splat
+
+#endif // OSGEARTH_SPLAT_SPLAT_EXTENSION
diff --git a/src/osgEarthExtensions/splat/SplatExtension.cpp b/src/osgEarthExtensions/splat/SplatExtension.cpp
new file mode 100644
index 0000000..591ee4f
--- /dev/null
+++ b/src/osgEarthExtensions/splat/SplatExtension.cpp
@@ -0,0 +1,239 @@
+/* -*-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.
+ *
+ * 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 "SplatExtension"
+#include "SplatCatalog"
+#include "Biome"
+#include "SplatCoverageLegend"
+#include "SplatTerrainEffect"
+
+#include <osgEarth/MapNode>
+#include <osgEarth/TerrainEngineNode>
+#include <osgEarth/XmlUtils>
+
+using namespace osgEarth;
+using namespace osgEarth::Splat;
+
+#define LC "[SplatExtension] "
+
+//.........................................................................
+
+namespace
+{
+}
+
+//.........................................................................
+
+SplatExtension::SplatExtension()
+{
+    //nop
+}
+
+SplatExtension::SplatExtension(const SplatOptions& options) :
+_options( options )
+{
+    //nop
+}
+
+SplatExtension::~SplatExtension()
+{
+    //nop
+}
+
+void
+SplatExtension::setDBOptions(const osgDB::Options* dbOptions)
+{
+    _dbOptions = dbOptions;
+}
+
+bool
+SplatExtension::connect(MapNode* mapNode)
+{
+    if ( !mapNode )
+    {
+        OE_WARN << LC << "Illegal: MapNode cannot be null." << std::endl;
+        return false;
+    }
+
+    OE_INFO << LC << "Connecting to MapNode.\n";
+
+    if ( !_options.catalogURI().isSet() && !_options.biomesURI().isSet() )
+    {
+        OE_WARN << LC << "Illegal: either a catalog URI or a biomes URI is required.\n";
+        return false;
+    }
+
+    if ( !_options.legendURI().isSet() )
+    {
+        OE_WARN << LC << "Illegal: a legend URI is required.\n";
+        return false;
+    }
+
+    if ( !_options.coverageLayerName().isSet() )
+    {
+        OE_WARN << LC << "Illegal: a coverage layer name is required.\n";
+        return false;
+    }
+
+    // Locate the coverage layer in the map.
+    const Map* map = mapNode->getMap();
+    const ImageLayer* coverageLayer = map->getImageLayerByName( _options.coverageLayerName().get() );
+    if ( !coverageLayer )
+    {
+        OE_WARN << LC << "Coverage layer \""
+            << _options.coverageLayerName().get()
+            << "\" not found in map.\n";
+        return false;
+    }
+
+    // Read in the legend.
+    osg::ref_ptr<SplatCoverageLegend> legend = new SplatCoverageLegend();
+    {
+        osg::ref_ptr<XmlDocument> doc = XmlDocument::load(
+            _options.legendURI().get(),
+            _dbOptions.get() );
+
+        if ( doc.valid() )
+        {
+            legend->fromConfig( doc->getConfig().child("legend") );
+        }
+
+        if ( legend->empty() )
+        {
+            OE_WARN << LC
+                << "Failed to read required legend from \""
+                << _options.legendURI()->full() << "\"\n";
+            return false;
+        }
+        else
+        {
+            OE_INFO << LC << "Legend: found " << legend->getPredicates().size() << " mappings \n";
+        }
+    }
+
+    BiomeVector biomes;
+
+    // If there is a biome file, read it; it will point to one or more catalog files.
+    if ( _options.biomesURI().isSet() )
+    {
+        osg::ref_ptr<XmlDocument> doc = XmlDocument::load(
+            _options.biomesURI().get(),
+            _dbOptions.get() );
+
+        if ( doc.valid() )
+        {
+            Config conf = doc->getConfig().child("biomes");
+            if ( !conf.empty() )
+            {
+                for(ConfigSet::const_iterator i = conf.children().begin();
+                    i != conf.children().end();
+                    ++i)
+                {
+                    Biome biome( *i );
+                    if ( biome.catalogURI().isSet() )
+                    {
+                        SplatCatalog* catalog = SplatCatalog::read( biome.catalogURI().get(), _dbOptions.get() );
+                        if ( catalog )
+                        {
+                            biome.setCatalog( catalog );
+                            biomes.push_back( biome );
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    // Otherwise, no biome file, so read a single catalog directly:
+    if ( biomes.empty() )
+    {
+        // Read in the catalog.
+        SplatCatalog* catalog = SplatCatalog::read(
+            _options.catalogURI().get(),
+            _dbOptions.get() );
+
+        if ( catalog )
+        {
+            biomes.push_back( Biome() );
+            biomes.back().setCatalog( catalog );
+        }
+    }
+
+    if ( !biomes.empty() )
+    {
+        // Terrain effect that implements splatting.
+        _effect = new SplatTerrainEffect( biomes, legend, _dbOptions.get() );
+
+        // set the coverage layer (mandatory)
+        _effect->setCoverageLayer( coverageLayer );
+
+        // set the render order (optional)
+        if ( _options.drawAfterImageLayers() == true )
+            _effect->setRenderOrder( 1.0f );
+
+        // set the various rendering options.
+        if ( _options.coverageWarp().isSet() )
+            _effect->getCoverageWarpUniform()->set( _options.coverageWarp().get() );
+    
+        if ( _options.coverageBlur().isSet() )
+            _effect->getCoverageBlurUniform()->set( _options.coverageBlur().get() );
+
+        if ( _options.scaleLevelOffset().isSet() )
+            _effect->getScaleLevelOffsetUniform()->set( (float)_options.scaleLevelOffset().get() );
+
+        // add it to the terrain.
+        mapNode->getTerrainEngine()->addEffect( _effect.get() );
+    }
+
+    else
+    {
+        OE_WARN << LC << "Extension not installed become there are no valid biomes.\n";
+    }
+
+    return true;
+}
+
+bool
+SplatExtension::disconnect(MapNode* mapNode)
+{
+    if ( mapNode && _effect.valid() )
+    {
+        mapNode->getTerrainEngine()->removeEffect( _effect.get() );
+    }
+    _effect = 0L;
+    return true;
+}
+
+bool
+SplatExtension::connect(Control* control)
+{
+    //TODO add a UI.
+    Container* container = dynamic_cast<Container*>(control);
+    if ( container )
+    {
+        container->addControl( new LabelControl("Splatting is on!") );
+    }
+    return true;
+}
+
+bool
+SplatExtension::disconnect(Control* control)
+{
+    // NOP
+    return true;
+}
diff --git a/src/osgEarthExtensions/splat/SplatOptions b/src/osgEarthExtensions/splat/SplatOptions
new file mode 100644
index 0000000..52c717a
--- /dev/null
+++ b/src/osgEarthExtensions/splat/SplatOptions
@@ -0,0 +1,151 @@
+/* -*-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.
+ *
+ * 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_OPTIONS
+#define OSGEARTH_DRIVER_SPLAT_OPTIONS 1
+
+#include <osgEarth/Common>
+#include <osgEarth/URI>
+
+namespace osgEarth { namespace Splat
+{
+    using namespace osgEarth;
+
+    /**
+     * Options governing the classification splatting engine.
+     */
+    class /*header-only*/ SplatOptions : public DriverConfigOptions
+    {
+    public:
+        /** URI of the splat texture catalog description file;
+            If you set a biomesURI, this will be ignored. */
+        optional<URI>& catalogURI() { return _catalogURI; }
+        const optional<URI>& catalogURI() const { return _catalogURI; }
+
+        /** URI of a biomes definition file.
+            Required is you do not specify a catalogURI. */
+        optional<URI>& biomesURI() { return _biomesURI; }
+        const optional<URI>& biomesURI() const { return _biomesURI; }
+        
+        /** URI of the legend that describes the values in the coverage layer
+            and maps them to the catalog. Required. */
+        optional<URI>& legendURI() { return _legendURI; }
+        const optional<URI>& legendURI() const { return _legendURI; }
+
+        /** Name of the coverage layer. The layer should be installed in the Map and
+            should be marked as shared. */
+        optional<std::string>& coverageLayerName() { return _coverageLayerName; }
+        const optional<std::string>& coverageLayerName() const { return _coverageLayerName; }
+
+        /** Whether to splat the terrain after drawing the map's image layers. The default
+            setting is to splat first and then draw imagery. */
+        optional<bool>& drawAfterImageLayers() { return _drawAfterImageLayers; }
+        const optional<bool>& drawAfterImageLayers() const { return _drawAfterImageLayers; }
+
+        /** Warping factor for coverage sampling. Please refer to
+            SplatTerrainEffect::getCoverageWarpUniform() for details. */
+        optional<float>& coverageWarp() { return _coverageWarp; }
+        const optional<float>& coverageWarp() const { return _coverageWarp; }
+
+        /** Blurring factor for coverage sampling. Please refer to
+            SplatTerrainEffect::getCoverageBlurUniform() for details. */
+        optional<float>& coverageBlur() { return _coverageBlur; }
+        const optional<float>& coverageBlur() const { return _coverageBlur; }
+
+        /** Level-of-detail offset for scaling the splat textures. */
+        optional<int>& scaleLevelOffset() { return _scaleLevelOffset; }
+        const optional<int>& scaleLevelOffset() const { return _scaleLevelOffset; }
+
+        /** Whether to use bilinear sampling to smooth the coverage data.
+            Default is true; set to false for higher performance. */
+        optional<bool>& bilinearSampling() { return _bilinearSampling; }
+        const optional<bool>& bilinearSampling() const { return _bilinearSampling; }
+
+        /** Maximum distance from the camera at which to render detail splats. */
+        optional<float>& detailMaxRange() { return _detailMaxRange; }
+        const optional<float>& detailMaxRange() const { return _detailMaxRange; }
+
+    public:
+        SplatOptions( const ConfigOptions& opt =ConfigOptions() ) : DriverConfigOptions( opt )
+        {
+            setDriver( "splat" );
+
+            // Set default values:
+            _coverageWarp.init(0.01f);
+            _coverageBlur.init(1.0f);
+            _scaleLevelOffset.init(0);
+            _drawAfterImageLayers.init(false);
+            _bilinearSampling.init(true);
+            _detailMaxRange.init(1000000.0f);
+
+            fromConfig( _conf );
+        }
+
+        virtual ~SplatOptions() { }
+
+    public:
+        Config getConfig() const {
+            Config conf = DriverConfigOptions::getConfig();
+            conf.updateIfSet("catalog",  _catalogURI);
+            conf.updateIfSet("biomes",   _biomesURI);
+            conf.updateIfSet("legend",   _legendURI);
+            conf.updateIfSet("coverage", _coverageLayerName );
+            conf.updateIfSet("draw_after_image_layers", _drawAfterImageLayers);
+            conf.updateIfSet("coverage_warp", _coverageWarp );
+            conf.updateIfSet("coverage_blur", _coverageBlur );
+            conf.updateIfSet("scale_level_offset", _scaleLevelOffset );
+            conf.updateIfSet("bilinear_sampling", _bilinearSampling);
+            conf.updateIfSet("detail_max_range", _detailMaxRange);
+            return conf;
+        }
+
+    protected:
+        void mergeConfig( const Config& conf ) {
+            DriverConfigOptions::mergeConfig( conf );
+            fromConfig( conf );
+        }
+
+    private:
+        void fromConfig( const Config& conf ) {
+            conf.getIfSet("catalog",  _catalogURI);
+            conf.getIfSet("biomes",   _biomesURI);
+            conf.getIfSet("legend",   _legendURI);
+            conf.getIfSet("coverage", _coverageLayerName );
+            conf.getIfSet("draw_after_image_layers", _drawAfterImageLayers);
+            conf.getIfSet("coverage_warp", _coverageWarp );
+            conf.getIfSet("coverage_blur", _coverageBlur );
+            conf.getIfSet("scale_level_offset", _scaleLevelOffset );
+            conf.getIfSet("bilinear_sampling", _bilinearSampling);
+            conf.getIfSet("detail_max_range", _detailMaxRange);
+        }
+
+        optional<URI>         _catalogURI;
+        optional<URI>         _biomesURI;
+        optional<URI>         _legendURI;
+        optional<std::string> _coverageLayerName;
+        optional<bool>        _drawAfterImageLayers;
+        optional<float>       _coverageWarp;
+        optional<float>       _coverageBlur;
+        optional<int>         _scaleLevelOffset;
+        optional<bool>        _bilinearSampling;
+        optional<float>       _detailMaxRange;
+    };
+
+} } // namespace osgEarth::Splat
+
+#endif // OSGEARTH_DRIVER_SPLAT_OPTIONS
diff --git a/src/osgEarthExtensions/splat/SplatPlugin.cpp b/src/osgEarthExtensions/splat/SplatPlugin.cpp
new file mode 100644
index 0000000..cc7d888
--- /dev/null
+++ b/src/osgEarthExtensions/splat/SplatPlugin.cpp
@@ -0,0 +1,56 @@
+/* -*-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.
+ *
+ * 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 "SplatOptions"
+#include "SplatExtension"
+
+#include <osgDB/ReaderWriter>
+#include <osgDB/Registry>
+#include <osgDB/FileNameUtils>
+
+namespace osgEarth { namespace Splat
+{
+    /**
+     * Plugin entry point
+     */
+    class SplatPlugin : public osgDB::ReaderWriter
+    {
+    public: // Plugin stuff
+
+        SplatPlugin() {
+            supportsExtension( "osgearth_splat", "osgEarth Splat Extension Plugin" );
+        }
+        
+        const char* className() {
+            return "osgEarth Splat Extension Plugin";
+        }
+
+        virtual ~SplatPlugin() { }
+
+        ReadResult readObject(const std::string& filename, const osgDB::Options* dbOptions) const
+        {
+          if ( !acceptsExtension(osgDB::getLowerCaseFileExtension(filename)) )
+                return ReadResult::FILE_NOT_HANDLED;
+
+          return ReadResult( new SplatExtension(Extension::getConfigOptions(dbOptions)) );
+        }
+    };
+
+    REGISTER_OSGPLUGIN(osgearth_splat, SplatPlugin)
+
+} } // namespace osgEarth::Splat
diff --git a/src/osgEarthExtensions/splat/SplatShaders b/src/osgEarthExtensions/splat/SplatShaders
new file mode 100644
index 0000000..c147c5a
--- /dev/null
+++ b/src/osgEarthExtensions/splat/SplatShaders
@@ -0,0 +1,46 @@
+/* -*-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_SHADERS
+#define OSGEARTH_SPLAT_SHADERS 1
+
+#include <osgEarth/ShaderLoader>
+
+namespace osgEarth { namespace Splat
+{
+    struct Shaders : public osgEarth::ShaderPackage
+	{
+        Shaders();
+
+        std::string
+            Types,
+            Noise,
+            VertModel,
+            VertView,
+            Frag,
+            FragCommon,
+            FragGetRenderInfo,
+            Util;
+	};
+	
+} } // namespace osgEarth::Splat
+
+#endif // OSGEARTH_SPLAT_SHADERS
diff --git a/src/osgEarthExtensions/splat/SplatShaders.cpp.in b/src/osgEarthExtensions/splat/SplatShaders.cpp.in
new file mode 100644
index 0000000..b6d40af
--- /dev/null
+++ b/src/osgEarthExtensions/splat/SplatShaders.cpp.in
@@ -0,0 +1,34 @@
+// CMake will compile this file into AutoGenShaders.cpp
+
+#include <osgEarthExtensions/splat/SplatShaders>
+
+#define MULTILINE(...) #__VA_ARGS__
+
+using namespace osgEarth::Splat;
+
+Shaders::Shaders()
+{
+    Types = "Splat.types.glsl";
+    _sources[Types] = MULTILINE(@Splat.types.glsl@);
+
+    Noise = "Splat.Noise.glsl";
+    _sources[Noise] = MULTILINE(@Splat.Noise.glsl@);
+
+    VertModel = "Splat.vert.model.glsl";
+    _sources[VertModel] = MULTILINE(@Splat.vert.model.glsl@);
+
+    VertView = "Splat.vert.view.glsl";
+    _sources[VertView] = MULTILINE(@Splat.vert.view.glsl@);
+
+    Frag = "Splat.frag.glsl";
+    _sources[Frag] = MULTILINE(@Splat.frag.glsl@);
+
+    FragCommon = "Splat.frag.common.glsl";
+    _sources[FragCommon] = MULTILINE(@Splat.frag.common.glsl@);
+
+    FragGetRenderInfo = "Splat.frag.getRenderInfo.glsl";
+    _sources[FragGetRenderInfo] = MULTILINE(@Splat.frag.getRenderInfo.glsl@);
+
+    Util = "Splat.util.glsl";
+    _sources[Util] = MULTILINE(@Splat.util.glsl@);
+}
\ No newline at end of file
diff --git a/src/osgEarthExtensions/splat/SplatTerrainEffect b/src/osgEarthExtensions/splat/SplatTerrainEffect
new file mode 100644
index 0000000..d77fd63
--- /dev/null
+++ b/src/osgEarthExtensions/splat/SplatTerrainEffect
@@ -0,0 +1,138 @@
+/* -*-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 "Biome"
+#include "BiomeSelector"
+#include "SplatShaders"
+
+#include <osgEarth/TerrainEffect>
+#include <osgEarth/ImageLayer>
+#include <osg/Image>
+#include <osg/Uniform>
+#include <osg/Texture2DArray>
+
+using namespace osgEarth;
+
+namespace osgEarth { namespace Splat
+{
+    /**
+     * Effect that applies texture splatting to the terrain.
+     */
+    class SplatTerrainEffect : public TerrainEffect
+    {
+    public:
+        /** construct a new terrain effect. */
+        SplatTerrainEffect(
+            const BiomeVector&    biomes,
+            SplatCoverageLegend*  legend,
+            const osgDB::Options* dbOptions);
+
+        /**
+         * Sets the image layer that supplies the coverage values. This value
+         * is mandatory, and you must call it prior to installing the effect.
+         */
+        void setCoverageLayer(const ImageLayer* layer) { _coverageLayer = layer; }
+        const ImageLayer* getCoverageLayer() { return _coverageLayer.get(); }
+
+        /**
+         * Sets the draw priority of the splat effect. Default is -1.0, which means
+         * the splat will render before the terrain engine renders any image layers.
+         * You can set it to 1.0 to splat after drawing image layers.
+         *
+         * This is options, but if you call it you must do so prior to installing
+         * the effect.
+         */
+        void setRenderOrder(float value) { _renderOrder = value; }
+        float getRenderOrder() const { return _renderOrder; }
+
+        /**
+         * Uniform that governs coverage sample warping. The value is type FLOAT.
+         * This warps the texture coordinates used to sample the coverage data
+         * using a simplex noise function. This can help to mitigate the "blockiness"
+         * that somes from low resolution coverage data.
+         * Reasonable values are in the range [0 .. 0.01]
+         */
+        osg::Uniform* getCoverageWarpUniform() { return _warpUniform.get(); }
+
+        /**
+         * Uniform that governs the bilinear sampling of the coverage texture.
+         * The default is 1.0, at which normal bilinear sampling occurs.
+         * Higher values will blur the splatting, and lower values will sharpen it.
+         */
+        osg::Uniform* getCoverageBlurUniform() { return _blurUniform.get(); }
+
+        /**
+         * Uniform that governs the scale level-of-detail offset when sampling 
+         * splat textures. Defaults to 0. This is an integer such that for a
+         * value of N, the texture coordinates are scaled by 2^N when sampling
+         * splat textures.
+         */
+        osg::Uniform* getScaleLevelOffsetUniform() { return _scaleOffsetUniform.get(); }
+
+
+    public: // TerrainEffect interface
+
+        void onInstall(TerrainEngineNode* engine);
+
+        void onUninstall(TerrainEngineNode* engine);
+
+
+    protected:
+        virtual ~SplatTerrainEffect() { }
+
+        void installCoverageSamplingFunction(SplatTextureDef& textureDef);
+        osg::Texture* createNoiseTexture() const;
+
+        // these 2 vectors are index-aligned:
+        BiomeVector                         _biomes;
+        SplatTextureDefVector               _textureDefs;
+
+        bool                                _ok;
+        int                                 _splatTexUnit;
+        osg::ref_ptr<osg::Uniform>          _splatTexUniform;
+        osg::ref_ptr<osg::Uniform>          _coverageTexUniform;
+        osg::ref_ptr<osg::Uniform>          _scaleOffsetUniform;
+        osg::ref_ptr<osg::Uniform>          _warpUniform;
+        osg::ref_ptr<osg::Uniform>          _blurUniform;
+        osg::ref_ptr<SplatCoverageLegend>   _legend;
+        osg::observer_ptr<const ImageLayer> _coverageLayer;
+        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;
+
+        osg::ref_ptr<BiomeSelector>         _biomeSelector;
+
+        bool                                _editMode;
+        bool                                _gpuNoise;
+
+    };
+
+} } // namespace osgEarth::Splat
+
+#endif // OSGEARTH_SPLAT_SPLAT_TERRAIN_EFFECT_H
diff --git a/src/osgEarthExtensions/splat/SplatTerrainEffect.cpp b/src/osgEarthExtensions/splat/SplatTerrainEffect.cpp
new file mode 100644
index 0000000..d116771
--- /dev/null
+++ b/src/osgEarthExtensions/splat/SplatTerrainEffect.cpp
@@ -0,0 +1,513 @@
+/* -*-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 <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 <osgDB/WriteFile>
+
+#include "SplatShaders"
+
+#define LC "[Splat] "
+
+#define COVERAGE_SAMPLER "oe_splat_coverageTex"
+#define SPLAT_SAMPLER    "oe_splatTex"
+#define NOISE_SAMPLER    "oe_splat_noiseTex"
+
+using namespace osgEarth;
+using namespace osgEarth::Splat;
+
+SplatTerrainEffect::SplatTerrainEffect(const BiomeVector&    biomes,
+                                       SplatCoverageLegend*  legend,
+                                       const osgDB::Options* dbOptions) :
+_biomes     ( biomes ),
+_legend     ( legend ),
+_renderOrder( -1.0f ),
+_ok         ( false ),
+_editMode   ( false ),
+_gpuNoise   ( false )
+{
+    if ( biomes.size() == 0 )
+    {
+        OE_WARN << LC << "Internal: no biomes.\n";
+    }
+
+    // Create a texture def for each biome.
+    for(unsigned b = 0; b < biomes.size(); ++b)
+    {
+        const Biome& biome = biomes[b];
+        SplatTextureDef def;
+
+        if ( biome.getCatalog() )
+        {
+            if ( biome.getCatalog()->createSplatTextureDef(dbOptions, def) )
+            {
+                // install the sampling function.
+                installCoverageSamplingFunction( def );
+            }
+            else
+            {
+                OE_WARN << LC << "Failed to create a texture for a catalog (" 
+                    << biome.getCatalog()->name().get() << ")\n";
+            }
+
+        }
+        else
+        {
+            OE_WARN << LC << "Biome \""
+                << biome.name().get() << "\"" 
+                << " has an empty catalog and will be ignored.\n";
+        }
+
+        // put it on the list either way, since the vector indicies of biomes
+        // and texturedefs need to line up
+        _textureDefs.push_back( def );
+
+        if ( !_ok )
+        {
+            _ok = def._texture.valid();
+        }
+    }
+
+    SplatOptions def;
+
+    _scaleOffsetUniform    = new osg::Uniform("oe_splat_scaleOffsetInt",   *def.scaleLevelOffset());
+    _warpUniform           = new osg::Uniform("oe_splat_warp",             *def.coverageWarp());
+    _blurUniform           = new osg::Uniform("oe_splat_blur",             *def.coverageBlur());
+    _useBilinearUniform    = new osg::Uniform("oe_splat_useBilinear",      (def.bilinearSampling()==true?1.0f:0.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::onInstall(TerrainEngineNode* engine)
+{
+    if ( engine && _ok )
+    {
+        if ( !_coverageLayer.valid() )
+        {
+            OE_WARN << LC << "No coverage layer set\n";
+            return;
+        }
+
+        // Do not need this until/unless the splatting algorithm uses elevation.
+        //engine->requireElevationTextures();
+
+        // install the splat texture array:
+        if ( engine->getResources()->reserveTextureImageUnit(_splatTexUnit, "Splat Coverage") )
+        {
+            osg::StateSet* stateset;
+
+#ifdef REX // note, rex doesn't support the biome selector cull callback yet b/c of the render binning
+            if ( _biomes.size() == 1 )
+                stateset = engine->getSurfaceStateSet();
+            else
+                stateset = new osg::StateSet();
+#else
+            stateset = new osg::StateSet();
+#endif
+
+            // TODO: reinstate "biomes"
+            //osg::StateSet* stateset = new osg::StateSet();
+
+            // splat sampler
+            _splatTexUniform = stateset->getOrCreateUniform( SPLAT_SAMPLER, osg::Uniform::SAMPLER_2D_ARRAY );
+            _splatTexUniform->set( _splatTexUnit );
+            stateset->setTextureAttribute( _splatTexUnit, _textureDefs[0]._texture.get() );
+
+            // coverage sampler
+            _coverageTexUniform = stateset->getOrCreateUniform( COVERAGE_SAMPLER, osg::Uniform::SAMPLER_2D );
+            _coverageTexUniform->set( _coverageLayer->shareImageUnit().get() );
+
+            // control 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));
+
+
+            Shaders package;
+
+            package.define( "SPLAT_EDIT",        _editMode );
+            package.define( "SPLAT_GPU_NOISE",   _gpuNoise );
+            package.define( "OE_USE_NORMAL_MAP", engine->normalTexturesRequired() );
+
+            package.replace( "$COVERAGE_TEXMAT_UNIFORM", _coverageLayer->shareTexMatUniformName().get() );
+            
+            VirtualProgram* vp = VirtualProgram::getOrCreate(stateset);
+            package.load( vp, package.VertModel );
+            package.load( vp, package.VertView );
+            package.load( vp, package.Frag );
+            package.load( vp, package.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));
+            }
+            else // use a noise texture (the default)
+            {
+                if (engine->getResources()->reserveTextureImageUnit(_noiseTexUnit, "Splat Noise"))
+                {
+                    _noiseTex = createNoiseTexture();
+                    stateset->setTextureAttribute( _noiseTexUnit, _noiseTex.get() );
+                    _noiseTexUniform = stateset->getOrCreateUniform( NOISE_SAMPLER, osg::Uniform::SAMPLER_2D );
+                    _noiseTexUniform->set( _noiseTexUnit );
+                }
+            }
+
+            if ( _gpuNoise )
+            {
+                // support shaders
+                std::string noiseShaderSource = ShaderLoader::load( package.Noise, package );
+                osg::Shader* noiseShader = new osg::Shader(osg::Shader::FRAGMENT, noiseShaderSource);
+                vp->setShader( "oe_splat_noiseshaders", noiseShader );
+            }
+
+#ifdef REX
+            // TODO: I disabled BIOMES temporarily because the callback impl applies the splatting shader
+            // to the land cover bin as well as the surface bin, which we do not want -- find another way!
+            if ( _biomes.size() == 1 )
+            {
+                // install his biome's texture set:
+                stateset->setTextureAttribute(_splatTexUnit, _textureDefs[0]._texture.get());
+
+                // install this biome's sampling function. Use cloneOrCreate since each
+                // stateset needs a different shader set in its VP.
+                VirtualProgram* vp = VirtualProgram::cloneOrCreate( stateset );
+                osg::Shader* shader = new osg::Shader(osg::Shader::FRAGMENT, _textureDefs[0]._samplingFunction);
+                vp->setShader( "oe_splat_getRenderInfo", shader );
+            }
+
+            else
+#endif
+            {
+                //OE_WARN << LC << "Multi-biome setup needs re-implementing (reminder)\n";
+
+                // install the cull callback that will select the appropriate
+                // state based on the position of the camera.
+                _biomeSelector = new BiomeSelector(
+                    _biomes,
+                    _textureDefs,
+                    stateset,
+                    _splatTexUnit );
+
+                engine->addCullCallback( _biomeSelector.get() );
+            }
+        }
+    }
+}
+
+
+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;
+        }
+
+        if ( _biomeSelector.valid() )
+        {
+            engine->removeCullCallback( _biomeSelector.get() );
+            _biomeSelector = 0L;
+        }
+    }
+}
+
+#define IND "    "
+
+void
+SplatTerrainEffect::installCoverageSamplingFunction(SplatTextureDef& textureDef)
+{
+    if ( !textureDef._texture.valid() )
+    {
+        OE_WARN << LC << "Internal: texture is not set; cannot create a sampling function\n";
+        return;
+    }
+
+    std::stringstream
+        weightBuf,
+        primaryBuf,
+        detailBuf,
+        brightnessBuf,
+        contrastBuf,
+        thresholdBuf,
+        slopeBuf;
+
+    unsigned
+        primaryCount    = 0,
+        detailCount     = 0,
+        brightnessCount = 0,
+        contrastCount   = 0,
+        thresholdCount  = 0,
+        slopeCount      = 0;
+
+    const SplatCoverageLegend::Predicates& preds = _legend->getPredicates();
+    for(SplatCoverageLegend::Predicates::const_iterator p = preds.begin(); p != preds.end(); ++p)
+    {
+        const CoverageValuePredicate* pred = p->get();
+
+        if ( pred->_exactValue.isSet() )
+        {
+            // 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() )
+            {
+                // found it; loop over the range selectors:
+                int selectorCount = 0;
+                const SplatSelectorVector& selectors = i->second;
+
+                OE_DEBUG << LC << "Class " << className << " has " << selectors.size() << " selectors.\n";
+
+                for(SplatSelectorVector::const_iterator selector = selectors.begin();
+                    selector != selectors.end();
+                    ++selector)
+                {
+                    const std::string&    expression = selector->first;
+                    const SplatRangeData& rangeData  = selector->second;
+
+                    std::string val = pred->_exactValue.get();
+
+                    weightBuf
+                        << IND "float w" << val
+                        << " = (1.0-clamp(abs(value-" << val << ".0),0.0,1.0));\n";
+
+                    // Primary texture index:
+                    if ( primaryCount == 0 )
+                        primaryBuf << IND "primary += ";
+                    else
+                        primaryBuf << " + ";
+
+                    // the "+1" is because "primary" starts out at -1.
+                    primaryBuf << "w"<<val << "*" << (rangeData._textureIndex + 1) << ".0";
+                    primaryCount++;
+
+                    // Detail texture index:
+                    if ( rangeData._detail.isSet() )
+                    {
+                        if ( detailCount == 0 )
+                            detailBuf << IND "detail += ";
+                        else
+                            detailBuf << " + ";
+                        // the "+1" is because "detail" starts out at -1.
+                        detailBuf << "w"<<val << "*" << (rangeData._detail->_textureIndex + 1) << ".0";
+                        detailCount++;
+
+                        if ( rangeData._detail->_brightness.isSet() )
+                        {
+                            if ( brightnessCount == 0 )
+                                brightnessBuf << IND "brightness += ";
+                            else
+                                brightnessBuf << " + ";
+                            brightnessBuf << "w"<<val << "*" << rangeData._detail->_brightness.get();
+                            brightnessCount++;
+                        }
+
+                        if ( rangeData._detail->_contrast.isSet() )
+                        {
+                            if ( contrastCount == 0 )
+                                contrastBuf << IND "contrast += ";
+                            else
+                                contrastBuf << " + ";
+                            contrastBuf << "w"<<val << "*" << rangeData._detail->_contrast.get();
+                            contrastCount++;
+                        }
+
+                        if ( rangeData._detail->_threshold.isSet() )
+                        {
+                            if ( thresholdCount == 0 )
+                                thresholdBuf << IND "threshold += ";
+                            else
+                                thresholdBuf << " + ";
+                            thresholdBuf << "w"<<val << "*" << rangeData._detail->_threshold.get();
+                            thresholdCount++;
+                        }
+
+                        if ( rangeData._detail->_slope.isSet() )
+                        {
+                            if ( slopeCount == 0 )
+                                slopeBuf << IND "slope += ";
+                            else
+                                slopeBuf << " + ";
+                            slopeBuf << "w"<<val << "*" << rangeData._detail->_slope.get();
+                            slopeCount++;
+                        }
+                    }                    
+                }
+            }
+        }
+    }
+
+    if ( primaryCount > 0 )
+        primaryBuf << ";\n";
+
+    if ( detailCount > 0 )
+        detailBuf << ";\n";
+
+    if ( brightnessCount > 0 )
+        brightnessBuf << ";\n";
+
+    if ( contrastCount > 0 )
+        contrastBuf << ";\n";
+
+    if ( thresholdCount > 0 )
+        thresholdBuf << ";\n";
+
+    if ( slopeCount > 0 )
+        slopeBuf << ";\n";
+
+    Shaders package;
+    std::string code = ShaderLoader::load(
+        package.FragGetRenderInfo,
+        package);
+
+    std::string codeToInject = Stringify()
+        << IND
+        << weightBuf.str()
+        << primaryBuf.str()
+        << detailBuf.str()
+        << brightnessBuf.str()
+        << contrastBuf.str()
+        << thresholdBuf.str()
+        << slopeBuf.str();
+
+    osgEarth::replaceIn(code, "$CODE_INJECTION_POINT", codeToInject);
+
+    textureDef._samplingFunction = code;
+
+    OE_DEBUG << LC << "Sampling function = \n" << code << "\n\n";
+}
+
+osg::Texture*
+SplatTerrainEffect::createNoiseTexture() const
+{
+    const int size = 1024;
+    const int slices = 1;
+
+    GLenum type = slices > 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<slices; ++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( 1.0f );
+    tex->setUnRefImageDataAfterApply( true );
+
+    return tex;
+}
diff --git a/src/osgEarthExtensions/terrainshader/CMakeLists.txt b/src/osgEarthExtensions/terrainshader/CMakeLists.txt
new file mode 100644
index 0000000..f6bf94d
--- /dev/null
+++ b/src/osgEarthExtensions/terrainshader/CMakeLists.txt
@@ -0,0 +1,24 @@
+#
+# terrain shader plugin
+#
+set(TARGET_SRC
+	TerrainShaderPlugin.cpp
+	TerrainShaderExtension.cpp
+	${SHADERS_CPP} )
+	
+set(LIB_PUBLIC_HEADERS
+	TerrainShaderExtension
+	TerrainShaderOptions)
+	
+set(TARGET_H
+	${LIB_PUBLIC_HEADERS})
+
+
+set(TARGET_COMMON_LIBRARIES ${TARGET_COMMON_LIBRARIES}
+    osgEarthUtil)
+	
+setup_extension(osgearth_terrainshader)
+
+# to install public driver includes:
+set(LIB_NAME terrainshader)
+
diff --git a/src/osgEarthExtensions/terrainshader/TerrainShaderExtension b/src/osgEarthExtensions/terrainshader/TerrainShaderExtension
new file mode 100644
index 0000000..207d32f
--- /dev/null
+++ b/src/osgEarthExtensions/terrainshader/TerrainShaderExtension
@@ -0,0 +1,68 @@
+/* -*-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.
+ *
+ * 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_EXT_TERRAIN_SHADER_H
+#define OSGEARTH_EXT_TERRAIN_SHADER_H 1
+
+#include "TerrainShaderOptions"
+#include <osgEarth/Extension>
+#include <osgEarth/MapNode>
+#include <osgEarthUtil/Controls>
+
+namespace osgEarth { namespace TerrainShader
+{
+    using namespace osgEarth;
+
+    class TerrainShaderExtension : public Extension,
+                                   public ExtensionInterface<MapNode>
+    {
+    public:
+        META_Object(osgearth_ext_terrainshader, TerrainShaderExtension);
+
+        // CTORs
+        TerrainShaderExtension();
+        TerrainShaderExtension(const TerrainShaderOptions& options);
+
+        // DTOR
+        virtual ~TerrainShaderExtension();
+
+
+    public: // Extension
+
+        void setDBOptions(const osgDB::Options* dbOptions);
+
+
+    public: // ExtensionInterface<MapNode>
+
+        bool connect(MapNode* mapNode);
+
+        bool disconnect(MapNode* mapNode);
+
+
+    protected: // Object
+        TerrainShaderExtension(const TerrainShaderExtension& rhs, const osg::CopyOp& op) { }
+
+    private:
+        const TerrainShaderOptions           _options;
+        osg::ref_ptr<const osgDB::Options>   _dbOptions;
+        osg::ref_ptr<class TerrainEffect>    _effect;
+    };
+
+} } // namespace osgEarth::TerrainShader
+
+#endif // OSGEARTH_EXT_TERRAIN_SHADER_H
diff --git a/src/osgEarthExtensions/terrainshader/TerrainShaderExtension.cpp b/src/osgEarthExtensions/terrainshader/TerrainShaderExtension.cpp
new file mode 100644
index 0000000..d577172
--- /dev/null
+++ b/src/osgEarthExtensions/terrainshader/TerrainShaderExtension.cpp
@@ -0,0 +1,121 @@
+/* -*-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.
+ *
+ * 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 "TerrainShaderExtension"
+#include <osgEarth/TerrainEffect>
+#include <osgEarth/MapNode>
+#include <osgEarth/VirtualProgram>
+#include <osgEarth/ShaderLoader>
+
+using namespace osgEarth;
+using namespace osgEarth::TerrainShader;
+
+#define LC "[TerrainShaderExtension] "
+
+namespace
+{
+    class GLSLEffect : public TerrainEffect
+    {
+    public:
+        GLSLEffect(const std::vector<TerrainShaderOptions::Code>& code,
+                   const osgDB::Options*                          dbOptions ) : _code(code), _dbOptions(dbOptions)
+        {
+            for(unsigned i=0; i<code.size(); ++i)
+            {
+                std::string fn = code[i]._uri.isSet() ? code[i]._uri->full() : "$code." + i;
+                _package.add( fn, code[i]._source );
+            }
+        }
+
+        void onInstall(TerrainEngineNode* engine)
+        {
+            if ( !engine ) return;
+
+            VirtualProgram* vp = VirtualProgram::getOrCreate(engine->getOrCreateStateSet());
+            _package.loadAll( vp, _dbOptions.get() );
+        }
+
+        void onUninstall(TerrainEngineNode* engine)
+        {
+            if ( engine && engine->getStateSet() )
+            {
+                VirtualProgram* vp = VirtualProgram::get(engine->getStateSet());
+                if ( vp )
+                {
+                    _package.unloadAll( vp, _dbOptions.get() );
+                }
+            }
+        }
+
+        std::vector<TerrainShaderOptions::Code> _code;
+        ShaderPackage                           _package;
+        osg::ref_ptr<const osgDB::Options>      _dbOptions;
+    };
+}
+
+
+TerrainShaderExtension::TerrainShaderExtension()
+{
+    //nop
+}
+
+TerrainShaderExtension::TerrainShaderExtension(const TerrainShaderOptions& options) :
+_options( options )
+{
+    //nop
+}
+
+TerrainShaderExtension::~TerrainShaderExtension()
+{
+    //nop
+}
+
+void
+TerrainShaderExtension::setDBOptions(const osgDB::Options* dbOptions)
+{
+    _dbOptions = dbOptions;
+}
+
+bool
+TerrainShaderExtension::connect(MapNode* mapNode)
+{
+    if ( !mapNode )
+    {
+        OE_WARN << LC << "Illegal: MapNode cannot be null." << std::endl;
+        return false;
+    }
+    _effect = new GLSLEffect( _options.code(), _dbOptions.get() );
+    mapNode->getTerrainEngine()->addEffect( _effect.get() );
+    
+    OE_INFO << LC << "Installed.\n";
+
+    return true;
+}
+
+bool
+TerrainShaderExtension::disconnect(MapNode* mapNode)
+{
+    if ( mapNode && _effect.valid() )
+    {
+        mapNode->getTerrainEngine()->removeEffect( _effect.get() );
+        _effect = 0L;
+    }
+
+    return true;
+}
+
diff --git a/src/osgEarthExtensions/terrainshader/TerrainShaderOptions b/src/osgEarthExtensions/terrainshader/TerrainShaderOptions
new file mode 100644
index 0000000..a58bc8b
--- /dev/null
+++ b/src/osgEarthExtensions/terrainshader/TerrainShaderOptions
@@ -0,0 +1,87 @@
+/* -*-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.
+ *
+ * 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_EXT_TERRAIN_SHADER_OPTIONS
+#define OSGEARTH_EXT_TERRAIN_SHADER_OPTIONS 1
+
+#include <osgEarth/Config>
+#include <osgEarth/URI>
+#include <vector>
+
+namespace osgEarth { namespace TerrainShader
+{
+    using namespace osgEarth;
+
+    /**
+     * Options for applying an inline shader to the terrain.
+     */
+    class TerrainShaderOptions : public DriverConfigOptions // NO EXPORT; header only
+    {
+    public:
+        struct Code
+        {
+            std::string   _source;
+            optional<URI> _uri;
+        };
+
+        std::vector<Code>& code() { return _code; }
+        const std::vector<Code>& code() const { return _code; }
+
+    public:
+        TerrainShaderOptions( const ConfigOptions& opt =ConfigOptions() ) : DriverConfigOptions( opt )
+        {
+            setDriver( "terrainshader" );
+            fromConfig( _conf );
+        }
+
+        virtual ~TerrainShaderOptions() { }
+
+    public:
+        Config getConfig() const {
+            Config conf = DriverConfigOptions::getConfig();
+            for(unsigned i=0; i<_code.size(); ++i) {
+                Config c("code", _code[i]._source);
+                c.addIfSet("url", _code[i]._uri);
+                conf.add( c );
+            }
+            return conf;
+        }
+
+    protected:
+        void mergeConfig( const Config& conf ) {
+            DriverConfigOptions::mergeConfig( conf );
+            fromConfig( conf );
+        }
+
+    private:
+        void fromConfig( const Config& conf ) {
+            ConfigSet s = conf.children("code");
+            for(ConfigSet::const_iterator i = s.begin(); i != s.end(); ++i) {
+                _code.push_back(Code());
+                _code.back()._source = i->value();
+                i->getIfSet("url", _code.back()._uri);
+            }
+        }
+
+        std::vector<Code> _code;
+    };
+
+} } // namespace osgEarth::TerrainShader
+
+#endif // OSGEARTH_EXT_TERRAIN_SHADER_OPTIONS
+
diff --git a/src/osgEarthExtensions/terrainshader/TerrainShaderPlugin.cpp b/src/osgEarthExtensions/terrainshader/TerrainShaderPlugin.cpp
new file mode 100644
index 0000000..be96b77
--- /dev/null
+++ b/src/osgEarthExtensions/terrainshader/TerrainShaderPlugin.cpp
@@ -0,0 +1,56 @@
+/* -*-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.
+ *
+ * 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 "TerrainShaderOptions"
+#include "TerrainShaderExtension"
+
+#include <osgDB/ReaderWriter>
+#include <osgDB/Registry>
+#include <osgDB/FileNameUtils>
+
+namespace osgEarth { namespace TerrainShader
+{
+    /**
+     * Plugin entry point
+     */
+    class TerrainShaderPlugin : public osgDB::ReaderWriter
+    {
+    public: // Plugin stuff
+
+        TerrainShaderPlugin() {
+            supportsExtension( "osgearth_terrainshader", "osgEarth Terrain Shader Extension Plugin" );
+        }
+        
+        const char* className() {
+            return "osgEarth Terrain Shader Extension Plugin";
+        }
+
+        virtual ~TerrainShaderPlugin() { }
+
+        ReadResult readObject(const std::string& filename, const osgDB::Options* dbOptions) const
+        {
+          if ( !acceptsExtension(osgDB::getLowerCaseFileExtension(filename)) )
+                return ReadResult::FILE_NOT_HANDLED;
+
+          return ReadResult( new TerrainShaderExtension(Extension::getConfigOptions(dbOptions)) );
+        }
+    };
+
+    REGISTER_OSGPLUGIN(osgearth_terrainshader, TerrainShaderPlugin)
+
+} } // namespace osgEarth::BumpMap
diff --git a/src/osgEarthExtensions/viewpoints/CMakeLists.txt b/src/osgEarthExtensions/viewpoints/CMakeLists.txt
new file mode 100644
index 0000000..65c7f60
--- /dev/null
+++ b/src/osgEarthExtensions/viewpoints/CMakeLists.txt
@@ -0,0 +1,24 @@
+#
+# Viewpoints plugin
+#
+
+SET(TARGET_SRC
+	ViewpointsPlugin.cpp
+	ViewpointsExtension.cpp)
+	
+SET(LIB_PUBLIC_HEADERS
+	ViewpointsExtension)
+	
+SET(TARGET_H
+	${LIB_PUBLIC_HEADERS} )
+
+SET(TARGET_COMMON_LIBRARIES ${TARGET_COMMON_LIBRARIES}
+    osgEarthUtil)
+	
+SETUP_EXTENSION(osgearth_viewpoints)
+
+# to install public driver includes:
+SET(LIB_NAME viewpoints)
+
+INCLUDE(ModuleInstallOsgEarthExtensionIncludes OPTIONAL)
+
diff --git a/src/osgEarthExtensions/viewpoints/ViewpointsExtension b/src/osgEarthExtensions/viewpoints/ViewpointsExtension
new file mode 100644
index 0000000..08dfada
--- /dev/null
+++ b/src/osgEarthExtensions/viewpoints/ViewpointsExtension
@@ -0,0 +1,83 @@
+/* -*-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.
+ *
+ * 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_VIEWPOINTS_EXTENSION
+#define OSGEARTH_VIEWPOINTS_EXTENSION 1
+
+#include <osgEarth/Extension>
+#include <osgEarth/Viewpoint>
+#include <osgEarthUtil/Controls>
+#include <osgGA/GUIEventAdapter>
+#include <osg/View>
+#include <vector>
+
+namespace osgEarth { namespace Viewpoints
+{
+    using namespace osgEarth;
+    using namespace osgEarth::Util::Controls;
+
+    /**
+     * Loads a collection of viewpoints and makes them available
+     * through a Controls UI.
+     */
+    class ViewpointsExtension : public Extension,
+                                public ExtensionInterface<osg::View>,
+                                public ExtensionInterface<Control>
+    {
+    public:
+        META_Object(osgearth_ext_viewpoints, ViewpointsExtension);
+
+        // CTORs
+        ViewpointsExtension();
+        ViewpointsExtension(const ConfigOptions& options);
+
+        // DTOR
+        virtual ~ViewpointsExtension();
+
+
+    public: // Extension
+
+        void setDBOptions(const osgDB::Options* dbOptions);
+
+
+    public: // ExtensionInterface<osg::View>
+
+        bool connect(osg::View* view);
+
+        bool disconnect(osg::View* view);
+
+
+    public: // ExtensionInterface<Control>
+
+        bool connect(Control* control);
+
+        bool disconnect(Control* control);
+
+
+    protected: // Object
+        ViewpointsExtension(const ViewpointsExtension& rhs, const osg::CopyOp& op) { }
+
+    private:
+        osg::ref_ptr<const osgDB::Options>   _dbOptions;
+        std::vector<Viewpoint>               _viewpoints;
+        osg::ref_ptr<osgGA::GUIEventHandler> _handler;
+    };
+
+} } // namespace osgEarth::Viewpoints
+
+#endif // OSGEARTH_VIEWPOINTS_EXTENSION
diff --git a/src/osgEarthExtensions/viewpoints/ViewpointsExtension.cpp b/src/osgEarthExtensions/viewpoints/ViewpointsExtension.cpp
new file mode 100644
index 0000000..3766553
--- /dev/null
+++ b/src/osgEarthExtensions/viewpoints/ViewpointsExtension.cpp
@@ -0,0 +1,242 @@
+/* -*-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.
+ *
+ * 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 "ViewpointsExtension"
+#include <osgEarth/Viewpoint>
+#include <osgEarth/XmlUtils>
+#include <osgEarthUtil/EarthManipulator>
+#include <osgViewer/View>
+
+using namespace osgEarth;
+using namespace osgEarth::Util;
+using namespace osgEarth::Util::Controls;
+using namespace osgEarth::Viewpoints;
+
+#define LC "[ViewpointsExtension] "
+
+
+#define VP_MIN_DURATION      2.0     // minimum fly time.
+#define VP_METERS_PER_SECOND 2500.0  // fly speed
+#define VP_MAX_DURATION      2.0     // maximum fly time.
+
+namespace
+{
+    void flyToViewpoint(EarthManipulator* manip, const Viewpoint& vp)
+    {
+        Viewpoint currentVP = manip->getViewpoint();
+        GeoPoint vp0 = currentVP.focalPoint().get();
+        GeoPoint vp1 = vp.focalPoint().get();
+        double distance = vp0.distanceTo(vp1);
+        double duration = osg::clampBetween(distance / VP_METERS_PER_SECOND, VP_MIN_DURATION, VP_MAX_DURATION);
+        manip->setViewpoint( vp, duration );
+    }
+
+
+    struct ViewpointsHandler : public osgGA::GUIEventHandler
+    {
+        ViewpointsHandler(const std::vector<Viewpoint>& viewpoints)
+            : _viewpoints( viewpoints ) { } 
+
+        bool handle( const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa )
+        {
+            if ( ea.getEventType() == ea.KEYDOWN )
+            {
+                if ( !_viewpoints.empty() )
+                {
+                    int index = (int)ea.getKey() - (int)'1';
+                    if ( index >= 0 && index < (int)_viewpoints.size() )
+                    {
+                        EarthManipulator* manip = getManip(aa);
+                        if ( manip )
+                            flyToViewpoint( manip, _viewpoints[index] );
+                    }
+                }
+                if ( ea.getKey() == 'v' )
+                {
+                    osgViewer::View* view = dynamic_cast<osgViewer::View*>(aa.asView());
+                    if ( view )
+                    {
+                        EarthManipulator* manip = getManip(aa);
+                        if ( manip )
+                        {
+                            XmlDocument xml( manip->getViewpoint().getConfig() );
+                            xml.store( std::cout );
+                            std::cout << std::endl;
+                        }
+                    }
+                }
+                aa.requestRedraw();
+            }
+
+            else if ( ea.getEventType() == ea.FRAME && _flyTo.isSet() )
+            {
+                EarthManipulator* manip = getManip(aa);
+                if ( manip )
+                    flyToViewpoint(manip, *_flyTo);
+                _flyTo.unset();
+            }
+
+            return false;
+        }
+
+        EarthManipulator* getManip(osgGA::GUIActionAdapter& aa)
+        {
+            osgViewer::View* view = dynamic_cast<osgViewer::View*>(aa.asView());
+            return view ? dynamic_cast<EarthManipulator*>(view->getCameraManipulator()) : 0L;
+        }
+
+        std::vector<Viewpoint> _viewpoints;
+        optional<Viewpoint>    _flyTo;
+    };
+
+
+    // flies to a viewpoint in response to control event (click)
+    struct ClickViewpointHandler : public ControlEventHandler
+    {
+        ClickViewpointHandler(const Viewpoint& vp, ViewpointsHandler* handler) :
+            _vp(vp), _handler(handler) { }
+
+        Viewpoint          _vp;
+        ViewpointsHandler* _handler;
+
+        virtual void onClick(Control* control)
+        {
+            _handler->_flyTo = _vp;
+        }
+    };
+
+
+    Control* createViewpointControl(ViewpointsHandler* handler)
+    {
+        Grid* grid = 0L;
+
+        if ( handler->_viewpoints.size() > 0 )
+        {
+            // the viewpoint container:
+            grid = new Grid();
+            grid->setChildSpacing( 0 );
+            grid->setChildVertAlign( Control::ALIGN_CENTER );
+
+            for( unsigned i=0; i<handler->_viewpoints.size(); ++i )
+            {
+                const Viewpoint& vp = handler->_viewpoints[i];
+                Control* num = new LabelControl(Stringify() << (i+1), 16.0f, osg::Vec4f(1,1,0,1));
+                num->setPadding( 4 );
+                grid->setControl( 0, i, num );
+
+                Control* vpc = new LabelControl(vp.name()->empty() ? "<no name>" : vp.name().get(), 16.0f);
+                vpc->setPadding( 4 );
+                vpc->setHorizFill( true );
+                vpc->setActiveColor( osg::Vec4(0.4,0.4,1.0,1.0) ); // blue
+                vpc->addEventHandler( new ClickViewpointHandler(vp, handler) );
+                grid->setControl( 1, i, vpc );
+            }
+        }
+
+        return grid;
+    }
+}
+
+//.........................................................................
+
+
+ViewpointsExtension::ViewpointsExtension()
+{
+    //NOP
+}
+
+ViewpointsExtension::ViewpointsExtension(const ConfigOptions& options)
+{
+    // backwards-compatibility: read viewpoints at the top level???
+    const Config& viewpointsConf = options.getConfig();
+
+    std::vector<Viewpoint> viewpoints;
+
+    const ConfigSet& children = viewpointsConf.children("viewpoint");
+    if ( children.size() > 0 )
+    {
+        for( ConfigSet::const_iterator i = children.begin(); i != children.end(); ++i )
+        {
+
+            viewpoints.push_back( Viewpoint(*i) );
+        }
+    }
+
+    OE_INFO << LC << "Read " << _viewpoints.size() << " viewpoints\n";
+
+    _handler = new ViewpointsHandler(viewpoints);
+}
+
+ViewpointsExtension::~ViewpointsExtension()
+{
+    //nop
+}
+
+void
+ViewpointsExtension::setDBOptions(const osgDB::Options* dbOptions)
+{
+    _dbOptions = dbOptions;
+}
+
+bool
+ViewpointsExtension::connect(osg::View* view)
+{
+    osgViewer::View* v = dynamic_cast<osgViewer::View*>(view);
+    if ( v && _handler.valid() )
+    {
+        v->addEventHandler( _handler.get() );
+    }
+    return true;
+}
+
+bool
+ViewpointsExtension::disconnect(osg::View* view)
+{
+    //TODO: remove the event handler
+    osgViewer::View* v = dynamic_cast<osgViewer::View*>(view);
+    if ( v && _handler.valid() )
+    {
+        v->removeEventHandler( _handler.get() );
+    }
+    return true;
+}
+
+bool
+ViewpointsExtension::connect(Control* control)
+{
+    //TODO add a UI.
+    Container* container = dynamic_cast<Container*>(control);
+    if ( container )
+    {
+        ViewpointsHandler* vh = static_cast<ViewpointsHandler*>(_handler.get());
+        if ( vh->_viewpoints.size() > 0 )
+        {
+            Control* c = createViewpointControl( vh );
+            if ( c )
+                container->addControl( c );
+        }
+    }
+    return true;
+}
+
+bool
+ViewpointsExtension::disconnect(Control* control)
+{
+    // TODO: remove the UI
+    return true;
+}
diff --git a/src/osgEarthExtensions/viewpoints/ViewpointsPlugin.cpp b/src/osgEarthExtensions/viewpoints/ViewpointsPlugin.cpp
new file mode 100644
index 0000000..fcfe110
--- /dev/null
+++ b/src/osgEarthExtensions/viewpoints/ViewpointsPlugin.cpp
@@ -0,0 +1,55 @@
+/* -*-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.
+ *
+ * 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 "ViewpointsExtension"
+
+#include <osgDB/ReaderWriter>
+#include <osgDB/Registry>
+#include <osgDB/FileNameUtils>
+
+namespace osgEarth { namespace Viewpoints
+{
+    /**
+     * Plugin entry point
+     */
+    class ViewpointsPlugin : public osgDB::ReaderWriter
+    {
+    public: // Plugin stuff
+
+        ViewpointsPlugin() {
+            supportsExtension( "osgearth_viewpoints", "osgEarth Viewpoints Extension" );
+        }
+        
+        const char* className() {
+            return "osgEarth Viewpoints Extension";
+        }
+
+        virtual ~ViewpointsPlugin() { }
+
+        ReadResult readObject(const std::string& filename, const osgDB::Options* dbOptions) const
+        {
+          if ( !acceptsExtension(osgDB::getLowerCaseFileExtension(filename)) )
+                return ReadResult::FILE_NOT_HANDLED;
+
+          return ReadResult( new ViewpointsExtension(Extension::getConfigOptions(dbOptions)) );
+        }
+    };
+
+    REGISTER_OSGPLUGIN(osgearth_viewpoints, ViewpointsPlugin)
+
+} } // namespace osgEarth::Viewpoints
diff --git a/src/osgEarthFeatures/AltitudeFilter b/src/osgEarthFeatures/AltitudeFilter
index eb2ad3f..4b5d7af 100644
--- a/src/osgEarthFeatures/AltitudeFilter
+++ b/src/osgEarthFeatures/AltitudeFilter
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthFeatures/AltitudeFilter.cpp b/src/osgEarthFeatures/AltitudeFilter.cpp
index 5bf5eb2..5264e8d 100644
--- a/src/osgEarthFeatures/AltitudeFilter.cpp
+++ b/src/osgEarthFeatures/AltitudeFilter.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -73,6 +73,10 @@ AltitudeFilter::pushAndDontClamp( FeatureList& features, FilterContext& cx )
     if ( _altitude.valid() && _altitude->verticalOffset().isSet() )
         offsetExpr = *_altitude->verticalOffset();
 
+    bool gpuClamping =
+        _altitude.valid() &&
+        _altitude->technique() == _altitude->TECHNIQUE_GPU;
+
     for( FeatureList::iterator i = features.begin(); i != features.end(); ++i )
     {
         Feature* feature = i->get();
@@ -101,8 +105,11 @@ AltitudeFilter::pushAndDontClamp( FeatureList& features, FilterContext& cx )
             Geometry* geom = gi.next();
             for( Geometry::iterator g = geom->begin(); g != geom->end(); ++g )
             {
-                g->z() *= scaleZ;
-                g->z() += offsetZ;
+                if ( !gpuClamping )
+                {
+                    g->z() *= scaleZ;
+                    g->z() += offsetZ;
+                }
 
                 if ( g->z() < minHAT )
                     minHAT = g->z();
@@ -116,6 +123,13 @@ AltitudeFilter::pushAndDontClamp( FeatureList& features, FilterContext& cx )
             feature->set( "__min_hat", minHAT );
             feature->set( "__max_hat", maxHAT );
         }
+
+        // encode the Z offset if
+        if ( gpuClamping )
+        {
+            feature->set("__oe_verticalScale",  scaleZ);
+            feature->set("__oe_verticalOffset", offsetZ);
+        }
     }
 }
 
@@ -135,6 +149,9 @@ 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();
@@ -156,6 +173,8 @@ 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();
diff --git a/src/osgEarthFeatures/BufferFilter b/src/osgEarthFeatures/BufferFilter
index 64317f3..bcbccf9 100644
--- a/src/osgEarthFeatures/BufferFilter
+++ b/src/osgEarthFeatures/BufferFilter
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthFeatures/BufferFilter.cpp b/src/osgEarthFeatures/BufferFilter.cpp
index c5c2179..eef4614 100644
--- a/src/osgEarthFeatures/BufferFilter.cpp
+++ b/src/osgEarthFeatures/BufferFilter.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthFeatures/BuildGeometryFilter b/src/osgEarthFeatures/BuildGeometryFilter
index 377cd36..5c65b9f 100644
--- a/src/osgEarthFeatures/BuildGeometryFilter
+++ b/src/osgEarthFeatures/BuildGeometryFilter
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -99,10 +99,10 @@ namespace osgEarth { namespace Features
             osg::Geometry*          osgGeom,
             const osg::Matrixd      &world2local);
 
-        osg::Geode* processPolygons        (FeatureList& input, const FilterContext& cx);
-        osg::Geode* processLines           (FeatureList& input, const FilterContext& cx);
-        osg::Geode* processPolygonizedLines(FeatureList& input, bool twosided, const FilterContext& cx);
-        osg::Geode* processPoints          (FeatureList& input, const FilterContext& cx);
+        osg::Geode* processPolygons        (FeatureList& input, FilterContext& cx);
+        osg::Geode* processLines           (FeatureList& input, FilterContext& cx);
+        osg::Geode* processPolygonizedLines(FeatureList& input, bool twosided, FilterContext& cx);
+        osg::Geode* processPoints          (FeatureList& input, FilterContext& cx);
     };
 
 } } // namespace osgEarth::Features
diff --git a/src/osgEarthFeatures/BuildGeometryFilter.cpp b/src/osgEarthFeatures/BuildGeometryFilter.cpp
index 2937ae2..fa579f4 100644
--- a/src/osgEarthFeatures/BuildGeometryFilter.cpp
+++ b/src/osgEarthFeatures/BuildGeometryFilter.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -25,10 +25,10 @@
 #include <osgEarthSymbology/LineSymbol>
 #include <osgEarthSymbology/PolygonSymbol>
 #include <osgEarthSymbology/MeshSubdivider>
-#include <osgEarthSymbology/MeshConsolidator>
 #include <osgEarthSymbology/ResourceCache>
 #include <osgEarth/Tessellator>
 #include <osgEarth/Utils>
+#include <osgEarth/Clamping>
 #include <osg/Geode>
 #include <osg/Geometry>
 #include <osg/LineWidth>
@@ -101,7 +101,7 @@ _geoInterp    ( GEOINTERP_RHUMB_LINE )
 }
 
 osg::Geode*
-BuildGeometryFilter::processPolygons(FeatureList& features, const FilterContext& context)
+BuildGeometryFilter::processPolygons(FeatureList& features, FilterContext& context)
 {
     osg::Geode* geode = new osg::Geode();
 
@@ -141,6 +141,8 @@ BuildGeometryFilter::processPolygons(FeatureList& features, const FilterContext&
         {
             Geometry* part = parts.next();
 
+            part->removeDuplicates();
+
             // skip geometry that is invalid for a polygon
             if ( part->size() < 3 )
                 continue;
@@ -149,8 +151,8 @@ BuildGeometryFilter::processPolygons(FeatureList& features, const FilterContext&
             osg::Vec4f primaryColor = poly->fill()->color();
             
             osg::ref_ptr<osg::Geometry> osgGeom = new osg::Geometry();
-            osgGeom->setUseVertexBufferObjects( true );
-            osgGeom->setUseDisplayList( false );
+            //osgGeom->setUseVertexBufferObjects( true );
+            //osgGeom->setUseDisplayList( false );
 
             // are we embedding a feature name?
             if ( _featureNameExpr.isSet() )
@@ -186,7 +188,6 @@ BuildGeometryFilter::processPolygons(FeatureList& features, const FilterContext&
                 // subdivide the mesh if necessary to conform to an ECEF globe:
                 if ( makeECEF )
                 {
-
                     //convert back to world coords
                     for( osg::Vec3Array::iterator i = allPoints->begin(); i != allPoints->end(); ++i )
                     {
@@ -211,8 +212,9 @@ BuildGeometryFilter::processPolygons(FeatureList& features, const FilterContext&
 
                 // assign the primary color array. PER_VERTEX required in order to support
                 // vertex optimization later
+                unsigned count = osgGeom->getVertexArray()->getNumElements();
                 osg::Vec4Array* colors = new osg::Vec4Array;
-                colors->assign( osgGeom->getVertexArray()->getNumElements(), primaryColor );
+                colors->assign( count, primaryColor );
                 osgGeom->setColorArray( colors );
                 osgGeom->setColorBinding( osg::Geometry::BIND_PER_VERTEX );
 
@@ -220,7 +222,14 @@ BuildGeometryFilter::processPolygons(FeatureList& features, const FilterContext&
 
                 // record the geometry's primitive set(s) in the index:
                 if ( context.featureIndex() )
-                    context.featureIndex()->tagPrimitiveSets( osgGeom, input );
+                    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) );
+                }
             }
         }
     }
@@ -230,9 +239,9 @@ BuildGeometryFilter::processPolygons(FeatureList& features, const FilterContext&
 
 
 osg::Geode*
-BuildGeometryFilter::processPolygonizedLines(FeatureList&         features, 
-                                             bool                 twosided,
-                                             const FilterContext& context)
+BuildGeometryFilter::processPolygonizedLines(FeatureList&   features, 
+                                             bool           twosided,
+                                             FilterContext& context)
 {
     osg::Geode* geode = new osg::Geode();
 
@@ -252,7 +261,6 @@ BuildGeometryFilter::processPolygonizedLines(FeatureList&         features,
     for( FeatureList::iterator i = features.begin(); i != features.end(); ++i )
     {
         Feature* input = i->get();
-
         // extract the required line symbol; bail out if not found.
         const LineSymbol* line =
             input->style().isSet() && input->style()->has<LineSymbol>() ? input->style()->get<LineSymbol>() :
@@ -303,7 +311,14 @@ BuildGeometryFilter::processPolygonizedLines(FeatureList&         features,
 
             // record the geometry's primitive set(s) in the index:
             if ( context.featureIndex() )
-                context.featureIndex()->tagPrimitiveSets( geom, input );
+                context.featureIndex()->tagDrawable( geom, input );
+        
+            // install clamping attributes if necessary
+            if (_style.has<AltitudeSymbol>() &&
+                _style.get<AltitudeSymbol>()->technique() == AltitudeSymbol::TECHNIQUE_GPU)
+            {
+                Clamping::applyDefaultClampingAttrs( geom, input->getDouble("__oe_verticalOffset", 0.0) );
+            }
         }
 
         polygonizer.installShaders( geode );
@@ -313,7 +328,7 @@ BuildGeometryFilter::processPolygonizedLines(FeatureList&         features,
 
 
 osg::Geode*
-BuildGeometryFilter::processLines(FeatureList& features, const FilterContext& context)
+BuildGeometryFilter::processLines(FeatureList& features, FilterContext& context)
 {
     osg::Geode* geode = new osg::Geode();
 
@@ -365,8 +380,8 @@ BuildGeometryFilter::processLines(FeatureList& features, const FilterContext& co
             osg::Vec4f primaryColor = line->stroke()->color();
             
             osg::ref_ptr<osg::Geometry> osgGeom = new osg::Geometry();
-            osgGeom->setUseVertexBufferObjects( true );
-            osgGeom->setUseDisplayList( false );
+            //osgGeom->setUseVertexBufferObjects( true );
+            //osgGeom->setUseDisplayList( false );
 
             // embed the feature name if requested. Warning: blocks geometry merge optimization!
             if ( _featureNameExpr.isSet() )
@@ -389,8 +404,10 @@ BuildGeometryFilter::processLines(FeatureList& features, const FilterContext& co
                 applyLineSymbology( osgGeom->getOrCreateStateSet(), line );
             }
             
-            // subdivide the mesh if necessary to conform to an ECEF globe:
-            if ( makeECEF && !line->tessellation().isSetTo(0) )
+            // 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;
@@ -413,7 +430,14 @@ BuildGeometryFilter::processLines(FeatureList& features, const FilterContext& co
 
             // record the geometry's primitive set(s) in the index:
             if ( context.featureIndex() )
-                context.featureIndex()->tagPrimitiveSets( osgGeom, input );
+                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) );
+            }
         }
     }
     
@@ -422,7 +446,7 @@ BuildGeometryFilter::processLines(FeatureList& features, const FilterContext& co
 
 
 osg::Geode*
-BuildGeometryFilter::processPoints(FeatureList& features, const FilterContext& context)
+BuildGeometryFilter::processPoints(FeatureList& features, FilterContext& context)
 {
     osg::Geode* geode = new osg::Geode();
 
@@ -459,8 +483,8 @@ BuildGeometryFilter::processPoints(FeatureList& features, const FilterContext& c
             osg::Vec4f primaryColor = point->fill()->color();
             
             osg::ref_ptr<osg::Geometry> osgGeom = new osg::Geometry();
-            osgGeom->setUseVertexBufferObjects( true );
-            osgGeom->setUseDisplayList( false );
+            //osgGeom->setUseVertexBufferObjects( true );
+            //osgGeom->setUseDisplayList( false );
 
             // embed the feature name if requested. Warning: blocks geometry merge optimization!
             if ( _featureNameExpr.isSet() )
@@ -493,7 +517,14 @@ BuildGeometryFilter::processPoints(FeatureList& features, const FilterContext& c
 
             // record the geometry's primitive set(s) in the index:
             if ( context.featureIndex() )
-                context.featureIndex()->tagPrimitiveSets( osgGeom, input );
+                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) );
+            }
         }
     }
     
@@ -528,6 +559,13 @@ BuildGeometryFilter::tileAndBuildPolygon(Geometry*               ring,
 
         OE_DEBUG << "Found " << count << " points; cropping to " << tx << " x " << ty << std::endl;
 
+        // Get the average Z, since GEOS will set teh Z of new verts to that of the cropping polygon,
+        // which is stupid but that's how it is.
+        double z = 0.0;
+        for(unsigned i=0; i<ring->size(); ++i)
+            z += ring->at(i).z();
+        z /= ring->size();
+
         osg::ref_ptr<Polygon> poly = new Polygon;
         poly->resize( 4 );
 
@@ -536,10 +574,10 @@ BuildGeometryFilter::tileAndBuildPolygon(Geometry*               ring,
         {
             for(int y=0; y<(int)ty; ++y)
             {
-                (*poly)[0].set( b.xMin() + tw*(double)x,     b.yMin() + th*(double)y,     0.0 );
-                (*poly)[1].set( b.xMin() + tw*(double)(x+1), b.yMin() + th*(double)y,     0.0 );
-                (*poly)[2].set( b.xMin() + tw*(double)(x+1), b.yMin() + th*(double)(y+1), 0.0 );
-                (*poly)[3].set( b.xMin() + tw*(double)x,     b.yMin() + th*(double)(y+1), 0.0 );
+                (*poly)[0].set( b.xMin() + tw*(double)x,     b.yMin() + th*(double)y,     z );
+                (*poly)[1].set( b.xMin() + tw*(double)(x+1), b.yMin() + th*(double)y,     z );
+                (*poly)[2].set( b.xMin() + tw*(double)(x+1), b.yMin() + th*(double)(y+1), z );
+                (*poly)[3].set( b.xMin() + tw*(double)x,     b.yMin() + th*(double)(y+1), z );
                 
                 osg::ref_ptr<Geometry> ringTile;
                 if ( ring->crop(poly.get(), ringTile) )
@@ -596,7 +634,9 @@ BuildGeometryFilter::tileAndBuildPolygon(Geometry*               ring,
             tess.setWindingType( osgUtil::Tessellator::TESS_WINDING_POSITIVE );
             tess.retessellatePolygons( *osgGeom );
         }
-    }
+    }    
+    
+    osgUtil::SmoothingVisitor::smooth( *osgGeom );
 
 #else
 
@@ -894,15 +934,13 @@ BuildGeometryFilter::push( FeatureList& input, FilterContext& context )
         osg::ref_ptr<osg::Geode> geode = processPolygons(polygons, context);
         if ( geode->getNumDrawables() > 0 )
         {
-            if ( !context.featureIndex() )
-            {
-                osgUtil::Optimizer o;
-                o.optimize( geode.get(), 
-                    osgUtil::Optimizer::MERGE_GEOMETRY |
-                    osgUtil::Optimizer::VERTEX_PRETRANSFORM |
-                    osgUtil::Optimizer::INDEX_MESH |
-                    osgUtil::Optimizer::VERTEX_POSTTRANSFORM );
-            }
+            osgUtil::Optimizer o;
+            o.optimize( geode.get(), 
+                osgUtil::Optimizer::MERGE_GEOMETRY |
+                osgUtil::Optimizer::INDEX_MESH |
+                osgUtil::Optimizer::VERTEX_PRETRANSFORM |
+                osgUtil::Optimizer::VERTEX_POSTTRANSFORM );
+
             result->addChild( geode.get() );
         }
     }
@@ -914,15 +952,13 @@ BuildGeometryFilter::push( FeatureList& input, FilterContext& context )
         osg::ref_ptr<osg::Geode> geode = processPolygonizedLines(polygonizedLines, twosided, context);
         if ( geode->getNumDrawables() > 0 )
         {
-            if ( !context.featureIndex() )
-            {
-                osgUtil::Optimizer o;
-                o.optimize( geode.get(), 
-                    osgUtil::Optimizer::MERGE_GEOMETRY |
-                    osgUtil::Optimizer::VERTEX_PRETRANSFORM |
-                    osgUtil::Optimizer::INDEX_MESH |
-                    osgUtil::Optimizer::VERTEX_POSTTRANSFORM );
-            }
+            osgUtil::Optimizer o;
+            o.optimize( geode.get(), 
+                osgUtil::Optimizer::MERGE_GEOMETRY |
+                osgUtil::Optimizer::INDEX_MESH |
+                osgUtil::Optimizer::VERTEX_PRETRANSFORM |
+                osgUtil::Optimizer::VERTEX_POSTTRANSFORM );
+
             result->addChild( geode.get() );
         }
     }
@@ -933,12 +969,10 @@ BuildGeometryFilter::push( FeatureList& input, FilterContext& context )
         osg::ref_ptr<osg::Geode> geode = processLines(lines, context);
         if ( geode->getNumDrawables() > 0 )
         {
-            if ( !context.featureIndex() )
-            {
-                osgUtil::Optimizer o;
-                o.optimize( geode.get(), 
-                    osgUtil::Optimizer::MERGE_GEOMETRY );
-            }
+            osgUtil::Optimizer o;
+            o.optimize( geode.get(), 
+                osgUtil::Optimizer::MERGE_GEOMETRY );
+
             applyLineSymbology( geode->getOrCreateStateSet(), line );
             result->addChild( geode.get() );
         }
@@ -950,17 +984,27 @@ BuildGeometryFilter::push( FeatureList& input, FilterContext& context )
         osg::ref_ptr<osg::Geode> geode = processPoints(points, context);
         if ( geode->getNumDrawables() > 0 )
         {
-            if ( !context.featureIndex() )
-            {
-                osgUtil::Optimizer o;
-                o.optimize( geode.get(), 
-                    osgUtil::Optimizer::MERGE_GEOMETRY );
-            }
+            osgUtil::Optimizer o;
+            o.optimize( geode.get(), 
+                osgUtil::Optimizer::MERGE_GEOMETRY );
+
             applyPointSymbology( geode->getOrCreateStateSet(), point );
             result->addChild( geode.get() );
         }
     }
 
+    // indicate that geometry contains clamping attributes
+    if (_style.has<AltitudeSymbol>() &&
+        _style.get<AltitudeSymbol>()->technique() == AltitudeSymbol::TECHNIQUE_GPU)
+    {
+        Clamping::installHasAttrsUniform( result->getOrCreateStateSet() );
+    }    
+
+    // Prepare buffer objects.
+    AllocateAndMergeBufferObjectsVisitor allocAndMerge;
+    result->accept( allocAndMerge );
+
+
     if ( result->getNumChildren() > 0 )
     {
         // apply the delocalization matrix for no-jitter
diff --git a/src/osgEarthFeatures/BuildTextFilter b/src/osgEarthFeatures/BuildTextFilter
index 99096ef..0389a33 100644
--- a/src/osgEarthFeatures/BuildTextFilter
+++ b/src/osgEarthFeatures/BuildTextFilter
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthFeatures/BuildTextFilter.cpp b/src/osgEarthFeatures/BuildTextFilter.cpp
index 50de374..8d2db19 100644
--- a/src/osgEarthFeatures/BuildTextFilter.cpp
+++ b/src/osgEarthFeatures/BuildTextFilter.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthFeatures/BuildTextOperator b/src/osgEarthFeatures/BuildTextOperator
index 3a954e6..602796e 100644
--- a/src/osgEarthFeatures/BuildTextOperator
+++ b/src/osgEarthFeatures/BuildTextOperator
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/osgEarthFeatures/BuildTextOperator.cpp b/src/osgEarthFeatures/BuildTextOperator.cpp
index b7372b2..9faca1f 100644
--- a/src/osgEarthFeatures/BuildTextOperator.cpp
+++ b/src/osgEarthFeatures/BuildTextOperator.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthFeatures/CMakeLists.txt b/src/osgEarthFeatures/CMakeLists.txt
index b84652a..c3868e0 100644
--- a/src/osgEarthFeatures/CMakeLists.txt
+++ b/src/osgEarthFeatures/CMakeLists.txt
@@ -26,6 +26,7 @@ SET(LIB_PUBLIC_HEADERS
     FeatureCursor
     FeatureDisplayLayout
     FeatureDrawSet
+    FeatureIndex
     FeatureListSource
     FeatureModelGraph
     FeatureModelSource
diff --git a/src/osgEarthFeatures/CentroidFilter b/src/osgEarthFeatures/CentroidFilter
index 3f31f34..536b99e 100644
--- a/src/osgEarthFeatures/CentroidFilter
+++ b/src/osgEarthFeatures/CentroidFilter
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthFeatures/CentroidFilter.cpp b/src/osgEarthFeatures/CentroidFilter.cpp
index d2bb9b6..1695038 100644
--- a/src/osgEarthFeatures/CentroidFilter.cpp
+++ b/src/osgEarthFeatures/CentroidFilter.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthFeatures/Common b/src/osgEarthFeatures/Common
index 7820cb9..e4ded2f 100644
--- a/src/osgEarthFeatures/Common
+++ b/src/osgEarthFeatures/Common
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthFeatures/ConvertTypeFilter b/src/osgEarthFeatures/ConvertTypeFilter
index 03efeb6..327aec2 100644
--- a/src/osgEarthFeatures/ConvertTypeFilter
+++ b/src/osgEarthFeatures/ConvertTypeFilter
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthFeatures/ConvertTypeFilter.cpp b/src/osgEarthFeatures/ConvertTypeFilter.cpp
index 6853a64..def3597 100644
--- a/src/osgEarthFeatures/ConvertTypeFilter.cpp
+++ b/src/osgEarthFeatures/ConvertTypeFilter.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthFeatures/CropFilter b/src/osgEarthFeatures/CropFilter
index ce51978..0da8958 100644
--- a/src/osgEarthFeatures/CropFilter
+++ b/src/osgEarthFeatures/CropFilter
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthFeatures/CropFilter.cpp b/src/osgEarthFeatures/CropFilter.cpp
index 7d1658b..dc937ea 100644
--- a/src/osgEarthFeatures/CropFilter.cpp
+++ b/src/osgEarthFeatures/CropFilter.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -62,6 +62,7 @@ CropFilter::push( FeatureList& input, FilterContext& context )
                     {
                         keepFeature = true;
                         newExtent.expandToInclude( bounds.xMin(), bounds.yMin() );
+                        newExtent.expandToInclude( bounds.xMax(), bounds.yMax() );
                     }
                 }
             }
diff --git a/src/osgEarthFeatures/ExtrudeGeometryFilter b/src/osgEarthFeatures/ExtrudeGeometryFilter
index 98cd059..13617ea 100644
--- a/src/osgEarthFeatures/ExtrudeGeometryFilter
+++ b/src/osgEarthFeatures/ExtrudeGeometryFilter
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -108,6 +108,7 @@ namespace osgEarth { namespace Features
             float      wallTexHeightAdjusted;
             bool       isFromSource;
             float      cosAngle;
+            float      height;
         };
         typedef std::list<Corner> Corners; // use a list to prevent iterator invalidation
 
@@ -137,6 +138,8 @@ namespace osgEarth { namespace Features
         {
             Elevations elevations;
             bool       isPolygon;
+            osg::Vec3d baseCentroid;
+            float      verticalOffset;
 
             unsigned getNumPoints() const {
                 unsigned c = 0;
@@ -159,7 +162,6 @@ namespace osgEarth { namespace Features
         float                          _cosWallAngleThresh;
         StringExpression               _featureNameExpr;
         osg::ref_ptr<HeightCallback>   _heightCallback;
-        optional<NumericExpression>    _heightOffsetExpr;
         optional<NumericExpression>    _heightExpr;
         bool                           _makeStencilVolume;
         optional<bool>                 _useVertexBufferObjects;
@@ -167,6 +169,7 @@ namespace osgEarth { namespace Features
         Style                          _style;
         bool                           _styleDirty;
         optional<bool>                 _useTextureArrays;
+        bool                           _gpuClamping;
 
         osg::ref_ptr<const ExtrusionSymbol> _extrusionSymbol;
         osg::ref_ptr<const SkinSymbol>      _wallSkinSymbol;
@@ -180,11 +183,11 @@ namespace osgEarth { namespace Features
         void reset( const FilterContext& context );
         
         void addDrawable( 
-            osg::Drawable*      drawable, 
-            osg::StateSet*      stateSet, 
-            const std::string&  name,
-            Feature*            feature,
-            FeatureSourceIndex* index);
+            osg::Drawable*       drawable, 
+            osg::StateSet*       stateSet, 
+            const std::string&   name,
+            Feature*             feature,
+            FeatureIndexBuilder* index);
         
         bool process( 
             FeatureList&     input,
@@ -192,8 +195,8 @@ namespace osgEarth { namespace Features
         
         bool buildStructure(const Geometry*         input,
                             double                  height,
-                            double                  heightOffset,
                             bool                    flatten,
+                            float                   verticalOffset,
                             const SkinResource*     wallSkin,
                             const SkinResource*     roofSkin,
                             Structure&              out_structure,
@@ -214,27 +217,6 @@ namespace osgEarth { namespace Features
                                   osg::Geometry*    outline,
                                   const osg::Vec4&  outlineColor,
                                   float             minCreaseAngleDeg);
-
-#if 0
-        bool extrudeGeometry(
-            const Geometry*      input,
-            double               height,
-            double               offset,
-            bool                 uniformHeight,
-            osg::Geometry*       walls,
-            osg::Geometry*       top_cap,
-            osg::Geometry*       bottom_cap,
-            osg::Geometry*       outline,
-            const osg::Vec4&     wallColor,
-            const osg::Vec4&     wallBaseColor,
-            const osg::Vec4&     roofColor,
-            const osg::Vec4&     outlineColor,
-            const SkinResource*  wallSkin,
-            int wallLayer,
-            const SkinResource*  roofSkin,
-            int roofLayer,
-            FilterContext&       cx );
-#endif
     };
 
 } } // namespace osgEarth::Features
diff --git a/src/osgEarthFeatures/ExtrudeGeometryFilter.cpp b/src/osgEarthFeatures/ExtrudeGeometryFilter.cpp
index fa26581..53e5499 100644
--- a/src/osgEarthFeatures/ExtrudeGeometryFilter.cpp
+++ b/src/osgEarthFeatures/ExtrudeGeometryFilter.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -19,20 +19,19 @@
 #include <osgEarthFeatures/ExtrudeGeometryFilter>
 #include <osgEarthFeatures/Session>
 #include <osgEarthFeatures/FeatureSourceIndexNode>
-#include <osgEarthSymbology/MeshSubdivider>
-#include <osgEarthSymbology/MeshConsolidator>
 #include <osgEarthSymbology/ResourceCache>
 #include <osgEarth/ECEF>
 #include <osgEarth/ImageUtils>
-#include <osgEarth/Registry>
-#include <osgEarth/Capabilities>
+#include <osgEarth/Clamping>
 #include <osgEarth/Utils>
+#include <osgEarth/Tessellator>
 #include <osg/Geode>
 #include <osg/Geometry>
 #include <osg/MatrixTransform>
 #include <osgUtil/Tessellator>
 #include <osgUtil/Optimizer>
 #include <osgUtil/SmoothingVisitor>
+#include <osgUtil/Simplifier>
 #include <osg/LineWidth>
 #include <osg/PolygonOffset>
 
@@ -71,16 +70,19 @@ namespace
     }
 }
 
+#define AS_VEC4(V3, X) osg::Vec4f( (V3).x(), (V3).y(), (V3).z(), X )
+
 //------------------------------------------------------------------------
 
 ExtrudeGeometryFilter::ExtrudeGeometryFilter() :
-_maxAngle_deg       ( 5.0 ),
-_mergeGeometry      ( true ),
-_wallAngleThresh_deg( 60.0 ),
-_styleDirty         ( true ),
-_makeStencilVolume  ( false ),
+_maxAngle_deg          ( 5.0 ),
+_mergeGeometry         ( true ),
+_wallAngleThresh_deg   ( 60.0 ),
+_styleDirty            ( true ),
+_makeStencilVolume     ( false ),
 _useVertexBufferObjects( true ),
-_useTextureArrays( true )
+_useTextureArrays      ( true ),
+_gpuClamping           ( false )
 {
     //NOP
 }
@@ -109,6 +111,8 @@ ExtrudeGeometryFilter::reset( const FilterContext& context )
         _extrusionSymbol   = 0L;
         _outlineSymbol     = 0L;
 
+        _gpuClamping = false;
+
         _extrusionSymbol = _style.get<ExtrusionSymbol>();
         if ( _extrusionSymbol.valid() )
         {
@@ -130,6 +134,12 @@ ExtrudeGeometryFilter::reset( const FilterContext& context )
                     _heightExpr = NumericExpression( "0-[__max_hat]" );
                 }
             }
+
+            // cache the GPU Clamping directive:
+            if ( alt && alt->technique() == AltitudeSymbol::TECHNIQUE_GPU )
+            {
+                _gpuClamping = true;
+            }
             
             // attempt to extract the wall symbols:
             if ( _extrusionSymbol->wallStyleName().isSet() && sheet != 0L )
@@ -184,8 +194,8 @@ ExtrudeGeometryFilter::reset( const FilterContext& context )
 bool
 ExtrudeGeometryFilter::buildStructure(const Geometry*         input,
                                       double                  height,
-                                      double                  heightOffset,
                                       bool                    flatten,
+                                      float                   verticalOffset,
                                       const SkinResource*     wallSkin,
                                       const SkinResource*     roofSkin,
                                       Structure&              structure,
@@ -205,6 +215,9 @@ ExtrudeGeometryFilter::buildStructure(const Geometry*         input,
     // whether this is a closed polygon structure.
     structure.isPolygon = (input->getComponentType() == Geometry::TYPE_POLYGON);
 
+    // store the vert offset for later encoding
+    structure.verticalOffset = verticalOffset;
+
     // extrusion working variables
     double     targetLen = -DBL_MAX;
     osg::Vec3d minLoc(DBL_MAX, DBL_MAX, DBL_MAX);
@@ -236,9 +249,13 @@ ExtrudeGeometryFilter::buildStructure(const Geometry*         input,
         }
     }
 
+    osg::Vec2d c = input->getBounds().center2d();
+    osg::Vec3d centroid(c.x(), c.y(), minLoc.z());
+    transformAndLocalize(centroid, srs, structure.baseCentroid, mapSRS, _world2local, makeECEF );
+
     // apply the height offsets
-    height    -= heightOffset;
-    targetLen -= heightOffset;
+    //height    -= heightOffset;
+    //targetLen -= heightOffset;
     
     float   roofRotation  = 0.0f;
     Bounds  roofBounds;
@@ -255,7 +272,10 @@ ExtrudeGeometryFilter::buildStructure(const Geometry*         input,
         if ( srs && srs->isGeographic() )
         {
             osg::Vec2d geogCenter = roofBounds.center2d();
-            roofProjSRS = srs->createUTMFromLonLat( Angle(geogCenter.x()), Angle(geogCenter.y()) );
+
+            // This sometimes fails with the aerodrom stuff. No idea why -gw.
+            //roofProjSRS = srs->createUTMFromLonLat( Angle(geogCenter.x()), Angle(geogCenter.y()) );
+            roofProjSRS = SpatialReference::create("spherical-mercator");
             if ( roofProjSRS.valid() )
             {
                 roofBounds.transform( srs, roofProjSRS.get() );
@@ -355,10 +375,13 @@ ExtrudeGeometryFilter::buildStructure(const Geometry*         input,
                 corner->roofTexU = (cosR*xr - sinR*yr) / roofTexSpanX;
                 corner->roofTexV = (sinR*xr + cosR*yr) / roofTexSpanY;
             }
-            
+
             // transform into target SRS.
             transformAndLocalize( corner->base, srs, corner->base, mapSRS, _world2local, makeECEF );
             transformAndLocalize( corner->roof, srs, corner->roof, mapSRS, _world2local, makeECEF );
+
+            // cache the length for later use.
+            corner->height = (corner->roof - corner->base).length();
         }
 
         // Step 2 - Insert intermediate Corners as needed to satify texturing
@@ -410,6 +433,7 @@ ExtrudeGeometryFilter::buildStructure(const Geometry*         input,
                     double advance = nextTexBoundary-cornerOffset;
                     new_corner->base = this_corner->base + base_vec*advance;
                     new_corner->roof = this_corner->roof + roof_vec*advance;
+                    new_corner->height = (new_corner->roof - new_corner->base).length();
                     new_corner->offsetX = cornerOffset + advance;
                     nextTexBoundary += texWidthM;
 
@@ -521,15 +545,29 @@ ExtrudeGeometryFilter::buildWallGeometry(const Structure&     structure,
     osg::Vec4Array* colors = 0L;
     if ( useColor )
     {
-        // per-vertex colors are necessary if we are going to use the MeshConsolidator -gw
         colors = new osg::Vec4Array( numWallVerts );
         walls->setColorArray( colors );
         walls->setColorBinding( osg::Geometry::BIND_PER_VERTEX );
     }
 
+    osg::Vec4Array* anchors = 0L;
+    
+    // If GPU clamping is in effect, create clamping attributes.
+    if ( _gpuClamping )
+    {
+        anchors = new osg::Vec4Array( numWallVerts );
+        walls->setVertexAttribArray    ( Clamping::AnchorAttrLocation, anchors );
+        walls->setVertexAttribBinding  ( Clamping::AnchorAttrLocation, osg::Geometry::BIND_PER_VERTEX );
+        walls->setVertexAttribNormalize( Clamping::AnchorAttrLocation, false );
+    }
+
     unsigned vertptr = 0;
     bool     tex_repeats_y = wallSkin && wallSkin->isTiled() == true;
 
+    bool flatten =
+        _style.has<ExtrusionSymbol>() &&
+        _style.get<ExtrusionSymbol>()->flatten() == true;
+
     for(Elevations::const_iterator elev = structure.elevations.begin(); elev != structure.elevations.end(); ++elev)
     {
         osg::DrawElements* de = 
@@ -551,34 +589,38 @@ ExtrudeGeometryFilter::buildWallGeometry(const Structure&     structure,
             (*verts)[vertptr+3] = f->right.base;
             (*verts)[vertptr+4] = f->right.roof;
             (*verts)[vertptr+5] = f->left.roof;
+            
+            if ( anchors )
+            {
+                float x = structure.baseCentroid.x(), y = structure.baseCentroid.y(), vo = structure.verticalOffset;
+
+                (*anchors)[vertptr+1].set( x, y, vo, Clamping::ClampToGround );
+                (*anchors)[vertptr+2].set( x, y, vo, Clamping::ClampToGround );
+                (*anchors)[vertptr+3].set( x, y, vo, Clamping::ClampToGround );
+
+                if ( flatten )
+                {
+                    (*anchors)[vertptr+0].set( x, y, vo, Clamping::ClampToAnchor );
+                    (*anchors)[vertptr+4].set( x, y, vo, Clamping::ClampToAnchor );
+                    (*anchors)[vertptr+5].set( x, y, vo, Clamping::ClampToAnchor );
+                }
+                else
+                {                    
+                    (*anchors)[vertptr+0].set( x, y, vo + f->left.height,  Clamping::ClampToGround );
+                    (*anchors)[vertptr+4].set( x, y, vo + f->right.height, Clamping::ClampToGround );
+                    (*anchors)[vertptr+5].set( x, y, vo + f->left.height,  Clamping::ClampToGround );
+                }
+            }
 
             // Assign wall polygon colors.
             if (useColor)
             {
-#if 0
-                // experimental: apply some ambient occlusion to tight inside corners                
-                float bL = f->left.cosAngle > 0.0 ? 1.0 : (1.0+f->left.cosAngle);
-                float bR = f->right.cosAngle > 0.0 ? 1.0 : (1.0+f->right.cosAngle);
-
-                osg::Vec4f leftColor      = Color(wallColor).brightness(bL);
-                osg::Vec4f leftBaseColor  = Color(wallBaseColor).brightness(bL);
-                osg::Vec4f rightColor     = Color(wallColor).brightness(bR);
-                osg::Vec4f rightBaseColor = Color(wallBaseColor).brightness(bR);
-
-                (*colors)[vertptr+0] = leftColor;
-                (*colors)[vertptr+1] = leftBaseColor;
-                (*colors)[vertptr+2] = rightBaseColor;
-                (*colors)[vertptr+3] = rightBaseColor;
-                (*colors)[vertptr+4] = rightColor;
-                (*colors)[vertptr+5] = leftColor;
-#else
                 (*colors)[vertptr+0] = wallColor;
                 (*colors)[vertptr+1] = wallBaseColor;
                 (*colors)[vertptr+2] = wallBaseColor;
                 (*colors)[vertptr+3] = wallBaseColor;
                 (*colors)[vertptr+4] = wallColor;
                 (*colors)[vertptr+5] = wallColor;
-#endif
             }
 
             // Calculate texture coordinates:
@@ -617,7 +659,9 @@ ExtrudeGeometryFilter::buildWallGeometry(const Structure&     structure,
             }
 
             for(int i=0; i<6; ++i)
+            {
                 de->addElement( vertptr+i );
+            }
         }
     }
     
@@ -654,6 +698,20 @@ ExtrudeGeometryFilter::buildRoofGeometry(const Structure&     structure,
         roof->setTexCoordArray(0, tex);
     }
 
+    osg::Vec4Array* anchors = 0L;    
+    if ( _gpuClamping )
+    {
+        // fake out the OSG tessellator. It does not preserve attrib arrays in the Tessellator.
+        // so we will put them in one of the texture arrays and copy them to an attrib array 
+        // after tessellation. #osghack
+        anchors = new osg::Vec4Array();
+        roof->setTexCoordArray(1, anchors);
+    }
+
+    bool flatten =
+        _style.has<ExtrusionSymbol>() &&
+        _style.get<ExtrusionSymbol>()->flatten() == true;
+
     // Create a series of line loops that the tessellator can reorganize
     // into polygons.
     unsigned vertptr = 0;
@@ -669,27 +727,66 @@ ExtrudeGeometryFilter::buildRoofGeometry(const Structure&     structure,
             {
                 verts->push_back( f->left.roof );
                 color->push_back( roofColor );
+
                 if ( tex )
                 {
                     tex->push_back( osg::Vec3f(f->left.roofTexU, f->left.roofTexV, (float)0.0f) );
                 }
+
+                if ( anchors )
+                {
+                    float 
+                        x = structure.baseCentroid.x(),
+                        y = structure.baseCentroid.y(), 
+                        vo = structure.verticalOffset;
+
+                    if ( flatten )
+                    {
+                        anchors->push_back( osg::Vec4f(x, y, vo, Clamping::ClampToAnchor) );
+                    }
+                    else
+                    {
+                        anchors->push_back( osg::Vec4f(x, y, vo + f->left.height, Clamping::ClampToGround) );
+                    }
+                }
                 ++vertptr;
             }
         }
         roof->addPrimitiveSet( new osg::DrawArrays(GL_LINE_LOOP, elevptr, vertptr-elevptr) );
-    }
-    
+    } 
 
     osg::Vec3Array* normal = new osg::Vec3Array(verts->size());
     roof->setNormalArray( normal );
     roof->setNormalBinding( osg::Geometry::BIND_PER_VERTEX );
     normal->assign( verts->size(), osg::Vec3(0,0,1) );
 
+    int v = verts->size();
+
     // Tessellate the roof lines into polygons.
-    osgUtil::Tessellator tess;
-    tess.setTessellationType( osgUtil::Tessellator::TESS_TYPE_GEOMETRY );
-    tess.setWindingType( osgUtil::Tessellator::TESS_WINDING_ODD );
-    tess.retessellatePolygons( *roof );
+    osgEarth::Tessellator oeTess;
+    if (!oeTess.tessellateGeometry(*roof))
+    {
+        //fallback to osg tessellator
+        OE_DEBUG << LC << "Falling back on OSG tessellator (" << roof->getName() << ")" << std::endl;
+
+        osgUtil::Tessellator tess;
+        tess.setTessellationType( osgUtil::Tessellator::TESS_TYPE_GEOMETRY );
+        tess.setWindingType( osgUtil::Tessellator::TESS_WINDING_ODD );
+        tess.retessellatePolygons( *roof );
+    }
+
+    // Move the anchors to the correct place. :)
+    if ( _gpuClamping )
+    {
+        osg::Vec4Array* a = static_cast<osg::Vec4Array*>(roof->getTexCoordArray(1));
+        if ( a )
+        {
+            roof->setVertexAttribArray    ( Clamping::AnchorAttrLocation, a );
+            roof->setVertexAttribBinding  ( Clamping::AnchorAttrLocation, osg::Geometry::BIND_PER_VERTEX );
+            roof->setVertexAttribNormalize( Clamping::AnchorAttrLocation, false );
+            roof->setTexCoordArray(1, 0L);
+        }
+    }
 
     return true;
 }
@@ -714,6 +811,24 @@ ExtrudeGeometryFilter::buildOutlineGeometry(const Structure&  structure,
 
     osg::DrawElements* de = new osg::DrawElementsUInt(GL_LINES);
     outline->addPrimitiveSet(de);
+        
+    osg::Vec4Array* anchors = 0L;
+    if ( _gpuClamping )
+    {
+        anchors = new osg::Vec4Array();
+        outline->setVertexAttribArray    ( Clamping::AnchorAttrLocation, anchors );
+        outline->setVertexAttribBinding  ( Clamping::AnchorAttrLocation, osg::Geometry::BIND_PER_VERTEX );
+        outline->setVertexAttribNormalize( Clamping::AnchorAttrLocation, false );
+    }
+
+    bool flatten =
+        _style.has<ExtrusionSymbol>() &&
+        _style.get<ExtrusionSymbol>()->flatten() == true;
+    
+    float
+        x  = structure.baseCentroid.x(),
+        y  = structure.baseCentroid.y(),
+        vo = structure.verticalOffset;
 
     unsigned vertptr = 0;
     for(Elevations::const_iterator e = structure.elevations.begin(); e != structure.elevations.end(); ++e)
@@ -737,11 +852,14 @@ ExtrudeGeometryFilter::buildOutlineGeometry(const Structure&  structure,
             if ( drawPost || drawCrossbar )
             {
                 verts->push_back( f->left.roof );
+                if ( anchors && flatten  ) anchors->push_back(osg::Vec4f(x, y, vo, Clamping::ClampToAnchor));
+                if ( anchors && !flatten ) anchors->push_back(osg::Vec4f(x, y, vo + f->left.height, Clamping::ClampToGround));
             }
 
             if ( drawPost )
             {
                 verts->push_back( f->left.base );
+                if ( anchors ) anchors->push_back( osg::Vec4f(x, y, vo, Clamping::ClampToGround) );
                 de->addElement(vertptr);
                 de->addElement(verts->size()-1);
             }
@@ -749,7 +867,8 @@ ExtrudeGeometryFilter::buildOutlineGeometry(const Structure&  structure,
             if ( drawCrossbar )
             {
                 verts->push_back( f->right.roof );
-
+                if ( anchors && flatten  ) anchors->push_back(osg::Vec4f(x, y, vo, Clamping::ClampToAnchor));
+                if ( anchors && !flatten ) anchors->push_back(osg::Vec4f(x, y, vo + f->right.height, Clamping::ClampToGround));
                 de->addElement(vertptr);
                 de->addElement(verts->size()-1);
             }
@@ -764,8 +883,11 @@ ExtrudeGeometryFilter::buildOutlineGeometry(const Structure&  structure,
         {
             Faces::const_iterator last = e->faces.end()-1;
             verts->push_back( last->right.roof );
+            if ( anchors && flatten  ) anchors->push_back(osg::Vec4f(x, y, vo, Clamping::ClampToAnchor));
+            if ( anchors && !flatten ) anchors->push_back(osg::Vec4f(x, y, vo + last->right.height, Clamping::ClampToGround));
             de->addElement( verts->size()-1 );
             verts->push_back( last->right.base );
+            if ( anchors ) anchors->push_back( osg::Vec4f(x, y, vo, Clamping::ClampToGround));
             de->addElement( verts->size()-1 );
         }
     }
@@ -774,11 +896,11 @@ ExtrudeGeometryFilter::buildOutlineGeometry(const Structure&  structure,
 }
 
 void
-ExtrudeGeometryFilter::addDrawable(osg::Drawable*      drawable,
-                                   osg::StateSet*      stateSet,
-                                   const std::string&  name,
-                                   Feature*            feature,
-                                   FeatureSourceIndex* index )
+ExtrudeGeometryFilter::addDrawable(osg::Drawable*       drawable,
+                                   osg::StateSet*       stateSet,
+                                   const std::string&   name,
+                                   Feature*             feature,
+                                   FeatureIndexBuilder* index )
 {
     // find the geode for the active stateset, creating a new one if necessary. NULL is a 
     // valid key as well.
@@ -799,7 +921,7 @@ ExtrudeGeometryFilter::addDrawable(osg::Drawable*      drawable,
 
     if ( index )
     {
-        index->tagPrimitiveSets( drawable, feature );
+        index->tagDrawable( drawable, feature );
     }
 }
 
@@ -828,7 +950,7 @@ ExtrudeGeometryFilter::process( FeatureList& features, FilterContext& context )
             Geometry* part = iter.next();
 
             osg::ref_ptr<osg::Geometry> walls = new osg::Geometry();
-            walls->setUseVertexBufferObjects( _useVertexBufferObjects.get() );
+            //walls->setUseVertexBufferObjects( _useVertexBufferObjects.get() );
             
             osg::ref_ptr<osg::Geometry> rooflines = 0L;
             osg::ref_ptr<osg::Geometry> baselines = 0L;
@@ -837,7 +959,6 @@ ExtrudeGeometryFilter::process( FeatureList& features, FilterContext& context )
             if ( part->getType() == Geometry::TYPE_POLYGON )
             {
                 rooflines = new osg::Geometry();
-                rooflines->setUseVertexBufferObjects( _useVertexBufferObjects.get() );
 
                 // prep the shapes by making sure all polys are open:
                 static_cast<Polygon*>(part)->open();
@@ -847,14 +968,12 @@ ExtrudeGeometryFilter::process( FeatureList& features, FilterContext& context )
             if ( _outlineSymbol != 0L )
             {
                 outlines = new osg::Geometry();
-                outlines->setUseVertexBufferObjects( _useVertexBufferObjects.get() );
             }
 
             // make a base cap if we're doing stencil volumes.
             if ( _makeStencilVolume )
             {
                 baselines = new osg::Geometry();
-                baselines->setUseVertexBufferObjects( _useVertexBufferObjects.get() );
             }
 
             // calculate the extrusion height:
@@ -873,13 +992,6 @@ ExtrudeGeometryFilter::process( FeatureList& features, FilterContext& context )
                 height = *_extrusionSymbol->height();
             }
 
-            // calculate the height offset from the base:
-            float offset = 0.0;
-            if ( _heightOffsetExpr.isSet() )
-            {
-                offset = input->eval( _heightOffsetExpr.mutable_value(), &context );
-            }
-
             osg::ref_ptr<osg::StateSet> wallStateSet;
             osg::ref_ptr<osg::StateSet> roofStateSet;
 
@@ -890,7 +1002,7 @@ ExtrudeGeometryFilter::process( FeatureList& features, FilterContext& context )
                 if ( _wallResLib.valid() )
                 {
                     SkinSymbol querySymbol( *_wallSkinSymbol.get() );
-                    querySymbol.objectHeight() = fabs(height) - offset;
+                    querySymbol.objectHeight() = fabs(height);
                     wallSkin = _wallResLib->getSkin( &querySymbol, wallSkinPRNG, context.getDBOptions() );
                 }
 
@@ -916,14 +1028,16 @@ ExtrudeGeometryFilter::process( FeatureList& features, FilterContext& context )
                 }
             }
 
+            float verticalOffset = (float)input->getDouble("__oe_verticalOffset", 0.0);
+
             // Build the data model for the structure.
             Structure structure;
 
             buildStructure(
                 part, 
-                height, 
-                offset, 
+                height,
                 _extrusionSymbol->flatten().get(),
+                verticalOffset,
                 wallSkin,
                 roofSkin,
                 structure,
@@ -1001,24 +1115,24 @@ ExtrudeGeometryFilter::process( FeatureList& features, FilterContext& context )
             if ( !_featureNameExpr.empty() )
                 name = input->eval( _featureNameExpr, &context );
 
-            FeatureSourceIndex* index = context.featureIndex();
+            FeatureIndexBuilder* index = context.featureIndex();
 
-            if ( walls.valid() )
+            if ( walls.valid() && walls->getVertexArray() && walls->getVertexArray()->getNumElements() > 0 )
             {
                 addDrawable( walls.get(), wallStateSet.get(), name, input, index );
             }
 
-            if ( rooflines.valid() )
+            if ( rooflines.valid() && rooflines->getVertexArray() && rooflines->getVertexArray()->getNumElements() > 0 )
             {
                 addDrawable( rooflines.get(), roofStateSet.get(), name, input, index );
             }
 
-            if ( baselines.valid() )
+            if ( baselines.valid() && baselines->getVertexArray() && baselines->getVertexArray()->getNumElements() > 0 )
             {
                 addDrawable( baselines.get(), 0L, name, input, index );
             }
 
-            if ( outlines.valid() )
+            if ( outlines.valid() && outlines->getVertexArray() && outlines->getVertexArray()->getNumElements() > 0 )
             {
                 addDrawable( outlines.get(), 0L, name, input, index );
             }
@@ -1048,9 +1162,9 @@ ExtrudeGeometryFilter::push( FeatureList& input, FilterContext& context )
 
     if ( sheet != 0L )
     {
-        if ( _wallSkinSymbol.valid() && _wallSkinSymbol->libraryName().isSet() )
+        if ( _wallSkinSymbol.valid() && _wallSkinSymbol->library().isSet() )
         {
-            _wallResLib = sheet->getResourceLibrary( *_wallSkinSymbol->libraryName() );
+            _wallResLib = sheet->getResourceLibrary( *_wallSkinSymbol->library() );
 
             if ( !_wallResLib.valid() )
             {
@@ -1060,12 +1174,12 @@ ExtrudeGeometryFilter::push( FeatureList& input, FilterContext& context )
             }
         }
 
-        if ( _roofSkinSymbol.valid() && _roofSkinSymbol->libraryName().isSet() )
+        if ( _roofSkinSymbol.valid() && _roofSkinSymbol->library().isSet() )
         {
-            _roofResLib = sheet->getResourceLibrary( *_roofSkinSymbol->libraryName() );
+            _roofResLib = sheet->getResourceLibrary( *_roofSkinSymbol->library() );
             if ( !_roofResLib.valid() )
             {
-                OE_WARN << LC << "Unable to load resource library '" << *_roofSkinSymbol->libraryName() << "'"
+                OE_WARN << LC << "Unable to load resource library '" << *_roofSkinSymbol->library() << "'"
                     << "; roof geometry will not be textured." << std::endl;
                 _roofSkinSymbol = 0L;
             }
@@ -1078,47 +1192,40 @@ ExtrudeGeometryFilter::push( FeatureList& input, FilterContext& context )
     // push all the features through the extruder.
     bool ok = process( input, context );
 
-    // convert everything to triangles and combine drawables.
-    if ( _mergeGeometry == true && _featureNameExpr.empty() )
-    {
-        for( SortedGeodeMap::iterator i = _geodes.begin(); i != _geodes.end(); ++i )
-        {
-            if ( context.featureIndex() || _outlineSymbol.valid())
-            {
-                // The MC will recognize the presence of feature indexing tags and
-                // preserve them. The Cache optimizer however will not, so it is
-                // out for now.
-                // The Optimizer also doesn't work with line geometry, so if we have outlines
-                // then we need to use MC.                
-                MeshConsolidator::run( *i->second.get() );
-
-                //VertexCacheOptimizer vco;
-                //i->second->accept( vco );
-            }
-            else
-            {                
-                //TODO: try this -- issues: it won't work on lines, and will it screw up
-                // feature indexing?
-                osgUtil::Optimizer o;
-                o.optimize( i->second.get(),
-                    osgUtil::Optimizer::MERGE_GEOMETRY |
-                    osgUtil::Optimizer::VERTEX_PRETRANSFORM |
-                    osgUtil::Optimizer::INDEX_MESH |
-                    osgUtil::Optimizer::VERTEX_POSTTRANSFORM );
-            }
-        }
-    }
-
     // parent geometry with a delocalizer (if necessary)
     osg::Group* group = createDelocalizeGroup();
     
-    // combines geometries where the statesets are the same.
+    // add all the geodes
     for( SortedGeodeMap::iterator i = _geodes.begin(); i != _geodes.end(); ++i )
     {
         group->addChild( i->second.get() );
     }
     _geodes.clear();
 
+    if ( _mergeGeometry == true && _featureNameExpr.empty() )
+    {
+        osgUtil::Optimizer o;
+
+        unsigned mask = osgUtil::Optimizer::MERGE_GEOMETRY;
+
+        // Because the mesh optimizers damaga line geometry.
+        if ( !_outlineSymbol.valid() )
+        {
+            mask |= osgUtil::Optimizer::INDEX_MESH;
+            mask |= osgUtil::Optimizer::VERTEX_PRETRANSFORM;
+            mask |= osgUtil::Optimizer::VERTEX_POSTTRANSFORM;
+        }
+
+        o.optimize( group, mask );
+    }
+
+    // Prepare buffer objects.
+    AllocateAndMergeBufferObjectsVisitor allocAndMerge;
+    group->accept( allocAndMerge );
+
+    // set a uniform indiciating that clamping attributes are available.
+    Clamping::installHasAttrsUniform( group->getOrCreateStateSet() );
+
     // if we drew outlines, apply a poly offset too.
     if ( _outlineSymbol.valid() )
     {
diff --git a/src/osgEarthFeatures/Feature b/src/osgEarthFeatures/Feature
index 3353963..808fdd7 100644
--- a/src/osgEarthFeatures/Feature
+++ b/src/osgEarthFeatures/Feature
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -47,13 +47,12 @@ namespace osgEarth { namespace Features
         virtual ~FeatureProfile() { }
 
         /** Gets the spatial extents of the features in this profile. */
-        const GeoExtent& getExtent() const {
-            return _extent; }
+        const GeoExtent& getExtent() const { return _extent; }
 
         /** Gets the spatial reference system of feature shapes in this class. */
-        const SpatialReference* getSRS() const {
-            return _extent.getSRS(); }
+        const SpatialReference* getSRS() const { return _extent.getSRS(); }
 
+        /** Whether the feature data is pre-tiled */
         bool getTiled() const;
         void setTiled(bool tiled);
 
@@ -66,12 +65,17 @@ namespace osgEarth { namespace Features
         const osgEarth::Profile* getProfile() const;
         void setProfile( const osgEarth::Profile* profile );
 
+        /** Interpolation method for geodetic data */
+        optional<GeoInterpolation>& geoInterp() { return _geoInterp; }
+        const optional<GeoInterpolation>& geoInterp() const { return _geoInterp; }
+
     protected:
         osg::ref_ptr< const osgEarth::Profile > _profile;
         GeoExtent _extent;
         bool _tiled;
         int _firstLevel;
         int _maxLevel;
+        optional<GeoInterpolation> _geoInterp;
     };
 
     struct AttributeValueUnion
@@ -101,8 +105,8 @@ namespace osgEarth { namespace Features
         int getInt( int defaultValue =0 ) const;
         bool getBool( bool defaultValue =false ) const;              
     };
-
-    typedef std::map<std::string, AttributeValue> AttributeTable;
+    
+    typedef std::map<std::string, AttributeValue, CIStringComp> AttributeTable;
 
     typedef unsigned long FeatureID;
 
@@ -175,6 +179,13 @@ namespace osgEarth { namespace Features
          */
         bool getWorldBoundingPolytope( const SpatialReference* srs, osg::Polytope& out_polytope ) const;
 
+        /** 
+         * Gets a polytope, in world coordinates (proj or ECEF) that bounds the
+         * world coordinates covered by the given bounding sphere. This is useful for roughly
+         * intersecting features with the terrain graph.
+         */
+        static bool getWorldBoundingPolytope( const osg::BoundingSphered& bs, const SpatialReference* srs, osg::Polytope& out_polytope );
+
 
         const AttributeTable& getAttrs() const { return _attrs; }
 
diff --git a/src/osgEarthFeatures/Feature.cpp b/src/osgEarthFeatures/Feature.cpp
index 8ef3d41..fbe6e47 100644
--- a/src/osgEarthFeatures/Feature.cpp
+++ b/src/osgEarthFeatures/Feature.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -405,6 +405,15 @@ Feature::getWorldBoundingPolytope(const SpatialReference* srs,
     osg::BoundingSphered bs;
     if ( getWorldBound(srs, bs) && bs.valid() )
     {
+        return getWorldBoundingPolytope( bs, srs, out_polytope );
+    }
+    return false;
+}
+
+bool Feature::getWorldBoundingPolytope( const osg::BoundingSphered& bs, const SpatialReference* srs, osg::Polytope& out_polytope )
+{
+    if ( bs.valid() )
+    {
         out_polytope.clear();
 
         // add planes for the four sides of the BS. Normals point inwards.
diff --git a/src/osgEarthFeatures/FeatureCursor b/src/osgEarthFeatures/FeatureCursor
index 5acd776..da3ca6f 100644
--- a/src/osgEarthFeatures/FeatureCursor
+++ b/src/osgEarthFeatures/FeatureCursor
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthFeatures/FeatureCursor.cpp b/src/osgEarthFeatures/FeatureCursor.cpp
index b6c50e6..76db756 100644
--- a/src/osgEarthFeatures/FeatureCursor.cpp
+++ b/src/osgEarthFeatures/FeatureCursor.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -66,16 +66,17 @@ _geom( geom )
 
 GeometryFeatureCursor::GeometryFeatureCursor(Geometry* geom,
                                              const FeatureProfile* fp,
-                                             const FeatureFilterList& filters ) :
-_geom( geom ),
+                                             const FeatureFilterList& filters) :
+_geom          ( geom ),
 _featureProfile( fp ),
-_filters( filters )
+_filters       ( filters )
 {
     //nop
 }
 
 bool
-GeometryFeatureCursor::hasMore() const {
+GeometryFeatureCursor::hasMore() const
+{
     return _geom.valid();
 }
 
@@ -85,11 +86,16 @@ GeometryFeatureCursor::nextFeature()
     if ( hasMore() )
     {        
         _lastFeature = new Feature( _geom.get(), _featureProfile.valid() ? _featureProfile->getSRS() : 0L );
+
+        if ( _featureProfile && _featureProfile->geoInterp().isSet() )
+            _lastFeature->geoInterp() = _featureProfile->geoInterp().get();
+
         FilterContext cx;
         cx.setProfile( _featureProfile.get() );
         FeatureList list;
         list.push_back( _lastFeature.get() );
-        for( FeatureFilterList::const_iterator i = _filters.begin(); i != _filters.end(); ++i ) {
+        for( FeatureFilterList::const_iterator i = _filters.begin(); i != _filters.end(); ++i )
+        {
             cx = i->get()->push( list, cx );
         }
         _geom = 0L;
diff --git a/src/osgEarthFeatures/FeatureDisplayLayout b/src/osgEarthFeatures/FeatureDisplayLayout
index 4490452..024a124 100644
--- a/src/osgEarthFeatures/FeatureDisplayLayout
+++ b/src/osgEarthFeatures/FeatureDisplayLayout
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -47,8 +47,11 @@ namespace osgEarth { namespace Features
         FeatureLevel( float minRange, float maxRange, const std::string& styleName );
 
         /** Minimum and aximum display ranges for this level */
-        float minRange() const { return *_minRange; }
-        float maxRange() const { return *_maxRange; }
+        optional<float>& minRange() { return _minRange; }
+        const optional<float>& minRange() const { return _minRange; }
+
+        optional<float>& maxRange() { return _maxRange; }
+        const optional<float>& maxRange() const { return _maxRange; }
 
         /** Style class (or style selector) name associated with this level (optional) */
         optional<std::string>& styleName() { return _styleName; }
@@ -79,6 +82,13 @@ namespace osgEarth { namespace Features
         virtual ~FeatureDisplayLayout() { }
 
         /**
+         * The size (in one dimension) of each tile of features in the layout
+         * at the maximum range. Maximum range must be set for this to take effect.
+         */
+        optional<float>& tileSize() { return _tileSize; }
+        const optional<float>& tileSize() const { return _tileSize; }
+
+        /**
          * The ratio of visibility range to feature tile radius. Default is 15.
          * Increase this to produce more, smaller tiles at a given visibility
          * range; decrease this to produce fewer, larger tiles.
@@ -156,6 +166,7 @@ namespace osgEarth { namespace Features
         Config getConfig() const;
 
     protected:
+        optional<float> _tileSize;
         optional<float> _tileSizeFactor;
         optional<float> _minRange;
         optional<float> _maxRange;
diff --git a/src/osgEarthFeatures/FeatureDisplayLayout.cpp b/src/osgEarthFeatures/FeatureDisplayLayout.cpp
index 2794a1f..fbfb110 100644
--- a/src/osgEarthFeatures/FeatureDisplayLayout.cpp
+++ b/src/osgEarthFeatures/FeatureDisplayLayout.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -80,6 +80,7 @@ _priorityScale ( 1.0f )
 void
 FeatureDisplayLayout::fromConfig( const Config& conf )
 {
+    conf.getIfSet( "tile_size",        _tileSize );
     conf.getIfSet( "tile_size_factor", _tileSizeFactor );
     conf.getIfSet( "crop_features",    _cropFeatures );
     conf.getIfSet( "priority_offset",  _priorityOffset );
@@ -95,6 +96,7 @@ Config
 FeatureDisplayLayout::getConfig() const
 {
     Config conf( "layout" );
+    conf.addIfSet( "tile_size",        _tileSize );
     conf.addIfSet( "tile_size_factor", _tileSizeFactor );
     conf.addIfSet( "crop_features",    _cropFeatures );
     conf.addIfSet( "priority_offset",  _priorityOffset );
@@ -109,7 +111,7 @@ FeatureDisplayLayout::getConfig() const
 void 
 FeatureDisplayLayout::addLevel( const FeatureLevel& level )
 {
-    _levels.insert( std::make_pair( -level.maxRange(), level ) );
+    _levels.insert( std::make_pair( -level.maxRange().get(), level ) );
 }
 
 unsigned 
@@ -130,12 +132,6 @@ FeatureDisplayLayout::getLevel( unsigned n ) const
     return 0L;
 }
 
-//float
-//FeatureDisplayLayout::getMaxRange() const
-//{
-//    return _levels.size() > 0 ? _levels.begin()->second.maxRange() : 0.0f;
-//}
-
 unsigned
 FeatureDisplayLayout::chooseLOD( const FeatureLevel& level, double fullExtentRadius ) const
 {
diff --git a/src/osgEarthFeatures/FeatureDrawSet b/src/osgEarthFeatures/FeatureDrawSet
index c8d1bda..87dd9d4 100644
--- a/src/osgEarthFeatures/FeatureDrawSet
+++ b/src/osgEarthFeatures/FeatureDrawSet
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthFeatures/FeatureDrawSet.cpp b/src/osgEarthFeatures/FeatureDrawSet.cpp
index 541c499..9f59217 100644
--- a/src/osgEarthFeatures/FeatureDrawSet.cpp
+++ b/src/osgEarthFeatures/FeatureDrawSet.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthFeatures/FeatureIndex b/src/osgEarthFeatures/FeatureIndex
new file mode 100644
index 0000000..f849255
--- /dev/null
+++ b/src/osgEarthFeatures/FeatureIndex
@@ -0,0 +1,54 @@
+/* -*-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.
+ *
+ * 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_INDEX
+#define OSGEARTHFEATURES_FEATURE_INDEX 1
+
+#include <osgEarthFeatures/Common>
+#include <osgEarthFeatures/Feature>
+#include <osgEarth/ObjectIndex>
+
+namespace osgEarth { namespace Features
+{
+    using namespace osgEarth;
+
+    /**
+     * Client interface for fetching features from an index given an object id.
+     */
+    class OSGEARTHFEATURES_EXPORT FeatureIndex : public osg::Referenced
+    {
+    public:
+        /** Get a feature given an object id. */
+        virtual Feature* getFeature(ObjectID oid) const =0;
+
+        /** Number of features in the index */
+        virtual int size() const =0;
+    };
+
+    /**
+     * Interface for building a Feature Index.
+     */
+    class OSGEARTHFEATURES_EXPORT FeatureIndexBuilder : public ObjectIndexBuilder<Feature>
+    {
+        //empty
+    };
+
+} } // namespace osgEarth::Features
+
+#endif // OSGEARTHFEATURES_FEATURE_INDEX
diff --git a/src/osgEarthFeatures/FeatureListSource b/src/osgEarthFeatures/FeatureListSource
index b55a32b..6ab0729 100644
--- a/src/osgEarthFeatures/FeatureListSource
+++ b/src/osgEarthFeatures/FeatureListSource
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
@@ -52,12 +55,14 @@ namespace osgEarth { namespace Features
         virtual bool isWritable() const { return true; }
         virtual bool deleteFeature(FeatureID fid);
         virtual int getFeatureCount() const { return _features.size(); }
+        virtual bool supportsGetFeature() const { return true; }
         virtual Feature* getFeature( FeatureID fid );
         virtual bool insertFeature(Feature* feature);
         virtual Geometry::Type getGeometryType() const { return Geometry::TYPE_UNKNOWN; }
 
         FeatureList& getFeatures() { return _features; }
 
+
     public: // Styling
 
         virtual bool hasEmbeddedStyles() const { return false; }
diff --git a/src/osgEarthFeatures/FeatureListSource.cpp b/src/osgEarthFeatures/FeatureListSource.cpp
index dad3abf..c54f990 100644
--- a/src/osgEarthFeatures/FeatureListSource.cpp
+++ b/src/osgEarthFeatures/FeatureListSource.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthFeatures/FeatureModelGraph b/src/osgEarthFeatures/FeatureModelGraph
index e7b77a4..2ea3988 100644
--- a/src/osgEarthFeatures/FeatureModelGraph
+++ b/src/osgEarthFeatures/FeatureModelGraph
@@ -1,6 +1,6 @@
 /* --*-c++-*-- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -70,6 +70,15 @@ namespace osgEarth { namespace Features
             Session*                         session,
             const FeatureModelSourceOptions& options,
             FeatureNodeFactory*              factory,
+            ModelSource*                     modelSource,
+            RefNodeOperationVector*          preMergeOperations,
+            RefNodeOperationVector*          postMergeOperations);
+        
+        /* @deprecated */
+        FeatureModelGraph(
+            Session*                         session,
+            const FeatureModelSourceOptions& options,
+            FeatureNodeFactory*              factory,
             RefNodeOperationVector*          preMergeOperations,
             RefNodeOperationVector*          postMergeOperations);
 
@@ -124,18 +133,20 @@ namespace osgEarth { namespace Features
             const TileKey*      key);
 
         osg::Group* build( 
-            const Style&        baseStyle, 
-            const Query&        baseQuery, 
-            const GeoExtent&    extent, 
-            FeatureSourceIndex* index);
+            const Style&         baseStyle, 
+            const Query&         baseQuery, 
+            const GeoExtent&     extent, 
+            FeatureIndexBuilder* index);
 
 
     private:
+
+        void ctor();
         
         osg::Group* createStyleGroup(
-            const Style&        style, 
-            const Query&        query, 
-            FeatureSourceIndex* index);
+            const Style&         style, 
+            const Query&         query, 
+            FeatureIndexBuilder* index);
 
         osg::Group* createStyleGroup(
             const Style&         style, 
@@ -145,13 +156,13 @@ namespace osgEarth { namespace Features
         void buildStyleGroups(
             const StyleSelector* selector,
             const Query&         baseQuery,
-            FeatureSourceIndex*  index,
+            FeatureIndexBuilder* index,
             osg::Group*          parent);
 
         void queryAndSortIntoStyleGroups(
             const Query&            query,
             const StringExpression& styleExpr,
-            FeatureSourceIndex*     index,
+            FeatureIndexBuilder*    index,
             osg::Group*             parent);
 
         osg::Group* getOrCreateStyleGroupFromFactory(
@@ -179,7 +190,7 @@ namespace osgEarth { namespace Features
         GeoExtent                        _usableMapExtent;
         osg::BoundingSphered             _fullWorldBound;
         bool                             _useTiledSource;
-        osgEarth::Revision               _revision;
+        osgEarth::Revision               _featureSourceRev, _modelSourceRev;
         bool                             _dirty;
         bool                             _pendingUpdate;
         std::vector<const FeatureLevel*> _lodmap;
@@ -203,6 +214,10 @@ namespace osgEarth { namespace Features
         osg::ref_ptr<RefNodeOperationVector> _preMergeOperations;
         osg::ref_ptr<RefNodeOperationVector> _postMergeOperations;
 
+        osg::observer_ptr<ModelSource> _modelSource;
+
+        osg::ref_ptr<FeatureSourceIndex> _featureIndex;
+
         void runPreMergeOperations(osg::Node* node);
         void runPostMergeOperations(osg::Node* node);
         void checkForGlobalStyles(const Style& style);
diff --git a/src/osgEarthFeatures/FeatureModelGraph.cpp b/src/osgEarthFeatures/FeatureModelGraph.cpp
index 687a256..f54c96f 100644
--- a/src/osgEarthFeatures/FeatureModelGraph.cpp
+++ b/src/osgEarthFeatures/FeatureModelGraph.cpp
@@ -1,6 +1,6 @@
 /* --*-c++-*-- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -24,6 +24,7 @@
 
 #include <osgEarth/Map>
 #include <osgEarth/Capabilities>
+#include <osgEarth/Clamping>
 #include <osgEarth/ClampableNode>
 #include <osgEarth/CullingUtils>
 #include <osgEarth/DrapeableNode>
@@ -45,7 +46,7 @@
 #include <algorithm>
 #include <iterator>
 
-#define LC "[FeatureModelGraph] "
+#define LC "[FeatureModelGraph] " << getName()
 
 using namespace osgEarth;
 using namespace osgEarth::Features;
@@ -106,9 +107,10 @@ namespace
 #else
         PagedLODWithNodeOperations* p = new PagedLODWithNodeOperations(postMergeOps);
         p->setCenter( bs.center() );
-        p->setRadius( bs.radius() );
+        p->setRadius( bs.radius() ); //maxRange + bs.radius() );
+        //p->setRadius(-1);
         p->setFileName( 0, uri );
-        p->setRange( 0, minRange, maxRange + bs.radius() );
+        p->setRange( 0, minRange, maxRange );
         p->setPriorityOffset( 0, priOffset );
         p->setPriorityScale( 0, priScale );
 #endif
@@ -169,7 +171,7 @@ struct osgEarthFeatureModelPseudoLoader : public osgDB::ReaderWriter
         Threading::ScopedWriteLock lock( _fmgMutex );
         UID key = ++_uid;
         _fmgRegistry[key] = graph;
-        OE_TEST << LC << "Registered FMG " << key << std::endl;
+        OE_TEST << "Registered FMG " << key << std::endl;
         return key;
     }
 
@@ -177,7 +179,7 @@ struct osgEarthFeatureModelPseudoLoader : public osgDB::ReaderWriter
     {
         Threading::ScopedWriteLock lock( _fmgMutex );
         _fmgRegistry.erase( uid );
-        OE_TEST << LC << "UNregistered FMG " << uid << std::endl;
+        OE_TEST << "UNregistered FMG " << uid << std::endl;
     }
 
     static FeatureModelGraph* getGraph( UID uid ) 
@@ -234,11 +236,13 @@ namespace
 FeatureModelGraph::FeatureModelGraph(Session*                         session,
                                      const FeatureModelSourceOptions& options,
                                      FeatureNodeFactory*              factory,
+                                     ModelSource*                     modelSource,
                                      RefNodeOperationVector*          preMergeOperations,
                                      RefNodeOperationVector*          postMergeOperations) :
 _session            ( session ),
 _options            ( options ),
 _factory            ( factory ),
+_modelSource        ( modelSource ),
 _preMergeOperations ( preMergeOperations ),
 _postMergeOperations( postMergeOperations ),
 _dirty              ( false ),
@@ -249,6 +253,34 @@ _clampable          ( 0L ),
 _drapeable          ( 0L ),
 _overlayChange      ( OVERLAY_NO_CHANGE )
 {
+    ctor();
+}
+
+FeatureModelGraph::FeatureModelGraph(Session*                         session,
+                                     const FeatureModelSourceOptions& options,
+                                     FeatureNodeFactory*              factory,
+                                     RefNodeOperationVector*          preMergeOperations,
+                                     RefNodeOperationVector*          postMergeOperations) :
+_session            ( session ),
+_options            ( options ),
+_factory            ( factory ),
+_modelSource        ( 0L ),
+_preMergeOperations ( preMergeOperations ),
+_postMergeOperations( postMergeOperations ),
+_dirty              ( false ),
+_pendingUpdate      ( false ),
+_overlayInstalled   ( 0L ),
+_overlayPlaceholder ( 0L ),
+_clampable          ( 0L ),
+_drapeable          ( 0L ),
+_overlayChange      ( OVERLAY_NO_CHANGE )
+{
+    ctor();
+}
+
+void
+FeatureModelGraph::ctor()
+{
     _uid = osgEarthFeatureModelPseudoLoader::registerGraph( this );
 
     // an FLC that queues feature data on the high-latency thread.
@@ -265,10 +297,10 @@ _overlayChange      ( OVERLAY_NO_CHANGE )
         _postMergeOperations = new RefNodeOperationVector();
 
     // install the stylesheet in the session if it doesn't already have one.
-    if ( !session->styles() )
-        session->setStyles( _options.styles().get() );
+    if ( !_session->styles() )
+        _session->setStyles( _options.styles().get() );
 
-    if ( !session->getFeatureSource() )
+    if ( !_session->getFeatureSource() )
     {
         OE_WARN << LC << "ILLEGAL: Session must have a feature source" << std::endl;
         return;
@@ -280,14 +312,14 @@ _overlayChange      ( OVERLAY_NO_CHANGE )
     // StateSet will be used across the entire Session. That also means that StateSets
     // in the ResourceCache can potentially also be in the live graph; so you should
     // take care in dealing with them in a multi-threaded environment.
-    if ( !session->getResourceCache() && _options.sessionWideResourceCache() == true )
+    if ( !_session->getResourceCache() && _options.sessionWideResourceCache() == true )
     {
-        session->setResourceCache( new ResourceCache(session->getDBOptions()) );
+        _session->setResourceCache( new ResourceCache(_session->getDBOptions()) );
     }
     
     // Calculate the usable extent (in both feature and map coordinates) and bounds.
-    const Profile* mapProfile = session->getMapInfo().getProfile();
-    const FeatureProfile* featureProfile = session->getFeatureSource()->getFeatureProfile();
+    const Profile* mapProfile = _session->getMapInfo().getProfile();
+    const FeatureProfile* featureProfile = _session->getFeatureSource()->getFeatureProfile();
 
     // Bail out if the feature profile is bad
     if ( !featureProfile || !featureProfile->getExtent().isValid() )
@@ -306,35 +338,116 @@ _overlayChange      ( OVERLAY_NO_CHANGE )
 
     // world-space bounds of the feature layer
     _fullWorldBound = getBoundInWorldCoords( _usableMapExtent, 0L );
-
+    
     // whether to request tiles from the source (if available). if the source is tiled, but the
     // user manually specified schema levels, don't use the tiles.
     _useTiledSource = featureProfile->getTiled();
 
-    if ( options.layout().isSet() && options.layout()->getNumLevels() > 0 )
+
+    // compute an appropriate tileSizeFactor for a tiled source if a max range was set but no tilesize factor
+    if (_options.layout().isSet() && (_options.layout()->maxRange().isSet() || _options.maxRange().isSet()))
+    {
+        // select the max range either from the Layout or from the model layer options.
+        float userMaxRange = FLT_MAX;
+        if ( _options.layout()->maxRange().isSet() )
+            userMaxRange = *_options.layout()->maxRange();
+        if ( _options.maxRange().isSet() )
+            userMaxRange = std::min(userMaxRange, *_options.maxRange());
+        
+        if ( featureProfile->getTiled() )
+        {
+            // Cannot change the tile size of a tiled data source.
+            if (_options.layout()->tileSize().isSet() )
+            {
+                OE_WARN << LC << getName()
+                    << ": Illegal: you cannot set a tile size on a pre-tiled feature source. Ignoring.\n";
+            }
+
+            if ( !_options.layout()->tileSizeFactor().isSet() )
+            {
+                // So automatically compute the tileSizeFactor based on the max range
+                double width, height;
+                featureProfile->getProfile()->getTileDimensions(featureProfile->getFirstLevel(), width, height);
+
+                MapFrame mapf = _session->createMapFrame();
+
+
+                GeoExtent ext(featureProfile->getSRS(),
+                    featureProfile->getExtent().west(),
+                    featureProfile->getExtent().south(),
+                    featureProfile->getExtent().west() + width,
+                    featureProfile->getExtent().south() + height);
+                osg::BoundingSphered bounds = getBoundInWorldCoords( ext, &mapf );
+
+                float tileSizeFactor = userMaxRange / bounds.radius();
+                //The tilesize factor must be at least 1.0 to avoid culling the tile when you are within it's bounding sphere. 
+                tileSizeFactor = osg::maximum( tileSizeFactor, 1.0f);
+                OE_INFO << LC << "Computed a tilesize factor of " << tileSizeFactor << " with max range setting of " <<  userMaxRange << std::endl;
+                _options.layout()->tileSizeFactor() = tileSizeFactor * 1.5; // approx sqrt(2)
+            }
+        }
+    }
+
+
+    if ( _options.layout().isSet() && _options.layout()->getNumLevels() > 0 )
     {
         // the user provided a custom levels setup, so don't use the tiled source (which
         // provides its own levels setup)
         _useTiledSource = false;
 
+        // If the user asked for a particular tile size, give it to them!
+        if (_options.layout()->tileSize().isSet() &&
+            _options.layout()->tileSize() > 0.0 )
+        {
+            float maxRange = FLT_MAX;
+            maxRange = _options.maxRange().getOrUse(maxRange);
+            maxRange = _options.layout()->maxRange().getOrUse(maxRange);
+            maxRange = std::min( maxRange, _options.layout()->getLevel(0)->maxRange().get() );
+        
+            _options.layout()->tileSizeFactor() = maxRange / _options.layout()->tileSize().get();
+
+            OE_INFO << LC << "Tile size = " << (*_options.layout()->tileSize()) << " ==> TRF = " << 
+                (*_options.layout()->tileSizeFactor()) << "\n";
+        }
+
         // for each custom level, calculate the best LOD match and store it in the level
         // layout data. We will use this information later when constructing the SG in
         // the pager.
-        for( unsigned i = 0; i < options.layout()->getNumLevels(); ++i )
+        for( unsigned i = 0; i < _options.layout()->getNumLevels(); ++i )
         {
-            const FeatureLevel* level = options.layout()->getLevel( i );
-            unsigned lod = options.layout()->chooseLOD( *level, _fullWorldBound.radius() );
+            const FeatureLevel* level = _options.layout()->getLevel( i );
+            unsigned lod = _options.layout()->chooseLOD( *level, _fullWorldBound.radius() );
             _lodmap.resize( lod+1, 0L );
             _lodmap[lod] = level;
 
-            OE_INFO << LC << session->getFeatureSource()->getName() 
-                << ": F.Level max=" << level->maxRange() << ", min=" << level->minRange()
+            OE_INFO << LC << _session->getFeatureSource()->getName() 
+                << ": F.Level max=" << level->maxRange().get() << ", min=" << level->minRange().get()
                 << ", LOD=" << lod
-                << ", Tile size=" << (level->maxRange() / options.layout()->tileSizeFactor().get())
                 << std::endl;
         }
     }
 
+
+    // Compute the feature levels up front for tiled sources.
+    if (featureProfile->getTiled() && _useTiledSource)
+    {    
+        // Get the max range of the root level
+        MapFrame mapf = _session->createMapFrame();
+        osg::BoundingSphered bounds = getBoundInWorldCoords( featureProfile->getExtent(), &mapf );
+        double maxRange = bounds.radius() * *_options.layout()->tileSizeFactor();
+
+        _lodmap.resize(featureProfile->getMaxLevel() + 1);
+         
+        // Compute the max range of all the feature levels.  Each subsequent level if half of the parent.
+        for (unsigned int i = 0; i < featureProfile->getMaxLevel()+1; i++)
+        {
+            OE_INFO << LC << "Computed max range " << maxRange << " for lod " << i << std::endl;
+            FeatureLevel* level = new FeatureLevel(0.0, maxRange);
+            _lodmap[i] = level;
+            maxRange /= 2.0;
+        }
+    }
+
     // Apply some default state. The options properties let you override the
     // defaults, but we'll set some reasonable state if they are not set.
 
@@ -383,6 +496,8 @@ FeatureModelGraph::dirty()
     _dirty = true;
 }
 
+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,
                                          const MapFrame*  mapf ) const
@@ -412,6 +527,7 @@ FeatureModelGraph::getBoundInWorldCoords(const GeoExtent& extent,
         // 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();
@@ -423,11 +539,78 @@ FeatureModelGraph::getBoundInWorldCoords(const GeoExtent& extent,
 
     if ( _session->getMapInfo().isGeocentric() )
     {
-        const SpatialReference* ecefSRS = workingExtent.getSRS()->getECEF();
-        workingExtent.getSRS()->transform( center, ecefSRS, center );
-        workingExtent.getSRS()->transform( corner, ecefSRS, corner );
-        //workingExtent.getSRS()->transformToECEF( center, center );
-        //workingExtent.getSRS()->transformToECEF( corner, corner );
+#if 0
+        // Convert the extent to lat/long and center it on the equator; this will ensure
+        // that all tiles in the same LOD have the same bounding radius.
+        GeoExtent eq = workingExtent.transform( workingExtent.getSRS()->getGeographicSRS() );
+
+        GeoExtent equatorialExtent(
+            eq.getSRS(),
+            eq.west(),
+            -eq.height()/2.0,
+            eq.east(),
+            eq.height()/2.0 );
+        
+        GeoPoint centerPoint( workingExtent.getSRS(), center, ALTMODE_ABSOLUTE );
+        centerPoint.toWorld( center );
+
+        return osg::BoundingSphered( center, equatorialExtent.getBoundingGeoCircle().getRadius() );
+#else
+        
+        /*
+        GeoPoint centerPoint( workingExtent.getSRS(), center, ALTMODE_ABSOLUTE );
+        centerPoint.toWorld( center );
+        double radius = workingExtent.getBoundingGeoCircle().getRadius();
+        //OE_WARN << LC << "Extent=" << workingExtent.toString() << "; center=" << center << "; radius=" << radius << "\n";
+        return osg::BoundingSphered( center, radius );  
+        */
+
+        // Compute the bounding sphere by sampling points along the extent.
+        int samples = 6;
+
+        double xSample = workingExtent.width() / (double)samples;
+        double ySample = workingExtent.height() / (double)samples;
+
+        osg::BoundingSphered bs;
+        for (unsigned int c = 0; c < samples+1; c++)
+        {
+            double x = workingExtent.xMin() + (double)c * xSample;
+            for (unsigned int r = 0; r < samples+1; r++)
+            {
+                double y = workingExtent.yMin() + (double)r * ySample;
+                osg::Vec3d world;
+                GeoPoint(workingExtent.getSRS(), x, y, 0, ALTMODE_ABSOLUTE).toWorld(world);
+                bs.expandBy(world);
+            }
+        }
+        return bs;
+              
+
+        /*
+        // Compute the bounding sphere by sampling the corners.
+        osg::Vec3d sw, se, ne, nw, e, w, s, n;
+        GeoPoint(workingExtent.getSRS(), workingExtent.west(), workingExtent.south(), 0, ALTMODE_ABSOLUTE).toWorld(sw);
+        GeoPoint(workingExtent.getSRS(), workingExtent.east(), workingExtent.south(), 0, ALTMODE_ABSOLUTE).toWorld(se);
+        GeoPoint(workingExtent.getSRS(), workingExtent.east(), workingExtent.north(), 0, ALTMODE_ABSOLUTE).toWorld(ne);
+        GeoPoint(workingExtent.getSRS(), workingExtent.west(), workingExtent.north(), 0, ALTMODE_ABSOLUTE).toWorld(nw);
+        GeoPoint(workingExtent.getSRS(), workingExtent.west(), center.y(),            0, ALTMODE_ABSOLUTE).toWorld(w);
+        GeoPoint(workingExtent.getSRS(), workingExtent.east(), center.y(),            0, ALTMODE_ABSOLUTE).toWorld(e);
+        GeoPoint(workingExtent.getSRS(), center.x(),           workingExtent.north(), 0, ALTMODE_ABSOLUTE).toWorld(n);
+        GeoPoint(workingExtent.getSRS(), center.x(),           workingExtent.south(), 0, ALTMODE_ABSOLUTE).toWorld(s);
+      
+        osg::BoundingSphered bs;
+        bs.expandBy(center);
+        bs.expandBy(sw);
+        bs.expandBy(se);
+        bs.expandBy(ne);
+        bs.expandBy(nw);
+        bs.expandBy(w);
+        bs.expandBy(e);
+        bs.expandBy(n);
+        bs.expandBy(s);
+       
+        */ 
+#endif
     }
 
     if (workingExtent.getSRS()->isGeographic() &&
@@ -458,33 +641,8 @@ FeatureModelGraph::setupPaging()
             userMaxRange = *_options.layout()->maxRange();
         if ( _options.maxRange().isSet() )
             userMaxRange = std::min(userMaxRange, *_options.maxRange());
-
-        if (featureProfile->getTiled() )
-        {
-            if ( !_options.layout()->tileSizeFactor().isSet() )
-            {
-                //Automatically compute the tileSizeFactor based on the max range
-                //TODO: This is no longer correct. "max_range" is actually an altitude,
-                //not a distance-from-tile, so if the user sets it we need to do something
-                //different. -gw
-                double width, height;
-                featureProfile->getProfile()->getTileDimensions(featureProfile->getFirstLevel(), width, height);
-
-                GeoExtent ext(featureProfile->getSRS(),
-                              featureProfile->getExtent().west(),
-                              featureProfile->getExtent().south(),
-                              featureProfile->getExtent().west() + width,
-                              featureProfile->getExtent().south() + height);
-                osg::BoundingSphered bounds = getBoundInWorldCoords( ext, &mapf);
-
-                float tileSizeFactor = userMaxRange / bounds.radius();
-                //The tilesize factor must be at least 1.0 to avoid culling the tile when you are within it's bounding sphere. 
-                tileSizeFactor = osg::maximum( tileSizeFactor, 1.0f);
-                OE_DEBUG << LC << "Computed a tilesize factor of " << tileSizeFactor << " with max range setting of " <<  userMaxRange << std::endl;
-                _options.layout()->tileSizeFactor() = tileSizeFactor * 1.5;
-            }
-        }
-        else
+        
+        if ( !featureProfile->getTiled() )
         {
             // user set a max_range, but we'd not tiled. Just override the top level plod.
             maxRangeOverride = userMaxRange;
@@ -554,6 +712,12 @@ FeatureModelGraph::load( unsigned lod, unsigned tileX, unsigned tileY, const std
             //OE_NOTICE << "  tileFactor = " << tileFactor << " maxRange=" << maxRange << " radius=" << tileBound.radius() << std::endl;
             
             // Construct a tile key that will be used to query the source for this tile.
+#if 0
+            unsigned int w, h;
+            featureProfile->getProfile()->getNumTiles(lod, w, h);
+            tileY = h - tileY - 1;
+#endif
+
             TileKey key(lod, tileX, tileY, featureProfile->getProfile());
             geometry = buildLevel( level, tileExtent, &key );
             result = geometry;
@@ -668,6 +832,23 @@ FeatureModelGraph::buildSubTilePagedLODs(unsigned        parentLOD,
     unsigned subtileX = parentTileX * 2;
     unsigned subtileY = parentTileY * 2;
 
+    
+    // Find the next level with data:
+    const FeatureLevel* flevel = 0L;
+    
+    for(unsigned lod=subtileLOD; lod<_lodmap.size() && !flevel; ++lod)
+    {
+        flevel = _lodmap[lod];
+    }
+
+    // should not happen (or this method would never have been called in teh first place) but
+    // check anyway.
+    if ( !flevel )
+    {
+        OE_INFO << LC << "INTERNAL: buildSubTilePagedLODs called but no further levels exist\n";
+        return;
+    }
+    
     // make a paged LOD for each subtile:
     for( unsigned u = subtileX; u <= subtileX + 1; ++u )
     {
@@ -675,12 +856,24 @@ FeatureModelGraph::buildSubTilePagedLODs(unsigned        parentLOD,
         {
             GeoExtent subtileFeatureExtent = s_getTileExtent( subtileLOD, u, v, _usableFeatureExtent );
             osg::BoundingSphered subtile_bs = getBoundInWorldCoords( subtileFeatureExtent, mapf );
+      
+            // Calculate the maximum camera range for the LOD.
+            float maxRange;
 
-            // Camera range for the PLODs. This should always be sufficient because
-            // the max range of a FeatureLevel below this will, by definition, have a max range
-            // less than or equal to this number -- based on how the LODs were chosen in 
-            // setupPaging.
-            float maxRange = subtile_bs.radius() * _options.layout()->tileSizeFactor().value();
+            
+            if ( flevel && flevel->maxRange().isSet() )
+            {
+                // User set it expressly
+                maxRange = flevel->maxRange().get();
+                if ( maxRange < FLT_MAX )
+                    maxRange += subtile_bs.radius();
+            }
+            
+            else
+            {
+                // Calculate it based on the tile size factor.
+                maxRange = subtile_bs.radius() * _options.layout()->tileSizeFactor().value();
+            }
 
             std::string uri = s_makeURI( _uid, subtileLOD, u, v );
 
@@ -729,12 +922,20 @@ FeatureModelGraph::buildLevel( const FeatureLevel& level, const GeoExtent& exten
     osg::ref_ptr<osg::Group> group;
     FeatureSourceIndexNode* index = 0L;
 
-    if ( _session->getFeatureSource() && _options.featureIndexing().isSet() )
+    FeatureSource* featureSource = _session->getFeatureSource();
+
+    if (featureSource)
     {
-        index = new FeatureSourceIndexNode( _session->getFeatureSource(), *_options.featureIndexing() );
-        group = index;
+        const FeatureProfile* fp = featureSource->getFeatureProfile();
+
+        if ( _featureIndex.valid() )
+        {
+            index = new FeatureSourceIndexNode( _featureIndex.get() );
+            group = index;
+        }
     }
-    else
+
+    if ( !group.valid() )
     {
         group = new osg::Group();
     }
@@ -748,6 +949,8 @@ FeatureModelGraph::buildLevel( const FeatureLevel& level, const GeoExtent& exten
     if ( key )
         query.tileKey() = *key;
 
+    query.setMap( _session->getMap() );
+
     // does the level have a style name set?
     if ( level.styleName().isSet() )
     {
@@ -790,7 +993,7 @@ FeatureModelGraph::buildLevel( const FeatureLevel& level, const GeoExtent& exten
     {
         // account for a min-range here. Do not address the max-range here; that happens
         // above when generating paged LOD nodes, etc.
-        float minRange = level.minRange();
+        float minRange = level.minRange().get();
         if ( minRange > 0.0f )
         {
             ElevationLOD* lod = new ElevationLOD( _session->getMapSRS() );
@@ -825,10 +1028,6 @@ FeatureModelGraph::buildLevel( const FeatureLevel& level, const GeoExtent& exten
             }
         }
 
-        // if indexing is enabled, build the index now.
-        if ( index )
-            index->reindex();
-
         return group.release();
     }
 
@@ -840,10 +1039,10 @@ FeatureModelGraph::buildLevel( const FeatureLevel& level, const GeoExtent& exten
 
 
 osg::Group*
-FeatureModelGraph::build(const Style&        defaultStyle, 
-                         const Query&        baseQuery, 
-                         const GeoExtent&    workingExtent,
-                         FeatureSourceIndex* index)
+FeatureModelGraph::build(const Style&         defaultStyle, 
+                         const Query&         baseQuery, 
+                         const GeoExtent&     workingExtent,
+                         FeatureIndexBuilder* index)
 {
     osg::ref_ptr<osg::Group> group = new osg::Group();
 
@@ -916,6 +1115,7 @@ FeatureModelGraph::build(const Style&        defaultStyle,
                 {
                     // merge the selector's query into the existing query
                     Query combinedQuery = baseQuery.combineWith( *sel.query() );
+                    combinedQuery.setMap( _session->getMap() );
 
                     // query, sort, and add each style group to th parent:
                     queryAndSortIntoStyleGroups( combinedQuery, *sel.styleExpression(), index, group );
@@ -930,6 +1130,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() );
 
                     // then create the node.
                     osg::Group* styleGroup = createStyleGroup( combinedStyle, combinedQuery, index );
@@ -976,7 +1177,7 @@ FeatureModelGraph::build(const Style&        defaultStyle,
 void
 FeatureModelGraph::buildStyleGroups(const StyleSelector* selector,
                                     const Query&         baseQuery,
-                                    FeatureSourceIndex*  index,
+                                    FeatureIndexBuilder* index,
                                     osg::Group*          parent)
 {
     OE_TEST << LC << "buildStyleGroups: " << selector->name() << std::endl;
@@ -987,6 +1188,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() );
 
         // query, sort, and add each style group to the parent:
         queryAndSortIntoStyleGroups( combinedQuery, *selector->styleExpression(), index, parent );
@@ -1003,6 +1205,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() );
 
         // then create the node.
         osg::Node* node = createStyleGroup( style, combinedQuery, index );
@@ -1022,7 +1225,7 @@ FeatureModelGraph::buildStyleGroups(const StyleSelector* selector,
 void
 FeatureModelGraph::queryAndSortIntoStyleGroups(const Query&            query,
                                                const StringExpression& styleExpr,
-                                               FeatureSourceIndex*     index,
+                                               FeatureIndexBuilder*    index,
                                                osg::Group*             parent)
 {
     // the profile of the features
@@ -1101,15 +1304,27 @@ FeatureModelGraph::createStyleGroup(const Style&         style,
 {
     osg::Group* styleGroup = 0L;
 
+    OE_DEBUG << LC << "Created style group \"" << style.getName() << "\"\n";
+
     FilterContext context(contextPrototype);
 
-    // first Crop the feature set to the working extent:
+    // First Crop the feature set to the working extent.
+    // Note: There is an obscure edge case that can happen is a feature's centroid
+    // falls exactly on the crop extent boundary. In that case the feature can
+    // show up in more than one tile. It's rare and not trivial to mitigate so for now
+    // we have decided to do nothing. :)
     CropFilter crop( 
         _options.layout().isSet() && _options.layout()->cropFeatures() == true ? 
         CropFilter::METHOD_CROPPING : CropFilter::METHOD_CENTROID );
 
+    unsigned sizeBefore = workingSet.size();
+
     context = crop.push( workingSet, context );
 
+    unsigned sizeAfter = workingSet.size();
+
+    OE_DEBUG << LC << "Cropped out " << sizeBefore-sizeAfter << " features\n";
+
     // next, if the usable extent is less than the full extent (i.e. we had to clamp the feature
     // extent to fit on the map), calculate the extent of the features in this tile and 
     // crop to the map extent if necessary. (Note, if cropFeatures was set to true, this is
@@ -1143,9 +1358,9 @@ FeatureModelGraph::createStyleGroup(const Style&         style,
 
 
 osg::Group*
-FeatureModelGraph::createStyleGroup(const Style&        style, 
-                                    const Query&        query, 
-                                    FeatureSourceIndex* index)
+FeatureModelGraph::createStyleGroup(const Style&         style, 
+                                    const Query&         query, 
+                                    FeatureIndexBuilder* index)
 {
     osg::Group* styleGroup = 0L;
 
@@ -1237,6 +1452,12 @@ FeatureModelGraph::checkForGlobalStyles( const Style& style )
         {
             _drapeable->setRenderOrder( render->order()->eval() );
         }
+
+        if ( render && render->renderBin().isSet() )
+        {
+            osg::StateSet* ss = getOrCreateStateSet();
+            ss->setRenderBinDetails(ss->getBinNumber(), render->renderBin().get());
+        }
     }
 }
 
@@ -1259,7 +1480,10 @@ FeatureModelGraph::traverse(osg::NodeVisitor& nv)
 {
     if ( nv.getVisitorType() == nv.EVENT_VISITOR )
     {
-        if ( !_pendingUpdate && (_dirty || _session->getFeatureSource()->outOfSyncWith(_revision)) )
+        if (!_pendingUpdate && 
+             (_dirty ||
+              _session->getFeatureSource()->outOfSyncWith(_featureSourceRev) ||
+              (_modelSource.valid() && _modelSource->outOfSyncWith(_modelSourceRev))))
         {
             _pendingUpdate = true;
             ADJUST_UPDATE_TRAV_COUNT( this, 1 );
@@ -1369,6 +1593,15 @@ FeatureModelGraph::redraw()
     // clear it out
     removeChildren( 0, getNumChildren() );
 
+    // initialize the index if necessary.
+    if ( _options.featureIndexing()->enabled() == true )
+    {
+        _featureIndex = new FeatureSourceIndex(
+            _session->getFeatureSource(),
+            Registry::objectIndex(),
+            _options.featureIndexing().get() );
+    }
+
     // zero out any decorators
     _clampable          = 0L;
     _drapeable          = 0L;
@@ -1432,7 +1665,10 @@ FeatureModelGraph::redraw()
 
     addChild( node );
 
-    _session->getFeatureSource()->sync( _revision );
+    _session->getFeatureSource()->sync( _featureSourceRev );
+    if ( _modelSource.valid() )
+        _modelSource->sync( _modelSourceRev );
+
     _dirty = false;
 }
 
diff --git a/src/osgEarthFeatures/FeatureModelSource b/src/osgEarthFeatures/FeatureModelSource
index 7f789f0..abe0a6d 100644
--- a/src/osgEarthFeatures/FeatureModelSource
+++ b/src/osgEarthFeatures/FeatureModelSource
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -199,9 +199,8 @@ namespace osgEarth { namespace Features
         virtual void initialize( const osgDB::Options* dbOptions =0L );
 
         virtual osg::Node* createNodeImplementation(
-            const Map*            map,
-            const osgDB::Options* dbOptions,
-            ProgressCallback*     progress );
+            const Map*        map,
+            ProgressCallback* progress );
     
 
     public:
@@ -213,12 +212,6 @@ namespace osgEarth { namespace Features
         virtual FeatureNodeFactory* createFeatureNodeFactory() =0;
 
 
-        /**
-         * Creates an implementation-specific data object to be passed to buildNodeForStyle
-         * @deprecated
-         */
-        //virtual osg::Referenced* createBuildData() { return NULL; }
-
     public: // properties:
 
         /** Sets a feature source. */
diff --git a/src/osgEarthFeatures/FeatureModelSource.cpp b/src/osgEarthFeatures/FeatureModelSource.cpp
index 875457a..da0a5f6 100644
--- a/src/osgEarthFeatures/FeatureModelSource.cpp
+++ b/src/osgEarthFeatures/FeatureModelSource.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -168,9 +168,8 @@ FeatureModelSource::initialize(const osgDB::Options* dbOptions)
 }
 
 osg::Node*
-FeatureModelSource::createNodeImplementation(const Map*            map,
-                                             const osgDB::Options* dbOptions,
-                                             ProgressCallback*     progress )
+FeatureModelSource::createNodeImplementation(const Map*        map,
+                                             ProgressCallback* progress )
 {
     // user must provide a valid map.
     if ( !map )
@@ -195,16 +194,26 @@ FeatureModelSource::createNodeImplementation(const Map*            map,
     }
 
     // Session holds data that's shared across the life of the FMG
-    Session* session = new Session( map, _options.styles().get(), _features.get(), dbOptions );
+    Session* session = new Session( 
+        map, 
+        _options.styles().get(), 
+        _features.get(), 
+        _dbOptions.get() );
+
+    // Name the session (for debugging purposes)
+    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 );
 
@@ -255,11 +264,13 @@ FeatureNodeFactory::getOrCreateStyleGroup(const Style& style,
                 (render->backfaceCulling() == true ? osg::StateAttribute::ON : osg::StateAttribute::OFF) | osg::StateAttribute::OVERRIDE );
         }
 
+#ifndef OSG_GLES2_AVAILABLE
         if ( render->clipPlane().isSet() )
         {
             GLenum mode = GL_CLIP_PLANE0 + (render->clipPlane().value());
             group->getOrCreateStateSet()->setMode(mode, 1);
         }
+#endif
 
         if ( render->minAlpha().isSet() )
         {
diff --git a/src/osgEarthFeatures/FeatureRasterizer b/src/osgEarthFeatures/FeatureRasterizer
deleted file mode 100644
index 56f15b3..0000000
--- a/src/osgEarthFeatures/FeatureRasterizer
+++ /dev/null
@@ -1,49 +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 OSGEARTHFEATURES_FEATURE_RASTERIZER_H
-#define OSGEARTHFEATURES_FEATURE_RASTERIZER_H 1
-
-#include <osgEarthFeatures/Common>
-#include <osgEarth/Config>
-#include <osgDB/ReaderWriter>
-
-namespace osgEarth { namespace Features
-{
-    class OSGEARTHFEATURES_EXPORT FeatureRasterizer : public osg::Referenced
-    {
-    public:
-        FeatureRasterizer( const osgDB::ReaderWriter::Options* options =0L );
-
-    protected:
-        osg::ref_ptr<const osgDB::ReaderWriter::Options> _options;
-    };
-
-    class OSGEARTHFEATURES_EXPORT FeatureRasterizerFactory
-    {
-    public:
-        FeatureRasterizer* create(
-            const std::string& driver,
-            const Config&      driverConf,
-            const osgDB::ReaderWriter::Options* globalOptions =NULL );
-    };
-
-} } // namespace osgEarth::Features
-
-#endif //OSGEARTHFEATURES_FEATURE_RASTERIZER_H
diff --git a/src/osgEarthFeatures/FeatureSource b/src/osgEarthFeatures/FeatureSource
index cef9764..30c1eb3 100644
--- a/src/osgEarthFeatures/FeatureSource
+++ b/src/osgEarthFeatures/FeatureSource
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -65,6 +65,10 @@ namespace osgEarth { namespace Features
         /** Explicitly overrides profile information contained in the actual data source. */
         optional<ProfileOptions>& profile() { return _profile; }
         const optional<ProfileOptions>& profile() const { return _profile; }
+        
+        /** Interpolation type to use for geodetic points */
+        optional<GeoInterpolation>& geoInterp() { return _geoInterp; }
+        const optional<GeoInterpolation>& geoInterp() const { return _geoInterp; }
 
     public:
         FeatureSourceOptions( const ConfigOptions& options =ConfigOptions() );
@@ -80,11 +84,12 @@ namespace osgEarth { namespace Features
     private:
         void fromConfig( const Config& conf );
 
-        FeatureFilterList        _filters;
-        optional<std::string>    _name;
-        optional< bool >         _openWrite;
-        optional<ProfileOptions> _profile;
-        optional<CachePolicy>    _cachePolicy;
+        FeatureFilterList          _filters;
+        optional<std::string>      _name;
+        optional< bool >           _openWrite;
+        optional<ProfileOptions>   _profile;
+        optional<CachePolicy>      _cachePolicy;
+        optional<GeoInterpolation> _geoInterp;
     };
 
     /**
@@ -140,6 +145,12 @@ namespace osgEarth { namespace Features
         virtual int getFeatureCount() const { return -1; }
 
         /**
+         * Whether the source can look up a Feature by its ID.
+         * @return True or False
+         */
+        virtual bool supportsGetFeature() const { return false; }
+
+        /**
          * Gets the Feature with the given FID
          * @return
          *     The Feature with the given FID or NULL if not found.
diff --git a/src/osgEarthFeatures/FeatureSource.cpp b/src/osgEarthFeatures/FeatureSource.cpp
index f9529c2..f5411fa 100644
--- a/src/osgEarthFeatures/FeatureSource.cpp
+++ b/src/osgEarthFeatures/FeatureSource.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -50,6 +50,8 @@ FeatureSourceOptions::fromConfig( const Config& conf )
     conf.getIfSet   ( "name",         _name );
     conf.getObjIfSet( "profile",      _profile );
     conf.getObjIfSet( "cache_policy", _cachePolicy );
+    conf.getIfSet   ( "geo_interpolation", "great_circle", _geoInterp, GEOINTERP_GREAT_CIRCLE );
+    conf.getIfSet   ( "geo_interpolation", "rhumb_line",   _geoInterp, GEOINTERP_RHUMB_LINE );
 
     const ConfigSet& children = conf.children();
     for( ConfigSet::const_iterator i = children.begin(); i != children.end(); ++i )
@@ -95,6 +97,8 @@ FeatureSourceOptions::getConfig() const
     conf.updateIfSet   ( "name",         _name );
     conf.updateObjIfSet( "profile",      _profile );
     conf.updateObjIfSet( "cache_policy", _cachePolicy );
+    conf.updateIfSet   ( "geo_interpolation", "great_circle", _geoInterp, GEOINTERP_GREAT_CIRCLE );
+    conf.updateIfSet   ( "geo_interpolation", "rhumb_line",   _geoInterp, GEOINTERP_RHUMB_LINE );
     
     for( FeatureFilterList::const_iterator i = _filters.begin(); i != _filters.end(); ++i )
     {
diff --git a/src/osgEarthFeatures/FeatureSourceIndexNode b/src/osgEarthFeatures/FeatureSourceIndexNode
index ba7a1be..19732ee 100644
--- a/src/osgEarthFeatures/FeatureSourceIndexNode
+++ b/src/osgEarthFeatures/FeatureSourceIndexNode
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -21,14 +21,20 @@
 #define OSGEARTHFEATURES_FEATURE_SOURCE_INDEX_NODE_H 1
 
 #include <osgEarthFeatures/Common>
-#include <osgEarthFeatures/FeatureDrawSet>
+#include <osgEarthFeatures/Feature>
+#include <osgEarthFeatures/FeatureIndex>
 #include <osgEarthFeatures/FeatureSource>
+#include <osgEarth/ObjectIndex>
 #include <osg/Config>
 #include <osg/Group>
 #include <osg/Drawable>
+#include <map>
+#include <set>
 
 namespace osgEarth { namespace Features
 {
+    using namespace osgEarth;
+
     /**
      * Options for a feature index
      */
@@ -37,7 +43,11 @@ namespace osgEarth { namespace Features
     public:
         FeatureSourceIndexOptions(const Config& conf =Config());
 
-        /** Wheter to embed the actual Feature objects in the index (instead of
+        /** Whether indexing is enabled. */
+        optional<bool>& enabled() { return _enabled; }
+        const optional<bool>& enabled() const { return _enabled; }
+
+        /** Whether to embed the actual Feature objects in the index (instead of
          *  just the FeatureID). This is useful for feature sources that cannot
          *  be queried by ID (e.g., streaming data like TFS) */
         optional<bool>& embedFeatures() { return _embedFeatures; }
@@ -47,123 +57,121 @@ namespace osgEarth { namespace Features
         Config getConfig() const;
 
     private:
+        optional<bool> _enabled;
         optional<bool> _embedFeatures;
     };
 
-
-    /**
-     * Interface for feature indexing.
-     */
-    class FeatureSourceIndex
+    struct RefIDPair : public osg::Referenced
     {
-    public: // tagging functions
-        virtual void tagPrimitiveSets( osg::Drawable* drawable, Feature* feature ) const =0;
-        virtual void tagNode( osg::Node* node, Feature* feature ) const =0;
-
-        virtual ~FeatureSourceIndex() { }
+        RefIDPair(FeatureID fid, ObjectID oid) : _fid(fid), _oid(oid) { }
+        FeatureID _fid;
+        ObjectID  _oid;
     };
 
     /**
-     * Maintains an index that maps FeatureID's from a FeatureSource to
-     * PrimitiveSets within the subgraph's geometry.
+     * Internal class that maintains a feature index for a single feature source.
+     * Internal - not exported!
      */
-    class OSGEARTHFEATURES_EXPORT FeatureSourceIndexNode : public osg::Group, public FeatureSourceIndex
+    class OSGEARTHFEATURES_EXPORT FeatureSourceIndex : public FeatureIndex
     {
     public:
-        /**
-         * Constructs a new index node.
-         */
-        FeatureSourceIndexNode(
-            FeatureSource*                   featureSource,
-            const FeatureSourceIndexOptions& options );
+        FeatureSourceIndex(FeatureSource* source,
+                           ObjectIndex*   masterIndex,
+                           const FeatureSourceIndexOptions& options);
 
-        virtual ~FeatureSourceIndexNode() { }
+        /** FeatureSource behind this index */
+        FeatureSource* getFeatureSource() { return _featureSource.get(); }
 
+    public: // FeatureIndex
+
+        Feature* getFeature(ObjectID oid) const;
+
+        int size() const { return _fids.size(); }
+
+    public: // Functions called by FeatureSourceIndexNode
+
+        RefIDPair* tagDrawable    (osg::Drawable* drawable, Feature* feature);
+        RefIDPair* tagAllDrawables(osg::Node*     node,     Feature* feature);
+        RefIDPair* tagNode        (osg::Node*     node,     Feature* feature);
+
+        // removes a collection of FIDs from the index. If the refcount goes to zero,
+        // remove it from the master index as well.
+        template<typename InputIter>
+        void removeFIDs(InputIter first, InputIter last)
+        {
+            Threading::ScopedMutexLock lock(_mutex);
+            for(InputIter fid = first; fid != last; ++fid )
+            {
+                FIDMap::iterator f = _fids.find( *fid );
+                if ( f != _fids.end() && f->second->referenceCount() == 1 )
+                {
+                    ObjectID oid = f->second->_oid;
+                    _oids.erase( oid );
+                    _fids.erase( f );
+                    _embeddedFeatures.erase( *fid );
+                    if ( _masterIndex.valid() )
+                        _masterIndex->remove( oid );
+                }
+            }
+        }
+        
+    public: // types
 
-    public: // FeatureSourceIndex
+        typedef std::map<ObjectID,  FeatureID>                OIDMap;
+        typedef std::map<FeatureID, osg::ref_ptr<RefIDPair> > FIDMap;
+        typedef std::map<FeatureID, osg::ref_ptr<Feature> >   FeatureMap;
 
-        /**
-         * Tags all the primitive sets in a Drawable with the specified FeatureID.
-         */
-        void tagPrimitiveSets( osg::Drawable* drawable, Feature* feature ) const;
+    protected:
+        virtual ~FeatureSourceIndex();
 
-        /**
-         * Tags a node with the specified FeatureID.
-         */
-        void tagNode( osg::Node* node, Feature* feature ) const;
+    private:
+        osg::ref_ptr<FeatureSource> _featureSource;
+        osg::ref_ptr<ObjectIndex>   _masterIndex;
+        FeatureSourceIndexOptions   _options;        
+        bool                        _embed;
+        
+        mutable Threading::Mutex _mutex;
+
+        OIDMap     _oids;
+        FIDMap     _fids;
+        FeatureMap _embeddedFeatures;
+    };
 
 
+    /**
+     * Node that houses a FeatureSourceIndex, so that it can un-register index
+     * entries when it pages out.
+     */
+    class OSGEARTHFEATURES_EXPORT FeatureSourceIndexNode : public osg::Group,
+                                                           public FeatureIndexBuilder
+    {
     public:
-        /**
-         * The feature source tied to this node 
-         */
-        FeatureSource* getFeatureSource() { return _featureSource.get(); }
+        FeatureSourceIndexNode(FeatureSourceIndex* index);
 
-        /**
-         * Traverses this node's subgraph and rebuilds the feature index based on
-         * any tagged drawables found. (See tagPrimitiveSets for tagging drawables).
-         */
-        void reindex();
-
-        /**
-         * Given a primitive set, returns the feature ID corresponding to that set.
-         *
-         * @param pset Primitive set to query
-         * @param output Holds the result of the query, if returning true
-         * @return true if successful
-         */
-        bool getFID(osg::PrimitiveSet* pset, FeatureID& output) const;
-
-        /**
-         * Gets the Feature ID corresponding to a drawable and a prim index. This is
-         * useful to call using the results of an intersection test.
-         *
-         * @param drawable       Drawable for which to lookup the feature ID
-         * @param primitiveIndex Index of the primitive to look up
-         * @param output         Holds the result of the query, if returning true
-         * @return true if successful
-         */
-        bool getFID(osg::Drawable* drawable, int primitiveIndex, FeatureID& output) const;
-
-        /**
-         * Given a FeatureID, returns the collection of drawable/primitiveset combinations
-         * corresponding to that feature.
-         *
-         * @param fid Feature ID to look up
-         * @return Corresponding collection of primitive sets (empty if the query fails)
-         */
-        FeatureDrawSet& getDrawSet( const FeatureID& fid );
-
-        /**
-         * Given a FeatureID, returns the cached feature.
-         *
-         * @param fid     Feature ID to look up
-         * @param output  cached feature 
-         * @return true if successful 
-         */
-        bool getFeature(const FeatureID& fid, const Feature*& output) const;
+        /** The index referenced by this node. */
+        FeatureSourceIndex* getIndex() { return _index.get(); }
 
-    private:
-        osg::ref_ptr<FeatureSource> _featureSource;
+        /** Fetches the entire set of FIDs registered with the index by this node. */
+        bool getAllFIDs(std::vector<FeatureID>& output) const;
 
-        typedef std::map<FeatureID, FeatureDrawSet> FeatureIDDrawSetMap;
-        FeatureIDDrawSetMap _drawSets;
+    public: // FeatureIndexBuilder
 
-        struct Collect : public osg::NodeVisitor {
-            Collect(FeatureIDDrawSetMap&);
-            void apply(osg::Node&);
-            void apply(osg::Geode&);
-            FeatureIDDrawSetMap& _index;
-            unsigned _psets;
-        };
+        ObjectID tagDrawable    (osg::Drawable* drawable, Feature* feature);
+        ObjectID tagAllDrawables(osg::Node*     node,     Feature* feature);
+        ObjectID tagNode        (osg::Node*     node,     Feature* feature);
+
+    protected:
         
-        FeatureSourceIndexOptions _options;
+        /** dtor - unregisters any FIDs added by this node. */
+        virtual ~FeatureSourceIndexNode();
 
-        typedef std::map< FeatureID, osg::ref_ptr<const Feature> > FeatureMap;
-        mutable FeatureMap _features; // cache
+    private:
+        typedef std::map<FeatureID, osg::ref_ptr<RefIDPair> > FIDMap;
+        osg::ref_ptr<FeatureSourceIndex> _index;
+        FIDMap _fids;
 
     public:
-        virtual const char* className() const { return "FeatureSourceIndexNode"; }
+        virtual const char* className()   const { return "FeatureSourceIndexNode"; }
         virtual const char* libraryName() const { return "osgEarthFeatures"; }
     };
 
diff --git a/src/osgEarthFeatures/FeatureSourceIndexNode.cpp b/src/osgEarthFeatures/FeatureSourceIndexNode.cpp
index 8ef01a0..393e1e2 100644
--- a/src/osgEarthFeatures/FeatureSourceIndexNode.cpp
+++ b/src/osgEarthFeatures/FeatureSourceIndexNode.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -17,25 +17,46 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>
  */
 #include <osgEarthFeatures/FeatureSourceIndexNode>
-#include <osg/MatrixTransform>
+#include <osgEarth/Registry>
 #include <algorithm>
 
 using namespace osgEarth;
 using namespace osgEarth::Features;
 
-#define LC "[FeatureSourceIndexNode] "
-
 // for testing:
 //#undef  OE_DEBUG
 //#define OE_DEBUG OE_INFO
 
+namespace
+{
+    // nifty template to iterate over a map's keys
+    template<typename T>
+    struct KeyIter : public T::iterator
+    {
+        KeyIter() : T::iterator() { }
+        KeyIter(typename T::iterator i) : T::iterator(i) { }
+        typename T::key_type* operator->() { return (typename T::key_type* const)&(T::iterator::operator->()->first); }
+        typename T::key_type operator*() { return T::iterator::operator*().first; }
+    };
+
+    template<typename T>
+    struct ConstKeyIter : public T::const_iterator
+    {
+        ConstKeyIter() : T::const_iterator() { }
+        ConstKeyIter(typename T::const_iterator i) : T::const_iterator(i) { }
+        typename T::key_type* operator->() { return (typename T::key_type* const)&(T::const_iterator::operator->()->first); }
+        typename T::key_type operator*() { return T::const_iterator::operator*().first; }
+    };
+}
 
 //-----------------------------------------------------------------------------
 
 
 FeatureSourceIndexOptions::FeatureSourceIndexOptions(const Config& conf) :
+_enabled      ( true ),
 _embedFeatures( false )
 {
+    conf.getIfSet( "enabled",        _enabled );
     conf.getIfSet( "embed_features", _embedFeatures );
 }
 
@@ -43,235 +64,231 @@ Config
 FeatureSourceIndexOptions::getConfig() const
 {
     Config conf("feature_indexing");
+    conf.addIfSet( "enabled",        _enabled );
     conf.addIfSet( "embed_features", _embedFeatures );
     return conf;
 }
 
-
 //-----------------------------------------------------------------------------
 
-FeatureSourceIndexNode::Collect::Collect( FeatureIDDrawSetMap& index ) :
-osg::NodeVisitor( osg::NodeVisitor::TRAVERSE_ALL_CHILDREN ),
-_index          ( index ),
-_psets          ( 0 )
-{
-    _index.clear();
-}
+#undef  LC
+#define LC "[FeatureSourceIndexNode] "
 
-void
-FeatureSourceIndexNode::Collect::apply( osg::Node& node )
+FeatureSourceIndexNode::FeatureSourceIndexNode(FeatureSourceIndex* index) :
+_index( index )
 {
-    RefFeatureID* fid = dynamic_cast<RefFeatureID*>( node.getUserData() );
-    if ( fid )
+    if ( !index )
     {
-        FeatureDrawSet& drawSet = _index[*fid];
-        drawSet.nodes().push_back( &node );
+        OE_WARN << LC << "INTERNAL ERROR: created a feature source index node with a NULL index.\n";
     }
-    traverse(node);
 }
 
-void
-FeatureSourceIndexNode::Collect::apply( osg::Geode& geode )
+FeatureSourceIndexNode::~FeatureSourceIndexNode()
 {
-    RefFeatureID* fid = dynamic_cast<RefFeatureID*>( geode.getUserData() );
-    if ( fid )
+    if ( _index.valid() )
     {
-        FeatureDrawSet& drawSet = _index[*fid];
-        drawSet.nodes().push_back( &geode );
-    }
-    else
-    {
-        for( unsigned i = 0; i < geode.getNumDrawables(); ++i )
-        {
-            osg::Geometry* geom = dynamic_cast<osg::Geometry*>( geode.getDrawable(i) );
-            if ( geom )
-            {
-                osg::Geometry::PrimitiveSetList& psets = geom->getPrimitiveSetList();
-                for( unsigned p = 0; p < psets.size(); ++p )
-                {
-                    osg::PrimitiveSet* pset = psets[p];
-                    RefFeatureID* fid = dynamic_cast<RefFeatureID*>( pset->getUserData() );
-                    if ( fid )
-                    {
-                        FeatureDrawSet& drawSet = _index[*fid];
-                        drawSet.getOrCreateSlice(geom).push_back(pset);
-                        _psets++;
-                    }
-                }
-            }
-        }
-    }
+        // must copy and clear the original list first to dereference the RefIDPair instances.
+        std::set<FeatureID> fidsToRemove;
+        fidsToRemove.insert( KeyIter<FIDMap>(_fids.begin()), KeyIter<FIDMap>(_fids.end()) );
+        _fids.clear();
 
-    // NO traverse.
+        OE_DEBUG << LC << "Removing " << fidsToRemove.size() << " fids\n";
+        _index->removeFIDs( fidsToRemove.begin(), fidsToRemove.end() );
+    }
 }
 
-//-----------------------------------------------------------------------------
-
-FeatureSourceIndexNode::FeatureSourceIndexNode(FeatureSource*                   featureSource, 
-                                               const FeatureSourceIndexOptions& options) :
-_featureSource( featureSource ), 
-_options      ( options )
+ObjectID
+FeatureSourceIndexNode::tagDrawable(osg::Drawable* drawable, Feature* feature)
 {
-    //nop
+    if ( !feature || !_index.valid() ) return OSGEARTH_OBJECTID_EMPTY;
+    RefIDPair* r = _index->tagDrawable( drawable, feature );
+    if ( r ) _fids[ feature->getFID() ] = r;
+    return r ? r->_oid : OSGEARTH_OBJECTID_EMPTY;
 }
 
+ObjectID
+FeatureSourceIndexNode::tagAllDrawables(osg::Node* node, Feature* feature)
+{
+    if ( !feature || !_index.valid() ) return OSGEARTH_OBJECTID_EMPTY;
+    RefIDPair* r = _index->tagAllDrawables( node, feature );
+    if ( r ) _fids[ feature->getFID() ] = r;
+    return r ? r->_oid : OSGEARTH_OBJECTID_EMPTY;
+}
 
-// Rebuilds the feature index based on all the tagged primitive sets found in a graph
-void
-FeatureSourceIndexNode::reindex()
+ObjectID
+FeatureSourceIndexNode::tagNode(osg::Node* node, Feature* feature)
 {
-    _drawSets.clear();
+    if ( !feature || !_index.valid() ) return OSGEARTH_OBJECTID_EMPTY;
+    RefIDPair* r = _index->tagNode( node, feature );
+    if ( r ) _fids[ feature->getFID() ] = r;
+    return r ? r->_oid : OSGEARTH_OBJECTID_EMPTY;
+}
 
-    Collect c(_drawSets);
-    this->accept( c );
+bool
+FeatureSourceIndexNode::getAllFIDs(std::vector<FeatureID>& output) const
+{
+    ConstKeyIter<FIDMap> start( _fids.begin() );
+    ConstKeyIter<FIDMap> end  ( _fids.end() );
+    for(ConstKeyIter<FIDMap> i = start; i != end; ++i )
+    {
+        output.push_back( *i );
+    }
 
-    OE_DEBUG << LC << "Reindexed; draw sets = " << _drawSets.size() << std::endl;
+    return true;
 }
 
+//-----------------------------------------------------------------------------
 
-// Tags all the primitive sets in a Drawable with the specified FeatureID
-void
-FeatureSourceIndexNode::tagPrimitiveSets(osg::Drawable* drawable, Feature* feature) const
+#undef  LC
+#define LC "[FeatureSourceIndex] "
+
+FeatureSourceIndex::FeatureSourceIndex(FeatureSource* featureSource, 
+                                       ObjectIndex*   index,
+                                       const FeatureSourceIndexOptions& options) :
+_featureSource  ( featureSource ), 
+_masterIndex    ( index ),
+_options        ( options )
 {
-    if ( drawable == 0L )
-        return;
+    _embed = 
+        _options.embedFeatures() == true ||
+        featureSource == 0L ||
+        featureSource->supportsGetFeature() == false;
+}
 
-    osg::Geometry* geom = drawable->asGeometry();
-    if ( !geom )
-        return;
+FeatureSourceIndex::~FeatureSourceIndex()
+{
+    if ( _masterIndex.valid() && !_oids.empty() )
+    {
+        // remove all OIDs from the master index.
+        _masterIndex->remove( KeyIter<OIDMap>(_oids.begin()), KeyIter<OIDMap>(_oids.end()) );
+    }
 
-    RefFeatureID* rfid = 0L;
+    _oids.clear();
+    _fids.clear();
+    _embeddedFeatures.clear();
+}
 
-    osg::Geometry::PrimitiveSetList& plist = geom->getPrimitiveSetList();
-    for( osg::Geometry::PrimitiveSetList::iterator p = plist.begin(); p != plist.end(); ++p )
-    {
-        if ( !rfid )
-            rfid = new RefFeatureID(feature->getFID());
+RefIDPair*
+FeatureSourceIndex::tagDrawable(osg::Drawable* drawable, Feature* feature)
+{
+    if ( !feature ) return 0L;
 
-        p->get()->setUserData( rfid );
+    Threading::ScopedMutexLock lock(_mutex);
+    
+    RefIDPair* p = 0L;
+    FeatureID fid = feature->getFID();
 
-        if ( _options.embedFeatures() == true )
+    FIDMap::const_iterator f = _fids.find( fid );
+    if ( f != _fids.end() )
+    {
+        ObjectID oid = f->second->_oid;
+        _masterIndex->tagDrawable( drawable, oid );
+        p = f->second.get();
+    }
+    else
+    {
+        ObjectID oid = _masterIndex->tagDrawable( drawable, this );
+        p = new RefIDPair( fid, oid );
+        _fids[fid] = p;
+        _oids[oid] = fid;
+    
+        if ( _embed )
         {
-            _features[feature->getFID()] = feature;
+            _embeddedFeatures[fid] = feature;
         }
     }
-}
 
+    return p;
+}
 
-void
-FeatureSourceIndexNode::tagNode( osg::Node* node, Feature* feature ) const
+RefIDPair*
+FeatureSourceIndex::tagAllDrawables(osg::Node* node, Feature* feature)
 {
-    node->setUserData( new RefFeatureID(feature->getFID()) );
+    if ( !feature ) return 0L;
+
+    Threading::ScopedMutexLock lock(_mutex);
+    
+    RefIDPair* p = 0L;
+    FeatureID fid = feature->getFID();
 
-    if ( _options.embedFeatures() == true )
+    FIDMap::const_iterator f = _fids.find( fid );
+    if ( f != _fids.end() )
     {
-        _features[feature->getFID()] = feature;
+        ObjectID oid = f->second->_oid;
+        _masterIndex->tagAllDrawables( node, oid );
+        p = f->second.get();
     }
-}
-
-
-bool
-FeatureSourceIndexNode::getFID(osg::PrimitiveSet* primSet, FeatureID& output) const
-{
-    const RefFeatureID* fid = dynamic_cast<const RefFeatureID*>( primSet->getUserData() );
-    if ( fid )
+    else
     {
-        output = *fid;
-        return true;
+        ObjectID oid = _masterIndex->tagAllDrawables( node, this );
+        p = new RefIDPair( fid, oid );
+        _fids[fid] = p;
+        _oids[oid] = fid;
+    
+        if ( _embed )
+        {
+            _embeddedFeatures[fid] = feature;
+        }
     }
 
-    OE_DEBUG << LC << "getFID failed b/c the primSet was not tagged with a RefFeatureID" << std::endl;
-    return false;
+    return p;
 }
 
-
-bool
-FeatureSourceIndexNode::getFID(osg::Drawable* drawable, int primIndex, FeatureID& output) const
+RefIDPair*
+FeatureSourceIndex::tagNode(osg::Node* node, Feature* feature)
 {
-    if ( drawable == 0L || primIndex < 0 )
-        return false;
+    if ( !feature ) return 0L;
 
-    for( FeatureIDDrawSetMap::const_iterator i = _drawSets.begin(); i != _drawSets.end(); ++i )
+    Threading::ScopedMutexLock lock(_mutex);
+    
+    RefIDPair* p = 0L;
+    FeatureID fid = feature->getFID();
+    ObjectID oid;
+
+    FIDMap::const_iterator f = _fids.find( fid );
+    if ( f != _fids.end() )
     {
-        const FeatureDrawSet& drawSet = i->second;
-        FeatureDrawSet::DrawableSlices::const_iterator d = drawSet.slice(drawable);
-        if ( d != drawSet.slices().end() )
-        {
-            const osg::Geometry* geom = drawable->asGeometry();
-            if ( geom )
-            {
-                const osg::Geometry::PrimitiveSetList& geomPrimSets = geom->getPrimitiveSetList();
-
-                unsigned encounteredPrims = 0;
-                for( osg::Geometry::PrimitiveSetList::const_iterator p = geomPrimSets.begin(); p != geomPrimSets.end(); ++p )
-                {
-                    const osg::PrimitiveSet* pset = p->get();
-                    unsigned numPrims = pset->getNumPrimitives();
-                    encounteredPrims += numPrims;
-
-                    if ( encounteredPrims > (unsigned)primIndex )
-                    {
-                        const RefFeatureID* fid = dynamic_cast<const RefFeatureID*>( pset->getUserData() );
-                        if ( fid )
-                        {
-                            output = *fid;
-                            return true;
-                        }
-                        else
-                        {
-                            OE_DEBUG << LC << "INTERNAL: found primset, but it's not tagged with a FID" << std::endl;
-                            return false;
-                        }
-                    }
-                }
-            }
-        }
+        oid = f->second->_oid;
+        _masterIndex->tagNode( node, oid );
+        p = f->second.get();
     }
-
-    // see if we have a node in the path
-    for( osg::Node* node = drawable->getParent(0); node != 0L; node = (node->getNumParents()>0?node->getParent(0):0L) )
+    else
     {
-        RefFeatureID* fid = dynamic_cast<RefFeatureID*>( node->getUserData() );
-        if ( fid )
+        oid = _masterIndex->tagNode( node, this );
+        p = new RefIDPair( fid, oid );
+        _fids[fid] = p;
+        _oids[oid] = fid;
+    
+        if ( _embed )
         {
-            output = *fid;
-            return true;
+            _embeddedFeatures[fid] = feature;
         }
     }
 
-    return false;
-}
-
+    OE_DEBUG << LC << "Tagging feature ID = " << fid << " => " << oid << " (" << feature->getString("name") << ")\n";
 
-
-FeatureDrawSet&
-FeatureSourceIndexNode::getDrawSet(const FeatureID& fid )
-{
-    static FeatureDrawSet s_empty;
-
-    FeatureIDDrawSetMap::iterator i = _drawSets.find(fid);
-    return i != _drawSets.end() ? i->second : s_empty;
+    return p;
 }
 
-
-bool
-FeatureSourceIndexNode::getFeature(const FeatureID& fid, const Feature*& output) const
+Feature*
+FeatureSourceIndex::getFeature(ObjectID oid) const
 {
-    if ( _options.embedFeatures() == true )
+    Feature* feature = 0L;
+    Threading::ScopedMutexLock lock(_mutex);
+    OIDMap::const_iterator i = _oids.find( oid );
+    if ( i != _oids.end() )
     {
-        FeatureMap::const_iterator f = _features.find(fid);
+        FeatureID fid = i->second;
 
-        if(f != _features.end())
+        if ( _embed )
         {
-            output = f->second.get();
-            return output != 0L;
+            FeatureMap::const_iterator j = _embeddedFeatures.find( fid );
+            feature = j != _embeddedFeatures.end() ? j->second.get() : 0L;
+        }
+        else if ( _featureSource.valid() && _featureSource->supportsGetFeature() )
+        {
+            feature = _featureSource->getFeature( fid );
         }
     }
-    else if ( _featureSource.valid() )
-    {
-        output = _featureSource->getFeature( fid );
-        return output != 0L;
-    }
-
-    return false;
+    return feature;
 }
diff --git a/src/osgEarthFeatures/FeatureTileSource b/src/osgEarthFeatures/FeatureTileSource
index 94e566f..32adf72 100644
--- a/src/osgEarthFeatures/FeatureTileSource
+++ b/src/osgEarthFeatures/FeatureTileSource
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -105,13 +105,17 @@ namespace osgEarth { namespace Features
 
     protected:
 
+        /** Custom image allocation bu the subclass. Default image is getPixelsPerTile() RGBA. */
+        virtual osg::Image* allocateImage() { return NULL; }
+
         /** Creates an implementation-specific data object to be passed to buildNodeForStyle */
-        virtual osg::Referenced* createBuildData() {
-            return NULL; }     
+        virtual osg::Referenced* createBuildData() { return NULL; }     
 
         /**
          * Creates OSG graph(s) representing the specified feature list.
          *
+         * @param session
+         *      Feature processing session (shared)
          * @param style
          *      Styling information for the feature geometry
          * @param features
@@ -124,11 +128,20 @@ namespace osgEarth { namespace Features
          * @return true if the rendering succeeded, false if the out_image did not change.
          */
         virtual bool renderFeaturesForStyle(
+            Session*           session,
+            const Style&       style,
+            const FeatureList& features,
+            osg::Referenced*   buildData,
+            const GeoExtent&   imageExtent,
+            osg::Image*        out_image ) { return renderFeaturesForStyle(style, features, buildData, imageExtent, out_image); }
+
+        /** Backwards compatibility */
+        virtual bool renderFeaturesForStyle(
             const Style&       style,
             const FeatureList& features,
             osg::Referenced*   buildData,
             const GeoExtent&   imageExtent,
-            osg::Image*        out_image ) { return false; }            
+            osg::Image*        out_image ) { return false; }
             
         /**
          * Optional implementation hook to pre-process an image tile before any calls to 
@@ -165,6 +178,7 @@ namespace osgEarth { namespace Features
         //osg::ref_ptr<const FeatureTileSourceOptions> _options;
         osg::ref_ptr<const osgEarth::Map> _map;
         bool _initialized;
+        osg::ref_ptr<Session> _session;
         
         bool queryAndRenderFeaturesForStyle(
             const Style&     style,
diff --git a/src/osgEarthFeatures/FeatureTileSource.cpp b/src/osgEarthFeatures/FeatureTileSource.cpp
index 6243594..44a5001 100644
--- a/src/osgEarthFeatures/FeatureTileSource.cpp
+++ b/src/osgEarthFeatures/FeatureTileSource.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -133,6 +133,9 @@ FeatureTileSource::initialize(const osgDB::Options* dbOptions)
         return Status::Error("No FeatureSource provided; nothing will be rendered");
     }
 
+    // Create a session for feature processing. No map.
+    _session = new Session( 0L, _options.styles().get(), _features.get(), dbOptions );
+
     _initialized = true;
     return STATUS_OK;
 }
@@ -163,8 +166,12 @@ FeatureTileSource::createImage( const TileKey& key, ProgressCallback* progress )
     osg::ref_ptr<osg::Referenced> buildData = createBuildData();
 
     // allocate the image.
-    osg::ref_ptr<osg::Image> image = new osg::Image();
-    image->allocateImage( getPixelsPerTile(), getPixelsPerTile(), 1, GL_RGBA, GL_UNSIGNED_BYTE );
+    osg::ref_ptr<osg::Image> image = allocateImage();
+    if ( !image.valid() )
+    {
+        image = new osg::Image();
+        image->allocateImage( getPixelsPerTile(), getPixelsPerTile(), 1, GL_RGBA, GL_UNSIGNED_BYTE );
+    }
 
     preProcess( image.get(), buildData.get() );
 
@@ -180,9 +187,14 @@ FeatureTileSource::createImage( const TileKey& key, ProgressCallback* progress )
             {
                 FeatureList list;
                 list.push_back( feature );
-                renderFeaturesForStyle( 
-                    *feature->style(), list, buildData.get(),
-                    key.getExtent(), image.get() );
+
+                renderFeaturesForStyle(
+                    _session.get(),
+                    *feature->style(),
+                    list,
+                    buildData.get(),
+                    key.getExtent(),
+                    image.get() );
             }
         }
     }
@@ -273,7 +285,7 @@ FeatureTileSource::queryAndRenderFeaturesForStyle(const Style&     style,
         //    << queryExtent.toString() << ")"
         //    << std::endl;
 
-        return renderFeaturesForStyle( style, cellFeatures, data, imageExtent, out_image );
+        return renderFeaturesForStyle( _session.get(), style, cellFeatures, data, imageExtent, out_image );
     }
     else
     {
diff --git a/src/osgEarthFeatures/Filter b/src/osgEarthFeatures/Filter
index 5e83573..9a3ea8f 100644
--- a/src/osgEarthFeatures/Filter
+++ b/src/osgEarthFeatures/Filter
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthFeatures/Filter.cpp b/src/osgEarthFeatures/Filter.cpp
index daf6ed5..1d847d2 100644
--- a/src/osgEarthFeatures/Filter.cpp
+++ b/src/osgEarthFeatures/Filter.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -299,26 +299,11 @@ FeaturesToNodeFilter::applyLineSymbology(osg::StateSet*    stateset,
 
         if ( line->stroke()->stipplePattern().isSet() )
         {
-#if 1
             stateset->setAttributeAndModes(
                 new osg::LineStipple(
                     line->stroke()->stippleFactor().value(),
                     line->stroke()->stipplePattern().value()),
                 osg::StateAttribute::ON );
-#else
-            // goofing around...
-            const char* frag =
-                "#version 110\n"
-                "void oe_stipple_frag(inout vec4 color) {\n"
-                "    float x = mod(gl_FragCoord.x, 5.0);\n"
-                "    float y = mod(gl_FragCoord.y, 5.0);\n"
-                "    if (x < y)\n"
-                "        color.a = 0.0;\n"
-                "}\n";
-
-            VirtualProgram* vp = VirtualProgram::getOrCreate(stateset);
-            vp->setFunction("oe_stipple_frag", frag, ShaderComp::LOCATION_FRAGMENT_COLORING);
-#endif
         }
     }
 }
diff --git a/src/osgEarthFeatures/FilterContext b/src/osgEarthFeatures/FilterContext
index d48688c..4cc6c45 100644
--- a/src/osgEarthFeatures/FilterContext
+++ b/src/osgEarthFeatures/FilterContext
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -28,6 +28,7 @@
 #include <osgEarth/ShaderUtils>
 #include <osg/Matrix>
 #include <list>
+#include <vector>
 
 namespace osgEarth { namespace Features
 {
@@ -35,7 +36,7 @@ namespace osgEarth { namespace Features
     using namespace osgEarth::Symbology;
     class Session;
     class FeatureProfile;
-    class FeatureSourceIndex;
+    class FeatureIndexBuilder;
 
     /**
      * Context within which a chain of filters is executed.
@@ -47,7 +48,7 @@ namespace osgEarth { namespace Features
             Session*              session       =0L,
             const FeatureProfile* profile       =0L,
             const GeoExtent&      workingExtent =GeoExtent::INVALID,
-            FeatureSourceIndex*   index         =0L);
+            FeatureIndexBuilder*  index         =0L);
 
         FilterContext( const FilterContext& rhs );
         
@@ -60,11 +61,6 @@ namespace osgEarth { namespace Features
         void setResourceCache( ResourceCache* value ) { _resourceCache = value; }
 
         /**
-         * Sets the feature index that the filter chain should populate
-         */
-        void setFeatureIndex( FeatureSourceIndex* value ) { _index = value; }
-
-        /**
          * Sets the profile (SRS etc) of the feature data
          */
         void setProfile( const FeatureProfile* profile );
@@ -97,8 +93,8 @@ namespace osgEarth { namespace Features
         /**
          * The feature index
          */
-        FeatureSourceIndex* featureIndex() { return _index; }
-        const FeatureSourceIndex* featureIndex() const { return _index; }
+        FeatureIndexBuilder* featureIndex() { return _index; }
+        const FeatureIndexBuilder* featureIndex() const { return _index; }
 
         /**
          * Whether this context has a non-identity reference frame
@@ -177,6 +173,13 @@ namespace osgEarth { namespace Features
          */
         const osgDB::Options* getDBOptions() const;
 
+        /**
+         * Gets a "history" string for debugging purposes
+         */
+        std::string getHistory() const;
+
+        void pushHistory(const std::string& value) { _history.push_back(value); }
+
     protected:
         osg::ref_ptr<Session>              _session;
         osg::ref_ptr<const FeatureProfile> _profile;
@@ -185,8 +188,9 @@ namespace osgEarth { namespace Features
         osg::Matrixd                       _referenceFrame;
         osg::Matrixd                       _inverseReferenceFrame;
         osg::ref_ptr<ResourceCache>        _resourceCache;
-        FeatureSourceIndex*                _index;
+        FeatureIndexBuilder*               _index;
         optional<ShaderPolicy>             _shaderPolicy;
+        std::vector<std::string>           _history;
     };
 
 } } // namespace osgEarth::Features
diff --git a/src/osgEarthFeatures/FilterContext.cpp b/src/osgEarthFeatures/FilterContext.cpp
index 14baaa3..0a1bbad 100644
--- a/src/osgEarthFeatures/FilterContext.cpp
+++ b/src/osgEarthFeatures/FilterContext.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -26,7 +26,7 @@ using namespace osgEarth::Features;
 FilterContext::FilterContext(Session*               session,
                              const FeatureProfile*  profile,
                              const GeoExtent&       workingExtent,
-                             FeatureSourceIndex*    index ) :
+                             FeatureIndexBuilder*   index ) :
 _session     ( session ),
 _profile     ( profile ),
 _extent      ( workingExtent, workingExtent ),
@@ -61,6 +61,12 @@ _shaderPolicy( osgEarth::SHADERPOLICY_GENERATE )
     {
         _extent = session->getMapInfo().getProfile()->getExtent();
     }
+
+    // if the session is set, push its name as the first bc.
+    if ( _session.valid() )
+    {
+        pushHistory( _session->getName() );
+    }
 }
 
 FilterContext::FilterContext( const FilterContext& rhs ) :
@@ -72,7 +78,8 @@ _referenceFrame       ( rhs._referenceFrame ),
 _inverseReferenceFrame( rhs._inverseReferenceFrame ),
 _resourceCache        ( rhs._resourceCache.get() ),
 _index                ( rhs._index ),
-_shaderPolicy         ( rhs._shaderPolicy )
+_shaderPolicy         ( rhs._shaderPolicy ),
+_history              ( rhs._history )
 {
     //nop
 }
@@ -149,12 +156,25 @@ FilterContext::toString() const
 
     buf << std::fixed
         << "CONTEXT: ["
-        << "profile extent = "  << profile()->getExtent().toString()
+        << "profile extent = "   << profile()->getExtent().toString()
         << ", working extent = " << extent()->toString()
         << ", geocentric = "     << osgEarth::toString(_isGeocentric)
+        << ", history = "        << getHistory()
         << "]";
 
     std::string str;
     str = buf.str();
     return str;
 }
+
+std::string
+FilterContext::getHistory() const
+{
+    std::stringstream buf;
+    for(unsigned i=0; i<_history.size(); ++i)
+    {
+        if ( i > 0 ) buf << " : ";
+        buf << _history[i];
+    }
+    return buf.str();
+}
diff --git a/src/osgEarthFeatures/GeometryCompiler b/src/osgEarthFeatures/GeometryCompiler
index 4e8ab90..9e295c2 100644
--- a/src/osgEarthFeatures/GeometryCompiler
+++ b/src/osgEarthFeatures/GeometryCompiler
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -96,6 +96,14 @@ namespace osgEarth { namespace Features
         optional<bool>& optimizeStateSharing() { return _optimizeStateSharing; }
         const optional<bool>& optimizeStateSharing() const { return _optimizeStateSharing; }
 
+        /** Whether to run the optimizer on the resulting group. */
+        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
+        purposes and will dump issues to the console. */
+        optional<bool>& validate() { return _validate; }
+        const optional<bool>& validate() const { return _validate; }
 
     public:
         Config getConfig() const;
@@ -115,6 +123,8 @@ namespace osgEarth { namespace Features
         optional<ShaderPolicy>         _shaderPolicy;
         optional<bool>                 _useTextureArrays;
         optional<bool>                 _optimizeStateSharing;
+        optional<bool>                 _optimize;
+        optional<bool>                 _validate;
 
         void fromConfig( const Config& conf );
 
diff --git a/src/osgEarthFeatures/GeometryCompiler.cpp b/src/osgEarthFeatures/GeometryCompiler.cpp
index 4386034..9bfd420 100644
--- a/src/osgEarthFeatures/GeometryCompiler.cpp
+++ b/src/osgEarthFeatures/GeometryCompiler.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -37,6 +37,8 @@
 #include <osg/MatrixTransform>
 #include <osg/Timer>
 #include <osgDB/WriteFile>
+#include <osgUtil/Optimizer>
+
 
 #define LC "[GeometryCompiler] "
 
@@ -48,43 +50,6 @@ using namespace osgEarth::Symbology;
 
 //-----------------------------------------------------------------------
 
-namespace
-{
-    /**
-     * Visitor that will exaggerate the bounding box of each Drawable
-     * in the scene graph to encompass a local high and low mark. We use this
-     * to support GPU-clamping, which will move vertex positions in the 
-     * GPU code. Since OSG is not aware of this movement, it may inadvertenly
-     * cull geometry which is actually visible.
-     */
-    struct OverlayGeometryAdjuster : public osg::NodeVisitor
-    {
-        float _low, _high;
-
-        OverlayGeometryAdjuster(float low, float high)
-            : osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN), _low(low), _high(high) { }
-
-        void apply(osg::Geode& geode)
-        {
-            for( unsigned i=0; i<geode.getNumDrawables(); ++i )
-            {
-                osg::Drawable* d = geode.getDrawable(i);
-
-                osg::BoundingBox bbox = Utils::getBoundingBox(d);
-
-                if ( bbox.zMin() > _low )
-                    bbox.expandBy( osg::Vec3f(bbox.xMin(), bbox.yMin(), _low) );
-                if ( bbox.zMax() < _high )
-                    bbox.expandBy( osg::Vec3f(bbox.xMax(), bbox.yMax(), _high) );
-                d->setInitialBound( bbox );
-                d->dirtyBound();
-            }
-        }
-    };
-}
-
-//-----------------------------------------------------------------------
-
 GeometryCompilerOptions GeometryCompilerOptions::s_defaults(true);
 
 void
@@ -104,7 +69,9 @@ _useVertexBufferObjects( true ),
 _useTextureArrays      ( true ),
 _shaderPolicy          ( SHADERPOLICY_GENERATE ),
 _geoInterp             ( GEOINTERP_GREAT_CIRCLE ),
-_optimizeStateSharing  ( true )
+_optimizeStateSharing  ( true ),
+_optimize              ( false ),
+_validate              ( false )
 {
    //nop
 }
@@ -122,7 +89,9 @@ _useVertexBufferObjects( s_defaults.useVertexBufferObjects().value() ),
 _useTextureArrays      ( s_defaults.useTextureArrays().value() ),
 _shaderPolicy          ( s_defaults.shaderPolicy().value() ),
 _geoInterp             ( s_defaults.geoInterp().value() ),
-_optimizeStateSharing  ( s_defaults.optimizeStateSharing().value() )
+_optimizeStateSharing  ( s_defaults.optimizeStateSharing().value() ),
+_optimize              ( s_defaults.optimize().value() ),
+_validate              ( s_defaults.validate().value() )
 {
     fromConfig(_conf);
 }
@@ -141,6 +110,8 @@ GeometryCompilerOptions::fromConfig( const Config& conf )
     conf.getIfSet   ( "use_vbo", _useVertexBufferObjects);
     conf.getIfSet   ( "use_texture_arrays", _useTextureArrays );
     conf.getIfSet   ( "optimize_state_sharing", _optimizeStateSharing );
+    conf.getIfSet   ( "optimize", _optimize );
+    conf.getIfSet   ( "validate", _validate );
 
     conf.getIfSet( "shader_policy", "disable",  _shaderPolicy, SHADERPOLICY_DISABLE );
     conf.getIfSet( "shader_policy", "inherit",  _shaderPolicy, SHADERPOLICY_INHERIT );
@@ -162,6 +133,8 @@ GeometryCompilerOptions::getConfig() const
     conf.addIfSet   ( "use_vbo", _useVertexBufferObjects);
     conf.addIfSet   ( "use_texture_arrays", _useTextureArrays );
     conf.addIfSet   ( "optimize_state_sharing", _optimizeStateSharing );
+    conf.addIfSet   ( "optimize", _optimize );
+    conf.addIfSet   ( "validate", _validate );
 
     conf.addIfSet( "shader_policy", "disable",  _shaderPolicy, SHADERPOLICY_DISABLE );
     conf.addIfSet( "shader_policy", "inherit",  _shaderPolicy, SHADERPOLICY_INHERIT );
@@ -245,10 +218,15 @@ GeometryCompiler::compile(FeatureList&          workingSet,
     unsigned p_features = workingSet.size();
 #endif
 
+    // for debugging/validation.
+    std::vector<std::string> history;
+    bool trackHistory = (_options.validate() == true);
+
     osg::ref_ptr<osg::Group> resultGroup = new osg::Group();
 
     // create a filter context that will track feature data through the process
     FilterContext sharedCX = context;
+
     if ( !sharedCX.extent().isSet() && sharedCX.profile() )
     {
         sharedCX.extent() = sharedCX.profile()->getExtent();
@@ -270,12 +248,25 @@ GeometryCompiler::compile(FeatureList&          workingSet,
     const IconSymbol*      icon      = style.get<IconSymbol>();
     const ModelSymbol*     model     = style.get<ModelSymbol>();
 
-    // check whether we need tessellation:
-    if ( line && line->tessellation().isSet() )
+    // Perform tessellation first.
+    if ( line )
     {
-        TemplateFeatureFilter<TessellateOperator> filter;
-        filter.setNumPartitions( *line->tessellation() );
-        sharedCX = filter.push( workingSet, sharedCX );
+        if ( line->tessellation().isSet() )
+        {
+            TemplateFeatureFilter<TessellateOperator> filter;
+            filter.setNumPartitions( *line->tessellation() );
+            filter.setDefaultGeoInterp( _options.geoInterp().get() );
+            sharedCX = filter.push( workingSet, sharedCX );
+            if ( trackHistory ) history.push_back( "tessellation" );
+        }
+        else if ( line->tessellationSize().isSet() )
+        {
+            TemplateFeatureFilter<TessellateOperator> filter;
+            filter.setMaxPartitionSize( *line->tessellationSize() );
+            filter.setDefaultGeoInterp( _options.geoInterp().get() );
+            sharedCX = filter.push( workingSet, sharedCX );
+            if ( trackHistory ) history.push_back( "tessellationSize" );
+        }
     }
 
     // if the style was empty, use some defaults based on the geometry type of the
@@ -317,7 +308,8 @@ GeometryCompiler::compile(FeatureList&          workingSet,
         {
             resample.maxLength() = *_options.resampleMaxLength();
         }                   
-        sharedCX = resample.push( workingSet, sharedCX );        
+        sharedCX = resample.push( workingSet, sharedCX ); 
+        if ( trackHistory ) history.push_back( "resample" );
     }    
     
     // check whether we need to do elevation clamping:
@@ -332,6 +324,8 @@ GeometryCompiler::compile(FeatureList&          workingSet,
     // marker substitution -- to be deprecated in favor of model/icon
     if ( marker )
     {
+        if ( trackHistory ) history.push_back( "marker" );
+
         // use a separate filter context since we'll be munging the data
         FilterContext markerCX = sharedCX;
 
@@ -343,11 +337,13 @@ GeometryCompiler::compile(FeatureList&          workingSet,
             scatter.setRandom( marker->placement() == MarkerSymbol::PLACEMENT_RANDOM );
             scatter.setRandomSeed( *marker->randomSeed() );
             markerCX = scatter.push( workingSet, markerCX );
+            if ( trackHistory ) history.push_back( "scatter" );
         }
         else if ( marker->placement() == MarkerSymbol::PLACEMENT_CENTROID )
         {
             CentroidFilter centroid;
-            centroid.push( workingSet, markerCX );
+            markerCX = centroid.push( workingSet, markerCX );  
+            if ( trackHistory ) history.push_back( "centroid" );
         }
 
         if ( altRequired )
@@ -355,6 +351,7 @@ GeometryCompiler::compile(FeatureList&          workingSet,
             AltitudeFilter clamp;
             clamp.setPropertiesFromStyle( style );
             markerCX = clamp.push( workingSet, markerCX );
+            if ( trackHistory ) history.push_back( "altitude" );
 
             // don't set this; we changed the input data.
             //altRequired = false;
@@ -372,6 +369,7 @@ GeometryCompiler::compile(FeatureList&          workingSet,
         osg::Node* node = sub.push( workingSet, markerCX );
         if ( node )
         {
+            if ( trackHistory ) history.push_back( "substitute" );
             resultGroup->addChild( node );
         }
     }
@@ -383,6 +381,8 @@ GeometryCompiler::compile(FeatureList&          workingSet,
 
         // use a separate filter context since we'll be munging the data
         FilterContext localCX = sharedCX;
+        
+        if ( trackHistory ) history.push_back( "model");
 
         if ( instance->placement() == InstanceSymbol::PLACEMENT_RANDOM   ||
              instance->placement() == InstanceSymbol::PLACEMENT_INTERVAL )
@@ -392,11 +392,13 @@ GeometryCompiler::compile(FeatureList&          workingSet,
             scatter.setRandom( instance->placement() == InstanceSymbol::PLACEMENT_RANDOM );
             scatter.setRandomSeed( *instance->randomSeed() );
             localCX = scatter.push( workingSet, localCX );
+            if ( trackHistory ) history.push_back( "scatter" );
         }
         else if ( instance->placement() == InstanceSymbol::PLACEMENT_CENTROID )
         {
             CentroidFilter centroid;
-            centroid.push( workingSet, localCX );
+            localCX = centroid.push( workingSet, localCX );
+            if ( trackHistory ) history.push_back( "centroid" );
         }
 
         if ( altRequired )
@@ -404,6 +406,7 @@ GeometryCompiler::compile(FeatureList&          workingSet,
             AltitudeFilter clamp;
             clamp.setPropertiesFromStyle( style );
             localCX = clamp.push( workingSet, localCX );
+            if ( trackHistory ) history.push_back( "altitude" );
         }
 
         SubstituteModelFilter sub( style );
@@ -417,10 +420,13 @@ GeometryCompiler::compile(FeatureList&          workingSet,
         // activate feature naming
         if ( _options.featureName().isSet() )
             sub.setFeatureNameExpr( *_options.featureName() );
+        
 
         osg::Node* node = sub.push( workingSet, localCX );
         if ( node )
         {
+            if ( trackHistory ) history.push_back( "substitute" );
+
             resultGroup->addChild( node );
 
             // enable auto scaling on the group?
@@ -439,6 +445,7 @@ GeometryCompiler::compile(FeatureList&          workingSet,
             AltitudeFilter clamp;
             clamp.setPropertiesFromStyle( style );
             sharedCX = clamp.push( workingSet, sharedCX );
+            if ( trackHistory ) history.push_back( "altitude" );
             altRequired = false;
         }
 
@@ -460,6 +467,7 @@ GeometryCompiler::compile(FeatureList&          workingSet,
         osg::Node* node = extrude.push( workingSet, sharedCX );
         if ( node )
         {
+            if ( trackHistory ) history.push_back( "extrude" );
             resultGroup->addChild( node );
         }
         
@@ -473,6 +481,7 @@ GeometryCompiler::compile(FeatureList&          workingSet,
             AltitudeFilter clamp;
             clamp.setPropertiesFromStyle( style );
             sharedCX = clamp.push( workingSet, sharedCX );
+            if ( trackHistory ) history.push_back( "altitude" );
             altRequired = false;
         }
 
@@ -486,6 +495,7 @@ GeometryCompiler::compile(FeatureList&          workingSet,
         osg::Node* node = filter.push( workingSet, sharedCX );
         if ( node )
         {
+            if ( trackHistory ) history.push_back( "geometry" );
             resultGroup->addChild( node );
         }
     }
@@ -497,6 +507,7 @@ GeometryCompiler::compile(FeatureList&          workingSet,
             AltitudeFilter clamp;
             clamp.setPropertiesFromStyle( style );
             sharedCX = clamp.push( workingSet, sharedCX );
+            if ( trackHistory ) history.push_back( "altitude" );
             altRequired = false;
         }
 
@@ -504,6 +515,7 @@ GeometryCompiler::compile(FeatureList&          workingSet,
         osg::Node* node = filter.push( workingSet, sharedCX );
         if ( node )
         {
+            if ( trackHistory ) history.push_back( "text" );
             resultGroup->addChild( node );
         }
     }
@@ -522,6 +534,8 @@ GeometryCompiler::compile(FeatureList&          workingSet,
             resultGroup->getOrCreateStateSet()->setAttributeAndModes(
                 new osg::Program(),
                 osg::StateAttribute::OFF | osg::StateAttribute::OVERRIDE );
+        
+            if ( trackHistory ) history.push_back( "no shaders" );
         }
     }
 
@@ -543,8 +557,33 @@ GeometryCompiler::compile(FeatureList&          workingSet,
             sscache = new StateSetCache();
             sscache->optimize( resultGroup.get() );
         }
+        
+        if ( trackHistory ) history.push_back( "share state" );
     }
 
+    if ( _options.optimize() == true )
+    {
+        OE_DEBUG << LC << "optimize begin" << std::endl;
+
+        // Run the optimizer on the resulting graph
+        int optimizations =
+            osgUtil::Optimizer::FLATTEN_STATIC_TRANSFORMS |
+            osgUtil::Optimizer::REMOVE_REDUNDANT_NODES |
+            osgUtil::Optimizer::COMBINE_ADJACENT_LODS |
+            osgUtil::Optimizer::SHARE_DUPLICATE_STATE |
+            osgUtil::Optimizer::MERGE_GEOMETRY |
+            osgUtil::Optimizer::CHECK_GEOMETRY |
+            osgUtil::Optimizer::MERGE_GEODES |
+            osgUtil::Optimizer::STATIC_OBJECT_DETECTION;
+
+        osgUtil::Optimizer opt;
+        opt.optimize(resultGroup.get(), optimizations);
+        OE_DEBUG << LC << "optimize complete" << std::endl;
+
+        if ( trackHistory ) history.push_back( "optimize" );
+    }
+    
+
     //test: dump the tile to disk
     //osgDB::writeNodeFile( *(resultGroup.get()), "out.osg" );
 
@@ -563,11 +602,19 @@ GeometryCompiler::compile(FeatureList&          workingSet,
         << std::endl;
 #endif
 
-#if 0
-    //test: run the geometry validator to make sure geometry it legal
-    osgEarth::GeometryValidator validator;
-    resultGroup->accept(validator);
-#endif
+
+    if ( _options.validate() == true )
+    {
+        OE_NOTICE << LC << "-- Start Debugging --\n";
+        std::stringstream buf;
+        buf << "HISTORY ";
+        for(std::vector<std::string>::iterator h = history.begin(); h != history.end(); ++h)
+            buf << ".. " << *h;
+        OE_NOTICE << LC << buf.str() << "\n";
+        osgEarth::GeometryValidator validator;
+        resultGroup->accept(validator);
+        OE_NOTICE << LC << "-- End Debugging --\n";
+    }
 
     return resultGroup.release();
 }
diff --git a/src/osgEarthFeatures/GeometryUtils b/src/osgEarthFeatures/GeometryUtils
index b162391..8222da2 100644
--- a/src/osgEarthFeatures/GeometryUtils
+++ b/src/osgEarthFeatures/GeometryUtils
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/osgEarthFeatures/GeometryUtils.cpp b/src/osgEarthFeatures/GeometryUtils.cpp
index e55f1a7..94c1c36 100644
--- a/src/osgEarthFeatures/GeometryUtils.cpp
+++ b/src/osgEarthFeatures/GeometryUtils.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/osgEarthFeatures/LabelSource b/src/osgEarthFeatures/LabelSource
index 53362e1..9345172 100644
--- a/src/osgEarthFeatures/LabelSource
+++ b/src/osgEarthFeatures/LabelSource
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -74,20 +74,7 @@ namespace osgEarth { namespace Features
         virtual osg::Node* createNode(
             const FeatureList&   input,
             const Style&         style,
-            const FilterContext& context ) =0;
-
-        /**
-         * Creates a single labeling node.
-         *
-         * @param text     Text string to put in the label
-         * @param style    Style information for the label
-         *
-         * @return A scene graph node
-         */
-        //virtual osg::Node* createNode(
-        //    const std::string&  text,
-        //    const Style&        style ) =0;
-
+            FilterContext&       context ) =0;
 
     public:
         
diff --git a/src/osgEarthFeatures/LabelSource.cpp b/src/osgEarthFeatures/LabelSource.cpp
index 168284b..0cebd38 100644
--- a/src/osgEarthFeatures/LabelSource.cpp
+++ b/src/osgEarthFeatures/LabelSource.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthFeatures/MeshClamper b/src/osgEarthFeatures/MeshClamper
index adb047a..1c5fa6c 100644
--- a/src/osgEarthFeatures/MeshClamper
+++ b/src/osgEarthFeatures/MeshClamper
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthFeatures/MeshClamper.cpp b/src/osgEarthFeatures/MeshClamper.cpp
index 1678cc3..67b7779 100644
--- a/src/osgEarthFeatures/MeshClamper.cpp
+++ b/src/osgEarthFeatures/MeshClamper.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthFeatures/OgrUtils b/src/osgEarthFeatures/OgrUtils
index 8aebfda..aeb8015 100644
--- a/src/osgEarthFeatures/OgrUtils
+++ b/src/osgEarthFeatures/OgrUtils
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
@@ -43,10 +46,14 @@ struct OSGEARTHFEATURES_EXPORT OgrUtils
     static OGRGeometryH encodeShape( const Geometry* geometry, OGRwkbGeometryType shape_type, OGRwkbGeometryType part_type );    
 
     static OGRGeometryH createOgrGeometry(const Geometry* geometry, OGRwkbGeometryType requestedType = wkbUnknown);
+
+    static Feature* createFeature( OGRFeatureH handle, const FeatureProfile* profile );
     
-    static Feature* createFeature( OGRFeatureH handle, const SpatialReference* srs );
+    static AttributeType getAttributeType( OGRFieldType type );  
+
+private:
     
-    static AttributeType getAttributeType( OGRFieldType type );    
+    static Feature* createFeature( OGRFeatureH handle, const SpatialReference* srs );
 };
 
 
diff --git a/src/osgEarthFeatures/OgrUtils.cpp b/src/osgEarthFeatures/OgrUtils.cpp
index f0110a7..54dbec8 100644
--- a/src/osgEarthFeatures/OgrUtils.cpp
+++ b/src/osgEarthFeatures/OgrUtils.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
@@ -261,6 +264,23 @@ OgrUtils::createOgrGeometry(const osgEarth::Symbology::Geometry* geometry, OGRwk
 }
 
 Feature*
+OgrUtils::createFeature(OGRFeatureH handle, const FeatureProfile* profile)
+{
+    Feature* f = 0L;
+    if ( profile )
+    {
+        f = createFeature( handle, profile->getSRS() );
+        if ( f && profile->geoInterp().isSet() )
+            f->geoInterp() = profile->geoInterp().get();
+    }
+    else
+    {
+        f = createFeature( handle, (const SpatialReference*)0L );
+    }
+    return f;
+}            
+
+Feature*
 OgrUtils::createFeature( OGRFeatureH handle, const SpatialReference* srs )
 {
     long fid = OGR_F_GetFID( handle );
diff --git a/src/osgEarthFeatures/OptimizerHints b/src/osgEarthFeatures/OptimizerHints
index 208647a..4367db3 100644
--- a/src/osgEarthFeatures/OptimizerHints
+++ b/src/osgEarthFeatures/OptimizerHints
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthFeatures/OptimizerHints.cpp b/src/osgEarthFeatures/OptimizerHints.cpp
index 83c540a..fc1aeb5 100644
--- a/src/osgEarthFeatures/OptimizerHints.cpp
+++ b/src/osgEarthFeatures/OptimizerHints.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthFeatures/PolygonizeLines b/src/osgEarthFeatures/PolygonizeLines
index ff624e5..14c5168 100644
--- a/src/osgEarthFeatures/PolygonizeLines
+++ b/src/osgEarthFeatures/PolygonizeLines
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthFeatures/PolygonizeLines.cpp b/src/osgEarthFeatures/PolygonizeLines.cpp
index 749ba8a..eb54c85 100644
--- a/src/osgEarthFeatures/PolygonizeLines.cpp
+++ b/src/osgEarthFeatures/PolygonizeLines.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -30,24 +30,17 @@ using namespace osgEarth::Features;
 
 #define OV(p) "("<<p.x()<<","<<p.y()<<")"
 
+#define ATTR_LOCATION osg::Drawable::ATTRIBUTE_7
+
 namespace
 {
-    inline osg::Vec3 normalize(const osg::Vec3& in) 
-    {
-      osg::Vec3 temp(in);
-      temp.normalize();
-      return temp;
-    }
-
-
     typedef std::pair<osg::Vec3,osg::Vec3> Segment;
-
+    
     // Given two rays (point + direction vector), find the intersection
     // of those rays in 2D space and put the result in [out]. Return true
     // if they intersect, false if they do not.
     bool interesctRays(const osg::Vec3& p0, const osg::Vec3& pd, // point, dir
                        const osg::Vec3& q0, const osg::Vec3& qd, // point, dir
-                       const osg::Vec3& cp,                      // control point
                        const osg::Vec3& normal,                  // normal at control point
                        osg::Vec3&       out)
     {
@@ -57,9 +50,9 @@ namespace
         toWorld.makeRotate( osg::Vec3(0,0,1), normal );
 
         // convert the inputs:
-        osg::Vec3 p0r = toLocal*p0; //(p0-cp);
+        osg::Vec3 p0r = p0; //toLocal*p0; //(p0-cp);
         osg::Vec3 pdr = toLocal*pd;
-        osg::Vec3 q0r = toLocal*q0; //(q0-cp);
+        osg::Vec3 q0r = q0; //toLocal*q0; //(q0-cp);
         osg::Vec3 qdr = toLocal*qd;
 
         // this epsilon will cause us to skip invalid or colinear rays.
@@ -173,9 +166,9 @@ PolygonizeLinesOperator::operator()(osg::Vec3Array* verts,
     if ( autoScale )
     {
         spine = new osg::Vec3Array( *verts );
-        geom->setVertexAttribArray    ( osg::Drawable::ATTRIBUTE_6, spine );
-        geom->setVertexAttribBinding  ( osg::Drawable::ATTRIBUTE_6, osg::Geometry::BIND_PER_VERTEX );
-        geom->setVertexAttribNormalize( osg::Drawable::ATTRIBUTE_6, false );
+        geom->setVertexAttribArray    ( ATTR_LOCATION, spine );
+        geom->setVertexAttribBinding  ( ATTR_LOCATION, osg::Geometry::BIND_PER_VERTEX );
+        geom->setVertexAttribNormalize( ATTR_LOCATION, false );
     }
 
     // initialize the texture coordinates.
@@ -201,15 +194,19 @@ PolygonizeLinesOperator::operator()(osg::Vec3Array* verts,
     unsigned  prevBufVertPtr;
     unsigned  eboPtr = 0;
     osg::Vec3 prevDir;
-    osg::Quat rot, unrot;
+    
+    osg::Vec3 up(0,0,1);
 
     // iterate over both "sides" of the center line:
-    int lastside = twosided ? 1 : 0;
+    int firstside = 0;
+    int lastside  = twosided ? 1 : 0;
+    
+    const float RIGHT_SIDE = 1.0f;
+    const float LEFT_SIDE = -1.0f;
 
-    for( int ss=0; ss<=lastside; ++ss )
+    for( int ss=firstside; ss<=lastside; ++ss )
     {
-        //float side = s == 0 ? -1.0f : 1.0f;
-        float side = ss == 0 ? 1.0f : -1.0f;
+        float side = ss == 0 ? RIGHT_SIDE : LEFT_SIDE;
 
         // iterate over each line segment.
         for( i=0; i<lineSize-1; ++i )
@@ -225,14 +222,13 @@ PolygonizeLinesOperator::operator()(osg::Vec3Array* verts,
 
             // the buffering vector is orthogonal to the direction vector and the normal;
             // flip it depending on the current side.
-            //osg::Vec3 bufVecUnit = (s==0) ? normal ^ dir : dir ^ normal;
-            osg::Vec3 bufVecUnit = (side < 0.0f) ? normal ^ dir : dir ^ normal;
+            osg::Vec3 bufVecUnit = (dir ^ up) * side;
             bufVecUnit.normalize();
 
             // scale the buffering vector to half the stroke width.
             osg::Vec3 bufVec = bufVecUnit * halfWidth;
 
-            // calculate the starting buffered vector.
+            // calculate the starting buffered vertex
             osg::Vec3 bufVert = (*verts)[i] + bufVec;
 
             if ( i == 0 )
@@ -264,7 +260,8 @@ PolygonizeLinesOperator::operator()(osg::Vec3Array* verts,
                     {
                         osg::Vec3 v;
                         float a = step * (float)j;
-                        rotate( circlevec, -(side)*a, (*normals)[i], v );
+                        //rotate( circlevec, -(side)*a, (*normals)[i], v );
+                        rotate( circlevec, -(side)*a, up, v );
 
                         verts->push_back( (*verts)[i] + v );
                         addTri( ebo, i, verts->size()-2, verts->size()-1, side );
@@ -293,8 +290,9 @@ PolygonizeLinesOperator::operator()(osg::Vec3Array* verts,
             else
             {
                 // does the new segment turn create a reflex angle (>180deg)?
-                float z = (prevDir ^ dir).z();
-                bool isOutside = side < 0.0f ? z <= 0.0 : z >= 0.0;
+                osg::Vec3f cp = prevDir ^ dir;
+                float z = cp.z();
+                bool isOutside = side == LEFT_SIDE ? z <= 0.0 : z >= 0.0;
                 bool isInside = !isOutside;
 
                 // if this is an inside angle (or we're using mitered corners)
@@ -302,25 +300,41 @@ PolygonizeLinesOperator::operator()(osg::Vec3Array* verts,
                 // vectors enimating from the previous and next buffered points.
                 if ( isInside || _stroke.lineJoin() == Stroke::LINEJOIN_MITRE )
                 {
-                    // unit vector from the previous buffered vert to the current one
-                    osg::Vec3 vec1 = (*verts)[i] - (*verts)[i-1];
-                    vec1.normalize();
-
-                    // unit vector from the current buffered vert to the next one.
-                    osg::Vec3 nextBufVert = seg.second + bufVec;
-                    osg::Vec3 vec2        = (*verts)[i] - (*verts)[i+1];
-                    vec2.normalize();
-
-                    // find the 2D intersection of these two vectors. Check for the 
-                    // special case of colinearity.
-                    osg::Vec3 isect;
-                    if ( interesctRays(prevBufVert, vec1, nextBufVert, vec2, (*verts)[i], (*normals)[i], isect) )
-                        verts->push_back(isect);
-                    else
+                    bool addedVertex = false;
+                    {
+                        osg::Vec3 nextBufVert = seg.second + bufVec;
+
+                        OE_DEBUG 
+                            << "\n"
+                            << "point " << i << " : \n"
+                            << "seg f: " << seg.first.x() << ", " << seg.first.y() << "\n"
+                            << "seg s: " << seg.second.x() << ", " << seg.second.y() << "\n"
+                            << "pnt 1: " << prevBufVert.x() << ", " << prevBufVert.y() << "\n"
+                            << "ray 1: " << prevDir.x() << ", " << prevDir.y() << "\n"
+                            << "pnt 2: " << nextBufVert.x() << ", " << nextBufVert.y() << "\n"
+                            << "ray 2: " << -dir.x() << ", " << -dir.y() << "\n"
+                            << "bufvec: " << bufVec.x() << ", " << bufVec.y() << "\n"
+                            << "\n";
+
+                        // find the 2D intersection of these two vectors. Check for the 
+                        // special case of colinearity.
+                        osg::Vec3 isect;
+
+                        if ( interesctRays(prevBufVert, prevDir, nextBufVert, -dir, up, isect) )//(*normals)[i], isect) )
+                        {
+                            verts->push_back(isect);
+                            addedVertex = true;
+                        }
+                    }
+                    
+                    if ( !addedVertex )
+                    {
                         verts->push_back(bufVert);
+                    }
 
                     // now that we have the current buffered point, build triangles
                     // for *previous* segment.
+                    //if ( addedVertex )
                     addTris( ebo, i, prevBufVertPtr, verts->size()-1, side );
                     tverts->push_back( osg::Vec2f(1.0*side, (*tverts)[i].y()) );
                     normals->push_back( (*normals)[i] );
@@ -348,7 +362,8 @@ PolygonizeLinesOperator::operator()(osg::Vec3Array* verts,
                     {
                         osg::Vec3 v;
                         float a = step * (float)j;
-                        rotate( circlevec, side*a, (*normals)[i], v );
+                        rotate( circlevec, side*a, up, v );
+                        //rotate( circlevec, side*a, (*normals)[i], v );
 
                         verts->push_back( (*verts)[i] + v );
                         addTri( ebo, i, verts->size()-1, verts->size()-2, side );
@@ -388,7 +403,8 @@ PolygonizeLinesOperator::operator()(osg::Vec3Array* verts,
             {
                 osg::Vec3 v;
                 float a = step * (float)j;
-                rotate( circlevec, (side)*a, (*normals)[i], v );
+                //rotate( circlevec, (side)*a, (*normals)[i], v );
+                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()) );
@@ -432,7 +448,7 @@ 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");
@@ -454,6 +470,7 @@ namespace
     {
         PixelSizeVectorCullCallback(osg::StateSet* stateset)
         {
+            _frameNumber = 0;
             _pixelSizeVectorUniform = new osg::Uniform(osg::Uniform::FLOAT_VEC4, "oe_PixelSizeVector");
             stateset->addUniform( _pixelSizeVectorUniform.get() );
         }
@@ -461,11 +478,43 @@ namespace
         void operator()(osg::Node* node, osg::NodeVisitor* nv)
         {
             osgUtil::CullVisitor* cv = Culling::asCullVisitor(nv);
-            _pixelSizeVectorUniform->set( cv->getCurrentCullingSet().getPixelSizeVector() );
+
+            // temporary patch to prevent uniform overwrite -gw
+            if ( nv->getFrameStamp() && nv->getFrameStamp()->getFrameNumber() > _frameNumber )
+            {
+                _pixelSizeVectorUniform->set( cv->getCurrentCullingSet().getPixelSizeVector() );    
+                _frameNumber = nv->getFrameStamp()->getFrameNumber();
+            }
+
             traverse(node, nv);
         }
 
         osg::ref_ptr<osg::Uniform> _pixelSizeVectorUniform;
+        int _frameNumber;        
+    };
+
+    class PixelScalingGeode : public osg::Geode
+    {
+    public:
+        void traverse(osg::NodeVisitor& nv)
+        {
+            osgUtil::CullVisitor* cv = 0L;
+            if (nv.getVisitorType() == nv.CULL_VISITOR &&
+                (cv = Culling::asCullVisitor(nv)) != 0L &&
+                cv->getCurrentCamera() )
+            {
+                osg::ref_ptr<osg::StateSet>& ss = _stateSets.get( cv->getCurrentCamera() );
+                if ( !ss.valid() )
+                    ss = new osg::StateSet();
+
+                ss->getOrCreateUniform("oe_PixelSizeVector", osg::Uniform::FLOAT_VEC4)->set(
+                    cv->getCurrentCullingSet().getPixelSizeVector() );
+            }
+
+            osg::Geode::traverse( nv );
+        }
+
+        PerObjectFastMap<osg::Camera*, osg::ref_ptr<osg::StateSet> > _stateSets;
     };
 }
 
@@ -500,25 +549,23 @@ PolygonizeLinesOperator::installShaders(osg::Node* node) const
 
         "void oe_polyline_scalelines(inout vec4 vertex_model4) \n"
         "{ \n"
-        "   vec4  center_model = vec4(oe_polyline_center, 1.0); \n"
-        "   vec4  vertex_model = vertex_model4/vertex_model4.w; \n"
-        "   vec3  vector_model = vertex_model.xyz - center_model.xyz; \n"
+        "   const float epsilon = 0.0001; \n"
+
+        "   vec4 center = vec4(oe_polyline_center, 1.0); \n"
+        "   vec3 vector = vertex_model4.xyz - center.xyz; \n"
         
-        "   float r = length(vector_model); \n"
+        "   float r = length(vector); \n"
 
-        "   if ( r > 0.0 && oe_polyline_min_pixels > 0.0 ) \n"
-        "   { \n"
-        "       float pixelSize = abs(r/dot(center_model, oe_PixelSizeVector)); \n"
-        "       float min_scale = max( oe_polyline_min_pixels/pixelSize, 1.0 ); \n"
-        "       float scale = max( oe_polyline_scale, min_scale ); \n"
+        "   float activate  = step(epsilon, r*oe_polyline_min_pixels);\n"
+        "   float pixelSize = max(epsilon, 2.0*abs(r/dot(center, oe_PixelSizeVector))); \n"
+        "   float min_scale = max(oe_polyline_min_pixels/pixelSize, 1.0); \n"
+        "   float scale     = mix(1.0, max(oe_polyline_scale, min_scale), activate); \n"
 
-        "       vertex_model.xyz += vector_model*scale; \n"
-        "       vertex_model4 = vec4(vertex_model.xyz, 1.0); \n"
-        "    } \n"
+        "   vertex_model4.xyz = center.xyz + vector*scale; \n"
         "} \n";
 
-    vp->setFunction( "oe_polyline_scalelines", vs, ShaderComp::LOCATION_VERTEX_MODEL );
-    vp->addBindAttribLocation( "oe_polyline_center", osg::Drawable::ATTRIBUTE_6 );
+    vp->setFunction( "oe_polyline_scalelines", vs, ShaderComp::LOCATION_VERTEX_MODEL, 0.5f );
+    vp->addBindAttribLocation( "oe_polyline_center", ATTR_LOCATION );
 
     // add the default scaling uniform.
     // good way to test:
@@ -574,7 +621,7 @@ PolygonizeLinesFilter::push(FeatureList& input, FilterContext& cx)
     PolygonizeLinesOperator polygonize( line ? (*line->stroke()) : Stroke() );
 
     // Geode to hold all the geometries.
-    osg::Geode* geode = new osg::Geode();
+    osg::Geode* geode = new PixelScalingGeode(); //osg::Geode();
 
     // iterate over all features.
     for( FeatureList::iterator i = input.begin(); i != input.end(); ++i )
@@ -600,11 +647,13 @@ PolygonizeLinesFilter::push(FeatureList& input, FilterContext& cx)
 
             // turn the lines into polygons.
             osg::Geometry* geom = polygonize( verts, normals );
+
+            // install.
             geode->addDrawable( geom );
 
             // record the geometry's primitive set(s) in the index:
             if ( cx.featureIndex() )
-                cx.featureIndex()->tagPrimitiveSets( geom, f );
+                cx.featureIndex()->tagDrawable( geom, f );
         }
     }
 
diff --git a/src/osgEarthFeatures/ResampleFilter b/src/osgEarthFeatures/ResampleFilter
index a6c6d64..cd12c59 100644
--- a/src/osgEarthFeatures/ResampleFilter
+++ b/src/osgEarthFeatures/ResampleFilter
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthFeatures/ResampleFilter.cpp b/src/osgEarthFeatures/ResampleFilter.cpp
index d7c76ec..386625c 100644
--- a/src/osgEarthFeatures/ResampleFilter.cpp
+++ b/src/osgEarthFeatures/ResampleFilter.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -93,6 +93,11 @@ ResampleFilter::push( Feature* input, FilterContext& context )
 
         unsigned int origSize = part->size();
 
+        // whether to resample the virtual segment that closes an open ring:
+        bool loop =
+            part->getComponentType() == Geometry::TYPE_RING &&
+            static_cast<Ring*>(part)->isOpen();
+
         // copy the original part to a linked list. use a std::list since insert/erase
         // will not invalidate iterators.
         std::list<osg::Vec3d> plist;
diff --git a/src/osgEarthFeatures/ScaleFilter b/src/osgEarthFeatures/ScaleFilter
index 2c8d107..9d7b7f8 100644
--- a/src/osgEarthFeatures/ScaleFilter
+++ b/src/osgEarthFeatures/ScaleFilter
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthFeatures/ScaleFilter.cpp b/src/osgEarthFeatures/ScaleFilter.cpp
index 700de8e..fe3611c 100644
--- a/src/osgEarthFeatures/ScaleFilter.cpp
+++ b/src/osgEarthFeatures/ScaleFilter.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthFeatures/ScatterFilter b/src/osgEarthFeatures/ScatterFilter
index d75f6c0..c81b4a6 100644
--- a/src/osgEarthFeatures/ScatterFilter
+++ b/src/osgEarthFeatures/ScatterFilter
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthFeatures/ScatterFilter.cpp b/src/osgEarthFeatures/ScatterFilter.cpp
index 44cd8a7..a725945 100644
--- a/src/osgEarthFeatures/ScatterFilter.cpp
+++ b/src/osgEarthFeatures/ScatterFilter.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthFeatures/Script b/src/osgEarthFeatures/Script
index ce6a2f6..1531785 100644
--- a/src/osgEarthFeatures/Script
+++ b/src/osgEarthFeatures/Script
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthFeatures/ScriptEngine b/src/osgEarthFeatures/ScriptEngine
index a1e0a30..1516269 100644
--- a/src/osgEarthFeatures/ScriptEngine
+++ b/src/osgEarthFeatures/ScriptEngine
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthFeatures/ScriptEngine.cpp b/src/osgEarthFeatures/ScriptEngine.cpp
index 5599ba5..12442f2 100644
--- a/src/osgEarthFeatures/ScriptEngine.cpp
+++ b/src/osgEarthFeatures/ScriptEngine.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthFeatures/Session b/src/osgEarthFeatures/Session
index 1029ef8..71fb329 100644
--- a/src/osgEarthFeatures/Session
+++ b/src/osgEarthFeatures/Session
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -97,6 +97,10 @@ namespace osgEarth { namespace Features
         void setResourceCache(ResourceCache* cache);
         ResourceCache* getResourceCache();
 
+        /** Optional name for this session */
+        void setName(const std::string& name) { _name = name; }
+        const std::string& getName() const { return _name; }
+
     public:
         template<typename T>
         struct CreateFunctor {
@@ -179,6 +183,7 @@ namespace osgEarth { namespace Features
         osg::ref_ptr<FeatureSource>        _featureSource;
         osg::ref_ptr<StateSetCache>        _stateSetCache;
         osg::ref_ptr<ResourceCache>        _resourceCache;
+        std::string                        _name;
     };
 
 } }
diff --git a/src/osgEarthFeatures/Session.cpp b/src/osgEarthFeatures/Session.cpp
index ed14d08..9d76aa0 100644
--- a/src/osgEarthFeatures/Session.cpp
+++ b/src/osgEarthFeatures/Session.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -56,6 +56,8 @@ _dbOptions     ( dbOptions )
     // geometry created under this session takes advantage of it. That's reasonable since
     // tiles in a particular "layer" will tend to share state.
     _stateSetCache = new StateSetCache();
+
+    _name = "Session (unnamed)";
 }
 
 Session::~Session()
diff --git a/src/osgEarthFeatures/StencilVolumeNode b/src/osgEarthFeatures/StencilVolumeNode
deleted file mode 100644
index c806e93..0000000
--- a/src/osgEarthFeatures/StencilVolumeNode
+++ /dev/null
@@ -1,103 +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 OSGEARTHFEATURES_STENCIL_VOLUME_NODE_H
-#define OSGEARTHFEATURES_STENCIL_VOLUME_NODE_H 1
-
-#include <osgEarthFeatures/Common>
-#include <osgEarthFeatures/Filter>
-#include <osgEarthFeatures/Geometry>
-#include <osgEarth/MaskNode>
-#include <osg/Group>
-#include <osg/Vec4>
-
-namespace osgEarth { namespace Features 
-{
-    class OSGEARTHFEATURES_EXPORT StencilVolumeNode : public osgEarth::MaskNode
-    {
-    public:
-        /**
-         * Constructs a stencil masking node.
-         *
-         * @param preRenderChildrenToDepthBuffer
-         *     Normally, this node will render the stencil volumes first and then
-         *     render the children (which will be masked by the stencil). If you need
-         *     to pre-render the children to the depth buffer, set this to TRUE. You
-         *     need to do this is you are creating a straight render mask.
-         *
-         * @param inverted
-         *     Inverts the stencil buffer, masking the opposite pixels that would
-         *     normally be masked.
-         */
-        StencilVolumeNode(
-            bool preRenderChildrenToDepthBuffer =false,
-            bool inverted =false );
-
-        StencilVolumeNode( const StencilVolumeNode& rhs, const osg::CopyOp& op =osg::CopyOp::DEEP_COPY_ALL );
-
-        META_Node(osgEarth::Features,StencilVolumeNode);
-        
-        virtual void traverse( osg::NodeVisitor& nv );
-
-    public: // props
-
-        // sets the render bins and returns the next available bin.
-        int setBaseRenderBin( int bin );
-
-        // adds stenciling volume geometry
-        void addVolumes( osg::Node* node );
-
-        // adds the node to draw once the stencil is in place.
-        //void setChild( osg::Node* node );
-
-    public: //osg::Group overrides
-
-        virtual bool addChild( Node *child );
-        virtual bool insertChild( unsigned int index, Node *child );
-        virtual bool removeChildren(unsigned int pos,unsigned int numChildrenToRemove);
-        virtual bool replaceChild( Node *origChild, Node* newChild );
-        virtual bool setChild( unsigned  int i, Node* node );
-        virtual osg::BoundingSphere computeBound() const;
-
-    protected:
-        void init();
-
-        osg::ref_ptr<osg::Group> _root;
-        osg::Group* _stencilGroup1;
-        osg::Group* _stencilGroup2;
-        osg::Group* _depthPass;
-        osg::Group* _renderPass;
-        bool _inverted;
-        bool _preRenderChildrenToDepthBuffer;
-    };
-
-
-    class OSGEARTHFEATURES_EXPORT StencilVolumeFactory
-    {
-    public:
-        static osg::Geode* createVolume(
-            Geometry*            geom,
-            double               offset,
-            double               height,
-            const FilterContext& context );
-    };
-
-} } // namespace osgEarth::Features
-
-#endif // OSGEARTHFEATURES_STENCIL_VOLUME_NODE_H
-
diff --git a/src/osgEarthFeatures/SubstituteModelFilter b/src/osgEarthFeatures/SubstituteModelFilter
index d162365..1746148 100644
--- a/src/osgEarthFeatures/SubstituteModelFilter
+++ b/src/osgEarthFeatures/SubstituteModelFilter
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -91,7 +91,6 @@ namespace osgEarth { namespace Features
         InstanceCache _instanceCache;
         
         bool process(const FeatureList& features, const InstanceSymbol* symbol, Session* session, osg::Group* ap, FilterContext& context );
-        bool cluster(const FeatureList& features, const InstanceSymbol* symbol, Session* session, osg::Group* ap, FilterContext& context );
         
         bool findResource( const URI& instanceURI, const InstanceSymbol* symbol, FilterContext& context, std::set<URI>& missing, osg::ref_ptr<InstanceResource>& output );
     };
diff --git a/src/osgEarthFeatures/SubstituteModelFilter.cpp b/src/osgEarthFeatures/SubstituteModelFilter.cpp
index 259f8a7..f7661ef 100644
--- a/src/osgEarthFeatures/SubstituteModelFilter.cpp
+++ b/src/osgEarthFeatures/SubstituteModelFilter.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -21,6 +21,7 @@
 #include <osgEarthFeatures/Session>
 #include <osgEarthFeatures/GeometryUtils>
 #include <osgEarthSymbology/MeshConsolidator>
+#include <osgEarthSymbology/MeshFlattener>
 #include <osgEarth/ECEF>
 #include <osgEarth/VirtualProgram>
 #include <osgEarth/DrawInstanced>
@@ -36,9 +37,13 @@
 #include <osg/NodeVisitor>
 #include <osg/ShapeDrawable>
 #include <osg/AlphaFunc>
+#include <osg/Billboard>
+
+#include <osgSim/LightPointNode>
 
 #include <osgDB/FileNameUtils>
 #include <osgDB/Registry>
+#include <osgDB/WriteFile>
 
 #include <osgUtil/Optimizer>
 #include <osgUtil/MeshOptimizers>
@@ -282,7 +287,8 @@ SubstituteModelFilter::process(const FeatureList&           features,
                     xform->addChild( model.get() );
                     attachPoint->addChild( xform );
 
-                    if ( context.featureIndex() && !_useDrawInstanced )
+                    // Only tag nodes if we aren't using clustering.
+                    if ( context.featureIndex() && !_cluster)
                     {
                         context.featureIndex()->tagNode( xform, input );
                     }
@@ -310,6 +316,7 @@ SubstituteModelFilter::process(const FeatureList&           features,
         // activate horizon culling if we are in geocentric space
         if ( context.getSession() && context.getSession()->getMapInfo().isGeocentric() )
         {
+            //TODO: re-evaluate this; use Horizon?
             HorizonCullingProgram::install( attachPoint->getOrCreateStateSet() );
         }
     }
@@ -329,216 +336,50 @@ SubstituteModelFilter::process(const FeatureList&           features,
 
 
 
-struct ClusterVisitor : public osg::NodeVisitor
+namespace
 {
-    ClusterVisitor( const FeatureList& features, const InstanceSymbol* symbol, FeaturesToNodeFilter* f2n, FilterContext& cx )
-        : _features   ( features ),
-          _symbol     ( symbol ),
-          _f2n        ( f2n ),
-          _cx         ( cx ),
-          osg::NodeVisitor( osg::NodeVisitor::TRAVERSE_ALL_CHILDREN )
+    /**
+     * Extracts unclusterable things like lightpoints and billboards from the given scene graph and copies them into a cloned scene graph
+     * This actually just removes all geodes from the scene graph, so this could be applied to any other type of node that you want to keep
+     * The geodes will be clustered together in the flattened graph.
+     */
+    osg::Node* extractUnclusterables(osg::Node* node)
     {
-        _modelSymbol = dynamic_cast<const ModelSymbol*>( symbol );
-        if ( _modelSymbol )
-            _headingExpr = *_modelSymbol->heading();
-
-        _scaleExpr = *_symbol->scale();
-
-        _makeECEF  = _cx.getSession()->getMapInfo().isGeocentric();
-        _srs       = _cx.profile()->getSRS();
-        _targetSRS = _cx.getSession()->getMapInfo().getSRS();
-    }
-
-    void apply( osg::Geode& geode )
-    {
-        // save the geode's drawables..
-        typedef std::vector<osg::ref_ptr<osg::Drawable> > Drawables;        
-        Drawables old_drawables;
-        old_drawables.reserve( geode.getNumDrawables() );
-        for(unsigned i=0; i<geode.getNumDrawables(); ++i)
-            old_drawables.push_back( geode.getDrawable(i) );
-
-        //OE_DEBUG << "ClusterVisitor geode " << &geode << " featureNode=" << _featureNode << " drawables=" << old_drawables.size() << std::endl;
-
-        // ..and clear out the drawables list.
-        geode.removeDrawables( 0, geode.getNumDrawables() );
-
-        // foreach each drawable that was originally in the geode...
-        for( Drawables::iterator i = old_drawables.begin(); i != old_drawables.end(); i++ )
+        // Clone the scene graph
+        osg::ref_ptr< osg::Node > clone = (osg::Node*)node->clone(osg::CopyOp::DEEP_COPY_NODES);
+       
+        // Now remove any geodes
+        FindNodesVisitor<osg::Geode> findGeodes;
+        clone->accept(findGeodes);
+        for (unsigned int i = 0; i < findGeodes._results.size(); i++)
         {
-            osg::Geometry* originalDrawable = dynamic_cast<osg::Geometry*>( i->get() );
-            if ( !originalDrawable )
-                continue;
-
-            // go through the list of input features...
-            for( FeatureList::const_iterator j = _features.begin(); j != _features.end(); j++ )
-            {
-                Feature* feature = j->get();
-
-                osg::Matrixd scaleMatrix;
+            osg::ref_ptr< osg::Geode > geode = findGeodes._results[i];
+            
 
-                if ( _symbol->scale().isSet() )
-                {
-                    double scale = feature->eval( _scaleExpr, &_cx );
-                    scaleMatrix.makeScale( scale, scale, scale );
-                }
+            // Special check for billboards.  Me want to keep them in this special graph of 
+            // unclusterable stuff.
+            osg::Billboard* billboard = dynamic_cast< osg::Billboard* >( geode.get() );
+            
 
-                osg::Matrixd rotationMatrix;
-                if ( _modelSymbol && _modelSymbol->heading().isSet() )
+            if (geode->getNumParents() > 0 && !billboard)
+            {
+                // Get all the parents for the geode and remove it from them.
+                std::vector< osg::ref_ptr< osg::Group > > parents;
+                for (unsigned int j = 0; j < geode->getNumParents(); j++)
                 {
-                    float heading = feature->eval( _headingExpr, &_cx );
-                    rotationMatrix.makeRotate( osg::Quat(osg::DegreesToRadians(heading), osg::Vec3(0,0,1)) );
+                    parents.push_back(geode->getParent(j));
                 }
 
-                GeometryIterator gi( feature->getGeometry(), false );
-                while( gi.hasMore() )
+                for (unsigned int j = 0; j < parents.size(); j++)
                 {
-                    Geometry* geom = gi.next();
-
-                    // if necessary, transform the points to the target SRS:
-                    if ( !_makeECEF && !_targetSRS->isEquivalentTo(_srs) )
-                    {
-                        _srs->transform( geom->asVector(), _targetSRS );
-                    }
-
-                    for( Geometry::const_iterator k = geom->begin(); k != geom->end(); ++k )
-                    {
-                        osg::Vec3d   point = *k;
-                        osg::Matrixd mat;
-
-                        if ( _makeECEF )
-                        {
-                            osg::Matrixd rotation;
-                            ECEF::transformAndGetRotationMatrix( point, _srs, point, _targetSRS, rotation );
-                            mat = rotationMatrix * rotation * scaleMatrix * osg::Matrixd::translate(point) * _f2n->world2local();
-                        }
-                        else
-                        {
-                            mat = rotationMatrix * scaleMatrix * osg::Matrixd::translate(point) * _f2n->world2local();
-                        }
-
-                        // clone the source drawable once for each input feature.
-                        osg::ref_ptr<osg::Geometry> newDrawable = osg::clone( 
-                            originalDrawable, 
-                            osg::CopyOp::DEEP_COPY_ARRAYS | osg::CopyOp::DEEP_COPY_PRIMITIVES );
-
-                        osg::Vec3Array* verts = dynamic_cast<osg::Vec3Array*>( newDrawable->getVertexArray() );
-                        if ( verts )
-                        {
-                            for( osg::Vec3Array::iterator v = verts->begin(); v != verts->end(); ++v )
-                            {
-                                (*v).set( (*v) * mat );
-                            }
-
-                            // add the new cloned, translated drawable back to the geode.
-                            geode.addDrawable( newDrawable.get() );
-
-                            if ( _cx.featureIndex() )
-                                _cx.featureIndex()->tagPrimitiveSets( newDrawable.get(), feature );
-                        }
-                    }
-
+                    parents[j]->removeChild(geode);
                 }
             }
+            
         }
 
-        geode.dirtyBound();
-
-        MeshConsolidator::run( geode );
-
-        osg::NodeVisitor::apply( geode );
-    }
-
-private:
-    const FeatureList&      _features;
-    FilterContext&          _cx;
-    const InstanceSymbol*   _symbol;
-    const ModelSymbol*      _modelSymbol;
-    FeaturesToNodeFilter*   _f2n;
-    NumericExpression       _scaleExpr;
-    NumericExpression       _headingExpr;
-    bool                    _makeECEF;
-    const SpatialReference* _srs;
-    const SpatialReference* _targetSRS;
-};
-
-
-//typedef std::map< osg::Node*, FeatureList > MarkerToFeatures;
-typedef std::map< osg::ref_ptr<osg::Node>, FeatureList > ModelBins;
-
-//clustering:
-//  troll the external model for geodes. for each geode, create a geode in the target
-//  model. then, for each geometry in that geode, replicate it once for each instance of
-//  the model in the feature batch and transform the actual verts to a local offset
-//  relative to the tile centroid. Finally, reassemble all the geodes and optimize. 
-//  hopefully stateset sharing etc will work out. we may need to strip out LODs too.
-bool
-SubstituteModelFilter::cluster(const FeatureList&           features,
-                               const InstanceSymbol*        symbol, 
-                               Session*                     session,
-                               osg::Group*                  attachPoint,
-                               FilterContext&               context )
-{
-    ModelBins modelBins;
-
-    std::set<URI> missing;
-
-    // first, sort the features into buckets, each bucket corresponding to a
-    // unique marker.
-    for (FeatureList::const_iterator i = features.begin(); i != features.end(); ++i)
-    {
-        Feature* f = i->get();
-
-        // resolve the URI for the marker:
-        StringExpression uriEx( *symbol->url() );
-        URI instanceURI( f->eval( uriEx, &context ), uriEx.uriContext() );
-
-        // find and load the corresponding marker model. We're using the session-level
-        // object store to cache models. This is thread-safe sine we are always going
-        // to CLONE the model before using it.
-        osg::ref_ptr<osg::Node> model = context.getSession()->getObject<osg::Node>( instanceURI.full() );
-        if ( !model.valid() )
-        {
-            osg::ref_ptr<InstanceResource> instance;
-            if ( !findResource( instanceURI, symbol, context, missing, instance) )
-                continue;
-
-            model = instance->createNode( context.getSession()->getDBOptions() );
-            if ( model.valid() )
-            {
-                model = context.getSession()->putObject( instanceURI.full(), model.get(), false );
-            }
-        }
-
-        if ( model.valid() )
-        {
-            ModelBins::iterator itr = modelBins.find( model.get() );
-            if (itr == modelBins.end())
-                modelBins[ model.get() ].push_back( f );
-            else
-                itr->second.push_back( f );
-        }
-    }
-
-    // For each model, cluster the features that use that marker
-    for (ModelBins::iterator i = modelBins.begin(); i != modelBins.end(); ++i)
-    {
-        osg::Node* prototype = i->first.get();
-
-        // we're using the Session cache since we know we'll be cloning.
-        if ( prototype )
-        {
-            osg::Node* clone = osg::clone( prototype, osg::CopyOp::DEEP_COPY_ALL );
-
-            // ..and apply the clustering to the copy.
-            ClusterVisitor cv( i->second, symbol, this, context );
-            clone->accept( cv );
-
-            attachPoint->addChild( clone );
-        }
-    }
-
-    return true;
+        return clone.release();
+    };
 }
 
 osg::Node*
@@ -576,13 +417,13 @@ SubstituteModelFilter::push(FeatureList& features, FilterContext& context)
 
     const StyleSheet* sheet = context.getSession() ? context.getSession()->styles() : 0L;
 
-    if ( symbol->libraryName().isSet() )
+    if ( symbol->library().isSet() )
     {
-        _resourceLib = sheet->getResourceLibrary( symbol->libraryName()->expr() );
+        _resourceLib = sheet->getResourceLibrary( symbol->library()->expr() );
 
         if ( !_resourceLib.valid() )
         {
-            OE_WARN << LC << "Unable to load resource library '" << symbol->libraryName()->expr() << "'"
+            OE_WARN << LC << "Unable to load resource library '" << symbol->library()->expr() << "'"
                 << "; may not find instance models." << std::endl;
         }
     }
@@ -597,16 +438,26 @@ SubstituteModelFilter::push(FeatureList& features, FilterContext& context)
 
     osg::Group* group = createDelocalizeGroup();
 
+    osg::ref_ptr< osg::Group > attachPoint = new osg::Group;
+    group->addChild(attachPoint.get());
+
     // Process the feature set, using clustering if requested
     bool ok = true;
-    if ( _cluster )
-    {
-        ok = cluster( features, symbol, context.getSession(), group, newContext );
-    }
 
-    else
+    process( features, symbol, context.getSession(), attachPoint.get(), newContext );
+    if (_cluster)
     {
-        process( features, symbol, context.getSession(), group, newContext );
+        // Extract the unclusterable things
+        osg::ref_ptr< osg::Node > unclusterables = extractUnclusterables(attachPoint);
+
+        // We run on the attachPoint instead of the main group so that we don't lose the double precision declocalizer transform.
+        MeshFlattener::run(attachPoint);
+
+        // Add the unclusterables back to the attach point after the rest of the graph was flattened.
+        if (unclusterables.valid())
+        {
+            attachPoint->addChild(unclusterables);
+        }
     }
 
     // return proper context
@@ -620,7 +471,7 @@ SubstituteModelFilter::push(FeatureList& features, FilterContext& context)
         // TODO: carefully test for this, since GL_NORMALIZE hurts performance in 
         // FFP mode (RESCALE_NORMAL is faster for uniform scaling); and I think auto-normal-scaling
         // is disabled entirely when using shaders. For now I believe we are dropping to FFP
-        // when not using instancing...so just check for that
+        // when not using instancing ...so just check for that
         if ( !_useDrawInstanced )
         {
             group->getOrCreateStateSet()->setMode( GL_NORMALIZE, osg::StateAttribute::ON );
@@ -628,5 +479,7 @@ SubstituteModelFilter::push(FeatureList& features, FilterContext& context)
     }
 #endif
 
+    //osgDB::writeNodeFile(*group, "c:/temp/clustered.osg");
+
     return group;
 }
diff --git a/src/osgEarthFeatures/TessellateOperator b/src/osgEarthFeatures/TessellateOperator
index 8f8c014..2e5ac33 100644
--- a/src/osgEarthFeatures/TessellateOperator
+++ b/src/osgEarthFeatures/TessellateOperator
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
@@ -58,20 +61,17 @@ namespace osgEarth { namespace Features
     public:
         /**
          * Constructs a new tessellation operator.
-         * @param numPartitions
-         *      Each geometry segment should be subdivided into this many partitions
-         * @param referenceSRS
-         *      Feature geometry is assumed to be expressed in this SRS; if NULL, assume
-         *      geometry is geodetic (lat/long degrees).
-         * @param defaultInterp
-         *      If the feature does not specifiy an interpolation method, use this one
          */
-        TessellateOperator( 
-            unsigned                numPartitions =20,
-            GeoInterpolation        defaultInterp =GEOINTERP_GREAT_CIRCLE );
+        TessellateOperator();
 
         virtual ~TessellateOperator() { }
 
+        /**
+         * Sets the maximum size of each tessellated partition in the geometry.
+         * The "numPartitions" is calculated from this value.
+         */
+        void setMaxPartitionSize( const Distance& value ) { _maxDistance = value; }
+
         void setNumPartitions( unsigned value ) { _numPartitions = value; }
 
         void setDefaultGeoInterp( GeoInterpolation value ) { _defaultInterp = value; }
@@ -83,8 +83,9 @@ namespace osgEarth { namespace Features
         void operator()( Feature* feature, FilterContext& context ) const;
 
     protected:
-        unsigned                             _numPartitions;
-        GeoInterpolation                     _defaultInterp;
+        optional<Distance> _maxDistance;
+        unsigned           _numPartitions;
+        GeoInterpolation   _defaultInterp;
     };
 
 } } // namespace osgEarth::Features
diff --git a/src/osgEarthFeatures/TessellateOperator.cpp b/src/osgEarthFeatures/TessellateOperator.cpp
index 679d4ab..99bcaf8 100644
--- a/src/osgEarthFeatures/TessellateOperator.cpp
+++ b/src/osgEarthFeatures/TessellateOperator.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -69,7 +69,7 @@ TessellateOperator::tessellateGeo( const osg::Vec3d& p0, const osg::Vec3d& p1, u
             double bearing  = GeoMath::rhumbBearing( lat1, lon1, lat2, lon2 );
 
             double interpDistance = t * totalDistance;
-           
+
             double lat3, lon3;
             GeoMath::rhumbDestination(lat1, lon1, bearing, interpDistance, lat3, lon3);
 
@@ -82,10 +82,9 @@ TessellateOperator::tessellateGeo( const osg::Vec3d& p0, const osg::Vec3d& p1, u
 
 //------------------------------------------------------------------------
 
-TessellateOperator::TessellateOperator(unsigned                numPartitions,
-                                       GeoInterpolation        defaultInterp ) :
-_numPartitions( numPartitions ),
-_defaultInterp( defaultInterp )
+TessellateOperator::TessellateOperator() :
+_numPartitions( 20 ),
+_defaultInterp( GEOINTERP_GREAT_CIRCLE )
 {
     //nop
 }
@@ -101,10 +100,20 @@ TessellateOperator::operator()( Feature* feature, FilterContext& context ) const
         return;
     }
 
+    Units featureUnits = feature->getSRS() ? feature->getSRS()->getUnits() : Units::METERS;
     bool isGeo = feature->getSRS() ? feature->getSRS()->isGeographic() : true;
-    //bool isGeo = context.profile() ? context.profile()->getSRS()->isGeographic() : true;
     GeoInterpolation interp = feature->geoInterp().isSet() ? *feature->geoInterp() : _defaultInterp;
 
+    double sliceSize = 0.0;
+    int    numPartitions = _numPartitions;
+
+    if ( _maxDistance.isSet() )
+    {
+        // copmpute the slice size in feature units.
+        double latitude = feature->getGeometry()->getBounds().center().y();
+        sliceSize = SpatialReference::transformUnits( _maxDistance.value(), feature->getSRS(), latitude );
+    }
+
     GeometryIterator i( feature->getGeometry(), true );
     while( i.hasMore() )
     {
@@ -112,24 +121,33 @@ TessellateOperator::operator()( Feature* feature, FilterContext& context ) const
         bool isRing = dynamic_cast<Ring*>( g ) != 0L;
 
         Vec3dVector newVerts;
-        newVerts.reserve( g->size() * _numPartitions );
 
         for( Geometry::const_iterator v = g->begin(); v != g->end(); ++v )
         {
+            unsigned slices = _numPartitions;
+
             const osg::Vec3d& p0 = *v;
             if ( v != g->end()-1 ) // not last vert
             {
+                // calculate slice count
+                if ( sliceSize > 0.0 )
+                    slices = std::max( 1u, (unsigned)((*v - *(v+1)).length() / sliceSize) );
+
                 if ( isGeo )
-                    tessellateGeo( *v, *(v+1), _numPartitions, interp, newVerts );
+                    tessellateGeo( *v, *(v+1), slices, interp, newVerts );
                 else
-                    tessellateLinear( *v, *(v+1), _numPartitions, newVerts );
+                    tessellateLinear( *v, *(v+1), slices, newVerts );
             }
             else if ( isRing )
             {
+                // calculate slice count
+                if ( sliceSize > 0.0 )
+                    slices = std::max( 1u, (unsigned)((*v - *g->begin()).length() / sliceSize) );
+
                 if ( isGeo )
-                    tessellateGeo( *v, *g->begin(), _numPartitions, interp, newVerts );
+                    tessellateGeo( *v, *g->begin(), slices, interp, newVerts );
                 else
-                    tessellateLinear( *v, *g->begin(), _numPartitions, newVerts );
+                    tessellateLinear( *v, *g->begin(), slices, newVerts );
             }
             else 
             {
diff --git a/src/osgEarthFeatures/TextSymbolizer b/src/osgEarthFeatures/TextSymbolizer
index dddc54f..66bb49f 100644
--- a/src/osgEarthFeatures/TextSymbolizer
+++ b/src/osgEarthFeatures/TextSymbolizer
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthFeatures/TextSymbolizer.cpp b/src/osgEarthFeatures/TextSymbolizer.cpp
index 5d0aa95..afa36b0 100644
--- a/src/osgEarthFeatures/TextSymbolizer.cpp
+++ b/src/osgEarthFeatures/TextSymbolizer.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthFeatures/TransformFilter b/src/osgEarthFeatures/TransformFilter
index ff529d8..c7eaedd 100644
--- a/src/osgEarthFeatures/TransformFilter
+++ b/src/osgEarthFeatures/TransformFilter
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthFeatures/TransformFilter.cpp b/src/osgEarthFeatures/TransformFilter.cpp
index aa6a56a..f24ff78 100644
--- a/src/osgEarthFeatures/TransformFilter.cpp
+++ b/src/osgEarthFeatures/TransformFilter.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthFeatures/VirtualFeatureSource b/src/osgEarthFeatures/VirtualFeatureSource
index 7b77ef4..f1712e2 100644
--- a/src/osgEarthFeatures/VirtualFeatureSource
+++ b/src/osgEarthFeatures/VirtualFeatureSource
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthFeatures/VirtualFeatureSource.cpp b/src/osgEarthFeatures/VirtualFeatureSource.cpp
index 324ff91..cfc3de1 100644
--- a/src/osgEarthFeatures/VirtualFeatureSource.cpp
+++ b/src/osgEarthFeatures/VirtualFeatureSource.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthQt/Actions b/src/osgEarthQt/Actions
index 3bdd4b1..431db0f 100644
--- a/src/osgEarthQt/Actions
+++ b/src/osgEarthQt/Actions
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthQt/AnnotationDialogs b/src/osgEarthQt/AnnotationDialogs
index a03cf8e..0cb083b 100644
--- a/src/osgEarthQt/AnnotationDialogs
+++ b/src/osgEarthQt/AnnotationDialogs
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -21,7 +21,7 @@
 
 #include <osgEarthQt/Common>
 
-#include <osgEarth/Draggers>
+#include <osgEarthAnnotation/Draggers>
 #include <osgEarth/GeoMath>
 #include <osgEarthAnnotation/AnnotationEditing>
 #include <osgEarthAnnotation/EllipseNode>
@@ -395,14 +395,14 @@ namespace osgEarth { namespace QtGui
     osg::Vec3d _currentPoint;
   };
 
-  struct PointDraggerCallback : public osgEarth::Dragger::PositionChangedCallback
+  struct PointDraggerCallback : public osgEarth::Annotation::Dragger::PositionChangedCallback
   {
     PointDraggerCallback(int index, BaseAnnotationDialog* dialog)
       : _index(index), _dialog(dialog)
     {
     }
 
-    void onPositionChanged(const Dragger* sender, const osgEarth::GeoPoint& position)
+    void onPositionChanged(const osgEarth::Annotation::Dragger* sender, const osgEarth::GeoPoint& position)
     {
       _dialog->movePoint(_index, position);
     }
diff --git a/src/osgEarthQt/AnnotationDialogs.cpp b/src/osgEarthQt/AnnotationDialogs.cpp
index b6994f1..62ff14a 100644
--- a/src/osgEarthQt/AnnotationDialogs.cpp
+++ b/src/osgEarthQt/AnnotationDialogs.cpp
@@ -1,5 +1,5 @@
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -7,10 +7,13 @@
 * 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.
+* 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/>
@@ -19,8 +22,8 @@
 #include <osgEarthQt/AnnotationDialogs>
 #include <osgEarthQt/Common>
 
-#include <osgEarth/Draggers>
-#include <osgEarth/Pickers>
+#include <osgEarthAnnotation/Draggers>
+#include <osgEarth/IntersectionPicker>
 #include <osgEarthAnnotation/AnnotationData>
 #include <osgEarthAnnotation/AnnotationEditing>
 #include <osgEarthAnnotation/EllipseNode>
@@ -256,7 +259,12 @@ void AddMarkerDialog::accept()
     osgEarth::Annotation::AnnotationData* annoData = new osgEarth::Annotation::AnnotationData();
     annoData->setName(_altName.length() > 0 ? _altName : getName());
     annoData->setDescription(getDescription());
-    annoData->setViewpoint(osgEarth::Viewpoint(_placeNode->getPosition().vec3d(), 0.0, -90.0, 1e5, _placeNode->getPosition().getSRS()));
+    Viewpoint vp;
+    vp.focalPoint() = _placeNode->getPosition();
+    vp.pitch()->set(-90.0, Units::DEGREES);
+    vp.range()->set(1e5, Units::METERS);
+    annoData->setViewpoint( vp );
+    //annoData->setViewpoint(osgEarth::Viewpoint(_placeNode->getPosition().vec3d(), 0.0, -90.0, 1e5, _placeNode->getPosition().getSRS()));
     _placeNode->setAnnotationData(annoData);
   }
 
@@ -427,7 +435,7 @@ void AddPathDialog::refreshFeatureNode()
 
 void AddPathDialog::createPointDragger(int index, const osgEarth::GeoPoint& point)
 {
-  osgEarth::SphereDragger* sd = new osgEarth::SphereDragger(_mapNode);
+  osgEarth::Annotation::SphereDragger* sd = new osgEarth::Annotation::SphereDragger(_mapNode);
   sd->setSize(4.0f);
   sd->setColor(Color::Magenta);
   sd->setPickColor(Color::Green);
@@ -578,8 +586,14 @@ AddPolygonDialog::AddPolygonDialog(osg::Group* root, osgEarth::MapNode* mapNode,
         updateButtonColorStyle(_fillColorButton, QColor::fromRgbF(_fillColor.r(), _fillColor.g(), _fillColor.b(), _fillColor.a()));
       }
 
+      bool draped = false;
+      if (feat->style()->has<AltitudeSymbol>() && feat->style()->get<AltitudeSymbol>()->technique() == AltitudeSymbol::TECHNIQUE_DRAPE)
+      {
+          draped = true;
+      }
+
       //Get draping
-      _drapeCheckbox->setChecked(polygon->isDraped());
+      _drapeCheckbox->setChecked(draped);
 
       //Get path points
       const osgEarth::Symbology::Polygon* polyGeom = dynamic_cast<const osgEarth::Symbology::Polygon*>(feat->getGeometry());
@@ -716,7 +730,7 @@ void AddPolygonDialog::refreshFeatureNode(bool geometryOnly)
 
 void AddPolygonDialog::createPointDragger(int index, const osgEarth::GeoPoint& point)
 {
-  osgEarth::SphereDragger* sd = new osgEarth::SphereDragger(_mapNode);
+  osgEarth::Annotation::SphereDragger* sd = new osgEarth::Annotation::SphereDragger(_mapNode);
   sd->setSize(4.0f);
   sd->setColor(Color::Magenta);
   sd->setPickColor(Color::Green);
@@ -754,12 +768,16 @@ void AddPolygonDialog::addPoint(const osgEarth::GeoPoint& point)
     polyStyle.getOrCreate<LineSymbol>()->tessellation() = 20;
     polyStyle.getOrCreate<PolygonSymbol>()->fill()->color() = _fillColor;
     polyStyle.getOrCreate<AltitudeSymbol>()->clamping() = AltitudeSymbol::CLAMP_TO_TERRAIN;
+    if (_drapeCheckbox->checkState() == Qt::Checked)
+    {
+        polyStyle.getOrCreate<AltitudeSymbol>()->technique() = AltitudeSymbol::TECHNIQUE_DRAPE;
+    }
 
     _polyFeature = new osgEarth::Features::Feature(_polygon, _mapNode->getMapSRS(), polyStyle);
 
     if (!_polyNode.valid())
     {
-      _polyNode = new osgEarth::Annotation::FeatureNode(_mapNode, _polyFeature, _drapeCheckbox->checkState() == Qt::Checked);
+      _polyNode = new osgEarth::Annotation::FeatureNode(_mapNode, _polyFeature);
       _root->addChild(_polyNode);
     }
 
@@ -813,7 +831,19 @@ void AddPolygonDialog::onDrapeCheckStateChanged(int state)
   if (_polyNode.valid())
   {
     _root->removeChild(_polyNode);
-    _polyNode = new osgEarth::Annotation::FeatureNode(_mapNode, _polyFeature, _drapeCheckbox->checkState() == Qt::Checked);
+
+    Style style;
+    if (_drapeCheckbox->checkState() == Qt::Checked)
+    {
+        style.getOrCreate<AltitudeSymbol>()->clamping()  = AltitudeSymbol::CLAMP_TO_TERRAIN;
+        style.getOrCreate<AltitudeSymbol>()->technique() = AltitudeSymbol::TECHNIQUE_DRAPE;
+    }
+    else
+    {
+        style.getOrCreate<AltitudeSymbol>()->clamping()  = AltitudeSymbol::CLAMP_NONE;
+    }
+    
+    _polyNode = new osgEarth::Annotation::FeatureNode(_mapNode, _polyFeature, style);
     _root->addChild(_polyNode);
   }
 }
@@ -888,8 +918,14 @@ _editing(ellipse ? true : false), _inStyle(ellipse ? ellipse->getStyle() : osgEa
       updateButtonColorStyle(_fillColorButton, QColor::fromRgbF(_fillColor.r(), _fillColor.g(), _fillColor.b(), _fillColor.a()));
     }
 
+    bool draped = false;
+    if (ellipse->getStyle().has<AltitudeSymbol>() && ellipse->getStyle().get<AltitudeSymbol>()->technique() == AltitudeSymbol::TECHNIQUE_DRAPE)
+    {
+        draped = true;
+    }
+
     //Get draping
-    _drapeCheckbox->setChecked(ellipse->isDraped());
+    _drapeCheckbox->setChecked(draped);
 
     //Call refreshFeatureNode to update _ellipseStyle
     refreshFeatureNode();
@@ -1043,7 +1079,12 @@ void AddEllipseDialog::accept()
     osgEarth::Annotation::AnnotationData* annoData = new osgEarth::Annotation::AnnotationData();
     annoData->setName(getName());
     annoData->setDescription(getDescription());
-    annoData->setViewpoint(osgEarth::Viewpoint(_ellipseNode->getPosition().vec3d(), 0.0, -90.0, 1e5, _ellipseNode->getPosition().getSRS()));
+    Viewpoint vp;
+    vp.focalPoint() = _ellipseNode->getPosition();
+    vp.pitch()->set( -90.0, Units::DEGREES );
+    vp.range()->set( 1e5, Units::METERS );
+    annoData->setViewpoint( vp );
+    //annoData->setViewpoint(osgEarth::Viewpoint(_ellipseNode->getPosition().vec3d(), 0.0, -90.0, 1e5, _ellipseNode->getPosition().getSRS()));
 
     _ellipseNode->setAnnotationData(annoData);
   }
diff --git a/src/osgEarthQt/AnnotationListWidget b/src/osgEarthQt/AnnotationListWidget
index ecd55df..5f40ef1 100644
--- a/src/osgEarthQt/AnnotationListWidget
+++ b/src/osgEarthQt/AnnotationListWidget
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthQt/AnnotationListWidget.cpp b/src/osgEarthQt/AnnotationListWidget.cpp
index 149650c..5a47417 100644
--- a/src/osgEarthQt/AnnotationListWidget.cpp
+++ b/src/osgEarthQt/AnnotationListWidget.cpp
@@ -1,5 +1,5 @@
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -7,10 +7,13 @@
 * 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.
+* 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/>
@@ -253,7 +256,8 @@ void AnnotationListWidget::onItemDoubleClicked(QListWidgetItem* item)
       output.fromWorld( _manager->map()->getSRS(), center );
       //_manager->map()->worldPointToMapPoint(center, output);
 
-      _manager->doAction(this, new SetViewpointAction(osgEarth::Viewpoint(output.vec3d(), 0.0, -90.0, 1e5), _views));
+      _manager->doAction(this, new SetViewpointAction(osgEarth::Viewpoint(
+          "doubleclick", output.x(), output.y(), output.z(), 0.0, -90.0, 1e5), _views));
     }
   }
 }
diff --git a/src/osgEarthQt/AnnotationToolbar b/src/osgEarthQt/AnnotationToolbar
index aecd4a3..a0fb27a 100644
--- a/src/osgEarthQt/AnnotationToolbar
+++ b/src/osgEarthQt/AnnotationToolbar
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthQt/AnnotationToolbar.cpp b/src/osgEarthQt/AnnotationToolbar.cpp
index d2ab30b..c09551d 100644
--- a/src/osgEarthQt/AnnotationToolbar.cpp
+++ b/src/osgEarthQt/AnnotationToolbar.cpp
@@ -1,5 +1,5 @@
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -7,10 +7,13 @@
 * 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.
+* 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/>
diff --git a/src/osgEarthQt/CollapsiblePairWidget b/src/osgEarthQt/CollapsiblePairWidget
index 925f3b7..4755584 100644
--- a/src/osgEarthQt/CollapsiblePairWidget
+++ b/src/osgEarthQt/CollapsiblePairWidget
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthQt/CollapsiblePairWidget.cpp b/src/osgEarthQt/CollapsiblePairWidget.cpp
index 9c87496..a9f04a9 100644
--- a/src/osgEarthQt/CollapsiblePairWidget.cpp
+++ b/src/osgEarthQt/CollapsiblePairWidget.cpp
@@ -1,5 +1,5 @@
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -7,10 +7,13 @@
 * 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.
+* 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/>
diff --git a/src/osgEarthQt/Common b/src/osgEarthQt/Common
index 7e354ee..73147d9 100644
--- a/src/osgEarthQt/Common
+++ b/src/osgEarthQt/Common
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthQt/DataManager b/src/osgEarthQt/DataManager
index 53c3b9b..6390749 100644
--- a/src/osgEarthQt/DataManager
+++ b/src/osgEarthQt/DataManager
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthQt/DataManager.cpp b/src/osgEarthQt/DataManager.cpp
index 9b7dffb..7b87b58 100644
--- a/src/osgEarthQt/DataManager.cpp
+++ b/src/osgEarthQt/DataManager.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/osgEarthQt/GuiActions b/src/osgEarthQt/GuiActions
index 6aa0ce6..628ce2a 100644
--- a/src/osgEarthQt/GuiActions
+++ b/src/osgEarthQt/GuiActions
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthQt/LOSControlWidget b/src/osgEarthQt/LOSControlWidget
index d5d9fd2..f500ec8 100644
--- a/src/osgEarthQt/LOSControlWidget
+++ b/src/osgEarthQt/LOSControlWidget
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthQt/LOSControlWidget.cpp b/src/osgEarthQt/LOSControlWidget.cpp
index 9832a1b..4214c23 100644
--- a/src/osgEarthQt/LOSControlWidget.cpp
+++ b/src/osgEarthQt/LOSControlWidget.cpp
@@ -1,5 +1,5 @@
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -7,10 +7,13 @@
 * 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.
+* 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/>
@@ -238,7 +241,8 @@ void LOSControlWidget::onItemDoubleClicked(QListWidgetItem* item)
 
     double range = losItem->los()->getBound().radius() / 0.267949849;
 
-    _manager->doAction(this, new SetViewpointAction(osgEarth::Viewpoint(output.vec3d(), 0.0, -90.0, range), _views));
+    _manager->doAction(this, new SetViewpointAction(osgEarth::Viewpoint(
+        "doubleclick", output.x(), output.y(), output.z(), 0.0, -90.0, range), _views));
   }
 }
 
diff --git a/src/osgEarthQt/LOSCreationDialog b/src/osgEarthQt/LOSCreationDialog
index d0f2f87..eb38c08 100644
--- a/src/osgEarthQt/LOSCreationDialog
+++ b/src/osgEarthQt/LOSCreationDialog
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -24,7 +24,7 @@
 #include <osgEarthQt/Common>
 #include <osgEarthQt/DataManager>
 
-#include <osgEarth/Draggers>
+#include <osgEarthAnnotation/Draggers>
 #include <osgEarthUtil/LinearLineOfSight>
 #include <osgEarthUtil/RadialLineOfSight>
 
@@ -34,11 +34,11 @@
 
 namespace osgEarth { namespace QtGui 
 {
-  class LOSIntersectingDragger : public osgEarth::SphereDragger
+  class LOSIntersectingDragger : public osgEarth::Annotation::SphereDragger
   {
   public:
       LOSIntersectingDragger(osgEarth::MapNode* mapNode):
-        osgEarth::SphereDragger(mapNode),
+        osgEarth::Annotation::SphereDragger(mapNode),
         _heightAboveTerrain(0.0)
     {
       setLineColor(osg::Vec4(1.0f, 1.0f, 0.0f, 0.0f));
diff --git a/src/osgEarthQt/LOSCreationDialog.cpp b/src/osgEarthQt/LOSCreationDialog.cpp
index 59cbdc8..0be65e2 100644
--- a/src/osgEarthQt/LOSCreationDialog.cpp
+++ b/src/osgEarthQt/LOSCreationDialog.cpp
@@ -1,5 +1,5 @@
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -7,10 +7,13 @@
 * 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.
+* 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/>
@@ -760,7 +763,8 @@ void LOSCreationDialog::centerMapOnNode(osg::Node* node)
       output.fromWorld( _map->getSRS(), center );
       //_map->worldPointToMapPoint(center, output);
 
-      _manager->doAction(this, new SetViewpointAction(osgEarth::Viewpoint(output.vec3d(), 0.0, -90.0, 1e5), *_views));
+      _manager->doAction(this, new SetViewpointAction(osgEarth::Viewpoint(
+          "center", output.x(), output.y(), output.z(), 0.0, -90.0, 1e5), *_views));
     }
   }
 }
diff --git a/src/osgEarthQt/LayerManagerWidget b/src/osgEarthQt/LayerManagerWidget
index 6d66be3..a46906a 100644
--- a/src/osgEarthQt/LayerManagerWidget
+++ b/src/osgEarthQt/LayerManagerWidget
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthQt/LayerManagerWidget.cpp b/src/osgEarthQt/LayerManagerWidget.cpp
index 0e3ce65..ce1a8e7 100644
--- a/src/osgEarthQt/LayerManagerWidget.cpp
+++ b/src/osgEarthQt/LayerManagerWidget.cpp
@@ -1,5 +1,5 @@
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -7,10 +7,13 @@
 * 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.
+* 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/>
@@ -410,7 +413,7 @@ Action* ElevationLayerControlWidget::getDoubleClickAction(const ViewVector& view
 	  if (range == 0.0)
 		  range = 20000000.0;
 
-    _doubleClick = new SetViewpointAction(osgEarth::Viewpoint(focalPoint, 0.0, -90.0, range), views);
+    _doubleClick = new SetViewpointAction(osgEarth::Viewpoint("ClickAction", focalPoint.x(), focalPoint.y(), focalPoint.z(), 0.0, -90.0, range), views);
   }
 
   return _doubleClick.get();
@@ -533,7 +536,7 @@ Action* ImageLayerControlWidget::getDoubleClickAction(const ViewVector& views)
 	  if (range == 0.0)
 		  range = 20000000.0;
 
-    _doubleClick = new SetViewpointAction(osgEarth::Viewpoint(focalPoint, 0.0, -90.0, range), views);
+    _doubleClick = new SetViewpointAction(osgEarth::Viewpoint("DoubleClick", focalPoint.x(), focalPoint.y(), focalPoint.z(), 0.0, -90.0, range), views);
   }
 
   return _doubleClick.get();
@@ -639,7 +642,7 @@ Action* ModelLayerControlWidget::getDoubleClickAction(const ViewVector& views)
         //_map->worldPointToMapPoint(center, output);
 
         //TODO: make a better range calculation
-        return new SetViewpointAction(osgEarth::Viewpoint(output.vec3d(), 0.0, -90.0, bs.radius() * 4.0), views);
+        return new SetViewpointAction(osgEarth::Viewpoint("DoubleClick", output.x(), output.y(), output.z(), 0.0, -90.0, bs.radius() * 4.0), views);
       }
     }
   }
diff --git a/src/osgEarthQt/MapCatalogWidget b/src/osgEarthQt/MapCatalogWidget
index 4fa4bdc..8c9e31e 100644
--- a/src/osgEarthQt/MapCatalogWidget
+++ b/src/osgEarthQt/MapCatalogWidget
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthQt/MapCatalogWidget.cpp b/src/osgEarthQt/MapCatalogWidget.cpp
index f00b55d..01d088c 100644
--- a/src/osgEarthQt/MapCatalogWidget.cpp
+++ b/src/osgEarthQt/MapCatalogWidget.cpp
@@ -1,5 +1,5 @@
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -7,10 +7,13 @@
 * 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.
+* 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/>
@@ -102,7 +105,8 @@ namespace
           if (range == 0.0)
               range = 20000000.0;
 
-          _doubleClick = new SetViewpointAction(osgEarth::Viewpoint(focalPoint, 0.0, -90.0, range), views);
+          _doubleClick = new SetViewpointAction(osgEarth::Viewpoint(
+              "doubleclick", focalPoint.x(), focalPoint.y(), focalPoint.z(), 0.0, -90.0, range), views);
         }
         else
         {
@@ -132,7 +136,8 @@ namespace
                 //_map->worldPointToMapPoint(center, output);
 
                 //TODO: make a better range calculation
-                return new SetViewpointAction(osgEarth::Viewpoint(output.vec3d(), 0.0, -90.0, bs.radius() * 4.0), views);
+                return new SetViewpointAction(osgEarth::Viewpoint(
+                    "doubleclick", output.x(), output.y(), output.z(), 0.0, -90.0, bs.radius() * 4.0), views);
               }
             }
           }
@@ -190,7 +195,13 @@ namespace
           output.fromWorld( _map->getSRS(), center );
           //_map->worldPointToMapPoint(center, output);
 
-          return new SetViewpointAction(osgEarth::Viewpoint(output.vec3d(), 0.0, -90.0, 1e5), views);
+          osgEarth::Viewpoint vp;
+          vp.focalPoint() = output;
+          vp.range() = 1e5;
+          vp.heading() = 0.0;
+          vp.pitch() = -90.0;
+
+          return new SetViewpointAction(vp, views);
         }
       }
 
@@ -595,7 +606,7 @@ void MapCatalogWidget::refreshViewpoints()
     for (std::vector<osgEarth::Viewpoint>::const_iterator it = viewpoints.begin(); it != viewpoints.end(); ++it)
     {
       ViewpointTreeItem* vpItem = new ViewpointTreeItem(*it);
-      vpItem->setText(0, QString((*it).getName().c_str()));
+      vpItem->setText(0, QString((*it).name()->c_str()));
 			_viewpointsItem->addChild(vpItem);
     }
 
diff --git a/src/osgEarthQt/TerrainProfileGraph b/src/osgEarthQt/TerrainProfileGraph
index 3d120a0..d0932eb 100644
--- a/src/osgEarthQt/TerrainProfileGraph
+++ b/src/osgEarthQt/TerrainProfileGraph
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthQt/TerrainProfileGraph.cpp b/src/osgEarthQt/TerrainProfileGraph.cpp
index fca6485..cee6bd9 100644
--- a/src/osgEarthQt/TerrainProfileGraph.cpp
+++ b/src/osgEarthQt/TerrainProfileGraph.cpp
@@ -1,5 +1,5 @@
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -7,10 +7,13 @@
 * 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.
+* 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/>
diff --git a/src/osgEarthQt/TerrainProfileWidget b/src/osgEarthQt/TerrainProfileWidget
index 79961ba..bce1bef 100644
--- a/src/osgEarthQt/TerrainProfileWidget
+++ b/src/osgEarthQt/TerrainProfileWidget
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthQt/TerrainProfileWidget.cpp b/src/osgEarthQt/TerrainProfileWidget.cpp
index 4b17ba2..a767d2f 100644
--- a/src/osgEarthQt/TerrainProfileWidget.cpp
+++ b/src/osgEarthQt/TerrainProfileWidget.cpp
@@ -1,5 +1,5 @@
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -7,10 +7,13 @@
 * 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.
+* 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/>
diff --git a/src/osgEarthQt/ViewWidget b/src/osgEarthQt/ViewWidget
index 11c2bac..6aeed19 100644
--- a/src/osgEarthQt/ViewWidget
+++ b/src/osgEarthQt/ViewWidget
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -47,7 +47,7 @@ namespace osgEarth { namespace QtGui
          *
          * @param[in ] view   View to attach to this widget. The widget will install
          *             a new camera in the view. (NOTE: this widget does not take
-         *             ownership of the View. You are still respsonsile for its
+         *             ownership of the View. You are still responsible for its
          *             destruction)
          * @param[in ] gcToShare Graphics context to share with this new view.
          *             Usually you should just leave this NULL
@@ -55,6 +55,18 @@ namespace osgEarth { namespace QtGui
         ViewWidget(osgViewer::View* view, osg::GraphicsContext* gcToShare =0L);
 
         /**
+        * Constructs a new ViewWidget, attaching an existing view, with specific GL Format.
+        *
+        * @param[in ] view   View to attach to this widget. The widget will install
+        *             a new camera in the view. (NOTE: this widget does not take
+        *             ownership of the View. You are still responsible for its
+        *             destruction)
+        * @param[in ] format  GL format for the graphics context.  Use this parameter to
+        *             specify multisampling, for example.
+        */
+        ViewWidget(osgViewer::View* view, const QGLFormat& format);
+
+        /**
          * Access the underlying view.
          */
         osgViewer::View* getView() { return _view.get(); }
diff --git a/src/osgEarthQt/ViewWidget.cpp b/src/osgEarthQt/ViewWidget.cpp
index 833c438..700fd86 100644
--- a/src/osgEarthQt/ViewWidget.cpp
+++ b/src/osgEarthQt/ViewWidget.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
@@ -30,12 +33,21 @@ using namespace osgEarth::QtGui;
 
 
 ViewWidget::ViewWidget(osgViewer::View* view, osg::GraphicsContext* gc) :
+osgQt::GLWidget(),
 _view( view )
 {
     init( gc );
 }
 
 
+ViewWidget::ViewWidget(osgViewer::View* view, const QGLFormat& format) :
+osgQt::GLWidget(format),
+_view( view )
+{
+    init( NULL );
+}
+
+
 void
 ViewWidget::init(osg::GraphicsContext* gc)
 {
diff --git a/src/osgEarthQt/ViewerWidget b/src/osgEarthQt/ViewerWidget
index 75af407..f7125c3 100644
--- a/src/osgEarthQt/ViewerWidget
+++ b/src/osgEarthQt/ViewerWidget
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthQt/ViewerWidget.cpp b/src/osgEarthQt/ViewerWidget.cpp
index 164712b..8c36c2a 100644
--- a/src/osgEarthQt/ViewerWidget.cpp
+++ b/src/osgEarthQt/ViewerWidget.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/osgEarthSymbology/AGG.h b/src/osgEarthSymbology/AGG.h
index fc56413..f838b21 100644
--- a/src/osgEarthSymbology/AGG.h
+++ b/src/osgEarthSymbology/AGG.h
@@ -403,7 +403,7 @@ namespace agg
     //     ras.render(ren, agg::rgba8(200, 100, 80));
     //  
     //------------------------------------------------------------------------
-    template<class Span> class renderer
+    template<class Span, class Type> class renderer
     {
     public:
         //--------------------------------------------------------------------
@@ -412,7 +412,7 @@ namespace agg
         }
         
         //--------------------------------------------------------------------
-        void clear(const rgba8& c)
+        void clear(const Type& c)
         {
             unsigned y;
             for(y = 0; y < m_rbuf->height(); y++)
@@ -422,7 +422,7 @@ namespace agg
         }
 
         //--------------------------------------------------------------------
-        void pixel(int x, int y, const rgba8& c)
+        void pixel(int x, int y, const Type& c)
         {
             if(m_rbuf->inbox(x, y))
             {
@@ -431,17 +431,17 @@ namespace agg
         }
 
         //--------------------------------------------------------------------
-        rgba8 pixel(int x, int y) const
+        Type pixel(int x, int y) const
         {
             if(m_rbuf->inbox(x, y))
             {
                 return m_span.get(m_rbuf->row(y), x);
             }
-            return rgba8(0,0,0);
+            return Type(0,0,0);
         }
 
         //--------------------------------------------------------------------
-        void render(const scanline& sl, const rgba8& c)
+        void render(const scanline& sl, const Type& c)
         {
             if(sl.y() < 0 || sl.y() >= int(m_rbuf->height()))
             {
@@ -694,10 +694,10 @@ namespace agg
         }
 
         //--------------------------------------------------------------------
-        template<class Renderer> void render(Renderer& r, 
-                                             const rgba8& c, 
-                                             int dx=0, 
-                                             int dy=0)
+        template<class Renderer, class Type> void render(Renderer& r, 
+                                                         const Type& c, //const rgba8& c, 
+                                                         int dx=0, 
+                                                         int dy=0)
         {
             const cell* const* cells = m_outline.cells();
             if(m_outline.num_cells() == 0) return;
diff --git a/src/osgEarthSymbology/AltitudeSymbol b/src/osgEarthSymbology/AltitudeSymbol
index b84b735..0f612b4 100644
--- a/src/osgEarthSymbology/AltitudeSymbol
+++ b/src/osgEarthSymbology/AltitudeSymbol
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -84,8 +84,9 @@ namespace osgEarth { namespace Symbology
         };
 
     public:
-        META_Symbol(AltitudeSymbol);
+        META_Object(osgEarthSymbology, AltitudeSymbol);
 
+        AltitudeSymbol(const AltitudeSymbol& rhs,const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY);
         AltitudeSymbol( const Config& conf =Config() );
 
         /** How to clamp instances to the terrain (default is CLAMP_NONE) */
diff --git a/src/osgEarthSymbology/AltitudeSymbol.cpp b/src/osgEarthSymbology/AltitudeSymbol.cpp
index 2fb3373..6358c1c 100644
--- a/src/osgEarthSymbology/AltitudeSymbol.cpp
+++ b/src/osgEarthSymbology/AltitudeSymbol.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -36,6 +36,18 @@ _verticalOffset    ( NumericExpression(0.0) )
     mergeConfig( conf );
 }
 
+AltitudeSymbol::AltitudeSymbol(const AltitudeSymbol& rhs,const osg::CopyOp& copyop):
+Symbol(rhs, copyop),
+_clamping(rhs._clamping),
+_technique(rhs._technique),
+_binding(rhs._binding),
+_resolution(rhs._resolution),
+_verticalOffset(rhs._verticalOffset),
+_verticalScale(rhs._verticalScale)
+{
+
+}
+
 Config 
 AltitudeSymbol::getConfig() const
 {
diff --git a/src/osgEarthSymbology/CMakeLists.txt b/src/osgEarthSymbology/CMakeLists.txt
index 0a922e4..58b09ab 100644
--- a/src/osgEarthSymbology/CMakeLists.txt
+++ b/src/osgEarthSymbology/CMakeLists.txt
@@ -17,6 +17,7 @@ SET(LIB_PUBLIC_HEADERS
     AltitudeSymbol
     Common
     Color
+    CoverageSymbol
     CssUtils
     Expression
     ExtrusionSymbol
@@ -33,6 +34,7 @@ SET(LIB_PUBLIC_HEADERS
     MarkerResource
     MarkerSymbol
     MeshConsolidator
+    MeshFlattener
     MeshSubdivider
     ModelResource
     ModelSymbol
@@ -59,6 +61,7 @@ ADD_LIBRARY(${LIB_NAME} ${OSGEARTH_USER_DEFINED_DYNAMIC_OR_STATIC}
 #  .cpp files go here
     AltitudeSymbol.cpp
     Color.cpp
+    CoverageSymbol.cpp
     CssUtils.cpp
     Expression.cpp
     ExtrusionSymbol.cpp
@@ -75,6 +78,7 @@ ADD_LIBRARY(${LIB_NAME} ${OSGEARTH_USER_DEFINED_DYNAMIC_OR_STATIC}
     MarkerResource.cpp
     MarkerSymbol.cpp
     MeshConsolidator.cpp
+    MeshFlattener.cpp
     MeshSubdivider.cpp
     ModelResource.cpp
     ModelSymbol.cpp
diff --git a/src/osgEarthSymbology/Color b/src/osgEarthSymbology/Color
index e52c82c..507a85a 100644
--- a/src/osgEarthSymbology/Color
+++ b/src/osgEarthSymbology/Color
@@ -1,6 +1,6 @@
 /* --*-c++-*-- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthSymbology/Color.cpp b/src/osgEarthSymbology/Color.cpp
index 85fb7bb..f8ff0b0 100644
--- a/src/osgEarthSymbology/Color.cpp
+++ b/src/osgEarthSymbology/Color.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthSymbology/Common b/src/osgEarthSymbology/Common
index 75be97e..124ef99 100644
--- a/src/osgEarthSymbology/Common
+++ b/src/osgEarthSymbology/Common
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthSymbology/CoverageSymbol b/src/osgEarthSymbology/CoverageSymbol
new file mode 100644
index 0000000..cc0e09c
--- /dev/null
+++ b/src/osgEarthSymbology/CoverageSymbol
@@ -0,0 +1,61 @@
+/* -*-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.
+ *
+ * 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 OSGEARTHSYMBOLOGY_COVERAGE_SYMBOL_H
+#define OSGEARTHSYMBOLOGY_COVERAGE_SYMBOL_H 1
+
+#include <osgEarth/Common>
+#include <osgEarthSymbology/Expression>
+#include <osgEarthSymbology/Symbol>
+#include <osg/Referenced>
+#include <vector>
+
+namespace osgEarth { namespace Symbology
+{
+    /** 
+     * Symbol that contains coverage encoding information
+     */
+    class OSGEARTHSYMBOLOGY_EXPORT CoverageSymbol : public Symbol
+    {
+    public:
+        META_Object(osgEarthSymbology, CoverageSymbol);
+
+        /** construct a symbol */
+        CoverageSymbol(const Config& conf =Config());
+        CoverageSymbol(const CoverageSymbol& rhs,const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY);
+
+        /** coverage value expression; resolves to a 32-bit floating point number. */
+        optional<NumericExpression>& valueExpression() { return _valueExpr; }
+        const optional<NumericExpression>& valueExpression() const { return _valueExpr; }
+
+    public:
+        virtual Config getConfig() const;
+        virtual void mergeConfig( const Config& conf );
+        static void parseSLD(const Config& c, class Style& style);
+
+    protected:
+        optional<NumericExpression> _valueExpr;
+        
+        /** dtor */
+        virtual ~CoverageSymbol() { }
+    };
+
+} } // namespace osgEarth::Symbology
+
+#endif // OSGEARTHSYMBOLOGY_COVERAGE_SYMBOL_H
diff --git a/src/osgEarthSymbology/CoverageSymbol.cpp b/src/osgEarthSymbology/CoverageSymbol.cpp
new file mode 100644
index 0000000..c5176b0
--- /dev/null
+++ b/src/osgEarthSymbology/CoverageSymbol.cpp
@@ -0,0 +1,63 @@
+/* -*-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.
+ *
+ * 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 <osgEarthSymbology/CoverageSymbol>
+#include <osgEarthSymbology/Style>
+
+using namespace osgEarth;
+using namespace osgEarth::Symbology;
+
+OSGEARTH_REGISTER_SIMPLE_SYMBOL(coverage, CoverageSymbol);
+
+CoverageSymbol::CoverageSymbol(const CoverageSymbol& rhs,const osg::CopyOp& copyop):
+Symbol(rhs, copyop),
+_valueExpr( rhs._valueExpr )
+{
+    //nop
+}
+
+CoverageSymbol::CoverageSymbol( const Config& conf ) :
+Symbol( conf )
+{
+    mergeConfig(conf);
+}
+
+Config 
+CoverageSymbol::getConfig() const
+{
+    Config conf = Symbol::getConfig();
+    conf.key() = "coverage";
+    conf.addObjIfSet( "value", _valueExpr );
+    return conf;
+}
+
+void 
+CoverageSymbol::mergeConfig( const Config& conf )
+{
+    conf.getObjIfSet( "value", _valueExpr );
+}
+
+
+void
+CoverageSymbol::parseSLD(const Config& c, Style& style)
+{
+    if ( match(c.key(), "coverage-value") ) {
+        style.getOrCreate<CoverageSymbol>()->valueExpression() = NumericExpression(c.value());
+    }
+}
+
diff --git a/src/osgEarthSymbology/CssUtils b/src/osgEarthSymbology/CssUtils
index 4b99cc7..a3b6add 100644
--- a/src/osgEarthSymbology/CssUtils
+++ b/src/osgEarthSymbology/CssUtils
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthSymbology/CssUtils.cpp b/src/osgEarthSymbology/CssUtils.cpp
index f88eb10..5fa7c46 100644
--- a/src/osgEarthSymbology/CssUtils.cpp
+++ b/src/osgEarthSymbology/CssUtils.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthSymbology/Expression b/src/osgEarthSymbology/Expression
index c2992e1..3e4a61f 100644
--- a/src/osgEarthSymbology/Expression
+++ b/src/osgEarthSymbology/Expression
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthSymbology/Expression.cpp b/src/osgEarthSymbology/Expression.cpp
index f23c1a4..f94dbfd 100644
--- a/src/osgEarthSymbology/Expression.cpp
+++ b/src/osgEarthSymbology/Expression.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/osgEarthSymbology/ExtrusionSymbol b/src/osgEarthSymbology/ExtrusionSymbol
index 685d59f..b680ca7 100644
--- a/src/osgEarthSymbology/ExtrusionSymbol
+++ b/src/osgEarthSymbology/ExtrusionSymbol
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -49,8 +49,9 @@ namespace osgEarth { namespace Symbology
         };
 
     public:
-        META_Symbol(ExtrusionSymbol);
+        META_Object(osgEarthSymbology, ExtrusionSymbol);
 
+        ExtrusionSymbol(const ExtrusionSymbol& rhs,const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY);
         ExtrusionSymbol( const Config& conf =Config() );
 
         /** dtor */
diff --git a/src/osgEarthSymbology/ExtrusionSymbol.cpp b/src/osgEarthSymbology/ExtrusionSymbol.cpp
index f4e836d..69f10a0 100644
--- a/src/osgEarthSymbology/ExtrusionSymbol.cpp
+++ b/src/osgEarthSymbology/ExtrusionSymbol.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -24,6 +24,18 @@ using namespace osgEarth::Symbology;
 
 OSGEARTH_REGISTER_SIMPLE_SYMBOL(extrusion, ExtrusionSymbol);
 
+ExtrusionSymbol::ExtrusionSymbol(const ExtrusionSymbol& rhs,const osg::CopyOp& copyop):
+Symbol(rhs, copyop)
+{
+    _height = rhs._height;
+    _flatten = rhs._flatten;
+    _heightExpr = rhs._heightExpr;
+    _heightRef = rhs._heightRef;
+    _wallStyleName = rhs._wallStyleName;
+    _roofStyleName = rhs._roofStyleName;
+    _wallGradientPercentage = rhs._wallGradientPercentage;
+}
+
 ExtrusionSymbol::ExtrusionSymbol( const Config& conf ) :
 Symbol    ( conf ),
 _height   ( 10.0 ),
diff --git a/src/osgEarthSymbology/FeatureDataSet b/src/osgEarthSymbology/FeatureDataSet
deleted file mode 100644
index b008325..0000000
--- a/src/osgEarthSymbology/FeatureDataSet
+++ /dev/null
@@ -1,54 +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 OSGEARTHSYMBOLOGY_FEATUREDATASET_H
-#define OSGEARTHSYMBOLOGY_FEATUREDATASET_H 1
-
-#include <osgEarthSymbology/Common>
-#include <osgEarthFeatures/FeatureSource>
-
-namespace osgEarth { namespace Symbology 
-{
-    /**
-     * Source for (possibly dynamic) feature data that is to be symbolized.
-     */
-    class OSGEARTHSYMBOLOGY_EXPORT FeatureDataSet : public osg::Referenced
-    {
-    public:
-        /**
-         * Creates and returns an iterator that the caller can use to access
-         * each of the Features in this dataset. Caller is responsible for the
-         * return object.
-         */
-        virtual osgEarth::Features::FeatureCursor* createCursor() =0;
-
-        /**
-         * Gets the revision number of the dataset. Caller can use this is detect
-         * whether it is up to date with the latest version of the data set.
-         */
-        virtual int getRevision() const =0;
-
-    protected:
-        FeatureDataSet();
-    };
-
-} } // namespace osgEarth::Symbology
-
-
-#endif // OSGEARTHSYMBOLOGY_FEATUREDATASET_H
-
diff --git a/src/osgEarthSymbology/FeatureDataSetAdapter b/src/osgEarthSymbology/FeatureDataSetAdapter
deleted file mode 100644
index ad64c00..0000000
--- a/src/osgEarthSymbology/FeatureDataSetAdapter
+++ /dev/null
@@ -1,58 +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 OSGEARTHSYMBOLOGY_FEATUREDATASET_ADAPTER_H
-#define OSGEARTHSYMBOLOGY_FEATUREDATASET_ADAPTER_H 1
-
-#include <osgEarthSymbology/Common>
-#include <osgEarthSymbology/FeatureDataSet>
-#include <osgEarthFeatures/FeatureSource>
-
-namespace osgEarth { namespace Symbology 
-{
-    /**
-     * Source for (possibly dynamic) feature data that is to be symbolized.
-     */
-    class OSGEARTHSYMBOLOGY_EXPORT FeatureDataSetAdapter : public FeatureDataSet
-    {
-    public:
-        /**
-         * Creates and returns an iterator that the caller can use to access
-         * each of the Features in this dataset. Caller is responsible for the
-         * return object.
-         */
-        osgEarth::Features::FeatureCursor* createCursor();
-
-        /**
-         * Gets the revision number of the dataset. Caller can use this is detect
-         * whether it is up to date with the latest version of the data set.
-         */
-        int getRevision() const;
-
-        FeatureDataSetAdapter(osgEarth::Features::FeatureSource* source);
-        
-    protected:
-
-        osg::ref_ptr<osgEarth::Features::FeatureSource> _features;
-    };
-
-} } // namespace osgEarth::Symbology
-
-
-#endif
-
diff --git a/src/osgEarthSymbology/Fill b/src/osgEarthSymbology/Fill
index 542f3c9..512d778 100644
--- a/src/osgEarthSymbology/Fill
+++ b/src/osgEarthSymbology/Fill
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthSymbology/Fill.cpp b/src/osgEarthSymbology/Fill.cpp
index fc02379..392498b 100644
--- a/src/osgEarthSymbology/Fill.cpp
+++ b/src/osgEarthSymbology/Fill.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthSymbology/GEOS b/src/osgEarthSymbology/GEOS
index bd11e43..b695bb7 100644
--- a/src/osgEarthSymbology/GEOS
+++ b/src/osgEarthSymbology/GEOS
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthSymbology/GEOS.cpp b/src/osgEarthSymbology/GEOS.cpp
index 6f7f1bd..cf04e33 100644
--- a/src/osgEarthSymbology/GEOS.cpp
+++ b/src/osgEarthSymbology/GEOS.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -54,7 +54,7 @@ namespace
         coords->reserve( input->size() + (needToClose ? 1 : 0) );
         for( osg::Vec3dArray::const_iterator i = input->begin(); i != input->end(); ++i )
         {
-            coords->push_back( geom::Coordinate( i->x(), i->y() ) ); //, i->z() ));
+            coords->push_back( geom::Coordinate( i->x(), i->y(), i->z() ));
         }
         if ( needToClose )
         {
@@ -175,7 +175,6 @@ namespace
         const geom::LineString* outerRing = input->getExteriorRing();
         if ( outerRing )
         {
-            // leaks here:
             const geom::CoordinateSequence* s = outerRing->getCoordinatesRO();
 
             output = new Symbology::Polygon( s->getSize() );
@@ -183,7 +182,8 @@ namespace
             for( unsigned int j=0; j<s->getSize(); j++ ) 
             {
                 const geom::Coordinate& c = s->getAt( j );
-                output->push_back( osg::Vec3d( c.x, c.y, 0 ) ); 
+                output->push_back( osg::Vec3d( c.x, c.y, !osg::isNaN(c.z)? c.z : 0.0) );
+                //OE_NOTICE << "c.z = " << c.z << "\n";
             }
             output->rewind( Symbology::Ring::ORIENTATION_CCW );
 
@@ -195,7 +195,7 @@ namespace
                 for( unsigned int m = 0; m<s->getSize(); m++ )
                 {
                     const geom::Coordinate& c = s->getAt( m );
-                    hole->push_back( osg::Vec3d( c.x, c.y, 0 ) );
+                    hole->push_back( osg::Vec3d( c.x, c.y, !osg::isNaN(c.z)? c.z : 0.0) );
                 }
                 hole->rewind( Symbology::Ring::ORIENTATION_CW );
                 output->getHoles().push_back( hole );
@@ -256,9 +256,15 @@ GEOSContext::exportGeometry(const geom::Geometry* input)
         Symbology::PointSet* part = new Symbology::PointSet( mp->getNumPoints() );
         for( unsigned int i=0; i < mp->getNumPoints(); i++ )
         {
-            const geom::Point* p = dynamic_cast<const geom::Point*>( mp->getGeometryN(i) );
-            if ( p )
-                part->push_back( osg::Vec3d( p->getX(), p->getY(), 0 ) );
+            const geom::Geometry* g = mp->getGeometryN(i);
+            if ( g )
+            {
+                const geom::Coordinate* c = mp->getCoordinate();
+                if ( c )
+                {
+                    part->push_back( osg::Vec3d( c->x, c->y, c->z ) ); //p->getX(), p->getY(), 0 ) );
+                }
+            }
         }
         parts.push_back( part );
     }
@@ -269,7 +275,7 @@ GEOSContext::exportGeometry(const geom::Geometry* input)
         for( unsigned int i=0; i<line->getNumPoints(); i++ )
         {
             const geom::Coordinate& c = line->getCoordinateN(i);
-            part->push_back( osg::Vec3d( c.x, c.y, 0 ) );
+            part->push_back( osg::Vec3d( c.x, c.y, c.z ) ); //0 ) );
         }
         parts.push_back( part );
     }
diff --git a/src/osgEarthSymbology/Geometry b/src/osgEarthSymbology/Geometry
index 21e6894..5e41302 100644
--- a/src/osgEarthSymbology/Geometry
+++ b/src/osgEarthSymbology/Geometry
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -134,6 +134,11 @@ namespace osgEarth { namespace Symbology
         virtual Bounds getBounds() const;
 
         /**
+         * Length of the [outermost] geometry.
+         */
+        virtual double getLength() const;
+
+        /**
          * Whether the geometry is lines
          */
         bool isLinear() const { return getComponentType() == TYPE_LINESTRING || getComponentType() == TYPE_RING; }
@@ -156,6 +161,14 @@ namespace osgEarth { namespace Symbology
             osg::ref_ptr<Geometry>& output ) const;
 
         /**
+         * Creates the union of this geometry with the other geometry, returning
+         * the result in the output parameter. Returns true if the op succeeded.
+         */
+        bool geounion(
+            const Geometry* other,
+            osg::ref_ptr<Geometry>& output ) const;
+
+        /**
          * Boolean difference - subtracts diffPolygon from this geometry, and put the
          * result in output.
          */
@@ -181,6 +194,11 @@ namespace osgEarth { namespace Symbology
         virtual void rewind( Orientation ori );
 
         /**
+         * Removes consecutive duplicates in the geometry to prepare for tesselation.
+         */
+        virtual void removeDuplicates();
+
+        /**
          * Get the winding orientation of the geometry (if you consider the last point
          * to connect back to the first in a ring.)
          */
@@ -235,7 +253,6 @@ namespace osgEarth { namespace Symbology
         /** dtor */
         virtual ~LineString();
 
-        double getLength() const;
         bool getSegment(double length, osg::Vec3d& start, osg::Vec3d& end);
 
     public:
@@ -267,12 +284,18 @@ namespace osgEarth { namespace Symbology
         // gets the signed area of a part that is known to be open.
         virtual double getSignedArea2D() const;
 
+        // gets the length of the ring (override)
+        virtual double getLength() const;
+
         // ensures that the first and last points are not idential.
         virtual void open();
 
         // ensures that the first and last points are identical.
         virtual void close();
 
+        // whether the ring is open (i.e. first and last points are different)
+        virtual bool isOpen() const;
+
         // opens and winds the ring in the specified direction
         virtual void rewind( Orientation ori );
 
@@ -311,6 +334,8 @@ namespace osgEarth { namespace Symbology
 
         virtual void close();
 
+        virtual void removeDuplicates();
+
     public:
         RingCollection& getHoles() { return _holes; }
         const RingCollection& getHoles() const { return _holes; }
@@ -340,6 +365,9 @@ namespace osgEarth { namespace Symbology
         
         virtual unsigned getNumGeometries() const;
 
+        // gets the combined length of all parts
+        virtual double getLength() const;
+
         // override
         virtual Geometry* cloneAs( const Geometry::Type& newType ) const;
         virtual bool isValid() const;
diff --git a/src/osgEarthSymbology/Geometry.cpp b/src/osgEarthSymbology/Geometry.cpp
index 1ebb2e2..6d1053c 100644
--- a/src/osgEarthSymbology/Geometry.cpp
+++ b/src/osgEarthSymbology/Geometry.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
@@ -300,6 +303,79 @@ Geometry::crop( const Polygon* cropPoly, osg::ref_ptr<Geometry>& output ) const
 }
 
 bool
+Geometry::geounion( const Geometry* other, osg::ref_ptr<Geometry>& output ) const
+{
+#ifdef OSGEARTH_HAVE_GEOS
+    bool success = false;
+    output = 0L;
+
+    GEOSContext gc;
+
+    //Create the GEOS Geometries
+    geom::Geometry* inGeom   = gc.importGeometry( this );
+    geom::Geometry* otherGeom = gc.importGeometry( other );
+
+    if ( inGeom )
+    {    
+        geom::Geometry* outGeom = 0L;
+        try {
+            outGeom = overlay::OverlayOp::overlayOp(
+                inGeom,
+                otherGeom,
+                overlay::OverlayOp::opUNION );
+        }
+        catch(const geos::util::GEOSException& ex) {
+            OE_NOTICE << LC << "Union(GEOS): "
+                << (ex.what()? ex.what() : " no error message")
+                << std::endl;
+            outGeom = 0L;
+        }
+
+        if ( outGeom )
+        {
+            output = gc.exportGeometry( outGeom );
+
+            if ( output.valid())
+            {
+                if ( output->isValid() )
+                {
+                    success = true;
+                }
+                else
+                {
+                    // GEOS result is invalid
+                    output = 0L;
+                }
+            }
+            else
+            {
+                // set output to empty geometry to indicate the (valid) empty case,
+                // still returning false but allows for check.
+                if (outGeom->getNumPoints() == 0)
+                {
+                    output = new osgEarth::Symbology::Geometry();
+                }
+            }
+
+            gc.disposeGeometry( outGeom );
+        }
+    }
+
+    //Destroy the geometry
+    gc.disposeGeometry( otherGeom );
+    gc.disposeGeometry( inGeom );
+
+    return success;
+
+#else // OSGEARTH_HAVE_GEOS
+
+    OE_WARN << LC << "Union failed - GEOS not available" << std::endl;
+    return false;
+
+#endif // OSGEARTH_HAVE_GEOS
+}
+
+bool
 Geometry::difference( const Polygon* diffPolygon, osg::ref_ptr<Geometry>& output ) const
 {
 #ifdef OSGEARTH_HAVE_GEOS
@@ -401,6 +477,26 @@ Geometry::rewind( Orientation orientation )
     }
 }
 
+void Geometry::removeDuplicates()
+{
+    if (size() > 1)
+    {
+        osg::Vec3d v = front();
+        for (Geometry::iterator itr = begin(); itr != end(); )
+        {
+            if (itr != begin() && v == *itr)
+            {
+                itr = erase(itr);
+            }
+            else
+            {
+                v = *itr;
+                itr++;
+            }
+        }
+    }
+}
+
 Geometry::Orientation 
 Geometry::getOrientation() const
 {
@@ -449,6 +545,19 @@ Geometry::getOrientation() const
         Geometry::ORIENTATION_DEGENERATE;
 }
 
+double
+Geometry::getLength() const
+{
+    double length = 0;
+    for (unsigned int i = 0; i < size()-1; ++i)
+    {
+        osg::Vec3d current = (*this)[i];
+        osg::Vec3d next    = (*this)[i+1];
+        length += (next - current).length();
+    }
+    return length;
+}
+
 //----------------------------------------------------------------------------
 
 PointSet::PointSet( const PointSet& rhs ) :
@@ -479,19 +588,6 @@ LineString::~LineString()
 {
 }
 
-double
-LineString::getLength() const
-{
-    double length = 0;
-    for (unsigned int i = 0; i < size()-1; ++i)
-    {
-        osg::Vec3d current = (*this)[i];
-        osg::Vec3d next    = (*this)[i+1];
-        length += (next - current).length();
-    }
-    return length;
-}
-
 bool
 LineString::getSegment(double length, osg::Vec3d& start, osg::Vec3d& end)
 {
@@ -542,6 +638,17 @@ Ring::cloneAs( const Geometry::Type& newType ) const
     else return Geometry::cloneAs( newType );
 }
 
+double
+Ring::getLength() const
+{
+    double length = Geometry::getLength();
+    if ( isOpen() )
+    {
+        length += (front()-back()).length();
+    }
+    return length;
+}
+
 // ensures that the first and last points are not idential.
 void 
 Ring::open()
@@ -558,6 +665,13 @@ Ring::close()
         push_back( front() );
 }
 
+// whether the ring is open.
+bool
+Ring::isOpen() const
+{
+    return size() > 1 && front() != back();
+}
+
 // gets the signed area.
 double
 Ring::getSignedArea2D() const
@@ -663,6 +777,14 @@ Polygon::close()
         (*i)->close();
 }
 
+void
+Polygon::removeDuplicates()
+{
+    Ring::removeDuplicates();
+    for( RingCollection::const_iterator i = _holes.begin(); i != _holes.end(); ++i )
+        (*i)->removeDuplicates();
+}
+
 //----------------------------------------------------------------------------
 
 MultiGeometry::MultiGeometry( const MultiGeometry& rhs ) :
@@ -698,6 +820,15 @@ MultiGeometry::getTotalPointCount() const
     return total;
 }
 
+double
+MultiGeometry::getLength() const
+{
+    double total = 0.0;
+    for( GeometryCollection::const_iterator i = _parts.begin(); i != _parts.end(); ++i )
+        total += i->get()->getLength();
+    return total;
+}
+
 unsigned
 MultiGeometry::getNumGeometries() const
 {
diff --git a/src/osgEarthSymbology/GeometryExtrudeSymbolizer b/src/osgEarthSymbology/GeometryExtrudeSymbolizer
deleted file mode 100644
index 5451eda..0000000
--- a/src/osgEarthSymbology/GeometryExtrudeSymbolizer
+++ /dev/null
@@ -1,52 +0,0 @@
-/* -*-c++-*- */
-/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2010 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 OSGEARTHSYMBOLOGY_GEOMETRY_EXTRUDE_SYMBOLIZER_H
-#define OSGEARTHSYMBOLOGY_GEOMETRY_EXTRUDE_SYMBOLIZER_H 1
-
-#include <osgEarthSymbology/Common>
-#include <osgEarthSymbology/Symbolizer>
-#include <osg/Geode>
-
-namespace osgEarth { namespace Symbology
-{
-    class OSGEARTHSYMBOLOGY_EXPORT GeometryExtrudeSymbolizer : public Symbolizer< State<GeometryContent> >
-    {
-    public:
-        
-        GeometryExtrudeSymbolizer();
-
-        osg::Geode* extrude(Geometry* geom, 
-                            double offset, 
-                            double height, 
-                            SymbolizerContext* context );
-
-        void tessellate( osg::Geometry* geom );
-
-    public: // Symbolizer
-
-        bool compile(
-            State<GeometryContent>* state,
-            osg::Group*           attachPoint);
-
-    };
-
-} }
-
-#endif
diff --git a/src/osgEarthSymbology/GeometryFactory b/src/osgEarthSymbology/GeometryFactory
index f58bf70..e196cee 100644
--- a/src/osgEarthSymbology/GeometryFactory
+++ b/src/osgEarthSymbology/GeometryFactory
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthSymbology/GeometryFactory.cpp b/src/osgEarthSymbology/GeometryFactory.cpp
index fe48641..32e1d3c 100644
--- a/src/osgEarthSymbology/GeometryFactory.cpp
+++ b/src/osgEarthSymbology/GeometryFactory.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthSymbology/GeometryInput b/src/osgEarthSymbology/GeometryInput
deleted file mode 100644
index 0c7c2de..0000000
--- a/src/osgEarthSymbology/GeometryInput
+++ /dev/null
@@ -1,44 +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 OSGEARTHSYMBOLOGY_GEOMETRY_INPUT_H
-//#define OSGEARTHSYMBOLOGY_GEOMETRY_INPUT_H 1
-//
-//#include <osgEarthSymbology/Common>
-//#include <osgEarthSymbology/Geometry>
-//#include <osgEarthSymbology/Symbolizer>
-//
-//
-//namespace osgEarth { namespace Symbology
-//{
-//    class OSGEARTHSYMBOLOGY_EXPORT GeometryContent : public Content<osg::Referenced>
-//    {
-//    public:
-//
-//        GeometryContent();
-//
-//        const GeometryList& getGeometryList() const { return _geometryList; }
-//        GeometryList& getGeometryList() { return _geometryList; }
-//
-//    protected:
-//        GeometryList _geometryList;
-//    };
-//
-//}}
-//#endif
diff --git a/src/osgEarthSymbology/GeometryRasterizer b/src/osgEarthSymbology/GeometryRasterizer
index bd8f990..97b80be 100644
--- a/src/osgEarthSymbology/GeometryRasterizer
+++ b/src/osgEarthSymbology/GeometryRasterizer
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthSymbology/GeometryRasterizer.cpp b/src/osgEarthSymbology/GeometryRasterizer.cpp
index 1e8e9c0..2abc1ce 100644
--- a/src/osgEarthSymbology/GeometryRasterizer.cpp
+++ b/src/osgEarthSymbology/GeometryRasterizer.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -42,7 +42,7 @@ struct AggState : public osg::Referenced
     }
 
     agg::rendering_buffer           _rbuf;
-    agg::renderer<agg::span_abgr32> _ren;
+    agg::renderer<agg::span_abgr32, agg::rgba8> _ren;
     agg::rasterizer                 _ras;
 };
 
diff --git a/src/osgEarthSymbology/GeometrySymbol b/src/osgEarthSymbology/GeometrySymbol
deleted file mode 100644
index 6e7a1bb..0000000
--- a/src/osgEarthSymbology/GeometrySymbol
+++ /dev/null
@@ -1,190 +0,0 @@
-///* -*-c++-*- */
-///* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-// * Copyright 2008-2010 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 OSGEARTHSYMBOLOGY_GEOMETRY_SYMBOL_H
-//#define OSGEARTHSYMBOLOGY_GEOMETRY_SYMBOL_H 1
-//
-//#include <osgEarth/Common>
-//#include <osgEarthSymbology/Common>
-//#include <osgEarthSymbology/Symbol>
-//#include <osg/Referenced>
-//#include <vector>
-//
-//namespace osgEarth { namespace Symbology
-//{
-//    class OSGEARTHSYMBOLOGY_EXPORT Stroke
-//    {
-//    public:
-//        enum LineCapStyle {
-//            LINECAP_DEFAULT,
-//            LINECAP_BUTT,
-//            LINECAP_SQUARE,
-//            LINECAP_ROUND
-//        };
-//
-//        enum LineJoinStyle {
-//            LINEJOIN_DEFAULT
-//        };
-//
-//    public:
-//        Stroke();
-//        Stroke( float r, float g, float b, float a );
-//        Stroke( const Config& conf ) { mergeConfig(conf); }
-//
-//        osg::Vec4f& color() { return _color; }
-//        const osg::Vec4f& color() const { return _color; }
-//
-//        optional<LineCapStyle>& lineCap() { return _lineCap; }
-//        const optional<LineCapStyle>& lineCap() const { return _lineCap; }
-//
-//        optional<LineJoinStyle>& lineJoin() { return _lineJoin; }
-//        const optional<LineJoinStyle>& lineJoin() const { return _lineJoin; }
-//
-//        optional<float>& width() { return _width; }        
-//        const optional<float>& width() const { return _width; }
-//
-//    public:
-//        virtual Config getConfig() const {
-//            Config conf("stroke");
-//            conf.add( "color", vec4fToHtmlColor(_color) );
-//            conf.addIfSet("linecap", "butt", _lineCap, LINECAP_BUTT);
-//            conf.addIfSet("linecap", "square", _lineCap, LINECAP_SQUARE);
-//            conf.addIfSet("linecap", "round", _lineCap, LINECAP_ROUND);
-//            conf.addIfSet("width", _width);
-//            return conf;
-//        }
-//            
-//        virtual void mergeConfig( const Config& conf ) {
-//            _color = htmlColorToVec4f( conf.value("color") );
-//            conf.getIfSet("linecap", "butt", _lineCap, LINECAP_BUTT);
-//            conf.getIfSet("linecap", "square", _lineCap, LINECAP_SQUARE);
-//            conf.getIfSet("linecap", "round", _lineCap, LINECAP_ROUND);
-//            conf.getIfSet("width", _width);
-//        }
-//
-//    protected:
-//        osg::Vec4f              _color;
-//        optional<LineCapStyle>  _lineCap;
-//        optional<LineJoinStyle> _lineJoin;
-//        optional<float>         _width;
-//    };
-//
-//    class OSGEARTHSYMBOLOGY_EXPORT Fill
-//    {
-//    public:
-//        Fill();
-//        Fill( float r, float g, float b, float a );
-//        Fill( const Config& conf ) { mergeConfig(conf); }
-//
-//        osg::Vec4f& color() { return _color; }
-//        const osg::Vec4f& color() const { return _color; }
-//
-//    public:
-//        virtual Config getConfig() const {
-//            Config conf("fill");
-//            conf.add("color", vec4fToHtmlColor(_color));
-//            return conf;
-//        }
-//        virtual void mergeConfig( const Config& conf ) {
-//            _color = htmlColorToVec4f(conf.value("color"));
-//        }
-//
-//    protected:
-//        osg::Vec4f _color;
-//    };
-//
-//    class OSGEARTHSYMBOLOGY_EXPORT LineSymbol : public Symbol
-//    {
-//    public:
-//        LineSymbol();
-//        LineSymbol(const Config& conf) { mergeConfig(conf); }
-//
-//        optional<Stroke>& stroke() { return _stroke; }
-//        const optional<Stroke>& stroke() const { return _stroke; }
-//
-//    public:
-//        virtual Config getConfig() const {
-//            Config conf( "line" );
-//            conf.addObjIfSet("stroke", _stroke);
-//            return conf;
-//        }
-//        virtual void mergeConfig( const Config& conf ) {
-//            conf.getObjIfSet("stroke", _stroke);
-//        }
-//
-//    protected:
-//        optional<Stroke> _stroke;
-//    };
-//
-//    class OSGEARTHSYMBOLOGY_EXPORT PointSymbol : public Symbol
-//    {
-//    public:
-//        PointSymbol();
-//        PointSymbol( const Config& conf ) { mergeConfig(conf); }
-//
-//        optional<Fill>& fill() { return _fill; }
-//        const optional<Fill>& fill() const { return _fill; }
-//
-//        optional<float>& size() { return _size; }
-//        const optional<float>& size() const { return _size; }
-//
-//    public:
-//        virtual Config getConfig() const {
-//            Config conf( "point" );
-//            conf.addObjIfSet( "fill", _fill );
-//            conf.addIfSet( "size", _size );
-//            return conf;
-//        }
-//        virtual void mergeConfig( const Config& conf ) {
-//            conf.getObjIfSet( "fill", _fill );
-//            conf.getIfSet( "size", _size );
-//        }
-//
-//    protected:
-//        optional<Fill> _fill;
-//        optional<float>  _size;
-//    };
-//
-//    class OSGEARTHSYMBOLOGY_EXPORT PolygonSymbol : public Symbol
-//    {
-//    public:
-//        PolygonSymbol();
-//        PolygonSymbol( const Config& conf ) { mergeConfig(conf); }
-//
-//        optional<Fill>& fill() { return _fill; }
-//        const optional<Fill>& fill() const { return _fill; }
-//
-//    public:
-//        virtual Config getConfig() const {
-//            Config conf( "polygon" );
-//            conf.addObjIfSet( "fill", _fill );
-//            return conf;
-//        }
-//        virtual void mergeConfig(const Config& conf ) {
-//            conf.getObjIfSet( "fill", _fill );
-//        }
-//
-//    protected:
-//        optional<Fill> _fill;
-//    };
-//
-//
-//} } // namespace osgEarth::Symbology
-//
-//#endif // OSGEARTH_SYMBOLOGY_SYMBOL_H
diff --git a/src/osgEarthSymbology/IconResource b/src/osgEarthSymbology/IconResource
index 69361db..3ed5112 100644
--- a/src/osgEarthSymbology/IconResource
+++ b/src/osgEarthSymbology/IconResource
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthSymbology/IconResource.cpp b/src/osgEarthSymbology/IconResource.cpp
index 36f16f3..fe74518 100644
--- a/src/osgEarthSymbology/IconResource.cpp
+++ b/src/osgEarthSymbology/IconResource.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -24,8 +24,10 @@
 
 #include <osg/AutoTransform>
 #include <osg/Depth>
+#include <osg/Geode>
 #include <osg/Geometry>
 #include <osg/TextureRectangle>
+#include <osg/Texture2D>
 #include <osg/Program>
 
 #define LC "[IconResource] "
diff --git a/src/osgEarthSymbology/IconSymbol b/src/osgEarthSymbology/IconSymbol
index 07600d3..f1f0549 100644
--- a/src/osgEarthSymbology/IconSymbol
+++ b/src/osgEarthSymbology/IconSymbol
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -50,7 +50,9 @@ namespace osgEarth { namespace Symbology
         };
 
     public:
-        META_Symbol(IconSymbol);
+        META_Object(osgEarthSymbology, IconSymbol);
+
+        IconSymbol(const IconSymbol& rhs,const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY);
 
         IconSymbol( const Config& conf =Config() );
 
diff --git a/src/osgEarthSymbology/IconSymbol.cpp b/src/osgEarthSymbology/IconSymbol.cpp
index 89560a3..d6ad467 100644
--- a/src/osgEarthSymbology/IconSymbol.cpp
+++ b/src/osgEarthSymbology/IconSymbol.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -29,6 +29,17 @@ using namespace osgEarth::Symbology;
 
 OSGEARTH_REGISTER_SIMPLE_SYMBOL(icon, IconSymbol);
 
+IconSymbol::IconSymbol(const IconSymbol& rhs,const osg::CopyOp& copyop):
+InstanceSymbol(rhs, copyop),
+_alignment(rhs._alignment),
+_heading(rhs._heading),
+_declutter(rhs._declutter),
+_image(rhs._image),
+_occlusionCull(rhs._occlusionCull),
+_occlusionCullAltitude(rhs._occlusionCullAltitude)
+{
+}
+
 IconSymbol::IconSymbol( const Config& conf ) :
 InstanceSymbol        ( conf ),
 _alignment            ( ALIGN_CENTER_BOTTOM ),
@@ -143,7 +154,7 @@ IconSymbol::parseSLD(const Config& c, Style& style)
         style.getOrCreate<IconSymbol>()->url()->setURIContext( c.referrer() );
     }
     else if ( match(c.key(),"icon-library") ) {
-        style.getOrCreate<IconSymbol>()->libraryName() = StringExpression(c.value());
+        style.getOrCreate<IconSymbol>()->library() = StringExpression(c.value());
     }
     else if ( match(c.key(), "icon-placement") ) {
         if      ( match(c.value(), "vertex") )   
diff --git a/src/osgEarthSymbology/InstanceResource b/src/osgEarthSymbology/InstanceResource
index 907b52b..ec1e664 100644
--- a/src/osgEarthSymbology/InstanceResource
+++ b/src/osgEarthSymbology/InstanceResource
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthSymbology/InstanceResource.cpp b/src/osgEarthSymbology/InstanceResource.cpp
index b7e02c4..8c9297d 100644
--- a/src/osgEarthSymbology/InstanceResource.cpp
+++ b/src/osgEarthSymbology/InstanceResource.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthSymbology/InstanceSymbol b/src/osgEarthSymbology/InstanceSymbol
index 5523de2..a72c41e 100644
--- a/src/osgEarthSymbology/InstanceSymbol
+++ b/src/osgEarthSymbology/InstanceSymbol
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -60,6 +60,9 @@ namespace osgEarth { namespace Symbology
         };
 
     public:
+
+        InstanceSymbol(const InstanceSymbol& rhs,const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY);
+
         /** dtor */
         virtual ~InstanceSymbol() { }
 
@@ -68,8 +71,8 @@ namespace osgEarth { namespace Symbology
         const optional<StringExpression>& url() const { return _url; }
 
         /** Name of the resource library to use with this symbol (optional) */
-        optional<StringExpression>& libraryName() { return _libraryName; }
-        const optional<StringExpression>& libraryName() const { return _libraryName; }   
+        optional<StringExpression>& library() { return _library; }
+        const optional<StringExpression>& library() const { return _library; }   
 
         /** How to map feature geometry to instance placement. (default is PLACEMENT_CENTROID) */
         optional<Placement>& placement() { return _placement; }
@@ -115,7 +118,7 @@ namespace osgEarth { namespace Symbology
 
     protected:
         optional<StringExpression>   _url;
-        optional<StringExpression>   _libraryName;
+        optional<StringExpression>   _library;
         optional<NumericExpression>  _scale;
         optional<Placement>          _placement;
         optional<float>              _density;
diff --git a/src/osgEarthSymbology/InstanceSymbol.cpp b/src/osgEarthSymbology/InstanceSymbol.cpp
index b1977b7..782850a 100644
--- a/src/osgEarthSymbology/InstanceSymbol.cpp
+++ b/src/osgEarthSymbology/InstanceSymbol.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -33,13 +33,26 @@ _scale     ( NumericExpression(1.0) )
     mergeConfig( conf );
 }
 
+InstanceSymbol::InstanceSymbol(const InstanceSymbol& rhs,const osg::CopyOp& copyop):
+Symbol(rhs, copyop),
+_url(rhs._url),
+_library(rhs._library),
+_scale(rhs._scale),
+_placement(rhs._placement),
+_density(rhs._density),
+_randomSeed(rhs._randomSeed),
+_uriAliasMap(rhs._uriAliasMap),
+_script(rhs._script)
+{
+}
+
 Config 
 InstanceSymbol::getConfig() const
 {
     Config conf = Symbol::getConfig();
     conf.key() = "instance";
     conf.addObjIfSet( "url", _url );
-    conf.addObjIfSet( "library", _libraryName );
+    conf.addObjIfSet( "library", _library );
     conf.addObjIfSet( "scale", _scale );
     conf.addObjIfSet( "script", _script );
     conf.addIfSet   ( "placement", "vertex",    _placement, PLACEMENT_VERTEX );
@@ -55,7 +68,7 @@ void
 InstanceSymbol::mergeConfig( const Config& conf )
 {
     conf.getObjIfSet( "url", _url );
-    conf.getObjIfSet( "library", _libraryName );
+    conf.getObjIfSet( "library", _library );
     conf.getObjIfSet( "scale", _scale );
     conf.getObjIfSet( "script", _script );
     conf.getIfSet   ( "placement", "vertex",   _placement, PLACEMENT_VERTEX );
diff --git a/src/osgEarthSymbology/LineSymbol b/src/osgEarthSymbology/LineSymbol
index 4f3384d..ced4040 100644
--- a/src/osgEarthSymbology/LineSymbol
+++ b/src/osgEarthSymbology/LineSymbol
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -31,8 +31,9 @@ namespace osgEarth { namespace Symbology
     class OSGEARTHSYMBOLOGY_EXPORT LineSymbol : public Symbol
     {
     public:
-        META_Symbol(LineSymbol);
+        META_Object(osgEarthSymbology, LineSymbol);
 
+        LineSymbol(const LineSymbol& rhs,const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY);
         LineSymbol(const Config& conf =Config());
 
         /** dtor */
@@ -42,7 +43,11 @@ namespace osgEarth { namespace Symbology
         optional<Stroke>& stroke() { return _stroke; }
         const optional<Stroke>& stroke() const { return _stroke; }
 
-        /** Whether to tessellate line geometry (# of subdivision per segment) */
+        /** Tessellate the line geometry such that no segment is longer than this value */
+        optional<Distance>& tessellationSize() { return _tessellationSize; }
+        const optional<Distance>& tessellationSize() const { return _tessellationSize; }
+
+        /** Tessellate the line geometry such that each source segment is divided into this many parts. */
         optional<unsigned>& tessellation() { return _tessellation; }
         const optional<unsigned>& tessellation() const { return _tessellation; }
 
@@ -59,6 +64,7 @@ namespace osgEarth { namespace Symbology
         optional<Stroke>   _stroke;
         optional<unsigned> _tessellation;
         optional<float>    _creaseAngle;
+        optional<Distance> _tessellationSize;
     };
 
 } } // namespace osgEarth::Symbology
diff --git a/src/osgEarthSymbology/LineSymbol.cpp b/src/osgEarthSymbology/LineSymbol.cpp
index 0e23796..0cb5636 100644
--- a/src/osgEarthSymbology/LineSymbol.cpp
+++ b/src/osgEarthSymbology/LineSymbol.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -33,6 +33,16 @@ _creaseAngle ( 0.0f )
     mergeConfig(conf);
 }
 
+LineSymbol::LineSymbol(const LineSymbol& rhs,const osg::CopyOp& copyop):
+Symbol(rhs, copyop),
+_stroke          (rhs._stroke),
+_tessellation    (rhs._tessellation),
+_creaseAngle     (rhs._creaseAngle),
+_tessellationSize(rhs._tessellationSize)
+{
+    //nop
+}
+
 Config 
 LineSymbol::getConfig() const
 {
@@ -41,6 +51,7 @@ LineSymbol::getConfig() const
     conf.addObjIfSet("stroke",       _stroke);
     conf.addIfSet   ("tessellation", _tessellation);
     conf.addIfSet   ("crease_angle", _creaseAngle);
+    conf.addObjIfSet("tessellation_size", _tessellationSize );
     return conf;
 }
 
@@ -50,6 +61,7 @@ LineSymbol::mergeConfig( const Config& conf )
     conf.getObjIfSet("stroke",       _stroke);
     conf.getIfSet   ("tessellation", _tessellation);
     conf.getIfSet   ("crease_angle", _creaseAngle);
+    conf.getObjIfSet("tessellation_size", _tessellationSize);
 }
 
 void
@@ -88,6 +100,13 @@ LineSymbol::parseSLD(const Config& c, Style& style)
     else if ( match(c.key(), "stroke-tessellation") ) {
         style.getOrCreate<LineSymbol>()->tessellation() = as<unsigned>( c.value(), 0 );
     }
+    else if ( match(c.key(), "stroke-tessellation-size") ) {
+        float value;
+        Units units;
+        if ( Units::parse(c.value(), value, units, Units::METERS) ) {
+            style.getOrCreate<LineSymbol>()->tessellationSize() = Distance(value, units);
+        }
+    }        
     else if ( match(c.key(), "stroke-min-pixels") ) {
         style.getOrCreate<LineSymbol>()->stroke()->minPixels() = as<float>(c.value(), 0.0f);
     }
diff --git a/src/osgEarthSymbology/MarkerResource b/src/osgEarthSymbology/MarkerResource
index beb5300..8d7a7e7 100644
--- a/src/osgEarthSymbology/MarkerResource
+++ b/src/osgEarthSymbology/MarkerResource
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthSymbology/MarkerResource.cpp b/src/osgEarthSymbology/MarkerResource.cpp
index baf057c..04b3975 100644
--- a/src/osgEarthSymbology/MarkerResource.cpp
+++ b/src/osgEarthSymbology/MarkerResource.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthSymbology/MarkerSymbol b/src/osgEarthSymbology/MarkerSymbol
index 923f762..f2e4f63 100644
--- a/src/osgEarthSymbology/MarkerSymbol
+++ b/src/osgEarthSymbology/MarkerSymbol
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -73,10 +73,12 @@ namespace osgEarth { namespace Symbology
         };
 
     public:
-        META_Symbol(MarkerSymbol);
+        META_Object(osgEarthSymbology, MarkerSymbol);
 
+        MarkerSymbol(const MarkerSymbol& rhs,const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY);
         MarkerSymbol( const Config& conf =Config() );
 
+
         /** Since MarkerSymbol is deprecated, this conveneince function will convert to an InstanceSymbol */
         class InstanceSymbol* convertToInstanceSymbol() const;
 
@@ -88,8 +90,8 @@ namespace osgEarth { namespace Symbology
         const optional<StringExpression>& url() const { return _url; }     
 
         /** Name of the resource library to use with this symbol (optional) */
-        optional<StringExpression>& libraryName() { return _libraryName; }
-        const optional<StringExpression>& libraryName() const { return _libraryName; }   
+        optional<StringExpression>& library() { return _library; }
+        const optional<StringExpression>& library() const { return _library; }   
 
         /** How to map feature geometry to model placement. (default is PLACEMENT_CENTROID) */
         optional<Placement>& placement() { return _placement; }
@@ -136,7 +138,7 @@ namespace osgEarth { namespace Symbology
 
     protected:
         optional<StringExpression>   _url;
-        optional<StringExpression>   _libraryName;
+        optional<StringExpression>   _library;
         optional<NumericExpression>  _scale;
         optional<Placement>          _placement;
         optional<osg::Vec3f>         _orientation;
diff --git a/src/osgEarthSymbology/MarkerSymbol.cpp b/src/osgEarthSymbology/MarkerSymbol.cpp
index 0e22347..d3f9846 100644
--- a/src/osgEarthSymbology/MarkerSymbol.cpp
+++ b/src/osgEarthSymbology/MarkerSymbol.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -33,6 +33,22 @@ using namespace osgEarth::Symbology;
 
 OSGEARTH_REGISTER_SIMPLE_SYMBOL(marker, MarkerSymbol);
 
+MarkerSymbol::MarkerSymbol(const MarkerSymbol& rhs,const osg::CopyOp& copyop):
+Symbol(rhs, copyop),
+_url(rhs._url),
+_library(rhs._library),
+_scale(rhs._scale),
+_placement(rhs._placement),
+_orientation(rhs._orientation),
+_density(rhs._density),
+_randomSeed(rhs._randomSeed),
+_isModelHint(rhs._isModelHint),
+_alignment(rhs._alignment),
+_node(rhs._node),
+_image(rhs._image)
+{
+}
+
 MarkerSymbol::MarkerSymbol( const Config& conf ) :
 Symbol     ( conf ),
 _placement ( PLACEMENT_CENTROID ),
@@ -49,7 +65,7 @@ MarkerSymbol::getConfig() const
     Config conf = Symbol::getConfig();
     conf.key() = "marker";
     conf.addObjIfSet( "url", _url );
-    conf.addObjIfSet( "library", _libraryName );
+    conf.addObjIfSet( "library", _library );
     conf.addObjIfSet( "scale", _scale );
     conf.addIfSet( "orientation", _orientation);
     conf.addIfSet( "placement", "vertex",   _placement, PLACEMENT_VERTEX );
@@ -78,7 +94,7 @@ void
 MarkerSymbol::mergeConfig( const Config& conf )
 {
     conf.getObjIfSet( "url", _url );
-    conf.getObjIfSet( "library", _libraryName );
+    conf.getObjIfSet( "library", _library );
     conf.getObjIfSet( "scale", _scale );    
     conf.getIfSet( "placement", "vertex",   _placement, PLACEMENT_VERTEX );
     conf.getIfSet( "placement", "interval", _placement, PLACEMENT_INTERVAL );
@@ -213,8 +229,8 @@ MarkerSymbol::convertToInstanceSymbol() const
 
     if ( this->url().isSet() )
         result->url() = this->url().get();
-    if ( this->libraryName().isSet() )
-        result->libraryName() = this->libraryName().get();
+    if ( this->library().isSet() )
+        result->library() = this->library().get();
     if ( this->placement().isSet() )
         result->placement() = (InstanceSymbol::Placement)this->placement().get();
     if ( this->density().isSet() )
@@ -236,7 +252,7 @@ MarkerSymbol::parseSLD(const Config& c, Style& style)
         style.getOrCreate<MarkerSymbol>()->url()->setURIContext( c.referrer() );
     }
     else if ( match(c.key(),"marker-library") ) {
-        style.getOrCreate<MarkerSymbol>()->libraryName() = StringExpression(c.value());
+        style.getOrCreate<MarkerSymbol>()->library() = StringExpression(c.value());
     }
     else if ( match(c.key(), "marker-placement") ) {
         if      ( match(c.value(), "vertex") )   
diff --git a/src/osgEarthSymbology/MarkerSymbolizer b/src/osgEarthSymbology/MarkerSymbolizer
deleted file mode 100644
index 16e9780..0000000
--- a/src/osgEarthSymbology/MarkerSymbolizer
+++ /dev/null
@@ -1,47 +0,0 @@
-/* -*-c++-*- */
-/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2010 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 OSGEARTHSYMBOLOGY_MARKER_SYMBOLIZER_H
-#define OSGEARTHSYMBOLOGY_MARKER_SYMBOLIZER_H 1
-
-#include <osgEarthSymbology/Common>
-#include <osgEarthSymbology/Symbolizer>
-
-namespace osgEarth { namespace Symbology
-{    
-    typedef State<GeometryContent> MarkerSymbolizerState;
-
-    class OSGEARTHSYMBOLOGY_EXPORT MarkerSymbolizer : public Symbolizer<MarkerSymbolizerState>
-    {
-    public:
-        MarkerSymbolizer();
-
-    public: // Symbolizer
-
-        virtual bool compile(
-            MarkerSymbolizerState* state,
-            osg::Group* attachPoint);
-
-    protected:
-        bool pointInPolygon(const osg::Vec3d& point, osg::Vec3dArray* polygon);
-    };
-
-} } // namespace osgEarth::Symbology
-
-#endif
diff --git a/src/osgEarthSymbology/MeshConsolidator b/src/osgEarthSymbology/MeshConsolidator
index 3b75507..c541c03 100644
--- a/src/osgEarthSymbology/MeshConsolidator
+++ b/src/osgEarthSymbology/MeshConsolidator
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthSymbology/MeshConsolidator.cpp b/src/osgEarthSymbology/MeshConsolidator.cpp
index 9a41646..c506e63 100644
--- a/src/osgEarthSymbology/MeshConsolidator.cpp
+++ b/src/osgEarthSymbology/MeshConsolidator.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
@@ -21,6 +24,7 @@
 #include <osgEarth/StringUtils>
 #include <osg/TriangleFunctor>
 #include <osg/TriangleIndexFunctor>
+#include <osg/Version>
 #include <osgDB/WriteFile>
 #include <osgUtil/MeshOptimizers>
 #include <limits>
@@ -81,6 +85,134 @@ namespace
             return copy<FROM,osg::DrawElementsUInt>( src, offset );
     }
 
+    template<typename TYPE>
+    osg::Array* convertToBindPerVertex( TYPE* src, unsigned int numVerts)
+    {
+        TYPE* result = new TYPE();
+        result->reserve(numVerts);
+        for (unsigned int i = 0; i < numVerts; i++)
+        {
+            result->push_back((*src)[0]);
+        }
+        return result;
+    }
+
+    osg::Array* makeBindPerVertex( osg::Array* array, unsigned int numVerts)
+    {
+        switch (array->getType())
+        {
+        case osg::Array::ByteArrayType:
+            return convertToBindPerVertex<osg::ByteArray>(static_cast<osg::ByteArray*>(array), numVerts);
+
+        case osg::Array::ShortArrayType:
+            return convertToBindPerVertex<osg::ShortArray>(static_cast<osg::ShortArray*>(array), numVerts);
+
+        case osg::Array::IntArrayType:
+            return convertToBindPerVertex<osg::IntArray>(static_cast<osg::IntArray*>(array), numVerts);
+
+        case osg::Array::UByteArrayType:
+            return convertToBindPerVertex<osg::UByteArray>(static_cast<osg::UByteArray*>(array), numVerts);
+
+        case osg::Array::UShortArrayType:
+            return convertToBindPerVertex<osg::UShortArray>(static_cast<osg::UShortArray*>(array), numVerts);
+
+        case osg::Array::UIntArrayType:
+            return convertToBindPerVertex<osg::UIntArray>(static_cast<osg::UIntArray*>(array), numVerts);
+
+        case osg::Array::FloatArrayType:
+            return convertToBindPerVertex<osg::FloatArray>(static_cast<osg::FloatArray*>(array), numVerts);
+
+        case osg::Array::DoubleArrayType:
+            return convertToBindPerVertex<osg::DoubleArray>(static_cast<osg::DoubleArray*>(array), numVerts);
+
+        case osg::Array::Vec2bArrayType:
+            return convertToBindPerVertex<osg::Vec2bArray>(static_cast<osg::Vec2bArray*>(array), numVerts);
+
+        case osg::Array::Vec3bArrayType:
+            return convertToBindPerVertex<osg::Vec3bArray>(static_cast<osg::Vec3bArray*>(array), numVerts);
+
+        case osg::Array::Vec4bArrayType:
+            return convertToBindPerVertex<osg::Vec4bArray>(static_cast<osg::Vec4bArray*>(array), numVerts);
+
+        case osg::Array::Vec2sArrayType:
+            return convertToBindPerVertex<osg::Vec2sArray>(static_cast<osg::Vec2sArray*>(array), numVerts);
+
+        case osg::Array::Vec3sArrayType:
+            return convertToBindPerVertex<osg::Vec3sArray>(static_cast<osg::Vec3sArray*>(array), numVerts);
+
+        case osg::Array::Vec4sArrayType:
+            return convertToBindPerVertex<osg::Vec4sArray>(static_cast<osg::Vec4sArray*>(array), numVerts);
+
+        
+
+        case osg::Array::Vec4ubArrayType:
+            return convertToBindPerVertex<osg::Vec4ubArray>(static_cast<osg::Vec4ubArray*>(array), numVerts);
+
+
+        case osg::Array::Vec2ArrayType:
+            return convertToBindPerVertex<osg::Vec2Array>(static_cast<osg::Vec2Array*>(array), numVerts);
+
+        case osg::Array::Vec3ArrayType:
+            return convertToBindPerVertex<osg::Vec3Array>(static_cast<osg::Vec3Array*>(array), numVerts);
+
+        case osg::Array::Vec4ArrayType:
+            return convertToBindPerVertex<osg::Vec4Array>(static_cast<osg::Vec4Array*>(array), numVerts);
+
+
+        case osg::Array::Vec2dArrayType:
+            return convertToBindPerVertex<osg::Vec2dArray>(static_cast<osg::Vec2dArray*>(array), numVerts);
+
+        case osg::Array::Vec3dArrayType:
+            return convertToBindPerVertex<osg::Vec3dArray>(static_cast<osg::Vec3dArray*>(array), numVerts);
+
+        case osg::Array::Vec4dArrayType:
+            return convertToBindPerVertex<osg::Vec4dArray>(static_cast<osg::Vec4dArray*>(array), numVerts);
+
+        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);
+
+        case osg::Array::Vec3iArrayType:
+            return convertToBindPerVertex<osg::Vec3iArray>(static_cast<osg::Vec3iArray*>(array), numVerts);
+
+        case osg::Array::Vec4iArrayType:
+            return convertToBindPerVertex<osg::Vec4iArray>(static_cast<osg::Vec4iArray*>(array), numVerts);
+
+        case osg::Array::Vec2ubArrayType:
+            return convertToBindPerVertex<osg::Vec2ubArray>(static_cast<osg::Vec2ubArray*>(array), numVerts );
+
+        case osg::Array::Vec3ubArrayType:
+            return convertToBindPerVertex<osg::Vec3ubArray>(static_cast<osg::Vec3ubArray*>(array), numVerts );
+
+        case osg::Array::Vec2usArrayType:
+            return convertToBindPerVertex<osg::Vec2usArray>(static_cast<osg::Vec2usArray*>(array), numVerts);
+
+        case osg::Array::Vec3usArrayType:
+            return convertToBindPerVertex<osg::Vec3usArray>(static_cast<osg::Vec3usArray*>(array), numVerts );
+
+        case osg::Array::Vec4usArrayType:
+            return convertToBindPerVertex<osg::Vec4usArray >(static_cast<osg::Vec4usArray*>(array), numVerts );
+
+        case osg::Array::Vec2uiArrayType:
+            return convertToBindPerVertex<osg::Vec2uiArray>(static_cast<osg::Vec2uiArray*>(array), numVerts);
+
+        case osg::Array::Vec3uiArrayType:
+            return convertToBindPerVertex<osg::Vec3uiArray>(static_cast<osg::Vec3uiArray*>(array), numVerts);
+
+        case osg::Array::Vec4uiArrayType:
+            return convertToBindPerVertex<osg::Vec4uiArray>(static_cast<osg::Vec4uiArray*>(array), numVerts);
+
+        case osg::Array::MatrixdArrayType:
+            return convertToBindPerVertex<osg::MatrixdArray>(static_cast<osg::MatrixdArray*>(array),  numVerts);
+#endif
+        default:
+            return array;
+        }
+    }
+
     osg::PrimitiveSet* convertDAtoDE( osg::DrawArrays* da, unsigned numVerts, unsigned offset )
     {
         osg::DrawElements* de = 0L;
@@ -100,24 +232,55 @@ namespace
 
     bool canOptimize( osg::Geometry& geom )
     {
-        osg::Array* vertexArray = geom.getVertexArray();
+        osg::Vec3Array* vertexArray = dynamic_cast<osg::Vec3Array*>(geom.getVertexArray());
         if ( !vertexArray )
+        {
             return false;
+        }
 
-        // check that everything is bound per-vertex
+        // check that everything is bound per-vertex or convert bind overall
 
         if ( geom.getColorArray() != 0L && geom.getColorBinding() != osg::Geometry::BIND_PER_VERTEX )
-            return false;
+        {
+            if (geom.getColorBinding() == osg::Geometry::BIND_OVERALL)
+            {
+                geom.setColorArray(makeBindPerVertex(geom.getColorArray(), vertexArray->size()));
+            }
+            else
+            {
+                return false;
+            }
+        }
 
         if ( geom.getNormalArray() != 0L && geom.getNormalBinding() != osg::Geometry::BIND_PER_VERTEX )
-            return false;
+        {
+            if (geom.getNormalBinding() == osg::Geometry::BIND_OVERALL)
+            {
+                geom.setNormalArray(makeBindPerVertex(geom.getNormalArray(), vertexArray->size()));
+            }
+            else
+            {
+                return false;
+            }
+        }
 
         if ( geom.getSecondaryColorArray() != 0L && geom.getSecondaryColorBinding() != osg::Geometry::BIND_PER_VERTEX )
-            return false;
+        {
+            if (geom.getSecondaryColorBinding() == osg::Geometry::BIND_OVERALL)
+            {
+                geom.setSecondaryColorArray(makeBindPerVertex(geom.getSecondaryColorArray(), vertexArray->size()));
+            }
+            else
+            {
+                return false;
+            }
+        }
 
         // just for now.... TODO: allow thi later
         if ( geom.getVertexAttribArrayList().size() > 0 )
+        {
             return false;
+        }
 
         // check that all primitive sets share the same user data
         osg::Geometry::PrimitiveSetList& pslist = geom.getPrimitiveSetList();
diff --git a/src/osgEarthSymbology/MeshFlattener b/src/osgEarthSymbology/MeshFlattener
new file mode 100644
index 0000000..5608952
--- /dev/null
+++ b/src/osgEarthSymbology/MeshFlattener
@@ -0,0 +1,91 @@
+/* -*-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.
+ *
+ * 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 OSGEARTHSYMBOLOGY_MESH_FLATTENER
+#define OSGEARTHSYMBOLOGY_MESH_FLATTENER
+
+#include <osgEarthSymbology/Common>
+
+namespace osgEarth { namespace Symbology
+{
+    /**
+    * Prepares a scene graph for aggressive optimization.  Removes things like names, user data, callbacks, etc.
+    * Use carefully.
+    */
+    struct OSGEARTHSYMBOLOGY_EXPORT PrepareForOptimizationVisitor : public osg::NodeVisitor
+    {
+        PrepareForOptimizationVisitor();
+
+        virtual void apply(osg::Node& node);
+    };
+
+
+    /**
+     * Utility visitor to aid in flattening a scene graph.  Collects a map of StateSet stacks to Geodes.
+     * Assumes that all transforms have already been removed from the scene graph.
+     */
+    struct OSGEARTHSYMBOLOGY_EXPORT FlattenSceneGraphVisitor : public osg::NodeVisitor
+    {
+        FlattenSceneGraphVisitor();
+
+        virtual void apply(osg::Node& node);
+
+        virtual void apply(osg::Geode& geode);
+
+        void pushStateSet(osg::StateSet* stateSet);
+
+        void popStateSet();
+
+        /**
+        * Build the flattened scene graph from the statesets and geodes.
+        */
+        osg::Node* build();
+
+        typedef std::vector< osg::ref_ptr< osg::StateSet > > StateSetStack;
+        typedef std::vector< osg::ref_ptr< osg::Geometry > > GeometryVector;
+
+        StateSetStack _ssStack;
+
+        typedef std::map< StateSetStack, GeometryVector > StateSetStackToGeometryMap;
+
+        StateSetStackToGeometryMap _geometries;
+    };
+
+
+    /**
+     * Flattens "flatten" the given scene graph, turning it into a 
+     * a very basic scene graph consisting of a group at the top with a list of geodes
+     * each consisting of as small of a number of drawables as possible.
+     *
+     *  Limitations:
+     *      This will only work on relatively simple scene graphs consisting of MatrixTransforms, Groups, Geodes, etc.
+     *      If your scene graph constains things likes AutoTransforms, LightPointNodes, Sequences, etc it will not preserve them.
+     */
+    class OSGEARTHSYMBOLOGY_EXPORT MeshFlattener
+    {
+    public:
+        static void run( osg::Group* group );
+    };
+
+
+
+
+
+} } // namespace osgEarth::Symbology
+
+#endif // OSGEARTHSYMBOLOGY_MESH_FLATTENER
diff --git a/src/osgEarthSymbology/MeshFlattener.cpp b/src/osgEarthSymbology/MeshFlattener.cpp
new file mode 100644
index 0000000..5ded310
--- /dev/null
+++ b/src/osgEarthSymbology/MeshFlattener.cpp
@@ -0,0 +1,204 @@
+/* -*-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 <osgEarthSymbology/MeshConsolidator>
+#include <osgEarthSymbology/MeshFlattener>
+#include <osgEarth/StateSetCache>
+#include <osgUtil/Optimizer>
+#include <osgDB/WriteFile>
+#include <osg/Billboard>
+
+using namespace osgEarth;
+using namespace osgEarth::Symbology;
+
+#define LC "[MeshFlattener] "
+
+using namespace osgEarth;
+
+
+/********************************/
+PrepareForOptimizationVisitor::PrepareForOptimizationVisitor():
+osg::NodeVisitor( osg::NodeVisitor::TRAVERSE_ALL_CHILDREN )
+{
+    setNodeMaskOverride(~0);
+}
+
+void PrepareForOptimizationVisitor::apply(osg::Node& node)
+{
+    node.setUserData(0);
+    node.setUserDataContainer(0);
+    node.setName("");
+    node.setDataVariance(osg::Object::STATIC);
+    node.setCullCallback(0);
+    node.setEventCallback(0);
+    node.setUpdateCallback(0);
+    traverse(node);
+}
+
+/********************************/
+    FlattenSceneGraphVisitor::FlattenSceneGraphVisitor():
+osg::NodeVisitor( osg::NodeVisitor::TRAVERSE_ALL_CHILDREN )
+{
+    setNodeMaskOverride(~0);
+}
+
+    void FlattenSceneGraphVisitor::apply(osg::Node& node)
+    {
+        osg::ref_ptr< osg::StateSet > ss = node.getStateSet();
+        if (ss)
+        {
+            pushStateSet(ss.get());
+        }
+        traverse(node);
+        if (ss)
+        {
+            popStateSet();
+        }
+    }
+
+     void FlattenSceneGraphVisitor::apply(osg::Geode& geode)
+    {
+        osg::Billboard* billboard = dynamic_cast< osg::Billboard* >(&geode);
+        // Special case, skip billboards since we can't cluster them.
+        if (billboard)
+        {
+            return;
+        }
+
+        osg::ref_ptr< osg::StateSet > ss = geode.getStateSet();
+        if (ss)
+        {
+            pushStateSet(ss.get());
+        }
+
+        for (unsigned int i = 0; i < geode.getNumDrawables(); i++)
+        {
+            osg::Geometry* geometry = geode.getDrawable(i)->asGeometry();
+            if (geometry)
+            {
+                osg::ref_ptr< osg::StateSet > geomSS = geometry->getStateSet();
+                if (geomSS.get())
+                {
+                    pushStateSet( geomSS.get() );
+                }
+
+                GeometryVector& geometries = _geometries[_ssStack];
+                geometries.push_back(geometry);
+
+                if (geomSS.get())
+                {
+                    popStateSet();
+                }
+
+                
+            }
+        }
+        
+        if (ss)
+        {
+            popStateSet();
+        }
+
+    }
+
+    void FlattenSceneGraphVisitor::pushStateSet(osg::StateSet* stateSet)
+    {
+        _ssStack.push_back(stateSet);
+    }
+
+    void FlattenSceneGraphVisitor::popStateSet()
+    {
+        _ssStack.pop_back();
+    }
+
+    osg::Node* FlattenSceneGraphVisitor::build()
+    {
+        // Build a group that contains one geode per group
+        osg::Group* result = new osg::Group;
+
+        OE_DEBUG << "We have " << _geometries.size() << " stateset stacks" << std::endl;
+
+        unsigned int i = 0;
+        for (StateSetStackToGeometryMap::iterator itr = _geometries.begin(); itr != _geometries.end(); ++itr)
+        {
+            OE_DEBUG << LC << "StateSetStack " << i++ << " has " << itr->second.size() << " geometries " << std::endl;
+            
+            // Merge all of the statesets
+            osg::StateSet* ss = new osg::StateSet();
+            for (StateSetStack::const_iterator ssItr = itr->first.begin(); ssItr != itr->first.end(); ++ssItr)
+            {
+                ss->merge(*(ssItr->get()));
+            }
+
+            osg::Geode* geode = new osg::Geode;
+            geode->setStateSet(ss);
+            // Add all of the drawables to the new Geode
+            for (GeometryVector::iterator gItr = itr->second.begin(); gItr != itr->second.end(); ++gItr)
+            {
+                osg::Geometry* g = gItr->get();
+                // Remove any stateset that might be on the Geometry
+                g->setStateSet(0);
+                geode->addDrawable( g );
+            }
+            result->addChild(geode);
+            
+            // Consolidate all the drawables in the geode.
+            MeshConsolidator::run(*geode);
+        }
+
+        // Run MERGE_GEOMETRY so that it will merge all the primitive sets
+        osgUtil::Optimizer opt;
+        opt.optimize( result, 
+            osgUtil::Optimizer::MERGE_GEOMETRY
+            );
+       
+        //osgDB::writeNodeFile(*result, "clustered.osg");
+
+        return result;
+    }
+
+
+/********************************/
+void MeshFlattener::run( osg::Group* group )
+{
+    // Do all that we can so the optimizer will actually do it's job.
+    PrepareForOptimizationVisitor v;
+    group->accept(v);
+
+    // Remove any transforms 
+    osgUtil::Optimizer optimizer;
+    optimizer.optimize(group, osgUtil::Optimizer::FLATTEN_STATIC_TRANSFORMS_DUPLICATING_SHARED_SUBGRAPHS);
+
+    // Share all statesets and attributes so we can just do a pointer based stack.
+    osg::ref_ptr< StateSetCache > sscache = new StateSetCache();
+    sscache->optimize( group );
+
+    // Now, collect all the geodes and merge them
+    FlattenSceneGraphVisitor flatten;
+    group->accept(flatten);
+
+    // Remove all the old children.
+    group->removeChildren(0, group->getNumChildren());
+
+    // Add the new flat graph.
+    group->addChild(flatten.build());
+}
diff --git a/src/osgEarthSymbology/MeshSubdivider b/src/osgEarthSymbology/MeshSubdivider
index f35028d..d562d98 100644
--- a/src/osgEarthSymbology/MeshSubdivider
+++ b/src/osgEarthSymbology/MeshSubdivider
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthSymbology/MeshSubdivider.cpp b/src/osgEarthSymbology/MeshSubdivider.cpp
index 55eae49..5655468 100644
--- a/src/osgEarthSymbology/MeshSubdivider.cpp
+++ b/src/osgEarthSymbology/MeshSubdivider.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthSymbology/ModelResource b/src/osgEarthSymbology/ModelResource
index 9c27cdb..1c9701a 100644
--- a/src/osgEarthSymbology/ModelResource
+++ b/src/osgEarthSymbology/ModelResource
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthSymbology/ModelResource.cpp b/src/osgEarthSymbology/ModelResource.cpp
index 75f074a..acdc104 100644
--- a/src/osgEarthSymbology/ModelResource.cpp
+++ b/src/osgEarthSymbology/ModelResource.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -54,9 +54,13 @@ ModelResource::getConfig() const
 osg::Node*
 ModelResource::createNodeFromURI( const URI& uri, const osgDB::Options* dbOptions ) const
 {
+    osg::ref_ptr< osgDB::Options > options = dbOptions ? new osgDB::Options( *dbOptions ) : 0;
+
+    // Explicitly cache images so that models that share images will only load one copy.
+    options->setObjectCacheHint( osgDB::Options::CACHE_IMAGES );
     osg::Node* node = 0L;
 
-    ReadResult r = uri.readNode( dbOptions );
+    ReadResult r = uri.readNode( options.get() );
     if ( r.succeeded() )
     {
         node = r.releaseNode();
@@ -77,7 +81,7 @@ ModelResource::createNodeFromURI( const URI& uri, const osgDB::Options* dbOption
         StringTokenizer( *uri, tok, "()" );
         if (tok.size() >= 2)
         {
-            node = createNodeFromURI( URI(tok[1]), dbOptions );
+            node = createNodeFromURI( URI(tok[1]), options.get() );
         }
     }
 
diff --git a/src/osgEarthSymbology/ModelSymbol b/src/osgEarthSymbology/ModelSymbol
index e96cad8..b63e5e7 100644
--- a/src/osgEarthSymbology/ModelSymbol
+++ b/src/osgEarthSymbology/ModelSymbol
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -36,8 +36,9 @@ namespace osgEarth { namespace Symbology
     class OSGEARTHSYMBOLOGY_EXPORT ModelSymbol : public InstanceSymbol
     {
     public:
-        META_Symbol(ModelSymbol);
+        META_Object(osgEarthSymbology, ModelSymbol);
 
+        ModelSymbol(const ModelSymbol& rhs,const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY);
         ModelSymbol( const Config& conf =Config() );
 
         /** dtor */
diff --git a/src/osgEarthSymbology/ModelSymbol.cpp b/src/osgEarthSymbology/ModelSymbol.cpp
index ec02819..c7b9562 100644
--- a/src/osgEarthSymbology/ModelSymbol.cpp
+++ b/src/osgEarthSymbology/ModelSymbol.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -25,6 +25,16 @@ using namespace osgEarth::Symbology;
 
 OSGEARTH_REGISTER_SIMPLE_SYMBOL(model, ModelSymbol);
 
+ModelSymbol::ModelSymbol(const ModelSymbol& rhs,const osg::CopyOp& copyop):
+InstanceSymbol(rhs, copyop),
+_heading(rhs._heading),
+_pitch(rhs._pitch),
+_roll(rhs._roll),
+_autoScale(rhs._autoScale),
+_node(rhs._node)
+{
+}
+
 ModelSymbol::ModelSymbol( const Config& conf ) :
 InstanceSymbol( conf ),
 _heading  ( NumericExpression(0.0) ),
@@ -78,7 +88,7 @@ ModelSymbol::parseSLD(const Config& c, Style& style)
         style.getOrCreate<ModelSymbol>()->url()->setURIContext( c.referrer() );
     }    
     else if ( match(c.key(),"model-library") ) {
-        style.getOrCreate<ModelSymbol>()->libraryName() = StringExpression(c.value());
+        style.getOrCreate<ModelSymbol>()->library() = StringExpression(c.value());
     }
     else if ( match(c.key(), "model-placement") ) {
         if      ( match(c.value(), "vertex") )   
diff --git a/src/osgEarthSymbology/ModelSymbolizer b/src/osgEarthSymbology/ModelSymbolizer
deleted file mode 100644
index 39672b6..0000000
--- a/src/osgEarthSymbology/ModelSymbolizer
+++ /dev/null
@@ -1,44 +0,0 @@
-/* -*-c++-*- */
-/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2010 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 OSGEARTHSYMBOLOGY_MODEL_SYMBOLIZER_H
-#define OSGEARTHSYMBOLOGY_MODEL_SYMBOLIZER_H 1
-
-#include <osgEarthSymbology/Common>
-#include <osgEarthSymbology/Symbolizer>
-
-namespace osgEarth { namespace Symbology
-{
-    class OSGEARTHSYMBOLOGY_EXPORT ModelSymbolizer : public Symbolizer< State<GeometryContent> >
-    {
-    public: // Symbolizer
-        /**
-         * Creates or updates a subgraph representing the symbolized data.
-         * The Symbolizer can attach the subgraph to the attachPoint.
-         */
-        virtual bool compile(
-            State<GeometryContent>* state,
-            osg::Group* attachPoint);
-
-        ModelSymbolizer();
-    };
-
-} } // namespace osgEarth::Symbology
-
-#endif
diff --git a/src/osgEarthSymbology/PointSymbol b/src/osgEarthSymbology/PointSymbol
index dc8a185..38d9a9a 100644
--- a/src/osgEarthSymbology/PointSymbol
+++ b/src/osgEarthSymbology/PointSymbol
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -30,8 +30,9 @@ namespace osgEarth { namespace Symbology
     class OSGEARTHSYMBOLOGY_EXPORT PointSymbol : public Symbol
     {
     public:
-        META_Symbol(PointSymbol);
+        META_Object(osgEarthSymbology, PointSymbol);
 
+        PointSymbol(const PointSymbol& rhs,const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY);
         PointSymbol( const Config& conf =Config() );
 
         /** dtor */
diff --git a/src/osgEarthSymbology/PointSymbol.cpp b/src/osgEarthSymbology/PointSymbol.cpp
index d8c6170..e3408b5 100644
--- a/src/osgEarthSymbology/PointSymbol.cpp
+++ b/src/osgEarthSymbology/PointSymbol.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -24,6 +24,13 @@ using namespace osgEarth::Symbology;
 
 OSGEARTH_REGISTER_SIMPLE_SYMBOL(point, PointSymbol);
 
+PointSymbol::PointSymbol(const PointSymbol& rhs,const osg::CopyOp& copyop):
+Symbol(rhs, copyop),
+_fill(rhs._fill),
+_size(rhs._size)
+{
+}
+
 PointSymbol::PointSymbol( const Config& conf ) :
 Symbol( conf ),
 _fill ( Fill() ), 
diff --git a/src/osgEarthSymbology/PolygonSymbol b/src/osgEarthSymbology/PolygonSymbol
index e966920..e1b183c 100644
--- a/src/osgEarthSymbology/PolygonSymbol
+++ b/src/osgEarthSymbology/PolygonSymbol
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -31,8 +31,9 @@ namespace osgEarth { namespace Symbology
     class OSGEARTHSYMBOLOGY_EXPORT PolygonSymbol : public Symbol
     {
     public:
-        META_Symbol(PolygonSymbol);
+        META_Object(osgEarthSymbology, PolygonSymbol);
 
+        PolygonSymbol(const PolygonSymbol& rhs,const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY);
         PolygonSymbol( const Config& conf =Config() );
 
         /** dtor */
diff --git a/src/osgEarthSymbology/PolygonSymbol.cpp b/src/osgEarthSymbology/PolygonSymbol.cpp
index e051f26..d40584f 100644
--- a/src/osgEarthSymbology/PolygonSymbol.cpp
+++ b/src/osgEarthSymbology/PolygonSymbol.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -24,6 +24,12 @@ using namespace osgEarth::Symbology;
 
 OSGEARTH_REGISTER_SIMPLE_SYMBOL(polygon, PolygonSymbol);
 
+PolygonSymbol::PolygonSymbol(const PolygonSymbol& rhs,const osg::CopyOp& copyop):
+Symbol(rhs, copyop),
+_fill(rhs._fill)
+{
+}
+
 PolygonSymbol::PolygonSymbol( const Config& conf ) :
 Symbol( conf ),
 _fill ( Fill() )
diff --git a/src/osgEarthSymbology/Query b/src/osgEarthSymbology/Query
index be0f9c9..6d3e449 100644
--- a/src/osgEarthSymbology/Query
+++ b/src/osgEarthSymbology/Query
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -22,6 +22,7 @@
 
 #include <osgEarthSymbology/Common>
 #include <osgEarth/Config>
+#include <osgEarth/Map>
 #include <osgEarth/GeoData>
 #include <osgEarth/TileKey>
 
@@ -54,6 +55,10 @@ namespace osgEarth { namespace Symbology
         /** Sets a driver-specific query expression. */
         optional<osgEarth::TileKey>& tileKey() { return _tileKey; }
         const optional<osgEarth::TileKey>& tileKey() const { return _tileKey; }
+
+        const Map* getMap() const;
+
+        void setMap(const Map* map);
         
 
         /**
@@ -71,6 +76,7 @@ namespace osgEarth { namespace Symbology
         optional<std::string> _expression;
         optional<std::string> _orderby;
         optional<osgEarth::TileKey> _tileKey;
+        const Map* _map;
     };
 
 } } // namespace osgEarth::Symbology
diff --git a/src/osgEarthSymbology/Query.cpp b/src/osgEarthSymbology/Query.cpp
index bd8dfcf..b21bdad 100644
--- a/src/osgEarthSymbology/Query.cpp
+++ b/src/osgEarthSymbology/Query.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -21,7 +21,8 @@
 using namespace osgEarth;
 using namespace osgEarth::Symbology;
 
-Query::Query( const Config& conf )
+Query::Query( const Config& conf ):
+_map(0)
 {
     mergeConfig( conf );
 }
@@ -96,22 +97,34 @@ Query::combineWith( const Query& rhs ) const
     {
         merged.tileKey() = *_tileKey;
     }
-    else
+    else if ( rhs._tileKey.isSet() )
     {
-        // merge the bounds:
-        if ( bounds().isSet() && rhs.bounds().isSet() )
-        {
-            merged.bounds() = bounds()->intersectionWith( *rhs.bounds() );
-        }
-        else if ( bounds().isSet() )
-        {
-            merged.bounds() = *bounds();
-        }
-        else if ( rhs.bounds().isSet() )
-        {
-            merged.bounds() = *rhs.bounds();
-        }
+        merged.tileKey() = *rhs._tileKey;
+    }
+
+    // merge the bounds:
+    if ( bounds().isSet() && rhs.bounds().isSet() )
+    {
+        merged.bounds() = bounds()->intersectionWith( *rhs.bounds() );
+    }
+    else if ( bounds().isSet() )
+    {
+        merged.bounds() = *bounds();
+    }
+    else if ( rhs.bounds().isSet() )
+    {
+        merged.bounds() = *rhs.bounds();
     }
 
     return merged;
 }
+
+const Map* Query::getMap() const
+{
+    return _map;
+}
+
+void Query::setMap(const Map* map)
+{
+    _map = map;
+}
diff --git a/src/osgEarthSymbology/RenderSymbol b/src/osgEarthSymbology/RenderSymbol
index 5ba1fc0..01f8df0 100644
--- a/src/osgEarthSymbology/RenderSymbol
+++ b/src/osgEarthSymbology/RenderSymbol
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -35,10 +35,11 @@ namespace osgEarth { namespace Symbology
     class OSGEARTHSYMBOLOGY_EXPORT RenderSymbol : public Symbol
     {
     public:
-        META_Symbol(RenderSymbol);
+        META_Object(osgEarthSymbology, RenderSymbol);
 
         /** construct a render symbol */
         RenderSymbol(const Config& conf =Config());
+        RenderSymbol(const RenderSymbol& rhs,const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY);
 
         /** whether to perform depth buffer testing */
         optional<bool>& depthTest() { return _depthTest; }
@@ -68,6 +69,10 @@ namespace osgEarth { namespace Symbology
         optional<float>& minAlpha() { return _minAlpha; }
         const optional<float>& minAlpha() const { return _minAlpha; }
 
+        /** render bin to use for sorting */
+        optional<std::string>& renderBin() { return _renderBin; }
+        const optional<std::string>& renderBin() const { return _renderBin; }
+
     public:
         virtual Config getConfig() const;
         virtual void mergeConfig( const Config& conf );
@@ -81,6 +86,7 @@ namespace osgEarth { namespace Symbology
         optional<NumericExpression>  _order;
         optional<unsigned>           _clipPlane;
         optional<float>              _minAlpha;
+        optional<std::string>        _renderBin;
         
         /** dtor */
         virtual ~RenderSymbol() { }
diff --git a/src/osgEarthSymbology/RenderSymbol.cpp b/src/osgEarthSymbology/RenderSymbol.cpp
index f05ba83..b526d1a 100644
--- a/src/osgEarthSymbology/RenderSymbol.cpp
+++ b/src/osgEarthSymbology/RenderSymbol.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -24,6 +24,19 @@ using namespace osgEarth::Symbology;
 
 OSGEARTH_REGISTER_SIMPLE_SYMBOL(render, RenderSymbol);
 
+RenderSymbol::RenderSymbol(const RenderSymbol& rhs,const osg::CopyOp& copyop):
+Symbol(rhs, copyop),
+_depthTest(rhs._depthTest),
+_lighting(rhs._lighting),
+_depthOffset(rhs._depthOffset),
+_backfaceCulling(rhs._backfaceCulling),
+_order(rhs._order),
+_clipPlane(rhs._clipPlane),
+_minAlpha(rhs._minAlpha),
+_renderBin(rhs._renderBin)
+{
+}
+
 RenderSymbol::RenderSymbol(const Config& conf) :
 Symbol          ( conf ),
 _depthTest      ( true ),
@@ -48,6 +61,7 @@ RenderSymbol::getConfig() const
     conf.addObjIfSet( "order",            _order );
     conf.addIfSet   ( "clip_plane",       _clipPlane );
     conf.addIfSet   ( "min_alpha",        _minAlpha );
+    conf.addIfSet   ( "render_bin",       _renderBin );
     return conf;
 }
 
@@ -61,6 +75,7 @@ RenderSymbol::mergeConfig( const Config& conf )
     conf.getObjIfSet( "order",            _order );
     conf.getIfSet   ( "clip_plane",       _clipPlane );
     conf.getIfSet   ( "min_alpha",        _minAlpha );
+    conf.getIfSet   ( "render_bin",       _renderBin );
 }
 
 void
@@ -105,4 +120,7 @@ RenderSymbol::parseSLD(const Config& c, Style& style)
     else if ( match(c.key(), "render-min-alpha") ) {
         style.getOrCreate<RenderSymbol>()->minAlpha() = as<float>(c.value(), *defaults.minAlpha() );
     }
+    else if ( match(c.key(), "render-bin") ) {
+        style.getOrCreate<RenderSymbol>()->renderBin() = c.value();
+    }
 }
diff --git a/src/osgEarthSymbology/Resource b/src/osgEarthSymbology/Resource
index bada14c..a8a1441 100644
--- a/src/osgEarthSymbology/Resource
+++ b/src/osgEarthSymbology/Resource
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -30,14 +30,22 @@ namespace osgEarth { namespace Symbology
      * Base class for a Resource, which is an external data element managed
      * by a ResourceLibrary.
      */
-    class OSGEARTHSYMBOLOGY_EXPORT Resource : public Taggable<osg::Referenced>
+    class OSGEARTHSYMBOLOGY_EXPORT Resource : public Taggable<osg::Object>
     {
     protected:
+        Resource(const Resource& rhs,const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY) {};
         Resource( const Config& config =Config() );
 
         /** dtor */
         virtual ~Resource() { }
 
+        // META_Object specialization:
+        virtual osg::Object* cloneType() const { return 0; } // cloneType() not appropriate
+        virtual osg::Object* clone(const osg::CopyOp&) const { return 0; } // clone() not appropriate
+        virtual bool isSameKindAs(const osg::Object* obj) const { return dynamic_cast<const Resource*>(obj)!=NULL; }
+        virtual const char* className() const { return "Resource"; }
+        virtual const char* libraryName() const { return "osgEarthSymbology"; }
+
     public: // properties
 
         /** Readable name of the resource. */
diff --git a/src/osgEarthSymbology/Resource.cpp b/src/osgEarthSymbology/Resource.cpp
index 85ac9a1..d118770 100644
--- a/src/osgEarthSymbology/Resource.cpp
+++ b/src/osgEarthSymbology/Resource.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthSymbology/ResourceCache b/src/osgEarthSymbology/ResourceCache
index 011f1a3..704199d 100644
--- a/src/osgEarthSymbology/ResourceCache
+++ b/src/osgEarthSymbology/ResourceCache
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthSymbology/ResourceCache.cpp b/src/osgEarthSymbology/ResourceCache.cpp
index 4ff88c3..df04bda 100644
--- a/src/osgEarthSymbology/ResourceCache.cpp
+++ b/src/osgEarthSymbology/ResourceCache.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -112,11 +112,14 @@ ResourceCache::cloneOrCreateInstanceNode(InstanceResource*        res,
     {
         Threading::ScopedMutexLock exclusive( _instanceMutex );
 
+        // Deep copy everything except for images.  Some models may share imagery so we only want one copy of it at a time.
+        osg::CopyOp copyOp = osg::CopyOp::DEEP_COPY_ALL & ~osg::CopyOp::DEEP_COPY_IMAGES;
+
         // double check to avoid race condition
         InstanceCache::Record rec;
         if ( _instanceCache.get(key, rec) && rec.value().valid() )
         {
-            output = osg::clone(rec.value().get(), osg::CopyOp::DEEP_COPY_ALL);
+            output = osg::clone(rec.value().get(), copyOp);
         }
         else
         {
@@ -125,7 +128,7 @@ ResourceCache::cloneOrCreateInstanceNode(InstanceResource*        res,
             if ( output.valid() )
             {
                 _instanceCache.insert( key, output.get() );
-                output = osg::clone(output.get(), osg::CopyOp::DEEP_COPY_ALL);
+                output = osg::clone(output.get(), copyOp);
             }
         }
     }
diff --git a/src/osgEarthSymbology/ResourceLibrary b/src/osgEarthSymbology/ResourceLibrary
index e0064f1..5a8ad6d 100644
--- a/src/osgEarthSymbology/ResourceLibrary
+++ b/src/osgEarthSymbology/ResourceLibrary
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthSymbology/ResourceLibrary.cpp b/src/osgEarthSymbology/ResourceLibrary.cpp
index ec54360..dffdbba 100644
--- a/src/osgEarthSymbology/ResourceLibrary.cpp
+++ b/src/osgEarthSymbology/ResourceLibrary.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthSymbology/Skins b/src/osgEarthSymbology/Skins
index 80d1e50..a9a216b 100644
--- a/src/osgEarthSymbology/Skins
+++ b/src/osgEarthSymbology/Skins
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -148,8 +148,9 @@ namespace osgEarth { namespace Symbology
     class OSGEARTHSYMBOLOGY_EXPORT SkinSymbol : public Taggable<Symbol>
     {
     public:
-        META_Symbol(SkinSymbol);
+        META_Object(osgEarthSymbology, SkinSymbol);
 
+        SkinSymbol(const SkinSymbol& rhs,const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY);
         SkinSymbol( const Config& conf =Config() );
 
         /** dtor */
@@ -158,8 +159,8 @@ namespace osgEarth { namespace Symbology
     public: // query parameters
 
         /** Name of the resource library to use with this symbol. */
-        optional<std::string>& libraryName() { return _libraryName; }
-        const optional<std::string>& libraryName() const { return _libraryName; }
+        optional<std::string>& library() { return _library; }
+        const optional<std::string>& library() const { return _library; }
 
         /** Object height in meters (must fall in the skin's min/max object height range to be accepted) */
         optional<float>& objectHeight() { return _objHeight; }
@@ -171,7 +172,7 @@ namespace osgEarth { namespace Symbology
 
         /** Maximum acceptable real-world object height for which this image would make an appropriate texture */
         optional<float>& maxObjectHeight() { return _maxObjHeight; }
-        const optional<float>& maxObjectHeight() const { return _minObjHeight; }
+        const optional<float>& maxObjectHeight() const { return _maxObjHeight; }
 
         /** Whether this image is suitable for use as a vertically repeating texture */
         optional<bool>& isTiled() { return _isTiled; }
@@ -191,7 +192,7 @@ namespace osgEarth { namespace Symbology
         static void parseSLD(const Config& c, class Style& style);
 
     protected:
-        optional<std::string> _libraryName;
+        optional<std::string> _library;
         optional<float>       _objHeight;
         optional<float>       _minObjHeight;
         optional<float>       _maxObjHeight;
diff --git a/src/osgEarthSymbology/Skins.cpp b/src/osgEarthSymbology/Skins.cpp
index eca3b47..b51ccab 100644
--- a/src/osgEarthSymbology/Skins.cpp
+++ b/src/osgEarthSymbology/Skins.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -190,6 +190,18 @@ SkinResource::createImage( const osgDB::Options* dbOptions ) const
 
 OSGEARTH_REGISTER_SIMPLE_SYMBOL(skin, SkinSymbol);
 
+SkinSymbol::SkinSymbol(const SkinSymbol& rhs,const osg::CopyOp& copyop):
+Taggable<Symbol>(rhs, copyop),
+_library(rhs._library),
+_objHeight(rhs._objHeight),
+_minObjHeight(rhs._minObjHeight),
+_maxObjHeight(rhs._maxObjHeight),
+_isTiled(rhs._isTiled),
+_randomSeed(rhs._randomSeed),
+_name(rhs._name)
+{
+}
+
 SkinSymbol::SkinSymbol( const Config& conf ) :
 _objHeight    ( 0.0f ),
 _minObjHeight ( 0.0f ),
@@ -204,7 +216,7 @@ _randomSeed   ( 0 )
 void 
 SkinSymbol::mergeConfig( const Config& conf )
 {
-    conf.getIfSet( "library",             _libraryName );
+    conf.getIfSet( "library",             _library );
     conf.getIfSet( "object_height",       _objHeight );
     conf.getIfSet( "min_object_height",   _minObjHeight );
     conf.getIfSet( "max_object_height",   _maxObjHeight );
@@ -221,7 +233,7 @@ SkinSymbol::getConfig() const
     Config conf = Symbol::getConfig();
     conf.key() = "skin";
 
-    conf.addIfSet( "library",             _libraryName );
+    conf.addIfSet( "library",             _library );
     conf.addIfSet( "object_height",       _objHeight );
     conf.addIfSet( "min_object_height",   _minObjHeight );
     conf.addIfSet( "max_object_height",   _maxObjHeight );
@@ -242,7 +254,7 @@ SkinSymbol::parseSLD(const Config& c, Style& style)
 {
     if ( match(c.key(), "skin-library") ) {
         if ( !c.value().empty() ) 
-            style.getOrCreate<SkinSymbol>()->libraryName() = c.value();
+            style.getOrCreate<SkinSymbol>()->library() = c.value();
     }
     else if ( match(c.key(), "skin-tags") ) {
         style.getOrCreate<SkinSymbol>()->addTags( c.value() );
diff --git a/src/osgEarthSymbology/StencilVolumeNode b/src/osgEarthSymbology/StencilVolumeNode
index b9fe08c..5a9fde9 100644
--- a/src/osgEarthSymbology/StencilVolumeNode
+++ b/src/osgEarthSymbology/StencilVolumeNode
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthSymbology/StencilVolumeNode.cpp b/src/osgEarthSymbology/StencilVolumeNode.cpp
index cb91515..e88dcb2 100644
--- a/src/osgEarthSymbology/StencilVolumeNode.cpp
+++ b/src/osgEarthSymbology/StencilVolumeNode.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthSymbology/Stroke b/src/osgEarthSymbology/Stroke
index d803cf2..e0c2fe4 100644
--- a/src/osgEarthSymbology/Stroke
+++ b/src/osgEarthSymbology/Stroke
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthSymbology/Stroke.cpp b/src/osgEarthSymbology/Stroke.cpp
index 8be1c8d..64b0d80 100644
--- a/src/osgEarthSymbology/Stroke.cpp
+++ b/src/osgEarthSymbology/Stroke.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthSymbology/Style b/src/osgEarthSymbology/Style
index 2f965a8..7d6c9a7 100644
--- a/src/osgEarthSymbology/Style
+++ b/src/osgEarthSymbology/Style
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -33,6 +33,7 @@
 #include <osgEarthSymbology/TextSymbol>
 #include <osgEarthSymbology/Skins>
 #include <osgEarthSymbology/RenderSymbol>
+#include <osgEarthSymbology/CoverageSymbol>
 
 #include <osgEarth/Config>
 #include <osg/Object>
@@ -83,6 +84,8 @@ namespace osgEarth { namespace Symbology
         /** Remove a symbol from the collection */
         bool removeSymbol (Symbol* symbol);
 
+        void copySymbols(const Style& style);
+
         /** Remove a symbol by type */
         template<typename T>
         bool remove()
diff --git a/src/osgEarthSymbology/Style.cpp b/src/osgEarthSymbology/Style.cpp
index af21d34..39a27f3 100644
--- a/src/osgEarthSymbology/Style.cpp
+++ b/src/osgEarthSymbology/Style.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
@@ -48,19 +51,19 @@ _uri     ( rhs._uri )
     else
     {
         _symbols.clear();
-        mergeConfig( rhs.getConfig(false) );
+        copySymbols(rhs);
     }
 }
 
 Style&
 Style::operator = ( const Style& rhs )
 {
-    _name.clear();
-    _origType.clear();
-    _origData.clear();
-    _uri.unset();
+    _name = rhs._name;
+    _origType = rhs._origType;
+    _origData = rhs._origData;
+    _uri = rhs._uri;
     _symbols.clear();
-    mergeConfig( rhs.getConfig(false) );
+    copySymbols(rhs);
     return *this;
 }
 
@@ -101,12 +104,8 @@ Style
 Style::combineWith( const Style& rhs ) const
 {
     // start by deep-cloning this style.
-    Config conf = getConfig( false );
-    Style newStyle( conf );
-
-    // next, merge in the symbology from the other style.
-    newStyle.mergeConfig( rhs.getConfig(false) );
-
+    Style newStyle(*this);
+    newStyle.copySymbols(rhs);
     if ( !this->empty() && !rhs.empty() )
         newStyle.setName( _name + std::string(":") + rhs.getName() );
     else if ( !this->empty() && rhs.empty() )
@@ -115,10 +114,17 @@ Style::combineWith( const Style& rhs ) const
         newStyle.setName( rhs.getName() );
     else
         newStyle.setName( _name );
-
     return newStyle;
 }
 
+void Style::copySymbols(const Style& style)
+{
+    for (SymbolList::const_iterator itr = style._symbols.begin(); itr != style._symbols.end(); ++itr)
+    {
+        addSymbol(static_cast<Symbol*>(itr->get()->clone(osg::CopyOp::SHALLOW_COPY)));
+    }
+}
+
 void
 Style::fromSLD( const Config& sld )
 {
diff --git a/src/osgEarthSymbology/StyleSelector b/src/osgEarthSymbology/StyleSelector
index 906e0e7..9650f02 100644
--- a/src/osgEarthSymbology/StyleSelector
+++ b/src/osgEarthSymbology/StyleSelector
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthSymbology/StyleSelector.cpp b/src/osgEarthSymbology/StyleSelector.cpp
index be55dd6..c308aa4 100644
--- a/src/osgEarthSymbology/StyleSelector.cpp
+++ b/src/osgEarthSymbology/StyleSelector.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/osgEarthSymbology/StyleSheet b/src/osgEarthSymbology/StyleSheet
index ca9e02b..f3e110f 100644
--- a/src/osgEarthSymbology/StyleSheet
+++ b/src/osgEarthSymbology/StyleSheet
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthSymbology/StyleSheet.cpp b/src/osgEarthSymbology/StyleSheet.cpp
index eb96486..f2aa948 100644
--- a/src/osgEarthSymbology/StyleSheet.cpp
+++ b/src/osgEarthSymbology/StyleSheet.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/osgEarthSymbology/Symbol b/src/osgEarthSymbology/Symbol
index 3c819ab..3076478 100644
--- a/src/osgEarthSymbology/Symbol
+++ b/src/osgEarthSymbology/Symbol
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -27,11 +27,6 @@
 #include <osg/Referenced>
 #include <vector>
 
-#define META_Symbol(name) \
-    virtual bool isSameKindAs(const Symbol* rhs) const { return dynamic_cast<const name *>(rhs)!=NULL; } \
-    virtual const char* className() const { return #name; }
-
-
 namespace osgEarth { namespace Symbology
 {
     class Style;
@@ -39,7 +34,7 @@ namespace osgEarth { namespace Symbology
     /**
      * Abstract base class for all Symbol types.
      */
-    class OSGEARTHSYMBOLOGY_EXPORT Symbol : public osg::Referenced
+    class OSGEARTHSYMBOLOGY_EXPORT Symbol : public osg::Object
     {
     public:
         /** Script expression, optionally implemented by symbolizers. */
@@ -49,11 +44,13 @@ namespace osgEarth { namespace Symbology
         /** URI context (relative paths, etc) associated wtih this symbol */
         const URIContext& uriContext() const { return _uriContext; }
 
-        virtual Config getConfig() const;
+        virtual Config getConfig() const;   
+
+    public:
+        /* methods required by osg::Object */
+        META_Object(osgEarthSymbology, Symbol);
 
-    public: // META_Symbol methods
-        virtual bool isSameKindAs(const Symbol* sym) const =0;
-        virtual const char* className() const =0;        
+        Symbol(const Symbol& rhs,const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY);
 
     protected:
         URIContext _uriContext;
diff --git a/src/osgEarthSymbology/Symbol.cpp b/src/osgEarthSymbology/Symbol.cpp
index 65714ee..57a4a18 100644
--- a/src/osgEarthSymbology/Symbol.cpp
+++ b/src/osgEarthSymbology/Symbol.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -76,6 +76,13 @@ _script( StringExpression("{}") )
     mergeConfig(conf);
 }
 
+Symbol::Symbol(const Symbol& rhs,const osg::CopyOp& copyop):
+osg::Object(rhs, copyop)
+{
+    _uriContext = rhs._uriContext;
+    _script = rhs._script;
+}
+
 void
 Symbol::mergeConfig(const Config& conf)
 {
diff --git a/src/osgEarthSymbology/Tags b/src/osgEarthSymbology/Tags
index 965388a..cf52071 100644
--- a/src/osgEarthSymbology/Tags
+++ b/src/osgEarthSymbology/Tags
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -22,6 +22,7 @@
 
 #include <osgEarthSymbology/Common>
 #include <osgEarth/StringUtils>
+#include <osg/CopyOp>
 #include <vector>
 #include <set>
 #include <algorithm>
@@ -35,6 +36,16 @@ namespace osgEarth { namespace Symbology
     class Taggable : public T
     {
     public:
+        Taggable()
+        {
+        }
+        
+        Taggable(const Taggable& rhs,const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY):
+        T(rhs, copyop)
+        {
+            _tags = rhs._tags;
+        }
+
         void addTag( const std::string& tag ) {
             _tags.insert( normalize( tag ) );
         }
diff --git a/src/osgEarthSymbology/TextSymbol b/src/osgEarthSymbology/TextSymbol
index dbb718a..51c6ea2 100644
--- a/src/osgEarthSymbology/TextSymbol
+++ b/src/osgEarthSymbology/TextSymbol
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -71,8 +71,9 @@ namespace osgEarth { namespace Symbology
             LAYOUT_VERTICAL
         };
 
-        META_Symbol(TextSymbol);
+        META_Object(osgEarthSymbology, TextSymbol);
 
+        TextSymbol(const TextSymbol& rhs,const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY);
         TextSymbol( const Config& conf =Config() );
 
         /** dtor */
diff --git a/src/osgEarthSymbology/TextSymbol.cpp b/src/osgEarthSymbology/TextSymbol.cpp
index b42409f..ebc491d 100644
--- a/src/osgEarthSymbology/TextSymbol.cpp
+++ b/src/osgEarthSymbology/TextSymbol.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
@@ -24,6 +27,27 @@ using namespace osgEarth::Symbology;
 
 OSGEARTH_REGISTER_SIMPLE_SYMBOL(text, TextSymbol);
 
+TextSymbol::TextSymbol(const TextSymbol& rhs,const osg::CopyOp& copyop):
+Symbol(rhs, copyop),
+_fill(rhs._fill),
+_halo(rhs._halo),
+_haloOffset(rhs._haloOffset),
+_font(rhs._font),
+_size(rhs._size),
+_content(rhs._content),
+_priority(rhs._priority),
+_removeDuplicateLabels(rhs._removeDuplicateLabels),
+_pixelOffset(rhs._pixelOffset),
+_provider(rhs._provider),
+_encoding(rhs._encoding),
+_alignment(rhs._alignment),
+_layout(rhs._layout),
+_declutter(rhs._declutter),
+_occlusionCull(rhs._occlusionCull),
+_occlusionCullAltitude(rhs._occlusionCullAltitude)
+{
+}
+
 TextSymbol::TextSymbol( const Config& conf ) :
 Symbol                ( conf ),
 _fill                 ( Fill( 1, 1, 1, 1 ) ),
diff --git a/src/osgEarthUtil/ActivityMonitorTool b/src/osgEarthUtil/ActivityMonitorTool
index e63d12a..51c6154 100644
--- a/src/osgEarthUtil/ActivityMonitorTool
+++ b/src/osgEarthUtil/ActivityMonitorTool
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthUtil/ActivityMonitorTool.cpp b/src/osgEarthUtil/ActivityMonitorTool.cpp
index 3be8c20..e38350e 100644
--- a/src/osgEarthUtil/ActivityMonitorTool.cpp
+++ b/src/osgEarthUtil/ActivityMonitorTool.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthUtil/AnnotationEvents b/src/osgEarthUtil/AnnotationEvents
index 0b97278..8484209 100644
--- a/src/osgEarthUtil/AnnotationEvents
+++ b/src/osgEarthUtil/AnnotationEvents
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
@@ -23,6 +26,13 @@
 #include <osgEarthAnnotation/AnnotationNode>
 #include <set>
 
+/**
+ * @deprecated
+ *
+ * These classes are deprecated, and will be removed in a future version.
+ * Please consider using the RTTPicker class instead and handling hovering and 
+ * clicking in your own code.
+ */
 namespace osgEarth { namespace Util
 {	
     using namespace osgEarth::Util;
@@ -30,6 +40,7 @@ namespace osgEarth { namespace Util
 
     /**
      * Event handler skeleton for handling events from the AnnotationEventCallback.
+     * @deprecated
      */
     struct AnnotationEventHandler : public osg::Referenced
     {
@@ -52,6 +63,7 @@ namespace osgEarth { namespace Util
     /**
      * Event-traversal node callback that handles user interaction with 
      * annotation objects.
+     * @deprecated
      */
     class OSGEARTHUTIL_EXPORT AnnotationEventCallback : public osg::NodeCallback
     {
diff --git a/src/osgEarthUtil/AnnotationEvents.cpp b/src/osgEarthUtil/AnnotationEvents.cpp
index 55659b8..de8c224 100644
--- a/src/osgEarthUtil/AnnotationEvents.cpp
+++ b/src/osgEarthUtil/AnnotationEvents.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,17 +8,20 @@
 * 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.
+* 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/AnnotationEvents>
-#include <osgEarth/Pickers>
+#include <osgEarth/IntersectionPicker>
 #include <osgGA/GUIEventAdapter>
 #include <osgGA/EventVisitor>
 #include <osgViewer/View>
@@ -81,13 +84,13 @@ AnnotationEventCallback::operator()( osg::Node* node, osg::NodeVisitor* nv )
             _args.buttons = ea->getButtonMask();
             _args.modkeys = ea->getModKeyMask();
 
-            Picker picker( view, node );
-            Picker::Hits hits;
+            IntersectionPicker picker( view, node );
+            IntersectionPicker::Hits hits;
             if ( picker.pick( _args.x, _args.y, hits ) )
             {
                 std::set<AnnotationNode*> fired; // prevent multiple hits on the same instance
 
-                for( Picker::Hits::const_iterator h = hits.begin(); h != hits.end(); ++h )
+                for( IntersectionPicker::Hits::const_iterator h = hits.begin(); h != hits.end(); ++h )
                 {
                     AnnotationNode* anno = picker.getNode<AnnotationNode>( *h );
                     if ( anno && fired.find(anno) == fired.end() )
@@ -114,14 +117,14 @@ AnnotationEventCallback::operator()( osg::Node* node, osg::NodeVisitor* nv )
                 toUnHover.insert( *i );
             }
 
-            Picker picker( view, node );
-            Picker::Hits hits;
+            IntersectionPicker picker( view, node );
+            IntersectionPicker::Hits hits;
 
             if ( picker.pick( _args.x, _args.y, hits ) )
             {
-                for( Picker::Hits::const_iterator h = hits.begin(); h != hits.end(); ++h )
+                for( IntersectionPicker::Hits::const_iterator h = hits.begin(); h != hits.end(); ++h )
                 {
-                    const Picker::Hit& hit = *h;
+                    const IntersectionPicker::Hit& hit = *h;
 
                     AnnotationNode* anno = picker.getNode<AnnotationNode>( hit );
                     if ( anno )
diff --git a/src/osgEarthUtil/ArcGIS b/src/osgEarthUtil/ArcGIS
index e5795e9..9ad620d 100644
--- a/src/osgEarthUtil/ArcGIS
+++ b/src/osgEarthUtil/ArcGIS
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/osgEarthUtil/ArcGIS.cpp b/src/osgEarthUtil/ArcGIS.cpp
index fc88eb0..32d9daf 100644
--- a/src/osgEarthUtil/ArcGIS.cpp
+++ b/src/osgEarthUtil/ArcGIS.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthUtil/AtlasBuilder b/src/osgEarthUtil/AtlasBuilder
index de2f6f5..394099c 100644
--- a/src/osgEarthUtil/AtlasBuilder
+++ b/src/osgEarthUtil/AtlasBuilder
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
@@ -62,6 +65,12 @@ namespace osgEarth { namespace Util
         const std::vector<std::string>& auxFilePatterns() const { return _auxPatterns; }
         const std::vector<osg::Vec4f>& auxDefaultValues() const { return _auxDefaults; }
 
+        /**
+         * Whether to generate an RGB texture.  By default RGBA textures are created.
+         */
+        bool getRGB() const { return _rgb; }
+        void setRGB( bool rgb ) { _rgb = rgb; }
+
         /** Builds an atlas. */
         bool build(
             const osgEarth::Symbology::ResourceLibrary* input,
@@ -76,6 +85,7 @@ namespace osgEarth { namespace Util
         std::vector<std::string> _auxPatterns;
         std::vector<osg::Vec4f>  _auxDefaults;
         bool _debug;
+        bool _rgb;
     };
 
 } } // namespace osgEarth::Util
diff --git a/src/osgEarthUtil/AtlasBuilder.cpp b/src/osgEarthUtil/AtlasBuilder.cpp
index 78d09a6..71a9059 100644
--- a/src/osgEarthUtil/AtlasBuilder.cpp
+++ b/src/osgEarthUtil/AtlasBuilder.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -70,7 +70,8 @@ AtlasBuilder::AtlasBuilder(const osgDB::Options* options) :
 _options( options ),
 _width  ( 1024 ),
 _height ( 1024 ),
-_debug  ( false )
+_debug  ( false ),
+_rgb    ( false )
 {
     //nop
     if (::getenv("OSGEARTH_ATLAS_DEBUG"))
@@ -270,7 +271,7 @@ AtlasBuilder::build(const ResourceLibrary* inputLib,
             maxS,
             maxT,
             atlasList.size(),
-            GL_RGBA,
+            _rgb ? GL_RGB : GL_RGBA,
             GL_UNSIGNED_BYTE);
 
         // initialize to all zeros
diff --git a/src/osgEarthUtil/AutoClipPlaneHandler b/src/osgEarthUtil/AutoClipPlaneHandler
index 808123d..f59acfc 100644
--- a/src/osgEarthUtil/AutoClipPlaneHandler
+++ b/src/osgEarthUtil/AutoClipPlaneHandler
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -20,7 +20,7 @@
 #define OSGEARTHUTIL_AUTOCLIPPLANEHANDLER_H
 
 #include <osgEarthUtil/Common>
-#include <osgEarth/ThreadingUtils>
+#include <osgEarth/Containers>
 #include <osgEarth/Utils>
 #include <osgGA/GUIEventHandler>
 #include <osgGA/EventVisitor>
@@ -94,7 +94,7 @@ namespace osgEarth { namespace Util
         double _rp2, _rp;
         bool   _autoFarPlaneClamping;
         osg::observer_ptr<MapNode> _mapNode;
-        Threading::PerObjectMap< osg::Camera*, osg::ref_ptr<osg::CullSettings::ClampProjectionMatrixCallback> > _clampers;
+        PerObjectFastMap< osg::Camera*, osg::ref_ptr<osg::CullSettings::ClampProjectionMatrixCallback> > _clampers;
     };
 
 
diff --git a/src/osgEarthUtil/AutoClipPlaneHandler.cpp b/src/osgEarthUtil/AutoClipPlaneHandler.cpp
index 761cf27..7aef102 100644
--- a/src/osgEarthUtil/AutoClipPlaneHandler.cpp
+++ b/src/osgEarthUtil/AutoClipPlaneHandler.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthUtil/BrightnessContrastColorFilter b/src/osgEarthUtil/BrightnessContrastColorFilter
index df85cd7..bfaf0b7 100644
--- a/src/osgEarthUtil/BrightnessContrastColorFilter
+++ b/src/osgEarthUtil/BrightnessContrastColorFilter
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/osgEarthUtil/BrightnessContrastColorFilter.cpp b/src/osgEarthUtil/BrightnessContrastColorFilter.cpp
index 2f9f312..9ed1b0f 100644
--- a/src/osgEarthUtil/BrightnessContrastColorFilter.cpp
+++ b/src/osgEarthUtil/BrightnessContrastColorFilter.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/osgEarthUtil/CMYKColorFilter b/src/osgEarthUtil/CMYKColorFilter
index 64ac220..2b1d7a3 100644
--- a/src/osgEarthUtil/CMYKColorFilter
+++ b/src/osgEarthUtil/CMYKColorFilter
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/osgEarthUtil/CMYKColorFilter.cpp b/src/osgEarthUtil/CMYKColorFilter.cpp
index 1afe79d..37baaf6 100644
--- a/src/osgEarthUtil/CMYKColorFilter.cpp
+++ b/src/osgEarthUtil/CMYKColorFilter.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/osgEarthUtil/CMakeLists.txt b/src/osgEarthUtil/CMakeLists.txt
index 1d38842..859fa41 100644
--- a/src/osgEarthUtil/CMakeLists.txt
+++ b/src/osgEarthUtil/CMakeLists.txt
@@ -19,17 +19,18 @@ SET(HEADERS_ROOT
     ContourMap
     ClampCallback
     DataScanner
-    DateTime
-    DetailTexture
     EarthManipulator
 	Ephemeris
     ExampleResources
     Export
-    FeatureManipTool
     FeatureQueryTool
     Fog
     Formatter
     GeodeticGraticule
+    GraticuleExtension
+    GraticuleNode
+    GraticuleOptions
+    GraticuleTerrainEffect
     HTM
     LatLongFormatter
     LineOfSight
@@ -40,18 +41,18 @@ SET(HEADERS_ROOT
     MGRSFormatter
     MGRSGraticule
     MouseCoordsTool
-    NormalMap
     ObjectLocator
     Ocean
     PolyhedralLineOfSight
     RadialLineOfSight
+    RTTPicker
+    Shaders
 	Shadowing
 	SimplexNoise
     Sky
     SpatialData
     StarData
     TerrainProfile
-	TextureSplatter
     TileIndex
     TileIndexBuilder
     TFS
@@ -67,6 +68,30 @@ SET(HEADERS_ROOT
 SOURCE_GROUP( Headers FILES ${HEADERS_ROOT} )
 
 
+# Generate inline shaders.
+set(SHADERS_CPP
+    "${CMAKE_CURRENT_BINARY_DIR}/AutoGenShaders.cpp")
+
+set(TARGET_GLSL
+    ContourMap.vert.glsl
+    ContourMap.frag.glsl
+    Fog.vert.glsl
+    Fog.frag.glsl
+    Graticule.vert.glsl
+    Graticule.frag.glsl
+    LogDepthBuffer.vert.glsl
+    LogDepthBuffer.VertOnly.vert.glsl    
+    LogDepthBuffer.frag.glsl )
+
+set(TARGET_IN
+    Shaders.cpp.in)
+
+configure_shaders(            
+    Shaders.cpp.in
+    ${SHADERS_CPP}
+    ${TARGET_GLSL} )
+
+
 SET(SOURCES_ROOT
 	ActivityMonitorTool.cpp
     AnnotationEvents.cpp
@@ -76,35 +101,35 @@ SET(SOURCES_ROOT
     ClampCallback.cpp
     Controls.cpp
     ContourMap.cpp
-    DataScanner.cpp    
-    DetailTexture.cpp
+    DataScanner.cpp
     EarthManipulator.cpp
 	Ephemeris.cpp
     ExampleResources.cpp
-    FeatureManipTool.cpp
     FeatureQueryTool.cpp
     Fog.cpp
     GeodeticGraticule.cpp
+    GraticuleExtension.cpp
+    GraticuleTerrainEffect.cpp
+    GraticuleNode.cpp
     HTM.cpp
     LatLongFormatter.cpp
     LinearLineOfSight.cpp
-    LODBlending.cpp
 	LogarithmicDepthBuffer.cpp
+    LODBlending.cpp
     MeasureTool.cpp
     MGRSFormatter.cpp
     MGRSGraticule.cpp
     MouseCoordsTool.cpp
-    NormalMap.cpp
     ObjectLocator.cpp
     Ocean.cpp
     PolyhedralLineOfSight.cpp
     RadialLineOfSight.cpp
+    RTTPicker.cpp
 	Shadowing.cpp
 	SimplexNoise.cpp
     SpatialData.cpp
     Sky.cpp
     TerrainProfile.cpp
-	TextureSplatter.cpp
     TileIndex.cpp
     TileIndexBuilder.cpp
     TFS.cpp
@@ -116,6 +141,7 @@ SET(SOURCES_ROOT
     VerticalScale.cpp
     WFS.cpp
     WMS.cpp
+    ${SHADERS_CPP}
 )
 
 SOURCE_GROUP( Sources FILES ${SOURCES_ROOT} )
@@ -129,6 +155,7 @@ SET(HEADERS_COLORFILTER
     HSLColorFilter
     RGBColorFilter
     ChromaKeyColorFilter
+    NightColorFilter
 )
 SOURCE_GROUP( Headers\\ColorFilters FILES ${HEADERS_COLORFILTER} )
 
@@ -141,6 +168,7 @@ SET(SOURCES_COLORFILTER
     HSLColorFilter.cpp
     RGBColorFilter.cpp
     ChromaKeyColorFilter.cpp
+    NightColorFilter.cpp
 )
 SOURCE_GROUP( Sources\\ColorFilters FILES ${SOURCES_COLORFILTER} )
 
@@ -157,6 +185,8 @@ SET(LIB_COMMON_FILES
 ADD_LIBRARY(${LIB_NAME} ${OSGEARTH_USER_DEFINED_DYNAMIC_OR_STATIC}
     ${LIB_PUBLIC_HEADERS}
     ${LIB_COMMON_FILES}
+    ${TARGET_GLSL}
+    ${TARGET_IN}
 )
 
 # Setting this tells ModuleInstall not to set source groups (since we're doing it here)
diff --git a/src/osgEarthUtil/ChromaKeyColorFilter b/src/osgEarthUtil/ChromaKeyColorFilter
index f8d7b4a..cd06c13 100644
--- a/src/osgEarthUtil/ChromaKeyColorFilter
+++ b/src/osgEarthUtil/ChromaKeyColorFilter
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthUtil/ChromaKeyColorFilter.cpp b/src/osgEarthUtil/ChromaKeyColorFilter.cpp
index cf2d116..cee482a 100644
--- a/src/osgEarthUtil/ChromaKeyColorFilter.cpp
+++ b/src/osgEarthUtil/ChromaKeyColorFilter.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthUtil/ClampCallback b/src/osgEarthUtil/ClampCallback
index a513d2b..db40bba 100644
--- a/src/osgEarthUtil/ClampCallback
+++ b/src/osgEarthUtil/ClampCallback
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/osgEarthUtil/ClampCallback.cpp b/src/osgEarthUtil/ClampCallback.cpp
index 5278419..ea4db48 100644
--- a/src/osgEarthUtil/ClampCallback.cpp
+++ b/src/osgEarthUtil/ClampCallback.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/osgEarthUtil/Common b/src/osgEarthUtil/Common
index 358df1e..42221e1 100644
--- a/src/osgEarthUtil/Common
+++ b/src/osgEarthUtil/Common
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthUtil/ContourMap b/src/osgEarthUtil/ContourMap
index 9154e0f..b930042 100644
--- a/src/osgEarthUtil/ContourMap
+++ b/src/osgEarthUtil/ContourMap
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/osgEarthUtil/ContourMap.cpp b/src/osgEarthUtil/ContourMap.cpp
index 8210a05..db67b08 100644
--- a/src/osgEarthUtil/ContourMap.cpp
+++ b/src/osgEarthUtil/ContourMap.cpp
@@ -8,15 +8,19 @@
 * 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.
+* 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/ContourMap>
+#include <osgEarthUtil/Shaders>
 #include <osgEarth/Registry>
 #include <osgEarth/Capabilities>
 #include <osgEarth/VirtualProgram>
@@ -27,40 +31,6 @@
 using namespace osgEarth;
 using namespace osgEarth::Util;
 
-namespace
-{
-    const char* vs =
-        "#version " GLSL_VERSION_STR "\n"
-        GLSL_DEFAULT_PRECISION_FLOAT "\n"
-
-        "attribute vec4 oe_terrain_attr; \n"
-        "uniform float oe_contour_min; \n"
-        "uniform float oe_contour_range; \n"
-        "varying float oe_contour_lookup; \n"
-
-        "void oe_contour_vertex(inout vec4 VertexModel) \n"
-        "{ \n"
-        "    float height = oe_terrain_attr[3]; \n"
-        "    float height_normalized = (height-oe_contour_min)/oe_contour_range; \n"
-        "    oe_contour_lookup = clamp( height_normalized, 0.0, 1.0 ); \n"
-        "} \n";
-
-
-    const char* fs =
-        "#version " GLSL_VERSION_STR "\n"
-        GLSL_DEFAULT_PRECISION_FLOAT "\n"
-
-        "uniform sampler1D oe_contour_xfer; \n"
-        "uniform float oe_contour_opacity; \n"
-        "varying float oe_contour_lookup; \n"
-
-        "void oe_contour_fragment( inout vec4 color ) \n"
-        "{ \n"
-        "    vec4 texel = texture1D( oe_contour_xfer, oe_contour_lookup ); \n"
-        "    color.rgb = mix(color.rgb, texel.rgb, texel.a * oe_contour_opacity); \n"
-        "} \n";
-}
-
 
 ContourMap::ContourMap() :
 TerrainEffect()
@@ -144,7 +114,7 @@ ContourMap::onInstall(TerrainEngineNode* engine)
 {
     if ( engine )
     {
-        if ( !engine->getTextureCompositor()->reserveTextureImageUnit(_unit) )
+        if ( !engine->getResources()->reserveTextureImageUnit(_unit, "ContourMap") )
         {
             OE_WARN << LC << "Failed to reserve a texture image unit; disabled." << std::endl;
             return;
@@ -162,8 +132,9 @@ ContourMap::onInstall(TerrainEngineNode* engine)
         // before the terrain's layers.)
         VirtualProgram* vp = VirtualProgram::getOrCreate(stateset);
 
-        vp->setFunction( "oe_contour_vertex",   vs, ShaderComp::LOCATION_VERTEX_MODEL);
-        vp->setFunction( "oe_contour_fragment", fs, ShaderComp::LOCATION_FRAGMENT_COLORING ); //, -1.0);
+        Shaders pkg;
+        pkg.load(vp, pkg.ContourMap_Vertex);
+        pkg.load(vp, pkg.ContourMap_Fragment);
 
         // Install some uniforms that tell the shader the height range of the color map.
         stateset->addUniform( _xferMin.get() );
@@ -195,14 +166,15 @@ ContourMap::onUninstall(TerrainEngineNode* engine)
             VirtualProgram* vp = VirtualProgram::get(stateset);
             if ( vp )
             {
-                vp->removeShader( "oe_contour_vertex" );
-                vp->removeShader( "oe_contour_fragment" );
+                Shaders pkg;
+                pkg.unload(vp, pkg.ContourMap_Vertex);
+                pkg.unload(vp, pkg.ContourMap_Fragment);
             }
         }
 
         if ( _unit >= 0 )
         {
-            engine->getTextureCompositor()->releaseTextureImageUnit( _unit );
+            engine->getResources()->releaseTextureImageUnit( _unit );
             _unit = -1;
         }
     }
diff --git a/src/osgEarthUtil/ContourMap.frag.glsl b/src/osgEarthUtil/ContourMap.frag.glsl
new file mode 100644
index 0000000..43035cc
--- /dev/null
+++ b/src/osgEarthUtil/ContourMap.frag.glsl
@@ -0,0 +1,16 @@
+#version $GLSL_VERSION_STR
+$GLSL_DEFAULT_PRECISION_FLOAT
+
+#pragma vp_entryPoint "oe_contour_fragment"
+#pragma vp_location   "fragment_coloring"
+#pragma vp_order      "0.2"
+
+uniform sampler1D oe_contour_xfer;
+uniform float oe_contour_opacity;
+varying float oe_contour_lookup;
+
+void oe_contour_fragment( inout vec4 color )
+{
+    vec4 texel = texture1D( oe_contour_xfer, oe_contour_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
new file mode 100644
index 0000000..2ac4c2a
--- /dev/null
+++ b/src/osgEarthUtil/ContourMap.vert.glsl
@@ -0,0 +1,18 @@
+#version $GLSL_VERSION_STR
+$GLSL_DEFAULT_PRECISION_FLOAT
+
+#pragma vp_entryPoint "oe_contour_vertex"
+#pragma vp_location   "vertex_model"
+#pragma vp_order      "0.5"
+
+attribute vec4 oe_terrain_attr;
+uniform float oe_contour_min;
+uniform float oe_contour_range;
+varying float oe_contour_lookup;
+
+void oe_contour_vertex(inout vec4 VertexModel)
+{
+    float height = oe_terrain_attr[3];
+    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 24f5568..9afc1ec 100644
--- a/src/osgEarthUtil/Controls
+++ b/src/osgEarthUtil/Controls
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -224,6 +224,8 @@ namespace osgEarth { namespace Util { namespace Controls
         void setHorizAlign( const Alignment& value );
         const optional<Alignment>& horizAlign() const { return _halign; }
 
+        void setAlign(const Alignment& horiz, const Alignment& vert );
+
         void setHorizFill( bool value, float minWidth =0.0f );
         bool horizFill() const { return _hfill; }
 
@@ -264,8 +266,8 @@ namespace osgEarth { namespace Util { namespace Controls
         bool getAbsorbEvents() const { return _absorbEvents; }
 
         /** control opacity [0..1] */
-        void setOpacity(float value) { _alphaEffect->setAlpha(value); }
-        float getOpacity() const { return _alphaEffect->getAlpha(); }
+        void setOpacity(float value) { if ( _alphaEffect.valid() ) _alphaEffect->setAlpha(value); }
+        float getOpacity() const { return _alphaEffect.valid() ? _alphaEffect->getAlpha() : 1.0f; }
 
         void addEventHandler( ControlEventHandler* handler, bool fire =false );
 
@@ -909,7 +911,6 @@ namespace osgEarth { namespace Util { namespace Controls
         };
     };
 
-
 } } } // namespace osgEarth::Util::Controls
 
 #endif // OSGEARTHUTIL_CONTROLS
diff --git a/src/osgEarthUtil/Controls.cpp b/src/osgEarthUtil/Controls.cpp
index 37eb220..c6467bf 100755
--- a/src/osgEarthUtil/Controls.cpp
+++ b/src/osgEarthUtil/Controls.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -25,6 +25,7 @@
 #include <osgGA/GUIEventHandler>
 #include <osgText/Text>
 #include <osgUtil/RenderBin>
+#include <osgUtil/Statistics>
 #include <osgEarthSymbology/Style>
 #include <osgEarthSymbology/Geometry>
 #include <osgEarthSymbology/GeometryRasterizer>
@@ -190,8 +191,10 @@ Control::init()
 
     _geode = new osg::Geode();
     this->addChild( _geode );
-
+    
+#ifdef OSG_GLES2_AVAILABLE
     _alphaEffect = new AlphaEffect(this->getOrCreateStateSet());
+#endif
 }
 
 void
@@ -351,6 +354,12 @@ Control::setVertAlign( const Alignment& value ) {
 }
 
 void
+Control::setAlign(const Alignment& h, const Alignment& v) {
+    setHorizAlign( h );
+    setVertAlign ( v );
+}
+
+void
 Control::setHorizFill( bool hfill, float minWidth ) {
     if ( hfill != _hfill || !_width.isSetTo(minWidth) ) { //minWidth != _width.value() ) {
         _hfill = hfill;
@@ -1241,7 +1250,7 @@ HSliderControl::draw( const ControlContext& cx )
 {
     Control::draw( cx );
 
-    if ( visible() == true && parentIsVisible())
+    if ( visible() && parentIsVisible())
     {
         osg::ref_ptr<osg::Geometry> g = newGeometry();
 
@@ -1353,7 +1362,7 @@ CheckBoxControl::draw( const ControlContext& cx )
 {
     Control::draw( cx );
 
-    if ( visible() == true && parentIsVisible() )
+    if ( visible() && parentIsVisible() )
     {
         osg::Geometry* g = newGeometry();
 
@@ -1679,7 +1688,7 @@ VBox::clearControls()
 void
 VBox::calcSize(const ControlContext& cx, osg::Vec2f& out_size)
 {
-    if ( visible() == true )
+    if ( visible() )
     {
         _renderSize.set( 0, 0 );
 
@@ -1769,13 +1778,16 @@ VBox::calcPos(const ControlContext& cx, const osg::Vec2f& cursor, const osg::Vec
 void
 VBox::draw( const ControlContext& cx )
 {
-    Container::draw( cx );
-
-    for( unsigned i=1; i<getNumChildren(); ++i )
+    if ( visible() )
     {
-        Control* c = dynamic_cast<Control*>(getChild(i));
-        if ( c )
-            c->draw( cx );
+        Container::draw( cx );
+
+        for( unsigned i=1; i<getNumChildren(); ++i )
+        {
+            Control* c = dynamic_cast<Control*>(getChild(i));
+            if ( c )
+                c->draw( cx );
+        }
     }
 }
 
@@ -1811,7 +1823,7 @@ HBox::clearControls()
 void
 HBox::calcSize(const ControlContext& cx, osg::Vec2f& out_size)
 {
-    if ( visible() == true )
+    if ( visible() )
     {
         _renderSize.set( 0, 0 );
 
@@ -1900,13 +1912,16 @@ HBox::calcPos(const ControlContext& cx, const osg::Vec2f& cursor, const osg::Vec
 void
 HBox::draw( const ControlContext& cx )
 {
-    Container::draw( cx );
-
-    for( unsigned i=1; i<getNumChildren(); ++i )
+    if ( visible() )
     {
-        Control* c = dynamic_cast<Control*>(getChild(i));
-        if ( c )
-            c->draw( cx );
+        Container::draw( cx );
+
+        for( unsigned i=1; i<getNumChildren(); ++i )
+        {
+            Control* c = dynamic_cast<Control*>(getChild(i));
+            if ( c )
+                c->draw( cx );
+        }
     }
 }
 
@@ -2054,7 +2069,7 @@ Grid::clearControls()
 void
 Grid::calcSize( const ControlContext& cx, osg::Vec2f& out_size )
 {
-    if ( visible() == true )
+    if ( visible() )
     {
         _renderSize.set( 0, 0 );
 
@@ -2152,19 +2167,22 @@ Grid::calcPos( const ControlContext& cx, const osg::Vec2f& cursor, const osg::Ve
 void
 Grid::draw( const ControlContext& cx )
 {
-    Container::draw( cx );
-
-    for( unsigned i=1; i<getNumChildren(); ++i )
+    if ( visible() )
     {
-        osg::Group* rowGroup = getChild(i)->asGroup();
-        if ( rowGroup )
+        Container::draw( cx );
+
+        for( unsigned i=1; i<getNumChildren(); ++i )
         {
-            for( unsigned j=0; j<rowGroup->getNumChildren(); ++j )
+            osg::Group* rowGroup = getChild(i)->asGroup();
+            if ( rowGroup )
             {
-                Control* c = dynamic_cast<Control*>( rowGroup->getChild(j) );
-                if ( c )
+                for( unsigned j=0; j<rowGroup->getNumChildren(); ++j )
                 {
-                    c->draw( cx );
+                    Control* c = dynamic_cast<Control*>( rowGroup->getChild(j) );
+                    if ( c )
+                    {
+                        c->draw( cx );
+                    }
                 }
             }
         }
@@ -2197,11 +2215,6 @@ ControlCanvas::EventCallback::operator()(osg::Node* node, osg::NodeVisitor* nv)
     osg::ref_ptr<ControlCanvas> canvas;
     if ( _canvas.lock(canvas) )
     {
-        if ( _firstTime )
-        {
-            handleResize( ev->getActionAdapter()->asView(), canvas.get() );
-        }
-
         const osgGA::EventQueue::Events& events = ev->getEvents();
         if ( events.size() > 0 )
         {
@@ -2214,9 +2227,12 @@ ControlCanvas::EventCallback::operator()(osg::Node* node, osg::NodeVisitor* nv)
                 {
                     osgGA::GUIEventAdapter* ea = AS_ADAPTER(e->get());
 
-                    if (!_firstTime && ea->getEventType() == osgGA::GUIEventAdapter::RESIZE)
+                    // check for a resize each frame. Don't rely on the RESIZE event;
+                    // it does always convey the new viewport dimensions (they aren't
+                    // always available until the following FRAME event)
+                    if ( ea->getEventType() == ea->FRAME )
                     {
-                        handleResize( aa->asView(), canvas.get() );
+                        handleResize(aa->asView(), canvas.get());
                     }
 
                     if (canvas->handle( *ea, *aa ))
diff --git a/src/osgEarthUtil/DataScanner b/src/osgEarthUtil/DataScanner
index b02e18e..4133373 100644
--- a/src/osgEarthUtil/DataScanner
+++ b/src/osgEarthUtil/DataScanner
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/osgEarthUtil/DataScanner.cpp b/src/osgEarthUtil/DataScanner.cpp
index 2ed45cf..00cfbd9 100644
--- a/src/osgEarthUtil/DataScanner.cpp
+++ b/src/osgEarthUtil/DataScanner.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/osgEarthUtil/DateTime b/src/osgEarthUtil/DateTime
deleted file mode 100644
index b13576a..0000000
--- a/src/osgEarthUtil/DateTime
+++ /dev/null
@@ -1,34 +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 OSGEARTHUTIL_DATE_TIME_H
-#define OSGEARTHUTIL_DATE_TIME_H
-
-#if 0
-#include <osgEarthUtil/Common>
-#include <osgEarth/DateTime>
-namespace osgEarth { namespace Util
-{
-    // for backwards-compability.
-    // @deprecated - place use osgEarth::DateTime instead.
-    typedef osgEarth::DateTime DateTime;
-
-} } // namespace osgEarth::Util
-#endif
-
-#endif // OSGEARTHUTIL_DATE_TIME_H
diff --git a/src/osgEarthUtil/DateTime.cpp b/src/osgEarthUtil/DateTime.cpp
deleted file mode 100644
index de021d3..0000000
--- a/src/osgEarthUtil/DateTime.cpp
+++ /dev/null
@@ -1,85 +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 <osgEarthUtil/DateTime>
-#include <string.h>
-using namespace osgEarth::Util;
-
-DateTime::DateTime()
-{
-    ::time( &_time_t );
-    _tm = *::gmtime(&_time_t);
-}
-
-DateTime::DateTime(const ::time_t& utc)
-{
-    _time_t = utc;
-    _tm = *::gmtime( &utc );
-}
-
-DateTime::DateTime(const ::tm& tm)
-: _tm( tm )
-{
-    _time_t = ::mktime( &_tm );
-}
-
-DateTime::DateTime(int year, int month, int day, double hour)
-{
-    _tm.tm_year = year - 1900;
-    _tm.tm_mon  = month - 1;
-    _tm.tm_mday = day;
-
-    double hour_whole = ::floor(hour);
-    _tm.tm_hour = (int)hour_whole;
-    double frac = hour - (double)_tm.tm_hour;
-    double min = frac*60.0;
-    _tm.tm_min = (int)::floor(min);
-    frac = min - (double)_tm.tm_min;
-    _tm.tm_sec = (int)(frac*60.0);
-}
-
-DateTime::DateTime(const DateTime& rhs) :
-_tm    ( rhs._tm ),
-_time_t( rhs._time_t )
-{
-    //nop
-}
-
-int 
-DateTime::year() const 
-{ 
-    return _tm.tm_year + 1900;
-}
-
-int
-DateTime::month() const
-{
-    return _tm.tm_mon + 1;
-}
-
-int
-DateTime::day() const
-{
-    return _tm.tm_mday;
-}
-
-double
-DateTime::hours() const
-{
-    return (double)_tm.tm_hour + ((double)_tm.tm_min)/60. + ((double)_tm.tm_sec)/3600.;
-}
diff --git a/src/osgEarthUtil/DetailTexture b/src/osgEarthUtil/DetailTexture
deleted file mode 100644
index 128a779..0000000
--- a/src/osgEarthUtil/DetailTexture
+++ /dev/null
@@ -1,114 +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 OSGEARTHUTIL_DETAIL_TEXTURE_H
-#define OSGEARTHUTIL_DETAIL_TEXTURE_H
-
-#include <osgEarthUtil/Common>
-#include <osgEarth/TerrainEffect>
-#include <osgEarth/ImageLayer>
-#include <osgEarth/URI>
-#include <osg/Image>
-#include <osg/Uniform>
-#include <osg/Texture2DArray>
-
-using namespace osgEarth;
-
-namespace osgEarth {
-class Map;
-
-namespace Util
-{
-    /**
-     * Controller that applies a detail texture to the terrain.
-     */
-    class OSGEARTHUTIL_EXPORT DetailTexture : public TerrainEffect
-    {
-    public:
-        /** construct a new detail texture controller */
-        DetailTexture();
-
-        /** Sets the image layer that generates the normal map. 
-            You must call this prior to installing the effect. */
-        void setMaskLayer(ImageLayer* layer) { _maskLayer = layer; }
-        ImageLayer* getMaskLayer() { return _maskLayer.get(); }
-
-        /** Sets the LOD at which to start detailing */
-        void setStartLOD( unsigned lod );
-        unsigned getStartLOD() const { return _startLOD.get(); }
-
-        /** Sets the intensity (0=none, 1=full) */
-        void setIntensity( float value );
-        float getIntensity() const { return _intensity.get(); }
-
-        /** Sets the attenuation distance, i.e. the distance from the camera
-            at which the detail texture will fade out completely. */
-        void setAttenuationDistance( float value );
-        float getAttenuationDistance() const { return _attenuationDistance.get(); }
-
-        /** Sets the scale factor for the texture (1..) */
-        void setScale( float value );
-        float getScale() const { return _scale.get(); }
-
-        /** Sets the number of detail octaves */
-        void setNumOctaves( unsigned value );
-        float getNumOctaves() const { return _octaves.get(); }
-
-    public: // TerrainEffect interface
-
-        void onInstall(TerrainEngineNode* engine);
-        void onUninstall(TerrainEngineNode* engine);
-
-    public: // serialization
-
-        DetailTexture(const Config& conf, const Map* map);
-        void mergeConfig(const Config& conf);
-        virtual Config getConfig() const;
-
-    protected:
-        virtual ~DetailTexture() { }
-        void init();
-
-        optional<float>          _intensity;
-        optional<unsigned>       _startLOD;
-        optional<std::string>    _maskLayerName;
-        optional<float>          _scale;
-        optional<float>          _attenuationDistance;
-        optional<unsigned>       _octaves;
-
-        struct TextureSource {
-            std::string _tag;
-            std::string _url;
-        };
-        std::vector<TextureSource> _textures;
-
-        osg::ref_ptr<osg::Uniform>         _intensityUniform;
-        osg::ref_ptr<osg::Uniform>         _startLODUniform;
-        osg::ref_ptr<osg::Uniform>         _scaleUniform;
-        osg::ref_ptr<osg::Uniform>         _attenuationDistanceUniform;
-        osg::ref_ptr<osg::Uniform>         _samplerUniform;
-        osg::ref_ptr<osg::Uniform>         _maskUniform;
-        osg::ref_ptr<osg::Texture2DArray>  _texture;
-        int                                _unit;
-        osg::observer_ptr<ImageLayer>      _maskLayer;
-        osg::ref_ptr<osgDB::Options>       _dbOptions;
-    };
-
-} } // namespace osgEarth::Util
-
-#endif // OSGEARTHUTIL_DATA_SCANNER_H
diff --git a/src/osgEarthUtil/DetailTexture.cpp b/src/osgEarthUtil/DetailTexture.cpp
deleted file mode 100644
index 3517369..0000000
--- a/src/osgEarthUtil/DetailTexture.cpp
+++ /dev/null
@@ -1,478 +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 <osgEarthUtil/DetailTexture>
-#include <osgEarth/Registry>
-#include <osgEarth/Capabilities>
-#include <osgEarth/VirtualProgram>
-#include <osgEarth/TerrainEngineNode>
-#include <osgEarth/ImageUtils>
-#include <osgEarth/URI>
-
-#define LC "[DetailTexture] "
-
-using namespace osgEarth;
-using namespace osgEarth::Util;
-
-namespace
-{
-    const char* snoise =
-        "vec3 mod289(vec3 x) {\n"
-        "   return x - floor(x * (1.0 / 289.0)) * 289.0;\n"
-        "}\n"
-        "vec2 mod289(vec2 x) {\n"
-        "   return x - floor(x * (1.0 / 289.0)) * 289.0;\n"
-        "}\n"
-        "vec3 permute(vec3 x) {\n"
-        "   return mod289(((x*34.0)+1.0)*x);\n"
-        "}\n"
-        "float snoise(vec2 v)\n"
-        "{\n"
-        "   const vec4 C = vec4(0.211324865405187,  // (3.0-sqrt(3.0))/6.0 \n"
-        "                        0.366025403784439,  // 0.5*(sqrt(3.0)-1.0) \n"
-        "                       -0.577350269189626,  // -1.0 + 2.0 * C.x \n"
-        "                        0.024390243902439); // 1.0 / 41.0 \n"
-        "   // First corner\n"
-        "   vec2 i  = floor(v + dot(v, C.yy) );\n"
-        "   vec2 x0 = v -   i + dot(i, C.xx);\n"
-        "   // Other corners\n"
-        "   vec2 i1;\n"
-        "//i1.x = step( x0.y, x0.x ); // x0.x > x0.y ? 1.0 : 0.0\n"
-        "//i1.y = 1.0 - i1.x;\n"
-        "i1 = (x0.x > x0.y) ? vec2(1.0, 0.0) : vec2(0.0, 1.0);\n"
-        "// x0 = x0 - 0.0 + 0.0 * C.xx ;\n"
-        "// x1 = x0 - i1 + 1.0 * C.xx ;\n"
-        "// x2 = x0 - 1.0 + 2.0 * C.xx ;\n"
-        "vec4 x12 = x0.xyxy + C.xxzz;\n"
-        "x12.xy -= i1;\n"
-
-        "// Permutations\n"
-        "i = mod289(i); // Avoid truncation effects in permutation\n"
-        "vec3 p = permute( permute( i.y + vec3(0.0, i1.y, 1.0 ))\n"
-        "              + i.x + vec3(0.0, i1.x, 1.0 ));\n"
-
-        "vec3 m = max(0.5 - vec3(dot(x0,x0), dot(x12.xy,x12.xy), dot(x12.zw,x12.zw)), 0.0);\n"
-        "m = m*m ;\n"
-        "m = m*m ;\n"
-
-        "// Gradients: 41 points uniformly over a line, mapped onto a diamond.\n"
-        "// The ring size 17*17 = 289 is close to a multiple of 41 (41*7 = 287)\n"
-
-        "vec3 x = 2.0 * fract(p * C.www) - 1.0;\n"
-        "vec3 h = abs(x) - 0.5;\n"
-        "vec3 ox = floor(x + 0.5);\n"
-        "vec3 a0 = x - ox;\n"
-
-        "// Normalise gradients implicitly by scaling m\n"
-        "// Approximation of: m *= inversesqrt( a0*a0 + h*h );\n"
-        "m *= 1.79284291400159 - 0.85373472095314 * ( a0*a0 + h*h );\n"
-
-        "// Compute final noise value at P\n"
-        "vec3 g;\n"
-        "g.x  = a0.x  * x0.x  + h.x  * x0.y;\n"
-        "g.yz = a0.yz * x12.xz + h.yz * x12.yw;\n"
-        "return 130.0 * dot(m, g);\n"
-        "}\n";
-
-    const char* vs =
-        "#version " GLSL_VERSION_STR "\n"
-        GLSL_DEFAULT_PRECISION_FLOAT "\n"
-
-        "uniform vec4 oe_tile_key; \n"
-        "uniform float oe_detail_L0; \n"
-        "uniform float oe_detail_scale; \n"
-        "uniform float oe_detail_attenuation_distance; \n"
-
-        "varying vec4 oe_layer_tilec; \n"
-        "varying vec2 oe_detail_tc; \n"
-        "varying float oe_detail_atten_factor; \n"
-
-        "int oe_detail_ipow(in int x, in int y) { \n"
-        "   int r = 1; \n"
-        "   while( y > 0 ) { \n"
-        "       r *= x; \n"
-        "       --y; \n"
-        "   } \n"
-        "   return r; \n"
-        "}\n"
-
-        "void oe_detail_vertex(inout vec4 VertexVIEW) \n"
-        "{ \n"
-        "    float dL = oe_tile_key.z - oe_detail_L0; \n"
-        "    float twoPowDeltaL = float(oe_detail_ipow(2, int(abs(dL)))); \n"
-        "    float factor = dL >= 0.0 ? twoPowDeltaL : 1.0/twoPowDeltaL; \n"
-
-        "    vec2 a = floor(oe_tile_key.xy / factor); \n"
-        "    vec2 b = a * factor; \n"
-        "    vec2 c = (a+1.0) * factor; \n"
-        "    vec2 offset = (oe_tile_key.xy-b)/(c-b); \n"
-        "    vec2 scale = vec2(1.0/factor); \n"
-
-        "    float tscale = pow(2.0, oe_detail_scale-1.0); \n"
-        "    oe_detail_tc = tscale * ((oe_layer_tilec.st * scale) + offset); \n"
-        
-        "    float r = 1.0-((-VertexVIEW.z/VertexVIEW.w)/oe_detail_attenuation_distance);\n"
-        "    oe_detail_atten_factor = clamp(r, 0.0, 1.0); \n"
-        "} \n";
-
-
-    std::string generateFragmentShader(unsigned numTextures, unsigned numOctaves)
-    {
-        std::stringstream buf;
-
-        buf <<
-            "#version " GLSL_VERSION_STR "\n"
-            "#extension GL_EXT_texture_array : enable\n"
-            GLSL_DEFAULT_PRECISION_FLOAT "\n"
-
-            "uniform vec4 oe_tile_key; \n"
-            "uniform float oe_detail_L0; \n"
-            "uniform sampler2D oe_detail_mask; \n"
-            "uniform sampler2DArray oe_detail_tex; \n"
-            "uniform float oe_detail_intensity; \n"
-            "varying vec2 oe_detail_tc; \n"
-            "varying vec4 oe_layer_tilec; \n"
-            "varying float oe_detail_atten_factor; \n"
-
-            "float snoise(vec2 v);\n"
-
-            "void oe_detail_fragment(inout vec4 color) \n"
-            "{ \n"
-            "    if ( oe_tile_key.z >= oe_detail_L0 ) \n"
-            "    { \n"
-            //"        vec4 m = texture2D(oe_detail_mask, oe_layer_tilec.st); \n"
-            "        vec4 m = 0.25 * (\n"
-            //"                 texture2D(oe_detail_mask, oe_layer_tilec.st) + \n"
-            "                 texture2D(oe_detail_mask, oe_layer_tilec.st + vec2(-0.4,0.0)) + \n"
-            "                 texture2D(oe_detail_mask, oe_layer_tilec.st + vec2( 0.4,0.0)) + \n"
-            "                 texture2D(oe_detail_mask, oe_layer_tilec.st + vec2( 0.0,-0.4)) + \n"
-            "                 texture2D(oe_detail_mask, oe_layer_tilec.st + vec2( 0.0, 0.4))); \n"
-            "        vec4 detail = vec4(0.0); \n";
-
-#if 0
-        buf <<
-            "        float nz = snoise(oe_detail_tc.st); \n"
-            "        if ( nz < -0.5 ) m[0] = 1.0; else m[0] = 0.0; \n"
-            "        if ( nz >= -0.5 && nz < 0.0 ) m[1] = 1.0; else m[1] = 0.0;  \n"
-            "        if ( nz >= 0.0 && nz < 0.5 ) m[2] = 1.0; else m[2] = 0.0; \n"
-            "        if ( nz >= 0.5 ) m[3] = 1.0; else m[3] = 0.0; \n";
-#endif
-
-        if ( numOctaves > 1 )
-        {
-            buf <<
-                "        float weight[" << numOctaves << "];\n"
-                "        float interval = 1.0/" << numOctaves << ".0;\n"
-                "        float d, w;\n"
-                "        float total = 0.0;\n"
-                "        float a = oe_detail_atten_factor; \n"
-                "        a = a*a*a*a*a*a*a; \n"; // weights the transitions to be close to the eyepoint
-
-            for(unsigned oct=0; oct<numOctaves; ++oct)
-            {
-                buf <<
-                    "        d = abs( (interval+(interval*" << oct << ".0)) - a);\n"
-                    "        w = (1.0-d)*(1.0-d)*(1.0-d); \n"
-                    "        total += w*w; \n"
-                    "        weight[" << oct << "] = w;\n";
-            }
-                
-            buf <<
-                "        float sqrttotal = sqrt(total);\n";
-
-            for(unsigned oct=0; oct<numOctaves; ++oct)
-            {
-                buf <<
-                    "        w = weight["<<oct<<"] / sqrttotal; \n";
-
-                unsigned ofx = 1+(5*oct); // todo: make that octave jump configurable?
-                for(unsigned i=0; i<numTextures; ++i)
-                {
-                    buf <<
-                    "        detail += w * m[" << i << "] * texture2DArray(oe_detail_tex, vec3(oe_detail_tc*" << ofx << ".0," << i << ")); \n";
-                }
-            }
-        }
-        else
-        {
-            for(unsigned i=0; i<numTextures; ++i)
-            {
-                buf <<
-                    "        detail += m[" << i << "] * texture2DArray(oe_detail_tex, vec3(oe_detail_tc," << i << ")); \n";
-            }
-        }
-
-        buf <<
-            //"        vec3 luminosity = vec3(detail.r*0.2125 + detail.g*0.7154 + detail.b*0.0721);\n"
-            "        color = mix(color, detail, oe_detail_intensity * oe_detail_atten_factor);\n"
-            "    } \n"
-            "} \n";
-
-        std::string r;
-        r = buf.str();
-        return r;
-    }
-}
-
-
-DetailTexture::DetailTexture() :
-TerrainEffect(),
-_startLOD    ( 10 ),
-_intensity   ( 1.0f ),
-_scale       ( 1.0f ),
-_attenuationDistance( FLT_MAX ),
-_octaves     ( 1 )
-{
-    init();
-}
-
-DetailTexture::DetailTexture(const Config& conf, const Map* map) :
-TerrainEffect(),
-_startLOD    ( 10 ),
-_intensity   ( 1.0f ),
-_scale       ( 1.0f ),
-_attenuationDistance( FLT_MAX ),
-_octaves     ( 1 )
-{
-    mergeConfig(conf);
-
-    if ( map )
-    {
-        if ( _maskLayerName.isSet() )
-        {
-            _maskLayer = map->getImageLayerByName(*_maskLayerName);
-        }
-
-        _dbOptions = Registry::instance()->cloneOrCreateOptions(map->getDBOptions());        
-    }
-
-    init();
-}
-
-
-void
-DetailTexture::init()
-{
-    // negative means unset:
-    _unit = -1;
-
-    _startLODUniform   = new osg::Uniform(osg::Uniform::FLOAT, "oe_detail_L0");
-    _startLODUniform->set( (float)_startLOD.get() );
-
-    _intensityUniform = new osg::Uniform(osg::Uniform::FLOAT, "oe_detail_intensity");
-    _intensityUniform->set( _intensity.get() );
-
-    _scaleUniform = new osg::Uniform(osg::Uniform::FLOAT, "oe_detail_scale");
-    _scaleUniform->set( _scale.get() );
-
-    _attenuationDistanceUniform = new osg::Uniform(osg::Uniform::FLOAT, "oe_detail_attenuation_distance");
-    _attenuationDistanceUniform->set( _attenuationDistance.get() );
-}
-
-
-void
-DetailTexture::setStartLOD(unsigned lod)
-{
-    if ( lod != _startLOD.get() )
-    {
-        _startLOD = lod;
-        _startLODUniform->set( (float)_startLOD.get() );
-    }
-}
-
-
-void
-DetailTexture::setIntensity(float intensity)
-{
-    _intensity = osg::clampBetween( intensity, 0.0f, 1.0f );
-    _intensityUniform->set( _intensity.get() );
-}
-
-
-void
-DetailTexture::setScale(float scale)
-{
-    _scale = osg::clampAbove( scale, 1.0f );
-    _scaleUniform->set( _scale.get() );
-}
-
-
-void
-DetailTexture::setAttenuationDistance(float value)
-{
-    _attenuationDistance = osg::clampAbove(value, 1.0f);
-    _attenuationDistanceUniform->set( _attenuationDistance.get() );
-}
-
-void
-DetailTexture::onInstall(TerrainEngineNode* engine)
-{
-    if ( engine )
-    {
-        if ( !_texture.valid() )
-        {
-            _texture = new osg::Texture2DArray();
-            _texture->setTextureSize(1024, 1024, _textures.size());
-            _texture->setWrap( osg::Texture::WRAP_S, osg::Texture::REPEAT );
-            _texture->setWrap( osg::Texture::WRAP_T, osg::Texture::REPEAT );
-            _texture->setFilter( osg::Texture::MIN_FILTER, osg::Texture::LINEAR_MIPMAP_LINEAR );
-            _texture->setFilter( osg::Texture::MAG_FILTER, osg::Texture::LINEAR );
-            _texture->setResizeNonPowerOfTwoHint( false );
-
-            for(unsigned i=0; i<_textures.size(); ++i)
-            {
-                const TextureSource& ts = _textures[i];
-                osg::ref_ptr<osg::Image> image = URI(ts._url).getImage(_dbOptions.get());
-                if ( image->s() != 1024 || image->t() != 1024 )
-                {
-                    osg::ref_ptr<osg::Image> imageResized;
-                    ImageUtils::resizeImage( image.get(), 1024, 1024, imageResized );
-                    _texture->setImage( i, imageResized.get() );
-                }
-                else
-                {
-                    _texture->setImage( i, image.get() );
-                }
-            }
-        }
-
-        osg::StateSet* stateset = engine->getOrCreateStateSet();
-
-        if ( engine->getTextureCompositor()->reserveTextureImageUnit(_unit) )
-        {
-            _samplerUniform = stateset->getOrCreateUniform( "oe_detail_tex", osg::Uniform::SAMPLER_2D_ARRAY );
-            _samplerUniform->set( _unit );
-            stateset->setTextureAttribute( _unit, _texture.get(), osg::StateAttribute::ON ); // don't use "..andModes"
-        }
-
-        if ( _maskLayer.valid() )
-        {
-            int unit = *_maskLayer->shareImageUnit();
-            _maskUniform = stateset->getOrCreateUniform("oe_detail_mask", osg::Uniform::SAMPLER_2D);
-            _maskUniform->set(unit);
-            OE_NOTICE << LC << "Installed layer " << _maskLayer->getName() << " as texture mask on unit " << unit << std::endl;
-        }
-        else
-        {
-            exit(-1);
-        }
-
-        stateset->addUniform( _startLODUniform.get() );
-        stateset->addUniform( _intensityUniform.get() );
-        stateset->addUniform( _scaleUniform.get() );
-        stateset->addUniform( _attenuationDistanceUniform.get() );
-
-        std::string fs = generateFragmentShader( _textures.size(), _octaves.value() );
-        VirtualProgram* vp = VirtualProgram::getOrCreate(stateset);
-        vp->setFunction( "oe_detail_vertex",   vs, ShaderComp::LOCATION_VERTEX_VIEW );
-        vp->setFunction( "oe_detail_fragment", fs, ShaderComp::LOCATION_FRAGMENT_COLORING );
-
-        vp->setShader(
-            "simplexNoise",
-            new osg::Shader(osg::Shader::FRAGMENT, snoise) );
-    }
-}
-
-
-void
-DetailTexture::onUninstall(TerrainEngineNode* engine)
-{
-    osg::StateSet* stateset = engine->getStateSet();
-    if ( stateset )
-    {
-        stateset->removeUniform( _startLODUniform.get() );
-        stateset->removeUniform( _intensityUniform.get() );
-        stateset->removeUniform( _scaleUniform.get() );
-        stateset->removeUniform( _attenuationDistanceUniform.get() );
-
-        if ( _samplerUniform.valid() )
-        {
-            int unit;
-            _samplerUniform->get(unit);
-            stateset->removeUniform( _samplerUniform.get() );
-            stateset->removeTextureAttribute(unit, osg::StateAttribute::TEXTURE);
-        }
-
-        VirtualProgram* vp = VirtualProgram::get(stateset);
-        if ( vp )
-        {
-            vp->removeShader( "oe_detail_vertex" );
-            vp->removeShader( "oe_detail_fragment" );
-        }
-    }
-
-    if ( _unit >= 0 )
-    {
-        engine->getTextureCompositor()->releaseTextureImageUnit( _unit );
-        _unit = -1;
-    }
-}
-
-
-
-
-//-------------------------------------------------------------
-
-void
-DetailTexture::mergeConfig(const Config& conf)
-{
-    conf.getIfSet( "start_lod",  _startLOD );
-    conf.getIfSet( "intensity",  _intensity );
-    conf.getIfSet( "scale",      _scale );
-    conf.getIfSet( "attenuation_distance", _attenuationDistance );
-    conf.getIfSet( "mask_layer", _maskLayerName );
-    conf.getIfSet( "octaves",    _octaves );
-
-    ConfigSet textures = conf.child("textures").children("texture");
-    for(ConfigSet::iterator i = textures.begin(); i != textures.end(); ++i)
-    {
-        _textures.push_back(TextureSource());
-        _textures.back()._tag = i->value("tag");
-        _textures.back()._url = i->value("url");
-    }
-}
-
-Config
-DetailTexture::getConfig() const
-{
-    optional<std::string> layername;
-
-    if ( _maskLayer.valid() && !_maskLayer->getName().empty() )
-        layername = _maskLayer->getName();
-
-    Config conf("detail_texture");
-    conf.addIfSet( "start_lod",  _startLOD );
-    conf.addIfSet( "intensity",  _intensity );
-    conf.addIfSet( "scale",      _scale );
-    conf.addIfSet( "attenuation_distance", _attenuationDistance );
-    conf.addIfSet( "mask_layer", layername );
-    conf.addIfSet( "octaves",    _octaves );
-
-    if ( _textures.size() > 0 )
-    {
-        Config textures("textures");
-        for(std::vector<TextureSource>::const_iterator i = _textures.begin(); i != _textures.end(); ++i )
-        {
-            Config texture("texture");
-            texture.set("tag", i->_tag);
-            texture.set("url", i->_url);
-        }
-    }
-
-    return conf;
-}
diff --git a/src/osgEarthUtil/EarthManipulator b/src/osgEarthUtil/EarthManipulator
index 69ed8e4..a4748ce 100644
--- a/src/osgEarthUtil/EarthManipulator
+++ b/src/osgEarthUtil/EarthManipulator
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -25,14 +25,13 @@
 #include <osgEarth/GeoData>
 #include <osgEarth/Revisioning>
 #include <osgEarth/Terrain>
+#include <osgEarth/MapNode>
 #include <osg/Timer>
 #include <osgGA/CameraManipulator>
 #include <map>
 #include <list>
 #include <utility>
 
-#define USE_OBSERVER_NODE_PATH 1
-
 namespace osgEarth { namespace Util
 {
     using namespace osgEarth;
@@ -321,7 +320,7 @@ namespace osgEarth { namespace Util
              *
              * @param options
              *      Action options. Valid options are:
-             *      OPTION_SCALE_Y
+             *      OPTION_SCALE_Y, OPTION_DURATION
              */
             void bindScroll(
                 ActionType action, int scrolling_motion,
@@ -466,16 +465,10 @@ namespace osgEarth { namespace Util
             */
             void setMaxOffset(double max_x_offset, double max_y_offset);
 
-            /**
-            * Gets the TetherMode
-            */
+            /** Mode used for tethering to a node. */
+            void setTetherMode( TetherMode value ) { _tether_mode = value; }
             TetherMode getTetherMode() const { return _tether_mode; }
 
-            /**
-            * Sets the TetherMode
-            */
-            void setTetherMode( TetherMode tether_mode ) { _tether_mode = tether_mode; }
-
             /** Access to the list of Actions that will automatically break a tether */
             ActionTypeVector& getBreakTetherActions() { return _breakTetherActions; }
             const ActionTypeVector& getBreakTetherActions() const { return _breakTetherActions; }
@@ -494,17 +487,23 @@ namespace osgEarth { namespace Util
                 out_maxSeconds = _max_vp_duration_s;
             }
 
+#if 0 // removed. please set the matrix yourself.
             /** The camera projection matrix type */
             void setCameraProjection( const CameraProjection& value );
             const CameraProjection& getCameraProjection() const { return _camProjType; }
 
             /** Frustum offset (in pixels) */
             void setCameraFrustumOffsets( const osg::Vec2s& offsets );
-            const osg::Vec2s& getCameraFrustumOffsets() const { return _camFrustOffsets; }            
+            const osg::Vec2s& getCameraFrustumOffsets() const { return _camFrustOffsets; }     
+#endif       
 
-            /** Whether or not to disable collision avoidance when new data pages in */
-            bool getDisableCollisionAvoidance() const { return _disableCollisionAvoidance; }
-            void setDisableCollisionAvoidance( bool disableCollisionAvoidance ) { _disableCollisionAvoidance = disableCollisionAvoidance; }
+            /** Whether to automatically adjust an orthographic camera so that it "tracks" the last known FOV and Aspect Ratio. */
+            bool getOrthoTracksPerspective() const { return _orthoTracksPerspective; }
+            void setOrthoTracksPerspective(bool value) { _orthoTracksPerspective = value; }
+
+            /** Whether or not to keep the camera from going through the terrain surface */
+            bool getTerrainAvoidanceEnabled() const { return _terrainAvoidanceEnabled; }
+            void setTerrainAvoidanceEnabled( bool value ) { _terrainAvoidanceEnabled = value; }
 
             void setThrowingEnabled(bool throwingEnabled) { _throwingEnabled = throwingEnabled; }
             bool getThrowingEnabled () const { return _throwingEnabled; }
@@ -550,10 +549,9 @@ namespace osgEarth { namespace Util
             bool _auto_vp_duration;
             double _min_vp_duration_s, _max_vp_duration_s;
 
-            CameraProjection _camProjType;
-            osg::Vec2s _camFrustOffsets;	
+            bool _orthoTracksPerspective;
 
-            bool _disableCollisionAvoidance;
+            bool _terrainAvoidanceEnabled;
 
             bool _throwingEnabled;
             double _throwDecayRate;
@@ -584,9 +582,17 @@ namespace osgEarth { namespace Util
         virtual void setViewpoint( const Viewpoint& vp, double duration_s =0.0 );
         
         /**
+         * @deprecated Please use clearViewpoint() instead.
+         *
          * Cancels a viewpoint transition if one is in progress
          */
-        void cancelViewpointTransition() { _setting_viewpoint = false; }        
+        void cancelViewpointTransition();
+
+        /**
+         * Cancels a call to setViewpoint that resulted in an ongoing transition OR
+         * attachment to a node.
+         */
+        void clearViewpoint();
 
         /**
          * Sets the viewpoint to activate when performing the ACTION_HOME action.
@@ -594,11 +600,18 @@ namespace osgEarth { namespace Util
         void setHomeViewpoint( const Viewpoint& vp, double duration_s = 0.0 );
 
         /**
-         * Whether the manipulator is performing a viewpoint transition
+         * Whether the manipulator is performing a viewpoint transition.
          */
-        bool isSettingViewpoint() const { return _setting_viewpoint; }
+        bool isSettingViewpoint() const;
 
         /**
+         * Whether the view is tethered to a node.
+         */
+        bool isTethering() const;
+
+        /**
+         * @deprecated Please use setViewpoint() instead.
+         *
          * Locks the camera's focal point on the center of a node's bounding sphere.
          * While tethered, you can still call navigate or call setViewpoint() to move the
          * camera relative to the tether object. Pass NULL to deactivate the tether.
@@ -608,19 +621,45 @@ namespace osgEarth { namespace Util
          * @param duration_s
          *      The duration that the camera should transition when tethering to the node.
          */
-        void setTetherNode( osg::Node* node, double duration_s =0.0  );
+        void setTetherNode(osg::Node* node, double duration_s =0.0);
+        
+        /**
+         * @deprecated Please use setViewpoint() instead.
+         *
+         * Same as setTether(node, duration) but you also specify the final 
+         * heading, pitch, and range offset from the tethered node.
+         */
+        void setTetherNode(
+            osg::Node* nodeToTetherTo,
+            double     duration_seconds,
+            double     newHeading_degrees,
+            double     newPitch_degrees,
+            double     newRange_meters);
 
         /**
-         * Gets the node to which the camera is tethered, or NULL if tethering is
-         * disabled.
+         * @deprecated - Please use getViewpoint().getNode() instead.
          */
         osg::Node* getTetherNode() const;
 
         /**
-         * Gets the spatial reference system of the terrain map to which this
-         * manipulator is currently attached.
+         * @deprecated Please call clearViewpoint() instead.
+         *
+         * Same as calling setTetherNode(NULL).
+         */
+        void breakTether();
+
+        /**
+         * Sets a callback to be invoked upon a tether or tether break
          */
-        const osgEarth::SpatialReference* getSRS() const;
+        class TetherCallback : public osg::Referenced
+        {
+        public:
+            virtual void operator()(osg::Node* tetherNode) { }
+        protected:
+            virtual ~TetherCallback() { }
+        };
+        void setTetherCallback(TetherCallback* cb) { _tetherCallback = cb; }
+        TetherCallback* getTetherCallback() { return _tetherCallback.get(); }
 
         /**
          * Move the focal point of the camera using deltas (normalized screen coords).
@@ -688,7 +727,7 @@ namespace osgEarth { namespace Util
          */
         void  setFindNodeTraversalMask( const osg::Node::NodeMask & nodeMask ) { _findNodeTraversalMask = nodeMask; }
 
-    public: // osgGA::MatrixManipulator
+    public: // osgGA::CameraManipulator
 
         virtual const char* className() const { return "EarthManipulator"; }
         
@@ -734,11 +773,14 @@ namespace osgEarth { namespace Util
         // react to a tile-added event from the Terrain
         virtual void handleTileAdded(const TileKey& key, osg::Node* tile, TerrainCallbackContext& context);
 
+        // returns the absoulte Euler angles composited from the composite rotation matrix.
+        void getCompositeEulerAngles( double* out_azim, double* out_pitch =0L ) const;
+
     protected:
 
         virtual ~EarthManipulator();
         
-        bool intersect(const osg::Vec3d& start, const osg::Vec3d& end, osg::Vec3d& intersection) const;
+        bool intersect(const osg::Vec3d& start, const osg::Vec3d& end, osg::Vec3d& intersection, osg::Vec3d& normal) const;
 
         bool intersectLookVector(osg::Vec3d& eye, osg::Vec3d& out_target, osg::Vec3d& up) const;
 
@@ -791,8 +833,11 @@ namespace osgEarth { namespace Util
         // movements.
         bool serviceTask();
 
-        //void recalculateLocalPitchAndAzimuth();
-        void getLocalEulerAngles( double* out_azim, double* out_pitch =0L ) const;
+        // returns the Euler Angles baked into _rotation, the local frame's rotation quaternion.
+        void getEulerAngles(const osg::Quat& quat, double* azim, double* pitch) const;
+
+        // Makes a quaternion from an azimuth and pitch.
+        osg::Quat getQuaternion(double azim, double pitch) const;
 
         /**
          * Fire a ray from the current eyepoint along the current look vector,
@@ -804,10 +849,7 @@ namespace osgEarth { namespace Util
         void recalculateCenter(const osg::CoordinateFrame& frame);
 
         osg::Matrixd getRotation(const osg::Vec3d& center) const;
-        osg::Quat makeCenterRotation(const osg::Vec3d& center) const
-        {
-            return getRotation(center).getRotate().inverse();
-        }
+        osg::Quat computeCenterRotation(const osg::Vec3d& center) const;
 
         void updateTether();
 
@@ -845,12 +887,6 @@ namespace osgEarth { namespace Util
         typedef std::vector<TouchPoint> MultiTouchPoint; // one per ID (finger/touchpoint)
         typedef std::deque<MultiTouchPoint> MultiTouchPointQueue;
         MultiTouchPointQueue _touchPointQueue;
-        //struct TouchEvent {
-        //    EventType _eventType;     // derived touch/other event
-        //    float     _deltaDistance; // change in distance between 2 touches
-        //    float     _dx[2], _dy[2]; // movement of primary and secondary touch points
-        //    float     _dot;
-        //};
         struct TouchEvent {
             TouchEvent() : _mbmask(0) { }
             EventType _eventType;
@@ -888,17 +924,9 @@ namespace osgEarth { namespace Util
         osg::ref_ptr<const osgGA::GUIEventAdapter> _mouse_down_event;
 
         osg::observer_ptr<osg::Node> _node;
-        osg::observer_ptr<osg::CoordinateSystemNode> _csn;
-#ifdef USE_OBSERVER_NODE_PATH
-        osg::ObserverNodePath _csnObserverPath;
-#endif
-        osg::NodePath _csnPath;
-
-        osg::ref_ptr<const osgEarth::SpatialReference> _cached_srs;
-        bool _is_geocentric;
-        bool _srs_lookup_failed;
+        osg::observer_ptr<MapNode>   _mapNode;
 
-        osg::observer_ptr<osg::Node> _tether_node;        
+        osg::ref_ptr<const osgEarth::SpatialReference> _srs;
 
         double                  _time_s_last_frame;
         double                  _time_s_now;        
@@ -917,17 +945,34 @@ namespace osgEarth { namespace Util
         // local2world matrix for the center point.
         osg::CoordinateFrame    _centerLocalToWorld;
 
+        // Rotation offset to _rotation when tethering.
+        osg::Quat               _tetherRotation;
+
+        // The initial offset applied to the tether rotation when orientation-tethering begins.
+        // This is usually just the inverse of the first-calculated _tetherRotation.
+        optional<osg::Quat>     _tetherRotationOffset;
+
         // The rotation (heading and pitch) of the camera in the
-        // earth-local frame.
+        // earth-local frame defined by _centerRotation.
         osg::Quat               _rotation;
 
         // The rotation that makes the camera look down on the focal
         // point on the earth. This is equivalent to a rotation by
         // latitude, longitude.
         osg::Quat               _centerRotation;
+
+        // distance from camera to center of rotation.
         double                  _distance;
-        double                  _offset_x;
-        double                  _offset_y;
+
+        // XYZ offsets of the focal point in the local tangent plane coordinate system
+        // of the focal point.
+        osg::Vec3d              _posOffset;
+
+        // XY offsets (left/right, down/up) of the focal point in the plane normal to
+        // the view heading.
+        osg::Vec2d              _viewOffset;
+
+
         osg::Vec3d              _previousUp;
         osg::ref_ptr<Task>      _task;
         osg::Timer_t            _time_last_frame;
@@ -942,20 +987,29 @@ namespace osgEarth { namespace Util
 
         // the "pending" viewpoint is only used to enable setting the
         // viewpoint before the frame loop starts
-        bool                    _has_pending_viewpoint;
-        Viewpoint               _pending_viewpoint;
-        double                  _pending_viewpoint_duration_s;
+        optional<Viewpoint>     _pendingViewpoint;
+        Duration                _pendingViewpointDuration;
+
+        optional<Viewpoint>     _setVP0;                    // Starting viewpoint
+        optional<Viewpoint>     _setVP1;                    // Final viewpoint
+        optional<Duration>      _setVPStartTime;            // Time of call to setViewpoint
+        Duration                _setVPDuration;             // Transition time for setViewpoint
+        double                  _setVPAccel, _setVPAccel2;  // Acceleration factors for setViewpoint
+        double                  _setVPArcHeight;            // Altitude arcing height for setViewpoint
+
+        osg::Quat               _tetherRotationVP0;         // saves _tetherRotation at the start of a transition
+        osg::Quat               _tetherRotationVP1;         // target _tetherRotation if not tethered
 
-        bool                    _setting_viewpoint;
-        Viewpoint               _start_viewpoint;
-        double                  _delta_heading, _delta_pitch, _delta_range, _arc_height;
-        osg::Vec3d              _delta_focal_point;
-        double                  _time_s_set_viewpoint;
-        double                  _set_viewpoint_duration_s;
-        double                  _set_viewpoint_accel;
-        double                  _set_viewpoint_accel_2;
+        TetherMode              _lastTetherMode;
 
-        unsigned                _frame_count;
+        // returns "t", the parametric coefficient of a timed transition. 1=finished.
+        double setViewpointFrame(double time_s);
+
+        void setLookAt(const osg::Vec3d& center, double azim, double pitch, double range, const osg::Vec3d& posoffset);
+        void resetLookAt();
+        void collapseTetherRotationIntoRotation();
+
+        unsigned _frameCount;
 
         osg::ref_ptr<Settings> _settings;		
 
@@ -977,8 +1031,12 @@ namespace osgEarth { namespace Util
         
         struct CameraPostUpdateCallback : public osg::NodeCallback {
             CameraPostUpdateCallback(EarthManipulator* m) : _m(m) { }
-            virtual void operator()(osg::Node* node, osg::NodeVisitor* nv) { _m->postUpdate(); traverse(node,nv); }
-            EarthManipulator* _m;
+            virtual void operator()(osg::Node* node, osg::NodeVisitor* nv) { 
+                osg::ref_ptr<EarthManipulator> m;
+                if ( _m.lock(m) ) m->postUpdate();
+                traverse(node,nv);
+            }
+            osg::observer_ptr<EarthManipulator> _m;
         };
         void postUpdate();
         friend struct CameraUpdateCallback;
@@ -995,6 +1053,10 @@ namespace osgEarth { namespace Util
 
         // Traversal mask used in established and dtor methods to find MapNode and CoordinateSystemNode
         osg::Node::NodeMask  _findNodeTraversalMask;
+
+        osg::ref_ptr<TetherCallback> _tetherCallback;
+
+        void collisionDetect();
     };
 
 } } // namespace osgEarth::Util
diff --git a/src/osgEarthUtil/EarthManipulator.cpp b/src/osgEarthUtil/EarthManipulator.cpp
index 44a3cba..215ee06 100644
--- a/src/osgEarthUtil/EarthManipulator.cpp
+++ b/src/osgEarthUtil/EarthManipulator.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -58,10 +58,72 @@ namespace
     accelerationInterp( double t, double a ) {
         return a == 0.0? t : a > 0.0? powFast( t, a ) : 1.0 - powFast(1.0-t, -a);
     }
+
+    // normalized linear intep
+    osg::Vec3d nlerp(const osg::Vec3d& a, const osg::Vec3d& b, double t) {
+        double am = a.length(), bm = b.length();
+        osg::Vec3d c = a*(1.0-t) + b*t;
+        c.normalize();
+        c *= (1.0-t)*am + t*bm;
+        return c;
+    }
+
+    // linear interp
+    osg::Vec3d lerp(const osg::Vec3d& a, const osg::Vec3d& b, double t) {
+        return a*(1.0-t) + b*t;
+    }
+
+    osg::Matrix computeLocalToWorld(osg::Node* node) {
+        osg::Matrix m;
+        if ( node ) {
+            osg::NodePathList nodePaths = node->getParentalNodePaths();
+            if ( nodePaths.size() > 0 ) {
+                m = osg::computeLocalToWorld( nodePaths[0] );
+            }
+            else {
+                osg::Transform* t = dynamic_cast<osg::Transform*>(node);
+                if ( t ) {
+                    t->computeLocalToWorldMatrix( m, 0L );
+                }
+            }
+        }
+        return m;
+    }
+
+    osg::Vec3d computeWorld(osg::Node* node) {
+        return node ? osg::Vec3d(0,0,0) * computeLocalToWorld(node) : osg::Vec3d(0,0,0);
+    }
+
+    double normalizeAzimRad( double input )
+    {
+        if(fabs(input) > 2*osg::PI)
+            input = fmod(input,2*osg::PI);
+        if( input < -osg::PI ) input += osg::PI*2.0;
+        if( input > osg::PI ) input -= osg::PI*2.0;
+        return input;
+    }
 }
 
 
 //------------------------------------------------------------------------
+namespace
+{
+    // Callback that notifies the manipulator whenever the terrain changes
+    // around its center point.
+    struct ManipTerrainCallback : public TerrainCallback
+    {
+        ManipTerrainCallback(EarthManipulator* manip) : _manip(manip) { }
+        void onTileAdded(const TileKey& key, osg::Node* tile, TerrainCallbackContext& context)
+        {
+            osg::ref_ptr<EarthManipulator> safe;
+            if ( _manip.lock(safe) )
+            {
+                safe->handleTileAdded(key, tile, context);
+            }            
+        }
+        osg::observer_ptr<EarthManipulator> _manip;
+    };
+}
 
 
 
@@ -174,20 +236,19 @@ _touch_sens                     ( 0.005 ),
 _keyboard_sens                  ( 1.0 ),
 _scroll_sens                    ( 1.0 ),
 _min_pitch                      ( -89.9 ),
-_max_pitch                      ( -4.0 ),
+_max_pitch                      ( -1.0 ),
 _max_x_offset                   ( 0.0 ),
 _max_y_offset                   ( 0.0 ),
-_min_distance                   ( 0.001 ),
+_min_distance                   ( 1.0 ),
 _max_distance                   ( DBL_MAX ),
 _tether_mode                    ( TETHER_CENTER ),
 _arc_viewpoints                 ( true ),
 _auto_vp_duration               ( false ),
 _min_vp_duration_s              ( 3.0 ),
 _max_vp_duration_s              ( 8.0 ),
-_camProjType                    ( PROJ_PERSPECTIVE ),
-_camFrustOffsets                ( 0, 0 ),
-_disableCollisionAvoidance      ( false ),
+_orthoTracksPerspective         ( true ),
 _throwingEnabled                ( false ),
+_terrainAvoidanceEnabled        ( true ),
 _throwDecayRate                 ( 0.05 )
 {
     //NOP
@@ -214,10 +275,9 @@ _arc_viewpoints( rhs._arc_viewpoints ),
 _auto_vp_duration( rhs._auto_vp_duration ),
 _min_vp_duration_s( rhs._min_vp_duration_s ),
 _max_vp_duration_s( rhs._max_vp_duration_s ),
-_camProjType( rhs._camProjType ),
-_camFrustOffsets( rhs._camFrustOffsets ),
+_orthoTracksPerspective( rhs._orthoTracksPerspective ),
 _breakTetherActions( rhs._breakTetherActions ),
-_disableCollisionAvoidance( rhs._disableCollisionAvoidance),
+_terrainAvoidanceEnabled( rhs._terrainAvoidanceEnabled ),
 _throwingEnabled( rhs._throwingEnabled ),
 _throwDecayRate( rhs._throwDecayRate )
 {
@@ -406,20 +466,6 @@ EarthManipulator::Settings::setAutoViewpointDurationLimits( double minSeconds, d
     dirty();
 }
 
-void
-EarthManipulator::Settings::setCameraProjection(const EarthManipulator::CameraProjection& value)
-{
-    _camProjType = value;
-    dirty();
-}
-
-void
-EarthManipulator::Settings::setCameraFrustumOffsets( const osg::Vec2s& value )
-{
-    _camFrustOffsets = value;
-    dirty();
-}
-
 /************************************************************************/
 
 
@@ -427,12 +473,13 @@ EarthManipulator::EarthManipulator() :
 osgGA::CameraManipulator(),
 _last_action           ( ACTION_NULL ),
 _last_event            ( EVENT_MOUSE_DOUBLE_CLICK ),
-_time_s_last_event     (0.0),
-_frame_count           ( 0 ),
+_time_s_last_event     ( 0.0 ),
+_frameCount            ( 0 ),
 _findNodeTraversalMask ( 0x01 )
 {
     reinitialize();
     configureDefaultSettings();
+    _lastTetherMode = _settings->getTetherMode();
 }
 
 EarthManipulator::EarthManipulator( const EarthManipulator& rhs ) :
@@ -440,7 +487,7 @@ osgGA::CameraManipulator( rhs ),
 _last_action            ( ACTION_NULL ),
 _last_event             ( EVENT_MOUSE_DOUBLE_CLICK ),
 _time_s_last_event      (0.0),
-_frame_count            ( 0 ),
+_frameCount             ( 0 ),
 _settings               ( new Settings(*rhs.getSettings()) ),
 _findNodeTraversalMask  ( rhs._findNodeTraversalMask )
 {
@@ -450,15 +497,10 @@ _findNodeTraversalMask  ( rhs._findNodeTraversalMask )
 
 EarthManipulator::~EarthManipulator()
 {
-    osg::ref_ptr<osg::Node> safeNode = _node.get();
-    if (safeNode && _terrainCallback)
+    osg::ref_ptr<MapNode> mapNode = _mapNode;
+    if (mapNode.valid() && _terrainCallback)
     {
-        // find a map node.
-        MapNode* mapNode = MapNode::findMapNode( safeNode.get(), _findNodeTraversalMask );
-        if ( mapNode )
-        {             
-            mapNode->getTerrain()->removeTerrainCallback( _terrainCallback );
-        }
+        mapNode->getTerrain()->removeTerrainCallback( _terrainCallback );
     }    
 }
 
@@ -534,7 +576,7 @@ EarthManipulator::applySettings( Settings* settings )
 
     // apply new pitch restrictions
     double old_pitch;
-    getLocalEulerAngles( 0L, &old_pitch );
+    getEulerAngles( _rotation, 0L, &old_pitch );
 
     double new_pitch = osg::clampBetween( old_pitch, _settings->getMinPitch(), _settings->getMaxPitch() );
 
@@ -543,7 +585,8 @@ EarthManipulator::applySettings( Settings* settings )
     if ( new_pitch != old_pitch )
     {
         Viewpoint vp = getViewpoint();
-        setViewpoint( Viewpoint(vp.getFocalPoint(), vp.getHeading(), new_pitch, vp.getRange(), vp.getSRS()) );
+        vp.pitch() = new_pitch;
+        setViewpoint( vp );
     }
 }
 
@@ -557,8 +600,8 @@ void
 EarthManipulator::reinitialize()
 {
     _distance = 1.0;
-    _offset_x = 0.0;
-    _offset_y = 0.0;
+    _viewOffset.set(0,0);
+    _posOffset.set(0,0,0);
     _thrown = false;
     _dx = 0.0;
     _dy = 0.0;
@@ -567,115 +610,120 @@ EarthManipulator::reinitialize()
     _continuous = false;
     _task = new Task();
     _last_action = ACTION_NULL;
-    _srs_lookup_failed = false;
-    _setting_viewpoint = false;
+    _srs = 0L;
+    //_setting_viewpoint = false;
     _delta_t = 0.0;    
-    _has_pending_viewpoint = false;
+    _pendingViewpoint.unset();
+    _setVP0.unset();
+    _setVP1.unset();
     _lastPointOnEarth.set(0.0, 0.0, 0.0);
-    _arc_height = 0.0;
+    _setVPArcHeight = 0.0;
     _vfov = 30.0;
     _tanHalfVFOV = tan(0.5*osg::DegreesToRadians(_vfov));
 }
 
+
 bool
 EarthManipulator::established()
 {
-#ifdef USE_OBSERVER_NODE_PATH
-    bool needToReestablish = (!_csn.valid() || _csnObserverPath.empty()) && _node.valid();
-#else
-    bool needToReestablish = !_csn.valid() && _node.valid();
-#endif
-
-    if ( needToReestablish )
-    {
-        osg::ref_ptr<osg::Node> safeNode;
-        if ( !_node.lock(safeNode) )
-            return false;      
-
-        // find a CSN node - if there is one, we want to attach the manip to that
-        _csn = findRelativeNodeOfType<osg::CoordinateSystemNode>( safeNode.get(), _findNodeTraversalMask );
-
-        if ( _csn.valid() )
-        {
-            _node = _csn.get();
+    if ( _srs.valid() && _mapNode.valid() && _node.valid() )
+        return true;
 
-#if USE_OBSERVER_NODE_PATH
-            _csnObserverPath.setNodePathTo( _csn.get() );
-#endif
+    // lock down the observed node:
+    osg::ref_ptr<osg::Node> safeNode;
+    if ( !_node.lock(safeNode) )
+        return false;
 
-            if ( !_homeViewpoint.isSet() )
-            {
-                if ( _has_pending_viewpoint )
-                {
-                    setHomeViewpoint(
-                        _pending_viewpoint,
-                        _pending_viewpoint_duration_s );
+    // find a map node or fail:
+    _mapNode = osgEarth::MapNode::findMapNode( safeNode.get() );
+    if ( !_mapNode.valid() )
+        return false;
 
-                    _has_pending_viewpoint = false;
-                }
-                //If we have a CoordinateSystemNode and it has an ellipsoid model
-                else if ( _csn->getEllipsoidModel() )
-                {
-                    setHomeViewpoint( 
-                        Viewpoint(osg::Vec3d(-90,0,0), 0, -89,
-                        _csn->getEllipsoidModel()->getRadiusEquator()*3.0 ) );
-                }
-                else
-                {
-                    setHomeViewpoint( Viewpoint(
-                        safeNode->getBound().center(),
-                        0, -89.9, 
-                        safeNode->getBound().radius()*2.0) );
-                }
-            }
+    // resetablish the terrain callback on the map node:
+    if ( _terrainCallback.valid() )
+    {
+        _mapNode->getTerrain()->removeTerrainCallback( _terrainCallback.get() );
+    }
+    _terrainCallback = new ManipTerrainCallback( this );
+    _mapNode->getTerrain()->addTerrainCallback( _terrainCallback ); 
 
-            if ( !_has_pending_viewpoint )
-                setViewpoint( _homeViewpoint.get(), _homeViewpointDuration );
-            else
-                setViewpoint( _pending_viewpoint, _pending_viewpoint_duration_s );
+    // Cache the SRS.
+    _srs = _mapNode->getMapSRS();
 
-            _has_pending_viewpoint = false;
+    // Set the home viewpoint if necessary.
+    if ( !_homeViewpoint.isSet() )
+    {
+        if ( _pendingViewpoint.isSet() )
+        {
+            setHomeViewpoint( _pendingViewpoint.get(), _pendingViewpointDuration.as(Units::SECONDS) );
         }
 
-        // reset the srs cache:
-        _cached_srs = NULL;
-        _srs_lookup_failed = false;
+        else if ( _srs->isGeographic() )
+        {
+            Viewpoint vp;
+            vp.focalPoint() = GeoPoint(_srs.get(), -90.0, 0, 0, ALTMODE_ABSOLUTE);
+            vp.heading()->set( 0.0, Units::DEGREES );
+            vp.pitch()->set( -89.0, Units::DEGREES );
+            vp.range()->set( _srs->getEllipsoid()->getRadiusEquator() * 3.0, Units::METERS );
+            vp.positionOffset()->set(0,0,0);
+            setHomeViewpoint( vp );
+        }
+        else 
+        {
+            Viewpoint vp;
+            vp.focalPoint() = GeoPoint(_srs.get(), safeNode->getBound().center(), ALTMODE_ABSOLUTE);
+            vp.heading()->set( 0.0, Units::DEGREES );
+            vp.pitch()->set( -89.0, Units::DEGREES );
+            vp.range()->set( safeNode->getBound().radius()*2.0, Units::METERS );
+            vp.positionOffset()->set(0,0,0);
+            setHomeViewpoint( vp );
+        }
+    }
 
-        OE_INFO << "[EarthManip] new CSN established." << std::endl;
+    if ( !_pendingViewpoint.isSet() )
+    {
+        setViewpoint( _homeViewpoint.get(), _homeViewpointDuration );
     }
+    else
+    {
+        setViewpoint( _pendingViewpoint.get(), _pendingViewpointDuration.as(Units::SECONDS) );
+    }
+
+    // Clear out any pending viewpoint.
+    _pendingViewpoint.unset();
 
-    return _csn.valid() && _node.valid();
+    return true;
 }
 
 
 void 
 EarthManipulator::handleTileAdded(const TileKey& key, osg::Node* tile, TerrainCallbackContext& context)
 {
-#if 0
-    // Only do collision avoidance if it's enabled, we're not tethering and we're not in the middle of setting a viewpoint.            
-    if (!getSettings()->getDisableCollisionAvoidance() &&
-        !getTetherNode() &&
+    // Only do collision avoidance if it's enabled, we're not tethering and
+    // we're not in the middle of setting a viewpoint.            
+    if (getSettings()->getTerrainAvoidanceEnabled() &&
+        !isTethering() &&
         !isSettingViewpoint() )
-    {                
+    {
         const GeoPoint& pt = centerMap();
         if ( key.getExtent().contains(pt.x(), pt.y()) )
         {
             recalculateCenterFromLookVector();
+            collisionDetect();
         }
     }
-#endif
 }
 
 bool
 EarthManipulator::createLocalCoordFrame( const osg::Vec3d& worldPos, osg::CoordinateFrame& out_frame ) const
 {
-    if ( _cached_srs.valid() )
+    if ( _srs.valid() )
     {
         osg::Vec3d mapPos;
-        _cached_srs->transformFromWorld( worldPos, mapPos ); 
-        _cached_srs->createLocalToWorld( mapPos, out_frame );
+        _srs->transformFromWorld( worldPos, mapPos ); 
+        _srs->createLocalToWorld( mapPos, out_frame );
     }
-    return _cached_srs.valid();
+    return _srs.valid();
 }
 
 
@@ -684,14 +732,14 @@ EarthManipulator::setCenter( const osg::Vec3d& worldPos )
 {
     _center = worldPos;
     createLocalCoordFrame( worldPos, _centerLocalToWorld );
-    if ( _cached_srs.valid() )
+    if ( _srs.valid() )
     {
-        _centerMap.fromWorld( _cached_srs.get(), worldPos );
+        _centerMap.fromWorld( _srs.get(), worldPos );
     }
 
     // cache the "last known" focal point height so we can use it as a
     // backup if necessary.
-    _centerHeight = _is_geocentric ? _center.length() : _center.z();
+    _centerHeight = _srs->isGeographic() ? _center.length() : _center.z();
 }
 
 
@@ -703,8 +751,9 @@ EarthManipulator::setNode(osg::Node* node)
     // OSG from overwriting the node after you have already set on manually.
     if ( node == 0L || !_node.valid() )
     {
-        _node = node;
-        _csn = 0L;
+        _node     = node;
+        _mapNode = 0L;
+        _srs     = 0L;
 
         if ( _viewCamera.valid() && _cameraUpdateCB.valid() )
         {
@@ -714,13 +763,7 @@ EarthManipulator::setNode(osg::Node* node)
 
         _viewCamera = 0L;
 
-#ifdef USE_OBSERVER_NODE_PATH
-        _csnObserverPath.clearNodePath();
-#endif
-        _csnPath.clear();
         reinitialize();
-
-        // this might be unnecessary..
         established();
     }
 }
@@ -731,60 +774,6 @@ EarthManipulator::getNode()
     return _node.get();
 }
 
-const osgEarth::SpatialReference*
-EarthManipulator::getSRS() const
-{
-    osg::ref_ptr<osg::Node> safeNode = _node.get();
-
-    if ( !_cached_srs.valid() && !_srs_lookup_failed && safeNode.valid() )
-    {
-        EarthManipulator* nonconst_this = const_cast<EarthManipulator*>(this);
-
-        nonconst_this->_is_geocentric = false;
-
-        // first try to find a map node:
-        osgEarth::MapNode* mapNode = osgEarth::MapNode::findMapNode( safeNode.get() );       
-        if ( mapNode )
-        {
-            nonconst_this->_cached_srs = mapNode->getMap()->getProfile()->getSRS();
-            nonconst_this->_is_geocentric = mapNode->isGeocentric();
-        }
-
-        // if that doesn't work, try gleaning info from a CSN:
-        if ( !_cached_srs.valid() )
-        {
-            osg::CoordinateSystemNode* csn = osgEarth::findTopMostNodeOfType<osg::CoordinateSystemNode>( safeNode.get() );
-            if ( csn )
-            {
-                nonconst_this->_cached_srs = osgEarth::SpatialReference::create( csn );
-                nonconst_this->_is_geocentric = csn->getEllipsoidModel() != NULL;
-            }
-        }
-
-        nonconst_this->_srs_lookup_failed = !_cached_srs.valid();
-
-        if ( _cached_srs.valid() )
-        {
-            OE_DEBUG << "[EarthManip] cached SRS: "
-                << _cached_srs->getName()
-                << ", geocentric=" << _is_geocentric
-                << std::endl;
-        }
-    }
-
-    return _cached_srs.get();
-}
-
-
-static double
-normalizeAzimRad( double input ) {
-    if(fabs(input) > 2*osg::PI)
-        input = fmod(input,2*osg::PI);
-    if( input < -osg::PI ) input += osg::PI*2.0;
-    if( input > osg::PI ) input -= osg::PI*2.0;
-    return input;
-}
-
 osg::Matrixd
 EarthManipulator::getRotation(const osg::Vec3d& point) const
 {
@@ -805,7 +794,6 @@ EarthManipulator::getRotation(const osg::Vec3d& point) const
     {
         //We are looking nearly straight down the up vector, so use the Y vector for world up instead
         worldUp = osg::Vec3d(0, 1, 0);
-        //OE_NOTICE << "using y vector victor" << std::endl;
     }
 
     side = lookVector ^ worldUp;
@@ -818,320 +806,516 @@ EarthManipulator::getRotation(const osg::Vec3d& point) const
     return osg::Matrixd::lookAt( point - (lookVector * offset), point, up);
 }
 
+osg::Quat
+EarthManipulator::computeCenterRotation(const osg::Vec3d& point) const
+{
+    return getRotation(point).getRotate().inverse();
+}
+
+
+Viewpoint
+EarthManipulator::getViewpoint() const
+{
+    Viewpoint vp;
+
+    // Tethering? Use the tether viewpoint.
+    if ( isTethering() && _setVP1.isSet() )
+    {
+        vp = _setVP1.get();
+    }
+
+    // Transitioning? Capture the last calculated intermediate position.
+    else if ( isSettingViewpoint() )
+    {
+        vp.focalPoint()->fromWorld( _srs.get(), _center );
+    }
+    
+    // If we are stationary:
+    else
+    {
+        vp.focalPoint()->fromWorld( _srs.get(), _center );
+    }
+
+    // Always update the local offsets.
+    double localAzim, localPitch;
+    getEulerAngles( _rotation, &localAzim, &localPitch );
+
+    vp.heading() = Angle(localAzim,  Units::RADIANS).to(Units::DEGREES);
+    vp.pitch()   = Angle(localPitch, Units::RADIANS).to(Units::DEGREES);
+    vp.range()->set( _distance, Units::METERS );
+
+    if ( _posOffset.x() != 0.0 || _posOffset.y() != 0.0 || _posOffset.z() != 0.0 )
+    {
+        vp.positionOffset()->set(_posOffset);
+    }
+
+    return vp;
+}
+
+void
+EarthManipulator::breakTether()
+{
+    // breakTether() is deprecated; add new code to clearViewpoint
+    clearViewpoint();
+}
+
 void
-EarthManipulator::setViewpoint( const Viewpoint& vp, double duration_s )
+EarthManipulator::setViewpoint(const Viewpoint& vp, double duration_seconds)
 {
-    if ( !established() ) 
+    // If the manip is not set up, save the viewpoint for later.
+    if ( !established() )
     {
-        _pending_viewpoint = vp;
-        _pending_viewpoint_duration_s = duration_s;
-        _has_pending_viewpoint = true;
+        _pendingViewpoint = vp;
+        _pendingViewpointDuration.set(duration_seconds, Units::SECONDS);
     }
 
-    else if ( duration_s > 0.0 )
+    else
     {
-        // xform viewpoint into map SRS
-        osg::Vec3d vpFocalPoint = vp.getFocalPoint();
-        if ( _cached_srs.valid() && vp.getSRS() && !_cached_srs->isEquivalentTo( vp.getSRS() ) )
-        {
-            vp.getSRS()->transform( vp.getFocalPoint(), _cached_srs.get(), vpFocalPoint );
-        }
+        // Save any existing tether node so we can properly invoke the callback.
+        osg::ref_ptr<osg::Node> oldEndNode;
+        if ( isTethering() && _tetherCallback.valid() )
+            _setVP1->getNode(oldEndNode);
 
-        _start_viewpoint = getViewpoint();
-        
-        _delta_heading = vp.getHeading() - _start_viewpoint.getHeading(); //TODO: adjust for crossing -180
-        _delta_pitch   = vp.getPitch() - _start_viewpoint.getPitch();
-        _delta_range   = vp.getRange() - _start_viewpoint.getRange();
-        _delta_focal_point = vpFocalPoint - _start_viewpoint.getFocalPoint(); // TODO: adjust for lon=180 crossing
+        // starting viewpoint; all fields will be set:
+        _setVP0 = getViewpoint();
 
-        while( _delta_heading > 180.0 ) _delta_heading -= 360.0;
-        while( _delta_heading < -180.0 ) _delta_heading += 360.0;
+        // ending viewpoint
+        _setVP1 = vp;
 
-        // adjust for geocentric date-line crossing
-        if ( _is_geocentric )
+        // If we're no longer going to be tethering, reset the tethering offset quat.
+        //if ( !_setVP1->nodeIsSet() )
         {
-            while( _delta_focal_point.x() > 180.0 ) _delta_focal_point.x() -= 360.0;
-            while( _delta_focal_point.x() < -180.0 ) _delta_focal_point.x() += 360.0;
+            //_tetherRotationOffset.unset();
+            //_tetherRotation = osg::Quat();
+            _tetherRotationVP0 = _tetherRotation;
+            _tetherRotationVP1 = osg::Quat();
         }
 
-        // calculate an acceleration factor based on the Z differential
-        double h0 = _start_viewpoint.getRange() * sin( osg::DegreesToRadians(-_start_viewpoint.getPitch()) );
-        double h1 = vp.getRange() * sin( osg::DegreesToRadians( -vp.getPitch() ) );
-        double dh = (h1 - h0);
+        // Fill in any missing end-point data with defaults matching the current camera setup.
+        // Then all fields are guaranteed to contain usable data during transition.
+        double defPitch, defAzim;
+        getEulerAngles( _rotation, &defAzim, &defPitch );
 
-        // calculate the total distance the focal point will travel and derive an arc height:
-        double de;
-        if ( _is_geocentric && (vp.getSRS() == 0L || vp.getSRS()->isGeographic()) )
-        {
-            osg::Vec3d startFP = _start_viewpoint.getFocalPoint();
-            double x0,y0,z0, x1,y1,z1;
-            _cached_srs->getEllipsoid()->convertLatLongHeightToXYZ(
-                osg::DegreesToRadians( _start_viewpoint.y() ), osg::DegreesToRadians( _start_viewpoint.x() ), 0.0, x0, y0, z0 );
-            _cached_srs->getEllipsoid()->convertLatLongHeightToXYZ(
-                osg::DegreesToRadians( vpFocalPoint.y() ), osg::DegreesToRadians( vpFocalPoint.x() ), 0.0, x1, y1, z1 );
-            de = (osg::Vec3d(x0,y0,z0) - osg::Vec3d(x1,y1,z1)).length();
-        }
-        else
+        if ( !_setVP1->heading().isSet() )
+            _setVP1->heading() = Angle(defAzim, Units::RADIANS);
+
+        if ( !_setVP1->pitch().isSet() )
+            _setVP1->pitch() = Angle(defPitch, Units::RADIANS);
+
+        if ( !_setVP1->range().isSet() )
+            _setVP1->range() = Distance(_distance, Units::METERS);
+
+        if ( !_setVP1->nodeIsSet() && !_setVP1->focalPoint().isSet() )
         {
-            de = _delta_focal_point.length();
+            osg::ref_ptr<osg::Node> safeNode;
+            if ( _setVP0->getNode( safeNode ) )
+                _setVP1->setNode( safeNode.get() );
+            else
+                _setVP1->focalPoint() = _setVP0->focalPoint().get();
         }
 
-        _arc_height = 0.0;
-        if ( _settings->getArcViewpointTransitions() )
-        {         
-            _arc_height = osg::maximum( de - fabs(dh), 0.0 );
-        }
+        _setVPDuration.set( std::max(duration_seconds, 0.0), Units::SECONDS );
 
-        // calculate acceleration coefficients
-        if ( _arc_height > 0.0 )
+        OE_DEBUG << LC << "setViewpoint:\n"
+            << "    from " << _setVP0->toString() << "\n"
+            << "    to   " << _setVP1->toString() << "\n";
+        
+        // access the new tether node if it exists:
+        osg::ref_ptr<osg::Node> endNode;
+        _setVP1->getNode(endNode);
+
+        // Timed transition, we need to calculate some things:
+        if ( duration_seconds > 0.0 )
         {
-            // if we're arcing, we need seperate coefficients for the up and down stages
-            double h_apex = 2.0*(h0+h1) + _arc_height;
-            double dh2_up = fabs(h_apex - h0)/100000.0;
-            _set_viewpoint_accel = log10( dh2_up );
-            double dh2_down = fabs(h_apex - h1)/100000.0;
-            _set_viewpoint_accel_2 = -log10( dh2_down );
+            // Start point is the current manipulator center:
+            osg::Vec3d startWorld;
+            osg::ref_ptr<osg::Node> startNode;
+            startWorld = _setVP0->getNode(startNode) ? computeWorld(startNode.get()) : _center;
+
+            _setVPStartTime.unset();
+
+            // End point is the world coordinates of the target viewpoint:
+            osg::Vec3d endWorld;
+            if ( endNode.valid() )
+                endWorld = computeWorld(endNode.get());
+            else
+                _setVP1->focalPoint()->transform( _srs.get() ).toWorld(endWorld);
+
+            // calculate an acceleration factor based on the Z differential.
+            _setVPArcHeight = 0.0;
+            double range0 = _setVP0->range()->as(Units::METERS);
+            double range1 = _setVP1->range()->as(Units::METERS);
+
+            double pitch0 = _setVP0->pitch()->as(Units::RADIANS);
+            double pitch1 = _setVP1->pitch()->as(Units::RADIANS);
+
+            double h0 = range0 * sin( -pitch0 );
+            double h1 = range1 * sin( -pitch1 );
+            double dh = (h1 - h0);
+
+            // calculate the total distance the focal point will travel and derive an arc height:
+            double de = (endWorld - startWorld).length();
+
+            // maximum height during viewpoint transition
+            if ( _settings->getArcViewpointTransitions() )
+            {         
+                _setVPArcHeight = osg::maximum( de - fabs(dh), 0.0 );
+            }
+
+            // calculate acceleration coefficients
+            if ( _setVPArcHeight > 0.0 )
+            {
+                // if we're arcing, we need seperate coefficients for the up and down stages
+                double h_apex = 2.0*(h0+h1) + _setVPArcHeight;
+                double dh2_up = fabs(h_apex - h0)/100000.0;
+                _setVPAccel = log10( dh2_up );
+                double dh2_down = fabs(h_apex - h1)/100000.0;
+                _setVPAccel2 = -log10( dh2_down );
+            }
+            else
+            {
+                // on arc => simple unidirectional acceleration:
+                double dh2 = (h1 - h0)/100000.0;
+                _setVPAccel = fabs(dh2) <= 1.0? 0.0 : dh2 > 0.0? log10( dh2 ) : -log10( -dh2 );
+                if ( fabs( _setVPAccel ) < 1.0 ) _setVPAccel = 0.0;
+            }
+
+            // Adjust the duration if necessary.
+            if ( _settings->getAutoViewpointDurationEnabled() )
+            {
+                double maxDistance = _srs->getEllipsoid()->getRadiusEquator();
+                double ratio = osg::clampBetween( de/maxDistance, 0.0, 1.0 );
+                ratio = accelerationInterp( ratio, -4.5 );
+                double minDur, maxDur;
+                _settings->getAutoViewpointDurationLimits( minDur, maxDur );
+                _setVPDuration.set( minDur + ratio*(maxDur-minDur), Units::SECONDS );
+            }
         }
+
         else
         {
-            // on arc => simple unidirectional acceleration:
-            double dh2 = (h1 - h0)/100000.0;
-            _set_viewpoint_accel = fabs(dh2) <= 1.0? 0.0 : dh2 > 0.0? log10( dh2 ) : -log10( -dh2 );
-            if ( fabs( _set_viewpoint_accel ) < 1.0 ) _set_viewpoint_accel = 0.0;
+            // Immediate transition? Just do it now.
+            _setVPStartTime->set( _time_s_now, Units::SECONDS );
+            setViewpointFrame( _time_s_now );
         }
-        
-        if ( _settings->getAutoViewpointDurationEnabled() )
+
+        // Fire a tether callback if required.
+        if ( _tetherCallback.valid() )
         {
-            double maxDistance = _cached_srs->getEllipsoid()->getRadiusEquator();
-            double ratio = osg::clampBetween( de/maxDistance, 0.0, 1.0 );
-            ratio = accelerationInterp( ratio, -4.5 );
-            double minDur, maxDur;
-            _settings->getAutoViewpointDurationLimits( minDur, maxDur );
-            duration_s = minDur + ratio*(maxDur-minDur);
+            // starting a tether to a NEW node:
+            if ( isTethering() && oldEndNode.get() != endNode.get() )
+                (*_tetherCallback)( endNode.get() );
+
+            // breaking a tether:
+            else if ( !isTethering() && oldEndNode.valid() )
+                (*_tetherCallback)( 0L );
         }
-        
-        // don't use _time_s_now; that's the time of the last event
-        _time_s_set_viewpoint = osg::Timer::instance()->time_s();
-        _set_viewpoint_duration_s = duration_s;
+    }
 
-        _setting_viewpoint = true;
-        
-        _thrown = false;
-        _task->_type = TASK_NONE;
+    // reset other global state flags.
+    _thrown      = false;
+    _task->_type = TASK_NONE;
+}
+
+// returns "t" [0..1], the interpolation coefficient.
+double
+EarthManipulator::setViewpointFrame(double time_s)
+{
+    if ( !_setVPStartTime.isSet() )
+    {
+        _setVPStartTime->set( time_s, Units::SECONDS );
+        return 0.0;
     }
     else
     {
-        osg::Vec3d new_center = vp.getFocalPoint();
+        // Start point is the current manipulator center:
+        osg::Vec3d startWorld;
+        osg::ref_ptr<osg::Node> startNode;
+        if ( _setVP0->getNode(startNode) )
+            startWorld = computeWorld(startNode);
+        else
+            _setVP0->focalPoint()->transform( _srs.get() ).toWorld(startWorld);
+
+        // End point is the world coordinates of the target viewpoint:
+        osg::Vec3d endWorld;
+        osg::ref_ptr<osg::Node> endNode;
+        if ( _setVP1->getNode(endNode) )
+            endWorld = computeWorld(endNode);
+        else
+            _setVP1->focalPoint()->transform( _srs.get() ).toWorld(endWorld);
 
-        // start by transforming the requested focal point into world coordinates:
-        if ( getSRS() )
+        // Remaining time is the full duration minus the time since initiation:
+        double elapsed = time_s - _setVPStartTime->as(Units::SECONDS);
+        double t = std::min(1.0, elapsed / _setVPDuration.as(Units::SECONDS));
+        
+        double tp = t;
+
+        if ( _setVPArcHeight > 0.0 )
         {
-            // resolve the VP's srs. If the VP's SRS is not specified, assume that it
-            // is either lat/long (if the map is geocentric) or X/Y (otherwise).
-            osg::ref_ptr<const SpatialReference> vp_srs = vp.getSRS()? vp.getSRS() :
-                _is_geocentric? getSRS()->getGeographicSRS() :
-                getSRS();
-
-    //TODO: streamline
-            if ( !getSRS()->isEquivalentTo( vp_srs.get() ) )
+            if ( tp <= 0.5 )
             {
-                osg::Vec3d local = new_center;
-                // reproject the focal point if necessary:
-                vp_srs->transform2D( new_center.x(), new_center.y(), getSRS(), local.x(), local.y() );
-                new_center = local;
+                double t2 = 2.0*tp;
+                tp = 0.5*t2;
             }
-
-            // convert to geocentric coords if necessary:
-            if ( _is_geocentric )
+            else
             {
-                osg::Vec3d geocentric;
-
-                getSRS()->getEllipsoid()->convertLatLongHeightToXYZ(
-                    osg::DegreesToRadians( new_center.y() ),
-                    osg::DegreesToRadians( new_center.x() ),
-                    new_center.z(),
-                    geocentric.x(), geocentric.y(), geocentric.z() );
-
-                new_center = geocentric;
+                double t2 = 2.0*(tp-0.5);
+                tp = 0.5+(0.5*t2);
             }
-        }
-
-        // now calculate the new rotation matrix based on the angles:
 
+            // the more smoothsteps you do, the more pronounced the fade-in/out effect        
+            tp = smoothStepInterp( tp );
+        }
+        else if ( t > 0.0 )
+        {
+            tp = smoothStepInterp( tp );
+        }
 
-        double new_pitch = osg::DegreesToRadians(
-            osg::clampBetween( vp.getPitch(), _settings->getMinPitch(), _settings->getMaxPitch() ) );
+        osg::Vec3d newCenter =
+            _srs->isGeographic() ? nlerp(startWorld, endWorld, tp) : lerp(startWorld, endWorld, tp);
+
+        // Calculate the delta-heading, and make sure we are going in the shortest direction:
+        Angle d_azim = _setVP1->heading().get() - _setVP0->heading().get();
+        if ( d_azim.as(Units::RADIANS) > osg::PI )
+            d_azim = d_azim - Angle(2.0*osg::PI, Units::RADIANS);
+        else if ( d_azim.as(Units::RADIANS) < -osg::PI )
+            d_azim = d_azim + Angle(2.0*osg::PI, Units::RADIANS);
+        double newAzim = _setVP0->heading()->as(Units::RADIANS) + tp*d_azim.as(Units::RADIANS);
+             
+        // Calculate the new pitch:
+        Angle d_pitch = _setVP1->pitch().get() - _setVP0->pitch().get();
+        double newPitch = _setVP0->pitch()->as(Units::RADIANS) + tp*d_pitch.as(Units::RADIANS);
+
+        // Calculate the new range:
+        Distance d_range = _setVP1->range().get() - _setVP0->range().get();
+        double newRange =
+            _setVP0->range()->as(Units::METERS) +
+            d_range.as(Units::METERS)*tp + sin(osg::PI*tp)*_setVPArcHeight;
+
+        // Calculate the offsets
+        osg::Vec3d offset0 = _setVP0->positionOffset().getOrUse(osg::Vec3d(0,0,0));
+        osg::Vec3d offset1 = _setVP1->positionOffset().getOrUse(osg::Vec3d(0,0,0));
+        osg::Vec3d newOffset = offset0 + (offset1-offset0)*tp;
+
+        // Activate.
+        setLookAt( newCenter, newAzim, newPitch, newRange, newOffset );
+
+        // interpolate tether rotation:
+        _tetherRotation.slerp(tp, _tetherRotationVP0, _tetherRotationVP1);
+
+        // At t=1 the transition is complete.
+        if ( t >= 1.0 )
+        {            
+            _setVP0.unset();
 
-        double new_azim = normalizeAzimRad( osg::DegreesToRadians( vp.getHeading() ) );
+            // If this was a transition into a tether, keep the endpoint around so we can
+            // continue tracking it.
+            if ( !isTethering() )
+            {
+                _setVP1.unset();
+            }
+        }
+    
+        return tp;
+    }
+}
 
-        setCenter( new_center );
-        setDistance( vp.getRange() );
+void
+EarthManipulator::setLookAt(const osg::Vec3d& center,
+                            double            azim,
+                            double            pitch,
+                            double            range,
+                            const osg::Vec3d& posOffset)
+{
+    setCenter( center );
+    setDistance( range );
 
-        _previousUp = getUpVector( _centerLocalToWorld );
+    _previousUp = getUpVector( _centerLocalToWorld );
+    _centerRotation = computeCenterRotation( center ); //getRotation( center ).getRotate().inverse();
 
-        _centerRotation = getRotation( new_center ).getRotate().inverse();
+    _posOffset = posOffset;
 
-        osg::Quat azim_q( new_azim, osg::Vec3d(0,0,1) );
-        osg::Quat pitch_q( -new_pitch -osg::PI_2, osg::Vec3d(1,0,0) );
+    azim = normalizeAzimRad( azim );
 
-        osg::Matrix new_rot = osg::Matrixd( azim_q * pitch_q );
+    pitch = osg::clampBetween(
+        pitch,
+        osg::DegreesToRadians(_settings->getMinPitch()),
+        osg::DegreesToRadians(_settings->getMaxPitch()) );
 
-        _rotation = osg::Matrixd::inverse(new_rot).getRotate();
-    }
+    _rotation = getQuaternion(azim, pitch);
 }
 
 void
-EarthManipulator::updateSetViewpoint()
+EarthManipulator::resetLookAt()
 {
-    double t = ( _time_s_now - _time_s_set_viewpoint ) / _set_viewpoint_duration_s;
-    double tp = t;
+    double pitch;
+    getEulerAngles( _rotation, 0L, &pitch );
 
-    if ( t >= 1.0 )
-    {
-        t = tp = 1.0;
-        _setting_viewpoint = false;
-    }
-    else if ( _arc_height > 0.0 )
-    {
-        if ( tp <= 0.5 )
-        {
-            double t2 = 2.0*tp;
-            t2 = accelerationInterp( t2, _set_viewpoint_accel );
-            tp = 0.5*t2;
-        }
-        else
-        {
-            double t2 = 2.0*(tp-0.5);
-            t2 = accelerationInterp( t2, _set_viewpoint_accel_2 );
-            tp = 0.5+(0.5*t2);
-        }
+    double maxPitch = osg::DegreesToRadians(-10.0);
+    if ( pitch > maxPitch )
+        rotate( 0.0, -(pitch-maxPitch) );
 
-        // the more smoothsteps you do, the more pronounced the fade-in/out effect        
-        tp = smoothStepInterp( tp );
-        tp = smoothStepInterp( tp );
-    }
-    else if ( t > 0.0 )
-    {
-        tp = accelerationInterp( tp, _set_viewpoint_accel );
-        tp = smoothStepInterp( tp );
-    }
+    osg::Vec3d eye = getMatrix().getTrans();
 
-    Viewpoint new_vp(
-        _start_viewpoint.getFocalPoint() + _delta_focal_point * tp,
-        _start_viewpoint.getHeading() + _delta_heading * tp,
-        _start_viewpoint.getPitch() + _delta_pitch * tp,
-        _start_viewpoint.getRange() + _delta_range * tp + (sin(osg::PI*tp)*_arc_height),
-        _start_viewpoint.getSRS() );
+    // calculate the center point in front of the eye. The reference frame here 
+    // is the view plane of the camera.
+    osg::Matrix m( _rotation * _centerRotation );
+    recalculateCenter( m );
 
-#if 0
-    OE_INFO
-        << "t=" << t 
-        << ", tp=" << tp
-        << ", tsv=" << _time_s_set_viewpoint
-        << ", now=" << _time_s_now
-        << ", accel=" << _set_viewpoint_accel
-        << ", accel2=" << _set_viewpoint_accel_2
-        << std::endl;
-#endif
+    double newDistance = (eye-_center).length();
+    setDistance( newDistance );
+
+    _posOffset.set(0,0,0);
+    _viewOffset.set(0,0);
 
-    setViewpoint( new_vp );
+    _tetherRotation = osg::Quat();
+    _tetherRotationVP0 = osg::Quat();
+    _tetherRotationVP1 = osg::Quat();
 }
 
+bool
+EarthManipulator::isSettingViewpoint() const
+{
+    return _setVP0.isSet() && _setVP1.isSet();
+}
 
-Viewpoint
-EarthManipulator::getViewpoint() const
+void
+EarthManipulator::cancelViewpointTransition()
 {
-    osg::Vec3d focal_point = _center;
+    // @deprecated function - please add new code to clearViewpoint() instead
+    clearViewpoint();
+}
 
-    if ( getSRS() && _is_geocentric )
-    {
-        // convert geocentric to lat/long:
-        getSRS()->getEllipsoid()->convertXYZToLatLongHeight(
-            _center.x(), _center.y(), _center.z(),
-            focal_point.y(), focal_point.x(), focal_point.z() );
+void
+EarthManipulator::clearViewpoint()
+{
+    bool breakingTether = isTethering();
 
-        focal_point.x() = osg::RadiansToDegrees( focal_point.x() );
-        focal_point.y() = osg::RadiansToDegrees( focal_point.y() );
-    }
+    // Cancel any ongoing transition or tethering:
+    _setVP0.unset();
+    _setVP1.unset();
 
-    double localAzim, localPitch;
-    getLocalEulerAngles( &localAzim, &localPitch );
+    // Restore the matrix values in a neutral state.
+    resetLookAt();
 
-    return Viewpoint(
-        focal_point,
-        osg::RadiansToDegrees( localAzim ),
-        osg::RadiansToDegrees( localPitch ),
-        _distance,
-        getSRS() );
+    // Fire the callback to indicate a tethering break.
+    if ( _tetherCallback.valid() && breakingTether )
+        (*_tetherCallback)( 0L );
 }
 
+bool
+EarthManipulator::isTethering() const
+{
+    // True if setViewpoint() was called and the viewpoint has a node.
+    return _setVP1.isSet() && _setVP1->nodeIsSet();
+}
 
 void
-EarthManipulator::setTetherNode( osg::Node* node, double duration_s )
+EarthManipulator::setTetherNode(osg::Node* node, double duration_s)
 {
-    if (_tether_node != node)
+    // @deprecated function - please don't add new code here.
+    if ( node )
     {
-        _offset_x = 0.0;
-        _offset_y = 0.0;
-
-        if ( node == 0L )
-        {
-            // rekajigger the distance, center, and pitch to legal non-tethered values:
-            double pitch;
-            getLocalEulerAngles(0L, &pitch);
+        Viewpoint vp;
+        vp.setNode( node );
+        setViewpoint( vp, duration_s );
+    }
 
-            double maxPitch = osg::DegreesToRadians(-10.0);
-            if ( pitch > maxPitch )
-                rotate( 0.0, -(pitch-maxPitch) );
+    else
+    {
+        clearViewpoint();
+    }
+}
 
-            osg::Vec3d eye = getMatrix().getTrans();
+void
+EarthManipulator::setTetherNode(osg::Node* node,
+                                double     duration_s,
+                                double     newHeadingDeg,
+                                double     newPitchDeg,
+                                double     newRangeM)
+{
+    // @deprecated function - please don't add new code here.
+    Viewpoint newVP;
+    newVP.setNode( node );
+    newVP.heading()->set( newHeadingDeg, Units::DEGREES );
+    newVP.pitch()->set( newPitchDeg, Units::DEGREES );
+    newVP.range()->set( newRangeM, Units::METERS );
 
-            // calculate the center point in front of the eye. The reference frame here 
-            // is the view plane of the camera.
-            osg::Matrix m( _rotation * _centerRotation );
-            recalculateCenter( m );
+    setViewpoint( newVP, duration_s );
 
-            double newDistance = (eye-_center).length();
-            setDistance( newDistance );
-        }
-    }    
+    OE_WARN << LC << "TODO: call the tether callback\n";
+}
 
-    _tether_node = node;
+osg::Node*
+EarthManipulator::getTetherNode() const
+{
+    if ( !isTethering() )
+        return 0L;
 
-    if (_tether_node.valid() && duration_s > 0.0)
-    {                
-        Viewpoint destVP = getTetherNodeViewpoint();
-        setViewpoint( destVP, duration_s );
-    }
+    osg::ref_ptr<osg::Node> node;
+    _setVP1->getNode(node);
+    return node.release();
 }
 
 
-osg::Node*
-EarthManipulator::getTetherNode() const
+void EarthManipulator::collisionDetect()
 {
-    return _tether_node.get();
+    if (!getSettings()->getTerrainAvoidanceEnabled() ||
+        !_srs.valid() )
+    {
+        return;
+    }
+
+    // The camera has changed, so make sure we aren't under the ground.
+
+    osg::Vec3d eye = getMatrix().getTrans();
+    osg::CoordinateFrame eyeCoordFrame;
+    createLocalCoordFrame( eye, eyeCoordFrame );
+    osg::Vec3d eyeUp = getUpVector(eyeCoordFrame);
+
+    // Try to intersect the terrain with a vector going straight up and down.
+    double r = std::min( _srs->getEllipsoid()->getRadiusEquator(), _srs->getEllipsoid()->getRadiusPolar() );
+    osg::Vec3d ip, normal;
+    if (intersect(eye + eyeUp * r, eye - eyeUp * r, ip, normal))
+    {
+        double eps = _settings->getMinDistance();
+        // Now determine if the point is above the ground or not
+        osg::Vec3d v0 = eyeUp;
+        v0.normalize();
+        osg::Vec3d v1 = eye - (ip + eyeUp * eps);
+        v1.normalize();
+
+        //osg::Vec3d adjVector = normal;
+        osg::Vec3d adjVector = eyeUp;
+        if (v0 * v1 <= 0 )
+        {
+            setByLookAtRaw(ip + adjVector * eps, _center, eyeUp);
+        }
+    }
+
 }
 
 
 bool
-EarthManipulator::intersect(const osg::Vec3d& start, const osg::Vec3d& end, osg::Vec3d& intersection) const
+EarthManipulator::intersect(const osg::Vec3d& start, const osg::Vec3d& end, osg::Vec3d& intersection, osg::Vec3d& normal) const
 {
-    osg::ref_ptr<osg::Node> safeNode = _node.get();
-    if ( safeNode.valid() )
+    osg::ref_ptr<MapNode> mapNode;
+    if ( _mapNode.lock(mapNode) )
     {
 		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);
 
-        safeNode->accept(iv);
+        //safeNode->accept(iv);
+        mapNode->getTerrainEngine()->accept(iv);
 
         if (lsi->containsIntersections())
         {
             intersection = lsi->getIntersections().begin()->getWorldIntersectPoint();
+            normal = lsi->getIntersections().begin()->getWorldIntersectNormal();
             return true;
         }
     }
@@ -1144,11 +1328,11 @@ EarthManipulator::intersectLookVector(osg::Vec3d& out_eye,
                                       osg::Vec3d& out_up ) const
 {
     bool success = false;
-
-    osg::ref_ptr<osg::Node> safeNode = _node.get();
-    if ( safeNode.valid() )
+    
+    osg::ref_ptr<MapNode> mapNode;
+    if ( _mapNode.lock(mapNode) )
     {
-        double R = _centerHeight; // = getSRS()->getEllipsoid()->getRadiusEquator();
+        double R = _centerHeight;
 
         getInverseMatrix().getLookAt(out_eye, out_target, out_up, 1.0);
         osg::Vec3d look = out_target-out_eye;
@@ -1161,12 +1345,12 @@ EarthManipulator::intersectLookVector(osg::Vec3d& out_eye,
         osgUtil::IntersectionVisitor iv(lsi.get());        
         iv.setTraversalMask(_intersectTraversalMask);
 
-        safeNode->accept(iv);
+        mapNode->getTerrainEngine()->accept(iv);
 
         if (lsi->containsIntersections())
         {
             out_target = lsi->getIntersections().begin()->getWorldIntersectPoint();
-            if ( !_is_geocentric || GeoMath::isPointVisible(out_eye, out_target, R) )
+            if ( !_srs->isGeographic() || GeoMath::isPointVisible(out_eye, out_target, R) )
             {
                 success = true;
             }
@@ -1175,7 +1359,7 @@ EarthManipulator::intersectLookVector(osg::Vec3d& out_eye,
         if ( !success )
         {
             // backup plan: intersect spheroid (if geocentric) or base plane (if projected)
-            if ( _is_geocentric )
+            if ( _srs->isGeographic() )
             {
                 osg::Vec3d i0, i1;
                 unsigned hits = GeoMath::interesectLineWithSphere(out_eye, out_eye+look*1e8, R, i0, i1);
@@ -1204,7 +1388,7 @@ EarthManipulator::intersectLookVector(osg::Vec3d& out_eye,
                 }
             }
 
-            else // !_is_geocentric
+            else // !_srs->isGeographic()
             {
                 osg::Vec3d i0;
                 osg::Plane zup(0, 0, 1, 0);
@@ -1309,12 +1493,12 @@ EarthManipulator::updateCamera( osg::Camera* eventCamera )
     // check to see if we need to install a new camera callback:
     if ( _viewCamera.valid() )
     {
-        if ( _tether_node.valid() && !_cameraUpdateCB.valid() )
+        if ( isTethering() && !_cameraUpdateCB.valid() )
         {
             _cameraUpdateCB = new CameraPostUpdateCallback(this);
             _viewCamera->addUpdateCallback( _cameraUpdateCB.get() );
         }
-        else if ( !_tether_node.valid() && _cameraUpdateCB.valid() )
+        else if ( !isTethering() && _cameraUpdateCB.valid() )
         {
             _viewCamera->removeUpdateCallback( _cameraUpdateCB.get() );
             _cameraUpdateCB = 0L;
@@ -1328,70 +1512,29 @@ EarthManipulator::updateCamera( osg::Camera* eventCamera )
         if ( vp )
         {
             const osg::Matrixd& proj = _viewCamera->getProjectionMatrix();
-            bool isOrtho = ( proj(3,3) == 1. ) && ( proj(2,3) == 0. ) && ( proj(1,3) == 0. ) && ( proj(0,3) == 0.);
-            CameraProjection type = _settings->getCameraProjection();
+            bool isOrtho = osg::equivalent(proj(3,3), 1.0);
 
-            if ( type == PROJ_PERSPECTIVE )
+            // For a perspective camera, remember the last known VFOV. We will need it if we 
+            // detect a switch to orthographic.
+            if ( !isOrtho )
             {
-                if ( isOrtho || settingsChanged )
+                double vfov, ar, zn, zf;
+                if (_viewCamera->getProjectionMatrixAsPerspective(vfov, ar, zn, zf))
                 {
-                    // need to switch from ortho to perspective
-                    if ( isOrtho )
-                        OE_INFO << LC << "Switching to PERSPECTIVE" << std::endl;
-
-                    const osg::Vec2s& p = _settings->getCameraFrustumOffsets();
-                    double px = 2.0*(((vp->width()/2)+p.x())/vp->width())-1.0;
-                    double py = 2.0*(((vp->height()/2)+p.y())/vp->height())-1.0;
-
-                    osg::Matrix projMatrix;
-
-                    // if we're in ortho, switch. If we are already in perspective, just
-                    // grab the active matrix so we can apply offsets to it.
-                    if ( isOrtho ) 
-                        projMatrix.makePerspective(_vfov, vp->width()/vp->height(), 1.0f, 10000.0f);
-                    else
-                        projMatrix = proj;
-
-                    projMatrix.postMult( osg::Matrix::translate(px, py, 0.0) );
-
-                    _viewCamera->setProjectionMatrix( projMatrix );
-
-                    if ( _savedCNFMode.isSet() )
-                    {
-                        _viewCamera->setComputeNearFarMode( *_savedCNFMode );
-                        _savedCNFMode.unset();
-                    }
+                    _vfov = vfov;
                 }
             }
-            else if ( type == PROJ_ORTHOGRAPHIC )
-            {
-                if ( !isOrtho )
-                {
-                    // need to switch from perspective to ortho, so cache the VFOV of the perspective
-                    // camera -- we'll need it in ortho mode to create a proper frustum.
-                    OE_INFO << LC << "Switching to ORTHO" << std::endl;
-
-                    double ar, zn, zf; // not used
-                    _viewCamera->getProjectionMatrixAsPerspective(_vfov, ar, zn, zf);
-                    _tanHalfVFOV = tan(0.5*(double)osg::DegreesToRadians(_vfov));
-                    _savedCNFMode = _viewCamera->getComputeNearFarMode();
-                    _viewCamera->setComputeNearFarMode( osg::CullSettings::DO_NOT_COMPUTE_NEAR_FAR );
-                }
-                
-                double pitch;
-                getLocalEulerAngles(0L, &pitch);
 
+            // For an orthographic camera, convert the distance and remembered VFOV
+            // into proper x/y extents to simulate "zoom".
+            else if ( _settings->getOrthoTracksPerspective() )
+            {
                 // need to update the ortho projection matrix to reflect the camera distance.
                 double ar = vp->width()/vp->height();
-                double y = _distance * _tanHalfVFOV;
+                double y = _distance * tan(0.5*osg::DegreesToRadians(_vfov));
                 double x = y * ar;
-                double f = std::max(x,y);
-                double znear = -f * 5.0;
-                double zfar  =  f * (5.0 + 10.0 * sin(pitch+osg::PI_2));
-
-                // assemble the projection matrix:
-                osg::Matrixd orthoMatrix;
 
+#if 0 // TODO: derive the pixel offsets and re-instate them.
                 // apply the offsets:
                 double px = 0.0, py = 0.0;
                 const osg::Vec2s& p = _settings->getCameraFrustumOffsets();
@@ -1401,7 +1544,20 @@ EarthManipulator::updateCamera( osg::Camera* eventCamera )
                     py = (2.0*y*(double)-p.y()) / (double)vp->height();
                 }
 
-                _viewCamera->setProjectionMatrixAsOrtho( px-x, px+x, py-y, py+y, znear, zfar );
+                double ignore, N, F;
+                proj.getOrtho(ignore, ignore, ignore, ignore, N, F);
+                _viewCamera->setProjectionMatrixAsOrtho( px-x, px+x, py-y, py+y, N, F);
+#else
+                double ignore, N, F;
+                proj.getOrtho(ignore, ignore, ignore, ignore, N, F);
+                _viewCamera->setProjectionMatrixAsOrtho( -x, +x, -y, +y, N, F );
+#endif
+
+                //OE_WARN << "ORTHO: "
+                //    << "ar = " << ar << ", width=" << vp->width() << ", height=" << vp->height()
+                //    << ", dist = " << _distance << ", vfov=" << _vfov
+                //    << ", X = " << x << ", Y = " << y
+                //    << std::endl;
             }
         }
 
@@ -1431,21 +1587,24 @@ EarthManipulator::handle(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapt
         _time_s_now = time_s_now;
         _delta_t = _time_s_now - _time_s_last_frame;
         
-        if ( _has_pending_viewpoint && _node.valid() )
-        {
-            _has_pending_viewpoint = false;
-            setViewpoint( _pending_viewpoint, _pending_viewpoint_duration_s );
-            aa.requestRedraw();
-        }
-
-        else if ( _setting_viewpoint && _node.valid() )
+        if ( _node.valid() )
         {
-            if ( _frame_count < 2 )
-                _time_s_set_viewpoint = _time_s_now;
+            if ( _pendingViewpoint.isSet() )
+            {
+                setViewpoint( _pendingViewpoint.get(), _pendingViewpointDuration.as(Units::SECONDS) );
+                _pendingViewpoint.unset();
+                aa.requestRedraw();
+            }
 
-            updateSetViewpoint();
+            else if ( isSettingViewpoint() && !isTethering() )
+            {
+                if ( _frameCount < 2 )
+                    _setVPStartTime->set(_time_s_now, Units::SECONDS);
+            
+                setViewpointFrame( time_s_now );
+            }
 
-            aa.requestContinuousUpdate( _setting_viewpoint );
+            aa.requestContinuousUpdate( isSettingViewpoint() );
         }
 
         else if (_thrown)
@@ -1488,7 +1647,7 @@ EarthManipulator::handle(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapt
             }
         }
 
-        _frame_count++;
+        _frameCount++;
 
         return false;
     }
@@ -1503,11 +1662,10 @@ EarthManipulator::handle(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapt
    
     // form the current Action based on the event type:
     Action action = ACTION_NULL;
-    //_time_s_now = osg::Timer::instance()->time_s();
 
     // if tethering is active, check to see whether the incoming event 
     // will break the tether.
-    if ( _tether_node.valid() )
+    if ( isTethering() )
     {
         const ActionTypeVector& atv = _settings->getBreakTetherActions();
         if ( atv.size() > 0 )
@@ -1515,7 +1673,7 @@ EarthManipulator::handle(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapt
             const Action& action = _settings->getAction( ea.getEventType(), ea.getButtonMask(), ea.getModKeyMask() );
             if ( std::find(atv.begin(), atv.end(), action._type) != atv.end() )
             {
-                setTetherNode( 0L );
+                clearViewpoint();
             }
         }
     }
@@ -1682,7 +1840,7 @@ EarthManipulator::handle(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapt
                 resetMouse( aa );
                 addMouseEvent( ea );
                 action = _settings->getAction( ea.getEventType(), ea.getScrollingMotion(), ea.getModKeyMask() );
-                if ( handleScrollAction( action, 0.2 ) )
+                if ( handleScrollAction( action, action.getDoubleOption(OPTION_DURATION, 0.2) ) )
                     aa.requestRedraw();
                 handled = true;
                 break;
@@ -1711,91 +1869,171 @@ EarthManipulator::postUpdate()
     updateTether();
 }
 
+namespace
+{
+    /// Helper class for generating NodePathList.
+    class CollectAllParentPaths : public osg::NodeVisitor
+    {
+    public:
+        CollectAllParentPaths(const osg::Node* haltTraversalAtNode=0) :
+            osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_PARENTS),
+            _haltTraversalAtNode(haltTraversalAtNode)
+        {
+            // This is the same as the osg::CollectParentPaths visitor (which is not exported)
+            // except for this node mask override to ensure that it will even traverse nodes hidden via  node mask.
+            setNodeMaskOverride(~0);
+        }
+
+        virtual void apply(osg::Node& node)
+        {
+            if (node.getNumParents()==0 || &node==_haltTraversalAtNode)
+            {
+                _nodePaths.push_back(getNodePath());
+            }
+            else
+            {
+                traverse(node);
+            }
+       }
+
+        const osg::Node*     _haltTraversalAtNode;
+        osg::NodePath        _nodePath;
+        osg::NodePathList    _nodePaths;
+    };
+}
+
+osg::NodePathList getAllParentalNodePaths(osg::Node* node, osg::Node* haltTraversalAtNode = 0)
+{
+    CollectAllParentPaths cpp(haltTraversalAtNode);
+    node->accept(cpp);
+    return cpp._nodePaths;
+}
+
 void
 EarthManipulator::updateTether()
 {
-    if (!_setting_viewpoint)
-    {        
-        osg::ref_ptr<osg::Node> tether_node;
-        if ( _tether_node.lock(tether_node) )
-        {            
-            osg::Matrix localToWorld;
+    double t = 1.0;
 
-            osg::NodePathList nodePaths = tether_node->getParentalNodePaths();
-            if ( nodePaths.empty() )
-                return;
+    // If we are still setting the viewpoint, tick that now.
+    if ( isSettingViewpoint() )
+    {
+        t = setViewpointFrame( _time_s_now );
+    }
 
-            localToWorld = osg::computeLocalToWorld( nodePaths[0] );
-            if ( !localToWorld.valid() )
-                return;
+    // Initial transition is complete, so update the camera for tether.
+    osg::ref_ptr<osg::Node> node;
+    if ( _setVP1->getNode(node) )
+    {
+        // We use our getAllParentalNodePaths function instead of tether_node->getParentalNodePaths() so that we can
+        // ensure even nodes hidden via a node mask are traversed.
+        // Establish the reference frame for the target node. If this fails, bail out.
+        osg::Matrix L2W;
+        osg::NodePathList nodePaths = getAllParentalNodePaths(node.get());
+        if ( nodePaths.empty() )
+            return;
 
-            setCenter( osg::Vec3d(0,0,0) * localToWorld );
+        L2W = osg::computeLocalToWorld( nodePaths[0] );
+        if ( !L2W.valid() )
+            return;
 
+        // If we just called setViewpointFrame, no need to calculate the center again.
+        if ( !isSettingViewpoint() )
+        {
+            setCenter( osg::Vec3d(0,0,0) * L2W );
+            _centerRotation = computeCenterRotation(_center);
             _previousUp = getUpVector( _centerLocalToWorld );
+        };
 
-            double sx = 1.0/sqrt(localToWorld(0,0)*localToWorld(0,0) + localToWorld(1,0)*localToWorld(1,0) + localToWorld(2,0)*localToWorld(2,0));
-            double sy = 1.0/sqrt(localToWorld(0,1)*localToWorld(0,1) + localToWorld(1,1)*localToWorld(1,1) + localToWorld(2,1)*localToWorld(2,1));
-            double sz = 1.0/sqrt(localToWorld(0,2)*localToWorld(0,2) + localToWorld(1,2)*localToWorld(1,2) + localToWorld(2,2)*localToWorld(2,2));
-            localToWorld = localToWorld*osg::Matrixd::scale(sx,sy,sz);
+        osg::Quat newTetherRotation;
 
-            //Just track the center
-            if (_settings->getTetherMode() == TETHER_CENTER)
-            {
-                _centerRotation = _centerLocalToWorld.getRotate();
-            }
-            //Track all rotations
-            else if (_settings->getTetherMode() == TETHER_CENTER_AND_ROTATION)
+        bool tetherModeChanged = _settings->getTetherMode() != _lastTetherMode;
+        if ( tetherModeChanged )
+        {
+            _tetherRotationOffset.unset();
+        }
+
+        //Just track the center
+        if (_settings->getTetherMode() == TETHER_CENTER)
+        {
+            if ( tetherModeChanged )
             {
-                _centerRotation = localToWorld.getRotate();
+                resetLookAt();
+                //collapseTetherRotationIntoRotation();
             }
-            else if (_settings->getTetherMode() == TETHER_CENTER_AND_HEADING)
+            _tetherRotationOffset.unset();
+        }
+        else
+        {
+            // remove any scaling introduced by the model
+            double sx = 1.0/sqrt(L2W(0,0)*L2W(0,0) + L2W(1,0)*L2W(1,0) + L2W(2,0)*L2W(2,0));
+            double sy = 1.0/sqrt(L2W(0,1)*L2W(0,1) + L2W(1,1)*L2W(1,1) + L2W(2,1)*L2W(2,1));
+            double sz = 1.0/sqrt(L2W(0,2)*L2W(0,2) + L2W(1,2)*L2W(1,2) + L2W(2,2)*L2W(2,2));
+            L2W = L2W*osg::Matrixd::scale(sx,sy,sz);
+
+            if (_settings->getTetherMode() == TETHER_CENTER_AND_HEADING)
             {
-                //Track just the heading
-                osg::Matrixd localToFrame(localToWorld*osg::Matrixd::inverse( _centerLocalToWorld ));
+                // Back out the tetheree's rotation, then discard all but the heading component:
+                osg::Matrixd localToFrame(L2W*osg::Matrixd::inverse( _centerLocalToWorld ));
                 double azim = atan2(-localToFrame(0,1),localToFrame(0,0));
-                osg::Quat nodeRotationRelToFrame, rotationOfFrame;
-                nodeRotationRelToFrame.makeRotate(-azim,0.0,0.0,1.0);
-                rotationOfFrame = _centerLocalToWorld.getRotate();
-                _centerRotation = nodeRotationRelToFrame*rotationOfFrame;
+
+                newTetherRotation.makeRotate(-azim, 0.0, 0.0, 1.0);
+
+                newTetherRotation.slerp(t, _tetherRotationVP0, newTetherRotation);
+
+                //osg::Quat final;
+                //final.makeRotate(-azim, 0, 0, 1);
+                //newTetherRotation.slerp(t, osg::Quat(), final);
+            
+                // Recalculate rotation to compensate, making for a smooth transition:
+                if ( !_tetherRotationOffset.isSet() )
+                {
+                    //_tetherRotationOffset = newTetherRotation.inverse();
+                    //_tetherRotationOffset = osg::Quat();
+                    //_rotation = osg::Quat();
+                }
             }
+
+            // Track all rotations
+            else if (_settings->getTetherMode() == TETHER_CENTER_AND_ROTATION)
+            {
+                newTetherRotation = L2W.getRotate() * _centerRotation.inverse();
+
+                // Recalculate rotation to compensate, making for a smooth transition.
+                // In this case the new tether rotation might include roll so we need to
+                // extract that.
+#if 0
+                // TODO: doesn't work properly; for now the eye will "jump" when you activate this mode. 
+                if ( !_tetherRotationOffset.isSet() )
+                {
+                    double azim, pitch;
+                    getEulerAngles( newTetherRotation, &azim, &pitch );
+                    _tetherRotationOffset = getQuaternion(azim, pitch).inverse();
+                }
+#endif
+            }   
         }
-    }
-    else
-    {
-        // Update the deltas since this is a moving node.
-        Viewpoint vp = getTetherNodeViewpoint();        
-        osg::Vec3d vpFocalPoint = vp.getFocalPoint();
-        if ( _cached_srs.valid() && vp.getSRS() && !_cached_srs->isEquivalentTo( vp.getSRS() ) )
-        {
-            vp.getSRS()->transform( vp.getFocalPoint(), _cached_srs.get(), vpFocalPoint );
-        }
-        _delta_focal_point = vpFocalPoint - _start_viewpoint.getFocalPoint(); // TODO: adjust for lon=180 crossing
+
+        if ( _tetherRotationOffset.isSet() )
+            _tetherRotation = newTetherRotation * _tetherRotationOffset.get();
+        else
+            _tetherRotation = newTetherRotation;
+
+        _lastTetherMode = _settings->getTetherMode();
     }
 }
 
-Viewpoint EarthManipulator::getTetherNodeViewpoint() const
+Viewpoint
+EarthManipulator::getTetherNodeViewpoint() const
 {
-    osg::ref_ptr<osg::Node> tether_node;
-    if ( _tether_node.lock(tether_node) )
+    // @deprecated; please do not add new code here.
+    if ( isTethering() )
     {
-        osg::Matrix localToWorld;
-
-        osg::NodePathList nodePaths = tether_node->getParentalNodePaths();
-        if ( nodePaths.empty() )
-            return Viewpoint();
-
-        localToWorld = osg::computeLocalToWorld( nodePaths[0] );
-        if ( !localToWorld.valid() )
-            return Viewpoint();
-
-        // For now we just care about the center point of the tethered node.
-        osg::Vec3d centerWorld = osg::Vec3d(0,0,0) * localToWorld;
-        GeoPoint centerMap;
-        centerMap.fromWorld( _cached_srs.get(), centerWorld );
-        Viewpoint vp = getViewpoint();
-        return Viewpoint( centerMap.vec3d(), vp.getHeading(), vp.getPitch(), vp.getRange(), vp.getSRS() );        
-    }    
-    return Viewpoint();
+        return _setVP1.get();
+    }
+    else
+    {
+        return Viewpoint();
+    }
 }
 
 bool
@@ -1850,7 +2088,6 @@ EarthManipulator::isMouseMoving()
     float dx = _ga_t0->getXnormalized()-_ga_t1->getXnormalized();
     float dy = _ga_t0->getYnormalized()-_ga_t1->getYnormalized();
     float len = sqrtf(dx*dx+dy*dy);
-    //float dt = _ga_t0->getTime()-_ga_t1->getTime();
 
     return len > _delta_t * velocity;
 }
@@ -1945,12 +2182,13 @@ EarthManipulator::parseTouchEvents( TouchEvents& output )
                 float angle[2];
                 angle[0] = atan2(p0[0].y - p0[1].y, p0[0].x - p0[1].x);
                 angle[1] = atan2(p1[0].y - p1[1].y, p1[0].x - p1[1].x);
-                float da = angle[0] - angle[1];
+                float da = angle[1] - angle[0];
 
-                float dragThres = 2.0f * 0.0005 / sens;         
 
-                // now see if that corresponds to any touch events:
-                
+                // Threshold in pixels for determining if a two finger drag happened.
+                float dragThres = 1.0f;
+
+                // now see if that corresponds to any touch events:                
                 if (osg::equivalent( vec0.x(), vec1.x(), dragThres) && 
                     osg::equivalent( vec0.y(), vec1.y(), dragThres))
                 {                    
@@ -2011,7 +2249,7 @@ EarthManipulator::parseTouchEvents( TouchEvents& output )
                 ev._eventType = EVENT_MOUSE_DRAG;
                 ev._mbmask = osgGA::GUIEventAdapter::LEFT_MOUSE_BUTTON;
                 ev._dx =  (p1[0].x - p0[0].x) * sens;
-                ev._dy = -(p1[0].y - p0[0].y) * sens;
+                ev._dy =  (p1[0].y - p0[0].y) * sens;
             }
         }
     }
@@ -2022,10 +2260,13 @@ EarthManipulator::parseTouchEvents( TouchEvents& output )
 void
 EarthManipulator::setByMatrix(const osg::Matrixd& matrix)
 {
+    if (!established())
+        return;
+
     osg::Vec3d lookVector(- matrix(2,0),-matrix(2,1),-matrix(2,2));
     osg::Vec3d eye(matrix(3,0),matrix(3,1),matrix(3,2));
 
-    _centerRotation = makeCenterRotation(_center);
+    _centerRotation = computeCenterRotation(_center);
 
     osg::ref_ptr<osg::Node> safeNode = _node.get();
 
@@ -2043,12 +2284,12 @@ EarthManipulator::setByMatrix(const osg::Matrixd& matrix)
     osg::Vec3d start_segment = eye;
     osg::Vec3d end_segment = eye + lookVector*distance;
     
-    osg::Vec3d ip;
+    osg::Vec3d ip, normal;
     bool hitFound = false;
-    if (intersect(start_segment, end_segment, ip))
+    if (intersect(start_segment, end_segment, ip, normal))
     {
         setCenter( ip );
-        _centerRotation = makeCenterRotation(_center);
+        _centerRotation = computeCenterRotation(_center);
         setDistance( (eye-ip).length());
 
         osg::Matrixd rotation_matrix = osg::Matrixd::translate(0.0,0.0,-_distance)*
@@ -2065,10 +2306,10 @@ EarthManipulator::setByMatrix(const osg::Matrixd& matrix)
 
         osg::Vec3d eyeUp = getUpVector(eyeCoordFrame);
 
-        if (intersect(eye + eyeUp*distance, eye - eyeUp*distance, ip))
+        if (intersect(eye + eyeUp*distance, eye - eyeUp*distance, ip, normal))
         {
             setCenter( ip );
-            _centerRotation = makeCenterRotation(_center);
+            _centerRotation = computeCenterRotation(_center);
             setDistance((eye-ip).length());
             _rotation.set(0,0,0,1);
             hitFound = true;
@@ -2081,14 +2322,18 @@ EarthManipulator::setByMatrix(const osg::Matrixd& matrix)
 
     recalculateRoll();
     //recalculateLocalPitchAndAzimuth();
+
+    collisionDetect();
 }
 
 osg::Matrixd
 EarthManipulator::getMatrix() const
 {
-    return osg::Matrixd::translate(-_offset_x,-_offset_y,_distance)*
-           osg::Matrixd::rotate(_rotation)*
-           osg::Matrixd::rotate(_centerRotation)*
+    return osg::Matrixd::translate(_viewOffset.x(), _viewOffset.y(), _distance) *
+           osg::Matrixd::rotate   (_rotation) *
+           osg::Matrixd::rotate   (_tetherRotation) *
+           osg::Matrixd::translate(_posOffset) *
+           osg::Matrixd::rotate   (_centerRotation) *
            osg::Matrixd::translate(_center);
 }
 
@@ -2096,9 +2341,11 @@ osg::Matrixd
 EarthManipulator::getInverseMatrix() const
 {
     return osg::Matrixd::translate(-_center)*
-           osg::Matrixd::rotate(_centerRotation.inverse() ) *
-           osg::Matrixd::rotate(_rotation.inverse())*
-           osg::Matrixd::translate(_offset_x,_offset_y,-_distance);
+           osg::Matrixd::rotate   (_centerRotation.inverse()) *
+           osg::Matrixd::translate(-_posOffset) *
+           osg::Matrixd::rotate   (_tetherRotation.inverse()) *
+           osg::Matrixd::rotate   (_rotation.inverse()) *
+           osg::Matrixd::translate(-_viewOffset.x(), -_viewOffset.y(), -_distance);
 }
 
 void
@@ -2124,8 +2371,8 @@ EarthManipulator::setByLookAt(const osg::Vec3d& eye,const osg::Vec3d& center,con
         {
             // compute the intersection with the scene.
             
-            osg::Vec3d ip;
-            if (intersect(eye, endPoint, ip))
+            osg::Vec3d ip, normal;
+            if (intersect(eye, endPoint, ip, normal))
             {
                 setCenter( ip );
                 setDistance( (ip-eye).length() );
@@ -2139,11 +2386,14 @@ EarthManipulator::setByLookAt(const osg::Vec3d& eye,const osg::Vec3d& center,con
 
     osg::Matrixd rotation_matrix = osg::Matrixd::lookAt(eye,center,up);
 
-    _centerRotation = getRotation( _center ).getRotate().inverse();
+    _centerRotation = computeCenterRotation(_center);// getRotation( _center ).getRotate().inverse();
     _rotation = rotation_matrix.getRotate().inverse() * _centerRotation.inverse();	
     
     _previousUp = getUpVector(_centerLocalToWorld);
 
+    _posOffset.set(0,0,0);
+    _viewOffset.set(0,0);
+
     recalculateRoll();
 }
 
@@ -2156,7 +2406,7 @@ EarthManipulator::setByLookAtRaw(const osg::Vec3d& eye,const osg::Vec3d& center,
     setCenter( center );
 
     osg::Matrixd rotation_matrix = osg::Matrixd::lookAt(eye,center,up);
-    _centerRotation = getRotation(_center).getRotate().inverse();
+    _centerRotation = computeCenterRotation(_center); // getRotation(_center).getRotate().inverse();
     _rotation = rotation_matrix.getRotate().inverse() * _centerRotation.inverse();
     _previousUp = getUpVector(_centerLocalToWorld);
 
@@ -2196,9 +2446,10 @@ 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);
-        bool hit_ip2 = intersect(_center + up * ilen * 0.1, _center - up * ilen, ip2);
+        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)
         {
             if (hit_ip2)
@@ -2221,15 +2472,13 @@ EarthManipulator::recalculateCenter( const osg::CoordinateFrame& frame )
 void
 EarthManipulator::pan( double dx, double dy )
 {
-    if (!_tether_node.valid())
+    if ( !isTethering() )
     {
         // to pan, we need a focus point on the terrain:
         if ( !recalculateCenterFromLookVector() )
             return;
 
         double scale = -0.3f*_distance;
-        double old_azim;
-        getLocalEulerAngles( &old_azim );
 
         osg::Matrixd rotation_matrix;
         rotation_matrix.makeRotate( _rotation * _centerRotation  );
@@ -2252,15 +2501,27 @@ EarthManipulator::pan( double dx, double dy )
         // save the previous CF so we can do azimuth locking:
         osg::CoordinateFrame oldCenterLocalToWorld = _centerLocalToWorld;
 
-        // move the center point:
-        setCenter( _center + dv );
+        // move the center point
+        double len = _center.length();
+        osg::Vec3d newCenter = _center + dv;
 
-        // need to recompute the intersection point along the look vector.
-        osg::ref_ptr<osg::Node> safeNode;
-        if ( _node.lock(safeNode) )
+        if ( _srs->isGeographic() )
         {
-            //recalculateCenter( oldCenterLocalToWorld );
+            // in geocentric, ensure that it doesn't change length.
+            newCenter.normalize();
+            newCenter *= len;
+        }
+        setCenter( newCenter );
 
+        if ( _settings->getLockAzimuthWhilePanning() )
+        {
+            // in azimuth-lock mode, _centerRotation maintains a consistent north vector
+            _centerRotation = computeCenterRotation( _center );
+        }
+        
+        else
+        {
+            // otherwise, we need to rotate _centerRotation manually.
             osg::Vec3d new_localUp = getUpVector( _centerLocalToWorld );
 
             osg::Quat pan_rotation;
@@ -2271,26 +2532,24 @@ EarthManipulator::pan( double dx, double dy )
                 _centerRotation = _centerRotation * pan_rotation;
                 _previousUp = new_localUp;
             }
+
+#if 0
             else
             {
                 //OE_DEBUG<<"New up orientation nearly inline - no need to rotate"<<std::endl;
             }
 
-            if ( _settings->getLockAzimuthWhilePanning() )
-            {
-                double new_azim;
-                getLocalEulerAngles( &new_azim );
-
-                double delta_azim = new_azim - old_azim;
-                //OE_NOTICE << "DeltaAzim" << delta_azim << std::endl;
+            double new_azim;
+            getEulerAngles( _rotation, &new_azim, 0L );
+            double delta_azim = new_azim - old_azim;
 
-                osg::Quat q;
-                q.makeRotate( delta_azim, new_localUp );
-                if ( !q.zeroRotation() )
-                {
-                    _centerRotation = _centerRotation * q;
-                }
+            osg::Quat q;
+            q.makeRotate( delta_azim, new_localUp );
+            if ( !q.zeroRotation() )
+            {
+                _centerRotation = _centerRotation * q;
             }
+#endif
         }
 
         //recalculateLocalPitchAndAzimuth();
@@ -2298,26 +2557,25 @@ EarthManipulator::pan( double dx, double dy )
     else
     {
         double scale = _distance;
-        _offset_x += dx * scale;
-        _offset_y += dy * scale;
+
+        // Panning in tether mode changes the focal view offsets.
+        _viewOffset.x() -= dx * scale;
+        _viewOffset.y() -= dy * scale;
 
         //Clamp values within range
-        if (_offset_x < -_settings->getMaxXOffset()) _offset_x = -_settings->getMaxXOffset();
-        if (_offset_y < -_settings->getMaxYOffset()) _offset_y = -_settings->getMaxYOffset();
-        if (_offset_x > _settings->getMaxXOffset()) _offset_x = _settings->getMaxXOffset();
-        if (_offset_y > _settings->getMaxYOffset()) _offset_y = _settings->getMaxYOffset();
+        _viewOffset.x() = osg::clampBetween( _viewOffset.x(), -_settings->getMaxXOffset(), _settings->getMaxXOffset() );
+        _viewOffset.y() = osg::clampBetween( _viewOffset.y(), -_settings->getMaxYOffset(), _settings->getMaxYOffset() );
     }
+
+    collisionDetect();
 }
 
 void
 EarthManipulator::rotate( double dx, double dy )
 {
-    //OE_NOTICE << "rotate " << dx <<", " << dy << std::endl;
     // clamp the local pitch delta; never allow the pitch to hit -90.
-
-    bool tether = _tether_node.valid();
     double minp = osg::DegreesToRadians( osg::clampAbove(_settings->getMinPitch(), -89.9) );
-    double maxp = osg::DegreesToRadians( osg::clampBelow(_settings->getMaxPitch(),  89.9) );//tether? 89.9 : -1.0) );
+    double maxp = osg::DegreesToRadians( osg::clampBelow(_settings->getMaxPitch(),  89.9) );
 
 #if 0
     OE_NOTICE << LC 
@@ -2330,7 +2588,7 @@ EarthManipulator::rotate( double dx, double dy )
 
     // clamp pitch range:
     double oldPitch;
-    getLocalEulerAngles( 0L, &oldPitch );
+    getEulerAngles( _rotation, 0L, &oldPitch );
 
     if ( dy + oldPitch > maxp || dy + oldPitch < minp )
         dy = 0;
@@ -2357,117 +2615,36 @@ EarthManipulator::rotate( double dx, double dy )
     rotate_azim.makeRotate(-dx,localUp);
 
     _rotation = _rotation * rotate_elevation * rotate_azim;
+    collisionDetect();
 }
 
 void
 EarthManipulator::zoom( double dx, double dy )
 {   
     // in normal (non-tethered mode) we need a valid zoom point.
-    if ( !_tether_node.valid() )
+    if ( !isTethering() )
+    {
         recalculateCenterFromLookVector();
+    }
 
     double scale = 1.0f + dy;
     setDistance( _distance * scale );
-}
-
-
-namespace
-{
-    // osg::View::getCameraContainingPosition has a bug in it. If the camera's current event
-    // state is not up to date (after a window resize, for example), it still uses that event
-    // state to get the window's current size instead of using the Viewport.
-    //
-    // This version works around that
-
-    const osg::Camera*
-    getCameraContainingPosition(osgViewer::View* view, float x, float y, float& out_local_x, float& out_local_y)
-    {
-        osg::Camera* camera = view->getCamera();
-        osg::Viewport* viewport = camera->getViewport();
-
-        if ( camera->getGraphicsContext() && viewport )
-        {
-            double new_x = x;
-            double new_y = y;
-            
-            const double epsilon = 0.5;
-
-            if (
-                new_x >= (viewport->x()-epsilon) && new_y >= (viewport->y()-epsilon) &&
-                new_x < (viewport->x()+viewport->width()-1.0+epsilon) && new_y <= (viewport->y()+viewport->height()-1.0+epsilon) )
-            {
-                out_local_x = new_x;
-                out_local_y = new_y;
-                return camera;
-            }
-        }
-
-        return view->getCameraContainingPosition(x, y, out_local_x, out_local_y);
-    }
+    collisionDetect();
 }
 
 
 bool
-EarthManipulator::screenToWorld(float x, float y, osg::View* theView, osg::Vec3d& out_coords ) const
+EarthManipulator::screenToWorld(float x, float y, osg::View* theView, osg::Vec3d& out_coords) const
 {
     osgViewer::View* view = dynamic_cast<osgViewer::View*>( theView );
     if ( !view || !view->getCamera() )
         return false;
 
-    osg::RefNodePath nodePath;
-    if ( !_csnObserverPath.getRefNodePath(nodePath) )
-        return false;
-
-    if ( nodePath.empty() )
-        return false;
-
-    float local_x, local_y = 0.0;
-    const osg::Camera* camera = getCameraContainingPosition(view, x, y, local_x, local_y);
-    if ( !camera )
+    osg::ref_ptr<MapNode> mapNode;
+    if ( !_mapNode.lock(mapNode) )
         return false;
 
-    osg::Matrixd matrix;
-    if (nodePath.size()>1)
-    {
-        osg::NodePath prunedNodePath(nodePath.begin(),nodePath.end()-1);
-        matrix = osg::computeLocalToWorld(prunedNodePath);
-    }
-
-    matrix.postMult(camera->getViewMatrix());
-    matrix.postMult(camera->getProjectionMatrix());
-
-    double zNear = -1.0;
-    double zFar = 1.0;
-    if (camera->getViewport())
-    {
-        matrix.postMult(camera->getViewport()->computeWindowMatrix());
-        zNear = 0.0;
-        zFar = 1.0;
-    }
-
-    osg::Matrixd inverse;
-    inverse.invert(matrix);
-
-    osg::Vec3d startVertex = osg::Vec3d(local_x,local_y,zNear) * inverse;
-    osg::Vec3d endVertex = osg::Vec3d(local_x,local_y,zFar) * inverse;
-
-	osg::ref_ptr<osgUtil::LineSegmentIntersector> picker = NULL;
-
-	picker = new osgEarth::DPLineSegmentIntersector(osgUtil::Intersector::MODEL, startVertex, endVertex);	
-	//picker = new osgUtil::LineSegmentIntersector(osgUtil::Intersector::MODEL, startVertex, endVertex);	
-
-    osgUtil::IntersectionVisitor iv(picker.get());
-    iv.setTraversalMask(_intersectTraversalMask);
-    nodePath.back()->accept(iv);
-
-    if ( picker->containsIntersections() )
-    {
-        osgUtil::LineSegmentIntersector::Intersections& results = picker->getIntersections();
-        out_coords = results.begin()->getWorldIntersectPoint();
-        return true;
-    }
-
-    return false;
+    return mapNode->getTerrain()->getWorldCoordsUnderMouse(view, x, y, out_coords);
 }
 
 
@@ -2545,22 +2722,21 @@ EarthManipulator::handlePointAction( const Action& action, float mx, float my, o
         {
             case ACTION_GOTO:
             {
-                Viewpoint here = getViewpoint();
+                Viewpoint here = getViewpoint();                
+                here.focalPoint()->fromWorld(_srs.get(), point);
 
-                if ( !here.getSRS() )
-                    return false;
-
-                osg::Vec3d pointVP;
-                here.getSRS()->transformFromWorld(point, pointVP);
+                //osg::Vec3d pointVP;
+                //here.getSRS()->transformFromWorld(point, pointVP);
 
                 //OE_NOTICE << "X=" << pointVP.x() << ", Y=" << pointVP.y() << std::endl;
 
-                here.setFocalPoint( pointVP );
+//                here.setFocalPoint( pointVP );
 
                 double duration_s = action.getDoubleOption(OPTION_DURATION, 1.0);
                 double range_factor = action.getDoubleOption(OPTION_GOTO_RANGE_FACTOR, 1.0);
 
-                here.setRange( here.getRange() * range_factor );
+                here.range() = here.range().get() * range_factor;
+                //here.setRange( here.getRange() * range_factor );
 
                 setViewpoint( here, duration_s );
             }
@@ -2771,7 +2947,7 @@ EarthManipulator::recalculateRoll()
 }
 
 void
-EarthManipulator::getLocalEulerAngles( double* out_azim, double* out_pitch ) const
+EarthManipulator::getCompositeEulerAngles( double* out_azim, double* out_pitch ) const
 {
     osg::Matrix m = getMatrix() * osg::Matrixd::inverse(_centerLocalToWorld);
     osg::Vec3d look = -getUpVector( m );
@@ -2799,6 +2975,66 @@ EarthManipulator::getLocalEulerAngles( double* out_azim, double* out_pitch ) con
 }
 
 
+// Extracts azim and pitch from a quaternion that does not contain any roll.
+void
+EarthManipulator::getEulerAngles(const osg::Quat& q, double* out_azim, double* out_pitch) const
+{
+    osg::Matrix m( q );
+
+    osg::Vec3d look = -getUpVector( m );
+    osg::Vec3d up   =  getFrontVector( m );
+    
+    look.normalize();
+    up.normalize();
+
+    if ( out_azim )
+    {
+        if ( look.z() < -0.9 )
+            *out_azim = atan2( up.x(), up.y() );
+        else if ( look.z() > 0.9 )
+            *out_azim = atan2( -up.x(), -up.y() );
+        else
+            *out_azim = atan2( look.x(), look.y() );
+
+        *out_azim = normalizeAzimRad( *out_azim );
+    }
+
+    if ( out_pitch )
+    {
+        *out_pitch = asin( look.z() );
+    }
+}
+
+osg::Quat
+EarthManipulator::getQuaternion(double azim, double pitch) const
+{
+    osg::Quat azim_q (  azim,            osg::Vec3d(0,0,1) );
+    osg::Quat pitch_q( -pitch-osg::PI_2, osg::Vec3d(1,0,0) );
+    osg::Matrix newRot = osg::Matrixd( azim_q * pitch_q );
+    return osg::Matrixd::inverse(newRot).getRotate();
+    //TODO: simplify this old code..
+}
+
+void
+EarthManipulator::collapseTetherRotationIntoRotation()
+{
+    // fetch the composite rotation angles (_rotation and _tetherRotation):
+
+    double azim, pitch;
+    getCompositeEulerAngles(&azim, &pitch); // TODO replace with getEulerAngles(_rotation*_tetherRotation, ...)
+    
+    pitch = osg::clampBetween(
+        pitch, 
+        osg::DegreesToRadians(_settings->getMinPitch()),
+        osg::DegreesToRadians(_settings->getMaxPitch()) );
+
+    _rotation = getQuaternion(azim, pitch);
+
+    _tetherRotation = osg::Quat();
+    _tetherRotationOffset.unset();
+}
+
+
 void
 EarthManipulator::setHomeViewpoint( const Viewpoint& vp, double duration_s )
 {
@@ -2965,46 +3201,50 @@ namespace // Utility functions for drag()
 void
 EarthManipulator::drag(double dx, double dy, osg::View* theView)
 {
-    using namespace osg;
+    osgViewer::View* view = dynamic_cast<osgViewer::View*>(theView);
+    if ( !view )
+        return;
+
     const osg::Vec3d zero(0.0, 0.0, 0.0);
     if (_last_action._type != ACTION_EARTH_DRAG)
         _lastPointOnEarth = zero;
 
-    ref_ptr<osg::CoordinateSystemNode> csnSafe = _csn.get();
-    double radiusEquator = csnSafe.valid() ? csnSafe->getEllipsoidModel()->getRadiusEquator() : 6378137.0;
+    double radiusEquator = _srs.valid() ? _srs->getEllipsoid()->getRadiusEquator() : 6378137.0;
 
-    osgViewer::View* view = dynamic_cast<osgViewer::View*>(theView);
     float x = _ga_t0->getX(), y = _ga_t0->getY();
     float local_x, local_y;
-    const osg::Camera* camera
-        = view->getCameraContainingPosition(x, y, local_x, local_y);
+
+    const osg::Camera* camera = view->getCameraContainingPosition(x, y, local_x, local_y);
     if (!camera)
         camera = view->getCamera();
+
+    if ( !camera )
+        return;
+
     osg::Matrix viewMat = camera->getViewMatrix();
     osg::Matrix viewMatInv = camera->getInverseViewMatrix();
     if (!_ga_t1.valid())
         return;
+
     osg::Vec3d worldStartDrag;
     // drag start in camera coordinate system.
-    bool onEarth;
-    if ((onEarth = screenToWorld(_ga_t1->getX(), _ga_t1->getY(),
-                                  view, worldStartDrag)))
+    bool onEarth = screenToWorld(_ga_t1->getX(), _ga_t1->getY(), view, worldStartDrag);
+    if (onEarth)
     {
         if (_lastPointOnEarth == zero)
             _lastPointOnEarth = worldStartDrag;
         else
             worldStartDrag = _lastPointOnEarth;
     }
-    else if (_is_geocentric)
+    else if (_srs->isGeographic())
     {
         if (_lastPointOnEarth != zero)
         {
             worldStartDrag =_lastPointOnEarth;
         }
-        else if (csnSafe.valid())
+        else if (_srs.valid())
         {
-            const osg::Vec3d startWinPt = getWindowPoint(view, _ga_t1->getX(),
-                                                         _ga_t1->getY());
+            const osg::Vec3d startWinPt = getWindowPoint(view, _ga_t1->getX(), _ga_t1->getY());
             const osg::Vec3d startDrag = calcTangentPoint(
                 zero, zero * viewMat, radiusEquator,
                 startWinPt);
@@ -3026,33 +3266,17 @@ EarthManipulator::drag(double dx, double dy, osg::View* theView)
     }
     else
     {
-        Vec3d earthOrigin = zero * viewMat;
-        const osg::Vec3d endDrag = calcTangentPoint(
-            zero, earthOrigin, radiusEquator, winpt);
+        osg::Vec3d earthOrigin = zero * viewMat;
+        const osg::Vec3d endDrag = calcTangentPoint(zero, earthOrigin, radiusEquator, winpt);
         worldEndDrag = endDrag * viewMatInv;
         //OE_INFO << "tangent: " << worldEndDrag << "\n";
     }
 
-#if 0
-    if (onEarth != endOnEarth)
-    {
-        std::streamsize oldPrecision = osgEarth::notify(INFO).precision(10);
-        OE_INFO << (onEarth ? "leaving earth\n" : "entering earth\n");
-        OE_INFO << "start drag: " << worldStartDrag.x() << " "
-                << worldStartDrag.y() << " "
-                << worldStartDrag.z() << "\n";
-        OE_INFO << "end drag: " << worldEndDrag.x() << " "
-                << worldEndDrag.y() << " "
-                << worldEndDrag.z() << "\n";
-        osgEarth::notify(INFO).precision(oldPrecision);
-    }
-#endif
-
-    if (_is_geocentric)
+    if (_srs->isGeographic())
     {
         worldRot.makeRotate(worldStartDrag, worldEndDrag);
         // Move the camera by the inverse rotation
-        Quat cameraRot = worldRot.conj();
+        osg::Quat cameraRot = worldRot.conj();
         // Derive manipulator parameters from the camera matrix. We
         // can't use _center, _centerRotation, and _rotation directly
         // from the manipulator because they may have been updated
@@ -3061,14 +3285,13 @@ EarthManipulator::drag(double dx, double dy, osg::View* theView)
         // when several mouse movement events arrive in a frame. there
         // will be bad stuttering artifacts if we use the updated
         // manipulator parameters.
-        Matrixd Mmanip = Matrixd::translate(_offset_x, _offset_y, -_distance)
-            * viewMatInv;
-        Vec3d center = Mmanip.getTrans();
-        Quat centerRotation = makeCenterRotation(center);
-        Matrixd Mrotation = (Mmanip * Matrixd::translate(center * -1)
-                             * Matrixd::rotate(centerRotation.inverse()));
-        Matrixd Me = Matrixd::rotate(centerRotation)
-            * Matrixd::translate(center) * Matrixd::rotate(cameraRot);
+        osg::Matrixd Mmanip = osg::Matrixd::translate(-_viewOffset.x(), -_viewOffset.y(), -_distance) * viewMatInv;
+        osg::Vec3d center = Mmanip.getTrans();
+        osg::Quat centerRotation = computeCenterRotation(center);
+        osg::Matrixd Mrotation = (Mmanip * osg::Matrixd::translate(center * -1)
+                             * osg::Matrixd::rotate(centerRotation.inverse()));
+        osg::Matrixd Me = osg::Matrixd::rotate(centerRotation)
+            * osg::Matrixd::translate(center) * osg::Matrixd::rotate(cameraRot);
         // In order for the Viewpoint settings to make sense, the
         // inverse camera matrix must not have a roll component, which
         // implies that its x axis remains parallel to the
@@ -3094,24 +3317,24 @@ EarthManipulator::drag(double dx, double dy, osg::View* theView)
             // Find cone with worldEndDrag->center axis and x
             // axis of coordinate frame as generator of the conical
             // surface.
-            Vec3d coneAxis = worldEndDrag * -1;
+            osg::Vec3d coneAxis = worldEndDrag * -1;
             coneAxis.normalize();
-            Vec3d xAxis(Me(0, 0), Me(0, 1), Me(0, 2));
+            osg::Vec3d xAxis(Me(0, 0), Me(0, 1), Me(0, 2));
             // Center of disk: project xAxis onto coneAxis
             double diskDist = xAxis * coneAxis;
-            Vec3d P1 = coneAxis * diskDist;
+            osg::Vec3d P1 = coneAxis * diskDist;
             // Basis of disk equation:
             // p = P1 + R * r * cos(theta) + S * r * sin(theta)
-            Vec3d R = xAxis - P1;
-            Vec3d S = R ^ coneAxis;
+            osg::Vec3d R = xAxis - P1;
+            osg::Vec3d S = R ^ coneAxis;
             double r = R.normalize();
             S.normalize();
             // Solve for angle that rotates xAxis into z = 0 plane.
             // soln to 0 = P1.z + r cos(theta) R.z + r sin(theta) S.z
-            double temp1 = r * (square(S.z()) + square(R.z()));
-            if (equivalent(temp1, 0.0))
+            double temp1 = r * (osg::square(S.z()) + osg::square(R.z()));
+            if (osg::equivalent(temp1, 0.0))
                 return;
-            double radical = r * temp1 - square(P1.z());
+            double radical = r * temp1 - osg::square(P1.z());
             if (radical < 0)
                 return;
             double temp2 = R.z() * sqrt(radical) / temp1;
@@ -3120,22 +3343,22 @@ EarthManipulator::drag(double dx, double dy, osg::View* theView)
             double sin2 = temp2 - temp3;
             double theta1 = DBL_MAX;
             double theta2 = DBL_MAX;
-            Matrixd cm1, cm2;
+            osg::Matrixd cm1, cm2;
             if (fabs(sin1) <= 1.0)
             {
                 theta1 = -asin(sin1);
-                Matrixd m = rotateAroundPoint(worldEndDrag, -theta1, coneAxis);
+                osg::Matrixd m = rotateAroundPoint(worldEndDrag, -theta1, coneAxis);
                 cm1 = Me * m;
             }
             if (fabs(sin2) <= 1.0)
             {
                 theta2 = asin(sin2);
-                Matrix m = rotateAroundPoint(worldEndDrag, -theta2, coneAxis);
+                osg::Matrix m = rotateAroundPoint(worldEndDrag, -theta2, coneAxis);
                 cm2 = Me * m;
             }
             if (theta1 == DBL_MAX && theta2 == DBL_MAX)
                 return;
-            Matrixd* CameraMat = 0;
+            osg::Matrixd* CameraMat = 0;
             if (theta1 != DBL_MAX && cm1(1, 2) >= 0.0)
                 CameraMat = &cm1;
             else if (theta2 != DBL_MAX && cm2(1, 2) >= 0.0)
@@ -3158,22 +3381,22 @@ EarthManipulator::drag(double dx, double dy, osg::View* theView)
                 s = -s;
                 c = -c;
             }
-            Matrixd m(c, s, 0, 0,
+            osg::Matrixd m(c, s, 0, 0,
                       -s, c, 0, 0,
                       0, 0, 1, 0,
                       0, 0, 0, 1);
-            Matrixd CameraMat = m * Me;
+            osg::Matrixd CameraMat = m * Me;
             setCenter( CameraMat.getTrans() );
             // It's not necessary to include the translation
             // component, but it's useful for debugging.
-            Matrixd headMat
-                = (Matrixd::translate(-_offset_x, -_offset_y, _distance)
-           * Mrotation);
-            headMat = headMat * Matrixd::inverse(m);
+            osg::Matrixd headMat
+                = (osg::Matrixd::translate(_viewOffset.x(), _viewOffset.y(), _distance)
+                * Mrotation);
+            headMat = headMat * osg::Matrixd::inverse(m);
             _rotation = headMat.getRotate();
             //recalculateLocalPitchAndAzimuth();
         }
-        _centerRotation = makeCenterRotation(_center);
+        _centerRotation = computeCenterRotation(_center);
 
         _previousUp = getUpVector(_centerLocalToWorld);
     }
diff --git a/src/osgEarthUtil/Ephemeris b/src/osgEarthUtil/Ephemeris
index 2d1d127..d1ad8ac 100644
--- a/src/osgEarthUtil/Ephemeris
+++ b/src/osgEarthUtil/Ephemeris
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/osgEarthUtil/Ephemeris.cpp b/src/osgEarthUtil/Ephemeris.cpp
index 2347aa9..d4598dd 100644
--- a/src/osgEarthUtil/Ephemeris.cpp
+++ b/src/osgEarthUtil/Ephemeris.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/osgEarthUtil/ExampleResources b/src/osgEarthUtil/ExampleResources
index 8cb93ad..81bcd36 100644
--- a/src/osgEarthUtil/ExampleResources
+++ b/src/osgEarthUtil/ExampleResources
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
@@ -60,7 +63,7 @@ namespace osgEarth { namespace Util
         osg::Group* load(
             osg::ArgumentParser& args, 
             osgViewer::View*     view,
-            Control*             userControl =0L ) const;
+            Container*           userContainer =0L ) const;
 
         /**
          * Takes an existing map node and processes all the built-in example command
@@ -70,8 +73,15 @@ namespace osgEarth { namespace Util
             MapNode*             mapNode,
             osg::ArgumentParser& args,
             osgViewer::View*     view,
-            osg::Group*          parentGroup =0L,
-            Control*             userControl =0L) const;
+            osg::Group*          parentGroup,
+            Container*           userContainer) const;
+
+        void parse(
+            MapNode*             mapNode,
+            osg::ArgumentParser& args,
+            osgViewer::View*     view,
+            osg::Group*          parentGroup,
+            LabelControl*        userLabel) const;
 
         /**
          * Configures a view with a stock set of event handlers that are useful
diff --git a/src/osgEarthUtil/ExampleResources.cpp b/src/osgEarthUtil/ExampleResources.cpp
index 6628631..3e90c64 100644
--- a/src/osgEarthUtil/ExampleResources.cpp
+++ b/src/osgEarthUtil/ExampleResources.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
@@ -30,12 +33,9 @@
 #include <osgEarthUtil/ActivityMonitorTool>
 #include <osgEarthUtil/LogarithmicDepthBuffer>
 
-#include <osgEarthUtil/NormalMap>
-#include <osgEarthUtil/DetailTexture>
 #include <osgEarthUtil/LODBlending>
 #include <osgEarthUtil/VerticalScale>
 #include <osgEarthUtil/ContourMap>
-#include <osgEarthUtil/TextureSplatter>
 
 #include <osgEarthAnnotation/AnnotationData>
 #include <osgEarthAnnotation/AnnotationRegistry>
@@ -53,7 +53,7 @@
 #include <osgViewer/View>
 #include <osgViewer/ViewerEventHandlers>
 
-#define KML_PUSHPIN_URL "http://demo.pelicanmapping.com/icons/pushpin_yellow.png"
+#define KML_PUSHPIN_URL "../data/placemark32.png"
 
 #define VP_MIN_DURATION      2.0     // minimum fly time.
 #define VP_METERS_PER_SECOND 2500.0  // fly speed
@@ -74,9 +74,7 @@ namespace
     void flyToViewpoint(EarthManipulator* manip, const Viewpoint& vp)
     {
         Viewpoint currentVP = manip->getViewpoint();
-        GeoPoint vp0(currentVP.getSRS(), currentVP.getFocalPoint(), ALTMODE_ABSOLUTE);
-        GeoPoint vp1(vp.getSRS(), vp.getFocalPoint(), ALTMODE_ABSOLUTE);
-        double distance = vp0.distanceTo(vp1);
+        double distance = currentVP.focalPoint()->distanceTo(currentVP.focalPoint().get());
         double duration = osg::clampBetween(distance / VP_METERS_PER_SECOND, VP_MIN_DURATION, VP_MAX_DURATION);
         manip->setViewpoint( vp, duration );
     }
@@ -125,78 +123,6 @@ namespace
     };
 }
 
-//------------------------------------------------------------------------
-
-namespace
-{
-    struct ViewpointHandler : public osgGA::GUIEventHandler
-    {
-        ViewpointHandler( const std::vector<Viewpoint>& viewpoints, osgViewer::View* view )
-            : _viewpoints( viewpoints ),
-              _manip( dynamic_cast<EarthManipulator*>(view->getCameraManipulator()) ) { }
-
-        bool handle( const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa )
-        {
-            if ( ea.getEventType() == ea.KEYDOWN )
-            {
-                if ( !_viewpoints.empty() )
-                {
-                    int index = (int)ea.getKey() - (int)'1';
-                    if ( index >= 0 && index < (int)_viewpoints.size() )
-                    {
-                        flyToViewpoint( _manip, _viewpoints[index] );
-                    }
-                }
-                if ( ea.getKey() == 'v' )
-                {
-                    XmlDocument xml( _manip->getViewpoint().getConfig() );
-                    xml.store( std::cout );
-                    std::cout << std::endl;
-                }
-                aa.requestRedraw();
-            }
-            return false;
-        }
-
-        std::vector<Viewpoint> _viewpoints;
-        EarthManipulator*      _manip;
-    };
-}
-
-
-Control*
-ViewpointControlFactory::create(const std::vector<Viewpoint>& viewpoints,
-                                osgViewer::View*              view) const
-{
-    Grid* grid = 0L;
-
-    if ( viewpoints.size() > 0 )
-    {
-        // the viewpoint container:
-        grid = new Grid();
-        grid->setChildSpacing( 0 );
-        grid->setChildVertAlign( Control::ALIGN_CENTER );
-
-        for( unsigned i=0; i<viewpoints.size(); ++i )
-        {
-            const Viewpoint& vp = viewpoints[i];
-            Control* num = new LabelControl(Stringify() << (i+1), 16.0f, osg::Vec4f(1,1,0,1));
-            num->setPadding( 4 );
-            grid->setControl( 0, i, num );
-
-            Control* vpc = new LabelControl(vp.getName().empty() ? "<no name>" : vp.getName(), 16.0f);
-            vpc->setPadding( 4 );
-            vpc->setHorizFill( true );
-            vpc->setActiveColor( Color::Blue );
-            vpc->addEventHandler( new ClickViewpointHandler(vp, view->getCameraManipulator()) );
-            grid->setControl( 1, i, vpc );
-        }
-    }
-
-    view->addEventHandler( new ViewpointHandler(viewpoints, view) );
-
-    return grid;
-}
 
 //------------------------------------------------------------------------
 
@@ -246,7 +172,7 @@ namespace
 
         virtual void onValueChanged( class Control* control, float value )
         {
-            _sky->getSunLight()->setAmbient(osg::Vec4(value,value,value,1));
+            _sky->setMinimumAmbient(osg::Vec4(value,value,value,1));
         }
     };
 #endif
@@ -258,40 +184,40 @@ namespace
         * @param rate    The time multipler from real time.  Default of 1440 means 1 minute real time will equal 1 day simulation time.
         */
         AnimateSkyUpdateCallback( double rate = 1440 ):
-    _rate( rate ),
-        _prevTime( -1 ),
-        _accumTime( 0.0 )
-    {
-    }
+            _rate( rate ),
+            _prevTime( -1 ),
+            _accumTime( 0.0 )
+        {
+        }
 
-    virtual void operator()(osg::Node* node, osg::NodeVisitor* nv)
-    {             
-        SkyNode* sky = dynamic_cast< SkyNode* >( node );
-        if (sky)
-        {            
-            double time = nv->getFrameStamp()->getSimulationTime();            
-            if (_prevTime > 0)
-            {                
-                TimeStamp t = sky->getDateTime().asTimeStamp();                  
-                double delta = ceil((time - _prevTime) * _rate);
-                _accumTime += delta;
-                // The time stamp only works in seconds so we wait until we've accumulated at least 1 second to change the date.
-                if (_accumTime > 1.0)
-                {
-                    double deltaS = floor(_accumTime );                    
-                    _accumTime -= deltaS;
-                    t += deltaS;
-                    sky->setDateTime( t );                        
-                }                
-            }            
-            _prevTime = time;
+        virtual void operator()(osg::Node* node, osg::NodeVisitor* nv)
+        {             
+            SkyNode* sky = dynamic_cast< SkyNode* >( node );
+            if (sky)
+            {            
+                double time = nv->getFrameStamp()->getSimulationTime();            
+                if (_prevTime > 0)
+                {                
+                    TimeStamp t = sky->getDateTime().asTimeStamp();                  
+                    double delta = ceil((time - _prevTime) * _rate);
+                    _accumTime += delta;
+                    // The time stamp only works in seconds so we wait until we've accumulated at least 1 second to change the date.
+                    if (_accumTime > 1.0)
+                    {
+                        double deltaS = floor(_accumTime );                    
+                        _accumTime -= deltaS;
+                        t += deltaS;
+                        sky->setDateTime( t );                        
+                    }                
+                }            
+                _prevTime = time;
+            }
+            traverse( node, nv );
         }
-        traverse( node, nv );
-    }
 
-    double _accumTime;
-    double _prevTime;    
-    double _rate;
+        double _accumTime;
+        double _prevTime;    
+        double _rate;
     };
 
 }
@@ -435,19 +361,32 @@ AnnotationGraphControlFactory::create(osg::Node*       graph,
 osg::Group*
 MapNodeHelper::load(osg::ArgumentParser& args,
                     osgViewer::View*     view,
-                    Control*             userControl ) const
+                    Container*           userContainer ) const
 {
     // do this first before scanning for an earth file
     std::string outEarth;
     args.read( "--out-earth", outEarth );
 
+    osg::ref_ptr<osgDB::Options> options = new osgDB::Options();
+    
+#if 1
+    Config c;
+    c.add("elevation_smoothing", false);
+    TerrainOptions to(c);
+
+    MapNodeOptions defMNO;
+    defMNO.setTerrainOptions( to );
+
+    options->setPluginStringData("osgEarth.defaultOptions", defMNO.getConfig().toJSON());
+#endif
+
     // read in the Earth file:
     osg::Node* node = 0L;
     for( int i=0; i<args.argc(); ++i )
     {
         if ( osgDB::getLowerCaseFileExtension(args[i]) == "earth" )
         {
-            node = osgDB::readNodeFile( args[i] );
+            node = osgDB::readNodeFile( args[i], options );
             args.remove(i);
             break;
         }
@@ -494,7 +433,7 @@ MapNodeHelper::load(osg::ArgumentParser& args,
     // parses common cmdline arguments.
     if ( view )
     {
-        parse( mapNode.get(), args, view, root, userControl );
+        parse( mapNode.get(), args, view, root, userContainer );
     }
 
     // Dump out an earth file if so directed.
@@ -509,7 +448,7 @@ MapNodeHelper::load(osg::ArgumentParser& args,
     {
         configureView( view );
     }
-
+    
     return root;
 }
 
@@ -519,7 +458,24 @@ MapNodeHelper::parse(MapNode*             mapNode,
                      osg::ArgumentParser& args,
                      osgViewer::View*     view,
                      osg::Group*          root,
-                     Control*             userControl ) const
+                     LabelControl*        userLabel ) const
+{
+    VBox* vbox = new VBox();
+    vbox->setAbsorbEvents( true );
+    vbox->setBackColor( Color(Color::Black, 0.8) );
+    vbox->setHorizAlign( Control::ALIGN_LEFT );
+    vbox->setVertAlign( Control::ALIGN_BOTTOM );
+    vbox->addControl( userLabel );
+
+    parse(mapNode, args, view, root, vbox);
+}
+
+void
+MapNodeHelper::parse(MapNode*             mapNode,
+                     osg::ArgumentParser& args,
+                     osgViewer::View*     view,
+                     osg::Group*          root,
+                     Container*           userContainer ) const
 {
     if ( !root )
         root = mapNode;
@@ -540,6 +496,9 @@ MapNodeHelper::parse(MapNode*             mapNode,
     bool animateSky    = args.read("--animate-sky");
     bool showActivity  = args.read("--activity");
     bool useLogDepth   = args.read("--logdepth");
+    bool useLogDepth2  = args.read("--logdepth2");
+    bool kmlUI         = args.read("--kmlui");
+    bool inspect       = args.read("--inspect");
 
     if (args.read("--verbose"))
         osgEarth::setNotifyLevel(osg::INFO);
@@ -569,15 +528,21 @@ MapNodeHelper::parse(MapNode*             mapNode,
     // Install a new Canvas for our UI controls, or use one that already exists.
     ControlCanvas* canvas = ControlCanvas::getOrCreate( view );
 
-    Container* mainContainer = canvas->addControl( new VBox() );
-    mainContainer->setAbsorbEvents( true );
-    mainContainer->setBackColor( Color(Color::Black, 0.8) );
-    mainContainer->setHorizAlign( Control::ALIGN_LEFT );
-    mainContainer->setVertAlign( Control::ALIGN_BOTTOM );
+    Container* mainContainer;
+    if ( userContainer )
+    {
+        mainContainer = userContainer;
+    }
+    else
+    {
+        mainContainer = new VBox();
+        mainContainer->setAbsorbEvents( true );
+        mainContainer->setBackColor( Color(Color::Black, 0.8) );
+        mainContainer->setHorizAlign( Control::ALIGN_LEFT );
+        mainContainer->setVertAlign( Control::ALIGN_BOTTOM );
+    }
+    canvas->addControl( mainContainer );
 
-    // install the user control:
-    if ( userControl )
-        mainContainer->addControl( userControl );
 
     // look for external data in the map node:
     const Config& externals = mapNode->externalConfig();
@@ -586,42 +551,12 @@ MapNodeHelper::parse(MapNode*             mapNode,
     const Config& oceanConf       = externals.child("ocean");
     const Config& annoConf        = externals.child("annotations");
     const Config& declutterConf   = externals.child("decluttering");
-    Config        viewpointsConf  = externals.child("viewpoints");
 
     // some terrain effects.
-    const Config& normalMapConf   = externals.child("normal_map");
-    const Config& detailTexConf   = externals.child("detail_texture");
+    // TODO: Most of these are likely to move into extensions.
     const Config& lodBlendingConf = externals.child("lod_blending");
     const Config& vertScaleConf   = externals.child("vertical_scale");
     const Config& contourMapConf  = externals.child("contour_map");
-    const Config& texSplatConf    = externals.child("texture_splatter");
-
-    // backwards-compatibility: read viewpoints at the top level:
-    const ConfigSet& old_viewpoints = externals.children("viewpoint");
-    for( ConfigSet::const_iterator i = old_viewpoints.begin(); i != old_viewpoints.end(); ++i )
-        viewpointsConf.add( *i );
-
-    // Loading a viewpoint list from the earth file:
-    if ( !viewpointsConf.empty() )
-    {
-        std::vector<Viewpoint> viewpoints;
-
-        const ConfigSet& children = viewpointsConf.children();
-        if ( children.size() > 0 )
-        {
-            for( ConfigSet::const_iterator i = children.begin(); i != children.end(); ++i )
-            {
-                viewpoints.push_back( Viewpoint(*i) );
-            }
-        }
-
-        if ( viewpoints.size() > 0 )
-        {
-            Control* c = ViewpointControlFactory().create(viewpoints, view);
-            if ( c )
-                mainContainer->addControl( c );
-        }
-    }
 
     // Adding a sky model:
     if ( useSky || !skyConf.empty() )
@@ -709,14 +644,21 @@ MapNodeHelper::parse(MapNode*             mapNode,
         osg::Node* kml = KML::load( URI(kmlFile), mapNode, kml_options );
         if ( kml )
         {
-            Control* c = AnnotationGraphControlFactory().create(kml, view);
-            if ( c )
+            if (kmlUI)
             {
-                c->setVertAlign( Control::ALIGN_TOP );
-                canvas->addControl( c );
+                Control* c = AnnotationGraphControlFactory().create(kml, view);
+                if ( c )
+                {
+                    c->setVertAlign( Control::ALIGN_TOP );
+                    canvas->addControl( c );
+                }
             }
             root->addChild( kml );
         }
+        else
+        {
+            OE_NOTICE << "Failed to load " << kmlFile << std::endl;
+        }
     }
 
     // Annotations in the map node externals:
@@ -726,7 +668,8 @@ MapNodeHelper::parse(MapNode*             mapNode,
         AnnotationRegistry::instance()->create( mapNode, annoConf, dbOptions.get(), annotations );
         if ( annotations )
         {
-            root->addChild( annotations );
+            mapNode->addChild( annotations );
+            //root->addChild( annotations );
         }
     }
 
@@ -760,11 +703,12 @@ MapNodeHelper::parse(MapNode*             mapNode,
     // Configure for an ortho camera:
     if ( useOrtho )
     {
-        EarthManipulator* manip = dynamic_cast<EarthManipulator*>(view->getCameraManipulator());
-        if ( manip )
-        {
-            manip->getSettings()->setCameraProjection( EarthManipulator::PROJ_ORTHOGRAPHIC );
-        }
+        view->getCamera()->setProjectionMatrixAsOrtho(-1, 1, -1, 1, 0, 1);
+        //EarthManipulator* manip = dynamic_cast<EarthManipulator*>(view->getCameraManipulator());
+        //if ( manip )
+        //{
+        //    manip->getSettings()->setCameraProjection( EarthManipulator::PROJ_ORTHOGRAPHIC );
+        //}
     }
 
     // activity monitor (debugging)
@@ -787,8 +731,17 @@ MapNodeHelper::parse(MapNode*             mapNode,
     // Install logarithmic depth buffer on main camera
     if ( useLogDepth )
     {
-        OE_INFO << LC << "Activating logarithmic depth buffer on main camera" << std::endl;
+        OE_INFO << LC << "Activating logarithmic depth buffer (vertex-only) on main camera" << std::endl;
         osgEarth::Util::LogarithmicDepthBuffer logDepth;
+        logDepth.setUseFragDepth( false );
+        logDepth.install( view->getCamera() );
+    }
+
+    else if ( useLogDepth2 )
+    {
+        OE_INFO << LC << "Activating logarithmic depth buffer (precise) on main camera" << std::endl;
+        osgEarth::Util::LogarithmicDepthBuffer logDepth;
+        logDepth.setUseFragDepth( true );
         logDepth.install( view->getCamera() );
     }
 
@@ -818,33 +771,6 @@ MapNodeHelper::parse(MapNode*             mapNode,
         OE_INFO << LC << "...found " << imageLayers.size() << " image layers." << std::endl;
     }
 
-    // Install a normal map layer.
-    if ( !normalMapConf.empty() )
-    {
-        osg::ref_ptr<NormalMap> effect = new NormalMap(normalMapConf, mapNode->getMap());
-        if ( effect->getNormalMapLayer() )
-        {
-            mapNode->getTerrainEngine()->addEffect( effect.get() );
-        }
-    }
-
-    // Install a detail texturer
-    if ( !detailTexConf.empty() )
-    {
-        osg::ref_ptr<DetailTexture> effect = new DetailTexture(detailTexConf, mapNode->getMap());
-        if ( true ) //effect->getImage() )
-        {
-            mapNode->getTerrainEngine()->addEffect( effect.get() );
-        }
-    }
-
-    // Install a texture splatter
-    if ( !texSplatConf.empty() )
-    {
-        osg::ref_ptr<TextureSplatter> effect = new TextureSplatter(texSplatConf, mapNode->getMap());
-        mapNode->getTerrainEngine()->addEffect( effect.get() );
-    }
-
     // Install elevation morphing
     if ( !lodBlendingConf.empty() )
     {
@@ -891,6 +817,30 @@ MapNodeHelper::parse(MapNode*             mapNode,
         }
     }
 
+    if ( inspect )
+    {
+        mapNode->addExtension( Extension::create("mapinspector", ConfigOptions()) );
+    }
+    
+
+    // Process extensions.
+    for(std::vector<osg::ref_ptr<Extension> >::const_iterator eiter = mapNode->getExtensions().begin();
+        eiter != mapNode->getExtensions().end();
+        ++eiter)
+    {
+        Extension* e = eiter->get();
+
+        // Check for a View interface:
+        ExtensionInterface<osg::View>* viewIF = ExtensionInterface<osg::View>::get( e );
+        if ( viewIF )
+            viewIF->connect( view );
+
+        // Check for a Control interface:
+        ExtensionInterface<Control>* controlIF = ExtensionInterface<Control>::get( e );
+        if ( controlIF )
+            controlIF->connect( mainContainer );
+    }
+
     root->addChild( canvas );
 }
 
@@ -913,15 +863,15 @@ MapNodeHelper::usage() const
 {
     return Stringify()
         << "  --sky                         : add a sky model\n"
-        << "  --ocean                       : add an ocean model\n"
         << "  --kml <file.kml>              : load a KML or KMZ file\n"
+        << "  --kmlui                       : display a UI for toggling nodes loaded with --kml\n"
         << "  --coords                      : display map coords under mouse\n"
         << "  --dms                         : dispay deg/min/sec coords under mouse\n"
         << "  --dd                          : display decimal degrees coords under mouse\n"
         << "  --mgrs                        : show MGRS coords under mouse\n"
         << "  --ortho                       : use an orthographic camera\n"
         << "  --logdepth                    : activates the logarithmic depth buffer\n"
-        << "  --autoclip                    : installs an auto-clip plane callback\n"
+        << "  --logdepth2                   : activates logarithmic depth buffer with per-fragment interpolation\n"
         << "  --images [path]               : finds and loads image layers from folder [path]\n"
         << "  --image-extensions [ext,...]  : with --images, extensions to use\n"
         << "  --out-earth [file]            : write the loaded map to an earth file\n"
diff --git a/src/osgEarthUtil/Export b/src/osgEarthUtil/Export
index 812315f..80c5648 100644
--- a/src/osgEarthUtil/Export
+++ b/src/osgEarthUtil/Export
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthUtil/FeatureQueryTool b/src/osgEarthUtil/FeatureQueryTool
index 2a7beef..204cb2d 100644
--- a/src/osgEarthUtil/FeatureQueryTool
+++ b/src/osgEarthUtil/FeatureQueryTool
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -23,13 +23,11 @@
 #include <osgEarthUtil/Common>
 #include <osgEarth/MapNode>
 #include <osgEarth/MapNodeObserver>
-#include <osgEarth/GeoData>
+#include <osgEarthUtil/RTTPicker>
 #include <osgEarthFeatures/Feature>
 #include <osgEarthFeatures/FeatureSource>
 #include <osgEarthFeatures/FeatureSourceIndexNode>
-#include <osgEarthUtil/Controls>
 #include <osgGA/GUIEventHandler>
-#include <osg/View>
 
 namespace osgEarth { namespace Util
 {
@@ -38,148 +36,22 @@ namespace osgEarth { namespace Util
 
     /**
      * Tool that let you query the map for Features generated from a FeatureSource.
-     *
-     * By default, an unmodified left-click will activate a query. You can replace
-     * this test by calling setInputPredicate().
      */
-    class OSGEARTHUTIL_EXPORT FeatureQueryTool : public osgGA::GUIEventHandler,
-                                                 public MapNodeObserver
+    class OSGEARTHUTIL_EXPORT FeatureQueryTool : public RTTPicker
     {
     public:
-
-        struct Callback : public osg::Referenced
-        {
-            struct EventArgs 
-            {
-                const osgGA::GUIEventAdapter*  _ea;
-                osgGA::GUIActionAdapter*       _aa;
-                osg::Vec3d                     _worldPoint;
-            };
-
-            // called when a valid feature is found under the mouse coords
-            virtual void onHit( FeatureSourceIndexNode* index, FeatureID fid, const EventArgs& args ) { }
-
-            // called when no feature is found under the mouse coords
-            virtual void onMiss( const EventArgs& args ) { }
-        };
-
-        /**
-         * Interface for a custom input test.
-         */
-        class InputPredicate : public osg::Referenced
-        {
-        public:
-            // return true to active a query under the mouse cursor.
-            virtual bool accept( const osgGA::GUIEventAdapter& ea ) =0;
-        };
-
-    public:
         /**
          * Constructs a new feature query tool.
          *
-         * @param mapNode
-         *      Map node containing feature data to query
          * @param callbackToAdd
          *      (optional) convenience; calls addCallback with this parameter
          */
-        FeatureQueryTool( 
-            MapNode*  mapNode,
-            Callback* callbackToAdd =0L );
+        FeatureQueryTool();
 
         /** dtor */
         virtual ~FeatureQueryTool() { }
-
-        /**
-         * Adds a feature query callback.
-         */
-        void addCallback( Callback* callback );
-
-        /**
-         * Removes a feature query callback.
-         */
-
-        /**
-         * Sets a custom input test. By default, the action is a left-click.
-         */
-        void setInputPredicate( InputPredicate* value ) { _inputPredicate = value; }
-
-
-    public: // GUIEventHandler
-
-        virtual bool handle( const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa );
-
-
-    public: // MapNodeObserver
-
-        virtual void setMapNode( MapNode* mapNode );
-
-        virtual MapNode* getMapNode() { return _mapNode.get(); }
-
-
-    protected:
-        osg::observer_ptr<MapNode> _mapNode;
-        bool                       _mouseDown;
-        float                      _mouseDownX, _mouseDownY;
-
-        osg::ref_ptr<InputPredicate> _inputPredicate;
-
-        typedef std::vector< osg::observer_ptr<Callback> > Callbacks;
-        Callbacks _callbacks;
     };
 
-    //--------------------------------------------------------------------
-
-    /**
-     * Sample callback that will highlight a feature upon a query hit.
-     */
-    class OSGEARTHUTIL_EXPORT FeatureHighlightCallback : public FeatureQueryTool::Callback
-    {
-    public:
-        virtual void onHit( FeatureSourceIndexNode* index, FeatureID fid, const EventArgs& args );
-
-        virtual void onMiss( const EventArgs& args );
-
-    protected:
-        void clear();
-
-        struct Selection {
-            osg::observer_ptr<FeatureSourceIndexNode> _index;
-            osg::observer_ptr<osg::Group>             _group;
-            FeatureID                                 _fid;
-            bool operator < ( const Selection& rhs ) const { return _fid < rhs._fid; }
-        };
-        typedef std::set<Selection> SelectionSet;
-        SelectionSet _selections;
-
-        /** dtor */
-        virtual ~FeatureHighlightCallback() { }
-    };
-
-
-    //--------------------------------------------------------------------
-
-    /**
-     * Sample callback that will display feature attributes upon a query hit.
-     */
-    class OSGEARTHUTIL_EXPORT FeatureReadoutCallback : public FeatureQueryTool::Callback
-    {
-    public:
-        FeatureReadoutCallback( Controls::Container* container );
-
-    public:
-        virtual void onHit( FeatureSourceIndexNode* index, FeatureID fid, const EventArgs& args );
-
-        virtual void onMiss( const EventArgs& args );
-
-    protected:
-        void clear();
-        Controls::Grid* _grid;
-
-        /** dtor */
-        virtual ~FeatureReadoutCallback() { }
-    };
-
-
 } } // namespace osgEarthUtil
 
 #endif // OSGEARTHUTIL_FEATURE_QUERY_TOOL_H
diff --git a/src/osgEarthUtil/FeatureQueryTool.cpp b/src/osgEarthUtil/FeatureQueryTool.cpp
index ce48f04..f996236 100644
--- a/src/osgEarthUtil/FeatureQueryTool.cpp
+++ b/src/osgEarthUtil/FeatureQueryTool.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -18,314 +18,18 @@
  */
 
 #include <osgEarthUtil/FeatureQueryTool>
-#include <osgEarth/Pickers>
-#include <osgEarth/Registry>
-#include <osgEarth/Capabilities>
-#include <osgViewer/View>
-#include <osg/Depth>
 
 #define LC "[FeatureQueryTool] "
 
-using namespace osgEarth;
-using namespace osgEarth::Features;
 using namespace osgEarth::Util;
-using namespace osgEarth::Util::Controls;
 
 //#undef OE_DEBUG
 //#define OE_DEBUG OE_INFO
 
 //-----------------------------------------------------------------------
 
-FeatureQueryTool::FeatureQueryTool(MapNode*                    mapNode,
-                                   FeatureQueryTool::Callback* callback) :
-_mapNode( mapNode )
+FeatureQueryTool::FeatureQueryTool() :
+RTTPicker()
 {
-    if ( callback )
-        addCallback( callback );
-}
-
-void
-FeatureQueryTool::addCallback( FeatureQueryTool::Callback* cb )
-{
-    if ( cb )
-        _callbacks.push_back( cb );
-}
-
-void
-FeatureQueryTool::setMapNode( MapNode* mapNode )
-{
-    _mapNode = mapNode;
-}
-
-bool
-FeatureQueryTool::handle( const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa )
-{
-    bool handled = false;
-    bool attempt;
-
-    if ( _inputPredicate.valid() )
-    {
-        attempt = _inputPredicate->accept(ea);
-    }
-    else
-    {
-        attempt =
-            ea.getEventType() == osgGA::GUIEventAdapter::RELEASE &&
-            _mouseDown && 
-            fabs(ea.getX()-_mouseDownX) <= 3.0 && 
-            fabs(ea.getY()-_mouseDownY) <= 3.0;
-    }
-
-    if ( attempt && getMapNode() )
-    {
-        osg::View* view = aa.asView();
-
-        Picker picker(
-            dynamic_cast<osgViewer::View*>(view),
-            getMapNode()->getModelLayerGroup() );
-
-        Picker::Hits hits;
-
-        if ( picker.pick( ea.getX(), ea.getY(), hits ) )
-        {
-            // find the closest indexed feature to the camera. It must be a feature
-            // that is not only closest, but exists in the index as well.
-
-            FeatureSourceIndexNode* closestIndex    = 0L;
-            FeatureID               closestFID;
-            double                  closestDistance = DBL_MAX;
-            osg::Vec3d              closestWorldPt;
-
-            for(Picker::Hits::iterator hit = hits.begin(); hit != hits.end(); ++hit )
-            {
-                FeatureSourceIndexNode* index = picker.getNode<FeatureSourceIndexNode>( *hit );
-                if ( index && (hit->ratio < closestDistance) )
-                {
-                    FeatureID fid;
-                    if ( index->getFID( hit->drawable, hit->primitiveIndex, fid ) )
-                    {
-                        closestIndex    = index;
-                        closestFID      = fid;
-                        closestDistance = hit->ratio;
-                        closestWorldPt  = hit->matrix.valid() ? hit->localIntersectionPoint * (*hit->matrix.get()) : hit->localIntersectionPoint;
-                    }
-                }
-            }
-
-            if ( closestIndex )
-            {
-                OE_DEBUG << LC << "HIT: feature ID = " << (unsigned)closestFID << std::endl;
-
-                Callback::EventArgs args;
-                args._ea = &ea;
-                args._aa = &aa;
-                args._worldPoint = closestWorldPt;
-
-                for( Callbacks::iterator i = _callbacks.begin(); i != _callbacks.end(); )
-                {
-                    if ( i->valid() )
-                    {
-                        i->get()->onHit( closestIndex, closestFID, args );
-                        ++i;
-                    }
-                    else
-                    {
-                        i = _callbacks.erase( i );
-                    }
-                }
-
-                handled = true;
-            }
-        }
-
-        if ( !handled )
-        {
-            OE_DEBUG << LC << "miss" << std::endl;
-
-            Callback::EventArgs args;
-            args._ea = &ea;
-            args._aa = &aa;
-
-            for( Callbacks::iterator i = _callbacks.begin(); i != _callbacks.end(); )
-            {
-                if ( i->valid() )
-                {
-                    i->get()->onMiss( args );
-                    ++i;
-                }
-                else
-                {
-                    i = _callbacks.erase( i );
-                }
-            }
-        }
-
-        _mouseDown = false;
-    }
-
-    // unmodified left mouse click
-    else if (
-        ea.getEventType()  == osgGA::GUIEventAdapter::PUSH &&
-        ea.getModKeyMask() == 0 &&
-        ea.getButtonMask() == osgGA::GUIEventAdapter::LEFT_MOUSE_BUTTON)
-    {
-        _mouseDown = true;
-        _mouseDownX = ea.getX();
-        _mouseDownY = ea.getY();
-    }
-
-    return handled;
-}
-
-//-----------------------------------------------------------------------
-
-void
-FeatureHighlightCallback::onHit( FeatureSourceIndexNode* index, FeatureID fid, const EventArgs& args )
-{
-    clear();
-
-    FeatureDrawSet& drawSet = index->getDrawSet(fid);
-    if ( !drawSet.empty() )
-    {
-        osg::Group* container = 0L;
-        osg::Group* group = new osg::Group();
-        osg::Geode* geode = 0L;
-
-        OE_DEBUG << "Slices = " << drawSet.slices().size() << std::endl;
-
-        for( FeatureDrawSet::DrawableSlices::iterator d = drawSet.slices().begin(); d != drawSet.slices().end(); ++d )
-        {
-            FeatureDrawSet::DrawableSlice& slice = *d;
-            osg::Geometry* featureGeom = slice.drawable->asGeometry();
-
-            osg::Geometry* highlightGeom = new osg::Geometry( *featureGeom, osg::CopyOp::SHALLOW_COPY );
-            osg::Vec4Array* highlightColor = new osg::Vec4Array(1);
-            (*highlightColor)[0] = osg::Vec4f(0,1,1,0.5);
-            highlightGeom->setColorArray(highlightColor);
-            highlightGeom->setColorBinding(osg::Geometry::BIND_OVERALL);
-            highlightGeom->setPrimitiveSetList( d->primSets );
-
-            if ( !geode )
-            {
-                geode = new osg::Geode();
-                group->addChild( geode );
-            }
-
-            geode->addDrawable(highlightGeom);
-
-            if ( !container )
-            {
-                // establishes a container for the highlight geometry.
-                osg::Geode* featureGeode = dynamic_cast<osg::Geode*>( featureGeom->getParent(0) );
-                container = featureGeode->getParent(0);
-                if ( featureGeom->getStateSet() )
-                    geode->getOrCreateStateSet()->merge( *featureGeom->getStateSet() );
-            }
-        }
-
-        for( FeatureDrawSet::Nodes::iterator n = drawSet.nodes().begin(); n != drawSet.nodes().end(); ++n )
-        {
-            group->addChild( *n );
-            if ( !container )
-                container = (*n)->getParent(0);
-        }
-
-        osg::StateSet* sset = group->getOrCreateStateSet();
-
-        // set up to overwrite the real geometry:
-        sset->setAttributeAndModes( new osg::Depth(osg::Depth::LEQUAL,0,1,false), osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE );
-        sset->setRenderBinDetails( 42, "DepthSortedBin" );
-
-        // turn off texturing:
-        for( int ii = 0; ii < Registry::instance()->getCapabilities().getMaxFFPTextureUnits(); ++ii )
-        {
-            sset->setTextureMode( ii, GL_TEXTURE_2D, osg::StateAttribute::OFF | osg::StateAttribute::OVERRIDE );
-            sset->setTextureMode( ii, GL_TEXTURE_3D, osg::StateAttribute::OFF | osg::StateAttribute::OVERRIDE );
-            //sset->setTextureMode( ii, GL_TEXTURE_RECTANGLE, osg::StateAttribute::OFF | osg::StateAttribute::OVERRIDE );
-            //sset->setTextureMode( ii, GL_TEXTURE_CUBE_MAP, osg::StateAttribute::OFF | osg::StateAttribute::OVERRIDE );
-        }
-
-        sset->setMode( GL_BLEND,    osg::StateAttribute::ON  | osg::StateAttribute::OVERRIDE );
-        sset->setMode( GL_LIGHTING, osg::StateAttribute::OFF | osg::StateAttribute::OVERRIDE );
-
-        container->addChild( group );
-
-        Selection selection;
-        selection._index     = index;
-        selection._fid       = fid;
-        selection._group     = group;
-
-        _selections.insert( selection );
-    }
-}
-
-void
-FeatureHighlightCallback::onMiss( const EventArgs& args )
-{
-    clear();
-}
-
-void
-FeatureHighlightCallback::clear()
-{
-    for( SelectionSet::const_iterator i = _selections.begin(); i != _selections.end(); ++i )
-    {
-        const Selection& selection = *i;
-        osg::ref_ptr<osg::Group> safeGroup = selection._group.get();
-        if ( safeGroup.valid() && safeGroup->getNumParents() > 0 )
-        {
-            osg::Group* parent = safeGroup->getParent(0);
-            if ( parent ) 
-                parent->removeChild( safeGroup.get() );
-        }
-    }
-    _selections.clear();
-}
-
-
-//-----------------------------------------------------------------------
-
-FeatureReadoutCallback::FeatureReadoutCallback( Container* container )
-{
-    _grid = new Grid();
-    _grid->setBackColor( Color(Color::Black,0.7f) );
-    container->addControl( _grid );
-}
-
-void
-FeatureReadoutCallback::onHit( FeatureSourceIndexNode* index, FeatureID fid, const EventArgs& args )
-{
-    clear();
-    const Feature* f = 0L;
-    if ( index && index->getFeature(fid, f) )
-    {
-        unsigned r=0;
-
-        _grid->setControl( 0, r, new LabelControl("FID", Color::Red) );
-        _grid->setControl( 1, r, new LabelControl(Stringify()<<fid, Color::White) );
-        ++r;
-
-        const AttributeTable& attrs = f->getAttrs();
-        for( AttributeTable::const_iterator i = attrs.begin(); i != attrs.end(); ++i, ++r )
-        {
-            _grid->setControl( 0, r, new LabelControl(i->first, 14.0f, Color::Yellow) );
-            _grid->setControl( 1, r, new LabelControl(i->second.getString(), 14.0f, Color::White) );
-        }
-        _grid->setVisible( true );
-    }
-    args._aa->requestRedraw();
-}
-
-void
-FeatureReadoutCallback::onMiss( const EventArgs& args )
-{
-    clear();
-    args._aa->requestRedraw();
-}
-
-void
-FeatureReadoutCallback::clear()
-{
-    _grid->clearControls();
-    _grid->setVisible( false );
+    //nop
 }
diff --git a/src/osgEarthUtil/Fog b/src/osgEarthUtil/Fog
index ff50930..aee79da 100644
--- a/src/osgEarthUtil/Fog
+++ b/src/osgEarthUtil/Fog
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/osgEarthUtil/Fog.cpp b/src/osgEarthUtil/Fog.cpp
index 64bdb8f..86cdb69 100644
--- a/src/osgEarthUtil/Fog.cpp
+++ b/src/osgEarthUtil/Fog.cpp
@@ -8,15 +8,19 @@
 * 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.
+* 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/Fog>
+#include <osgEarthUtil/Shaders>
 #include <osgEarth/Registry>
 #include <osgEarth/Capabilities>
 #include <osgEarth/VirtualProgram>
@@ -28,35 +32,6 @@ using namespace osgEarth;
 using namespace osgEarth::Util;
 
 
-namespace
-{
-    const char* vs =
-        "#version " GLSL_VERSION_STR "\n"
-        GLSL_DEFAULT_PRECISION_FLOAT "\n"
-
-        "varying float fogFactor;\n"
-
-        "void oe_fog_vertex(inout vec4 VertexVIEW) \n"
-        "{ \n"        
-        "    float z = length( VertexVIEW.xyz );\n"
-        "    const float LOG2 = 1.442695;\n"        
-        "    fogFactor = exp2( -gl_Fog.density * gl_Fog.density * z * z * LOG2 );\n"
-        "    fogFactor = clamp(fogFactor, 0.0, 1.0);\n"
-        "} \n";
-
-    const char* fs =
-        "#version " GLSL_VERSION_STR "\n"
-        GLSL_DEFAULT_PRECISION_FLOAT "\n"
-
-        "varying float fogFactor;\n"
-
-        "void oe_fog_frag(inout vec4 color) \n"
-        "{ \n"        
-        "    color.rgb = mix( gl_Fog.color.rgb, color.rgb, fogFactor);\n"
-        "} \n";
-}
-
-
 FogEffect::FogEffect()
 {
 }
@@ -69,8 +44,9 @@ FogEffect::~FogEffect()
 void FogEffect::attach( osg::StateSet* stateSet )
 {
     VirtualProgram* vp = VirtualProgram::getOrCreate( stateSet );
-    vp->setFunction( "oe_fog_vertex", vs, ShaderComp::LOCATION_VERTEX_VIEW );
-    vp->setFunction( "oe_fog_frag", fs, ShaderComp::LOCATION_FRAGMENT_LIGHTING );
+    Shaders pkg;
+    pkg.load( vp, pkg.Fog_Vertex );
+    pkg.load( vp, pkg.Fog_Fragment );
     _statesets.push_back(stateSet);
 }
 
@@ -79,8 +55,9 @@ void FogEffect::detach( osg::StateSet* stateSet )
     VirtualProgram* vp = VirtualProgram::get(stateSet);
     if ( vp )
     {
-        vp->removeShader( "oe_fog_vertex" );
-        vp->removeShader( "oe_fog_frag" );
+        Shaders pkg;
+        pkg.unload( vp, pkg.Fog_Vertex );
+        pkg.unload( vp, pkg.Fog_Fragment );
     }
 }
 
diff --git a/src/osgEarthUtil/Fog.frag.glsl b/src/osgEarthUtil/Fog.frag.glsl
new file mode 100644
index 0000000..ca04503
--- /dev/null
+++ b/src/osgEarthUtil/Fog.frag.glsl
@@ -0,0 +1,13 @@
+#version $GLSL_VERSION_STR
+$GLSL_DEFAULT_PRECISION_FLOAT
+
+#pragma vp_entryPoint "oe_fog_frag"
+#pragma vp_location   "fragment_lighting"
+#pragma vp_order      "0.7"
+
+varying float oe_fog_fogFactor;
+
+void oe_fog_frag(inout vec4 color)
+{        
+    color.rgb = mix( gl_Fog.color.rgb, color.rgb, oe_fog_fogFactor);
+}
\ No newline at end of file
diff --git a/src/osgEarthUtil/Fog.vert.glsl b/src/osgEarthUtil/Fog.vert.glsl
new file mode 100644
index 0000000..d96eac7
--- /dev/null
+++ b/src/osgEarthUtil/Fog.vert.glsl
@@ -0,0 +1,16 @@
+#version $GLSL_VERSION_STR
+$GLSL_DEFAULT_PRECISION_FLOAT
+
+#pragma vp_entryPoint "oe_fog_vertex"
+#pragma vp_location   "vertex_view"
+#pragma vp_order      "0.5"
+
+varying float oe_fog_fogFactor;
+
+void oe_fog_vertex(inout vec4 vertexVIEW)
+{
+    float z = length( vertexVIEW.xyz );
+    const float LOG2 = 1.442695;
+    oe_fog_fogFactor = exp2( -gl_Fog.density * gl_Fog.density * z * z * LOG2 );
+    oe_fog_fogFactor = clamp(oe_fog_fogFactor, 0.0, 1.0);
+}
\ No newline at end of file
diff --git a/src/osgEarthUtil/Formatter b/src/osgEarthUtil/Formatter
index ef2e30e..c703ba2 100644
--- a/src/osgEarthUtil/Formatter
+++ b/src/osgEarthUtil/Formatter
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthUtil/GLSLColorFilter b/src/osgEarthUtil/GLSLColorFilter
index 64f9b32..a43db11 100644
--- a/src/osgEarthUtil/GLSLColorFilter
+++ b/src/osgEarthUtil/GLSLColorFilter
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/osgEarthUtil/GLSLColorFilter.cpp b/src/osgEarthUtil/GLSLColorFilter.cpp
index 4f79687..725b987 100644
--- a/src/osgEarthUtil/GLSLColorFilter.cpp
+++ b/src/osgEarthUtil/GLSLColorFilter.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/osgEarthUtil/GammaColorFilter b/src/osgEarthUtil/GammaColorFilter
index 0c82523..8ae9eb8 100644
--- a/src/osgEarthUtil/GammaColorFilter
+++ b/src/osgEarthUtil/GammaColorFilter
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/osgEarthUtil/GammaColorFilter.cpp b/src/osgEarthUtil/GammaColorFilter.cpp
index 66c7e92..37446a8 100644
--- a/src/osgEarthUtil/GammaColorFilter.cpp
+++ b/src/osgEarthUtil/GammaColorFilter.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/osgEarthUtil/GeodeticGraticule b/src/osgEarthUtil/GeodeticGraticule
index 4c69eee..d1fd3bf 100644
--- a/src/osgEarthUtil/GeodeticGraticule
+++ b/src/osgEarthUtil/GeodeticGraticule
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthUtil/GeodeticGraticule.cpp b/src/osgEarthUtil/GeodeticGraticule.cpp
index 0498a67..c36b373 100644
--- a/src/osgEarthUtil/GeodeticGraticule.cpp
+++ b/src/osgEarthUtil/GeodeticGraticule.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -292,7 +292,7 @@ GeodeticGraticule::buildTile( const TileKey& key, Map* map ) const
                 LabelNode* label = new LabelNode( 
                     _mapNode.get(),
                     GeoPoint(geoSRS, clon, clat),
-                    s_llf.format(clon),
+                    s_llf.format(clon, false),
                     textStyle );
                 labels->addChild( label );
             }
@@ -319,7 +319,7 @@ GeodeticGraticule::buildTile( const TileKey& key, Map* map ) const
                 LabelNode* label = new LabelNode( 
                     _mapNode.get(), 
                     GeoPoint(geoSRS, clon, clat),
-                    s_llf.format(clat),
+                    s_llf.format(clat, true),
                     textStyle );
                 labels->addChild( label );
             }
diff --git a/src/osgEarthUtil/Graticule.frag.glsl b/src/osgEarthUtil/Graticule.frag.glsl
new file mode 100644
index 0000000..db9e591
--- /dev/null
+++ b/src/osgEarthUtil/Graticule.frag.glsl
@@ -0,0 +1,31 @@
+#version 110
+#pragma vp_entryPoint "oe_graticule_fragment"
+#pragma vp_location   "fragment_coloring"
+#pragma vp_order      "0.6"
+
+uniform float oe_graticule_lineWidth;
+uniform float oe_graticule_resolution;
+uniform vec4  oe_graticule_color;
+uniform mat4 osg_ViewMatrixInverse;
+
+varying vec2 oe_graticule_coord;
+
+void oe_graticule_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;
+
+    if ( any(lessThan(distanceToLine, dF)))
+    {
+        // Fade out the lines as you get closer to the ground.
+        vec3 eye = osg_ViewMatrixInverse[3].xyz;
+        float hae = length(eye) - 6378137.0;
+        float maxHAE = 2000.0;
+        float alpha = clamp(hae / maxHAE, 0.0, 1.0);
+        color.rgb = mix(color.rgb, oe_graticule_color.rgb, oe_graticule_color.a * alpha);
+    }
+}
\ No newline at end of file
diff --git a/src/osgEarthUtil/Graticule.vert.glsl b/src/osgEarthUtil/Graticule.vert.glsl
new file mode 100644
index 0000000..53121eb
--- /dev/null
+++ b/src/osgEarthUtil/Graticule.vert.glsl
@@ -0,0 +1,16 @@
+#version 110
+#pragma vp_entryPoint "oe_graticule_vertex"
+#pragma vp_location   "vertex_view"
+#pragma vp_order      "0.5"
+
+uniform vec4 oe_tile_key;
+varying vec4 oe_layer_tilec;
+
+varying vec2 oe_graticule_coord;
+
+void oe_graticule_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);
+}
diff --git a/src/osgEarthUtil/GraticuleExtension b/src/osgEarthUtil/GraticuleExtension
new file mode 100644
index 0000000..80081d7
--- /dev/null
+++ b/src/osgEarthUtil/GraticuleExtension
@@ -0,0 +1,74 @@
+/* -*-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.
+ *
+ * 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:
+        META_Object(osgearth_ext_graticule, GraticuleExtension);
+
+        // CTORs
+        GraticuleExtension();
+        GraticuleExtension(const GraticuleOptions& options);
+
+        // DTOR
+        virtual ~GraticuleExtension();
+
+
+    public: // Extension
+
+        void setDBOptions(const osgDB::Options* dbOptions);
+
+
+    public: // ExtensionInterface<MapNode>
+
+        bool connect(MapNode* mapNode);
+
+        bool disconnect(MapNode* mapNode);
+
+
+    protected: // Object
+        GraticuleExtension(const GraticuleExtension& rhs, const osg::CopyOp& op) { }
+
+    private:
+        const GraticuleOptions                 _options;
+        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
new file mode 100644
index 0000000..b98f064
--- /dev/null
+++ b/src/osgEarthUtil/GraticuleExtension.cpp
@@ -0,0 +1,109 @@
+/* -*-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.
+ *
+ * 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 <osgDB/FileNameUtils>
+
+using namespace osgEarth;
+using namespace osgEarth::Util;
+
+#define LC "[GraticuleExtension] "
+
+
+GraticuleExtension::GraticuleExtension()
+{
+    //nop
+}
+
+GraticuleExtension::GraticuleExtension(const GraticuleOptions& options) :
+_options( 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, _options);
+    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;
+}
+
+
+
+// Register the GraticuleExtension as a plugin
+class GraticulePlugin : public osgDB::ReaderWriter
+{
+public: // Plugin stuff
+
+    GraticulePlugin() {
+        supportsExtension( "osgearth_graticule", "osgEarth Graticule Extension" );
+    }
+
+    const char* className() {
+        return "osgEarth Graticule Extension";
+    }
+
+    virtual ~GraticulePlugin() { }
+
+    ReadResult readObject(const std::string& filename, const osgDB::Options* dbOptions) const
+    {
+        if ( !acceptsExtension(osgDB::getLowerCaseFileExtension(filename)) )
+            return ReadResult::FILE_NOT_HANDLED;
+
+        return ReadResult( new GraticuleExtension(Extension::getConfigOptions(dbOptions)) );
+    }
+};
+
+REGISTER_OSGPLUGIN(osgearth_graticule, GraticulePlugin)
+
diff --git a/src/osgEarthUtil/GraticuleNode b/src/osgEarthUtil/GraticuleNode
new file mode 100644
index 0000000..72b2002
--- /dev/null
+++ b/src/osgEarthUtil/GraticuleNode
@@ -0,0 +1,117 @@
+/* -*-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_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;
+
+        std::vector< double > _resolutions;
+
+    };
+
+} } // namespace osgEarth::Util
+
+#endif // OSGEARTH_GRATICULE_NODE
diff --git a/src/osgEarthUtil/GraticuleNode.cpp b/src/osgEarthUtil/GraticuleNode.cpp
new file mode 100644
index 0000000..b0df0e7
--- /dev/null
+++ b/src/osgEarthUtil/GraticuleNode.cpp
@@ -0,0 +1,417 @@
+/* -*-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 "GraticuleNode"
+
+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()->getTerrainStateSet()->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()->getTerrainStateSet()->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 (unsigned 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 (unsigned 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();
+
+
+        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();
+        }
+       
+    
+        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
new file mode 100644
index 0000000..bacba47
--- /dev/null
+++ b/src/osgEarthUtil/GraticuleOptions
@@ -0,0 +1,114 @@
+/* -*-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.
+ *
+ * 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 ( 1.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
new file mode 100644
index 0000000..3f72542
--- /dev/null
+++ b/src/osgEarthUtil/GraticuleTerrainEffect
@@ -0,0 +1,61 @@
+/* -*-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
new file mode 100644
index 0000000..c4f75c3
--- /dev/null
+++ b/src/osgEarthUtil/GraticuleTerrainEffect.cpp
@@ -0,0 +1,87 @@
+/* -*-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->getTerrainStateSet();
+        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->getTerrainStateSet();
+    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/HSLColorFilter b/src/osgEarthUtil/HSLColorFilter
index c4e4819..3f6f69d 100644
--- a/src/osgEarthUtil/HSLColorFilter
+++ b/src/osgEarthUtil/HSLColorFilter
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthUtil/HSLColorFilter.cpp b/src/osgEarthUtil/HSLColorFilter.cpp
index f11393a..f77fe79 100644
--- a/src/osgEarthUtil/HSLColorFilter.cpp
+++ b/src/osgEarthUtil/HSLColorFilter.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/osgEarthUtil/HTM b/src/osgEarthUtil/HTM
index f7b426d..b74584f 100644
--- a/src/osgEarthUtil/HTM
+++ b/src/osgEarthUtil/HTM
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/osgEarthUtil/HTM.cpp b/src/osgEarthUtil/HTM.cpp
index cf685d1..5f26eab 100644
--- a/src/osgEarthUtil/HTM.cpp
+++ b/src/osgEarthUtil/HTM.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/osgEarthUtil/LODBlending b/src/osgEarthUtil/LODBlending
index 4fea4c2..5efef86 100644
--- a/src/osgEarthUtil/LODBlending
+++ b/src/osgEarthUtil/LODBlending
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/osgEarthUtil/LODBlending.cpp b/src/osgEarthUtil/LODBlending.cpp
index 5d01b97..0da442f 100644
--- a/src/osgEarthUtil/LODBlending.cpp
+++ b/src/osgEarthUtil/LODBlending.cpp
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
@@ -57,7 +60,7 @@ namespace
         "uniform float oe_lodblend_delay; \n"
         "uniform float oe_lodblend_duration; \n"
 
-        "uniform mat4 oe_layer_parent_matrix; \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"
@@ -73,8 +76,8 @@ namespace
         "    float r_time     = 1.0 - clamp(osg_FrameTime-(oe_tile_birthtime+oe_lodblend_delay), 0.0, oe_lodblend_duration)/oe_lodblend_duration; \n"
         "    float r          = max(r_dist, r_time); \n"
 
-        "    oe_lodblend_texc = oe_layer_parent_matrix * oe_layer_texc; \n"
-        "    oe_lodblend_r    = oe_layer_parent_matrix[0][0] > 0.0 ? r : 0.0; \n" // obe?
+        "    oe_lodblend_texc = oe_layer_parent_texmat * oe_layer_texc; \n"
+        "    oe_lodblend_r    = oe_layer_parent_texmat[0][0] > 0.0 ? r : 0.0; \n" // obe?
         "} \n";
 
     const char* vs_elevation =
@@ -130,7 +133,7 @@ namespace
         "uniform float oe_lodblend_duration; \n"
         "uniform float oe_lodblend_vscale; \n"
 
-        "uniform mat4 oe_layer_parent_matrix; \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"
@@ -155,8 +158,8 @@ namespace
         "    vec3  blendOffset  = upVector * r * oe_lodblend_vscale * (elevOld-elev); \n"
         "    VertexMODEL       += vec4( (vscaleOffset + blendOffset)*VertexMODEL.w, 0.0 ); \n"
 
-        "    oe_lodblend_texc = oe_layer_parent_matrix * oe_layer_texc; \n"
-        "    oe_lodblend_r    = oe_layer_parent_matrix[0][0] > 0.0 ? r : 0.0; \n" // obe?
+        "    oe_lodblend_texc = oe_layer_parent_texmat * oe_layer_texc; \n"
+        "    oe_lodblend_r    = oe_layer_parent_texmat[0][0] > 0.0 ? r : 0.0; \n" // obe?
 
         "    oe_Normal = normalize(mix(normalize(oe_Normal), oe_terrain_attr2.xyz, r)); \n"
         "} \n";
@@ -279,6 +282,9 @@ LODBlending::onInstall(TerrainEngineNode* engine)
 {
     if ( engine )
     {
+        // need the parent textures for blending.
+        engine->requireParentTextures();
+
         osg::StateSet* stateset = engine->getOrCreateStateSet();
 
         stateset->addUniform( _delayUniform.get() );
@@ -298,6 +304,8 @@ LODBlending::onInstall(TerrainEngineNode* engine)
             vp->setFunction("oe_lodblend_imagery_vertex", vs_imagery, ShaderComp::LOCATION_VERTEX_VIEW);
             vp->setFunction("oe_lodblend_imagery_fragment", fs_imagery, ShaderComp::LOCATION_FRAGMENT_COLORING);
         }
+
+        OE_INFO << LC << "On!\n";
     }
 }
 
diff --git a/src/osgEarthUtil/LatLongFormatter b/src/osgEarthUtil/LatLongFormatter
index dcfc682..05cd06d 100644
--- a/src/osgEarthUtil/LatLongFormatter
+++ b/src/osgEarthUtil/LatLongFormatter
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -38,14 +38,17 @@ namespace osgEarth { namespace Util
             FORMAT_DEFAULT,
             FORMAT_DECIMAL_DEGREES,
             FORMAT_DEGREES_DECIMAL_MINUTES,
-            FORMAT_DEGREES_MINUTES_SECONDS
+            FORMAT_DEGREES_MINUTES_SECONDS,
+            FORMAT_DEGREES_MINUTES_SECONDS_TERSE // Display degrees minutes seconds but don't display values that are 0 and don't show fractional values.
         };
 
         /** Formatting options. */
         enum Options {
             USE_SYMBOLS     = 1 << 0,   // whether to use symbols
             USE_COLONS      = 1 << 1,   // whether to separate components with colons
-            USE_SPACES      = 1 << 2    // whether to separate components with spaces
+            USE_SPACES      = 1 << 2,   // whether to separate components with spaces
+            USE_PREFIXES    = 1 << 3,   // whether to use E/W/N/S prefixes
+            USE_SUFFIXES    = 1 << 4,   // whether to use E/W/N/S suffixes
         };
 
     public:
@@ -74,6 +77,7 @@ namespace osgEarth { namespace Util
          */
         std::string format(
             const Angular&       angle,
+            bool                 latitude,
             int                  precision    =-1, 
             const AngularFormat& outputFormat =FORMAT_DEFAULT) const;
 
diff --git a/src/osgEarthUtil/LatLongFormatter.cpp b/src/osgEarthUtil/LatLongFormatter.cpp
index 587b775..54ee64b 100644
--- a/src/osgEarthUtil/LatLongFormatter.cpp
+++ b/src/osgEarthUtil/LatLongFormatter.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -47,13 +47,13 @@ LatLongFormatter::format( const GeoPoint& p ) const
         return "";
 
     return Stringify()
-        << format( Angular(geo.y()) )
+        << format( Angular(geo.y()), true )
         << ", "
-        << format( Angular(geo.x()) );
+        << format( Angular(geo.x()), false );
 }
 
 std::string
-LatLongFormatter::format( const Angular& angle, int precision, const AngularFormat& format ) const
+LatLongFormatter::format( const Angular& angle, bool latitude, int precision, const AngularFormat& format ) const
 {
     std::stringstream buf;
     std::string result;
@@ -69,18 +69,52 @@ LatLongFormatter::format( const Angular& angle, int precision, const AngularForm
     if ( precision > 0 )
         buf << std::setprecision(precision);
 
+   
+       
+
     double df = angle.as(Units::DEGREES);
     while( df < -180. ) df += 360.;
     while( df >  180. ) df -= 360.;
 
+    // Determine the label to use for suffixes or prefixes
+    std::string label;
+    if (latitude)
+    {
+        label = df < 0 ? "S":"N";
+    }
+    else
+    {
+        label = df < 0 ? "W":"E";
+    }
+  
+    std::string prefix = "";
+    std::string suffix = "";
+    bool makePositive = false;
+    if (_options & USE_PREFIXES)
+    {
+        prefix = label;
+        makePositive = true;
+    }
+    else if (_options & USE_SUFFIXES)
+    {
+        suffix = label;
+        makePositive = true;
+    }
+
+    // If we are using suffixes or prefixes then make the value positive.
+    if (makePositive)
+    {
+        df = osg::absolute(df);
+    }
+
     switch( f )
     {
     case FORMAT_DECIMAL_DEGREES:
         {
             if ( _options & USE_SYMBOLS )
-                buf << df << "\xb0";
+                buf << prefix << df << "\xb0" << suffix;
             else
-                buf << df;
+                buf << prefix << df << suffix;
         }
         break;
 
@@ -93,11 +127,11 @@ LatLongFormatter::format( const Angular& angle, int precision, const AngularForm
                 mf = 0.0;
             }
             if ( _options & USE_SYMBOLS )
-                buf << d << "\xb0" << space << mf << "'";
+                buf << prefix << d << "\xb0" << space << mf << "'" << suffix;
             else if ( _options & USE_COLONS )
-                buf << d << ":" << mf;
+                buf << prefix << d << ":" << mf << suffix;
             else
-                buf << d << " " << mf;
+                buf << prefix << d << " " << mf << suffix;
         }
         break;
 
@@ -115,12 +149,76 @@ LatLongFormatter::format( const Angular& angle, int precision, const AngularForm
                     m = 0;
                 }
             }
+
+            if ( _options & USE_SYMBOLS )
+            {
+                buf << prefix << d << "\xb0" << space << m << "'" << space << sf << "\"" << suffix;
+            }
+            else if ( _options & USE_COLONS )
+                buf << prefix << d << ":" << m << ":" << sf << suffix;
+            else
+                buf << prefix << d << " " << m << " " << sf << suffix;
+        }
+        break;
+    case FORMAT_DEGREES_MINUTES_SECONDS_TERSE:
+        {
+            int    d  = (int)floor(df);
+            double mf = 60.0*(df-(double)d);
+            int    m  = (int)floor(mf);
+            double sf = 60.0*(mf-(double)m);
+            int s = osg::round(sf);
+
+            if ( s == 60 ) {
+                m += 1;
+                s = 0;
+                if ( m == 60 ) {
+                    d += 1;
+                    m = 0;
+                }
+            }
+
+            
+
             if ( _options & USE_SYMBOLS )
-                buf << d << "\xb0" << space << m << "'" << space << sf << "\"";
+            {
+                buf << prefix << d << "\xb0";
+                if (m != 0 || s != 0)
+                {
+                    buf << space << m << "'";
+                    if (s != 0)
+                    {
+                         buf << space << s << "\"" << suffix;
+                    }
+                }
+                buf << suffix;
+                
+            }
             else if ( _options & USE_COLONS )
-                buf << d << ":" << m << ":" << sf;
+            {
+                buf << prefix << d;
+                if (m != 0 || s != 0)
+                {
+                    buf << ":" << m;
+                    if (s != 0)
+                    {
+                        buf << ":" << s;
+                    }
+                }
+                buf << suffix;
+            }
             else
-                buf << d << " " << m << " " << sf;
+            {
+                buf << prefix << d;
+                if (m != 0 || s != 0)
+                {
+                    buf << " " << m;
+                    if (s != 0)
+                    {
+                        buf << " " << s;
+                    }
+                }
+                buf << suffix;
+            }
         }
         break;
 		case FORMAT_DEFAULT:
diff --git a/src/osgEarthUtil/LineOfSight b/src/osgEarthUtil/LineOfSight
index 279c1ea..3148861 100644
--- a/src/osgEarthUtil/LineOfSight
+++ b/src/osgEarthUtil/LineOfSight
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthUtil/LinearLineOfSight b/src/osgEarthUtil/LinearLineOfSight
index ab119cc..76e85ec 100644
--- a/src/osgEarthUtil/LinearLineOfSight
+++ b/src/osgEarthUtil/LinearLineOfSight
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
@@ -24,7 +27,7 @@
 #include <osgEarth/MapNodeObserver>
 #include <osgEarth/Terrain>
 #include <osgEarth/GeoData>
-#include <osgEarth/Draggers>
+#include <osgEarthAnnotation/Draggers>
 
 namespace osgEarth { namespace Util
 {
@@ -242,8 +245,8 @@ namespace osgEarth { namespace Util
         void updateDraggers();
     private:
         osg::ref_ptr< LinearLineOfSightNode > _los;
-        Dragger* _startDragger;
-        Dragger* _endDragger;
+        osgEarth::Annotation::Dragger* _startDragger;
+        osgEarth::Annotation::Dragger* _endDragger;
         osg::ref_ptr< LOSChangedCallback > _callback;
     };
 
diff --git a/src/osgEarthUtil/LinearLineOfSight.cpp b/src/osgEarthUtil/LinearLineOfSight.cpp
index af16214..a66ff1b 100644
--- a/src/osgEarthUtil/LinearLineOfSight.cpp
+++ b/src/osgEarthUtil/LinearLineOfSight.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
@@ -466,7 +469,7 @@ LineOfSightTether::operator()(osg::Node* node, osg::NodeVisitor* nv)
 
 namespace 
 {
-    class LOSDraggerCallback : public Dragger::PositionChangedCallback
+    class LOSDraggerCallback : public osgEarth::Annotation::Dragger::PositionChangedCallback
     {
     public:
         LOSDraggerCallback(LinearLineOfSightNode* los, bool start):
@@ -475,7 +478,7 @@ namespace
           {      
           }
 
-          virtual void onPositionChanged(const Dragger* sender, const osgEarth::GeoPoint& position)
+          virtual void onPositionChanged(const osgEarth::Annotation::Dragger* sender, const osgEarth::GeoPoint& position)
           {   
               if ( _start )
                   _los->setStart( position );
@@ -513,13 +516,13 @@ namespace
 LinearLineOfSightEditor::LinearLineOfSightEditor(LinearLineOfSightNode* los):
 _los(los)
 {
-    _startDragger  = new SphereDragger( _los->getMapNode());
+    _startDragger  = new osgEarth::Annotation::SphereDragger( _los->getMapNode());
     _startDragger->addPositionChangedCallback(new LOSDraggerCallback(_los, true ) );    
-    static_cast<SphereDragger*>(_startDragger)->setColor(osg::Vec4(0,0,1,0));
+    static_cast<osgEarth::Annotation::SphereDragger*>(_startDragger)->setColor(osg::Vec4(0,0,1,0));
     addChild(_startDragger);
 
-    _endDragger = new SphereDragger( _los->getMapNode());
-    static_cast<SphereDragger*>(_endDragger)->setColor(osg::Vec4(0,0,1,0));
+    _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 ) );
 
     addChild(_endDragger);
diff --git a/src/osgEarthUtil/LogDepthBuffer.VertOnly.vert.glsl b/src/osgEarthUtil/LogDepthBuffer.VertOnly.vert.glsl
new file mode 100644
index 0000000..bb92779
--- /dev/null
+++ b/src/osgEarthUtil/LogDepthBuffer.VertOnly.vert.glsl
@@ -0,0 +1,15 @@
+#version 110
+
+#pragma vp_entryPoint "oe_logDepth_vert"
+#pragma vp_location   "vertex_clip"
+#pragma vp_order      "0.99"
+
+uniform float oe_logDepth_FC;
+
+void oe_logDepth_vert(inout vec4 clip)
+{
+    if ( oe_logDepth_FC > 0.0 )
+    {
+        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
new file mode 100644
index 0000000..8e7600d
--- /dev/null
+++ b/src/osgEarthUtil/LogDepthBuffer.frag.glsl
@@ -0,0 +1,16 @@
+#version 110
+
+#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;
+
+void oe_logDepth_frag(inout vec4 color)
+{
+    if ( oe_logDepth_FC > 0.0 )
+    {
+        gl_FragDepth = log2(oe_logDepth_clipz) * 0.5*oe_logDepth_FC;
+    }
+}
diff --git a/src/osgEarthUtil/LogDepthBuffer.vert.glsl b/src/osgEarthUtil/LogDepthBuffer.vert.glsl
new file mode 100644
index 0000000..3b1dc7b
--- /dev/null
+++ b/src/osgEarthUtil/LogDepthBuffer.vert.glsl
@@ -0,0 +1,17 @@
+#version 110
+
+#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;
+
+void oe_logDepth_vert(inout vec4 clip)
+{
+    if ( oe_logDepth_FC > 0.0 )
+    {
+        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 8727efd..82eab7f 100644
--- a/src/osgEarthUtil/LogarithmicDepthBuffer
+++ b/src/osgEarthUtil/LogarithmicDepthBuffer
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
@@ -43,6 +46,17 @@ namespace osgEarth { namespace Util
         /** Constructs a logarithmic depth buffer controller. */
         LogarithmicDepthBuffer();
 
+        /**
+         * Whether to write to gl_FragDepth. (default = true)
+         * Pro: accurate clipping near the camera regardless of weak tessellation
+         * Con: writes to gl_FragDepth defeats early-Z testing on the GPU (hurting
+         *      performance in fill-limited scenarios)
+         *
+         * Set this before calling install().
+         */
+        void setUseFragDepth(bool value);
+        bool getUseFragDepth() const { return _useFragDepth; }
+
         /** is it supported on this platform? */
         bool supported() const { return _supported; }
 
@@ -55,6 +69,8 @@ namespace osgEarth { namespace Util
     protected:
         osg::ref_ptr<osg::NodeCallback> _cullCallback;
         bool _supported;
+        bool _useFragDepth;
+        osg::ref_ptr<osg::Uniform> _FCUniform;
     };
 
 } } // namespace osgEarth::Util
diff --git a/src/osgEarthUtil/LogarithmicDepthBuffer.cpp b/src/osgEarthUtil/LogarithmicDepthBuffer.cpp
index 3c67e61..64ae0d4 100644
--- a/src/osgEarthUtil/LogarithmicDepthBuffer.cpp
+++ b/src/osgEarthUtil/LogarithmicDepthBuffer.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,25 +8,37 @@
 * 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.
+* 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/LogarithmicDepthBuffer>
+#include <osgEarthUtil/Shaders>
 #include <osgEarth/CullingUtils>
 #include <osgEarth/VirtualProgram>
 #include <osgEarth/Registry>
 #include <osgEarth/Capabilities>
+#include <osgEarth/ShaderUtils>
 #include <osgUtil/CullVisitor>
 #include <osg/Uniform>
 #include <osg/buffered_value>
+#include <cmath>
 
 #define LC "[LogarithmicDepthBuffer] "
 
+#define LOG2(X) (::log((double)(X))/::log(2.0))
+#define FC_UNIFORM "oe_logDepth_FC"
+
+// This is only used in the "precise" variant.
+#define NEAR_RES_COEFF 0.001  // a.k.a. "C"
+
 using namespace osgEarth;
 using namespace osgEarth::Util;
 
@@ -34,98 +46,55 @@ using namespace osgEarth::Util;
 
 namespace
 {
-    struct LogDepthCullCallback : public osg::NodeCallback
+    // Callback to set the "far plane" uniform just before drawing.
+    struct SetFarPlaneUniformCallback : public osg::Camera::DrawCallback
     {
-        void operator()(osg::Node* node, osg::NodeVisitor* nv)
+        osg::ref_ptr<osg::Uniform>              _uniform;
+        osg::ref_ptr<osg::Camera::DrawCallback> _next;
+        float                                   _coeff;
+
+        SetFarPlaneUniformCallback(osg::Uniform*              uniform,
+                                   osg::Camera::DrawCallback* next )
         {
-            osgUtil::CullVisitor* cv = Culling::asCullVisitor(nv);
-            osg::Camera* camera = cv->getCurrentCamera();
-            if ( camera )
+            _uniform = uniform;
+            _next    = next;
+            _coeff   = 1.0f;
+        }
+
+        void operator () (osg::RenderInfo& renderInfo) const
+        {
+            const osg::Matrix& proj = renderInfo.getCurrentCamera()->getProjectionMatrix();
+
+            if ( osg::equivalent(proj(3,3), 0.0) ) // perspective
             {
-                // find (or create) a stateset
-                osg::StateSet* stateset = 0L;
-                osg::ref_ptr<osg::StateSet> refStateSet;
-
-                osg::GraphicsContext* gc = camera->getGraphicsContext();
-                if ( gc )
-                {
-                    // faster method of re-using statesets when a GC is present
-                    unsigned id = gc->getState()->getContextID();
-                    refStateSet = _stateSets[id];
-                    if ( !refStateSet.valid() )
-                        refStateSet = new osg::StateSet();
-                    stateset = refStateSet.get();
-                }
-                else
-                {
-                    // no GC is present (e.g., RTT camera) so just make a fresh one
-                    refStateSet = new osg::StateSet();
-                    stateset = refStateSet.get();
-                }
-
-                // the uniform conveying the far clip plane:
-                osg::Uniform* u = stateset->getOrCreateUniform("oe_ldb_far", osg::Uniform::FLOAT);
-
-                // calculate the far plane based on the camera location:
-                osg::Vec3d E, C, U;
-                camera->getViewMatrixAsLookAt(E, C, U);                
-                double farplane = E.length() + 1e6;
-                const double nearplane = 1.0;
-                
-                // set for culling purposes:
-                double L, R, B, T, N, F;
-                camera->getProjectionMatrixAsFrustum(L, R, B, T, N, F);                
-                camera->setProjectionMatrixAsFrustum(L, R, B, T, N, farplane);
-
-                // communicate to the shader:
-                u->set( (float)farplane );
-
-                // and continue traversal of the camera's subgraph.
-                cv->pushStateSet( stateset );
-                traverse(node, nv);
-                cv->popStateSet();
+                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
-            {                    
-                traverse(node, nv);
+            else // ortho
+            {
+                // Disable in ortho, because it just doesn't work.
+                _uniform->set( -1.0f );
             }
-        }
 
-        // context-specific stateset collection
-        osg::buffered_value<osg::ref_ptr<osg::StateSet> > _stateSets;
+            if ( _next.valid() )
+            {
+                _next->operator()( renderInfo );
+            }
+        }
     };
-
-    const char* vertSource =
-        "#version " GLSL_VERSION_STR "\n"
-        GLSL_DEFAULT_PRECISION_FLOAT "\n"
-        "uniform float oe_ldb_far; \n"
-        "varying float logz; \n"
-        "void oe_ldb_vert(inout vec4 clip) \n"
-        "{ \n"
-        "    const float C = 0.0005; \n"
-        "    float FC = 1.0/log2(oe_ldb_far*C + 1.0); \n"
-        "    logz = log2(clip.w*C + 1.0)*FC; \n"
-        "    clip.z = (2.0*logz - 1.0)*clip.w; \n"
-        "} \n";
-
-    const char* fragSource =
-        "#version " GLSL_VERSION_STR "\n"
-        GLSL_DEFAULT_PRECISION_FLOAT "\n"
-        "varying float logz; \n"
-        "void oe_ldb_frag(inout vec4 clip) \n"
-        "{\n"
-        "    gl_FragDepth = logz; \n"
-        "}\n";
 }
 
 //------------------------------------------------------------------------
 
-LogarithmicDepthBuffer::LogarithmicDepthBuffer()
+LogarithmicDepthBuffer::LogarithmicDepthBuffer() :
+_useFragDepth(false)
 {
     _supported = Registry::capabilities().supportsGLSL();
     if ( _supported )
     {
-        _cullCallback = new LogDepthCullCallback();
+        _FCUniform = new osg::Uniform(FC_UNIFORM, (float)0.0f);
     }
     else
     {
@@ -134,6 +103,12 @@ LogarithmicDepthBuffer::LogarithmicDepthBuffer()
 }
 
 void
+LogarithmicDepthBuffer::setUseFragDepth(bool value)
+{
+    _useFragDepth = value;
+}
+
+void
 LogarithmicDepthBuffer::install(osg::Camera* camera)
 {
     if ( camera && _supported )
@@ -142,14 +117,25 @@ LogarithmicDepthBuffer::install(osg::Camera* camera)
         osg::StateSet* stateset = camera->getOrCreateStateSet();
         
         VirtualProgram* vp = VirtualProgram::getOrCreate( stateset );
-        vp->setFunction( "oe_ldb_vert", vertSource, ShaderComp::LOCATION_VERTEX_CLIP, FLT_MAX );
-        vp->setFunction( "oe_ldb_frag", fragSource, ShaderComp::LOCATION_FRAGMENT_LIGHTING, FLT_MAX );
+        Shaders pkg;
+
+        if ( _useFragDepth )
+        {
+            pkg.load( vp, pkg.LogDepthBuffer_VertFile );
+            pkg.load( vp, pkg.LogDepthBuffer_FragFile );
+        }
+        else
+        {
+            pkg.load( vp, pkg.LogDepthBuffer_VertOnly_VertFile );
+        }
 
-        // configure the camera:
-        camera->setComputeNearFarMode(osg::CullSettings::DO_NOT_COMPUTE_NEAR_FAR);
+        osg::ref_ptr<osg::Camera::DrawCallback> next = camera->getPreDrawCallback();
+        if ( dynamic_cast<SetFarPlaneUniformCallback*>(next.get()) )
+            next = static_cast<SetFarPlaneUniformCallback*>(next.get())->_next.get();
+        
+        stateset->addUniform( _FCUniform.get() );
 
-        // install a cull callback to control the far plane:
-        camera->addCullCallback( _cullCallback.get() );
+        camera->setPreDrawCallback( new SetFarPlaneUniformCallback(_FCUniform.get(), next.get()) );
     }
 }
 
@@ -158,18 +144,26 @@ LogarithmicDepthBuffer::uninstall(osg::Camera* camera)
 {
     if ( camera && _supported )
     {
-        camera->removeCullCallback( _cullCallback.get() );
+        SetFarPlaneUniformCallback* dc = dynamic_cast<SetFarPlaneUniformCallback*>(camera->getPreDrawCallback());
+        if ( dc )
+        {
+            osg::ref_ptr<osg::Camera::DrawCallback> next = dc->_next.get();
+            camera->setPreDrawCallback( next.get() );
+        }
 
         osg::StateSet* stateset = camera->getStateSet();
         if ( stateset )
         {
-            VirtualProgram* vp = VirtualProgram::get( camera->getStateSet() );
+            VirtualProgram* vp = VirtualProgram::get( stateset );
             if ( vp )
             {
-                vp->removeShader( "oe_ldb_vert" );
+                Shaders pkg;
+                pkg.unload( vp, pkg.LogDepthBuffer_FragFile );
+                pkg.unload( vp, pkg.LogDepthBuffer_VertFile );
+                pkg.unload( vp, pkg.LogDepthBuffer_VertOnly_VertFile );
             }
 
-            stateset->removeUniform( "oe_ldb_far" );
+            stateset->removeUniform( FC_UNIFORM );
         }
     }
 }
diff --git a/src/osgEarthUtil/MGRSFormatter b/src/osgEarthUtil/MGRSFormatter
index 0e2c952..83a1d51 100644
--- a/src/osgEarthUtil/MGRSFormatter
+++ b/src/osgEarthUtil/MGRSFormatter
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthUtil/MGRSFormatter.cpp b/src/osgEarthUtil/MGRSFormatter.cpp
index 31c7510..759ee8d 100644
--- a/src/osgEarthUtil/MGRSFormatter.cpp
+++ b/src/osgEarthUtil/MGRSFormatter.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthUtil/MGRSGraticule b/src/osgEarthUtil/MGRSGraticule
index d6b658f..cb41b09 100644
--- a/src/osgEarthUtil/MGRSGraticule
+++ b/src/osgEarthUtil/MGRSGraticule
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthUtil/MGRSGraticule.cpp b/src/osgEarthUtil/MGRSGraticule.cpp
index 2115fbd..966f010 100644
--- a/src/osgEarthUtil/MGRSGraticule.cpp
+++ b/src/osgEarthUtil/MGRSGraticule.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthUtil/MeasureTool b/src/osgEarthUtil/MeasureTool
index f030aa4..7cb7525 100644
--- a/src/osgEarthUtil/MeasureTool
+++ b/src/osgEarthUtil/MeasureTool
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthUtil/MeasureTool.cpp b/src/osgEarthUtil/MeasureTool.cpp
index 8d8ca4f..2326f40 100644
--- a/src/osgEarthUtil/MeasureTool.cpp
+++ b/src/osgEarthUtil/MeasureTool.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -110,6 +110,7 @@ MeasureToolHandler::rebuild()
 
     _featureNode = new FeatureNode( getMapNode(), _feature.get() );
     _featureNode->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
+    _featureNode->setClusterCulling(false);
 
     _group->addChild (_featureNode.get() );
 
diff --git a/src/osgEarthUtil/MouseCoordsTool b/src/osgEarthUtil/MouseCoordsTool
index f0295e3..eed96d2 100644
--- a/src/osgEarthUtil/MouseCoordsTool
+++ b/src/osgEarthUtil/MouseCoordsTool
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthUtil/MouseCoordsTool.cpp b/src/osgEarthUtil/MouseCoordsTool.cpp
index 826bb65..ed97421 100644
--- a/src/osgEarthUtil/MouseCoordsTool.cpp
+++ b/src/osgEarthUtil/MouseCoordsTool.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthUtil/NearFarGroup b/src/osgEarthUtil/NearFarGroup
deleted file mode 100644
index 72a66e1..0000000
--- a/src/osgEarthUtil/NearFarGroup
+++ /dev/null
@@ -1,52 +0,0 @@
-/* -*-c++-*- */
-/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2010 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_NEAR_FAR_GROUP_H
-#define OSGEARTHUTIL_NEAR_FAR_GROUP_H
-
-#include <osgEarthUtil/Common>
-#include <osg/Camera>
-
-namespace osgEarth { namespace Util
-{
-    using namespace osgEarth;
-
-    /**
-     * A NearFarGroup is a container for objects that should generate a
-     * "self-contained" near/far clip plane calculation. The near/far clip
-     * calculation for this group exists independently of the rest of the
-     * scene graph.
-     *
-     * This is useful for creating more than one "depth domains" within the
-     * scene. For example, if you have the globe in the background, but at the 
-     * same time you want to zoom up very close to an aircraft, a combined
-     * clip plane domain may not have sufficient precision. Placing the
-     * aircraft under a NearFarGroup can help mitigate this problem.
-     */
-    class OSGEARTHUTIL_EXPORT NearFarGroup : public osg::Camera
-    {
-    public:
-        NearFarGroup();
-
-    public:
-        virtual void traverse(osg::NodeVisitor& nv);
-    };
-
-} } // namespace osgEarth::Util
-
-#endif // OSGEARTHUTIL_NEAR_FAR_GROUP_H
diff --git a/src/osgEarthUtil/NightColorFilter b/src/osgEarthUtil/NightColorFilter
new file mode 100644
index 0000000..b5c9e38
--- /dev/null
+++ b/src/osgEarthUtil/NightColorFilter
@@ -0,0 +1,55 @@
+/* -*-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 OSGEARTHUTIL_NIGHT_COLOR_FILTER
+#define OSGEARTHUTIL_NIGHT_COLOR_FILTER
+
+#include <osgEarthUtil/Common>
+#include <osgEarth/ColorFilter>
+#include <osg/Uniform>
+
+namespace osgEarth { namespace Util
+{
+    /**
+    * Color filter that only renders a layer in night
+    */
+    class OSGEARTHUTIL_EXPORT NightColorFilter : public osgEarth::ColorFilter
+    {
+    public:
+        NightColorFilter();
+        NightColorFilter(const Config& conf);
+        virtual ~NightColorFilter() { }
+
+    public: // ColorFilter
+        virtual std::string getEntryPointFunctionName(void) const;
+        virtual void install(osg::StateSet* stateSet) const;
+        virtual Config getConfig() const;
+
+    protected:
+        unsigned m_instanceId;
+    
+        void init();
+    };
+
+} } // namespace osgEarth::Util
+
+#endif // OSGEARTHUTIL_NIGHT_COLOR_FILTER
diff --git a/src/osgEarthUtil/NightColorFilter.cpp b/src/osgEarthUtil/NightColorFilter.cpp
new file mode 100644
index 0000000..bcce898
--- /dev/null
+++ b/src/osgEarthUtil/NightColorFilter.cpp
@@ -0,0 +1,114 @@
+/* -*-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 <osgEarthUtil/NightColorFilter>
+#include <osgEarth/VirtualProgram>
+#include <osgEarth/StringUtils>
+#include <osgEarth/ThreadingUtils>
+#include <osg/Program>
+#include <OpenThreads/Atomic>
+
+using namespace osgEarth;
+using namespace osgEarth::Util;
+
+namespace
+{
+    static OpenThreads::Atomic s_uniformNameGen;
+
+    static const char* s_localShaderSource =
+        "#version 110\n"
+
+        "varying vec3 atmos_lightDir;\n"    // light direction (view coords)
+        "varying vec3 atmos_up;\n"          // earth up vector at fragment (in view coords)
+
+        "void __ENTRY_POINT__(inout vec4 color)\n"
+        "{\n"
+        "    vec3 L = normalize(atmos_lightDir);\n"
+        "    vec3 N = normalize(atmos_up);\n"
+        "    float NdotL = dot(N,L);\n"
+        "    float vmin = -0.25;\n"
+        "    float vmax = 0.0;\n"
+        //   Remap the -0.25 to 0 to 0 to 1.0
+        "    float day = (clamp( NdotL, vmin, vmax) - vmin)/(vmax-vmin);\n"
+        "    color.a *= (1.0 - day);\n"
+        "} \n";
+}
+
+//---------------------------------------------------------------------------
+
+#define FUNCTION_PREFIX "osgearthutil_nightColorFilter_"
+#define UNIFORM_PREFIX  "osgearthutil_u_night_"
+
+//---------------------------------------------------------------------------
+
+NightColorFilter::NightColorFilter(void)
+{
+    init();
+}
+
+void NightColorFilter::init()
+{
+    // Generate a unique name for this filter's uniform. This is necessary
+    // so that each layer can have a unique uniform and entry point.
+    m_instanceId = (++s_uniformNameGen) - 1;
+}
+
+std::string NightColorFilter::getEntryPointFunctionName(void) const
+{
+    return (osgEarth::Stringify() << FUNCTION_PREFIX << m_instanceId);
+}
+
+void NightColorFilter::install(osg::StateSet* stateSet) const
+{
+    // safe: will not add twice.
+    osgEarth::VirtualProgram* vp = dynamic_cast<osgEarth::VirtualProgram*>(stateSet->getAttribute(VirtualProgram::SA_TYPE));
+    if (vp)
+    {
+        // build the local shader (unique per instance). We will
+        // use a template with search and replace for this one.
+        std::string entryPoint = osgEarth::Stringify() << FUNCTION_PREFIX << m_instanceId;
+        std::string code = s_localShaderSource;
+        osgEarth::replaceIn(code, "__ENTRY_POINT__", entryPoint);
+
+        osg::Shader* main = new osg::Shader(osg::Shader::FRAGMENT, code);
+        //main->setName(entryPoint);
+        vp->setShader(entryPoint, main);
+    }
+}
+
+
+//---------------------------------------------------------------------------
+
+OSGEARTH_REGISTER_COLORFILTER( night, osgEarth::Util::NightColorFilter );
+
+
+NightColorFilter::NightColorFilter(const Config& conf)
+{
+    init();
+}
+
+Config
+NightColorFilter::getConfig() const
+{
+    Config conf("night");   
+    return conf;
+}
diff --git a/src/osgEarthUtil/NormalMap b/src/osgEarthUtil/NormalMap
deleted file mode 100644
index 63f8fed..0000000
--- a/src/osgEarthUtil/NormalMap
+++ /dev/null
@@ -1,78 +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 OSGEARTHUTIL_NORMAL_MAP_H
-#define OSGEARTHUTIL_NORMAL_MAP_H
-
-#include <osgEarthUtil/Common>
-#include <osgEarth/TerrainEffect>
-#include <osgEarth/ImageLayer>
-#include <osg/observer_ptr>
-
-namespace osgEarth {
-    class Map;
-}
-
-namespace osgEarth { namespace Util
-{
-    /**
-     * Terrain effect that applies a normal map sampler to the
-     * terrain during the lighting phase. The normal map is 
-     * provided by a shared ImageLayer.
-     */
-    class OSGEARTHUTIL_EXPORT NormalMap : public TerrainEffect
-    {
-    public:
-        /** construct a new normal mapping effect */
-        NormalMap();
-
-        /** Sets the image layer that generates the normal map. 
-            You must call this prior to installing the effect. */
-        void setNormalMapLayer(ImageLayer* layer) { _layer = layer; }
-        ImageLayer* getNormalMapLayer() { return _layer.get(); }
-
-        /** Sets the LOD at which to start normal mapping */
-        void setStartLOD(unsigned lod);
-        unsigned getStartLOD() const { return *_startLOD; }
-
-    public: // TerrainEffect interface
-
-        void onInstall(TerrainEngineNode* engine);
-        void onUninstall(TerrainEngineNode* engine);
-
-    public: // serialization
-
-        NormalMap(const Config& conf, Map* map);
-        void mergeConfig(const Config& conf);
-        virtual Config getConfig() const;
-
-    protected:
-        virtual ~NormalMap();
-        void init();
-
-        optional<unsigned>    _startLOD;
-        optional<std::string> _layerName;
-
-        osg::observer_ptr<ImageLayer> _layer;
-        osg::ref_ptr<osg::Uniform>    _samplerUniform;
-        osg::ref_ptr<osg::Uniform>    _startLODUniform;
-    };
-
-} } // namespace osgEarth::Util
-
-#endif // OSGEARTHUTIL_NORMAL_MAP_H
diff --git a/src/osgEarthUtil/NormalMap.cpp b/src/osgEarthUtil/NormalMap.cpp
deleted file mode 100644
index 4a1fc48..0000000
--- a/src/osgEarthUtil/NormalMap.cpp
+++ /dev/null
@@ -1,209 +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 <osgEarthUtil/NormalMap>
-#include <osgEarth/Registry>
-#include <osgEarth/Capabilities>
-#include <osgEarth/VirtualProgram>
-#include <osgEarth/TerrainEngineNode>
-
-#define LC "[NormalMap] "
-
-using namespace osgEarth;
-using namespace osgEarth::Util;
-
-namespace
-{
-    const char* vs =
-        "#version " GLSL_VERSION_STR "\n"
-        GLSL_DEFAULT_PRECISION_FLOAT "\n"
-
-        "varying vec3 oe_nmap_light; \n"
-        "varying vec3 oe_nmap_view; \n"
-        "varying vec3 oe_Normal; \n"
-        "uniform bool oe_mode_GL_LIGHTING; \n"
-
-        "void oe_lighting_vertex(inout vec4 VertexVIEW) \n"
-        "{ \n"
-        "    if (oe_mode_GL_LIGHTING) \n"
-        "    { \n"
-        "        vec3 tangent = normalize(cross(gl_Normal, vec3(0,-1,0))); \n"
-
-        "        vec3 n = oe_Normal; \n" //normalize(gl_NormalMatrix * gl_Normal); \n"
-        "        vec3 t = normalize(gl_NormalMatrix * tangent); \n"
-        "        vec3 b = cross(n, t); \n"
-
-        "        vec3 tmp = gl_LightSource[0].position.xyz; \n"
-        "        oe_nmap_light.x = dot(tmp, t); \n"
-        "        oe_nmap_light.y = dot(tmp, b); \n"
-        "        oe_nmap_light.z = dot(tmp, n); \n"
-
-        "        tmp = -VertexVIEW.xyz; \n"
-        "        oe_nmap_view.x = dot(tmp, t); \n"
-        "        oe_nmap_view.y = dot(tmp, b); \n"
-        "        oe_nmap_view.z = dot(tmp, n); \n"
-        "    } \n"
-        "} \n";
-
-    const char* fs =
-        "#version " GLSL_VERSION_STR "\n"
-        GLSL_DEFAULT_PRECISION_FLOAT "\n"
-
-        "uniform sampler2D oe_nmap_tex; \n"
-        "uniform float oe_nmap_startlod; \n"
-        "uniform bool oe_mode_GL_LIGHTING; \n"
-
-        "varying vec4 oe_layer_tilec; \n"
-        "varying vec3 oe_nmap_light; \n"
-        "varying vec3 oe_nmap_view; \n"
-
-        "void oe_lighting_fragment(inout vec4 color) \n"
-        "{\n"
-        "    if (oe_mode_GL_LIGHTING) \n"
-        "    { \n"
-        "        vec3 L = normalize(oe_nmap_light); \n"
-        "        vec3 N = normalize(texture2D(oe_nmap_tex, oe_layer_tilec.st).xyz * 2.0 - 1.0); \n"
-        "        vec3 V = normalize(oe_nmap_view); \n"
-
-        "        vec4 ambient  = gl_LightSource[0].ambient * gl_FrontMaterial.ambient; \n"
-
-        "        float D = max(dot(L, N), 0.0); \n"
-        "        vec4 diffuse  = gl_LightSource[0].diffuse * gl_FrontMaterial.diffuse * D; \n"
-
-        //"        float S = pow(clamp(dot(reflect(-L,N),V),0.0,1.0), gl_FrontMaterial.shininess); \n"
-        //"        vec4 specular = gl_LightSource[0].specular * gl_FrontMaterial.specular * S; \n"
-
-        "        color.rgb = (ambient.rgb*color.rgb) + (diffuse.rgb*color.rgb); \n" // + specular.rgb; \n"
-        "    } \n"
-        "}\n";
-}
-
-
-NormalMap::NormalMap() :
-TerrainEffect(),
-_startLOD    ( 0 )
-{
-    init();
-}
-
-NormalMap::NormalMap(const Config& conf, Map* map) :
-TerrainEffect(),
-_startLOD    ( 0 )
-{
-    mergeConfig(conf);
-
-    if ( map && _layerName.isSet() )
-    {
-        setNormalMapLayer( map->getImageLayerByName(*_layerName) );
-    }
-
-    init();
-}
-
-
-void
-NormalMap::init()
-{
-    _startLODUniform = new osg::Uniform(osg::Uniform::FLOAT, "oe_nmap_startlod");
-    _startLODUniform->set( 0.0f );
-}
-
-
-void
-NormalMap::setStartLOD(unsigned value)
-{
-    _startLOD = value;
-    _startLODUniform->set( (float)value );
-}
-
-
-NormalMap::~NormalMap()
-{
-    //nop
-}
-
-void
-NormalMap::onInstall(TerrainEngineNode* engine)
-{
-    if ( engine )
-    {
-        osg::StateSet* stateset = engine->getOrCreateStateSet();
-        if ( _layer.valid() )
-        {
-            OE_NOTICE << LC << "Installing layer " << _layer->getName() << " as normal map" << std::endl;
-            int unit = *_layer->shareImageUnit();
-            _samplerUniform = stateset->getOrCreateUniform("oe_nmap_tex", osg::Uniform::SAMPLER_2D);
-            _samplerUniform->set(unit);
-        }
-        
-        stateset->addUniform( _startLODUniform.get() );
-
-        VirtualProgram* vp = VirtualProgram::getOrCreate(stateset);
-
-        // these special (built-in) function names are for the main lighting shaders.
-        // using them here will override the default lighting.
-        vp->setFunction( "oe_lighting_vertex",   vs, ShaderComp::LOCATION_VERTEX_VIEW, 0.0 );
-        vp->setFunction( "oe_lighting_fragment", fs, ShaderComp::LOCATION_FRAGMENT_LIGHTING, 0.0 );
-    }
-}
-
-
-void
-NormalMap::onUninstall(TerrainEngineNode* engine)
-{
-    if ( engine )
-    {
-        osg::StateSet* stateset = engine->getStateSet();
-        if ( stateset )
-        {
-            stateset->removeUniform( _samplerUniform.get() );
-            stateset->removeUniform( _startLODUniform.get() );
-            VirtualProgram* vp = VirtualProgram::get(stateset);
-            if ( vp )
-            {
-                vp->removeShader( "oe_lighting_vertex" );
-                vp->removeShader( "oe_lighting_fragment" );
-            }
-        }
-    }
-}
-
-
-//-------------------------------------------------------------
-
-void
-NormalMap::mergeConfig(const Config& conf)
-{
-    conf.getIfSet( "layer",       _layerName );
-    conf.getIfSet( "start_level", _startLOD );
-    conf.getIfSet( "start_lod",   _startLOD );
-}
-
-Config
-NormalMap::getConfig() const
-{
-    optional<std::string> layername;
-
-    if ( _layer.valid() && !_layer->getName().empty() )
-        layername = _layer->getName();
-
-    Config conf("normal_map");
-    conf.addIfSet( "layer",       layername );
-    conf.addIfSet( "start_level", _startLOD );
-    return conf;
-}
diff --git a/src/osgEarthUtil/ObjectLocator b/src/osgEarthUtil/ObjectLocator
index 9fdf0da..94c36cd 100644
--- a/src/osgEarthUtil/ObjectLocator
+++ b/src/osgEarthUtil/ObjectLocator
@@ -1,3 +1,21 @@
+/* -*-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.
+ *
+ * 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
 
@@ -11,6 +29,7 @@ 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
@@ -183,4 +202,3 @@ namespace osgEarth { namespace Util
 } } // namespace osgEarth::Util
 
 #endif // OSGEARTH_UTIL_OBJECT_LOCATOR_H
-
diff --git a/src/osgEarthUtil/Ocean b/src/osgEarthUtil/Ocean
index 8ea0d93..46f43e2 100644
--- a/src/osgEarthUtil/Ocean
+++ b/src/osgEarthUtil/Ocean
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/osgEarthUtil/Ocean.cpp b/src/osgEarthUtil/Ocean.cpp
index 83f30ce..6ad8be9 100644
--- a/src/osgEarthUtil/Ocean.cpp
+++ b/src/osgEarthUtil/Ocean.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
@@ -32,7 +35,7 @@ using namespace osgEarth::Util;
 
 OceanOptions::OceanOptions(const ConfigOptions& options) :
 DriverConfigOptions( options ),
-_maxAltitude       ( 250000.0 )
+_maxAltitude       ( 20000.0 )
 {
     fromConfig(_conf);
 }
@@ -148,7 +151,7 @@ OceanNode::create(const OceanOptions& options,
     if ( driver.empty() )
     {
         OE_INFO << LC << "No driver in options; defaulting to \"simple\"." << std::endl;
-        OE_INFO << LC << options.getConfig().toJSON(true) << std::endl;
+        //OE_INFO << LC << options.getConfig().toJSON(true) << std::endl;
         driver = "simple";
     }
 
diff --git a/src/osgEarthUtil/PolyhedralLineOfSight b/src/osgEarthUtil/PolyhedralLineOfSight
index 051cba5..c1729a7 100644
--- a/src/osgEarthUtil/PolyhedralLineOfSight
+++ b/src/osgEarthUtil/PolyhedralLineOfSight
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/osgEarthUtil/PolyhedralLineOfSight.cpp b/src/osgEarthUtil/PolyhedralLineOfSight.cpp
index 682f5f4..11b35f2 100644
--- a/src/osgEarthUtil/PolyhedralLineOfSight.cpp
+++ b/src/osgEarthUtil/PolyhedralLineOfSight.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/osgEarthUtil/RGBColorFilter b/src/osgEarthUtil/RGBColorFilter
index 0798577..6671916 100644
--- a/src/osgEarthUtil/RGBColorFilter
+++ b/src/osgEarthUtil/RGBColorFilter
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/osgEarthUtil/RGBColorFilter.cpp b/src/osgEarthUtil/RGBColorFilter.cpp
index dbcc20e..c239406 100644
--- a/src/osgEarthUtil/RGBColorFilter.cpp
+++ b/src/osgEarthUtil/RGBColorFilter.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/osgEarthUtil/RTTPicker b/src/osgEarthUtil/RTTPicker
new file mode 100644
index 0000000..e7382fa
--- /dev/null
+++ b/src/osgEarthUtil/RTTPicker
@@ -0,0 +1,144 @@
+/* -*-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.
+ *
+ * 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_RTT_PICKER_H
+#define OSGEARTH_UTIL_RTT_PICKER_H 1
+
+#include <osgEarthUtil/Common>
+#include <osgEarth/Picker>
+#include <osgEarth/VirtualProgram>
+#include <osg/Group>
+#include <osg/Image>
+#include <osg/Texture2D>
+#include <queue>
+
+namespace osgEarth { namespace Util
+{
+    /**
+     * Picks objects using an RTT camera and Vertex Attributes.
+     */
+    class OSGEARTHUTIL_EXPORT RTTPicker : public osgEarth::Picker
+    {
+    public:
+        /**
+         * Creates a new RTT-based object picker.
+         * @param cameraSize Size of the RTT picking viewpoint.
+         */
+        RTTPicker(int cameraSize =256);
+
+        /**
+         * Number of pixels on each side of the clicked pixel to check for hits.
+         */
+        void setBuffer(int value) { _buffer = value; }
+        int getBuffer() const { return _buffer; }
+        
+        /**
+         * Sets a default callback to use when installing the Picker as an EventHandler
+         * or when calling pick() with no callback.
+         */
+        void setDefaultCallback(Callback* value) { _defaultCallback = value; }
+
+        /**
+         * Convenience function that invokes "pick" with no callback, which will cause
+         * this picker to use the default callback installed with setDefaultCallback.
+         */
+        bool pick(osg::View* view, float mouseX, float mouseY);
+
+
+    public: // osgEarth::Picker
+
+        /**
+         * Starts a pick operation. When the operation completes, invokes the callback
+         * with the results. You can use this method if you want to use the picker directly
+         * WITHOUT installing it as an EventHandler. If you use it as an EventHandler, you
+         * 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.
+         */
+        virtual bool pick(osg::View* view, float mouseX, float mouseY, Callback* callback);
+
+
+    public: // osgGA::GUIEventHandler
+
+        virtual bool handle(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa);
+
+
+    public: // simulate osg::Group
+
+        virtual bool addChild(osg::Node* child);
+        virtual bool insertChild(unsigned i, osg::Node* child);
+        virtual bool removeChild(osg::Node* child);
+        virtual bool replaceChild(osg::Node* oldChild, osg::Node* newChild);
+
+    public: // for debugging
+
+        /** For debugging only - creates (if nec.) and returns a texture that captures
+            the RTT image so you can display it. */
+        osg::Texture2D* getOrCreateTexture(osg::View* View);
+
+    protected:
+
+        /** dtor */
+        virtual ~RTTPicker();
+
+        // builds the shaders for rendering to the pick camera. 
+        VirtualProgram* createRTTProgram();
+        
+        int                    _rttSize;     // size of the RTT image (pixels per side)
+        int                    _buffer;      // buffer around pick point to check (pixels)
+        osg::ref_ptr<Callback> _defaultCallback;
+
+        // Associates a view and a pick camera for that view.
+        struct PickContext
+        {
+            osg::observer_ptr<osg::View> _view;
+            osg::ref_ptr<osg::Camera>    _pickCamera;
+            osg::ref_ptr<osg::Image>     _image;
+            osg::ref_ptr<osg::Texture2D> _tex;
+        };
+        typedef std::vector<PickContext> PickContextVector;
+        PickContextVector _pickContexts;
+        
+        // Creates a new pick context on demand.
+        PickContext& getOrCreatePickContext(osg::View* view);
+        
+        // A single pick operation (within a pick context).
+        struct Pick
+        {
+            float                  _u, _v;
+            osg::ref_ptr<Callback> _callback;
+            unsigned               _frame;
+            PickContext*           _context;
+        };
+        std::queue<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);
+
+        // container for common RTT pick camera children (see addChild et al.)
+        osg::ref_ptr<osg::Group> _group;
+    };
+
+} } // namespace osgEarth::Util
+
+#endif // OSGEARTH_UTIL_RTT_PICKER_H
diff --git a/src/osgEarthUtil/RTTPicker.cpp b/src/osgEarthUtil/RTTPicker.cpp
new file mode 100644
index 0000000..8693def
--- /dev/null
+++ b/src/osgEarthUtil/RTTPicker.cpp
@@ -0,0 +1,409 @@
+/* -*-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.
+ *
+ * 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/RTTPicker>
+#include <osgEarth/VirtualProgram>
+#include <osgEarth/ImageUtils>
+#include <osgEarth/Registry>
+#include <osgEarth/ShaderLoader>
+#include <osgEarth/ObjectIndex>
+
+#include <osgDB/WriteFile>
+#include <osg/BlendFunc>
+
+using namespace osgEarth;
+using namespace osgEarth::Util;
+
+#define LC "[RTTPicker] "
+
+namespace
+{
+    // SHADERS for the RTT pick camera.
+
+    const char* pickVertexEncode =
+        "#version 130\n"
+
+        "#pragma vp_entryPoint \"oe_pick_encodeObjectID\" \n"
+        "#pragma vp_location   \"vertex_clip\" \n"
+        
+        "uint oe_index_objectid; \n"                        // Vertex stage global containing the Object ID; set in ObjectIndex shader.
+
+        "flat out vec4 oe_pick_encoded_objectid; \n"        // output encoded oid to fragment shader
+        "flat out int  oe_pick_color_contains_objectid; \n" // whether color already contains oid (written by another RTT camera)
+
+        "void oe_pick_encodeObjectID(inout vec4 vertex) \n"
+        "{ \n"
+        "    oe_pick_color_contains_objectid = (oe_index_objectid == 1u) ? 1 : 0; \n"
+        "    if ( oe_pick_color_contains_objectid == 0 ) \n"
+        "    { \n"
+        "        float b0 = float((oe_index_objectid & 0xff000000u) >> 24u); \n"
+        "        float b1 = float((oe_index_objectid & 0x00ff0000u) >> 16u); \n"
+        "        float b2 = float((oe_index_objectid & 0x0000ff00u) >> 8u ); \n"
+        "        float b3 = float((oe_index_objectid & 0x000000ffu)       ); \n"
+        "        oe_pick_encoded_objectid = vec4(b0, b1, b2, b3) * 0.00392156862; \n" // i.e. 1/2558
+        "    } \n"
+        "} \n";
+
+    const char* pickFragment =
+        "#version 130\n"
+
+        "#pragma vp_entryPoint \"oe_pick_renderEncodedObjectID\" \n"
+        "#pragma vp_location   \"fragment_output\" \n"
+        "#pragma vp_order      \"last\" \n"
+
+        "flat in vec4 oe_pick_encoded_objectid; \n"
+        "flat in int  oe_pick_color_contains_objectid; \n"
+        
+        "out vec4 fragColor; \n"
+
+        "void oe_pick_renderEncodedObjectID(inout vec4 color) \n"
+        "{ \n"
+        "    if ( oe_pick_color_contains_objectid == 1 ) \n"
+        "        fragColor = color; \n"
+        "    else \n"
+        "        fragColor = oe_pick_encoded_objectid; \n"
+        "} \n";
+}
+
+VirtualProgram* 
+RTTPicker::createRTTProgram()
+{    
+    VirtualProgram* vp = new VirtualProgram();
+    vp->setName( "osgEarth::RTTPicker" );
+
+    // Install RTT picker shaders:
+    ShaderPackage pickShaders;
+    pickShaders.add( "RTTPicker.vert.glsl", pickVertexEncode );
+    pickShaders.add( "RTTPicker.frag.glsl", pickFragment );
+    pickShaders.loadAll( vp );
+
+    // Install shaders and bindings from the ObjectIndex:
+    Registry::objectIndex()->loadShaders( vp );
+
+    return vp;
+}
+
+RTTPicker::RTTPicker(int cameraSize)
+{
+    // group that will hold RTT children for all cameras
+    _group = new osg::Group();
+
+    // Size of the RTT camera image
+    _rttSize = std::max(cameraSize, 4);    
+
+    // pixels around the click to test
+    _buffer = 2;
+}
+
+RTTPicker::~RTTPicker()
+{
+    // remove the RTT camera from all views
+    for(int i=0; i<_pickContexts.size(); ++i)
+    {
+        PickContext& pc = _pickContexts[i];
+        while( pc._pickCamera->getNumParents() > 0 )
+        {
+            pc._pickCamera->getParent(0)->removeChild( pc._pickCamera.get() );
+        }
+    }
+}
+
+osg::Texture2D*
+RTTPicker::getOrCreateTexture(osg::View* view)
+{
+    PickContext& pc = getOrCreatePickContext(view);
+    if ( !pc._tex.valid() )
+    {
+        pc._tex = new osg::Texture2D( pc._image.get() );
+        pc._tex->setTextureSize(pc._image->s(), pc._image->t());
+        pc._tex->setUnRefImageDataAfterApply(false);
+        pc._tex->setFilter(pc._tex->MIN_FILTER, pc._tex->NEAREST);
+        pc._tex->setFilter(pc._tex->MAG_FILTER, pc._tex->NEAREST);
+    }
+    return pc._tex.get();
+}
+
+RTTPicker::PickContext&
+RTTPicker::getOrCreatePickContext(osg::View* view)
+{
+    for(PickContextVector::iterator i = _pickContexts.begin(); i != _pickContexts.end(); ++i)
+    {
+        if ( i->_view.get() == view )
+        {
+            return *i;
+        }
+    }
+
+    // Make a new one:
+    _pickContexts.push_back( PickContext() );
+    PickContext& c = _pickContexts.back();
+
+    c._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:
+    c._pickCamera = new osg::Camera();
+    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->setRenderTargetImplementation( osg::Camera::FRAME_BUFFER_OBJECT );
+    c._pickCamera->attach( osg::Camera::COLOR_BUFFER0, c._image.get() );
+    
+    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;
+
+    rttSS->setMode(GL_BLEND,     disable );    
+    rttSS->setMode(GL_LIGHTING,  disable );
+    rttSS->setMode(GL_CULL_FACE, disable );
+    
+    // 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), osg::StateAttribute::OVERRIDE);
+
+    // 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 );
+
+    // 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.
+    c._pickCamera->setUserData( view->getCamera() );
+
+    return c;
+}
+
+bool
+RTTPicker::handle(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa)
+{
+    if ( ea.getEventType() == ea.FRAME )
+    {
+        osg::FrameStamp* fs = aa.asView() ? aa.asView()->getFrameStamp() : 0L;
+        if ( fs )
+        {
+            runPicks( fs->getFrameNumber() );           
+        }
+
+        // if there are picks in the queue, need to continuing rendering:
+        if ( !_picks.empty() )
+        {
+            aa.requestRedraw();
+        }
+    }
+
+    else if ( _defaultCallback.valid() && _defaultCallback->accept(ea, aa) )
+    {        
+        pick( aa.asView(), ea.getX(), ea.getY(), _defaultCallback.get() );
+        aa.requestRedraw();
+    }
+
+    return false;
+}
+
+bool
+RTTPicker::pick(osg::View* view, float mouseX, float mouseY)
+{
+    return pick(view, mouseX, mouseY, 0L);
+}
+
+bool
+RTTPicker::pick(osg::View* view, float mouseX, float mouseY, Callback* callback)
+{
+    if ( !view )
+        return false;
+
+    Callback* callbackToUse = callback ? callback : _defaultCallback.get();
+    if ( !callbackToUse )
+        return false;
+    
+    osg::Camera* cam = view->getCamera();
+    if ( !cam )
+        return false;
+
+    const osg::Viewport* vp = cam->getViewport();
+    if ( !vp )
+        return false;
+
+    // normalize the input cooridnates [0..1]
+    float u = (mouseX - (float)vp->x())/(float)vp->width();
+    float v = (mouseY - (float)vp->y())/(float)vp->height();
+
+    // check the bounds:
+    if ( u < 0.0f || u > 1.0f || v < 0.0f || v > 1.0f )
+        return false;
+
+    // install the RTT pick camera under this view's camera if it's not already:
+    PickContext& context = getOrCreatePickContext( view );
+
+    // Create a new pick
+    Pick pick;
+    pick._context  = &context;
+    pick._u        = u;
+    pick._v        = v;
+    pick._callback = callbackToUse;
+    pick._frame    = view->getFrameStamp() ? view->getFrameStamp()->getFrameNumber() : 0u;
+    
+    // Synchronize the matrices
+    pick._context->_pickCamera->setNodeMask( ~0 );
+    pick._context->_pickCamera->setViewMatrix( cam->getViewMatrix() );
+    pick._context->_pickCamera->setProjectionMatrix( cam->getProjectionMatrix() );
+
+    // Queue it up.
+    _picks.push( pick );
+    
+    return true;
+}
+
+void
+RTTPicker::runPicks(unsigned frameNumber)
+{
+    while( _picks.size() > 0 )
+    {
+        Pick& pick = _picks.front();
+        if ( frameNumber > pick._frame )
+        {
+            checkForPickResult(pick);
+            _picks.pop();
+        }
+        else
+        {
+            break;
+        }
+    }
+}
+
+namespace
+{
+    // Iterates through the pixels in a grid, starting at u,v [0..1] and spiraling out.
+    // It will stop when it reaches the "max ring", which is basically a distance from
+    // the starting point.
+    // Inspiration: http://stackoverflow.com/a/14010215/4218920
+    struct SpiralIterator
+    {
+        unsigned _ring;
+        unsigned _maxRing;
+        unsigned _leg;
+        int      _x, _y;
+        int      _w, _h;
+        int      _offsetX, _offsetY;
+        unsigned _count;
+
+        SpiralIterator(int w, int h, int maxDist, float u, float v) : 
+            _w(w), _h(h), _maxRing(maxDist), _count(0), _ring(1), _leg(0), _x(0), _y(0)
+        {
+            _offsetX = (int)(u * (float)w);
+            _offsetY = (int)(v * (float)h);
+        }
+
+        bool next()
+        {
+            // first time, just use the start point
+            if ( _count++ == 0 )
+                return true;
+
+            // spiral until we get to the next valid in-bounds pixel:
+            do {
+                switch(_leg) {
+                case 0: ++_x; if (  _x == _ring ) ++_leg; break;
+                case 1: ++_y; if (  _y == _ring ) ++_leg; break;
+                case 2: --_x; if ( -_x == _ring ) ++_leg; break;
+                case 3: --_y; if ( -_y == _ring ) { _leg = 0; ++_ring; } break;
+                }
+            }
+            while(_ring <= _maxRing && (_x+_offsetX < 0 || _x+_offsetX >= _w || _y+_offsetY < 0 || _y+_offsetY >= _h));
+
+            return _ring <= _maxRing;
+        }
+
+        int s() const { return _x+_offsetX; }
+
+        int t() const { return _y+_offsetY; }
+    };
+}
+
+void
+RTTPicker::checkForPickResult(Pick& pick)
+{
+    // turn the camera off:
+    pick._context->_pickCamera->setNodeMask( 0 );
+
+    // decode the results
+    osg::Image* image = pick._context->_image.get();
+    ImageUtils::PixelReader read( image );
+
+    // uncomment to see the RTT image.
+    //osgDB::writeImageFile(*image, "out.png");
+
+    osg::Vec4f value;
+    SpiralIterator iter(image->s(), image->t(), std::max(_buffer,1), pick._u, pick._v);
+    while(iter.next())
+    {
+        value = read(iter.s(), iter.t());
+
+        ObjectID id = (ObjectID)(
+            ((unsigned)(value.r()*255.0) << 24) +
+            ((unsigned)(value.g()*255.0) << 16) +
+            ((unsigned)(value.b()*255.0) <<  8) +
+            ((unsigned)(value.a()*255.0)));
+
+        if ( id > 0 )
+        {
+            pick._callback->onHit( id );
+            return;
+        }
+    }
+
+    pick._callback->onMiss();
+}
+
+bool
+RTTPicker::addChild(osg::Node* child)
+{
+    return _group->addChild( child );
+}
+
+bool
+RTTPicker::insertChild(unsigned i, osg::Node* child)
+{
+    return _group->insertChild( i, child );
+}
+
+bool
+RTTPicker::removeChild(osg::Node* child)
+{
+    return _group->removeChild( child );
+}
+
+bool
+RTTPicker::replaceChild(osg::Node* oldChild, osg::Node* newChild)
+{
+    return _group->replaceChild( oldChild, newChild );
+}
diff --git a/src/osgEarthUtil/RadialLineOfSight b/src/osgEarthUtil/RadialLineOfSight
index 86a0409..4093ecd 100644
--- a/src/osgEarthUtil/RadialLineOfSight
+++ b/src/osgEarthUtil/RadialLineOfSight
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
@@ -24,7 +27,7 @@
 #include <osgEarth/MapNodeObserver>
 #include <osgEarth/Terrain>
 #include <osgEarth/GeoData>
-#include <osgEarth/Draggers>
+#include <osgEarthAnnotation/Draggers>
 
 namespace osgEarth { namespace Util
 {
@@ -219,7 +222,7 @@ namespace osgEarth { namespace Util
         void updateDraggers();
     private:
         osg::ref_ptr< RadialLineOfSightNode > _los;
-        Dragger* _dragger;
+        osgEarth::Annotation::Dragger* _dragger;
         osg::ref_ptr< LOSChangedCallback > _callback;
     };
 
diff --git a/src/osgEarthUtil/RadialLineOfSight.cpp b/src/osgEarthUtil/RadialLineOfSight.cpp
index 39388c3..2876f97 100644
--- a/src/osgEarthUtil/RadialLineOfSight.cpp
+++ b/src/osgEarthUtil/RadialLineOfSight.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
@@ -88,7 +91,7 @@ _displayMode( LineOfSight::MODE_SPLIT ),
 _fill(false),
 _terrainOnly( false )
 {
-    compute(getNode());
+    //compute(getNode());
     _terrainChangedCallback = new RadialLineOfSightNodeTerrainChangedCallback( this );
     _mapNode->getTerrain()->addTerrainCallback( _terrainChangedCallback.get() );        
     setNumChildrenRequiringUpdateTraversal( 1 );
@@ -702,7 +705,7 @@ namespace
     };
 
         
-    class RadialLOSDraggerCallback : public Dragger::PositionChangedCallback
+    class RadialLOSDraggerCallback : public osgEarth::Annotation::Dragger::PositionChangedCallback
     {
     public:
         RadialLOSDraggerCallback(RadialLineOfSightNode* los):
@@ -710,7 +713,7 @@ namespace
           {
           }
 
-          virtual void onPositionChanged(const Dragger* sender, const osgEarth::GeoPoint& position)
+          virtual void onPositionChanged(const osgEarth::Annotation::Dragger* sender, const osgEarth::GeoPoint& position)
           {
               _los->setCenter( position );
 
@@ -736,9 +739,9 @@ RadialLineOfSightEditor::RadialLineOfSightEditor(RadialLineOfSightNode* los):
 _los(los)
 {
 
-    _dragger  = new SphereDragger(_los->getMapNode());
+    _dragger  = new osgEarth::Annotation::SphereDragger(_los->getMapNode());
     _dragger->addPositionChangedCallback(new RadialLOSDraggerCallback(_los ) );    
-    static_cast<SphereDragger*>(_dragger)->setColor(osg::Vec4(0,0,1,0));
+    static_cast<osgEarth::Annotation::SphereDragger*>(_dragger)->setColor(osg::Vec4(0,0,1,0));
     addChild(_dragger);    
 
     _callback = new RadialUpdateDraggersCallback( this );
diff --git a/src/osgEarthUtil/Shaders b/src/osgEarthUtil/Shaders
new file mode 100644
index 0000000..616b49d
--- /dev/null
+++ b/src/osgEarthUtil/Shaders
@@ -0,0 +1,50 @@
+/* -*-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_SHADERS
+#define OSGEARTH_SHADERS 1
+
+#include <osgEarthUtil/Export>
+#include <osgEarth/ShaderLoader>
+
+namespace osgEarth { namespace Util
+{
+    struct OSGEARTHUTIL_EXPORT Shaders : public osgEarth::ShaderPackage
+	{
+        Shaders();
+
+		std::string
+            ContourMap_Vertex,
+            ContourMap_Fragment,
+
+            Fog_Vertex,
+            Fog_Fragment,
+
+            Graticule_Fragment,
+            Graticule_Vertex,
+
+            LogDepthBuffer_VertFile,
+            LogDepthBuffer_FragFile,
+            LogDepthBuffer_VertOnly_VertFile;
+	};	
+} } // namespace osgEarth::Util
+
+#endif // OSGEARTH_SHADERS
diff --git a/src/osgEarthUtil/Shaders.cpp.in b/src/osgEarthUtil/Shaders.cpp.in
new file mode 100644
index 0000000..9a3275d
--- /dev/null
+++ b/src/osgEarthUtil/Shaders.cpp.in
@@ -0,0 +1,36 @@
+// ***DO NOT EDIT THIS FILE - IT IS AUTOMATICALLY GENERATED BY CMAKE***
+
+#include <osgEarthUtil/Shaders>
+
+using namespace osgEarth::Util;
+
+Shaders::Shaders()
+{
+    ContourMap_Vertex = "ContourMap.vert.glsl";
+    _sources[ContourMap_Vertex] = OE_MULTILINE(@ContourMap.vert.glsl@);
+
+    ContourMap_Fragment = "ContourMap.frag.glsl";
+    _sources[ContourMap_Fragment] = OE_MULTILINE(@ContourMap.frag.glsl@);
+
+    Fog_Vertex = "Fog.vert.glsl";
+    _sources[Fog_Vertex] = OE_MULTILINE(@Fog.vert.glsl@);
+
+    Fog_Fragment = "Fog.frag.glsl";
+    _sources[Fog_Fragment] = OE_MULTILINE(@Fog.frag.glsl@);
+
+    LogDepthBuffer_VertFile = "LogDepthBuffer.vert.glsl";
+    _sources[LogDepthBuffer_VertFile] = OE_MULTILINE(@LogDepthBuffer.vert.glsl@);
+
+    LogDepthBuffer_FragFile = "LogDepthBuffer.frag.glsl";
+    _sources[LogDepthBuffer_FragFile] = OE_MULTILINE(@LogDepthBuffer.frag.glsl@);
+
+    LogDepthBuffer_VertOnly_VertFile = "LogDepthBuffer.VertOnly.vert.glsl";
+    _sources[LogDepthBuffer_VertOnly_VertFile] = OE_MULTILINE(@LogDepthBuffer.VertOnly.vert.glsl@);
+
+    Graticule_Fragment = "Graticule.frag.glsl";
+    _sources[Graticule_Fragment] = OE_MULTILINE(@Graticule.frag.glsl@);
+
+    Graticule_Vertex = "Graticule.vert.glsl";
+    _sources[Graticule_Vertex] = OE_MULTILINE(@Graticule.vert.glsl@);
+
+}
diff --git a/src/osgEarthUtil/Shadowing b/src/osgEarthUtil/Shadowing
index 88e051b..b335bef 100644
--- a/src/osgEarthUtil/Shadowing
+++ b/src/osgEarthUtil/Shadowing
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/osgEarthUtil/Shadowing.cpp b/src/osgEarthUtil/Shadowing.cpp
index ae16549..8c0bb95 100644
--- a/src/osgEarthUtil/Shadowing.cpp
+++ b/src/osgEarthUtil/Shadowing.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
@@ -239,12 +242,14 @@ ShadowCaster::reinitialize()
     vp->setFunction(
         "oe_shadow_vertex", 
         vertex, 
-        ShaderComp::LOCATION_VERTEX_VIEW );
+        ShaderComp::LOCATION_VERTEX_VIEW,
+        0.9f );
 
     vp->setFunction(
         "oe_shadow_fragment",
         fragment,
-        ShaderComp::LOCATION_FRAGMENT_LIGHTING, 10.0f);
+        ShaderComp::LOCATION_FRAGMENT_LIGHTING,
+        0.9f );
 
     // the texture coord generator matrix array (from the caster):
     _shadowMapTexGenUniform = _renderStateSet->getOrCreateUniform(
diff --git a/src/osgEarthUtil/SimplexNoise b/src/osgEarthUtil/SimplexNoise
index 711f88c..35e83cf 100644
--- a/src/osgEarthUtil/SimplexNoise
+++ b/src/osgEarthUtil/SimplexNoise
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
@@ -39,6 +42,7 @@ namespace osgEarth { namespace Util
         static const double DefaultRangeLow;
         static const double DefaultRangeHigh;
         static const unsigned DefaultOctaves;
+        static const bool   DefaultNormalize;
 
         /**
          * Frequency is how often the noise pattern resets to zero across
@@ -87,6 +91,13 @@ namespace osgEarth { namespace Util
         double getRangeHigh() const { return _high; }
 
         /**
+         * Whether to normalize output values within the limits set by
+         * setRange().
+         */
+        void setNormalize(bool value) { _normalize = value; }
+        bool getNormalize() const { return _normalize; }
+        
+        /**
          * 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"
@@ -111,6 +122,13 @@ namespace osgEarth { namespace Util
          */
         double getValue(double x, double y, double z, double w) const;
 
+        /**
+         * Generates tilable 2D noise for x[0..1), y[0..1).
+         */
+        double getTiledValue(double x, double y) const;
+        
+        double getTiledValueWithTurbulence(double x, double y, double F) const;
+
     private:
         // Inner class to speed up gradient computations
         // (array access is a lot slower than member access)
@@ -152,6 +170,7 @@ namespace osgEarth { namespace Util
         double _lacunarity;
         double _low, _high;
         unsigned _octaves;
+        bool _normalize;
     };
 
 } } // namespace osgEarth::Util
diff --git a/src/osgEarthUtil/SimplexNoise.cpp b/src/osgEarthUtil/SimplexNoise.cpp
index 6a84aee..8a5fa06 100644
--- a/src/osgEarthUtil/SimplexNoise.cpp
+++ b/src/osgEarthUtil/SimplexNoise.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -123,6 +123,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;
 
 
 SimplexNoise::SimplexNoise() :
@@ -131,7 +132,8 @@ _freq      ( DefaultFrequency ),
 _pers      ( DefaultPersistence ),
 _lacunarity( DefaultLacunarity ),
 _low       ( DefaultRangeLow ),
-_high      ( DefaultRangeHigh )
+_high      ( DefaultRangeHigh ),
+_normalize ( DefaultNormalize )
 {
     for(unsigned int i=0; i<512; i++)
     {
@@ -139,6 +141,69 @@ _high      ( DefaultRangeHigh )
     }
 }
 
+double SimplexNoise::getTiledValue(double x, double y) const
+{
+    const double TwoPI = 2.0 * osg::PI;
+    double freq = _freq;
+    double o = std::max(1u, _octaves);
+    double amp = 1.0;
+    double maxamp = 0.0;
+    double n = 0.0;
+    
+    double nx = cos(x*TwoPI)/TwoPI;
+    double ny = cos(y*TwoPI)/TwoPI;
+    double nz = sin(x*TwoPI)/TwoPI;
+    double nw = sin(y*TwoPI)/TwoPI;
+
+    for(unsigned i=0; i<o; ++i)
+    {
+        n += Noise(nx*freq, ny*freq, nz*freq, nw*freq) * amp;
+        maxamp += amp;
+        amp *= _pers;
+        freq *= _lacunarity;
+    }
+
+    if ( _normalize )
+    {
+        n /= maxamp;
+        n = n * (_high-_low)/2.0 + (_high+_low)/2.0;
+    }
+    return n;
+}
+
+double SimplexNoise::getTiledValueWithTurbulence(double x, double y, double F) const
+{
+    const double TwoPI = 2.0 * osg::PI;
+    double freq = _freq;
+    double o = std::max(1u, _octaves);
+    double amp = 1.0;
+    double maxamp = 0.0;
+    double n = 0.0;
+    
+    double nx = cos(x*TwoPI)/TwoPI;
+    double ny = cos(y*TwoPI)/TwoPI;
+    double nz = sin(x*TwoPI)/TwoPI;
+    double nw = sin(y*TwoPI)/TwoPI;
+
+    for(unsigned i=0; i<o; ++i)
+    {
+        float t = -0.5f, FF = F;
+        for(; FF<127.0f; FF*=2.0)
+            t += fabs(getValue(nx*freq/F, ny*freq/F, nz*freq/F, nw*freq/F));
+        n += t * amp;
+        maxamp += amp;
+        amp *= _pers;
+        freq *= _lacunarity;
+    }
+
+    if ( _normalize )
+    {
+        n /= maxamp;
+        n = n * (_high-_low)/2.0 + (_high+_low)/2.0;
+    }
+    return n;
+}
+
 double SimplexNoise::getValue(double xin, double yin) const
 {
     double freq = _freq;
@@ -154,8 +219,11 @@ double SimplexNoise::getValue(double xin, double yin) const
         amp *= _pers;
         freq *= _lacunarity;
     }
-    n /= maxamp;
-    n = n * (_high-_low)/2.0 + (_high+_low)/2.0;
+    if ( _normalize )
+    {
+        n /= maxamp;
+        n = n * (_high-_low)/2.0 + (_high+_low)/2.0;
+    }
     return n;
 }
 
@@ -174,8 +242,12 @@ double SimplexNoise::getValue(double xin, double yin, double zin) const
         amp *= _pers;
         freq *= _lacunarity;
     }
-    n /= maxamp;
-    n = n * (_high-_low)/2.0 + (_high+_low)/2.0;
+    
+    if ( _normalize )
+    {
+        n /= maxamp;
+        n = n * (_high-_low)/2.0 + (_high+_low)/2.0;
+    }
     return n;
 }
 
@@ -194,8 +266,12 @@ double SimplexNoise::getValue(double xin, double yin, double zin, double win) co
         amp *= _pers;
         freq *= _lacunarity;
     }
-    n /= maxamp;
-    n = n * (_high-_low)/2.0 + (_high+_low)/2.0;
+    
+    if ( _normalize )
+    {
+        n /= maxamp;
+        n = n * (_high-_low)/2.0 + (_high+_low)/2.0;
+    }
     return n;
 }
 
diff --git a/src/osgEarthUtil/Sky b/src/osgEarthUtil/Sky
index dcab1df..9f86fa6 100644
--- a/src/osgEarthUtil/Sky
+++ b/src/osgEarthUtil/Sky
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
@@ -167,6 +170,10 @@ namespace osgEarth { namespace Util
         /** Access the osg::Light representing the sun */
         virtual osg::Light* getSunLight() = 0;
 
+        /** Sets a minimum ambient lighting value. */
+        virtual void setMinimumAmbient(const osg::Vec4f& ambient);
+        const osg::Vec4& getMinimumAmbient() const { return _minimumAmbient; }
+
     public:
         
         /** Attaches this sky node to a view (placing a sky light). Optional */
@@ -186,6 +193,7 @@ namespace osgEarth { namespace Util
         virtual void onSetMoonVisible() { }
         virtual void onSetStarsVisible() { }
         virtual void onSetSunVisible() { }
+        virtual void onSetMinimumAmbient() { }
 
     private:
 
@@ -194,6 +202,7 @@ namespace osgEarth { namespace Util
         bool                    _sunVisible;
         bool                    _moonVisible;
         bool                    _starsVisible;
+        osg::Vec4f              _minimumAmbient;
         optional<GeoPoint>      _refpoint;
 
         osg::StateAttribute::OverrideValue _lightingValue;
diff --git a/src/osgEarthUtil/Sky.cpp b/src/osgEarthUtil/Sky.cpp
index b790b73..1c63d3c 100644
--- a/src/osgEarthUtil/Sky.cpp
+++ b/src/osgEarthUtil/Sky.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
@@ -52,6 +55,9 @@ SkyNode::baseInit(const SkyOptions& options)
     _sunVisible = true;
     _moonVisible = true;
     _starsVisible = true;
+    _minimumAmbient.set(0.0f, 0.0f, 0.0f, 0.0f);
+
+    _lightingUniformsHelper = new UpdateLightingUniformsHelper();
 
     setLighting( osg::StateAttribute::ON );
 
@@ -99,7 +105,7 @@ SkyNode::setLighting(osg::StateAttribute::OverrideValue value)
     _lightingUniform = Registry::shaderFactory()->createUniformForGLMode(
         GL_LIGHTING, value );
 
-    this->getOrCreateStateSet()->addUniform( _lightingUniform.get() );
+    this->getOrCreateStateSet()->addUniform( _lightingUniform.get(), value );
 }
 
 void
@@ -124,6 +130,13 @@ SkyNode::setStarsVisible(bool value)
 }
 
 void
+SkyNode::setMinimumAmbient(const osg::Vec4f& value)
+{
+    _minimumAmbient = value;
+    onSetMinimumAmbient();
+}
+
+void
 SkyNode::traverse(osg::NodeVisitor& nv)
 {
     if ( nv.getVisitorType() == nv.CULL_VISITOR )
diff --git a/src/osgEarthUtil/SpatialData b/src/osgEarthUtil/SpatialData
index 7239d02..166c54d 100644
--- a/src/osgEarthUtil/SpatialData
+++ b/src/osgEarthUtil/SpatialData
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthUtil/SpatialData.cpp b/src/osgEarthUtil/SpatialData.cpp
index 854a06e..9a921a8 100644
--- a/src/osgEarthUtil/SpatialData.cpp
+++ b/src/osgEarthUtil/SpatialData.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthUtil/StarData b/src/osgEarthUtil/StarData
index ab46e13..16c6265 100644
--- a/src/osgEarthUtil/StarData
+++ b/src/osgEarthUtil/StarData
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/osgEarthUtil/TFS b/src/osgEarthUtil/TFS
index 787262b..037eee7 100644
--- a/src/osgEarthUtil/TFS
+++ b/src/osgEarthUtil/TFS
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/osgEarthUtil/TFS.cpp b/src/osgEarthUtil/TFS.cpp
index bc1a10d..e7279fc 100644
--- a/src/osgEarthUtil/TFS.cpp
+++ b/src/osgEarthUtil/TFS.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthUtil/TFSPackager b/src/osgEarthUtil/TFSPackager
index 3036132..7878eda 100644
--- a/src/osgEarthUtil/TFSPackager
+++ b/src/osgEarthUtil/TFSPackager
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthUtil/TFSPackager.cpp b/src/osgEarthUtil/TFSPackager.cpp
index d7343bf..86c4110 100644
--- a/src/osgEarthUtil/TFSPackager.cpp
+++ b/src/osgEarthUtil/TFSPackager.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/osgEarthUtil/TMS b/src/osgEarthUtil/TMS
index 7fbd0aa..a58361a 100644
--- a/src/osgEarthUtil/TMS
+++ b/src/osgEarthUtil/TMS
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/osgEarthUtil/TMS.cpp b/src/osgEarthUtil/TMS.cpp
index f68b348..fb58afd 100644
--- a/src/osgEarthUtil/TMS.cpp
+++ b/src/osgEarthUtil/TMS.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -766,6 +766,7 @@ TileMapServiceReader::read( const Config& conf, TileMapEntryList& tileMaps)
     if (!TileMapServiceConf)
     {
         OE_NOTICE << "Couldn't find root TileMapService element" << std::endl;
+        return false;
     }
 
     const Config* TileMapsConf = TileMapServiceConf->find("tilemaps");
diff --git a/src/osgEarthUtil/TMSBackFiller b/src/osgEarthUtil/TMSBackFiller
index e48751b..11a6d74 100644
--- a/src/osgEarthUtil/TMSBackFiller
+++ b/src/osgEarthUtil/TMSBackFiller
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/osgEarthUtil/TMSBackFiller.cpp b/src/osgEarthUtil/TMSBackFiller.cpp
index 99c8dba..ab4057c 100644
--- a/src/osgEarthUtil/TMSBackFiller.cpp
+++ b/src/osgEarthUtil/TMSBackFiller.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/osgEarthUtil/TMSPackager b/src/osgEarthUtil/TMSPackager
index e9ed51f..f64917e 100644
--- a/src/osgEarthUtil/TMSPackager
+++ b/src/osgEarthUtil/TMSPackager
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
@@ -116,6 +119,16 @@ namespace osgEarth { namespace Util
         void setKeepEmpties(bool keepEmpties);
 
         /**
+         * Gets whether to alpha mask out portions of imagery that aren't contained in the specified bounds.
+         */
+        bool getApplyAlphaMask() const;
+
+        /**
+         * Sets whether to alpha mask out portions of imagery that aren't contained in the specified bounds.
+         */
+        void setApplyAlphaMask(bool applyAlphaMask);
+
+        /**
          * Gets the image write options.
          */
         osgDB::Options* getOptions() const;
@@ -169,6 +182,8 @@ namespace osgEarth { namespace Util
 
         bool _keepEmpties;
 
+        bool _applyAlphaMask;
+
         osg::ref_ptr< TileVisitor > _visitor;
         osg::ref_ptr< WriteTMSTileHandler > _handler;
 
diff --git a/src/osgEarthUtil/TMSPackager.cpp b/src/osgEarthUtil/TMSPackager.cpp
index 0547f1b..078dfb9 100644
--- a/src/osgEarthUtil/TMSPackager.cpp
+++ b/src/osgEarthUtil/TMSPackager.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -66,7 +66,9 @@ bool WriteTMSTileHandler::handleTile(const TileKey& key, const TileVisitor& tv)
 
     // Don't write out a new file if we're not overwriting
     if (osgDB::fileExists(path) && !_packager->getOverwrite())
+    {
         return true;
+    }
 
     // attempt to create the output folder:        
     osgEarth::makeDirectoryForFile( path );       
@@ -84,12 +86,15 @@ bool WriteTMSTileHandler::handleTile(const TileKey& key, const TileVisitor& tv)
                 return false;
             }
 
-            // mask out areas not included in the request:
-            for(std::vector<GeoExtent>::const_iterator g = tv.getExtents().begin();
-                g != tv.getExtents().end();
-                ++g)
+            if (_packager->getApplyAlphaMask())
             {
-                geoImage.applyAlphaMask( *g );
+                // mask out areas not included in the request:
+                for(std::vector<GeoExtent>::const_iterator g = tv.getExtents().begin();
+                    g != tv.getExtents().end();
+                    ++g)
+                {
+                    geoImage.applyAlphaMask( *g );
+                }
             }
 
             // OE_NOTICE << "Created image for " << key.str() << std::endl;
@@ -114,6 +119,13 @@ bool WriteTMSTileHandler::handleTile(const TileKey& key, const TileVisitor& tv)
             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 
+    // traverse the children b/c a min level was set.
+    if (!_layer->isKeyInRange(key))
+    {
+        return true;
+    }
     return false;        
 } 
 
@@ -169,6 +181,10 @@ std::string WriteTMSTileHandler::getProcessString() const
     {
         buf << " --overwrite ";
     }            
+    if (_packager->getApplyAlphaMask())
+    {
+        buf << " --alpha-mask ";
+    }
     return buf.str();
 }
 
@@ -183,7 +199,8 @@ _visitor(new TileVisitor()),
     _width(0),
     _height(0),
     _overwrite(false),
-    _keepEmpties(false)
+    _keepEmpties(false),
+    _applyAlphaMask(false)
 {
 }
 
@@ -257,6 +274,16 @@ void TMSPackager::setKeepEmpties(bool keepEmpties)
     _keepEmpties = keepEmpties;
 }
 
+bool TMSPackager::getApplyAlphaMask() const
+{
+    return _applyAlphaMask;
+}
+
+void TMSPackager::setApplyAlphaMask(bool applyAlphaMask)
+{
+    _applyAlphaMask = applyAlphaMask;
+}
+
 TileVisitor* TMSPackager::getTileVisitor() const
 {
     return _visitor;
@@ -324,56 +351,33 @@ void TMSPackager::run( TerrainLayer* layer,  Map* map  )
 
     if (imageLayer)
     {
-        GeoImage testImage;
-        for( std::vector<TileKey>::iterator i = rootKeys.begin(); i != rootKeys.end() && !testImage.valid(); ++i )
+        int tileSize = imageLayer->getTileSize();
+        _width = tileSize;
+        _height = tileSize;
+
+        // Figure out the extension if we haven't already assigned one.
+        if (_extension.empty())
         {
-            testImage = imageLayer->createImage( *i );
+            // Just default to whatever the source reports as it's extension.
+            _extension = imageLayer->getTileSource()->getExtension();
         }
-        if (testImage.valid())
-        {
-            _width = testImage.getImage()->s();
-            _height = testImage.getImage()->t();
 
-            bool alphaChannelRequired =
-                ImageUtils::hasAlphaChannel(testImage.getImage()) ||
-                _visitor->getExtents().size() > 0;
+        if (_extension == "jpg" && _applyAlphaMask)
+        {
+            _extension = "png";
+            OE_NOTICE << LC << "Extension changed to PNG since output requires an alpha channel" << std::endl;
+        }
 
-            // Figure out the extension if we haven't already assigned one.
-            if (_extension.empty())
-            {
-                if (alphaChannelRequired)
-                {
-                    _extension = "png";
-                }
-                else
-                {
-                    _extension = "jpg";
-                }
-            }
-            else if (_extension == "jpg" && alphaChannelRequired)
-            {
-                _extension = "png";
-                OE_NOTICE << LC << "Extension changed to PNG since output requires an alpha channel" << std::endl;
-            }
+        OE_INFO << LC << "Output extension: " << _extension << std::endl;
 
-            OE_INFO << LC << "Output extension: " << _extension << std::endl;
-        }
     }
     else if (elevationLayer)
     {
         // We must use tif no matter what with elevation layers.  It's the only format that currently can read/write single band imagery.
         _extension = "tif";
-        GeoHeightField testHF;
-        for( std::vector<TileKey>::iterator i = rootKeys.begin(); i != rootKeys.end() && !testHF.valid(); ++i )
-        {
-            testHF = elevationLayer->createHeightField( *i );
-        }
-
-        if (testHF.valid())
-        {
-            _width = testHF.getHeightField()->getNumColumns();
-            _height = testHF.getHeightField()->getNumRows();
-        }
+        int tileSize = elevationLayer->getTileSize();
+        _width = tileSize;
+        _height = tileSize;
     }
 
 
diff --git a/src/osgEarthUtil/TerrainProfile b/src/osgEarthUtil/TerrainProfile
index 6fddd40..a4aa268 100644
--- a/src/osgEarthUtil/TerrainProfile
+++ b/src/osgEarthUtil/TerrainProfile
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/osgEarthUtil/TerrainProfile.cpp b/src/osgEarthUtil/TerrainProfile.cpp
index 6fa98c2..3ebd2db 100644
--- a/src/osgEarthUtil/TerrainProfile.cpp
+++ b/src/osgEarthUtil/TerrainProfile.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/osgEarthUtil/TextureSplatter b/src/osgEarthUtil/TextureSplatter
deleted file mode 100644
index 05a4127..0000000
--- a/src/osgEarthUtil/TextureSplatter
+++ /dev/null
@@ -1,121 +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 OSGEARTHUTIL_TEXTURE_SPLATTER_H
-#define OSGEARTHUTIL_TEXTURE_SPLATTER_H
-
-#include <osgEarthUtil/Common>
-#include <osgEarth/TerrainEffect>
-#include <osgEarth/ImageLayer>
-#include <osgEarth/URI>
-#include <osg/Image>
-#include <osg/Uniform>
-#include <osg/Texture2DArray>
-
-using namespace osgEarth;
-
-namespace osgEarth {
-class Map;
-
-namespace Util
-{
-    /**
-     * Controller that splats texture on the terrain according to a
-     * color mask.
-     */
-    class OSGEARTHUTIL_EXPORT TextureSplatter : public TerrainEffect
-    {
-    public:
-        /** construct a new splatting controller */
-        TextureSplatter();
-
-        /**
-         * The image layer that generates the splat mask. You must call
-         * this prior to installing the effect. The layer must be installed
-         * in the Map with shared=true.
-         */            
-        void setMaskLayer(ImageLayer* layer) { _maskLayer = layer; }
-        ImageLayer* getMaskLayer() { return _maskLayer.get(); }
-
-        /** Sets the LOD at which to start detailing */
-        void setStartLOD( unsigned lod );
-        unsigned getStartLOD() const { return _startLOD.get(); }
-
-        /** Sets the intensity (0=none, 1=full) */
-        void setIntensity( float value );
-        float getIntensity() const { return _intensity.get(); }
-
-        /** Sets the attenuation distance, i.e. the distance from the camera
-            at which the detail texture will fade out completely. */
-        void setAttenuationDistance( float value );
-        float getAttenuationDistance() const { return _attenuationDistance.get(); }
-
-        /** Sets the scale factor for the texture (1..) */
-        void setScale( float value );
-        float getScale() const { return _scale.get(); }
-
-        /** Whether to use the inverse of the mask */
-        void setInvert( bool value );
-        bool getInvert() const { return _invert.get(); }
-
-    public: // TerrainEffect interface
-
-        void onInstall(TerrainEngineNode* engine);
-        void onUninstall(TerrainEngineNode* engine);
-
-    public: // serialization
-
-        TextureSplatter(const Config& conf, const Map* map);
-        void mergeConfig(const Config& conf);
-        virtual Config getConfig() const;
-
-    protected:
-        virtual ~TextureSplatter() { }
-        void init();
-
-        optional<float>          _intensity;
-        optional<unsigned>       _startLOD;
-        optional<std::string>    _maskLayerName;
-        optional<float>          _scale;
-        optional<float>          _attenuationDistance;
-        optional<bool>           _invert;
-
-        struct TextureSource {
-            std::string _tag;
-            std::string _url;
-        };
-        std::vector<TextureSource> _textures;
-
-        osg::ref_ptr<osg::Uniform>         _intensityUniform;
-        osg::ref_ptr<osg::Uniform>         _startLODUniform;
-        osg::ref_ptr<osg::Uniform>         _scaleUniform;
-        osg::ref_ptr<osg::Uniform>         _attenuationDistanceUniform;
-        osg::ref_ptr<osg::Uniform>         _samplerUniform;
-        osg::ref_ptr<osg::Uniform>         _maskUniform;
-        osg::ref_ptr<osg::Uniform>         _brightnessUniform;
-        osg::ref_ptr<osg::Texture2DArray>  _texture;
-        int                                _unit;
-        osg::observer_ptr<ImageLayer>      _maskLayer;
-        osg::ref_ptr<osgDB::Options>       _dbOptions;
-
-        std::string genFragShader();
-    };
-
-} } // namespace osgEarth::Util
-
-#endif // OSGEARTHUTIL_TEXTURE_SPLATTER_H
diff --git a/src/osgEarthUtil/TextureSplatter.cpp b/src/osgEarthUtil/TextureSplatter.cpp
deleted file mode 100644
index 1bf3a12..0000000
--- a/src/osgEarthUtil/TextureSplatter.cpp
+++ /dev/null
@@ -1,362 +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 <osgEarthUtil/TextureSplatter>
-#include <osgEarth/Registry>
-#include <osgEarth/Capabilities>
-#include <osgEarth/VirtualProgram>
-#include <osgEarth/TerrainEngineNode>
-#include <osgEarth/ImageUtils>
-#include <osgEarth/URI>
-
-#define LC "[TextureSplatter] "
-
-using namespace osgEarth;
-using namespace osgEarth::Util;
-
-namespace
-{
-    const char* vs =
-        "#version " GLSL_VERSION_STR "\n"
-        GLSL_DEFAULT_PRECISION_FLOAT "\n"
-
-        "uniform vec4 oe_tile_key; \n"
-        "uniform float oe_splat_L0; \n"
-        "uniform float oe_splat_scale; \n"
-        "uniform float oe_splat_attenuation_distance; \n"
-
-        "varying vec4 oe_layer_tilec; \n"
-        "varying vec2 oe_splat_tc; \n"
-        "varying float oe_splat_atten_factor; \n"
-
-        "int oe_splat_ipow(in int x, in int y) { \n"
-        "   int r = 1; \n"
-        "   while( y > 0 ) { \n"
-        "       r *= x; \n"
-        "       --y; \n"
-        "   } \n"
-        "   return r; \n"
-        "}\n"
-
-        "void oe_splat_vertex(inout vec4 VertexVIEW) \n"
-        "{ \n"
-        "    float dL = oe_tile_key.z - oe_splat_L0; \n"
-        "    float twoPowDeltaL = float(oe_splat_ipow(2, int(abs(dL)))); \n"
-        "    float factor = dL >= 0.0 ? twoPowDeltaL : 1.0/twoPowDeltaL; \n"
-
-        "    vec2 a = floor(oe_tile_key.xy / factor); \n"
-        "    vec2 b = a * factor; \n"
-        "    vec2 c = (a+1.0) * factor; \n"
-        "    vec2 offset = (oe_tile_key.xy-b)/(c-b); \n"
-        "    vec2 scale = vec2(1.0/factor); \n"
-
-        "    float tscale = pow(2.0, oe_splat_scale-1.0); \n"
-        "    oe_splat_tc = tscale * ((oe_layer_tilec.st * scale) + offset); \n"
-        
-        "    float r = 1.0-((-VertexVIEW.z/VertexVIEW.w)/oe_splat_attenuation_distance);\n"
-        "    oe_splat_atten_factor = clamp(r, 0.0, 1.0); \n"
-        "} \n";
-
-}
-
-
-TextureSplatter::TextureSplatter() :
-TerrainEffect(),
-_startLOD    ( 10 ),
-_intensity   ( 1.0f ),
-_scale       ( 1.0f ),
-_attenuationDistance( FLT_MAX )
-{
-    init();
-}
-
-TextureSplatter::TextureSplatter(const Config& conf, const Map* map) :
-TerrainEffect(),
-_startLOD    ( 10 ),
-_intensity   ( 1.0f ),
-_scale       ( 1.0f ),
-_attenuationDistance( FLT_MAX )
-{
-    mergeConfig(conf);
-
-    if ( map )
-    {
-        if ( _maskLayerName.isSet() )
-        {
-            _maskLayer = map->getImageLayerByName(*_maskLayerName);
-        }
-
-        _dbOptions = Registry::instance()->cloneOrCreateOptions(map->getDBOptions());        
-    }
-
-    init();
-}
-
-
-void
-TextureSplatter::init()
-{
-    // negative means unset:
-    _unit = -1;
-
-    _startLODUniform   = new osg::Uniform(osg::Uniform::FLOAT, "oe_splat_L0");
-    _startLODUniform->set( (float)_startLOD.get() );
-
-    _intensityUniform = new osg::Uniform(osg::Uniform::FLOAT, "oe_splat_intensity");
-    _intensityUniform->set( _intensity.get() );
-
-    _scaleUniform = new osg::Uniform(osg::Uniform::FLOAT, "oe_splat_scale");
-    _scaleUniform->set( _scale.get() );
-
-    _attenuationDistanceUniform = new osg::Uniform(osg::Uniform::FLOAT, "oe_splat_attenuation_distance");
-    _attenuationDistanceUniform->set( _attenuationDistance.get() );
-
-    _brightnessUniform = new osg::Uniform(osg::Uniform::FLOAT, "oe_splat_brightness");
-    _brightnessUniform->set( 1.0f );
-}
-
-
-void
-TextureSplatter::setStartLOD(unsigned lod)
-{
-    if ( lod != _startLOD.get() )
-    {
-        _startLOD = lod;
-        _startLODUniform->set( (float)_startLOD.get() );
-    }
-}
-
-
-void
-TextureSplatter::setIntensity(float intensity)
-{
-    _intensity = osg::clampBetween( intensity, 0.0f, 1.0f );
-    _intensityUniform->set( _intensity.get() );
-}
-
-
-void
-TextureSplatter::setScale(float scale)
-{
-    _scale = osg::clampAbove( scale, 1.0f );
-    _scaleUniform->set( _scale.get() );
-}
-
-
-void
-TextureSplatter::setAttenuationDistance(float value)
-{
-    _attenuationDistance = osg::clampAbove(value, 1.0f);
-    _attenuationDistanceUniform->set( _attenuationDistance.get() );
-}
-
-
-void
-TextureSplatter::onInstall(TerrainEngineNode* engine)
-{
-    if ( engine )
-    {
-        if ( !_texture.valid() )
-        {
-            _texture = new osg::Texture2DArray();
-            _texture->setTextureSize(1024, 1024, _textures.size());
-            _texture->setWrap( osg::Texture::WRAP_S, osg::Texture::REPEAT );
-            _texture->setWrap( osg::Texture::WRAP_T, osg::Texture::REPEAT );
-            _texture->setFilter( osg::Texture::MIN_FILTER, osg::Texture::LINEAR_MIPMAP_LINEAR );
-            _texture->setFilter( osg::Texture::MAG_FILTER, osg::Texture::LINEAR );
-            _texture->setResizeNonPowerOfTwoHint( false );
-
-            for(unsigned i=0; i<_textures.size(); ++i)
-            {
-                const TextureSource& ts = _textures[i];
-                osg::ref_ptr<osg::Image> image = URI(ts._url).getImage(_dbOptions.get());
-                if ( image->s() != 1024 || image->t() != 1024 )
-                {
-                    osg::ref_ptr<osg::Image> imageResized;
-                    ImageUtils::resizeImage( image.get(), 1024, 1024, imageResized );
-                    _texture->setImage( i, imageResized.get() );
-                }
-                else
-                {
-                    _texture->setImage( i, image.get() );
-                }
-            }
-
-            OE_INFO << LC << "Loaded " << _textures.size() << " splat textures" << std::endl;
-        }
-
-        osg::StateSet* stateset = engine->getOrCreateStateSet();
-
-        if ( engine->getTextureCompositor()->reserveTextureImageUnit(_unit) )
-        {
-            _samplerUniform = stateset->getOrCreateUniform( "oe_splat_tex", osg::Uniform::SAMPLER_2D_ARRAY );
-            _samplerUniform->set( _unit );
-            stateset->setTextureAttribute( _unit, _texture.get(), osg::StateAttribute::ON ); // don't use "..andModes"
-        }
-
-        if ( _maskLayer.valid() )
-        {
-            int unit = *_maskLayer->shareImageUnit();
-            _maskUniform = stateset->getOrCreateUniform("oe_splat_mask", osg::Uniform::SAMPLER_2D);
-            _maskUniform->set(unit);
-            OE_NOTICE << LC << "Installed layer " << _maskLayer->getName() << " as texture mask on unit " << unit << std::endl;
-        }
-        else
-        {
-            exit(-1);
-        }
-
-        stateset->addUniform( _startLODUniform.get() );
-        stateset->addUniform( _intensityUniform.get() );
-        stateset->addUniform( _scaleUniform.get() );
-        stateset->addUniform( _attenuationDistanceUniform.get() );
-        stateset->addUniform( _brightnessUniform.get() );
-
-        std::string fs = genFragShader();
-        VirtualProgram* vp = VirtualProgram::getOrCreate(stateset);
-        vp->setFunction( "oe_splat_vertex",   vs, ShaderComp::LOCATION_VERTEX_VIEW );
-        vp->setFunction( "oe_splat_fragment", fs, ShaderComp::LOCATION_FRAGMENT_COLORING );
-    }
-}
-
-
-void
-TextureSplatter::onUninstall(TerrainEngineNode* engine)
-{
-    osg::StateSet* stateset = engine->getStateSet();
-    if ( stateset )
-    {
-        stateset->removeUniform( _startLODUniform.get() );
-        stateset->removeUniform( _intensityUniform.get() );
-        stateset->removeUniform( _scaleUniform.get() );
-        stateset->removeUniform( _attenuationDistanceUniform.get() );
-        stateset->removeUniform( _brightnessUniform.get() );
-
-        if ( _samplerUniform.valid() )
-        {
-            int unit;
-            _samplerUniform->get(unit);
-            stateset->removeUniform( _samplerUniform.get() );
-            stateset->removeTextureAttribute(unit, osg::StateAttribute::TEXTURE);
-        }
-
-        VirtualProgram* vp = VirtualProgram::get(stateset);
-        if ( vp )
-        {
-            vp->removeShader( "oe_splat_vertex" );
-            vp->removeShader( "oe_splat_fragment" );
-        }
-    }
-
-    if ( _unit >= 0 )
-    {
-        engine->getTextureCompositor()->releaseTextureImageUnit( _unit );
-        _unit = -1;
-    }
-}
-
-std::string
-TextureSplatter::genFragShader()
-{
-    std::stringstream buf;
-
-    buf <<
-        "#version " GLSL_VERSION_STR "\n"
-        "#extension GL_EXT_texture_array : enable\n"
-        GLSL_DEFAULT_PRECISION_FLOAT "\n"
-
-        "uniform vec4 oe_tile_key; \n"
-        "uniform float oe_splat_L0; \n"
-        "uniform sampler2D oe_splat_mask; \n"
-        "uniform sampler2DArray oe_splat_tex; \n"
-        "uniform float oe_splat_intensity; \n"
-        "uniform float oe_splat_brightness; \n"
-        "varying vec2 oe_splat_tc; \n"
-        "varying vec4 oe_layer_tilec; \n"
-        "varying float oe_splat_atten_factor; \n"
-
-        "void oe_splat_fragment(inout vec4 color) \n"
-        "{ \n"
-        "    if ( oe_tile_key.z >= oe_splat_L0 ) \n"
-        "    { \n"
-        "        vec4 m = texture2D(oe_splat_mask, oe_layer_tilec.st); \n"
-        "        vec4 texel; \n";
-
-    //TODO: cycle through the textures and splat splat spalt.
-    buf <<
-        "        texel = texture2DArray(oe_splat_tex, vec3(oe_splat_tc, 0.0));\n"
-        "        texel.rgb *= oe_splat_brightness;\n"
-        "        color.rgb = mix(color.rgb, texel.rgb, m.a * oe_splat_intensity * oe_splat_atten_factor);\n";
-
-    buf <<
-        "    } \n"
-        "} \n";
-
-    std::string r;
-    r = buf.str();
-    return r;
-}
-
-//-------------------------------------------------------------
-
-void
-TextureSplatter::mergeConfig(const Config& conf)
-{
-    conf.getIfSet( "start_lod",  _startLOD );
-    conf.getIfSet( "intensity",  _intensity );
-    conf.getIfSet( "scale",      _scale );
-    conf.getIfSet( "attenuation_distance", _attenuationDistance );
-    conf.getIfSet( "mask_layer", _maskLayerName );
-
-    ConfigSet textures = conf.child("textures").children("texture");
-    for(ConfigSet::iterator i = textures.begin(); i != textures.end(); ++i)
-    {
-        _textures.push_back(TextureSource());
-        _textures.back()._tag = i->value("tag");
-        _textures.back()._url = i->value("url");
-    }
-}
-
-Config
-TextureSplatter::getConfig() const
-{
-    Config conf("texture_splatter");
-
-    optional<std::string> layername;
-    if ( _maskLayer.valid() && !_maskLayer->getName().empty() )
-        layername = _maskLayer->getName();
-
-    conf.addIfSet( "start_lod",  _startLOD );
-    conf.addIfSet( "intensity",  _intensity );
-    conf.addIfSet( "scale",      _scale );
-    conf.addIfSet( "attenuation_distance", _attenuationDistance );
-    conf.addIfSet( "mask_layer", layername );
-
-    if ( _textures.size() > 0 )
-    {
-        Config textures("textures");
-        for(std::vector<TextureSource>::const_iterator i = _textures.begin(); i != _textures.end(); ++i )
-        {
-            Config texture("texture");
-            texture.set("tag", i->_tag);
-            texture.set("url", i->_url);
-        }
-    }
-
-    return conf;
-}
diff --git a/src/osgEarthUtil/TileIndex b/src/osgEarthUtil/TileIndex
index 3d8cc6a..edeafb0 100644
--- a/src/osgEarthUtil/TileIndex
+++ b/src/osgEarthUtil/TileIndex
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/osgEarthUtil/TileIndex.cpp b/src/osgEarthUtil/TileIndex.cpp
index ca35d93..c3565b4 100644
--- a/src/osgEarthUtil/TileIndex.cpp
+++ b/src/osgEarthUtil/TileIndex.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/osgEarthUtil/TileIndexBuilder b/src/osgEarthUtil/TileIndexBuilder
index 0ff168e..a5c3c58 100644
--- a/src/osgEarthUtil/TileIndexBuilder
+++ b/src/osgEarthUtil/TileIndexBuilder
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/osgEarthUtil/TileIndexBuilder.cpp b/src/osgEarthUtil/TileIndexBuilder.cpp
index c399960..1172100 100644
--- a/src/osgEarthUtil/TileIndexBuilder.cpp
+++ b/src/osgEarthUtil/TileIndexBuilder.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/osgEarthUtil/UTMGraticule b/src/osgEarthUtil/UTMGraticule
index a729506..7f1cf6d 100644
--- a/src/osgEarthUtil/UTMGraticule
+++ b/src/osgEarthUtil/UTMGraticule
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthUtil/UTMGraticule.cpp b/src/osgEarthUtil/UTMGraticule.cpp
index bc37c88..5257549 100644
--- a/src/osgEarthUtil/UTMGraticule.cpp
+++ b/src/osgEarthUtil/UTMGraticule.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -107,9 +107,11 @@ UTMGraticule::init()
     }
 
     // make the shared depth attr:
-    //_depthAttribute = new osg::Depth(osg::Depth::LEQUAL,0,1,false);
     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() );
 
diff --git a/src/osgEarthUtil/VerticalScale b/src/osgEarthUtil/VerticalScale
index bd86cb6..312c90d 100644
--- a/src/osgEarthUtil/VerticalScale
+++ b/src/osgEarthUtil/VerticalScale
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/osgEarthUtil/VerticalScale.cpp b/src/osgEarthUtil/VerticalScale.cpp
index 6d7b8bd..3bb5200 100644
--- a/src/osgEarthUtil/VerticalScale.cpp
+++ b/src/osgEarthUtil/VerticalScale.cpp
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
@@ -104,7 +107,7 @@ VerticalScale::onInstall(TerrainEngineNode* engine)
         stateset->addUniform( _scaleUniform.get() );
 
         VirtualProgram* vp = VirtualProgram::getOrCreate(stateset);
-        vp->setFunction( "oe_vertscale_vertex", vs, ShaderComp::LOCATION_VERTEX_MODEL );
+        vp->setFunction( "oe_vertscale_vertex", vs, ShaderComp::LOCATION_VERTEX_MODEL, 0.5f);
     }
 }
 
diff --git a/src/osgEarthUtil/WFS b/src/osgEarthUtil/WFS
index da80abc..357b558 100644
--- a/src/osgEarthUtil/WFS
+++ b/src/osgEarthUtil/WFS
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/osgEarthUtil/WFS.cpp b/src/osgEarthUtil/WFS.cpp
index 0e2af0d..b396da7 100644
--- a/src/osgEarthUtil/WFS.cpp
+++ b/src/osgEarthUtil/WFS.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthUtil/WMS b/src/osgEarthUtil/WMS
index 927a604..ed2ab22 100644
--- a/src/osgEarthUtil/WMS
+++ b/src/osgEarthUtil/WMS
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2014 Pelican Mapping
+* Copyright 2015 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -8,10 +8,13 @@
 * 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.
+* 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/>
diff --git a/src/osgEarthUtil/WMS.cpp b/src/osgEarthUtil/WMS.cpp
index fa90f04..e45d64f 100644
--- a/src/osgEarthUtil/WMS.cpp
+++ b/src/osgEarthUtil/WMS.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2014 Pelican Mapping
+ * Copyright 2015 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/tests/annotation.earth b/tests/annotation.earth
index c17a17f..389f4f7 100644
--- a/tests/annotation.earth
+++ b/tests/annotation.earth
@@ -29,11 +29,11 @@ osgEarth Sample - Annotations
         <annotations>
         
             <label text="Los Angeles">
-                <position lat="34.051" long="-117.974" alt="100" mode="relative"/>
+                <position lat="35.051" long="-117.974" alt="100" mode="relative"/>
                 <style type="text/css">
                     text-align:     center_center;
                     text-size:      20;
-                    text-declutter: true;
+                    text-declutter: false;
                 </style>
             </label>
             
diff --git a/tests/annotation_flat.earth b/tests/annotation_flat.earth
index e2925a9..b5d565b 100644
--- a/tests/annotation_flat.earth
+++ b/tests/annotation_flat.earth
@@ -3,10 +3,8 @@ osgEarth Sample - Annotations
 -->
 <map name="readymap.org" type="projected" version="2">
 
-    <image name="mapquest_osm" driver="xyz">
-        <url>http://otile[1234].mqcdn.com/tiles/1.0.0/osm/{z}/{x}/{y}.jpg</url>
-        <profile>global-mercator</profile>
-        <cache_policy usage="no_cache"/>
+    <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">
@@ -16,78 +14,157 @@ osgEarth Sample - Annotations
     <options>
         <terrain>
             <lighting>false</lighting>
-            <sample_ratio>0.125</sample_ratio>
         </terrain>
-        <!-- <cache_policy usage="cache_only"/> -->
+		<profile>spherical-mercator</profile>
     </options>
     
     <external>
-        <viewpoint name="Annotation Samples" heading="35.27" lat="33" long="-118" pitch="-35" range="500000"/>
+        <viewpoints>
+            <viewpoint name="Annotation Samples" 
+                       lat="33" long="-118" range="500000"
+                       heading="35.27" pitch="-35" />
+        </viewpoints>
         
-        <annotations declutter="true">
+        <annotations>
         
             <label text="Los Angeles">
                 <position lat="34.051" long="-117.974" alt="100" mode="relative"/>
                 <style type="text/css">
-                    text-align: center_center;
-                    text-size:  20;
-                    text-halo:  #000000;
+                    text-align:     center_center;
+                    text-size:      20;
+                    text-declutter: true;
                 </style>
             </label>
             
             <place text="San Diego">
                 <position lat="32.73" long="-117.17"/>
                 <icon>http://demo.pelicanmapping.com/icons/gmaps/yoga.png</icon>
+                <style type="text/css">
+                    text-declutter: true;
+                </style>
             </place>
             
-            <circle draped="true">
+            <circle name="draped circle">
                 <position lat="34.051" long="-117.974"/>
                 <radius value="50" units="km"/>
-                <style type="text/css">fill: #ffff005f;</style>
+                <style type="text/css">
+                    fill:               #ffff0080;
+                    altitude-clamping:  terrain;
+                    altitude-technique: drape;
+                </style>
             </circle>
             
-            <ellipse>
+            <ellipse name="ellipse relative">
+                <position lat="34.051" long="-116" hat="5000"/>
+                <radius_major value="50" units="km"/>
+                <radius_minor value="40" units="km"/>
+                <style type="text/css">
+                    fill: #ffff0080;
+                </style>
+            </ellipse>
+            
+            <ellipse name="ellipse extruded">
                 <position lat="32.73" long="-119.0"/>
                 <radius_major value="50" units="km"/>
                 <radius_minor value="20" units="km"/>
                 <style type="text/css">
-                    fill:             #ff7f008f;
+                    fill:             #ff7f007f;
                     stroke:           #ff0000ff;
                     extrusion-height: 5000;
                 </style>
             </ellipse>
             
-            <rectangle>
+            <rectangle name="absolute rectangle">
                 <position lat="32.2" long="-118" alt="1000"/>
                 <width value="50" units="nm"/>
                 <height value="25" units="nm"/>
-                <style type="text/css"> stroke: #00ffff; stroke-width: 2; </style>
+                <style type="text/css">
+                    stroke:       #00ffff; 
+                    stroke-width: 2; 
+                </style>
             </rectangle>
 
-            <feature>
+            <feature name="extruded line">
                 <srs>wgs84</srs>
                 <geometry>
                     LINESTRING(-120.37 34.039, -120.09 33.96, -119.75 34, -118.43 33.37, -118.48 32.88)
                 </geometry>
                 <style type="text/css">
-                    fill:             #ff00ff7f;
-                    stroke:           #ffff00;
-                    stroke-width:     3;
-                    extrusion-height: 30000;
+                    fill:                #ff00ff7f;
+                    stroke:              #ffff00;
+                    stroke-width:        3;
+					stroke-crease-angle: 45.0;
+                    extrusion-height:    30000;
+                    render-lighting:     false;
                 </style>
             </feature>
+
+            <feature name="gpu clamped line">
+                <srs>wgs84</srs>
+                <geometry>
+                    LINESTRING(-120 37, -120 33, -118 33, -118 32)
+                </geometry>
+                <style type="text/css">
+                    stroke:              #ff3000;
+                    stroke-width:        3;
+                    stroke-tessellation: 100;
+                    altitude-clamping:   terrain;
+                    altitude-technique:  gpu;
+                    render-lighting:     false;
+                </style>
+            </feature>
+            
+            <model name="flag model">
+                <url>../data/red_flag.osg.18000.scale</url>
+                <position lat="33" long="-117.75" hat="0"/>
+            </model>
+            
+            <imageoverlay>
+                <url>../data/fractal.png</url>
+                <alpha>1.0</alpha>
+                <geometry>POLYGON((-81 26, -80.5 26, -80.5 26.5, -81 26.5))</geometry>
+            </imageoverlay>
+            
+            <label text="image overlay">
+                <position lat="26" long="-81" />
+            </label>
             
-            <local_geometry>
+            <local_geometry name="3D geometry">
                 <geometry>
                     POLYGON((0 0 0, -1000 0 25000, -5000 0 25000, 0 0 30000, 5000 0 25000, 1000 0 25000))
                 </geometry>
-                <position lat="33.4" long="-116.6" alt="0"/>
+                <position lat="33.4" long="-116.6" hat="0"/>
                 <style type="text/css">
-                    fill: #00ff00;
-                    stroke: #ffff00;
+                    fill:            #00ff00;
+                    stroke:          #ffff00;
+                    render-lighting: false;
                 </style>
             </local_geometry>
             
+            <circle name="scene-clamped circle">
+                <position lat="33.4" long="-116.6"/>
+                <radius value="5" units="km"/>
+                <style type="text/css">
+                    fill: #afafff9f;
+                    render-lighting: false;
+                </style>
+            </circle>
+
+            <feature name="Long Line">
+                <srs>wgs84</srs>
+                <geometry>
+                    LINESTRING(0 0, 0 65)
+                </geometry>
+                <style type="text/css">
+                    stroke:              #ffff00;
+                    stroke-width:        3;
+                    stroke-tessellation: 90;
+                    altitude-clamping:   relative;
+					altitude-technique:  scene;
+                    render-lighting:     false;
+                </style>
+            </feature>
+            
         </annotations>
         
     </external>
diff --git a/tests/arcgisonline.earth b/tests/arcgisonline.earth
index 1cd13e9..cdd7669 100644
--- a/tests/arcgisonline.earth
+++ b/tests/arcgisonline.earth
@@ -29,7 +29,7 @@ http://resources.esri.com/arcgisonlineservices/index.cfm?fa=content
         <lighting>false</lighting>
     </options>
 	
-	<external>
+	<extensions>
 		<lod_blending/>
-	</external>
+	</extensions>
 </map>
diff --git a/tests/billboard.earth b/tests/billboard.earth
new file mode 100644
index 0000000..0e12037
--- /dev/null
+++ b/tests/billboard.earth
@@ -0,0 +1,39 @@
+<!--
+osgEarth Sample - Geometry Shader Billboard Experimentation
+-->
+<map name="geometry shader test" type="geocentric">
+    
+    <options>
+        <terrain first_lod="1"/>
+    </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/9/</url>
+    </elevation>
+
+    <extensions>
+
+        <billboard>
+
+            <features name="points" driver="ogr">
+                <url>../data/parks.shp</url>
+            </features>
+
+            <image>../data/textures/pine.png</image>
+            <height>20.0</height>
+            <width>15.0</width>
+            <density>15000</density>
+
+        </billboard>
+
+        <viewpoints>
+            <viewpoint name="DC" heading="-23.41597142139858" height="25.89056142792106" lat="38.88766329853004" long="-76.99080836437689" pitch="-24.9647839409773" range="18580.10989103711"/>
+        </viewpoints>
+
+    </extensions>
+
+</map>
diff --git a/tests/boston-gpu.earth b/tests/boston-gpu.earth
new file mode 100644
index 0000000..185de7b
--- /dev/null
+++ b/tests/boston-gpu.earth
@@ -0,0 +1,172 @@
+<!--
+osgEarth Sample.
+
+Demonstrates the use of a Resource Library in order to apply "typical" textures
+to extruded buildings.
+-->
+
+<map name="Boston Demo" type="geocentric" version="2">
+    
+    <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>
+      
+    <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>
+			<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
+           model (no tiling or paging).
+           
+           Each "level" is a level of detail for features, and should select a style
+              to use for that level. It also can specify min and max camera ranges,
+              in meters.
+              
+           The "tile size factor" controls how a feature is tiled. The higher this factor,
+              the smaller the tile size, and the more tiles will be used to render a given
+              level of detail. The default is 15. tile size = max range / tile size factor.
+        -->
+        
+        <layout tile_size_factor="52">
+            <level name="default" max_range="20000" style="buildings"/>
+        </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-wall-gradient: 0.5;
+                    extrusion-roof-style:    building-rooftop;
+                    altitude-clamping:       terrain-gpu;
+                }            
+                building-wall {
+                    skin-library:     us_resources;
+                    skin-tags:        building;
+                    skin-random-seed: 1;
+                }
+                building-rooftop {
+                    skin-library:     us_resources;
+                    skin-tags:        rooftop;
+                    skin-tiled:       true;
+                    skin-random-seed: 1;
+                }
+            </style>
+        </styles>   
+    </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>
+        
+        <layout crop_features="true" tile_size_factor="7.5">
+            <level max_range="5000"/>
+        </layout>
+        
+        <styles>
+            <style type="text/css">
+                streets {
+                    stroke:                       #ffff7f7f;
+                    stroke-width:                 7.5m;
+                    altitude-clamping:            terrain-gpu;
+                    render-depth-offset-min-bias: 3.6;
+                }
+            </style>
+        </styles>        
+    </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>
+
+        <layout tile_size_factor="5" crop_features="true">
+            <level max_range="1000" style="default"/>
+        </layout>
+        
+        <instancing>true</instancing>
+		<cluster_culling>false</cluster_culling>
+        
+        <styles>
+            <style type="text/css">
+                default {
+                    model:             "../data/streetlight.osgb";
+                    model-script:      positionAlongSegments();
+					model-heading:     feature.properties.heading;
+                    altitude-clamping: terrain-gpu;                    
+                    render-depth-offset:    false;
+                }
+            </style>
+            <script language="javascript">
+				<url>../data/scripts/createLineOffsetPoints.js</url>
+			</script>
+        </styles>   
+    </model>
+    
+    
+    <model name="Parks" driver="feature_geom" enabled="true">
+        <features name="parks" driver="ogr" build_spatial_index="true">
+            <url>../data/boston-parks.shp</url>
+        </features>
+        
+        <layout tile_size_factor="3">
+            <level max_range="2000"/>
+        </layout>
+        
+        <instancing>true</instancing>
+		<cluster_culling>false</cluster_culling>
+        
+        <styles>
+            <style type="text/css">
+                parks {
+                   model:                  "../data/loopix/tree4.osgb.(0.15).scale";
+				   //model-scale:            0.15 + 0.1*Math.random();
+                   model-placement:        random;
+                   model-density:          3000;
+				   model-heading:          Math.random() * 360.0;
+                   altitude-clamping:      terrain-gpu;
+				   render-min-alpha:       0.15;
+                   render-depth-offset:    false;
+                }
+            </style>
+        </styles>        
+    </model>
+    
+    
+    <external>
+        <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"/>
+        <vertical_scale/>
+        <normalmap/>
+    </external>
+  
+</map>
diff --git a/tests/boston.earth b/tests/boston.earth
index 571544b..e294d1a 100644
--- a/tests/boston.earth
+++ b/tests/boston.earth
@@ -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,7 @@ 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">
             <level name="default" max_range="20000" style="buildings"/>
         </layout>
         
diff --git a/tests/boston_buildings.earth b/tests/boston_buildings.earth
index 86cddf6..d75f1be 100644
--- a/tests/boston_buildings.earth
+++ b/tests/boston_buildings.earth
@@ -77,10 +77,12 @@ osgearth_viewer boston_buildings.earth --kml ../data/BostonBuildings.kmz
     </options> 
     
     <external>
-        <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.85" lat="42.36460" long="-71.053612" pitch="-10.1" range="85.034"/>
+        <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.85" lat="42.36460" long="-71.053612" pitch="-10.1" range="85.034"/>\
+        </viewpoints>
         <sky hours="21.0"/>
     </external>
   
diff --git a/tests/boston_projected.earth b/tests/boston_projected.earth
index c097c44..9f52542 100644
--- a/tests/boston_projected.earth
+++ b/tests/boston_projected.earth
@@ -174,7 +174,7 @@ to extruded buildings.
             <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"/>
+        <sky driver="gl" hours="14.0"/>
     </external>
   
 </map>
diff --git a/tests/detail_texture.earth b/tests/detail_texture.earth
deleted file mode 100644
index c95a71c..0000000
--- a/tests/detail_texture.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" version="2">
-    
-    <options>
-        <elevation_tile_size>15</elevation_tile_size>
-        <terrain lighting="true" 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>
-	    <detail_texture>
-			<image>../data/noise3.png</image>
-		</detail_texture>
-	</external>
-    
-</map>
diff --git a/tests/feature_clip_plane.earth b/tests/feature_clip_plane.earth
new file mode 100644
index 0000000..7add5a7
--- /dev/null
+++ b/tests/feature_clip_plane.earth
@@ -0,0 +1,31 @@
+<!--
+osgEarth Sample
+Use this earth file with the osgearth_clipplane example.
+
+This demonstrates how to render geometry on the earth's surface
+with depth testing off and a horizon clip plane. This is one
+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">
+        <features name="world" driver="ogr">
+            <url>../data/world.shp</url>
+        </features>        
+        <styles>
+            <style type="text/css">
+                states {
+                   stroke:            #ffff00;
+				   render-depth-test: false;
+				   render-clip-plane: 0;
+                }                    
+            </style>
+        </styles>        
+    </model>
+  
+</map>
diff --git a/tests/feature_draped_lines.earth b/tests/feature_draped_lines.earth
index f8d392d..529fb51 100644
--- a/tests/feature_draped_lines.earth
+++ b/tests/feature_draped_lines.earth
@@ -23,8 +23,6 @@ i.e. "altitude-clamping: terrain-drape".
             <url>../data/world.shp</url>
             <build_spatial_index>true</build_spatial_index>
         </features>
-		
-		<feature_indexing/>	
                 
         <styles>
             <style type="text/css">
diff --git a/tests/feature_gpx.earth b/tests/feature_gpx.earth
index 3fb0c25..9e52a02 100644
--- a/tests/feature_gpx.earth
+++ b/tests/feature_gpx.earth
@@ -36,7 +36,9 @@ Note:  You must have GDAL built with Expat support to read GPX files
     </model>
 	
 	<external>
-        <viewpoint name="Fells Loop" heading="0" height="0" lat="42.43095" long="-71.1076" pitch="-90" range="5000"/>
+        <viewpoints>
+            <viewpoint name="Fells Loop" heading="0" height="0" lat="42.43095" long="-71.1076" pitch="-90" range="5000"/>
+        </viewpoints>
     </external>
   
 </map>
diff --git a/tests/feature_labels.earth b/tests/feature_labels.earth
index fea8d67..38e2655 100644
--- a/tests/feature_labels.earth
+++ b/tests/feature_labels.earth
@@ -14,24 +14,26 @@ This shows how to label point features with an attribute.
         <features name="cities" driver="ogr">
             <url>../data/ne_cities.shp</url>
         </features>
+        
+        <feature_indexing enabled="true"/>
 
         <styles>
-			<selector class="cities">
-				<query><expr><![CDATA[rank_max > 9]]></expr></query>
-			</selector>
+            <selector class="cities">
+                <query><expr><![CDATA[rank_max > 9]]></expr></query>
+            </selector>
             <style type="text/css">              
                 cities {
-					icon: "../data/placemark32.png";
-					icon-placement: centroid;
-					icon-scale: 1.0;
-					icon-occlusion-cull: false;
-					icon-occlusion-cull-altitude: 8000;
-					icon-declutter: true;
-					text-content: [name];
-					text-priority: [rank_max];
-					altitude-offset: 100;
-					altitude-clamping: terrain;
-					altitude-technique: scene;
+                    icon: "../data/placemark32.png";
+                    icon-placement: centroid;
+                    icon-scale: 1.0;
+                    icon-occlusion-cull: false;
+                    icon-occlusion-cull-altitude: 8000;
+                    icon-declutter: true;
+                    text-content: [name];
+                    text-priority: [rank_max];
+                    altitude-offset: 100;
+                    altitude-clamping: terrain;
+                    altitude-technique: scene;
                 }     
             </style>
         </styles>
diff --git a/tests/feature_model_scatter.earth b/tests/feature_model_scatter.earth
index 7fdc17a..274b674 100644
--- a/tests/feature_model_scatter.earth
+++ b/tests/feature_model_scatter.earth
@@ -26,13 +26,6 @@ is randomized, but it is randomized exactly the same way each time.
         <!-- Instancing enables GL's "DrawInstanced" support. -->             
         <instancing>true</instancing>
         
-        <!-- Disables feature indexing, since the trees are not mapped 1-to-1 to
-             real features anyway. -->
-        <feature_indexing>false</feature_indexing>
-        
-        <!-- Fade in new tiles over one second. -->
-        <!-- <fade_in_duration>1.0</fade_in_duration> -->
-        
         <!-- The stylesheet will describe how to render the feature data. In this case
              we indicate model substitution with density-based scattering. The "density"
              units are instances-per-sqkm. -->
@@ -105,8 +98,10 @@ is randomized, but it is randomized exactly the same way each time.
     </elevation>
     
     <external>
-        <viewpoint name="DC overview" heading="0" height="25.83" lat="38.9015" long="-77.0217" pitch="-89.9" range="28262"/>
-        <viewpoint name="DC close up" heading="-13.48" lat="38.911" long="-77.051" pitch="-10.1" range="2711"/>
+        <viewpoints>
+            <viewpoint name="DC overview" heading="0" height="25.83" lat="38.9015" long="-77.0217" pitch="-89.9" range="28262"/>
+            <viewpoint name="DC close up" heading="-13.48" lat="38.911" long="-77.051" pitch="-10.1" range="2711"/>
+        </viewpoints>
     </external>
   
 </map>
diff --git a/tests/feature_models.earth b/tests/feature_models.earth
index 0269704..ebb0a7a 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.25.scale";
-                   model-scale:         auto;    
+                   model:               "../data/red_flag.osg.2500.scale";
                    altitude-clamping:   terrain;
                 }                                            
             </style>
diff --git a/tests/feature_offset_polygons.earth b/tests/feature_offset_polygons.earth
new file mode 100644
index 0000000..4597ece
--- /dev/null
+++ b/tests/feature_offset_polygons.earth
@@ -0,0 +1,112 @@
+<!--
+osgEarth Sample
+This one demonstrates use of the "terrainshader" extension to use 
+GLSL to affect a polygon offset effect.
+-->
+
+<map name="Feature Overlay Demo" type="geocentric" version="2">
+    
+    <options>
+        <lighting>false</lighting>
+        <overlay_blending>false</overlay_blending>
+        <terrain min_lod="8"/>
+    </options>
+    
+    <extensions>
+        <terrainshader>
+            <description>
+                A GLSL snippet that pushes the terrain back to prevent high-altitude z fighting.
+            </description>          
+            <code>
+                <![CDATA[
+                    #version 110
+                    #pragma vp_entryPoint "dp_vert"        // name of entry point function
+                    #pragma vp_location   "vertex_view"    // where to insert this shader in the pipeline
+                    
+                    varying out float tt;
+                    
+                    void dp_vert(inout vec4 v)
+                    {
+                        const float R    = 6371000.0;      // Approximate radius of the earth
+                        const float dmin = 100000.0;       // Minimum vertex distance at which to apply effect
+                        const float dmax = R;              // Vertex distance at which to apply maximum effect
+                        const float maxOffset = 250000.0;  // Maximum offset (applied at dmax)
+                        
+                        float d = length(v.xyz);
+                        float t = clamp( (d-dmin)/(dmax-dmin), 0.0, 1.0 );
+                        float offset = t*maxOffset;
+                        vec3 n = normalize(v.xyz);
+                        v.xyz = v.xyz + n*offset;
+                        
+                        tt = t;
+                    }
+                ]]>
+            </code>         
+        </terrainshader>
+    </extensions>
+
+    <image name="world" driver="gdal">
+        <url>../data/world.tif</url>
+    </image>
+    
+    <model name="countries" driver="feature_geom">
+                          
+        <features name="states" driver="ogr">
+            <url>../data/world.shp</url>
+            <buffer distance="-0.05"/>
+            <resample max_length="5.0"/>
+        </features>
+        
+        <styles>        
+            <style type="text/css">
+                p1 {
+                   fill:               #ffff8066;
+                }       
+                p2 {
+                   fill:               #80ffff66;
+                }   
+                p3 {
+                   fill:               #ff80ff66;
+                }       
+                p4 {
+                   fill:               #ff808066;
+                }     
+                p5 {
+                   fill:               #80ff8066;
+                }                                      
+            </style>
+        
+            <selector class="p1">
+                <query>
+                    <expr><![CDATA[ POP_CNTRY <= 14045470 ]]></expr>
+                </query>
+            </selector>
+        
+            <selector class="p2">
+                <query>
+                    <expr><![CDATA[ POP_CNTRY > 14045470 and POP_CNTRY <= 43410900 ]]></expr>
+                </query>
+            </selector>
+            
+            <selector class="p3">
+                <query>
+                    <expr><![CDATA[ POP_CNTRY > 43410900 and POP_CNTRY <= 97228750 ]]></expr>
+                </query>
+            </selector>
+            
+            <selector class="p4">
+                <query>
+                    <expr><![CDATA[ POP_CNTRY > 97228750 and POP_CNTRY <= 258833000 ]]></expr>
+                </query>
+            </selector>
+            
+            <selector class="p5">
+                <query>
+                    <expr><![CDATA[ POP_CNTRY > 258833000 ]]></expr>
+                </query>
+            </selector>
+            
+        </styles>
+        
+    </model>
+</map>
diff --git a/tests/feature_overlay.earth b/tests/feature_overlay.earth
index a2293e5..251531b 100644
--- a/tests/feature_overlay.earth
+++ b/tests/feature_overlay.earth
@@ -14,9 +14,9 @@ Drawing simple lines at a set altitude.
         <cache_policy usage="no_cache"/>
     </image>
     
+    
     <model name="world_boundaries" driver="feature_geom">
-
-        <!-- Configure the OGR feature driver to read the shapefile. -->             
+           
         <features name="world" driver="ogr">
             <url>../data/world.shp</url>
             <build_spatial_index>true</build_spatial_index>
@@ -25,10 +25,10 @@ Drawing simple lines at a set altitude.
         <styles>
             <style type="text/css">
                 world {
-                   stroke:             #ffff00;
-                   stroke-opacity:     1.0;
-                   stroke-width:       3.0;
-				   altitude-offset:    1000.0;
+                   stroke:                   #ffff00;
+                   stroke-width:             2.0;
+                   stroke-tessellation-size: 100km;
+                   altitude-offset:          1000.0;
                 }            
             </style>
         </styles>
diff --git a/tests/feature_population_cylinders.earth b/tests/feature_population_cylinders.earth
index 5527b79..c52f406 100644
--- a/tests/feature_population_cylinders.earth
+++ b/tests/feature_population_cylinders.earth
@@ -18,17 +18,17 @@ Shows how to use JavaScript to create geometry parametrically.
         <styles>
             <script>
               <![CDATA[
-			    var min_rank = 12;
+                var min_rank = 12;
                 function makePopCircles() {
-					if (feature.properties.rank_max >= min_rank) {
-						var radius = (feature.properties.rank_max-min_rank+1) * 75000;
-						feature.geometry = feature.geometry.buffer(radius);
-						feature.properties.height = radius*1.5;
-					}
-					else {
-						feature.geometry = null;
-					}
-					feature.save();
+                    if (feature.properties.rank_max >= min_rank) {
+                        var radius = (feature.properties.rank_max-min_rank+1) * 75000;
+                        feature.geometry = feature.geometry.buffer(radius);
+                        feature.properties.height = radius*1.5;
+                    }
+                    else {
+                        feature.geometry = null;
+                    }
+                    feature.save();
                 }
               ]]>
             </script>
@@ -36,43 +36,44 @@ Shows how to use JavaScript to create geometry parametrically.
                 default {
                     fill:              #ff80007f;
                     stroke:            #ffcf7f;
-					stroke-width:      1px;
+                    stroke-width:      1px;
                     altitude-script:   makePopCircles();
-					extrusion-height:  feature.properties.height;
+                    extrusion-height:  feature.properties.height;
+                    render-bin:        DepthSortedBin;
                 }     
             </style>
         </styles>
         
     </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>
-		
-		<layout>
-			<level name="far" style="large"  max_range="1e10"/>
-		</layout>
+        
+        <layout>
+            <level name="far" style="large"  max_range="1e10"/>
+        </layout>
 
         <styles>
-			<selector name="large" class="label-large">
-				<query>
-					<expr> <![CDATA[ rank_max >= 12 ]]> </expr>
-				</query>
-			</selector>
-			
+            <selector name="large" class="label-large">
+                <query>
+                    <expr> <![CDATA[ rank_max >= 12 ]]> </expr>
+                </query>
+            </selector>
+            
             <style type="text/css">              
                 label-large {
-					text-declutter: true;
-					text-content:   [name];
-					text-size:      16.0;
-					text-align:     center_center;
-					text-halo:      #1f1f1f;
-					text-priority:  [rank_max];
-				}
+                    text-declutter: true;
+                    text-content:   [name];
+                    text-size:      16.0;
+                    text-align:     center_center;
+                    text-halo:      #1f1f1f;
+                    text-priority:  [rank_max];
+                }
             </style>
         </styles>
         
diff --git a/tests/feature_tfs.earth b/tests/feature_tfs.earth
index a0939e9..a6f25e7 100644
--- a/tests/feature_tfs.earth
+++ b/tests/feature_tfs.earth
@@ -38,7 +38,9 @@ This example shows how to use the TFS driver.
     
     <external>
         <sky hours="20.0"/>
-        <viewpoint name="Mexico Buildings" height="0" lat="19.42" long="-99.163" pitch="-89" range="5000"/>
+        <viewpoints>
+            <viewpoint name="Mexico Buildings" height="0" lat="19.42" long="-99.163" pitch="-89" range="5000"/>
+        </viewpoints>
     </external>
   
 </map>
diff --git a/tests/feature_tfs_scripting.earth b/tests/feature_tfs_scripting.earth
index f36d38f..1e232fe 100644
--- a/tests/feature_tfs_scripting.earth
+++ b/tests/feature_tfs_scripting.earth
@@ -67,7 +67,9 @@ This example shows how to use the TFS driver.
     
     <external>
         <sky hours="20.0"/>
-        <viewpoint name="Mexico Buildings" height="0" lat="19.42" long="-99.163" pitch="-89" range="5000"/>
+        <viewpoints>
+            <viewpoint name="Mexico Buildings" height="0" lat="19.42" long="-99.163" pitch="-89" range="5000"/>
+        </viewpoints>
     </external>
   
 </map>
diff --git a/tests/fractal_detail.earth b/tests/fractal_detail.earth
deleted file mode 100644
index b0e5d6f..0000000
--- a/tests/fractal_detail.earth
+++ /dev/null
@@ -1,58 +0,0 @@
-<!-- 
-osgEarth Sample - Fractal detail
-
-Uses the noise driver to add fractal detail to the terrain,
-making it appear higher resolution than it really is.
-This can be useful for presentation purposes when you don't
-have very high resolution data.
--->
-
-<map version="2">
-    
-    <!--
-    Start with the ReadyMap elevation as our basis. 
-    -->
-    <elevation name="readymap_elevation" driver="tms" enabled="true">
-        <url>http://readymap.org/readymap/tiles/1.0.0/9/</url>
-    </elevation>
-    
-	
-	<!--
-	Set the frequency to be the reciprocal of your elevation resolution.
-	i.e., if your elevation data is 90m, set freq = 1/90. That will cause
-	the noise function to cycle every 90m. This means you will only get
-	noise between your real elevation values.
-	
-	Within that 90m span, the noise function will generate values between
-	[0..scale]. So if the scale=10, you will get up to a +/-10m variation in
-	elevation at each interpolated point eithin the 90m span.
-	
-	Octaves controls the recursion of this operation. The noise function will
-	run once as normal, then again to further refine the results of the first
-	output, and so on.
-	
-	With each successive octave, the output is scaled like this:
-	freq *= lacunarity, and scale *= persistence. This happens successively for
-	each octave.
-	-->
-    <elevation driver="noise" name="noise" enabled="true" offset="true">
-		<tile_size>31</tile_size>
-		<frequency>0.004</frequency>      <!-- one cycle every 250m -->
-		<lacunarity>2.0</lacunarity>      <!-- freq *= lac for each octave (default = 2) -->
-		<persistence>0.5</persistence>    <!-- amp *= pers for each octave (default = 0.5) -->
-		<octaves>24</octaves>
-		<scale>20</scale>
-		<bias>0</bias>
-	</elevation>
-	
-
-    <options>
-       <elevation_tile_size>31</elevation_tile_size>
-	   <elevation_interpolation>bilinear</elevation_interpolation>
-	   <terrain min_lod="16"/>
-    </options>
-
-    <external>
-        <contour_map/>
-    </external>
-</map>
diff --git a/tests/glsl.earth b/tests/glsl.earth
new file mode 100644
index 0000000..33cf528
--- /dev/null
+++ b/tests/glsl.earth
@@ -0,0 +1,28 @@
+<!-- 
+osgEarth Sample - GLSL in the Earth File.
+-->
+
+<map>
+    <image driver="gdal" name="world-tiff">
+        <url>../data/world.tif</url>
+        <caching_policy usage="no_cache"/>
+    </image>
+    
+    <extensions>
+        <terrainshader>
+            <code><![CDATA[
+            
+                #version 110
+                #pragma vp_entryPoint "adjustGamma"
+                #pragma vp_location   "fragment_coloring"
+                
+                void adjustGamma(inout vec4 color)
+                {
+                    const float gamma = 1.3;
+                    color.rgb = pow(color.rgb, 1.0/vec3(gamma));
+                }
+                
+            ]]></code>
+        </terrainshader>
+    </extensions>
+</map>
\ No newline at end of file
diff --git a/tests/graticule.earth b/tests/graticule.earth
new file mode 100644
index 0000000..2ed6d51
--- /dev/null
+++ b/tests/graticule.earth
@@ -0,0 +1,41 @@
+<!--
+osgEarth Sample - Graticule Extension.
+
+-->
+<map name="Graticule demo" type="geocentric">
+    
+    <options>
+        <terrain first_lod="1"/>
+    </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/9/</url>
+    </elevation>
+    
+    <extensions>
+        <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>
+    </extensions>
+</map>
diff --git a/tests/ldb.earth b/tests/ldb.earth
new file mode 100644
index 0000000..32ffd2b
--- /dev/null
+++ b/tests/ldb.earth
@@ -0,0 +1,28 @@
+<!--
+osgEarth Sample - Logrithmic depth buffer.
+
+Enable the logrithmic depth buffer by running osgearth_viewer with --logdepth
+-->
+<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>
+    
+    <external>
+        <viewpoints>
+            <viewpoint name="Plane" heading="0" height="-1526.619580102153" lat="0.7502917292180486" long="-90.00003984212466" pitch="-88.99970818676897" range="19080841.37590883"/>
+            <viewpoint name="Plane2" heading="9.977198612994472e-011" height="-1493.142864811234" lat="0.750286505217539" long="-90.00003984212442" pitch="-88.99958540266123" range="19003681.6859768"/>
+        </viewpoints>
+        
+        <annotations>            
+            <model name="flag model">
+                <url>../data/cessna.osgb.100.scale.(-90,-45,0).rot</url>
+                <position lat="0" long="-90" hat="19000000"/>
+                <!-- <position lat="33" long="-120" hat="1000000"/> -->
+            </model>            
+        </annotations>
+        
+        <sky driver="gl"/>
+    </external>
+</map>
diff --git a/tests/min_max_range.earth b/tests/min_max_range.earth
index 10e1650..7ea1cd6 100644
--- a/tests/min_max_range.earth
+++ b/tests/min_max_range.earth
@@ -10,11 +10,13 @@ TIP: set your OSG_NUM_HTTP_DATABASE_THREADS to 4 or more!
 <map name="MapQuest Open Aerial" type="geocentric" version="2">
  
     <image name="mapquest_open_aerial" driver="xyz">
-        <url>http://oatile[1234].mqcdn.com/naip/{z}/{x}/{y}.jpg</url>
+        <url>http://oatile[1234].mqcdn.com/tiles/1.0.0/sat/{z}/{x}/{y}.jpg</url>
         <profile>spherical-mercator</profile>
+        <cache_policy usage="no_cache"/>
+        <nodata_image>http://oatile3.mqcdn.com/tiles/1.0.0/sat/13/636/6210.jpg</nodata_image>
     </image>
-    
-    <image name="mapquest_osm" driver="xyz" max_range="5e6" min_range="1e6">
+	
+    <image name="mapquest_osm" driver="xyz" max_range="1e7" min_range="1e6">
         <url>http://otile[1234].mqcdn.com/tiles/1.0.0/osm/{z}/{x}/{y}.jpg</url>
         <profile>global-mercator</profile>
     </image>
diff --git a/tests/night.earth b/tests/night.earth
new file mode 100644
index 0000000..e8aca45
--- /dev/null
+++ b/tests/night.earth
@@ -0,0 +1,27 @@
+<!--
+osgEarth Sample
+
+Demonstrates the use of the night color filter, which only shows a layer in nighttime.  You need to have the sky activated for this shader to work.
+-->
+<map name="readymap.org" type="geocentric">
+    
+    <options>
+        <terrain first_lod="1" normalize_edges="true"/>
+    </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>
+        <color_filters>
+        	<night/>
+        </color_filters>
+    </image>      
+
+    <external>
+        <sky driver="simple" hours="14.0" ambient="1.0"/>
+    </external>
+    
+</map>
diff --git a/tests/nodata.earth b/tests/nodata.earth
index c831791..5b50495 100644
--- a/tests/nodata.earth
+++ b/tests/nodata.earth
@@ -15,4 +15,10 @@ Demonstrates the use of a file with nodata.  The white circle is a GeoTiff which
     </image>
     
     <options lighting="false"/>
+
+     <external>
+        <viewpoints>
+            <viewpoint name="NoData" heading="0.0" height="0" lat="5.0" long="15.0" pitch="-90" range="1e6"/>
+        </viewpoints>
+    </external>
 </map>
diff --git a/tests/noise.earth b/tests/noise.earth
index 9b4ad47..9078d68 100644
--- a/tests/noise.earth
+++ b/tests/noise.earth
@@ -1,28 +1,73 @@
 <!-- 
-osgEarth Sample - Noise Driver
-
-Demonstrates the use of libnoise to procedurally generate a
-fictional fractal landscape.
-
-We use a contour map to better visualize the terrain.
+osgEarth Sample
 -->
 
 <map version="2">
 
-    <elevation driver="noise" name="noisy_terrain"
-               frequency  ="1" 
-               octaves    ="20"
-               persistence="0.53"
-			   lacunarity ="2.1"
-               scale      ="6000" />
-               
-
-    <options
-        lighting="true"
-		elevation_interpolation="bilinear"/>
-
+    <elevation name="readymap_elevation" driver="tms">
+        <url>http://readymap.org/readymap/tiles/1.0.0/116/</url>
+    </elevation>
+    
     <external>
+        <noise/>
         <contour_map/>
+        <normal_map/>
         <sky 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>
     </external>
 </map>
diff --git a/tests/normalmap.earth b/tests/normalmap.earth
index a2d82df..c037d78 100644
--- a/tests/normalmap.earth
+++ b/tests/normalmap.earth
@@ -1,43 +1,22 @@
-<!-- 
-osgEarth Sample - Noise Driver
-Demonstrates the application of a normal map to add terrain detail.
+<!--
+osgEarth Sample - Shows an elevation-derived normal map.
 -->
-
-<map>
-    <options elevation_tile_size="15">
-        <terrain min_level="20" normalize_edges="true"/>
+<map name="readymap.org" type="geocentric">
+    
+    <options>
+        <terrain first_lod="1"/>
     </options>
-
-    <image name="readymap_imagery" driver="tms" visible="true">
+    
+    <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>
+        <url>http://readymap.org/readymap/tiles/1.0.0/116/</url>
     </elevation>
-               
-    <!--
-    The normal map layer using the 'noise' driver to generate bump map textures.
-    Note that we set visible='false' since we don't actually want to display this
-    layer in its raw form - rather, the shared='true' will make it available to
-    the <normal_map> effect below.
-    -->
-    <image name="normalmap" driver="noise" shared="true" visible="false"
-           tile_size="64"
-           normal_map="true"
-           frequency=".001"
-           persistence="0.45"
-           lacunarity="3.0"
-           octaves="6"
-           scale="15" >
-    </image>
-           
-    <!--
-    Install the normal map terrain effect and point it at our normal map layer.
-    -->
-    <external>
-        <normal_map layer="normalmap"/>
-        <contour_map/>
-        <sky hours="16.0"/>
-    </external>
+    
+    <extensions>
+        <normalmap/>
+        <sky/>
+    </extensions>
 </map>
diff --git a/tests/ocean.earth b/tests/ocean.earth
index 4235087..612b1ac 100644
--- a/tests/ocean.earth
+++ b/tests/ocean.earth
@@ -16,43 +16,41 @@ http://readymap.org
     <image name="ReadyMap.org - Imagery" driver="tms">
         <url>http://readymap.org/readymap/tiles/1.0.0/7/</url>
     </image>
-
-    <image name="ReadyMap.org - Street Map" driver="tms">
-        <url>http://readymap.org/readymap/tiles/1.0.0/35/</url>
-    </image>
         
     <elevation name="ReadyMap.org - Elevation" driver="tms">
         <url>http://readymap.org/readymap/tiles/1.0.0/9/</url>
     </elevation>
     
-    <options>
-        <terrain>
-            <lighting>true</lighting>
-        </terrain>
-    </options>
     
-    <external>
-        <sky/>
+    <extensions>
         
         <!-- Ocean parameters. -->
         <ocean>
         
             <!-- Surface texture to use -->
+            <!--
             <texture_url>../data/watersurface1.png</texture_url>
+            -->
             
             <!-- 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 (before texturing) -->
             <base_color>#334f7fbf</base_color>
+            
+            <max_altitude>20000</max_altitude>
+            
         </ocean>
         
-        <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"/>
-        <viewpoint name="Below water" heading="-24.96310172368127" height="-1300.000076910481" lat="33.27360337088133" long="-118.5514448058582" pitch="-10.0770016631354" range="6375.084038302656"/>
-    </external>
+        <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"/>
+            <viewpoint name="Below water" heading="-24.96310172368127" height="-1300.000076910481" lat="33.27360337088133" long="-118.5514448058582" pitch="-10.0770016631354" range="6375.084038302656"/>
+        </viewpoints>
+        
+    </extensions>
 </map>
diff --git a/tests/openweathermap_clouds.earth b/tests/openweathermap_clouds.earth
new file mode 100644
index 0000000..edde2c9
--- /dev/null
+++ b/tests/openweathermap_clouds.earth
@@ -0,0 +1,30 @@
+<!--
+osgEarth Sample - OpenWeatherMap Clouds
+
+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/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>
+
+</map>
diff --git a/tests/openweathermap_precipitation.earth b/tests/openweathermap_precipitation.earth
new file mode 100644
index 0000000..83cef88
--- /dev/null
+++ b/tests/openweathermap_precipitation.earth
@@ -0,0 +1,31 @@
+<!--
+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
new file mode 100644
index 0000000..797be7a
--- /dev/null
+++ b/tests/openweathermap_pressure.earth
@@ -0,0 +1,31 @@
+<!--
+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-osm.earth b/tests/readymap-osm.earth
index d30d587..35378fe 100644
--- a/tests/readymap-osm.earth
+++ b/tests/readymap-osm.earth
@@ -18,21 +18,10 @@ http://readymap.org
     </image>
 
     <image name="readymap_streets" driver="tms">
-        <url>http://readymap.org/readymap/tiles/1.0.0/35/</url>
+        <url>http://readymap.org/readymap/tiles/1.0.0/119/</url>
     </image>
-        
-    <elevation name="readymap_elevation" driver="tms">
-        <url>http://readymap.org/readymap/tiles/1.0.0/9/</url>
-    </elevation>
     
-    <options>
-        <terrain>
-            <lighting>false</lighting>
-            <sample_ratio>0.125</sample_ratio>
-        </terrain>
-    </options>
-    
-    <external>
+    <extensions>
         <viewpoint name="Los Angeles" heading="35.27" height="97.48" lat="34.051" long="-117.974" pitch="-17" range="136405"/>
-    </external>
+    </extensions>
 </map>
diff --git a/tests/readymap.earth b/tests/readymap.earth
index fd376cd..1275dda 100644
--- a/tests/readymap.earth
+++ b/tests/readymap.earth
@@ -14,7 +14,7 @@ http://readymap.org
 <map name="readymap.org" type="geocentric">
     
     <options>
-        <terrain first_lod="1"/>
+        <terrain first_lod="1" normalize_edges="true"/>
     </options>
     
     <image name="readymap_imagery" driver="tms">
@@ -22,6 +22,6 @@ http://readymap.org
     </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>
 </map>
diff --git a/tests/silverlining.earth b/tests/silverlining.earth
index 5141e60..de4c3bf 100644
--- a/tests/silverlining.earth
+++ b/tests/silverlining.earth
@@ -4,25 +4,24 @@ http://sundog-soft.com/sds/features/real-time-3d-clouds/
 -->
 <map name="readymap.org" type="geocentric" version="2">
     
-    <options>
-        <elevation_tile_size>7</elevation_tile_size>
-        <terrain first_lod="1"/>
-    </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>
 
-    <external>
+    <extensions>
         <sky driver="silverlining">
 			<user></user>
 			<license_code></license_code>
 			<clouds>true</clouds>
 			<clouds_max_altitude>100000</clouds_max_altitude>
 		</sky>
-    </external>
+        
+        <viewpoints>
+            <viewpoint name="Clouds" heading="37.95748484316797" height="251.618257785216" lat="46.5107100574977" long="-122.1696677949447" pitch="-8.543275459971998" range="6807.567220645097"/>
+        </viewpoints>
+    </extensions>
 </map>
diff --git a/tests/simple_model.earth b/tests/simple_model.earth
index 35e9ea0..891ad00 100644
--- a/tests/simple_model.earth
+++ b/tests/simple_model.earth
@@ -22,6 +22,8 @@ needs to be absolutely positioned.
     </options>
     
     <external>
-        <viewpoint name="Zoom to model" lat="40.717" long="-74.018" pitch="-60" range="6000"/>    
+        <viewpoints>
+            <viewpoint name="Zoom to model" lat="40.717" long="-74.018" pitch="-60" range="6000"/>    
+        </viewpoints>
     </external>	
 </map>
diff --git a/tests/splat-edit.bat b/tests/splat-edit.bat
new file mode 100644
index 0000000..3f42098
--- /dev/null
+++ b/tests/splat-edit.bat
@@ -0,0 +1,21 @@
+setlocal
+set OSGEARTH_SPLAT_EDIT=1
+osgearth_viewer splat-test.earth ^
+	--ico ^
+	--logdepth ^
+	--sky ^
+	--uniform oe_splat_detailRange 100000 0 ^
+	--uniform oe_splat_warp 0 0.02 ^
+	--uniform oe_splat_scaleOffset 0 7 ^
+	--uniform oe_splat_useBilinear 1 -1 ^
+	--uniform oe_splat_noiseScale 1 21 ^
+	--uniform oe_splat_contrast 1 4 ^
+	--uniform oe_splat_brightness 1 10 ^
+	--uniform oe_splat_threshold 0 1 ^
+	--uniform oe_splat_minSlope 0 0.5 ^
+	--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
diff --git a/tests/splat-gpunoise.bat b/tests/splat-gpunoise.bat
new file mode 100644
index 0000000..9944b10
--- /dev/null
+++ b/tests/splat-gpunoise.bat
@@ -0,0 +1,31 @@
+setlocal
+set OSGEARTH_SPLAT_EDIT=1
+set OSGEARTH_SPLAT_GPU_NOISE=1
+osgearth_viewer splat-test.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-test.earth b/tests/splat-test.earth
new file mode 100644
index 0000000..c4ca0ef
--- /dev/null
+++ b/tests/splat-test.earth
@@ -0,0 +1,57 @@
+<!--
+|  Texture splatting test.
+|
+|  Run with splat.bat, (or splat-edit.bat for tweakery)
+-->
+
+<map>
+        
+    <options>
+        <terrain max_lod="18" min_normal_map_lod="7"/>
+    </options>
+    
+    <elevation name="readymap_elevation" driver="tms" enabled="true">
+        <url>http://readymap.org/readymap/tiles/1.0.0/116/</url>
+    </elevation>
+    
+    <elevation driver="gdal" enabled="false">
+        <url>H:/data/ned/ned19_n47x00_w121x75_wa_mounttrainier_2008/ned19_n47x00_w121x75_wa_mounttrainier_2008.tif</url>
+    </elevation>
+    
+    <image name="GLOBCOVER" driver="gdal" shared="true" visible="false" coverage="true" enabled="true">
+        <url>H:/data/ESA/GLOBCOVER_L4_200901_200912_V2.3_Ant_tiled.tif</url>
+        <cache_policy usage="none"/>
+    </image>
+
+    <extensions>
+        
+        <normalmap/>    
+
+        <bumpmap>
+            <image>../data/rock_hard.jpg</image>
+            <octaves>32</octaves>
+            <intensity>1.5</intensity>
+        </bumpmap>
+    
+        <splat>
+            <coverage>GLOBCOVER</coverage>
+            <legend>../data/splat/GLOBCOVER_legend.xml</legend>
+            <catalog>../data/splat/splat_catalog.xml</catalog>
+        </splat>
+        
+        <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>
+        
+        <sky driver="simple" hours="18.6" atmospheric_lighting="true"/>
+        
+    </extensions>
+
+</map>
diff --git a/tests/splat-with-imagery.earth b/tests/splat-with-imagery.earth
new file mode 100644
index 0000000..cf8cdbb
--- /dev/null
+++ b/tests/splat-with-imagery.earth
@@ -0,0 +1,52 @@
+<!--
+|  Combining a normal image layer with the Splat effect.
+|  You can tweak the distances by using command-line uniforms:
+|
+|    --uniform oe_layer_minRange <min> <max>
+|    --uniform oe_layer_maxRange <min> <max>
+|    --uniform oe_layer_attenuationRange <range>
+-->
+
+<map>
+
+    <options>
+        <terrain attenuation_distance="40000"/>
+    </options>
+    
+    <image name="readymap_imagery" driver="tms" min_range="65000">
+        <url>http://readymap.org/readymap/tiles/1.0.0/7/</url>
+    </image>
+    
+    <elevation name="readymap_elevation" driver="tms" enabled="true">
+        <url>http://readymap.org/readymap/tiles/1.0.0/116/</url>
+    </elevation>
+    
+    <image name="GLOBCOVER" driver="gdal" shared="true" visible="false" coverage="true" enabled="true">
+        <url>H:/data/ESA/GLOBCOVER_L4_200901_200912_V2.3_Ant_tiled.tif</url>
+        <cache_policy usage="none"/>
+    </image>
+
+    <extensions>
+        
+        <normalmap/>  
+    
+        <splat>
+            <coverage>GLOBCOVER</coverage>
+            <legend>../data/splat/GLOBCOVER_legend.xml</legend>
+            <catalog>../data/splat/splat_catalog.xml</catalog>
+        </splat>
+        
+        <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>
+        
+    </extensions>
+
+</map>
diff --git a/tests/splat.bat b/tests/splat.bat
new file mode 100644
index 0000000..e82fa4c
--- /dev/null
+++ b/tests/splat.bat
@@ -0,0 +1,7 @@
+osgearth_viewer splat-test.earth ^
+	--ico ^
+	--uniform oe_splat_warp 0 0.02 ^
+	--uniform oe_splat_blur 1 4 ^
+	--uniform oe_splat_useBilinear 1 -1 ^
+	%*
+	
\ No newline at end of file
diff --git a/tests/splat.earth b/tests/splat.earth
deleted file mode 100644
index cb6a774..0000000
--- a/tests/splat.earth
+++ /dev/null
@@ -1,118 +0,0 @@
-<!-- 
-osgEarth Sample - Detail Texturing
--->
-
-<map>
-
-    <options elevation_interpolation="bilinear">
-        <terrain min_lod="19" max_lod="19" min_tile_range_factor="7"/>
-    </options>
-    
-    <image name="readymap_imagery" driver="tms" visible="true" opacity="1">
-        <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>
-    
-    <image driver="splat_mask" name="splat" shared="true" visible="false"
-           min_level     ="10" 
-           tile_size     ="5"
-           contrast      ="4.0">
-		<classification_path>
-			H:/data/esa/GLOBCOVER_L4_200901_200912_V2.3_tiled.tif
-		</classification_path>
-    </image>
-               
-    <image name="normalmap" driver="noise" shared="true" visible="false"
-           start_lod  ="10"
-           tile_size  ="64"
-           normal_map ="true"
-           frequency  =".001"
-           persistence="0.45"
-           lacunarity ="3.0"
-           octaves    ="6"
-           scale      ="15" >
-    </image> 
-
-    <external>
-        <detail_texture mask_layer="splat"
-                        start_lod ="10"
-                        scale     ="4"
-                        intensity ="0.9"
-                        octaves   ="3"
-                        attenuation_distance="20000">
-            <textures>
-                <texture name="rock"  url="../data/textures/seamless/rock.jpg"/>
-                <texture name="dirt"  url="../data/textures/seamless/dirt5.jpg"/>
-                <texture name="grass" url="../data/textures/seamless/grass.jpg"/>
-                <texture name="water" url="../data/textures/seamless/water.jpg"/>
-            </textures>
-        </detail_texture>
-        
-		<!--
-        <normal_map layer="normalmap"/>
-		-->
-		        
-        <viewpoints>
-            <viewpoint name="one" heading="15" height="4093" lat="46.8545" long="-121.7652" pitch="-29" range="41747"/>
-            <viewpoint name="two" heading="-145.5717976089649" height="1877.697350924835" lat="46.8725220319367" long="-121.6904520329511" pitch="-6.060576722588542" range="4608.703411154525"/>
-            <viewpoint name="three" heading="49.16286972143863" height="1596.743544967845" lat="46.79259290581516" long="-121.7574053228272" pitch="-5.905240611358435" range="2449.290556889881"/>
-        </viewpoints>
-    </external>
-    
-    
-    <model name="trees" driver="feature_geom" enabled="true">
-        <features name="trees" driver="ogr">
-			<geometry>
-				POLYGON((-121.82 46.833, -121.82 46.754, -121.63 46.754, -121.63 46.836))
-			</geometry>
-        </features>
-        
-        <instancing>true</instancing>
-        
-        <fading 
-            duration="1.0"
-            max_range="12000"
-            attenuation_distance="11000"/>
-        
-        <layout>
-            <tile_size_factor>3.5</tile_size_factor>
-            <crop_features>true</crop_features>
-            <level max_range="12000" style="parks-0"/>
-            <level max_range="3000" style="parks-2"/>
-            <level max_range="1000" style="parks-1"/>
-        </layout>
-             
-        <styles>
-            <style type="text/css">
-                parks-0 {
-                   model:               "../data/pinetree.ive";
-                   model-placement:     random;
-                   model-density:       50;
-                   model-scale:         24.0;
-                   altitude-clamping:   terrain;
-                   altitude-resolution: 0.001;
-                }            
-                parks-1 {
-                   model:               "../data/tree.ive";
-                   model-placement:     random;
-                   model-density:       650;
-                   model-scale:         3.0;
-                   altitude-clamping:   terrain;
-                   altitude-resolution: 0.001;
-                }            
-                parks-2 {
-                   model:               "../data/pinetree.ive";
-                   model-placement:     random;
-                   model-density:       350;
-                   model-scale:         20.5;
-                   model-random-seed:   1;
-                   altitude-clamping:   terrain;
-                   altitude-resolution: 0.001;
-                }              
-            </style>
-        </styles>        
-    </model>
-</map>
diff --git a/tests/triton.earth b/tests/triton.earth
index 4de7895..2f7e698 100644
--- a/tests/triton.earth
+++ b/tests/triton.earth
@@ -5,8 +5,7 @@ http://sundog-soft.com/sds/features/real-time-3d-clouds/
 <map name="readymap.org" type="geocentric" version="2">
     
     <options>
-        <elevation_tile_size>7</elevation_tile_size>
-        <terrain first_lod="1"/>
+        <terrain first_lod="1" tile_size="7"/>
     </options>
     
     <image name="readymap_imagery" driver="tms" visible="true">
@@ -17,20 +16,28 @@ http://sundog-soft.com/sds/features/real-time-3d-clouds/
         <url>http://readymap.org/readymap/tiles/1.0.0/9/</url>
     </elevation>
 
-    <external>
+    <extensions>
+    
         <ocean driver="triton"
                user="my_user_name"
                license_code="my_license_code"
-			   max_altitude="30000"/>
-        <sky hours="18"/>
+			   max_altitude="20000"/>
+               
+        <sky hours="18" driver="gl"/>
+        
         <viewpoints>
-            <viewpoint name="Click to see the water by Maui" heading="-29" height="-204" lat="20.6714" long="-156.5384" pitch="-4" range="3270"/>
+            <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="Horizon"  heading="-27.1911" height="-206.3652788307518" lat="20.69785423327782" long="-156.5550697849549" pitch="-16.0293" range="68323m"/>
         </viewpoints>
+        
 		<annotations>
 			<model name="Object in the water">
 				<url>../data/red_flag.osg.40.scale</url>
 				<position lat="20.6714" long="-156.5384" alt="0"/>
 			</model>
 		</annotations>
-    </external>
+        
+    </extensions>
 </map>
diff --git a/tests/wms_jpl_landsat.earth b/tests/wms_jpl_landsat.earth
deleted file mode 100644
index 7fe9099..0000000
--- a/tests/wms_jpl_landsat.earth
+++ /dev/null
@@ -1,18 +0,0 @@
-<!-- 
-osgEarth Sample - WMS Driver
-Demonstrates the WMS Driver connecting to the JPL landsat tiled WMS using their TileService spec.
--->
-
-<map version="2">
-	
-	<image name="Landsat" driver="wms">
-        <url>http://onearth.jpl.nasa.gov/wms.cgi</url>
-        <srs>EPSG:4326</srs>
-        <tile_size>512</tile_size>
-        <layers>global_mosaic</layers>
-        <styles>visual</styles>
-        <format>jpeg</format>
-    </image>  
-    
-    <options lighting="false"/>
-</map>

-- 
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